Skip to content

Commit

Permalink
feat: add import declaration in visitor
Browse files Browse the repository at this point in the history
  • Loading branch information
jbl428 committed May 7, 2023
1 parent 99c88a5 commit b03bd8a
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 52 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ rules:
caseInsensitive: true
'@typescript-eslint/no-extraneous-class': 0
'@typescript-eslint/no-explicit-any': 0
no-console:
- error
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`HttpInterfaceVisitor > should handle array return type 1`] = `
"import { HttpInterface, GetExchange } from '@r2don/nest-http-interface';
"\\"use strict\\";
Object.defineProperty(exports, \\"__esModule\\", { value: true });
const eager_module_1 = require(\\"@r2don/nest-http-interface\\");
const nest_http_interface_1 = require(\\"@r2don/nest-http-interface\\");
class ResponseClass {
}
let UserService = class UserService {
Expand All @@ -16,25 +19,27 @@ let UserService = class UserService {
}
};
__decorate([
GetExchange(),
ResponseBody(ResponseClass)
(0, nest_http_interface_1.GetExchange)(),
eager_module_1.ResponseBody(ResponseClass)
], UserService.prototype, \\"getUsers\\", null);
__decorate([
GetExchange(),
ResponseBody(ResponseClass)
(0, nest_http_interface_1.GetExchange)(),
eager_module_1.ResponseBody(ResponseClass)
], UserService.prototype, \\"getUserList\\", null);
__decorate([
GetExchange(),
ResponseBody(ResponseClass)
(0, nest_http_interface_1.GetExchange)(),
eager_module_1.ResponseBody(ResponseClass)
], UserService.prototype, \\"getUsersReadonly\\", null);
UserService = __decorate([
HttpInterface()
(0, nest_http_interface_1.HttpInterface)()
], UserService);
"
`;

exports[`HttpInterfaceVisitor > should ignore if file name is not match 1`] = `
"import { HttpInterface, GraphQLExchange } from '@r2don/nest-http-interface';
"\\"use strict\\";
Object.defineProperty(exports, \\"__esModule\\", { value: true });
const nest_http_interface_1 = require(\\"@r2don/nest-http-interface\\");
class ResponseClass {
}
let UserService = class UserService {
Expand All @@ -43,67 +48,76 @@ let UserService = class UserService {
}
};
__decorate([
GraphQLExchange()
(0, nest_http_interface_1.GraphQLExchange)()
], UserService.prototype, \\"getUser\\", null);
UserService = __decorate([
HttpInterface()
(0, nest_http_interface_1.HttpInterface)()
], UserService);
"
`;

exports[`HttpInterfaceVisitor > should ignore if method has ResponseBody decorator 1`] = `
"import { HttpInterface, PostExchange, ResponseBody } from '@r2don/nest-http-interface';
import { User } from './user.entity';
"\\"use strict\\";
Object.defineProperty(exports, \\"__esModule\\", { value: true });
const nest_http_interface_1 = require(\\"@r2don/nest-http-interface\\");
const user_entity_1 = require(\\"./user.entity\\");
let UserService = class UserService {
async getUser() {
throw new Error('not implemented');
}
};
__decorate([
PostExchange(),
ResponseBody(User)
(0, nest_http_interface_1.PostExchange)(),
(0, nest_http_interface_1.ResponseBody)(user_entity_1.User)
], UserService.prototype, \\"getUser\\", null);
UserService = __decorate([
HttpInterface()
(0, nest_http_interface_1.HttpInterface)()
], UserService);
"
`;

exports[`HttpInterfaceVisitor > should ignore if return type if not a promise 1`] = `
"import { HttpInterface, GetExchange } from '@r2don/nest-http-interface';
"\\"use strict\\";
Object.defineProperty(exports, \\"__esModule\\", { value: true });
const nest_http_interface_1 = require(\\"@r2don/nest-http-interface\\");
let UserService = class UserService {
getUser() {
throw new Error('not implemented');
}
};
__decorate([
GetExchange()
(0, nest_http_interface_1.GetExchange)()
], UserService.prototype, \\"getUser\\", null);
UserService = __decorate([
HttpInterface()
(0, nest_http_interface_1.HttpInterface)()
], UserService);
"
`;

exports[`HttpInterfaceVisitor > should ignore if return type is not a class 1`] = `
"import { HttpInterface, GetExchange } from '@r2don/nest-http-interface';
"\\"use strict\\";
Object.defineProperty(exports, \\"__esModule\\", { value: true });
const nest_http_interface_1 = require(\\"@r2don/nest-http-interface\\");
let TextService = class TextService {
constructor() { }
async getText() {
return 'text';
}
};
__decorate([
GetExchange()
(0, nest_http_interface_1.GetExchange)()
], TextService.prototype, \\"getText\\", null);
TextService = __decorate([
HttpInterface()
(0, nest_http_interface_1.HttpInterface)()
], TextService);
"
`;

exports[`HttpInterfaceVisitor > should override plugin suffix option 1`] = `
"import { HttpInterface, GraphQLExchange } from '@r2don/nest-http-interface';
"\\"use strict\\";
Object.defineProperty(exports, \\"__esModule\\", { value: true });
const eager_module_1 = require(\\"@r2don/nest-http-interface\\");
const nest_http_interface_1 = require(\\"@r2don/nest-http-interface\\");
class ResponseClass {
}
let UserService = class UserService {
Expand All @@ -112,11 +126,11 @@ let UserService = class UserService {
}
};
__decorate([
GraphQLExchange(),
ResponseBody(ResponseClass)
(0, nest_http_interface_1.GraphQLExchange)(),
eager_module_1.ResponseBody(ResponseClass)
], UserService.prototype, \\"getUser\\", null);
UserService = __decorate([
HttpInterface()
(0, nest_http_interface_1.HttpInterface)()
], UserService);
"
`;
2 changes: 1 addition & 1 deletion lib/plugin/visitors/http-interface.visitor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { before } from '../compiler-plugin';

describe('HttpInterfaceVisitor', () => {
const compilerOptions = {
module: ts.ModuleKind.ES2020,
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2020,
newLine: ts.NewLineKind.LineFeed,
noEmitHelpers: true,
Expand Down
99 changes: 74 additions & 25 deletions lib/plugin/visitors/http-interface.visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import { GraphQLExchange } from '../../decorators/graphql-exchange.decorator';
import { ResponseBody } from '../../decorators/response-body.decorator';

export class HttpInterfaceVisitor {
static HTTP_INTERFACE_DECORATOR = HttpInterface.name;
static RESPONSE_BODY_DECORATOR = ResponseBody.name;
static ALL_EXCHANGE_DECORATORS = [
HTTP_INTERFACE_DECORATOR = HttpInterface.name;
RESPONSE_BODY_DECORATOR = ResponseBody.name;
ALL_EXCHANGE_DECORATORS = [
GetExchange.name,
PostExchange.name,
PutExchange.name,
Expand All @@ -26,42 +26,87 @@ export class HttpInterfaceVisitor {
GraphQLExchange.name,
];

libModuleAlias = 'eager_module_1';
libName = '@r2don/nest-http-interface';
importSet = new Set<string>();

visit(
sourceFile: ts.SourceFile,
ctx: ts.TransformationContext,
_program: ts.Program,
): ts.Node {
this.importSet.clear();
const factory = ctx.factory;
const visitNode = (node: ts.Node): ts.Node => {
if (!this.isHttpInterfaceClass(node)) {
return ts.visitEachChild(node, visitNode, ctx);
if (this.isHttpInterfaceClass(node)) {
return this.visitMethods(node, factory);
}

if (ts.isSourceFile(node)) {
return this.updateSourceFile(node, visitNode, ctx, factory);
}

const updatedMembers = node.members.map((member) => {
if (!this.isExchangeMethod(member)) {
return member;
}

return this.appendResponseBodyDecorator(member, factory);
});

return factory.updateClassDeclaration(
node,
this.getDecorators(node),
node.name,
node.typeParameters,
node.heritageClauses,
updatedMembers,
);
return ts.visitEachChild(node, visitNode, ctx);
};

return ts.visitNode(sourceFile, visitNode);
}

private updateSourceFile(
node: ts.SourceFile,
visitNode: (node: ts.Node) => ts.Node,
ctx: ts.TransformationContext,
factory: ts.NodeFactory,
): ts.SourceFile {
const visitedNode = ts.visitEachChild(node, visitNode, ctx);
if (this.importSet.size === 0) {
return visitedNode;
}

const importStatements = [...this.importSet].map((value) =>
factory.createImportEqualsDeclaration(
undefined,
false,
value,
factory.createExternalModuleReference(
factory.createStringLiteral(this.libName),
),
),
);

const existingStatements = Array.from(visitedNode.statements);
return factory.updateSourceFile(visitedNode, [
...importStatements,
...existingStatements,
]);
}

private visitMethods(
node: ts.ClassDeclaration,
factory: ts.NodeFactory,
): ts.ClassDeclaration {
const updatedMembers = node.members.map((member) => {
if (!this.isExchangeMethod(member)) {
return member;
}

return this.appendResponseBodyDecorator(member, factory);
});

return factory.updateClassDeclaration(
node,
node.modifiers,
node.name,
node.typeParameters,
node.heritageClauses,
updatedMembers,
);
}

private isHttpInterfaceClass(node: ts.Node): node is ts.ClassDeclaration {
return (
ts.isClassDeclaration(node) &&
this.hasDecorator(node, [HttpInterfaceVisitor.HTTP_INTERFACE_DECORATOR])
this.hasDecorator(node, [this.HTTP_INTERFACE_DECORATOR])
);
}

Expand All @@ -75,9 +120,13 @@ export class HttpInterfaceVisitor {
return node;
}

this.importSet.add(this.libModuleAlias);
const decorator = factory.createDecorator(
factory.createCallExpression(
factory.createIdentifier('ResponseBody'),
factory.createPropertyAccessExpression(
factory.createIdentifier(this.libModuleAlias),
factory.createIdentifier(this.RESPONSE_BODY_DECORATOR),
),
[],
[factory.createIdentifier(returnType)],
),
Expand Down Expand Up @@ -151,8 +200,8 @@ export class HttpInterfaceVisitor {
member: ts.ClassElement,
): member is ts.MethodDeclaration {
return (
this.hasDecorator(member, HttpInterfaceVisitor.ALL_EXCHANGE_DECORATORS) &&
!this.hasDecorator(member, [HttpInterfaceVisitor.RESPONSE_BODY_DECORATOR])
this.hasDecorator(member, this.ALL_EXCHANGE_DECORATORS) &&
!this.hasDecorator(member, [this.RESPONSE_BODY_DECORATOR])
);
}

Expand Down

0 comments on commit b03bd8a

Please sign in to comment.