diff --git a/packages/cli/src/generator.test.ts b/packages/cli/src/generator.test.ts index 769af11e..dab347a0 100644 --- a/packages/cli/src/generator.test.ts +++ b/packages/cli/src/generator.test.ts @@ -914,3 +914,98 @@ test('should generate the correct SQL overload functions', async () => { const expected = `export function sqlFunc(s: \`SELECT id from users\`): ReturnType>;`; expect(result).toEqual(expected); }); + +describe('enum value escaping', () => { + test('escapes apostrophes in enum values', async () => { + const queryString = ` + /* @name GetModels */ + SELECT model FROM cars; + `; + const mockTypes: IQueryTypes = { + returnTypes: [ + { + returnName: 'model', + columnName: 'model', + type: { name: 'ModelType', enumValues: ["car's", 'truck'] }, + nullable: false, + }, + ], + paramMetadata: { + params: [], + mapping: [], + }, + }; + const typeSource = async (_: any) => mockTypes; + const types = new TypeAllocator(TypeMapping()); + await queryToTypeDeclarations( + parsedQuery(ProcessingMode.SQL, queryString), + typeSource, + types, + partialConfig, + ); + const expectedTypes = `export type ModelType = 'car\\'s' | 'truck';\n`; + expect(types.declaration('file.ts')).toEqual(expectedTypes); + }); + + test('escapes backslashes in enum values', async () => { + const queryString = ` + /* @name GetPaths */ + SELECT path FROM files; + `; + const mockTypes: IQueryTypes = { + returnTypes: [ + { + returnName: 'path', + columnName: 'path', + type: { name: 'PathType', enumValues: ['path\\dir', 'normal'] }, + nullable: false, + }, + ], + paramMetadata: { + params: [], + mapping: [], + }, + }; + const typeSource = async (_: any) => mockTypes; + const types = new TypeAllocator(TypeMapping()); + await queryToTypeDeclarations( + parsedQuery(ProcessingMode.SQL, queryString), + typeSource, + types, + partialConfig, + ); + const expectedTypes = `export type PathType = 'normal' | 'path\\\\dir';\n`; + expect(types.declaration('file.ts')).toEqual(expectedTypes); + }); + + test('escapes both apostrophes and backslashes in enum values', async () => { + const queryString = ` + /* @name GetComplex */ + SELECT value FROM complex; + `; + const mockTypes: IQueryTypes = { + returnTypes: [ + { + returnName: 'value', + columnName: 'value', + type: { name: 'ComplexType', enumValues: ["it's\\here", 'simple'] }, + nullable: false, + }, + ], + paramMetadata: { + params: [], + mapping: [], + }, + }; + const typeSource = async (_: any) => mockTypes; + const types = new TypeAllocator(TypeMapping()); + await queryToTypeDeclarations( + parsedQuery(ProcessingMode.SQL, queryString), + typeSource, + types, + partialConfig, + ); + const expectedTypes = `export type ComplexType = 'it\\'s\\\\here' | 'simple';\n`; + expect(types.declaration('file.ts')).toEqual(expectedTypes); + }); +}); diff --git a/packages/cli/src/types.ts b/packages/cli/src/types.ts index 4e61f43e..1a193370 100644 --- a/packages/cli/src/types.ts +++ b/packages/cli/src/types.ts @@ -220,12 +220,17 @@ function declareAlias(name: string, definition: string): string { return `export type ${name} = ${definition};\n`; } +/** Escape special characters in enum values for use in TypeScript string literals */ +function escapeEnumValue(value: string): string { + return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'"); +} + function declareStringUnion(name: string, values: string[]) { return declareAlias( name, values .sort() - .map((v) => `'${v}'`) + .map((v) => `'${escapeEnumValue(v)}'`) .join(' | '), ); }