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

fix: generated TS interfaces extend type defined by additional properties #1028

Closed
wants to merge 1 commit into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Array [
"/**
* Main Description
*/
interface Test {
interface Test extends Map<string, any> {
stringProp: string;
/**
* Description
Expand All @@ -17,7 +17,6 @@ interface Test {
* @example Example 1, Example 2
*/
objectProp?: NestedTest;
additionalProperties?: Map<string, any>;
}",
],
Array [
Expand All @@ -26,7 +25,6 @@ interface Test {
*/
interface NestedTest {
stringProp?: string;
additionalProperties?: Map<string, any>;
}",
],
]
Expand Down
1 change: 1 addition & 0 deletions examples/typescript-generate-comments/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const jsonSchemaDraft7 = {
$id: 'NestedTest',
properties: { stringProp: { type: 'string' } },
examples: ['Example 1', 'Example 2'],
additionalProperties: false,
},
},
};
Expand Down
4 changes: 3 additions & 1 deletion src/generators/typescript/TypeScriptGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface TypeScriptOptions extends CommonGeneratorOptions<TypeScriptPres
typeMapping: TypeMapping<TypeScriptOptions>;
constraints: Constraints;
moduleSystem: 'ESM' | 'CJS';
extendInterfaceWithAdditionalProperties: boolean;
}

export interface TypeScriptRenderCompleteModelOptions {
Expand All @@ -41,7 +42,8 @@ export class TypeScriptGenerator extends AbstractGenerator<TypeScriptOptions, Ty
defaultPreset: TS_DEFAULT_PRESET,
typeMapping: TypeScriptDefaultTypeMapping,
constraints: TypeScriptDefaultConstraints,
moduleSystem: 'ESM'
moduleSystem: 'ESM',
extendInterfaceWithAdditionalProperties: true
};

constructor(
Expand Down
32 changes: 31 additions & 1 deletion src/generators/typescript/renderers/InterfaceRenderer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { TypeScriptOptions } from '../TypeScriptGenerator';
import { TypeScriptObjectRenderer } from '../TypeScriptObjectRenderer';
import { InterfacePresetType } from '../TypeScriptPreset';
import { ConstrainedObjectPropertyModel } from '../../../models';
import { Logger } from '../../../utils';

/**
* Renderer for TypeScript's `interface` type
Expand All @@ -14,10 +16,38 @@ export class InterfaceRenderer extends TypeScriptObjectRenderer {
await this.runAdditionalContentPreset()
];

return `interface ${this.model.name} {
const extendsType = this.getExtendsWithAdditionalProperties();

return `interface ${this.model.name}${extendsType} {
${this.indent(this.renderBlock(content, 2))}
}`;
}

renderProperty(property: ConstrainedObjectPropertyModel): string {
if (property.propertyName === 'additionalProperties') {
return '';
}

return `${property.propertyName}${property.required === false ? '?' : ''}: ${property.property.type};`;
}

getExtendsWithAdditionalProperties(): string {
if (!this.options.extendInterfaceWithAdditionalProperties) {
return '';
}

if (this.options.mapType === 'indexedObject') {
Logger.error('Extending indexedObject as interface is not supported.');
return '';
}

if (this.model.properties.additionalProperties === undefined) {
return '';
}

const extendsType = this.model.properties.additionalProperties.property.type;
return ` extends ${extendsType}`;
}
}

export const TS_DEFAULT_INTERFACE_PRESET: InterfacePresetType<TypeScriptOptions> = {
Expand Down
18 changes: 18 additions & 0 deletions test/generators/typescript/TypeScriptGenerator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,24 @@ ${content}`;
expect(models[0].dependencies).toEqual([]);
});

test('should render `interface` type not extended with additionalProperties definition', async () => {
const doc = {
$id: 'Address',
type: 'object',
properties: {
street_name: { type: 'string' },
},
required: ['street_name'],
additionalProperties: false,
};

generator = new TypeScriptGenerator({ modelType: 'interface' });
const models = await generator.generate(doc);
expect(models).toHaveLength(1);
expect(models[0].result).toMatchSnapshot();
expect(models[0].dependencies).toEqual([]);
});

test('should work custom preset for `interface` type', async () => {
const doc = {
$id: 'CustomInterface',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ exports[`TypeScriptGenerator should render \`enum\` type 1`] = `
exports[`TypeScriptGenerator should render \`enum\` type as \`union\` if option enumType = \`union\` 1`] = `"type States = \\"Texas\\" | \\"Alabama\\" | \\"California\\";"`;

exports[`TypeScriptGenerator should render \`interface\` type 1`] = `
"interface Address {
"interface Address extends Map<string, any> {
streetName: string;
city: string;
state: string;
Expand All @@ -340,7 +340,12 @@ exports[`TypeScriptGenerator should render \`interface\` type 1`] = `
tupleType?: [string, number];
tupleTypeWithAdditionalItems?: (string | number | any)[];
arrayType: (string | any)[];
additionalProperties?: Map<string, any>;
}"
`;

exports[`TypeScriptGenerator should render \`interface\` type not extended with additionalProperties definition 1`] = `
"interface Address {
streetName: string;
}"
`;

Expand Down