Skip to content

Commit 780bffe

Browse files
committed
Automagically declare Props/SecuredProps to RegisterResource classes
1 parent 4338ea6 commit 780bffe

File tree

1 file changed

+78
-1
lines changed

1 file changed

+78
-1
lines changed

src/core/resources/plugin/resources.visitor.cts

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
11
import type { ReadonlyVisitor } from '@nestjs/cli/lib/compiler/interfaces/readonly-visitor.interface';
2+
import { hasDecorators } from '@nestjs/graphql/dist/plugin/utils/ast-utils.js';
23
import * as ts from 'typescript';
34

5+
const securedKeys = ['value', 'canRead', 'canEdit'];
6+
47
export class ResourceVisitor {
58
constructor(readonly readonly = false) {}
69

710
visit(sf: ts.SourceFile, ctx: ts.TransformationContext, program: ts.Program) {
11+
if (!sf.fileName.endsWith('dto.ts')) {
12+
return sf;
13+
}
14+
const { factory } = ctx;
815
const visitNode = (node: ts.Node): ts.Node => {
9-
// TODO logic
16+
const decorators =
17+
(ts.canHaveDecorators(node) && ts.getDecorators(node)) || [];
18+
if (
19+
ts.isClassDeclaration(node) &&
20+
hasDecorators(decorators, ['RegisterResource'])
21+
) {
22+
return this.enhanceDtoClass(node, program, factory);
23+
}
1024

1125
if (this.readonly) {
1226
ts.forEachChild(node, visitNode);
@@ -17,6 +31,69 @@ export class ResourceVisitor {
1731
};
1832
return ts.visitNode(sf, visitNode);
1933
}
34+
35+
private enhanceDtoClass(
36+
classNode: ts.ClassDeclaration,
37+
program: ts.Program,
38+
factory: ts.NodeFactory,
39+
) {
40+
const typeChecker = program.getTypeChecker();
41+
42+
const classProps = typeChecker
43+
.getTypeAtLocation(classNode)
44+
.getApparentProperties();
45+
46+
const securedProps = classProps.flatMap((member) => {
47+
const memberTypeProps = member.valueDeclaration
48+
? typeChecker.getTypeAtLocation(member.valueDeclaration).getProperties()
49+
: [];
50+
51+
const isSecured = securedKeys.every((securedKey) =>
52+
memberTypeProps.find((k) => k.getName() === securedKey),
53+
);
54+
return isSecured ? member : [];
55+
});
56+
57+
return this.updateClassMembers(factory, classNode, [
58+
this.createStaticPropArray(factory, 'Props', classProps),
59+
this.createStaticPropArray(factory, 'SecuredProps', securedProps),
60+
...classNode.members,
61+
]);
62+
}
63+
64+
private createStaticPropArray(
65+
factory: ts.NodeFactory,
66+
name: string,
67+
members: ts.Symbol[],
68+
) {
69+
return factory.createPropertyDeclaration(
70+
[
71+
factory.createModifier(ts.SyntaxKind.StaticKeyword),
72+
factory.createModifier(ts.SyntaxKind.ReadonlyKeyword),
73+
],
74+
factory.createIdentifier(name),
75+
undefined,
76+
undefined,
77+
factory.createArrayLiteralExpression(
78+
members.map((p) => factory.createStringLiteral(p.getName(), true)),
79+
),
80+
);
81+
}
82+
83+
private updateClassMembers(
84+
factory: ts.NodeFactory,
85+
classNode: ts.ClassDeclaration,
86+
newMembers: readonly ts.ClassElement[],
87+
) {
88+
return factory.updateClassDeclaration(
89+
classNode,
90+
classNode.modifiers,
91+
classNode.name,
92+
classNode.typeParameters,
93+
classNode.heritageClauses,
94+
newMembers,
95+
);
96+
}
2097
}
2198

2299
export class ResourceReadonlyVisitor implements ReadonlyVisitor {

0 commit comments

Comments
 (0)