Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-empty-string-enum-members.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"openapi-typescript": patch
---

Generate valid TypeScript enum members for empty string enum values when using `--enum`.
30 changes: 17 additions & 13 deletions packages/openapi-typescript/src/lib/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,20 +408,24 @@ function sanitizeMemberName(name: string) {
export function tsEnumMember(value: string | number, metadata: { name?: string; description?: string | null } = {}) {
let name = metadata.name ?? String(value);
if (!JS_PROPERTY_INDEX_RE.test(name)) {
if (Number(name[0]) >= 0) {
name = `Value${name}`.replace(".", "_"); // don't forged decimals;
} else if (name[0] === "-") {
name = `ValueMinus${name.slice(1)}`;
}
if (name === "") {
name = `"${name}"`;
} else {
if (Number(name[0]) >= 0) {
name = `Value${name}`.replace(".", "_"); // don't forged decimals;
} else if (name[0] === "-") {
name = `ValueMinus${name.slice(1)}`;
}

const invalidCharMatch = name.match(JS_PROPERTY_INDEX_INVALID_CHARS_RE);
if (invalidCharMatch) {
if (invalidCharMatch[0] === name) {
name = `"${name}"`;
} else {
name = name.replace(JS_PROPERTY_INDEX_INVALID_CHARS_RE, (s) => {
return s in SPECIAL_CHARACTER_MAP ? SPECIAL_CHARACTER_MAP[s] : "_";
});
const invalidCharMatch = name.match(JS_PROPERTY_INDEX_INVALID_CHARS_RE);
if (invalidCharMatch) {
if (invalidCharMatch[0] === name) {
name = `"${name}"`;
} else {
name = name.replace(JS_PROPERTY_INDEX_INVALID_CHARS_RE, (s) => {
return s in SPECIAL_CHARACTER_MAP ? SPECIAL_CHARACTER_MAP[s] : "_";
});
}
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions packages/openapi-typescript/test/lib/ts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,14 @@ describe("tsEnum", () => {
}`);
});

test("empty string member", () => {
expect(astToString(tsEnum("type", ["", "foo", "bar"])).trim()).toBe(`enum Type {
"" = "",
foo = "foo",
bar = "bar"
}`);
});

test("number members", () => {
expect(astToString(tsEnum(".Error.code.", [100, 101, 102, -100])).trim()).toBe(`enum ErrorCode {
Value100 = 100,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,62 @@ export type operations = Record<string, never>;`,
options: { ctx: createTestContext({ enum: true, conditionalEnums: true }) },
},
],
[
"options > enum: true with empty string enum member",
{
given: mockEmptyStringEnumSchema(),
want: `export interface paths {
"/test": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Test */
get: operations["test"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
}
export type webhooks = Record<string, never>;
export type components = Record<string, never>;
export type $defs = Record<string, never>;
export interface operations {
test: {
parameters: {
query?: {
type?: PathsTestGetParametersQueryType;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
}
export enum PathsTestGetParametersQueryType {
"" = "",
foo = "foo",
bar = "bar"
}`,
options: { ctx: createTestContext({ enum: true }) },
},
],
];

describe("transformComponentsObject", () => {
Expand Down Expand Up @@ -324,6 +380,42 @@ function mockSchema() {
};
}

function mockEmptyStringEnumSchema() {
return {
openapi: "3.1.0",
info: {
title: "Test",
version: "0.1.0",
},
paths: {
"/test": {
get: {
summary: "Test",
operationId: "test",
parameters: [
{
name: "type",
in: "query",
required: false,
schema: {
enum: ["", "foo", "bar"],
type: "string",
default: "",
title: "Type",
},
},
],
responses: {
200: {
description: "OK",
},
},
},
},
},
};
}

function createTestContext(overrides: Partial<typeof DEFAULT_CTX> = {}) {
return {
...DEFAULT_CTX,
Expand Down
Loading