Skip to content

Commit

Permalink
feat(experimental/transformer-plugin): add transformer plugin for cla…
Browse files Browse the repository at this point in the history
…sses package
  • Loading branch information
nartc committed Feb 18, 2021
1 parent eaef681 commit 67cdd29
Show file tree
Hide file tree
Showing 33 changed files with 762 additions and 12 deletions.
7 changes: 6 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,13 @@
"type:plugin",
"type:integration",
"type:type-defs",
"type:util"
"type:util",
"type:transformer-plugin"
]
},
{
"sourceTag": "type:transformer-plugin",
"onlyDependOnLibsWithTags": ["type:plugin"]
}
]
}
Expand Down
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ module.exports = {
'<rootDir>/packages/pojos',
'<rootDir>/packages/nestjs',
'<rootDir>/packages/nestjs-integration-test',
'<rootDir>/packages/experimental/transformer-plugin',
],
};
49 changes: 38 additions & 11 deletions nx.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
{
"npmScope": "automapper",
"affected": { "defaultBase": "main" },
"affected": {
"defaultBase": "main"
},
"implicitDependencies": {
"workspace.json": "*",
"package.json": { "dependencies": "*", "devDependencies": "*" },
"package.json": {
"dependencies": "*",
"devDependencies": "*"
},
"tsconfig.base.json": "*",
"tslint.json": "*",
".eslintrc.json": "*",
Expand All @@ -12,17 +17,39 @@
"tasksRunnerOptions": {
"default": {
"runner": "@nrwl/workspace/tasks-runners/default",
"options": { "cacheableOperations": ["build", "lint", "test", "e2e"] }
"options": {
"cacheableOperations": ["build", "lint", "test", "e2e"]
}
}
},
"projects": {
"core": { "tags": ["type:library"] },
"classes": { "tags": ["type:plugin"] },
"types": { "tags": ["type:type-defs"] },
"integration-test": { "tags": ["type:test"] },
"pojos": { "tags": ["type:plugin"] },
"nestjs": { "tags": ["type:integration"] },
"nestjs-integration-test": { "tags": ["type:test"] }
"core": {
"tags": ["type:library"]
},
"classes": {
"tags": ["type:plugin"]
},
"types": {
"tags": ["type:type-defs"]
},
"integration-test": {
"tags": ["type:test"]
},
"pojos": {
"tags": ["type:plugin"]
},
"nestjs": {
"tags": ["type:integration"]
},
"nestjs-integration-test": {
"tags": ["type:test"]
},
"experimental-transformer-plugin": {
"tags": ["type:transformer-plugin"]
}
},
"workspaceLayout": { "appsDir": "packages", "libsDir": "packages" }
"workspaceLayout": {
"appsDir": "packages",
"libsDir": "packages"
}
}
5 changes: 5 additions & 0 deletions packages/experimental/transformer-plugin/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"extends": "../../../.eslintrc.json",
"ignorePatterns": ["!**/*"],
"rules": {}
}
7 changes: 7 additions & 0 deletions packages/experimental/transformer-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# packages-experimental--transformer-plugin

This library was generated with [Nx](https://nx.dev).

## Running unit tests

Run `nx test packages-experimental--transformer-plugin` to execute the unit tests via [Jest](https://jestjs.io).
15 changes: 15 additions & 0 deletions packages/experimental/transformer-plugin/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module.exports = {
displayName: 'experimental-transformer-plugin',
preset: '../../../jest.preset.js',
globals: {
'ts-jest': {
tsConfig: '<rootDir>/tsconfig.spec.json',
},
},
transform: {
'^.+\\.[tj]sx?$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory:
'../../../coverage/packages/experimental/transformer-plugin',
};
25 changes: 25 additions & 0 deletions packages/experimental/transformer-plugin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@automapper/experimental/transformer-plugin",
"version": "2.2.1",
"sideEffects": false,
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/nartc/mapper/tree/main/packages/experimental/transformer-plugin"
},
"author": {
"name": "Chau Tran",
"email": "nartc7789@gmail.com",
"url": "https://nartc.me"
},
"description": "AutoMapper TypeScript Transformer Plugin (experimental)",
"keywords": [
"typescript",
"automapper",
"mapper",
"nx"
],
"license": "MIT"
}
39 changes: 39 additions & 0 deletions packages/experimental/transformer-plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as tss from 'typescript/lib/tsserverlibrary';
import { AutomapperTransformerPluginOptions } from './lib/interfaces';
import { ModelVisitor } from './lib/model-visitor';
import { isFilenameMatched } from './lib/plugin-utils';

const modelVisitor = new ModelVisitor();
const defaultOptions: AutomapperTransformerPluginOptions = {
modelFileNameSuffix: ['.model.ts', '.dto.ts', '.vm.ts'],
};

export default function automapperTransformerPlugin(
program: tss.Program,
options: AutomapperTransformerPluginOptions = defaultOptions
) {
return {
before(context: tss.TransformationContext) {
modelVisitor.reset();
return (sourceFile: tss.SourceFile): tss.SourceFile => {
if (
isFilenameMatched(
options.modelFileNameSuffix || [],
sourceFile.fileName
)
) {
return modelVisitor.visit(sourceFile, context, program);
}
return sourceFile;
};
},
};
}

export const before = (
options: AutomapperTransformerPluginOptions = defaultOptions,
program: tss.Program
) => automapperTransformerPlugin(program, options).before;

export * from './lib/constants';
export * from './lib/interfaces';
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import * as tss from 'typescript/lib/tsserverlibrary';
import { isDynamicallyAdded } from './is-dynamically-added.util';

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

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

export function getNameFromExpression(
expression: tss.LeftHandSideExpression
): tss.Identifier | tss.LeftHandSideExpression {
if (
expression &&
expression.kind === tss.SyntaxKind.PropertyAccessExpression
) {
return (expression as tss.PropertyAccessExpression).name as tss.Identifier;
}
return expression;
}

export function getIdentifierFromExpression(
expression: tss.LeftHandSideExpression
): tss.Identifier {
const identifier = getNameFromExpression(expression);
if (identifier && identifier.kind !== tss.SyntaxKind.Identifier) {
throw new Error();
}
return identifier as tss.Identifier;
}

export function getText(
type: tss.Type,
typeChecker: tss.TypeChecker,
enclosingNode?: tss.Node,
typeFormatFlags?: tss.TypeFormatFlags
): string {
if (!typeFormatFlags) {
typeFormatFlags = getDefaultTypeFormatFlags(enclosingNode);
}
const compilerNode = !enclosingNode ? undefined : enclosingNode;
return typeChecker.typeToString(type, compilerNode, typeFormatFlags);
}

export function getDefaultTypeFormatFlags(enclosingNode?: tss.Node): number {
let formatFlags =
tss.TypeFormatFlags.UseTypeOfFunction |
tss.TypeFormatFlags.NoTruncation |
tss.TypeFormatFlags.UseFullyQualifiedType |
tss.TypeFormatFlags.WriteTypeArgumentsOfSignature;
if (
enclosingNode &&
enclosingNode.kind === tss.SyntaxKind.TypeAliasDeclaration
)
formatFlags |= tss.TypeFormatFlags.InTypeAlias;
return formatFlags;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as tss from 'typescript/lib/tsserverlibrary';

export function hasFlag(type: tss.Type, flag: tss.TypeFlags): boolean {
return (type.flags & flag) === flag;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as tss from 'typescript/lib/tsserverlibrary';
import { isDynamicallyAdded } from './is-dynamically-added.util';

export function hasPropertyKey(
key: string,
properties: tss.NodeArray<tss.PropertyAssignment>
): boolean {
return properties
.filter((item) => !isDynamicallyAdded(item))
.some((item) => item.name.getText() === key);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export * from './is-enum-type.util';
export * from './is-date.util';
export * from './is-boolean.util';
export * from './is-number.util';
export * from './is-string.util';
export * from './is-array-type.util';

export * from './is-dynamically-added.util';
export * from './is-nullable-union-type.util';
export * from './is-any-type.util';
export * from './is-type-literal.util';

export * from './has-property-key.util';
export * from './has-flag.util';

export * from './get.util';
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as tss from 'typescript/lib/tsserverlibrary';

export function isAnyType(
type: tss.Type,
typeChecker: tss.TypeChecker,
node: tss.PropertyDeclaration | tss.GetAccessorDeclaration
): boolean {
return (
typeChecker.typeToString(type) === 'any' &&
!!node.type &&
node.type.kind === tss.SyntaxKind.AnyKeyword
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as tss from 'typescript/lib/tsserverlibrary';

export function isArrayType(type: tss.Type): boolean {
const symbol = type.getSymbol();
if (!symbol) {
return false;
}
return (
symbol.getName() === 'Array' &&
(type as tss.TypeReference).typeArguments.length === 1
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as tss from 'typescript/lib/tsserverlibrary';
import { hasFlag } from './has-flag.util';

export function isBoolean(type: tss.Type): boolean {
return (
hasFlag(type, tss.TypeFlags.Boolean) ||
(type.isUnionOrIntersection() &&
type.types[0].flags === tss.TypeFlags.BooleanLiteral)
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as tss from 'typescript/lib/tsserverlibrary';

export function isDate(type: tss.Type) {
return type.symbol && type.symbol.escapedName === 'Date';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as tss from 'typescript/lib/tsserverlibrary';

export function isDynamicallyAdded(identifier: tss.Node): boolean {
return identifier && !identifier.parent && identifier.pos === -1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as tss from 'typescript/lib/tsserverlibrary';
import { hasFlag } from './has-flag.util';

export function isEnumType(type: tss.Type): boolean {
if (hasFlag(type, tss.TypeFlags.Enum)) {
return true;
}

if (hasFlag(type, tss.TypeFlags.EnumLiteral) && !type.isUnion()) return false;

const symbol = type.getSymbol();
if (symbol == null) {
return false;
}
const { valueDeclaration } = symbol;
return (
valueDeclaration != null &&
valueDeclaration.kind === tss.SyntaxKind.EnumDeclaration
);
}

export function isStringEnum(type: tss.Type): boolean {
const isEnum = isEnumType(type);
const valueDeclaration = type.getSymbol()
?.valueDeclaration as tss.EnumDeclaration;

return (
(isEnum &&
valueDeclaration?.members?.some(
(member: tss.EnumMember) => member.initializer != null
)) ||
false
);
}

export function isNumberEnum(type: tss.Type): boolean {
const isEnum = isEnumType(type);
const valueDeclaration = type.getSymbol()
?.valueDeclaration as tss.EnumDeclaration;

return (
(isEnum &&
valueDeclaration?.members?.some(
(member: tss.EnumMember) => member.initializer == null
)) ||
false
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as tss from 'typescript/lib/tsserverlibrary';

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

0 comments on commit 67cdd29

Please sign in to comment.