@goboost/scanner-typescript 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +19 -0
- package/src/controller-extractor.ts +364 -0
- package/src/dto-extractor.ts +363 -0
- package/src/guard-extractor.ts +206 -0
- package/src/index.ts +284 -0
- package/src/prisma-schema-extractor.ts +345 -0
- package/src/unsupported-detector.ts +253 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unsupported feature detector - flags routes using V2 features
|
|
3
|
+
* Scans for @UseInterceptors, @UsePipes, @UseFilters, custom decorators
|
|
4
|
+
*/
|
|
5
|
+
import { Project, SourceFile, ClassDeclaration, MethodDeclaration, Decorator } from 'ts-morph';
|
|
6
|
+
|
|
7
|
+
interface RouteMapping {
|
|
8
|
+
handlerName: string;
|
|
9
|
+
unsupportedFeatures: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// V1 unsupported decorators
|
|
13
|
+
const UNSUPPORTED_DECORATORS = [
|
|
14
|
+
'UseInterceptors',
|
|
15
|
+
'UsePipes',
|
|
16
|
+
'UseFilters',
|
|
17
|
+
'SetMetadata',
|
|
18
|
+
'Redirect',
|
|
19
|
+
'Render',
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
// Parameter decorators that are not supported
|
|
23
|
+
const UNSUPPORTED_PARAM_DECORATORS = [
|
|
24
|
+
'Req',
|
|
25
|
+
'Res',
|
|
26
|
+
'Next',
|
|
27
|
+
'Session',
|
|
28
|
+
'Headers',
|
|
29
|
+
'Ip',
|
|
30
|
+
'HostParam',
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
// Patterns that indicate complex async behavior
|
|
34
|
+
const UNSUPPORTED_PATTERNS = [
|
|
35
|
+
'EventEmitter',
|
|
36
|
+
'Observable',
|
|
37
|
+
'MessagePattern',
|
|
38
|
+
'EventPattern',
|
|
39
|
+
'WebSocket',
|
|
40
|
+
'WsGateway',
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Detect unsupported features for a specific route
|
|
45
|
+
*/
|
|
46
|
+
export function detectUnsupported(
|
|
47
|
+
project: Project,
|
|
48
|
+
route: RouteMapping
|
|
49
|
+
): string[] {
|
|
50
|
+
const unsupported: string[] = [];
|
|
51
|
+
|
|
52
|
+
// Find the method in the project
|
|
53
|
+
for (const sourceFile of project.getSourceFiles()) {
|
|
54
|
+
for (const classDecl of sourceFile.getClasses()) {
|
|
55
|
+
const method = classDecl.getMethod(route.handlerName);
|
|
56
|
+
if (method) {
|
|
57
|
+
collectUnsupportedDecorators(method, unsupported);
|
|
58
|
+
collectUnsupportedParamDecorators(method, unsupported);
|
|
59
|
+
collectUnsupportedPatterns(method, unsupported);
|
|
60
|
+
return unsupported;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return unsupported;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Detect unsupported features for all routes in a controller class
|
|
70
|
+
*/
|
|
71
|
+
export function detectUnsupportedForController(
|
|
72
|
+
classDecl: ClassDeclaration
|
|
73
|
+
): Map<string, string[]> {
|
|
74
|
+
const routeUnsupported = new Map<string, string[]>();
|
|
75
|
+
|
|
76
|
+
for (const method of classDecl.getMethods()) {
|
|
77
|
+
const methodName = method.getName();
|
|
78
|
+
|
|
79
|
+
// Only check methods that are route handlers (have HTTP method decorators)
|
|
80
|
+
const httpDecorators = ['Get', 'Post', 'Put', 'Delete', 'Patch'];
|
|
81
|
+
const hasHttpDecorator = method.getDecorators().some(d =>
|
|
82
|
+
httpDecorators.includes(d.getName())
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (hasHttpDecorator) {
|
|
86
|
+
const unsupported: string[] = [];
|
|
87
|
+
collectUnsupportedDecorators(method, unsupported);
|
|
88
|
+
collectUnsupportedParamDecorators(method, unsupported);
|
|
89
|
+
collectUnsupportedPatterns(method, unsupported);
|
|
90
|
+
|
|
91
|
+
if (unsupported.length > 0) {
|
|
92
|
+
routeUnsupported.set(methodName, unsupported);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return routeUnsupported;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Check if a class has any controller-level unsupported features
|
|
102
|
+
*/
|
|
103
|
+
export function detectClassLevelUnsupported(classDecl: ClassDeclaration): string[] {
|
|
104
|
+
const unsupported: string[] = [];
|
|
105
|
+
|
|
106
|
+
for (const decorator of classDecl.getDecorators()) {
|
|
107
|
+
const name = decorator.getName();
|
|
108
|
+
if (UNSUPPORTED_DECORATORS.includes(name)) {
|
|
109
|
+
unsupported.push(`${name} decorator at class level`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return unsupported;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function collectUnsupportedDecorators(
|
|
117
|
+
method: MethodDeclaration,
|
|
118
|
+
unsupported: string[]
|
|
119
|
+
): void {
|
|
120
|
+
const decorators = method.getDecorators();
|
|
121
|
+
|
|
122
|
+
for (const decorator of decorators) {
|
|
123
|
+
const name = decorator.getName();
|
|
124
|
+
|
|
125
|
+
// Check for unsupported method decorators
|
|
126
|
+
if (UNSUPPORTED_DECORATORS.includes(name)) {
|
|
127
|
+
const feature = getFeatureName(name);
|
|
128
|
+
if (!unsupported.includes(feature)) {
|
|
129
|
+
unsupported.push(feature);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Check for custom decorators (not standard NestJS)
|
|
134
|
+
if (isCustomDecorator(decorator)) {
|
|
135
|
+
const customFeature = `custom decorator: ${name}`;
|
|
136
|
+
if (!unsupported.includes(customFeature)) {
|
|
137
|
+
unsupported.push(customFeature);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function collectUnsupportedParamDecorators(
|
|
144
|
+
method: MethodDeclaration,
|
|
145
|
+
unsupported: string[]
|
|
146
|
+
): void {
|
|
147
|
+
for (const param of method.getParameters()) {
|
|
148
|
+
for (const decorator of param.getDecorators()) {
|
|
149
|
+
const name = decorator.getName();
|
|
150
|
+
if (UNSUPPORTED_PARAM_DECORATORS.includes(name)) {
|
|
151
|
+
const feature = `${name}() parameter`;
|
|
152
|
+
if (!unsupported.includes(feature)) {
|
|
153
|
+
unsupported.push(feature);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function collectUnsupportedPatterns(
|
|
161
|
+
method: MethodDeclaration,
|
|
162
|
+
unsupported: string[]
|
|
163
|
+
): void {
|
|
164
|
+
const bodyText = method.getBodyText();
|
|
165
|
+
if (!bodyText) return;
|
|
166
|
+
|
|
167
|
+
for (const pattern of UNSUPPORTED_PATTERNS) {
|
|
168
|
+
if (bodyText.includes(pattern)) {
|
|
169
|
+
const feature = `complex pattern: ${pattern}`;
|
|
170
|
+
if (!unsupported.includes(feature)) {
|
|
171
|
+
unsupported.push(feature);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Check for file upload patterns
|
|
177
|
+
if (bodyText.includes('FileInterceptor') || bodyText.includes('FilesInterceptor') ||
|
|
178
|
+
bodyText.includes('UploadedFile') || bodyText.includes('UploadedFiles')) {
|
|
179
|
+
const feature = 'file upload';
|
|
180
|
+
if (!unsupported.includes(feature)) {
|
|
181
|
+
unsupported.push(feature);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Check for SSE/WebSocket patterns
|
|
186
|
+
if (bodyText.includes('Sse') || bodyText.includes('MessageEvent')) {
|
|
187
|
+
const feature = 'Server-Sent Events';
|
|
188
|
+
if (!unsupported.includes(feature)) {
|
|
189
|
+
unsupported.push(feature);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function getFeatureName(decoratorName: string): string {
|
|
195
|
+
const featureMap: Record<string, string> = {
|
|
196
|
+
'UseInterceptors': 'interceptor',
|
|
197
|
+
'UsePipes': 'pipe',
|
|
198
|
+
'UseFilters': 'exception filter',
|
|
199
|
+
'SetMetadata': 'custom metadata',
|
|
200
|
+
'Redirect': 'redirect',
|
|
201
|
+
'Render': 'view rendering',
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
return featureMap[decoratorName] || decoratorName.toLowerCase();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function isCustomDecorator(decorator: Decorator): boolean {
|
|
208
|
+
const knownDecorators = [
|
|
209
|
+
// HTTP methods
|
|
210
|
+
'Get', 'Post', 'Put', 'Delete', 'Patch', 'Options', 'Head', 'All',
|
|
211
|
+
// Parameters
|
|
212
|
+
'Body', 'Query', 'Param', 'Headers', 'Ip', 'HostParam',
|
|
213
|
+
// Guards
|
|
214
|
+
'UseGuards',
|
|
215
|
+
// Common
|
|
216
|
+
'HttpCode', 'Header', 'Redirect', 'Render',
|
|
217
|
+
// NestJS core
|
|
218
|
+
'Injectable', 'Controller', 'Module', 'Inject',
|
|
219
|
+
// Hybrid
|
|
220
|
+
'HybridRoute',
|
|
221
|
+
// Validation
|
|
222
|
+
...getValidationDecorators(),
|
|
223
|
+
];
|
|
224
|
+
|
|
225
|
+
return !knownDecorators.includes(decorator.getName());
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function getValidationDecorators(): string[] {
|
|
229
|
+
return [
|
|
230
|
+
'IsString', 'IsNumber', 'IsEmail', 'IsInt', 'IsBoolean',
|
|
231
|
+
'Min', 'Max', 'MinLength', 'MaxLength', 'IsOptional',
|
|
232
|
+
'IsDate', 'IsArray', 'IsEnum', 'IsUUID', 'IsUrl',
|
|
233
|
+
'Matches', 'IsNotEmpty', 'IsDefined', 'IsPositive',
|
|
234
|
+
];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Check if a route can be generated (no unsupported features)
|
|
239
|
+
*/
|
|
240
|
+
export function canGenerate(unsupportedFeatures: string[]): boolean {
|
|
241
|
+
return unsupportedFeatures.length === 0;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Get a human-readable summary of unsupported features
|
|
246
|
+
*/
|
|
247
|
+
export function getUnsupportedSummary(unsupportedFeatures: string[]): string {
|
|
248
|
+
if (unsupportedFeatures.length === 0) {
|
|
249
|
+
return 'All features supported';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return `Unsupported features: ${unsupportedFeatures.join(', ')}`;
|
|
253
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"declarationMap": true,
|
|
14
|
+
"sourceMap": true,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"moduleResolution": "node"
|
|
17
|
+
},
|
|
18
|
+
"include": ["src/**/*"],
|
|
19
|
+
"exclude": ["node_modules", "dist"]
|
|
20
|
+
}
|