diff --git a/.changeset/nice-lemons-care.md b/.changeset/nice-lemons-care.md new file mode 100644 index 0000000000..9939016e9e --- /dev/null +++ b/.changeset/nice-lemons-care.md @@ -0,0 +1,5 @@ +--- +"@hey-api/openapi-ts": patch +--- + +fix: escape schema names diff --git a/packages/openapi-ts/src/openApi/common/parser/__tests__/sanitize.spec.ts b/packages/openapi-ts/src/openApi/common/parser/__tests__/sanitize.spec.ts index 85c4afa737..7db116d5b0 100644 --- a/packages/openapi-ts/src/openApi/common/parser/__tests__/sanitize.spec.ts +++ b/packages/openapi-ts/src/openApi/common/parser/__tests__/sanitize.spec.ts @@ -1,6 +1,10 @@ import { describe, expect, it } from 'vitest'; -import { sanitizeNamespaceIdentifier, sanitizeOperationParameterName, sanitizeTypeName } from '../sanitize'; +import { + ensureValidTypeScriptJavaScriptIdentifier, + sanitizeNamespaceIdentifier, + sanitizeOperationParameterName, +} from '../sanitize'; describe('sanitizeOperationParameterName', () => { it.each([ @@ -26,13 +30,13 @@ describe('sanitizeNamespaceIdentifier', () => { }); }); -describe('sanitizeTypeName', () => { +describe('ensureValidTypeScriptJavaScriptIdentifier', () => { it.each([ { expected: 'abc', input: 'abc' }, { expected: 'æbc', input: 'æbc' }, { expected: 'æb_c', input: 'æb.c' }, { expected: 'æb_c', input: '1æb.c' }, - ])('sanitizeTypeName($input) -> $expected', ({ input, expected }) => { - expect(sanitizeTypeName(input)).toEqual(expected); + ])('ensureValidTypeScriptJavaScriptIdentifier($input) -> $expected', ({ input, expected }) => { + expect(ensureValidTypeScriptJavaScriptIdentifier(input)).toEqual(expected); }); }); diff --git a/packages/openapi-ts/src/openApi/common/parser/sanitize.ts b/packages/openapi-ts/src/openApi/common/parser/sanitize.ts index 76878c0351..f0f779d65e 100644 --- a/packages/openapi-ts/src/openApi/common/parser/sanitize.ts +++ b/packages/openapi-ts/src/openApi/common/parser/sanitize.ts @@ -1,16 +1,16 @@ /** - * Sanitizes names of types, so they are valid typescript identifiers of a certain form. + * Sanitizes names of types, so they are valid TypeScript identifiers of a certain form. * - * 1: Remove any leading characters that are illegal as starting character of a typescript identifier. + * 1: Remove any leading characters that are illegal as starting character of a TypeScript identifier. * 2: Replace illegal characters in remaining part of type name with underscore (_). * * Step 1 should perhaps instead also replace illegal characters with underscore, or prefix with it, like sanitizeEnumName * does. The way this is now one could perhaps end up removing all characters, if all are illegal start characters. It * would be sort of a breaking change to do so, though, previously generated code might change then. * - * Javascript identifier regexp pattern retrieved from https://developer.mozilla.org/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers + * JavaScript identifier regexp pattern retrieved from https://developer.mozilla.org/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers */ -export const sanitizeTypeName = (name: string) => +export const ensureValidTypeScriptJavaScriptIdentifier = (name: string) => name.replace(/^[^$_\p{ID_Start}]+/u, '').replace(/[^$\u200c\u200d\p{ID_Continue}]/gu, '_'); /** diff --git a/packages/openapi-ts/src/openApi/common/parser/type.ts b/packages/openapi-ts/src/openApi/common/parser/type.ts index cfd44daca5..bdc5e3e763 100644 --- a/packages/openapi-ts/src/openApi/common/parser/type.ts +++ b/packages/openapi-ts/src/openApi/common/parser/type.ts @@ -1,5 +1,5 @@ import type { Type } from '../interfaces/Type'; -import { sanitizeTypeName } from './sanitize'; +import { ensureValidTypeScriptJavaScriptIdentifier } from './sanitize'; import { stripNamespace } from './stripNamespace'; /** @@ -82,8 +82,8 @@ export const getType = (type: string | string[] = 'unknown', format?: string): T if (/\[.*\]$/g.test(typeWithoutNamespace)) { const matches = typeWithoutNamespace.match(/(.*?)\[(.*)\]$/); if (matches?.length) { - const match1 = getType(sanitizeTypeName(matches[1])); - const match2 = getType(sanitizeTypeName(matches[2])); + const match1 = getType(ensureValidTypeScriptJavaScriptIdentifier(matches[1])); + const match2 = getType(ensureValidTypeScriptJavaScriptIdentifier(matches[2])); if (match1.type === 'unknown[]') { result.type = `${match2.type}[]`; @@ -107,7 +107,7 @@ export const getType = (type: string | string[] = 'unknown', format?: string): T } if (typeWithoutNamespace) { - const encodedType = sanitizeTypeName(typeWithoutNamespace); + const encodedType = ensureValidTypeScriptJavaScriptIdentifier(typeWithoutNamespace); result.type = encodedType; result.base = encodedType; if (type.startsWith('#')) { diff --git a/packages/openapi-ts/src/utils/write/schemas.ts b/packages/openapi-ts/src/utils/write/schemas.ts index 1c371bcdd2..6169809c84 100644 --- a/packages/openapi-ts/src/utils/write/schemas.ts +++ b/packages/openapi-ts/src/utils/write/schemas.ts @@ -2,6 +2,7 @@ import path from 'node:path'; import { compiler, TypeScriptFile } from '../../compiler'; import type { OpenApi } from '../../openApi'; +import { ensureValidTypeScriptJavaScriptIdentifier } from '../../openApi/common/parser/sanitize'; /** * Generate Schemas using the Handlebar template and write to disk. @@ -12,8 +13,9 @@ export const writeClientSchemas = async (openApi: OpenApi, outputPath: string): const file = new TypeScriptFile(); const addSchema = (name: string, obj: any) => { + const validName = `$${ensureValidTypeScriptJavaScriptIdentifier(name)}`; const expression = compiler.types.object(obj); - const statement = compiler.export.asConst(name, expression); + const statement = compiler.export.asConst(validName, expression); file.add(statement); }; @@ -22,7 +24,7 @@ export const writeClientSchemas = async (openApi: OpenApi, outputPath: string): for (const name in openApi.definitions) { if (openApi.definitions.hasOwnProperty(name)) { const definition = openApi.definitions[name]; - addSchema(`$${name}`, definition); + addSchema(name, definition); } } } @@ -33,13 +35,13 @@ export const writeClientSchemas = async (openApi: OpenApi, outputPath: string): for (const name in openApi.components.schemas) { if (openApi.components.schemas.hasOwnProperty(name)) { const schema = openApi.components.schemas[name]; - addSchema(`$${name}`, schema); + addSchema(name, schema); } } for (const name in openApi.components.parameters) { if (openApi.components.parameters.hasOwnProperty(name)) { const parameter = openApi.components.parameters[name]; - addSchema(`$${name}`, parameter); + addSchema(name, parameter); } } } diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3/models.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3/models.ts.snap index f283546203..7359733ac4 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3/models.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3/models.ts.snap @@ -276,7 +276,7 @@ export type ModelWithString = { /** * `Comment` or `VoiceComment`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets) */ -export type ModelFromZendesk = string; +export type Model_From_Zendesk = string; /** * This is a model with one string property @@ -840,7 +840,7 @@ export type DefaultData = { export type SimpleData = { responses: { - ApiVversionOdataControllerCount: ModelFromZendesk; + ApiVversionOdataControllerCount: Model_From_Zendesk; GetCallWithoutParametersAndResponse: void; PutCallWithoutParametersAndResponse: void; PostCallWithoutParametersAndResponse: void; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3/schemas.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3/schemas.ts.snap index ef92fa7f45..7569aaa54c 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3/schemas.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3/schemas.ts.snap @@ -323,7 +323,7 @@ export const $ModelWithString = { }, } as const; -export const $ModelFromZendesk = { +export const $Model_From_Zendesk = { description: `\`Comment\` or \`VoiceComment\`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets)`, type: 'string', } as const; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3/services.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3/services.ts.snap index f3f9411650..b85a52ecd7 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3/services.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3/services.ts.snap @@ -57,7 +57,7 @@ export class DefaultService { export class SimpleService { /** - * @returns ModelFromZendesk Success + * @returns Model_From_Zendesk Success * @throws ApiError */ public static apiVVersionOdataControllerCount(): CancelablePromise< diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular/models.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular/models.ts.snap index 7cae3bcbeb..8774b000e6 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular/models.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular/models.ts.snap @@ -226,7 +226,7 @@ export type ModelWithString = { /** * `Comment` or `VoiceComment`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets) */ -export type ModelFromZendesk = string; +export type Model_From_Zendesk = string; /** * This is a model with one string property @@ -737,7 +737,7 @@ export type DefaultData = { export type SimpleData = { responses: { - ApiVversionOdataControllerCount: ModelFromZendesk; + ApiVversionOdataControllerCount: Model_From_Zendesk; GetCallWithoutParametersAndResponse: void; PutCallWithoutParametersAndResponse: void; PostCallWithoutParametersAndResponse: void; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular/schemas.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular/schemas.ts.snap index ef92fa7f45..7569aaa54c 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular/schemas.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular/schemas.ts.snap @@ -323,7 +323,7 @@ export const $ModelWithString = { }, } as const; -export const $ModelFromZendesk = { +export const $Model_From_Zendesk = { description: `\`Comment\` or \`VoiceComment\`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets)`, type: 'string', } as const; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular/services.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular/services.ts.snap index 850bfd9fc2..373b46d780 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular/services.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular/services.ts.snap @@ -69,7 +69,7 @@ export class SimpleService { constructor(public readonly http: HttpClient) {} /** - * @returns ModelFromZendesk Success + * @returns Model_From_Zendesk Success * @throws ApiError */ public apiVVersionOdataControllerCount(): Observable { diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_client/models.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_client/models.ts.snap index eb7efad053..7c6f7bba84 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_client/models.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_client/models.ts.snap @@ -276,7 +276,7 @@ export type ModelWithString = { /** * `Comment` or `VoiceComment`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets) */ -export type ModelFromZendesk = string; +export type Model_From_Zendesk = string; /** * This is a model with one string property @@ -840,7 +840,7 @@ export type DefaultData = { export type SimpleData = { responses: { - ApiVversionOdataControllerCount: ModelFromZendesk; + ApiVversionOdataControllerCount: Model_From_Zendesk; GetCallWithoutParametersAndResponse: void; PutCallWithoutParametersAndResponse: void; PostCallWithoutParametersAndResponse: void; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_client/services.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_client/services.ts.snap index 80a8d12106..285f6363ad 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_client/services.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_client/services.ts.snap @@ -60,7 +60,7 @@ export class SimpleService { constructor(public readonly httpRequest: BaseHttpRequest) {} /** - * @returns ModelFromZendesk Success + * @returns Model_From_Zendesk Success * @throws ApiError */ public apiVVersionOdataControllerCount(): CancelablePromise< diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_date/models.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_date/models.ts.snap index bc6d5e1b6b..2be25136e4 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_date/models.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_date/models.ts.snap @@ -28,7 +28,7 @@ export type DefaultData = { export type SimpleData = { responses: { - ApiVversionOdataControllerCount: ModelFromZendesk; + ApiVversionOdataControllerCount: Model_From_Zendesk; GetCallWithoutParametersAndResponse: void; PutCallWithoutParametersAndResponse: void; PostCallWithoutParametersAndResponse: void; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_date/schemas.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_date/schemas.ts.snap index ef92fa7f45..7569aaa54c 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_date/schemas.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_date/schemas.ts.snap @@ -323,7 +323,7 @@ export const $ModelWithString = { }, } as const; -export const $ModelFromZendesk = { +export const $Model_From_Zendesk = { description: `\`Comment\` or \`VoiceComment\`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets)`, type: 'string', } as const; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_enums_typescript/models.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_enums_typescript/models.ts.snap index a2d90a8e40..2705321150 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_enums_typescript/models.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_enums_typescript/models.ts.snap @@ -262,7 +262,7 @@ export type ModelWithString = { /** * `Comment` or `VoiceComment`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets) */ -export type ModelFromZendesk = string; +export type Model_From_Zendesk = string; /** * This is a model with one string property @@ -782,7 +782,7 @@ export type DefaultData = { export type SimpleData = { responses: { - ApiVversionOdataControllerCount: ModelFromZendesk; + ApiVversionOdataControllerCount: Model_From_Zendesk; GetCallWithoutParametersAndResponse: void; PutCallWithoutParametersAndResponse: void; PostCallWithoutParametersAndResponse: void; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_enums_typescript/schemas.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_enums_typescript/schemas.ts.snap index ef92fa7f45..7569aaa54c 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_enums_typescript/schemas.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_enums_typescript/schemas.ts.snap @@ -323,7 +323,7 @@ export const $ModelWithString = { }, } as const; -export const $ModelFromZendesk = { +export const $Model_From_Zendesk = { description: `\`Comment\` or \`VoiceComment\`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets)`, type: 'string', } as const; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_enums_typescript/services.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_enums_typescript/services.ts.snap index f3f9411650..b85a52ecd7 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_enums_typescript/services.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_enums_typescript/services.ts.snap @@ -57,7 +57,7 @@ export class DefaultService { export class SimpleService { /** - * @returns ModelFromZendesk Success + * @returns Model_From_Zendesk Success * @throws ApiError */ public static apiVVersionOdataControllerCount(): CancelablePromise< diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_experimental/models.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_experimental/models.ts.snap index 5533f844a1..ba94fef2ed 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_experimental/models.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_experimental/models.ts.snap @@ -226,7 +226,7 @@ export type ModelWithString = { /** * `Comment` or `VoiceComment`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets) */ -export type ModelFromZendesk = string; +export type Model_From_Zendesk = string; /** * This is a model with one string property @@ -739,7 +739,7 @@ export type DefaultData = { export type SimpleData = { responses: { - ApiVversionOdataControllerCount: ModelFromZendesk; + ApiVversionOdataControllerCount: Model_From_Zendesk; GetCallWithoutParametersAndResponse: void; PutCallWithoutParametersAndResponse: void; PostCallWithoutParametersAndResponse: void; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_experimental/schemas.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_experimental/schemas.ts.snap index ef92fa7f45..7569aaa54c 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_experimental/schemas.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_experimental/schemas.ts.snap @@ -323,7 +323,7 @@ export const $ModelWithString = { }, } as const; -export const $ModelFromZendesk = { +export const $Model_From_Zendesk = { description: `\`Comment\` or \`VoiceComment\`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets)`, type: 'string', } as const; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_experimental/services.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_experimental/services.ts.snap index 1cf2f223ae..a09d63a76c 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_experimental/services.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_experimental/services.ts.snap @@ -57,7 +57,7 @@ export class DefaultService { export class SimpleService { /** - * @returns ModelFromZendesk Success + * @returns Model_From_Zendesk Success * @throws ApiError */ public static apiVVersionOdataControllerCount(): CancelablePromise< diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_models/models.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_models/models.ts.snap index 7cae3bcbeb..8774b000e6 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_models/models.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_models/models.ts.snap @@ -226,7 +226,7 @@ export type ModelWithString = { /** * `Comment` or `VoiceComment`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets) */ -export type ModelFromZendesk = string; +export type Model_From_Zendesk = string; /** * This is a model with one string property @@ -737,7 +737,7 @@ export type DefaultData = { export type SimpleData = { responses: { - ApiVversionOdataControllerCount: ModelFromZendesk; + ApiVversionOdataControllerCount: Model_From_Zendesk; GetCallWithoutParametersAndResponse: void; PutCallWithoutParametersAndResponse: void; PostCallWithoutParametersAndResponse: void; diff --git a/packages/openapi-ts/test/spec/v3.json b/packages/openapi-ts/test/spec/v3.json index 750bf50356..2f0653f080 100644 --- a/packages/openapi-ts/test/spec/v3.json +++ b/packages/openapi-ts/test/spec/v3.json @@ -60,7 +60,7 @@ "content": { "application/json; type=collection": { "schema": { - "$ref": "#/components/schemas/ModelFromZendesk" + "$ref": "#/components/schemas/Model-From.Zendesk" } } } @@ -2048,7 +2048,7 @@ } } }, - "ModelFromZendesk": { + "Model-From.Zendesk": { "description": "`Comment` or `VoiceComment`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets)", "type": "string" },