Skip to content

Commit

Permalink
fix(classes): clean up transformer plugin to ensure decorator is ignored
Browse files Browse the repository at this point in the history
  • Loading branch information
nartc committed Mar 10, 2023
1 parent 88397b4 commit e82e9a1
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 112 deletions.
13 changes: 4 additions & 9 deletions packages/classes/transformer-plugin/src/lib/model-visitor.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { AUTOMAPPER_METADATA_FACTORY_KEY } from '@automapper/classes';
import type {
import {
ArrayLiteralExpression,
ClassDeclaration,
GetAccessorDeclaration,
getDecorators,
Identifier,
ImportDeclaration,
JSDocTag,
MethodDeclaration,
Modifier,
Modifier,
NodeFactory,
ObjectLiteralExpression,
Program,
Expand All @@ -19,8 +20,6 @@ import type {
TypeChecker,
TypeReferenceNode,
Visitor,
} from 'typescript/lib/tsserverlibrary';
import {
getAllJSDocTags,
isClassDeclaration,
isGetAccessorDeclaration,
Expand Down Expand Up @@ -99,11 +98,10 @@ export class ModelVisitor {
isPropertyDeclaration(node) ||
isGetAccessorDeclaration(node)
) {
const decorators = node.decorators;
const decorators = getDecorators(node);
const existingAutoMapDecorator =
getDecoratorOrUndefinedByNames(
[AUTOMAPPER_DECORATOR_NAME],
ctx.factory,
decorators
);

Expand Down Expand Up @@ -195,7 +193,6 @@ export class ModelVisitor {
// add the factory static method at the end of the class
return factory.updateClassDeclaration(
classNode,
classNode.decorators,
classNode.modifiers as unknown as Modifier[],
classNode.name,
classNode.typeParameters,
Expand Down Expand Up @@ -241,7 +238,6 @@ export class ModelVisitor {
* }
*/
return factory.createMethodDeclaration(
undefined,
[factory.createModifier(SyntaxKind.StaticKeyword)],
undefined,
factory.createIdentifier(AUTOMAPPER_METADATA_FACTORY_KEY),
Expand Down Expand Up @@ -396,7 +392,6 @@ export class ModelVisitor {
importDeclaration: ImportDeclaration
) {
return factory.createImportDeclaration(
importDeclaration.decorators,
importDeclaration.modifiers,
importDeclaration.importClause,
importDeclaration.moduleSpecifier
Expand Down
211 changes: 108 additions & 103 deletions packages/classes/transformer-plugin/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import {
isTypeNode,
LeftHandSideExpression,
Node,
NodeArray,
NodeBuilderFlags,
NodeFactory,
PropertyAccessExpression,
SyntaxKind,
Type,
Expand All @@ -23,29 +21,113 @@ import {
TypeReference,
} from 'typescript/lib/tsserverlibrary';

export function hasFlag(type: Type, flag: TypeFlags): boolean {
return (type.flags & flag) === flag;
}

export function isFilenameMatched(
patterns: string[],
filename: string
): boolean {
return patterns.some((path) => filename.includes(path));
}

export function isDynamicallyAdded(identifier: Node): boolean {
return identifier && !identifier.parent && identifier.pos === -1;
}

export function isNullableUnionType(type: Type): boolean {
return (
type.isUnion() &&
(type as unknown as { isNullableType: () => boolean }).isNullableType()
);
}

export function isArrayType(type: Type): boolean {
export function getDecoratorOrUndefinedByNames(
names: string[],
decorators?: readonly Decorator[]
): Decorator | undefined {
return (decorators || []).find((item) =>
names.includes(getDecoratorName(item) as string)
);
}

export function getTypeReference(
type: Type,
typeNode: TypeNode,
typeChecker: TypeChecker,
isArray = false
): [elementType: string | undefined, isArray: boolean] {
if (isArrayType(type) || isArrayTypeNode(typeNode)) {
const [arrayType] =
(type as TypeReference).typeArguments ||
(type as TypeReference).aliasTypeArguments ||
((typeNode as ArrayTypeNode).elementType
? [(typeNode as ArrayTypeNode).elementType]
: []) ||
[];
const isArrayTypeNode = isTypeNode(arrayType as TypeNode);
return getTypeReference(
isArrayTypeNode
? typeChecker.getTypeAtLocation(arrayType as TypeNode)
: (arrayType as Type),
isArrayTypeNode
? (arrayType as TypeNode)
: (typeChecker.typeToTypeNode(
arrayType as Type,
typeNode,
NodeBuilderFlags.NoTruncation
) as TypeNode),
typeChecker,
true
);
}

if (isBoolean(type)) {
return [Boolean.name, isArray];
}

if (isNumber(type) || isNumberEnum(type)) {
return [Number.name, isArray];
}

if (isString(type) || isStringEnum(type)) {
return [String.name, isArray];
}

if (isDate(type)) {
return [Date.name, isArray];
}

if (isBigInt(type)) {
return [BigInt.name, isArray];
}

if (type.isClass() || type.aliasSymbol) {
return [getText(type, typeChecker), isArray];
}

return [undefined, isArray];
}

export function replaceImportPath(
typeReference: string,
fileName: string
): string | undefined {
let importPath = /\("([^)]).+(")/.exec(typeReference)?.[0];
if (!importPath) {
return undefined;
}
importPath = importPath.slice(2, importPath.length - 1);

let relativePath = posix.relative(dirname(fileName), importPath);
relativePath = relativePath[0] !== '.' ? './' + relativePath : relativePath;
typeReference = typeReference.replace(importPath, relativePath);

return typeReference.replace('import', 'require');
}

function hasFlag(type: Type, flag: TypeFlags): boolean {
return (type.flags & flag) === flag;
}

function isDynamicallyAdded(identifier: Node): boolean {
return identifier && !identifier.parent && identifier.pos === -1;
}

function isArrayType(type: Type): boolean {
const symbol = type.getSymbol() || type.aliasSymbol;
if (!symbol) {
return false;
Expand All @@ -57,15 +139,15 @@ export function isArrayType(type: Type): boolean {
);
}

export function isBoolean(type: Type): boolean {
function isBoolean(type: Type): boolean {
return (
hasFlag(type, TypeFlags.Boolean) ||
(type.isUnionOrIntersection() &&
type.types[0].flags === TypeFlags.BooleanLiteral)
);
}

export function isEnumType(type: Type): boolean {
function isEnumType(type: Type): boolean {
if (hasFlag(type, TypeFlags.Enum)) {
return true;
}
Expand All @@ -83,11 +165,11 @@ export function isEnumType(type: Type): boolean {
);
}

export function isNumber(type: Type) {
function isNumber(type: Type) {
return hasFlag(type, TypeFlags.Number);
}

export function isNumberEnum(type: Type): boolean {
function isNumberEnum(type: Type): boolean {
const isEnum = isEnumType(type);
const valueDeclaration = type.getSymbol()
?.valueDeclaration as EnumDeclaration;
Expand All @@ -101,11 +183,11 @@ export function isNumberEnum(type: Type): boolean {
);
}

export function isString(type: Type): boolean {
function isString(type: Type): boolean {
return hasFlag(type, TypeFlags.String);
}

export function isStringEnum(type: Type): boolean {
function isStringEnum(type: Type): boolean {
const isEnum = isEnumType(type);
const valueDeclaration = type.getSymbol()
?.valueDeclaration as EnumDeclaration;
Expand All @@ -119,37 +201,31 @@ export function isStringEnum(type: Type): boolean {
);
}

export function isDate(type: Type) {
function isDate(type: Type) {
return type.symbol && type.symbol.escapedName === 'Date';
}

export function getDecoratorOrUndefinedByNames(
names: string[],
factory: NodeFactory,
decorators?: NodeArray<Decorator>
): Decorator | undefined {
return (decorators || factory.createNodeArray()).find((item) =>
names.includes(getDecoratorName(item) as string)
);
function isBigInt(type: Type) {
return type.symbol && type.symbol.escapedName === 'BigInt';
}

export function getDecoratorName(decorator: Decorator): string | undefined {
function getDecoratorName(decorator: Decorator): string {
const isDecoratorFactory =
decorator.expression.kind === SyntaxKind.CallExpression;
if (isDecoratorFactory) {
const callExpression = decorator.expression;
const expression = (callExpression as CallExpression).expression;
const identifier = expression as Identifier;
if (isDynamicallyAdded(identifier)) {
return undefined;
return '';
}

return getIdentifierFromExpression(expression).getText();
}
return getIdentifierFromExpression(decorator.expression).getText();
}

export function getNameFromExpression(
function getNameFromExpression(
expression: LeftHandSideExpression
): Identifier | LeftHandSideExpression {
if (expression && expression.kind === SyntaxKind.PropertyAccessExpression) {
Expand All @@ -158,7 +234,7 @@ export function getNameFromExpression(
return expression;
}

export function getIdentifierFromExpression(
function getIdentifierFromExpression(
expression: LeftHandSideExpression
): Identifier {
const identifier = getNameFromExpression(expression);
Expand All @@ -168,7 +244,7 @@ export function getIdentifierFromExpression(
return identifier as Identifier;
}

export function getText(
function getText(
type: Type,
typeChecker: TypeChecker,
enclosingNode?: Node,
Expand All @@ -181,7 +257,7 @@ export function getText(
return typeChecker.typeToString(type, compilerNode, typeFormatFlags);
}

export function getDefaultTypeFormatFlags(enclosingNode?: Node): number {
function getDefaultTypeFormatFlags(enclosingNode?: Node): number {
let formatFlags =
TypeFormatFlags.UseTypeOfFunction |
TypeFormatFlags.NoTruncation |
Expand All @@ -191,74 +267,3 @@ export function getDefaultTypeFormatFlags(enclosingNode?: Node): number {
formatFlags |= TypeFormatFlags.InTypeAlias;
return formatFlags;
}

export function getTypeReference(
type: Type,
typeNode: TypeNode,
typeChecker: TypeChecker,
isArray = false
): [elementType: string | undefined, isArray: boolean] {
if (isArrayType(type) || isArrayTypeNode(typeNode)) {
const [arrayType] =
(type as TypeReference).typeArguments ||
(type as TypeReference).aliasTypeArguments ||
((typeNode as ArrayTypeNode).elementType
? [(typeNode as ArrayTypeNode).elementType]
: []) ||
[];
const isArrayTypeNode = isTypeNode(arrayType as TypeNode);
return getTypeReference(
isArrayTypeNode
? typeChecker.getTypeAtLocation(arrayType as TypeNode)
: (arrayType as Type),
isArrayTypeNode
? (arrayType as TypeNode)
: (typeChecker.typeToTypeNode(
arrayType as Type,
typeNode,
NodeBuilderFlags.NoTruncation
) as TypeNode),
typeChecker,
true
);
}

if (isBoolean(type)) {
return [Boolean.name, isArray];
}

if (isNumber(type) || isNumberEnum(type)) {
return [Number.name, isArray];
}

if (isString(type) || isStringEnum(type)) {
return [String.name, isArray];
}

if (isDate(type)) {
return [Date.name, isArray];
}

if (type.isClass() || type.aliasSymbol) {
return [getText(type, typeChecker), isArray];
}

return [undefined, isArray];
}

export function replaceImportPath(
typeReference: string,
fileName: string
): string | undefined {
let importPath = /\("([^)]).+(")/.exec(typeReference)?.[0];
if (!importPath) {
return undefined;
}
importPath = importPath.slice(2, importPath.length - 1);

let relativePath = posix.relative(dirname(fileName), importPath);
relativePath = relativePath[0] !== '.' ? './' + relativePath : relativePath;
typeReference = typeReference.replace(importPath, relativePath);

return typeReference.replace('import', 'require');
}
Loading

0 comments on commit e82e9a1

Please sign in to comment.