Skip to content

Commit

Permalink
fix: merge type imports, ignore non-exported types
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilmysliwiec committed Jun 27, 2023
1 parent e18fd40 commit b365ed9
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 123 deletions.
4 changes: 3 additions & 1 deletion lib/plugin/merge-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface PluginOptions {
introspectComments?: boolean;
readonly?: boolean;
pathToSource?: string;
debug?: boolean;
}

const defaultOptions: PluginOptions = {
Expand All @@ -18,7 +19,8 @@ const defaultOptions: PluginOptions = {
dtoKeyOfComment: 'description',
controllerKeyOfComment: 'description',
introspectComments: false,
readonly: false
readonly: false,
debug: false
};

export const mergePluginOptions = (
Expand Down
5 changes: 5 additions & 0 deletions lib/plugin/plugin-debug-logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ConsoleLogger } from '@nestjs/common';

class PluginDebugLogger extends ConsoleLogger {}

export const pluginDebugLogger = new PluginDebugLogger('CLI Plugin');
81 changes: 81 additions & 0 deletions lib/plugin/utils/type-reference-to-identifier.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import * as ts from 'typescript';
import { PluginOptions } from '../merge-options';
import { pluginDebugLogger } from '../plugin-debug-logger';
import { replaceImportPath } from './plugin-utils';

export function typeReferenceToIdentifier(
typeReferenceDescriptor: {
typeName: string;
isArray?: boolean;
arrayDepth?: number;
},
hostFilename: string,
options: PluginOptions,
factory: ts.NodeFactory,
type: ts.Type,
typeImports: Record<string, string>
) {
if (options.readonly) {
assertReferenceableType(
type,
typeReferenceDescriptor.typeName,
hostFilename,
options
);
}

const { typeReference, importPath, typeName } = replaceImportPath(
typeReferenceDescriptor.typeName,
hostFilename,
options
);

let identifier: ts.Identifier;
if (options.readonly && typeReference?.includes('import')) {
if (!typeImports[importPath]) {
typeImports[importPath] = typeReference;
}

let ref = `t["${importPath}"].${typeName}`;
if (typeReferenceDescriptor.isArray) {
ref = wrapTypeInArray(ref, typeReferenceDescriptor.arrayDepth);
}
identifier = factory.createIdentifier(ref);
} else {
let ref = typeReference;
if (typeReferenceDescriptor.isArray) {
ref = wrapTypeInArray(ref, typeReferenceDescriptor.arrayDepth);
}
identifier = factory.createIdentifier(ref);
}
return identifier;
}

function wrapTypeInArray(typeRef: string, arrayDepth: number) {
for (let i = 0; i < arrayDepth; i++) {
typeRef = `[${typeRef}]`;
}
return typeRef;
}

function assertReferenceableType(
type: ts.Type,
parsedTypeName: string,
hostFilename: string,
options: PluginOptions
) {
if (!type.symbol) {
return true;
}
if (!(type.symbol as any).isReferenced) {
return true;
}
if (parsedTypeName.includes('import')) {
return true;
}
const errorMessage = `Type "${parsedTypeName}" is not referenceable ("${hostFilename}"). To fix this, make sure to export this type.`;
if (options.debug) {
pluginDebugLogger.debug(errorMessage);
}
throw new Error(errorMessage);
}
54 changes: 6 additions & 48 deletions lib/plugin/visitors/controller-class.visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import {
convertPath,
getDecoratorOrUndefinedByNames,
getTypeReferenceAsString,
hasPropertyKey,
replaceImportPath
hasPropertyKey
} from '../utils/plugin-utils';
import { typeReferenceToIdentifier } from '../utils/type-reference-to-identifier.util';
import { AbstractFileVisitor } from './abstract.visitor';

type ClassMetadata = Record<string, ts.ObjectLiteralExpression>;
Expand Down Expand Up @@ -360,11 +360,13 @@ export class ControllerClassVisitor extends AbstractFileVisitor {
if (typeReferenceDescriptor.typeName.includes('node_modules')) {
return undefined;
}
const identifier = this.typeReferenceStringToIdentifier(
const identifier = typeReferenceToIdentifier(
typeReferenceDescriptor,
hostFilename,
options,
factory
factory,
type,
this._typeImports
);
return factory.createPropertyAssignment('type', identifier);
}
Expand Down Expand Up @@ -410,48 +412,4 @@ export class ControllerClassVisitor extends AbstractFileVisitor {
relativePath = relativePath[0] !== '.' ? './' + relativePath : relativePath;
return relativePath;
}

private typeReferenceStringToIdentifier(
typeReferenceDescriptor: {
typeName: string;
isArray?: boolean;
arrayDepth?: number;
},
hostFilename: string,
options: PluginOptions,
factory: ts.NodeFactory
) {
const { typeReference, importPath, typeName } = replaceImportPath(
typeReferenceDescriptor.typeName,
hostFilename,
options
);

let identifier: ts.Identifier;
if (options.readonly && typeReference?.includes('import')) {
if (!this._typeImports[importPath]) {
this._typeImports[importPath] = typeReference;
}

let ref = `t["${importPath}"].${typeName}`;
if (typeReferenceDescriptor.isArray) {
ref = this.wrapTypeInArray(ref, typeReferenceDescriptor.arrayDepth);
}
identifier = factory.createIdentifier(ref);
} else {
let ref = typeReference;
if (typeReferenceDescriptor.isArray) {
ref = this.wrapTypeInArray(ref, typeReferenceDescriptor.arrayDepth);
}
identifier = factory.createIdentifier(ref);
}
return identifier;
}

private wrapTypeInArray(typeRef: string, arrayDepth: number) {
for (let i = 0; i < arrayDepth; i++) {
typeRef = `[${typeRef}]`;
}
return typeRef;
}
}
89 changes: 29 additions & 60 deletions lib/plugin/visitors/model-class.visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ import {
getTypeReferenceAsString,
hasPropertyKey,
isAutoGeneratedEnumUnion,
isAutoGeneratedTypeUnion,
replaceImportPath
isAutoGeneratedTypeUnion
} from '../utils/plugin-utils';
import { typeReferenceToIdentifier } from '../utils/type-reference-to-identifier.util';
import { AbstractFileVisitor } from './abstract.visitor';

type ClassMetadata = Record<string, ts.ObjectLiteralExpression>;
Expand Down Expand Up @@ -89,9 +89,16 @@ export class ModelClassVisitor extends AbstractFileVisitor {
const visitClassNode = (node: ts.Node): ts.Node => {
if (ts.isClassDeclaration(node)) {
const metadata: ClassMetadata = {};
const isExported = node.modifiers?.some(
(modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword
);

if (options.readonly) {
ts.forEachChild(node, propertyNodeVisitorFactory(metadata));
if (isExported) {
ts.forEachChild(node, propertyNodeVisitorFactory(metadata));
} else {
// TODO: Log debug warning
}
} else {
node = ts.visitEachChild(
node,
Expand All @@ -100,16 +107,18 @@ export class ModelClassVisitor extends AbstractFileVisitor {
);
}

const declaration = this.addMetadataFactory(
ctx.factory,
node as ts.ClassDeclaration,
metadata,
sourceFile,
options
);
if ((isExported && options.readonly) || !options.readonly) {
const declaration = this.addMetadataFactory(
ctx.factory,
node as ts.ClassDeclaration,
metadata,
sourceFile,
options
);

if (!options.readonly) {
return declaration;
if (!options.readonly) {
return declaration;
}
}
}

Expand Down Expand Up @@ -391,11 +400,13 @@ export class ModelClassVisitor extends AbstractFileVisitor {
if (!typeReferenceDescriptor.typeName) {
return [];
}
const identifier = this.typeReferenceToIdentifier(
const identifier = typeReferenceToIdentifier(
typeReferenceDescriptor,
hostFilename,
options,
factory
factory,
type,
this._typeImports
);

const initializer = factory.createArrowFunction(
Expand Down Expand Up @@ -454,11 +465,13 @@ export class ModelClassVisitor extends AbstractFileVisitor {
}

const typeReferenceDescriptor = { typeName: getText(type, typeChecker) };
const enumIdentifier = this.typeReferenceToIdentifier(
const enumIdentifier = typeReferenceToIdentifier(
typeReferenceDescriptor,
hostFilename,
options,
factory
factory,
type,
this._typeImports
);

const enumProperty = factory.createPropertyAssignment(key, enumIdentifier);
Expand Down Expand Up @@ -765,50 +778,6 @@ export class ModelClassVisitor extends AbstractFileVisitor {
return relativePath;
}

private typeReferenceToIdentifier(
typeReferenceDescriptor: {
typeName: string;
isArray?: boolean;
arrayDepth?: number;
},
hostFilename: string,
options: PluginOptions,
factory: ts.NodeFactory
) {
const { typeReference, importPath, typeName } = replaceImportPath(
typeReferenceDescriptor.typeName,
hostFilename,
options
);

let identifier: ts.Identifier;
if (options.readonly && typeReference?.includes('import')) {
if (!this._typeImports[importPath]) {
this._typeImports[importPath] = typeReference;
}

let ref = `t["${importPath}"].${typeName}`;
if (typeReferenceDescriptor.isArray) {
ref = this.wrapTypeInArray(ref, typeReferenceDescriptor.arrayDepth);
}
identifier = factory.createIdentifier(ref);
} else {
let ref = typeReference;
if (typeReferenceDescriptor.isArray) {
ref = this.wrapTypeInArray(ref, typeReferenceDescriptor.arrayDepth);
}
identifier = factory.createIdentifier(ref);
}
return identifier;
}

private wrapTypeInArray(typeRef: string, arrayDepth: number) {
for (let i = 0; i < arrayDepth; i++) {
typeRef = `[${typeRef}]`;
}
return typeRef;
}

private clonePrimitiveLiteral(factory: ts.NodeFactory, node: ts.Node) {
const primitiveTypeName = this.getInitializerPrimitiveTypeName(node);
if (!primitiveTypeName) {
Expand Down
5 changes: 4 additions & 1 deletion lib/plugin/visitors/readonly.visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ export class ReadonlyVisitor {
private readonly controllerClassVisitor = new ControllerClassVisitor();

get typeImports() {
return this.modelClassVisitor.typeImports;
return {
...this.modelClassVisitor.typeImports,
...this.controllerClassVisitor.typeImports
};
}

constructor(private readonly options: PluginOptions) {
Expand Down
Loading

0 comments on commit b365ed9

Please sign in to comment.