Skip to content

Commit 908bb52

Browse files
Merge pull request #2067 from nestjs/chore/support-typescript-v4.8
chore(): support typescript v4.8
2 parents 1dc851c + 8757d37 commit 908bb52

13 files changed

Lines changed: 257 additions & 138 deletions

.circleci/config.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ aliases:
1616
run:
1717
name: Test
1818
command: npm run test -- --runInBand
19+
- &run-unit-tests
20+
run:
21+
name: Test (TypeScript < v4.8)
22+
command: npm i --no-save -D typescript@4.7.2 && npm run test -- --runInBand
1923
- &run-e2e-tests
2024
run:
2125
name: E2E test

lib/plugin/utils/plugin-utils.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,21 @@ import {
66
getText,
77
getTypeArguments,
88
isArray,
9+
isBigInt,
910
isBoolean,
1011
isEnum,
1112
isInterface,
1213
isNumber,
13-
isBigInt,
1414
isString,
1515
isStringLiteral
1616
} from './ast-utils';
1717

1818
export function getDecoratorOrUndefinedByNames(
1919
names: string[],
20-
decorators: ts.NodeArray<ts.Decorator>
20+
decorators: ts.NodeArray<ts.Decorator>,
21+
factory: ts.NodeFactory
2122
): ts.Decorator | undefined {
22-
return (decorators || ts.createNodeArray()).find((item) => {
23+
return (decorators || factory.createNodeArray()).find((item) => {
2324
try {
2425
const decoratorName = getDecoratorName(item);
2526
return names.includes(decoratorName);

lib/plugin/visitors/abstract.visitor.ts

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import * as ts from 'typescript';
22
import { OPENAPI_NAMESPACE, OPENAPI_PACKAGE_NAME } from '../plugin-constants';
33

4+
const [major, minor] = ts.versionMajorMinor?.split('.').map((x) => +x);
45
export class AbstractFileVisitor {
56
updateImports(
67
sourceFile: ts.SourceFile,
7-
factory: ts.NodeFactory | undefined
8+
factory: ts.NodeFactory | undefined,
9+
program: ts.Program
810
): ts.SourceFile {
9-
const [major, minor] = ts.versionMajorMinor?.split('.').map((x) => +x);
1011
if (!factory) {
1112
// support TS v4.2+
1213
const importEqualsDeclaration =
@@ -34,17 +35,26 @@ export class AbstractFileVisitor {
3435
]);
3536
}
3637
// support TS v4.2+
37-
const importEqualsDeclaration =
38-
major == 4 && minor >= 2
39-
? (factory.createImportEqualsDeclaration as any)(
40-
undefined,
41-
undefined,
42-
false,
43-
OPENAPI_NAMESPACE,
44-
factory.createExternalModuleReference(
45-
factory.createStringLiteral(OPENAPI_PACKAGE_NAME)
38+
const importEqualsDeclaration: ts.ImportDeclaration =
39+
major >= 4 && minor >= 2
40+
? minor >= 8
41+
? (factory.createImportEqualsDeclaration as any)(
42+
undefined,
43+
false,
44+
factory.createIdentifier(OPENAPI_NAMESPACE),
45+
factory.createExternalModuleReference(
46+
factory.createStringLiteral(OPENAPI_PACKAGE_NAME)
47+
)
48+
)
49+
: (factory.createImportEqualsDeclaration as any)(
50+
undefined,
51+
undefined,
52+
false,
53+
OPENAPI_NAMESPACE,
54+
factory.createExternalModuleReference(
55+
factory.createStringLiteral(OPENAPI_PACKAGE_NAME)
56+
)
4657
)
47-
)
4858
: (factory.createImportEqualsDeclaration as any)(
4959
undefined,
5060
undefined,
@@ -54,9 +64,48 @@ export class AbstractFileVisitor {
5464
)
5565
);
5666

57-
return factory.updateSourceFile(sourceFile, [
58-
importEqualsDeclaration,
59-
...sourceFile.statements
60-
]);
67+
const compilerOptions = program.getCompilerOptions();
68+
// Support TS v4.8+
69+
if (
70+
compilerOptions.module >= ts.ModuleKind.ES2015 &&
71+
compilerOptions.module <= ts.ModuleKind.ESNext
72+
) {
73+
const importAsDeclaration =
74+
(minor >= 8 && major >= 4) || major >= 5
75+
? (factory.createImportDeclaration as any)(
76+
undefined,
77+
factory.createImportClause(
78+
false,
79+
undefined,
80+
factory.createNamespaceImport(
81+
factory.createIdentifier(OPENAPI_NAMESPACE)
82+
)
83+
),
84+
factory.createStringLiteral(OPENAPI_PACKAGE_NAME),
85+
undefined
86+
)
87+
: (factory.createImportDeclaration as any)(
88+
undefined,
89+
undefined,
90+
factory.createImportClause(
91+
false,
92+
undefined,
93+
factory.createNamespaceImport(
94+
factory.createIdentifier(OPENAPI_NAMESPACE)
95+
)
96+
),
97+
factory.createStringLiteral(OPENAPI_PACKAGE_NAME),
98+
undefined
99+
);
100+
return factory.updateSourceFile(sourceFile, [
101+
importAsDeclaration,
102+
...sourceFile.statements
103+
]);
104+
} else {
105+
return factory.updateSourceFile(sourceFile, [
106+
importEqualsDeclaration,
107+
...sourceFile.statements
108+
]);
109+
}
61110
}
62111
}

lib/plugin/visitors/controller-class.visitor.ts

Lines changed: 75 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ import {
1818
} from '../utils/plugin-utils';
1919
import { AbstractFileVisitor } from './abstract.visitor';
2020

21+
const [tsVersionMajor, tsVersionMinor] = ts.versionMajorMinor
22+
?.split('.')
23+
.map((x) => +x);
24+
const isInUpdatedAstContext = tsVersionMinor >= 8 || tsVersionMajor > 4;
25+
2126
export class ControllerClassVisitor extends AbstractFileVisitor {
2227
visit(
2328
sourceFile: ts.SourceFile,
@@ -26,7 +31,7 @@ export class ControllerClassVisitor extends AbstractFileVisitor {
2631
options: PluginOptions
2732
) {
2833
const typeChecker = program.getTypeChecker();
29-
sourceFile = this.updateImports(sourceFile, ctx.factory);
34+
sourceFile = this.updateImports(sourceFile, ctx.factory, program);
3035

3136
const visitNode = (node: ts.Node): ts.Node => {
3237
if (ts.isMethodDeclaration(node)) {
@@ -56,14 +61,18 @@ export class ControllerClassVisitor extends AbstractFileVisitor {
5661
hostFilename: string,
5762
sourceFile: ts.SourceFile
5863
): ts.MethodDeclaration {
59-
if (!compilerNode.decorators) {
64+
// Support both >= v4.8 and v4.7 and lower
65+
const decorators = (ts as any).canHaveDecorators
66+
? (ts as any).getDecorators(compilerNode)
67+
: compilerNode.decorators;
68+
if (!decorators) {
6069
return compilerNode;
6170
}
6271

6372
const apiOperationDecoratorsArray = this.createApiOperationDecorator(
6473
factory,
6574
compilerNode,
66-
compilerNode.decorators,
75+
decorators,
6776
options,
6877
sourceFile,
6978
typeChecker
@@ -72,43 +81,60 @@ export class ControllerClassVisitor extends AbstractFileVisitor {
7281
apiOperationDecoratorsArray.length > 0;
7382

7483
const existingDecorators = removeExistingApiOperationDecorator
75-
? compilerNode.decorators.filter(
84+
? decorators.filter(
7685
(item) => getDecoratorName(item) !== ApiOperation.name
7786
)
78-
: compilerNode.decorators;
87+
: decorators;
7988

80-
return factory.updateMethodDeclaration(
81-
compilerNode,
82-
[
83-
...apiOperationDecoratorsArray,
84-
...existingDecorators,
85-
factory.createDecorator(
86-
factory.createCallExpression(
87-
factory.createIdentifier(
88-
`${OPENAPI_NAMESPACE}.${ApiResponse.name}`
89-
),
90-
undefined,
91-
[
92-
this.createDecoratorObjectLiteralExpr(
93-
factory,
94-
compilerNode,
95-
typeChecker,
96-
factory.createNodeArray(),
97-
hostFilename
98-
)
99-
]
100-
)
89+
// Support both >= v4.8 and v4.7 and lower
90+
const modifiers = isInUpdatedAstContext
91+
? (ts as any).getModifiers(compilerNode)
92+
: compilerNode.modifiers;
93+
94+
const updatedDecorators = [
95+
...apiOperationDecoratorsArray,
96+
...existingDecorators,
97+
factory.createDecorator(
98+
factory.createCallExpression(
99+
factory.createIdentifier(`${OPENAPI_NAMESPACE}.${ApiResponse.name}`),
100+
undefined,
101+
[
102+
this.createDecoratorObjectLiteralExpr(
103+
factory,
104+
compilerNode,
105+
typeChecker,
106+
factory.createNodeArray(),
107+
hostFilename
108+
)
109+
]
101110
)
102-
],
103-
compilerNode.modifiers,
104-
compilerNode.asteriskToken,
105-
compilerNode.name,
106-
compilerNode.questionToken,
107-
compilerNode.typeParameters,
108-
compilerNode.parameters,
109-
compilerNode.type,
110-
compilerNode.body
111-
);
111+
)
112+
];
113+
114+
return isInUpdatedAstContext
115+
? (factory as any).updateMethodDeclaration(
116+
compilerNode,
117+
[...updatedDecorators, ...modifiers],
118+
compilerNode.asteriskToken,
119+
compilerNode.name,
120+
compilerNode.questionToken,
121+
compilerNode.typeParameters,
122+
compilerNode.parameters,
123+
compilerNode.type,
124+
compilerNode.body
125+
)
126+
: factory.updateMethodDeclaration(
127+
compilerNode,
128+
updatedDecorators,
129+
modifiers,
130+
compilerNode.asteriskToken,
131+
compilerNode.name,
132+
compilerNode.questionToken,
133+
compilerNode.typeParameters,
134+
compilerNode.parameters,
135+
compilerNode.type,
136+
compilerNode.body
137+
);
112138
}
113139

114140
createApiOperationDecorator(
@@ -125,7 +151,8 @@ export class ControllerClassVisitor extends AbstractFileVisitor {
125151
const keyToGenerate = options.controllerKeyOfComment;
126152
const apiOperationDecorator = getDecoratorOrUndefinedByNames(
127153
[ApiOperation.name],
128-
nodeArray
154+
nodeArray,
155+
factory
129156
);
130157
const apiOperationExpr: ts.ObjectLiteralExpression | undefined =
131158
apiOperationDecorator &&
@@ -248,18 +275,26 @@ export class ControllerClassVisitor extends AbstractFileVisitor {
248275
}
249276

250277
getStatusCodeIdentifier(factory: ts.NodeFactory, node: ts.MethodDeclaration) {
251-
const decorators = node.decorators;
278+
// Support both >= v4.8 and v4.7 and lower
279+
const decorators = (ts as any).canHaveDecorators
280+
? (ts as any).getDecorators(node)
281+
: node.decorators;
252282
const httpCodeDecorator = getDecoratorOrUndefinedByNames(
253283
['HttpCode'],
254-
decorators
284+
decorators,
285+
factory
255286
);
256287
if (httpCodeDecorator) {
257288
const argument = head(getDecoratorArguments(httpCodeDecorator));
258289
if (argument) {
259290
return argument;
260291
}
261292
}
262-
const postDecorator = getDecoratorOrUndefinedByNames(['Post'], decorators);
293+
const postDecorator = getDecoratorOrUndefinedByNames(
294+
['Post'],
295+
decorators,
296+
factory
297+
);
263298
if (postDecorator) {
264299
return factory.createIdentifier('201');
265300
}

0 commit comments

Comments
 (0)