Skip to content

Commit

Permalink
Merge pull request #3029 from Michsior14/fix/external-enums
Browse files Browse the repository at this point in the history
fix: properly import external enums
  • Loading branch information
kamilmysliwiec authored Oct 23, 2024
2 parents aa22bd3 + a6a9588 commit 0bf3bdd
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 14 deletions.
54 changes: 54 additions & 0 deletions lib/plugin/utils/external-imports.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import * as ts from 'typescript';

export function getExternalImports(
sourceFile: ts.SourceFile
): Record<string, string> {
const externalImports: Record<string, string> = {};

const importDeclarations = sourceFile.statements.filter(
ts.isImportDeclaration
);

for (const declaration of importDeclarations) {
const { moduleSpecifier, importClause } = declaration;

// Skip relative imports
if (
!ts.isStringLiteral(moduleSpecifier) ||
moduleSpecifier.text[0] === '.'
) {
continue;
}

if (
importClause?.namedBindings &&
ts.isNamedImports(importClause.namedBindings)
) {
const namedImports = importClause?.namedBindings as ts.NamedImports;
for (const namedImport of namedImports.elements) {
externalImports[namedImport.name.text] = moduleSpecifier.text;
}
}
}
return externalImports;
}

export function replaceExternalImportsInTypeReference(
typeReference: string,
externalImports: Record<string, string>
): string {
const regexp = /import\((.+)\).([^\]]+)(\])?/;
const match = regexp.exec(typeReference);

if (match?.length >= 3) {
const [, importPath, importName] = match;
if (externalImports[importName]) {
return typeReference.replace(
importPath,
`"${externalImports[importName]}"`
);
}
}

return typeReference;
}
46 changes: 33 additions & 13 deletions lib/plugin/visitors/model-class.visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ import {
} from '../utils/plugin-utils';
import { typeReferenceToIdentifier } from '../utils/type-reference-to-identifier.util';
import { AbstractFileVisitor } from './abstract.visitor';
import {
getExternalImports,
replaceExternalImportsInTypeReference
} from '../utils/external-imports.util';
import {
decoratorsProperties,
decoratorsPropertiesMappingType
Expand Down Expand Up @@ -66,6 +70,7 @@ export class ModelClassVisitor extends AbstractFileVisitor {
program: ts.Program,
options: PluginOptions
) {
const externalImports = getExternalImports(sourceFile);
const typeChecker = program.getTypeChecker();
sourceFile = this.updateImports(sourceFile, ctx.factory, program);

Expand All @@ -80,7 +85,8 @@ export class ModelClassVisitor extends AbstractFileVisitor {
typeChecker,
options,
sourceFile,
metadata
metadata,
externalImports
);
} else if (
options.parameterProperties &&
Expand All @@ -91,7 +97,8 @@ export class ModelClassVisitor extends AbstractFileVisitor {
typeChecker,
options,
sourceFile,
metadata
metadata,
externalImports
);
}
return node;
Expand Down Expand Up @@ -157,7 +164,8 @@ export class ModelClassVisitor extends AbstractFileVisitor {
typeChecker: ts.TypeChecker,
options: PluginOptions,
sourceFile: ts.SourceFile,
metadata: ClassMetadata
metadata: ClassMetadata,
externalImports: Record<string, string>
) {
const isPropertyStatic = (node.modifiers || []).some(
(modifier: ts.Modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword
Expand Down Expand Up @@ -204,7 +212,8 @@ export class ModelClassVisitor extends AbstractFileVisitor {
options,
sourceFile.fileName,
sourceFile,
metadata
metadata,
externalImports
);
} catch (err) {
return node;
Expand All @@ -216,7 +225,8 @@ export class ModelClassVisitor extends AbstractFileVisitor {
typeChecker: ts.TypeChecker,
options: PluginOptions,
sourceFile: ts.SourceFile,
metadata: ClassMetadata
metadata: ClassMetadata,
externalImports: Record<string, string>
) {
constructorNode.forEachChild((node) => {
if (
Expand All @@ -237,7 +247,8 @@ export class ModelClassVisitor extends AbstractFileVisitor {
factory.createNodeArray(),
options,
sourceFile.fileName,
sourceFile
sourceFile,
externalImports
);

const propertyName = node.name.getText();
Expand Down Expand Up @@ -303,7 +314,8 @@ export class ModelClassVisitor extends AbstractFileVisitor {
options: PluginOptions,
hostFilename: string,
sourceFile: ts.SourceFile,
metadata: ClassMetadata
metadata: ClassMetadata,
externalImports: Record<string, string>
) {
const objectLiteralExpr = this.createDecoratorObjectLiteralExpr(
factory,
Expand All @@ -312,7 +324,8 @@ export class ModelClassVisitor extends AbstractFileVisitor {
factory.createNodeArray(),
options,
hostFilename,
sourceFile
sourceFile,
externalImports
);
this.addClassMetadata(
compilerNode,
Expand All @@ -332,7 +345,8 @@ export class ModelClassVisitor extends AbstractFileVisitor {
existingProperties: ts.NodeArray<ts.PropertyAssignment> = factory.createNodeArray(),
options: PluginOptions = {},
hostFilename = '',
sourceFile?: ts.SourceFile
sourceFile?: ts.SourceFile,
externalImports: Record<string, string> = {}
): ts.ObjectLiteralExpression {
const isRequired = !node.questionToken;

Expand Down Expand Up @@ -371,7 +385,8 @@ export class ModelClassVisitor extends AbstractFileVisitor {
typeChecker,
existingProperties,
hostFilename,
options
options,
externalImports
)
];
if (
Expand Down Expand Up @@ -528,7 +543,8 @@ export class ModelClassVisitor extends AbstractFileVisitor {
typeChecker: ts.TypeChecker,
existingProperties: ts.NodeArray<ts.PropertyAssignment>,
hostFilename: string,
options: PluginOptions
options: PluginOptions,
externalImports: Record<string, string>
) {
const key = 'enum';
if (hasPropertyKey(key, existingProperties)) {
Expand Down Expand Up @@ -565,8 +581,12 @@ export class ModelClassVisitor extends AbstractFileVisitor {
isArrayType = typeIsArrayTuple.isArray;
type = typeIsArrayTuple.type;
}

const typeReferenceDescriptor = { typeName: getText(type, typeChecker) };
const typeReferenceDescriptor = {
typeName: replaceExternalImportsInTypeReference(
getText(type, typeChecker),
externalImports
)
};
const enumIdentifier = typeReferenceToIdentifier(
typeReferenceDescriptor,
hostFilename,
Expand Down
8 changes: 7 additions & 1 deletion test/plugin/fixtures/project/cats/dto/create-cat.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ConsoleLogger } from '@nestjs/common';
import { ConsoleLogger, HttpStatus } from '@nestjs/common';
import {
IsIn,
IsNegative,
Expand Down Expand Up @@ -99,6 +99,12 @@ export class CreateCatDto {
})
enum: LettersEnum;

@ApiProperty({
enum: HttpStatus,
enumName: 'HttpStatus'
})
externalEnum: HttpStatus;

/**
* Available language in the application
* @example FR
Expand Down
4 changes: 4 additions & 0 deletions test/plugin/fixtures/serialized-meta.fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ export default async () => {
required: true,
enum: t['./cats/dto/pagination-query.dto'].LettersEnum
},
externalEnum: {
required: true,
enum: require('@nestjs/common').HttpStatus
},
state: {
required: false,
description: 'Available language in the application',
Expand Down

0 comments on commit 0bf3bdd

Please sign in to comment.