Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(datasource-customizer): sort enum values in typings file #892

Merged
merged 8 commits into from
Nov 30, 2023
41 changes: 28 additions & 13 deletions packages/datasource-customizer/src/typing-generator.ts
Original file line number Diff line number Diff line change
@@ -18,6 +18,12 @@ export default class TypingGenerator {
this.options.maxFieldsCount = options.maxFieldsCount ?? this.options.maxFieldsCount;
}

private static sortedEntries<T>(
...args: Parameters<typeof Object.entries<T>>
): ReturnType<typeof Object.entries<T>> {
return Object.entries(...args).sort(([name1], [name2]) => name1.localeCompare(name2));
}

/**
* Write types to disk at a given path.
* This method read the file which is already there before overwriting so that customers
@@ -98,21 +104,21 @@ export default class TypingGenerator {
}

private getRow(collection: Collection): string {
const content = Object.entries(collection.schema.fields)
.sort(([nameA], [nameB]) => nameA.localeCompare(nameB))
.reduce((memo, [name, field]) => {
const content = TypingGenerator.sortedEntries(collection.schema.fields).reduce(
(memo, [name, field]) => {
return field.type === 'Column'
? [...memo, ` '${name}': ${this.getType(field)};`]
: memo;
}, []);
},
[],
);

return ` plain: {\n${content.join('\n')}\n };`;
}

private getRelations(collection: Collection): string {
const content = Object.entries(collection.schema.fields)
.sort(([nameA], [nameB]) => nameA.localeCompare(nameB))
.reduce((memo, [name, field]) => {
const content = TypingGenerator.sortedEntries(collection.schema.fields).reduce(
(memo, [name, field]) => {
if (field.type === 'ManyToOne' || field.type === 'OneToOne') {
const relation = field.foreignCollection;

@@ -123,7 +129,9 @@ export default class TypingGenerator {
}

return memo;
}, []);
},
[],
);

return content.length ? ` nested: {\n${content.join('\n')}\n };` : ` nested: {};`;
}
@@ -143,11 +151,11 @@ export default class TypingGenerator {

while (queue.length > 0 && result.length < this.options.maxFieldsCount) {
const { collection, depth, prefix, traversed } = queue.shift();
const sortedFields = TypingGenerator.sortedEntries(collection.schema.fields);

if (prefix) {
result.push(
...Object.entries(collection.schema.fields)
.sort(([nameA], [nameB]) => nameA.localeCompare(nameB))
...sortedFields
.filter(([, schema]) => schema.type === 'Column')
.map(
([name, schema]) => `'${prefix}:${name}': ${this.getType(schema as ColumnSchema)};`,
@@ -157,7 +165,7 @@ export default class TypingGenerator {

if (depth < maxDepth) {
queue.push(
...Object.entries(collection.schema.fields)
...sortedFields
.filter(([, schema]) => schema.type === 'ManyToOne' || schema.type === 'OneToOne')
.map(([name, schema]: [name: string, schema: OneToOneSchema | ManyToOneSchema]) => {
return {
@@ -203,7 +211,14 @@ export default class TypingGenerator {
}

if (field.columnType === 'Enum') {
return field.enumValues?.map(v => `'${v.replace(/'/g, "\\'")}'`).join(' | ') ?? 'string';
if (field.enumValues === undefined) return 'string';

return (
[...field.enumValues]
.sort((v1, v2) => v1.localeCompare(v2))
.map(v => `'${v.replace(/'/g, "\\'")}'`)
.join(' | ') ?? 'string'
);
}

if (typeof field.columnType === 'string') {
@@ -221,7 +236,7 @@ export default class TypingGenerator {
}[field.columnType];
}

return `{${Object.entries(field.columnType)
return `{${TypingGenerator.sortedEntries(field.columnType)
.map(([key, subType]) => `${key}: ${this.getType({ columnType: subType })}`)
.join('; ')}}`;
}
130 changes: 130 additions & 0 deletions packages/datasource-customizer/test/typing-generator.test.ts
Original file line number Diff line number Diff line change
@@ -78,6 +78,136 @@ describe('TypingGenerator', () => {
expectEqual(generated, expected);
});

test('should sort field names', () => {
const datasource = factories.dataSource.buildWithCollections([
factories.collection.build({
name: 'aCollectionName',
schema: {
fields: {
b: factories.columnSchema.build({ columnType: 'String' }),
A: factories.columnSchema.build({ columnType: 'String' }),
z: factories.columnSchema.build({ columnType: 'String' }),
a: factories.columnSchema.build({ columnType: 'String' }),
0: factories.columnSchema.build({ columnType: 'String' }),
},
},
}),
]);

const generated = new TypingGenerator(jest.fn()).generateTypes(datasource, 5);
const expected = `
export type Schema = {
'aCollectionName': {
plain: {
'0': string;
'a': string;
'A': string;
'b': string;
'z': string;
};
nested: {};
flat: {};
};
};`;

expectContains(generated, expected);
});

test('should sort nested field names', () => {
const datasource = factories.dataSource.buildWithCollections([
factories.collection.build({
name: 'z',
schema: {
fields: {
id: factories.columnSchema.build({ columnType: 'Number' }),
b: factories.manyToOneSchema.build({ foreignCollection: 'b', foreignKey: 'id' }),
a: factories.manyToOneSchema.build({ foreignCollection: 'a', foreignKey: 'id' }),
},
},
}),
factories.collection.build({
name: 'b',
schema: {
fields: {
id: factories.columnSchema.build({ columnType: 'Number' }),
},
},
}),
factories.collection.build({
name: 'a',
schema: {
fields: {
id: factories.columnSchema.build({ columnType: 'Number' }),
},
},
}),
]);

const generated = new TypingGenerator(jest.fn()).generateTypes(datasource, 5);
const expected = `
export type Schema = {
'a': {
plain: {
'id': number;
};
nested: {};
flat: {};
};
'b': {
plain: {
'id': number;
};
nested: {};
flat: {};
};
'z': {
plain: {
'id': number;
};
nested: {
'a': Schema['a']['plain'] & Schema['a']['nested'];
'b': Schema['b']['plain'] & Schema['b']['nested'];
};
flat: {
'a:id': number;
'b:id': number;
};
};
};`;

expectContains(generated, expected);
});

test('should sort enum values', () => {
const datasource = factories.dataSource.buildWithCollections([
factories.collection.build({
name: 'aCollectionName',
schema: {
fields: {
enum: factories.columnSchema.build({
columnType: 'Enum',
enumValues: ['b', '0', 'z', 'a', 'A'],
}),
},
},
}),
]);

const generated = new TypingGenerator(jest.fn()).generateTypes(datasource, 5);
const expected = `
export type Schema = {
'aCollectionName': {
plain: {
'enum': '0' | 'a' | 'A' | 'b' | 'z';
};
nested: {};
flat: {};
};
};`;

expectContains(generated, expected);
});

it.each(['_underscores', '-dashes'])('aliases should work with a collection with %s', char => {
const datasource = factories.dataSource.buildWithCollections([
factories.collection.build({ name: `aCollectionNameWith${char}` }),