@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.
@@ -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
+ }