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
95 changes: 95 additions & 0 deletions packages/cli/src/generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -914,3 +914,98 @@ test('should generate the correct SQL overload functions', async () => {
const expected = `export function sqlFunc(s: \`SELECT id from users\`): ReturnType<typeof sourceSql<IGetUsersQuery>>;`;
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);
});
});
7 changes: 6 additions & 1 deletion packages/cli/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(' | '),
);
}
Expand Down