Skip to content

Commit 4aa469c

Browse files
authored
Merge pull request #1759 from Gijsdeman/master
fix: spread assignments
2 parents 0c53697 + 05c769c commit 4aa469c

File tree

4 files changed

+46
-2
lines changed

4 files changed

+46
-2
lines changed

packages/cli/src/metadataGeneration/initializer-value.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Tsoa } from '@tsoa/runtime';
44
const hasInitializer = (node: ts.Node): node is ts.HasInitializer => Object.prototype.hasOwnProperty.call(node, 'initializer');
55
const extractInitializer = (decl?: ts.Declaration) => (decl && hasInitializer(decl) && (decl.initializer as ts.Expression)) || undefined;
66
const extractImportSpecifier = (symbol?: ts.Symbol) => (symbol?.declarations && symbol.declarations.length > 0 && ts.isImportSpecifier(symbol.declarations[0]) && symbol.declarations[0]) || undefined;
7+
const isIterable = (obj: any): obj is Iterable<any> => obj != null && typeof obj[Symbol.iterator] === 'function';
78

89
export type InitializerValue = string | number | boolean | undefined | null | InitializerValue[];
910
export type DefinedInitializerValue = string | number | boolean | null | DefinedInitializerValue[];
@@ -23,7 +24,19 @@ export function getInitializerValue(initializer?: ts.Expression | ts.ImportSpeci
2324
switch (initializer.kind) {
2425
case ts.SyntaxKind.ArrayLiteralExpression: {
2526
const arrayLiteral = initializer as ts.ArrayLiteralExpression;
26-
return arrayLiteral.elements.map(element => getInitializerValue(element, typeChecker));
27+
return arrayLiteral.elements.reduce((acc, element) => {
28+
if (ts.isSpreadElement(element)) {
29+
const spreadValue = getInitializerValue(element.expression, typeChecker);
30+
if (spreadValue && isIterable(spreadValue)) {
31+
return acc.concat(...spreadValue);
32+
} else {
33+
throw new Error(`${typeof spreadValue} is not iterable`);
34+
}
35+
} else {
36+
acc.push(getInitializerValue(element, typeChecker));
37+
}
38+
return acc;
39+
}, [] as InitializerValue[]);
2740
}
2841
case ts.SyntaxKind.StringLiteral:
2942
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
@@ -73,7 +86,18 @@ export function getInitializerValue(initializer?: ts.Expression | ts.ImportSpeci
7386
const objectLiteral = initializer as ts.ObjectLiteralExpression;
7487
const nestedObject: any = {};
7588
objectLiteral.properties.forEach((p: any) => {
76-
nestedObject[p.name.text] = getInitializerValue(p.initializer, typeChecker);
89+
if (ts.isSpreadAssignment(p)) {
90+
const spreadValue = getInitializerValue(p.expression, typeChecker);
91+
if (spreadValue) {
92+
if (typeof spreadValue === 'object') {
93+
Object.assign(nestedObject, spreadValue);
94+
} else {
95+
throw new Error(`Spread types may only be created from object types.`);
96+
}
97+
}
98+
} else {
99+
nestedObject[p.name.text] = getInitializerValue(p.initializer, typeChecker);
100+
}
77101
});
78102
return nestedObject;
79103
}

tests/fixtures/controllers/methodController.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,11 @@ export class MethodController extends Controller {
169169
@Extension('x-attKey7', { test: ['testVal', 123, true, null] })
170170
@Extension('x-attKey8', { test: { testArray: ['testVal1', true, null, ['testVal2', 'testVal3', 123, true, null]] } })
171171
@Extension(ATT_KEY9, 'identifierAttValue')
172+
@Extension('x-attKey10', ['testVal1', ...['testVal2', 123, true, null]])
173+
@Extension('x-attKey11', ['testVal1', ...'val'])
174+
@Extension('x-attKey12', { ...[1, 2, 3, 4] })
175+
@Extension('x-attKey13', { ...['testVal1', 123, true, null] })
176+
@Extension('x-attKey14', { ...{ y0: 'yt0', y1: 'yt1', y2: 123, y3: true, y4: null } })
172177
@Get('Extension')
173178
public async extension(): Promise<TestModel> {
174179
return new ModelService().getModel();

tests/unit/swagger/definitionsGeneration/metadata.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,11 @@ describe('Metadata generation', () => {
321321
{ key: 'x-attKey7', value: { test: ['testVal', 123, true, null] } },
322322
{ key: 'x-attKey8', value: { test: { testArray: ['testVal1', true, null, ['testVal2', 'testVal3', 123, true, null]] } } },
323323
{ key: 'x-attKey9', value: 'identifierAttValue' },
324+
{ key: 'x-attKey10', value: ['testVal1', 'testVal2', 123, true, null] },
325+
{ key: 'x-attKey11', value: ['testVal1', 'v', 'a', 'l'] },
326+
{ key: 'x-attKey12', value: { '0': 1, '1': 2, '2': 3, '3': 4 } },
327+
{ key: 'x-attKey13', value: { '0': 'testVal1', '1': 123, '2': true, '3': null } },
328+
{ key: 'x-attKey14', value: { y0: 'yt0', y1: 'yt1', y2: 123, y3: true, y4: null } },
324329
];
325330

326331
expect(method.extensions).to.deep.equal(expectedExtensions);

tests/unit/swagger/schemaDetails.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,11 @@ describe('Schema details generation', () => {
701701
expect(extensionPath).to.have.property('x-attKey6');
702702
expect(extensionPath).to.have.property('x-attKey7');
703703
expect(extensionPath).to.have.property('x-attKey8');
704+
expect(extensionPath).to.have.property('x-attKey10');
705+
expect(extensionPath).to.have.property('x-attKey11');
706+
expect(extensionPath).to.have.property('x-attKey12');
707+
expect(extensionPath).to.have.property('x-attKey13');
708+
expect(extensionPath).to.have.property('x-attKey14');
704709

705710
// Verify that extensions have correct values
706711
expect(extensionPath['x-attKey']).to.deep.equal('attValue');
@@ -712,6 +717,11 @@ describe('Schema details generation', () => {
712717
expect(extensionPath['x-attKey6']).to.deep.equal([{ y0: 'yt0', y1: 'yt1', y2: 123, y3: true, y4: null }, { y2: 'yt2' }]);
713718
expect(extensionPath['x-attKey7']).to.deep.equal({ test: ['testVal', 123, true, null] });
714719
expect(extensionPath['x-attKey8']).to.deep.equal({ test: { testArray: ['testVal1', true, null, ['testVal2', 'testVal3', 123, true, null]] } });
720+
expect(extensionPath['x-attKey10']).to.deep.equal(['testVal1', 'testVal2', 123, true, null]);
721+
expect(extensionPath['x-attKey11']).to.deep.equal(['testVal1', 'v', 'a', 'l']);
722+
expect(extensionPath['x-attKey12']).to.deep.equal({ '0': 1, '1': 2, '2': 3, '3': 4 });
723+
expect(extensionPath['x-attKey13']).to.deep.equal({ '0': 'testVal1', '1': 123, '2': true, '3': null });
724+
expect(extensionPath['x-attKey14']).to.deep.equal({ y0: 'yt0', y1: 'yt1', y2: 123, y3: true, y4: null });
715725
});
716726

717727
describe('@Res responses', () => {

0 commit comments

Comments
 (0)