Skip to content

Commit

Permalink
Merge pull request #2067 from nestjs/chore/support-typescript-v4.8
Browse files Browse the repository at this point in the history
chore(): support typescript v4.8
  • Loading branch information
kamilmysliwiec authored Sep 1, 2022
2 parents 1dc851c + 8757d37 commit 908bb52
Show file tree
Hide file tree
Showing 13 changed files with 257 additions and 138 deletions.
4 changes: 4 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ aliases:
run:
name: Test
command: npm run test -- --runInBand
- &run-unit-tests
run:
name: Test (TypeScript < v4.8)
command: npm i --no-save -D [email protected] && npm run test -- --runInBand
- &run-e2e-tests
run:
name: E2E test
Expand Down
7 changes: 4 additions & 3 deletions lib/plugin/utils/plugin-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@ import {
getText,
getTypeArguments,
isArray,
isBigInt,
isBoolean,
isEnum,
isInterface,
isNumber,
isBigInt,
isString,
isStringLiteral
} from './ast-utils';

export function getDecoratorOrUndefinedByNames(
names: string[],
decorators: ts.NodeArray<ts.Decorator>
decorators: ts.NodeArray<ts.Decorator>,
factory: ts.NodeFactory
): ts.Decorator | undefined {
return (decorators || ts.createNodeArray()).find((item) => {
return (decorators || factory.createNodeArray()).find((item) => {
try {
const decoratorName = getDecoratorName(item);
return names.includes(decoratorName);
Expand Down
81 changes: 65 additions & 16 deletions lib/plugin/visitors/abstract.visitor.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import * as ts from 'typescript';
import { OPENAPI_NAMESPACE, OPENAPI_PACKAGE_NAME } from '../plugin-constants';

const [major, minor] = ts.versionMajorMinor?.split('.').map((x) => +x);
export class AbstractFileVisitor {
updateImports(
sourceFile: ts.SourceFile,
factory: ts.NodeFactory | undefined
factory: ts.NodeFactory | undefined,
program: ts.Program
): ts.SourceFile {
const [major, minor] = ts.versionMajorMinor?.split('.').map((x) => +x);
if (!factory) {
// support TS v4.2+
const importEqualsDeclaration =
Expand Down Expand Up @@ -34,17 +35,26 @@ export class AbstractFileVisitor {
]);
}
// support TS v4.2+
const importEqualsDeclaration =
major == 4 && minor >= 2
? (factory.createImportEqualsDeclaration as any)(
undefined,
undefined,
false,
OPENAPI_NAMESPACE,
factory.createExternalModuleReference(
factory.createStringLiteral(OPENAPI_PACKAGE_NAME)
const importEqualsDeclaration: ts.ImportDeclaration =
major >= 4 && minor >= 2
? minor >= 8
? (factory.createImportEqualsDeclaration as any)(
undefined,
false,
factory.createIdentifier(OPENAPI_NAMESPACE),
factory.createExternalModuleReference(
factory.createStringLiteral(OPENAPI_PACKAGE_NAME)
)
)
: (factory.createImportEqualsDeclaration as any)(
undefined,
undefined,
false,
OPENAPI_NAMESPACE,
factory.createExternalModuleReference(
factory.createStringLiteral(OPENAPI_PACKAGE_NAME)
)
)
)
: (factory.createImportEqualsDeclaration as any)(
undefined,
undefined,
Expand All @@ -54,9 +64,48 @@ export class AbstractFileVisitor {
)
);

return factory.updateSourceFile(sourceFile, [
importEqualsDeclaration,
...sourceFile.statements
]);
const compilerOptions = program.getCompilerOptions();
// Support TS v4.8+
if (
compilerOptions.module >= ts.ModuleKind.ES2015 &&
compilerOptions.module <= ts.ModuleKind.ESNext
) {
const importAsDeclaration =
(minor >= 8 && major >= 4) || major >= 5
? (factory.createImportDeclaration as any)(
undefined,
factory.createImportClause(
false,
undefined,
factory.createNamespaceImport(
factory.createIdentifier(OPENAPI_NAMESPACE)
)
),
factory.createStringLiteral(OPENAPI_PACKAGE_NAME),
undefined
)
: (factory.createImportDeclaration as any)(
undefined,
undefined,
factory.createImportClause(
false,
undefined,
factory.createNamespaceImport(
factory.createIdentifier(OPENAPI_NAMESPACE)
)
),
factory.createStringLiteral(OPENAPI_PACKAGE_NAME),
undefined
);
return factory.updateSourceFile(sourceFile, [
importAsDeclaration,
...sourceFile.statements
]);
} else {
return factory.updateSourceFile(sourceFile, [
importEqualsDeclaration,
...sourceFile.statements
]);
}
}
}
115 changes: 75 additions & 40 deletions lib/plugin/visitors/controller-class.visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ import {
} from '../utils/plugin-utils';
import { AbstractFileVisitor } from './abstract.visitor';

const [tsVersionMajor, tsVersionMinor] = ts.versionMajorMinor
?.split('.')
.map((x) => +x);
const isInUpdatedAstContext = tsVersionMinor >= 8 || tsVersionMajor > 4;

export class ControllerClassVisitor extends AbstractFileVisitor {
visit(
sourceFile: ts.SourceFile,
Expand All @@ -26,7 +31,7 @@ export class ControllerClassVisitor extends AbstractFileVisitor {
options: PluginOptions
) {
const typeChecker = program.getTypeChecker();
sourceFile = this.updateImports(sourceFile, ctx.factory);
sourceFile = this.updateImports(sourceFile, ctx.factory, program);

const visitNode = (node: ts.Node): ts.Node => {
if (ts.isMethodDeclaration(node)) {
Expand Down Expand Up @@ -56,14 +61,18 @@ export class ControllerClassVisitor extends AbstractFileVisitor {
hostFilename: string,
sourceFile: ts.SourceFile
): ts.MethodDeclaration {
if (!compilerNode.decorators) {
// Support both >= v4.8 and v4.7 and lower
const decorators = (ts as any).canHaveDecorators
? (ts as any).getDecorators(compilerNode)
: compilerNode.decorators;
if (!decorators) {
return compilerNode;
}

const apiOperationDecoratorsArray = this.createApiOperationDecorator(
factory,
compilerNode,
compilerNode.decorators,
decorators,
options,
sourceFile,
typeChecker
Expand All @@ -72,43 +81,60 @@ export class ControllerClassVisitor extends AbstractFileVisitor {
apiOperationDecoratorsArray.length > 0;

const existingDecorators = removeExistingApiOperationDecorator
? compilerNode.decorators.filter(
? decorators.filter(
(item) => getDecoratorName(item) !== ApiOperation.name
)
: compilerNode.decorators;
: decorators;

return factory.updateMethodDeclaration(
compilerNode,
[
...apiOperationDecoratorsArray,
...existingDecorators,
factory.createDecorator(
factory.createCallExpression(
factory.createIdentifier(
`${OPENAPI_NAMESPACE}.${ApiResponse.name}`
),
undefined,
[
this.createDecoratorObjectLiteralExpr(
factory,
compilerNode,
typeChecker,
factory.createNodeArray(),
hostFilename
)
]
)
// Support both >= v4.8 and v4.7 and lower
const modifiers = isInUpdatedAstContext
? (ts as any).getModifiers(compilerNode)
: compilerNode.modifiers;

const updatedDecorators = [
...apiOperationDecoratorsArray,
...existingDecorators,
factory.createDecorator(
factory.createCallExpression(
factory.createIdentifier(`${OPENAPI_NAMESPACE}.${ApiResponse.name}`),
undefined,
[
this.createDecoratorObjectLiteralExpr(
factory,
compilerNode,
typeChecker,
factory.createNodeArray(),
hostFilename
)
]
)
],
compilerNode.modifiers,
compilerNode.asteriskToken,
compilerNode.name,
compilerNode.questionToken,
compilerNode.typeParameters,
compilerNode.parameters,
compilerNode.type,
compilerNode.body
);
)
];

return isInUpdatedAstContext
? (factory as any).updateMethodDeclaration(
compilerNode,
[...updatedDecorators, ...modifiers],
compilerNode.asteriskToken,
compilerNode.name,
compilerNode.questionToken,
compilerNode.typeParameters,
compilerNode.parameters,
compilerNode.type,
compilerNode.body
)
: factory.updateMethodDeclaration(
compilerNode,
updatedDecorators,
modifiers,
compilerNode.asteriskToken,
compilerNode.name,
compilerNode.questionToken,
compilerNode.typeParameters,
compilerNode.parameters,
compilerNode.type,
compilerNode.body
);
}

createApiOperationDecorator(
Expand All @@ -125,7 +151,8 @@ export class ControllerClassVisitor extends AbstractFileVisitor {
const keyToGenerate = options.controllerKeyOfComment;
const apiOperationDecorator = getDecoratorOrUndefinedByNames(
[ApiOperation.name],
nodeArray
nodeArray,
factory
);
const apiOperationExpr: ts.ObjectLiteralExpression | undefined =
apiOperationDecorator &&
Expand Down Expand Up @@ -248,18 +275,26 @@ export class ControllerClassVisitor extends AbstractFileVisitor {
}

getStatusCodeIdentifier(factory: ts.NodeFactory, node: ts.MethodDeclaration) {
const decorators = node.decorators;
// Support both >= v4.8 and v4.7 and lower
const decorators = (ts as any).canHaveDecorators
? (ts as any).getDecorators(node)
: node.decorators;
const httpCodeDecorator = getDecoratorOrUndefinedByNames(
['HttpCode'],
decorators
decorators,
factory
);
if (httpCodeDecorator) {
const argument = head(getDecoratorArguments(httpCodeDecorator));
if (argument) {
return argument;
}
}
const postDecorator = getDecoratorOrUndefinedByNames(['Post'], decorators);
const postDecorator = getDecoratorOrUndefinedByNames(
['Post'],
decorators,
factory
);
if (postDecorator) {
return factory.createIdentifier('201');
}
Expand Down
Loading

0 comments on commit 908bb52

Please sign in to comment.