@goboost/scanner-typescript 1.2.1 → 1.2.2
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/dist/controller-extractor.d.ts +40 -0
- package/dist/controller-extractor.d.ts.map +1 -0
- package/dist/controller-extractor.js +288 -0
- package/dist/controller-extractor.js.map +1 -0
- package/dist/dto-extractor.d.ts +30 -0
- package/dist/dto-extractor.d.ts.map +1 -0
- package/dist/dto-extractor.js +294 -0
- package/dist/dto-extractor.js.map +1 -0
- package/dist/guard-extractor.d.ts +15 -0
- package/dist/guard-extractor.d.ts.map +1 -0
- package/dist/guard-extractor.js +158 -0
- package/dist/guard-extractor.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +232 -0
- package/dist/index.js.map +1 -0
- package/dist/prisma-schema-extractor.d.ts +47 -0
- package/dist/prisma-schema-extractor.d.ts.map +1 -0
- package/dist/prisma-schema-extractor.js +298 -0
- package/dist/prisma-schema-extractor.js.map +1 -0
- package/dist/unsupported-detector.d.ts +31 -0
- package/dist/unsupported-detector.d.ts.map +1 -0
- package/dist/unsupported-detector.js +204 -0
- package/dist/unsupported-detector.js.map +1 -0
- package/package.json +11 -1
- package/tsconfig.json +0 -20
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Controller extractor - finds @HybridRoute + @Controller decorated classes
|
|
3
|
+
* Extracts basePath, method decorators, handler names, path/query params, and guard decorators
|
|
4
|
+
*/
|
|
5
|
+
import { Project } from 'ts-morph';
|
|
6
|
+
interface ControllerSpec {
|
|
7
|
+
name: string;
|
|
8
|
+
basePath: string;
|
|
9
|
+
sourceFile: string;
|
|
10
|
+
guards: string[];
|
|
11
|
+
dependencies: string[];
|
|
12
|
+
routes: RouteMapping[];
|
|
13
|
+
unsupportedFeatures: string[];
|
|
14
|
+
hasHybridRoute: boolean;
|
|
15
|
+
}
|
|
16
|
+
interface RouteMapping {
|
|
17
|
+
id: string;
|
|
18
|
+
httpMethod: string;
|
|
19
|
+
path: string;
|
|
20
|
+
handlerName: string;
|
|
21
|
+
pathParams: ParamDef[];
|
|
22
|
+
queryParams: ParamDef[];
|
|
23
|
+
requestBody: DtoRef | null;
|
|
24
|
+
responseBody: DtoRef | null;
|
|
25
|
+
statusCode: number;
|
|
26
|
+
guards: string[];
|
|
27
|
+
unsupportedFeatures: string[];
|
|
28
|
+
canGenerate: boolean;
|
|
29
|
+
}
|
|
30
|
+
interface DtoRef {
|
|
31
|
+
$ref: string;
|
|
32
|
+
}
|
|
33
|
+
interface ParamDef {
|
|
34
|
+
name: string;
|
|
35
|
+
type: string;
|
|
36
|
+
isRequired: boolean;
|
|
37
|
+
}
|
|
38
|
+
export declare function extractControllers(project: Project): ControllerSpec[];
|
|
39
|
+
export {};
|
|
40
|
+
//# sourceMappingURL=controller-extractor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controller-extractor.d.ts","sourceRoot":"","sources":["../src/controller-extractor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,OAAO,EAA0F,MAAM,UAAU,CAAC;AAE3H,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,UAAU,YAAY;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,QAAQ,EAAE,CAAC;IACvB,WAAW,EAAE,QAAQ,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,UAAU,MAAM;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,QAAQ;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;CACrB;AAaD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,cAAc,EAAE,CAgBrE"}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractControllers = extractControllers;
|
|
4
|
+
/**
|
|
5
|
+
* Controller extractor - finds @HybridRoute + @Controller decorated classes
|
|
6
|
+
* Extracts basePath, method decorators, handler names, path/query params, and guard decorators
|
|
7
|
+
*/
|
|
8
|
+
const ts_morph_1 = require("ts-morph");
|
|
9
|
+
const HTTP_METHOD_DECORATORS = ['Get', 'Post', 'Put', 'Delete', 'Patch'];
|
|
10
|
+
// Status code mapping based on HTTP method
|
|
11
|
+
const DEFAULT_STATUS_CODES = {
|
|
12
|
+
'GET': 200,
|
|
13
|
+
'POST': 201,
|
|
14
|
+
'PUT': 200,
|
|
15
|
+
'PATCH': 200,
|
|
16
|
+
'DELETE': 200,
|
|
17
|
+
};
|
|
18
|
+
function extractControllers(project) {
|
|
19
|
+
const controllers = [];
|
|
20
|
+
for (const sourceFile of project.getSourceFiles()) {
|
|
21
|
+
for (const classDecl of sourceFile.getClasses()) {
|
|
22
|
+
if (hasHybridRouteDecorator(classDecl)) {
|
|
23
|
+
const controller = extractController(classDecl, sourceFile);
|
|
24
|
+
controllers.push(controller);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Sort controllers alphabetically by name for determinism
|
|
29
|
+
controllers.sort((a, b) => a.name.localeCompare(b.name));
|
|
30
|
+
return controllers;
|
|
31
|
+
}
|
|
32
|
+
function hasHybridRouteDecorator(classDecl) {
|
|
33
|
+
const decorators = classDecl.getDecorators();
|
|
34
|
+
return decorators.some(d => d.getName() === 'HybridRoute');
|
|
35
|
+
}
|
|
36
|
+
function extractController(classDecl, sourceFile) {
|
|
37
|
+
const name = classDecl.getName() || 'AnonymousController';
|
|
38
|
+
const basePath = extractBasePath(classDecl);
|
|
39
|
+
const classGuards = extractClassGuards(classDecl);
|
|
40
|
+
const dependencies = extractDependencies(classDecl);
|
|
41
|
+
const { routes, unsupportedFeatures } = extractRoutes(classDecl, name, classGuards);
|
|
42
|
+
return {
|
|
43
|
+
name,
|
|
44
|
+
basePath,
|
|
45
|
+
sourceFile: getRelativePath(sourceFile.getFilePath()),
|
|
46
|
+
guards: classGuards,
|
|
47
|
+
dependencies,
|
|
48
|
+
routes,
|
|
49
|
+
unsupportedFeatures,
|
|
50
|
+
hasHybridRoute: true,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function extractBasePath(classDecl) {
|
|
54
|
+
const controllerDecorator = classDecl.getDecorators().find(d => d.getName() === 'Controller');
|
|
55
|
+
if (!controllerDecorator)
|
|
56
|
+
return '';
|
|
57
|
+
const args = controllerDecorator.getArguments();
|
|
58
|
+
if (args.length > 0) {
|
|
59
|
+
const arg = args[0];
|
|
60
|
+
if (ts_morph_1.Node.isStringLiteral(arg)) {
|
|
61
|
+
return arg.getText().replace(/['"]/g, '');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return '';
|
|
65
|
+
}
|
|
66
|
+
function extractClassGuards(classDecl) {
|
|
67
|
+
const guards = [];
|
|
68
|
+
const useGuardsDecorator = classDecl.getDecorators().find(d => d.getName() === 'UseGuards');
|
|
69
|
+
if (useGuardsDecorator) {
|
|
70
|
+
const args = useGuardsDecorator.getArguments();
|
|
71
|
+
for (const arg of args) {
|
|
72
|
+
const guardName = arg.getText().replace('()', '').trim();
|
|
73
|
+
guards.push(guardName);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return guards;
|
|
77
|
+
}
|
|
78
|
+
function extractDependencies(classDecl) {
|
|
79
|
+
const dependencies = [];
|
|
80
|
+
for (const prop of classDecl.getProperties()) {
|
|
81
|
+
const type = prop.getType().getText();
|
|
82
|
+
// Extract service names from injected properties
|
|
83
|
+
if (type.includes('Service')) {
|
|
84
|
+
dependencies.push(type);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return dependencies;
|
|
88
|
+
}
|
|
89
|
+
function extractRoutes(classDecl, controllerName, classGuards) {
|
|
90
|
+
const routes = [];
|
|
91
|
+
const controllerUnsupported = [];
|
|
92
|
+
for (const method of classDecl.getMethods()) {
|
|
93
|
+
for (const decoratorName of HTTP_METHOD_DECORATORS) {
|
|
94
|
+
const httpDecorator = method.getDecorators().find(d => d.getName() === decoratorName);
|
|
95
|
+
if (httpDecorator) {
|
|
96
|
+
const routePath = extractDecoratorPath(httpDecorator);
|
|
97
|
+
const methodGuards = extractMethodGuards(method);
|
|
98
|
+
const allGuards = [...new Set([...classGuards, ...methodGuards])];
|
|
99
|
+
const route = {
|
|
100
|
+
id: `${controllerName}:${decoratorName.toUpperCase()}:${routePath || '/'}`,
|
|
101
|
+
httpMethod: decoratorName.toUpperCase(),
|
|
102
|
+
path: routePath || '/',
|
|
103
|
+
handlerName: method.getName(),
|
|
104
|
+
pathParams: extractPathParams(method),
|
|
105
|
+
queryParams: extractQueryParams(method),
|
|
106
|
+
requestBody: extractRequestBody(method),
|
|
107
|
+
responseBody: extractResponseBody(method),
|
|
108
|
+
statusCode: extractStatusCode(method, decoratorName.toUpperCase()),
|
|
109
|
+
guards: methodGuards, // Only route-level guards
|
|
110
|
+
unsupportedFeatures: [],
|
|
111
|
+
canGenerate: true,
|
|
112
|
+
};
|
|
113
|
+
routes.push(route);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Sort routes by method then path for determinism
|
|
119
|
+
routes.sort((a, b) => {
|
|
120
|
+
const methodCompare = a.httpMethod.localeCompare(b.httpMethod);
|
|
121
|
+
if (methodCompare !== 0)
|
|
122
|
+
return methodCompare;
|
|
123
|
+
return a.path.localeCompare(b.path);
|
|
124
|
+
});
|
|
125
|
+
// Edge case: Empty controller (no routes)
|
|
126
|
+
// Add warning flag if controller has @HybridRoute but no HTTP method decorators
|
|
127
|
+
if (routes.length === 0) {
|
|
128
|
+
controllerUnsupported.push('empty-controller');
|
|
129
|
+
}
|
|
130
|
+
return { routes, unsupportedFeatures: controllerUnsupported };
|
|
131
|
+
}
|
|
132
|
+
function extractDecoratorPath(decorator) {
|
|
133
|
+
const args = decorator.getArguments();
|
|
134
|
+
if (args.length > 0) {
|
|
135
|
+
const arg = args[0];
|
|
136
|
+
if (ts_morph_1.Node.isStringLiteral(arg)) {
|
|
137
|
+
return arg.getText().replace(/['"]/g, '');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return '';
|
|
141
|
+
}
|
|
142
|
+
function extractPathParams(method) {
|
|
143
|
+
const params = [];
|
|
144
|
+
for (const param of method.getParameters()) {
|
|
145
|
+
const paramDecorator = param.getDecorators().find(d => d.getName() === 'Param');
|
|
146
|
+
if (paramDecorator) {
|
|
147
|
+
const args = paramDecorator.getArguments();
|
|
148
|
+
const paramName = args.length > 0 && ts_morph_1.Node.isStringLiteral(args[0])
|
|
149
|
+
? args[0].getText().replace(/['"]/g, '')
|
|
150
|
+
: param.getName();
|
|
151
|
+
params.push({
|
|
152
|
+
name: paramName,
|
|
153
|
+
type: mapTypeToGo(param.getType().getText()),
|
|
154
|
+
isRequired: true,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return params;
|
|
159
|
+
}
|
|
160
|
+
function extractQueryParams(method) {
|
|
161
|
+
const params = [];
|
|
162
|
+
for (const param of method.getParameters()) {
|
|
163
|
+
const queryDecorator = param.getDecorators().find(d => d.getName() === 'Query');
|
|
164
|
+
if (queryDecorator) {
|
|
165
|
+
const args = queryDecorator.getArguments();
|
|
166
|
+
const paramName = args.length > 0 && ts_morph_1.Node.isStringLiteral(args[0])
|
|
167
|
+
? args[0].getText().replace(/['"]/g, '')
|
|
168
|
+
: param.getName();
|
|
169
|
+
params.push({
|
|
170
|
+
name: paramName,
|
|
171
|
+
type: mapTypeToGo(param.getType().getText()),
|
|
172
|
+
isRequired: !param.hasQuestionToken(),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return params;
|
|
177
|
+
}
|
|
178
|
+
function extractRequestBody(method) {
|
|
179
|
+
for (const param of method.getParameters()) {
|
|
180
|
+
const bodyDecorator = param.getDecorators().find(d => d.getName() === 'Body');
|
|
181
|
+
if (bodyDecorator) {
|
|
182
|
+
const type = param.getType();
|
|
183
|
+
const typeName = extractTypeName(type.getText());
|
|
184
|
+
if (typeName) {
|
|
185
|
+
return { $ref: typeName };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
function extractResponseBody(method) {
|
|
192
|
+
const returnType = method.getReturnType();
|
|
193
|
+
const returnText = returnType.getText();
|
|
194
|
+
// Extract from Promise<T> or Observable<T>
|
|
195
|
+
const promiseMatch = returnText.match(/Promise<(.+)>/);
|
|
196
|
+
const observableMatch = returnText.match(/Observable<(.+)>/);
|
|
197
|
+
let typeName = returnText;
|
|
198
|
+
if (promiseMatch) {
|
|
199
|
+
typeName = promiseMatch[1];
|
|
200
|
+
}
|
|
201
|
+
else if (observableMatch) {
|
|
202
|
+
typeName = observableMatch[1];
|
|
203
|
+
}
|
|
204
|
+
// Skip void and primitive types
|
|
205
|
+
if (typeName === 'void' || isPrimitiveType(typeName)) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
const cleanName = extractTypeName(typeName);
|
|
209
|
+
if (cleanName) {
|
|
210
|
+
return { $ref: cleanName };
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
function extractStatusCode(method, httpMethod) {
|
|
215
|
+
const httpCodeDecorator = method.getDecorators().find(d => d.getName() === 'HttpCode');
|
|
216
|
+
if (httpCodeDecorator) {
|
|
217
|
+
const args = httpCodeDecorator.getArguments();
|
|
218
|
+
if (args.length > 0) {
|
|
219
|
+
const code = parseInt(args[0].getText(), 10);
|
|
220
|
+
if (!isNaN(code)) {
|
|
221
|
+
return code;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return DEFAULT_STATUS_CODES[httpMethod] || 200;
|
|
226
|
+
}
|
|
227
|
+
function extractMethodGuards(method) {
|
|
228
|
+
const guards = [];
|
|
229
|
+
const useGuardsDecorator = method.getDecorators().find(d => d.getName() === 'UseGuards');
|
|
230
|
+
if (useGuardsDecorator) {
|
|
231
|
+
const args = useGuardsDecorator.getArguments();
|
|
232
|
+
for (const arg of args) {
|
|
233
|
+
const guardName = arg.getText().replace('()', '').trim();
|
|
234
|
+
guards.push(guardName);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return guards;
|
|
238
|
+
}
|
|
239
|
+
function extractTypeName(typeText) {
|
|
240
|
+
// Handle arrays
|
|
241
|
+
if (typeText.endsWith('[]')) {
|
|
242
|
+
return extractTypeName(typeText.slice(0, -2));
|
|
243
|
+
}
|
|
244
|
+
// Handle generics like Array<T>
|
|
245
|
+
const arrayMatch = typeText.match(/Array<(.+)>/);
|
|
246
|
+
if (arrayMatch) {
|
|
247
|
+
return extractTypeName(arrayMatch[1]);
|
|
248
|
+
}
|
|
249
|
+
// Skip primitives
|
|
250
|
+
if (isPrimitiveType(typeText)) {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
// Clean up type name (remove imports path, etc.)
|
|
254
|
+
const cleanName = typeText.split('.').pop()?.split('<')[0].trim();
|
|
255
|
+
return cleanName || null;
|
|
256
|
+
}
|
|
257
|
+
function isPrimitiveType(type) {
|
|
258
|
+
const primitives = [
|
|
259
|
+
'string', 'number', 'boolean', 'void', 'any', 'unknown',
|
|
260
|
+
'null', 'undefined', 'object', 'Date', 'Buffer',
|
|
261
|
+
'Promise<void>', 'Observable<void>'
|
|
262
|
+
];
|
|
263
|
+
return primitives.some(p => type === p || type.includes(p + '<') || type === p + '[]');
|
|
264
|
+
}
|
|
265
|
+
function mapTypeToGo(tsType) {
|
|
266
|
+
const typeMap = {
|
|
267
|
+
'string': 'string',
|
|
268
|
+
'number': 'float64',
|
|
269
|
+
'boolean': 'bool',
|
|
270
|
+
'Date': 'time.Time',
|
|
271
|
+
};
|
|
272
|
+
for (const [ts, go] of Object.entries(typeMap)) {
|
|
273
|
+
if (tsType.includes(ts)) {
|
|
274
|
+
return go;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return 'any';
|
|
278
|
+
}
|
|
279
|
+
function getRelativePath(filePath) {
|
|
280
|
+
// Convert absolute path to relative from project root
|
|
281
|
+
const parts = filePath.split(/[\\/]/);
|
|
282
|
+
const srcIndex = parts.indexOf('src');
|
|
283
|
+
if (srcIndex !== -1) {
|
|
284
|
+
return parts.slice(srcIndex).join('/');
|
|
285
|
+
}
|
|
286
|
+
return filePath;
|
|
287
|
+
}
|
|
288
|
+
//# sourceMappingURL=controller-extractor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controller-extractor.js","sourceRoot":"","sources":["../src/controller-extractor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DTO extractor - finds DTO classes referenced by controller methods
|
|
3
|
+
* Extracts field names, TypeScript types, class-validator decorators, and nested DTO relationships
|
|
4
|
+
*/
|
|
5
|
+
import { Project } from 'ts-morph';
|
|
6
|
+
interface DTODefinition {
|
|
7
|
+
name: string;
|
|
8
|
+
sourceFile: string;
|
|
9
|
+
extends: string | null;
|
|
10
|
+
fields: DTOField[];
|
|
11
|
+
isShared: boolean;
|
|
12
|
+
}
|
|
13
|
+
interface DTOField {
|
|
14
|
+
name: string;
|
|
15
|
+
tsType: string;
|
|
16
|
+
isOptional: boolean;
|
|
17
|
+
isArray: boolean;
|
|
18
|
+
nestedDto: string | null;
|
|
19
|
+
validationRules: ValidationRule[];
|
|
20
|
+
}
|
|
21
|
+
interface ValidationRule {
|
|
22
|
+
decorator: string;
|
|
23
|
+
args: any[];
|
|
24
|
+
message: string | null;
|
|
25
|
+
}
|
|
26
|
+
export declare function extractDTOs(project: Project, referencedDTOs: Set<string>): Record<string, DTODefinition>;
|
|
27
|
+
export declare function collectReferencedDTOs(controllers: any[]): Set<string>;
|
|
28
|
+
export declare function markSharedDTOs(dtos: Record<string, DTODefinition>, controllers: any[]): void;
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=dto-extractor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dto-extractor.d.ts","sourceRoot":"","sources":["../src/dto-extractor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,OAAO,EAAsE,MAAM,UAAU,CAAC;AAEvG,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,QAAQ;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,eAAe,EAAE,cAAc,EAAE,CAAC;CACnC;AAED,UAAU,cAAc;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAoCD,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CA8BxG;AAkOD,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAerE;AAGD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,GAAG,IAAI,CAyB5F"}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractDTOs = extractDTOs;
|
|
4
|
+
exports.collectReferencedDTOs = collectReferencedDTOs;
|
|
5
|
+
exports.markSharedDTOs = markSharedDTOs;
|
|
6
|
+
/**
|
|
7
|
+
* DTO extractor - finds DTO classes referenced by controller methods
|
|
8
|
+
* Extracts field names, TypeScript types, class-validator decorators, and nested DTO relationships
|
|
9
|
+
*/
|
|
10
|
+
const ts_morph_1 = require("ts-morph");
|
|
11
|
+
const VALIDATION_DECORATORS = [
|
|
12
|
+
'IsString', 'IsNumber', 'IsEmail', 'IsInt', 'IsBoolean',
|
|
13
|
+
'Min', 'Max', 'MinLength', 'MaxLength', 'IsOptional',
|
|
14
|
+
'IsDate', 'IsArray', 'IsEnum', 'IsUUID', 'IsUrl',
|
|
15
|
+
'Matches', 'IsNotEmpty', 'IsDefined', 'IsPositive',
|
|
16
|
+
'IsNegative', 'IsDateString', 'IsISO8601', 'IsJSON',
|
|
17
|
+
'IsObject', 'IsNotEmptyObject', 'ValidateNested',
|
|
18
|
+
];
|
|
19
|
+
// Map class-validator decorators to Go validate tags
|
|
20
|
+
const VALIDATOR_TO_GO_TAG = {
|
|
21
|
+
'IsString': '',
|
|
22
|
+
'IsNumber': '',
|
|
23
|
+
'IsEmail': 'email',
|
|
24
|
+
'IsInt': '',
|
|
25
|
+
'IsBoolean': '',
|
|
26
|
+
'Min': 'min',
|
|
27
|
+
'Max': 'max',
|
|
28
|
+
'MinLength': 'min',
|
|
29
|
+
'MaxLength': 'max',
|
|
30
|
+
'IsOptional': 'omitempty',
|
|
31
|
+
'IsDate': '',
|
|
32
|
+
'IsArray': '',
|
|
33
|
+
'IsEnum': 'oneof',
|
|
34
|
+
'IsUUID': 'uuid',
|
|
35
|
+
'IsUrl': 'url',
|
|
36
|
+
'Matches': 'regexp',
|
|
37
|
+
'IsNotEmpty': 'required',
|
|
38
|
+
'IsDefined': 'required',
|
|
39
|
+
'IsPositive': 'gt=0',
|
|
40
|
+
'IsDateString': '',
|
|
41
|
+
'IsJSON': '',
|
|
42
|
+
};
|
|
43
|
+
function extractDTOs(project, referencedDTOs) {
|
|
44
|
+
const dtos = {};
|
|
45
|
+
const processedDTOs = new Set();
|
|
46
|
+
// First pass: collect all DTO classes
|
|
47
|
+
const allDTOClasses = [];
|
|
48
|
+
for (const sourceFile of project.getSourceFiles()) {
|
|
49
|
+
for (const classDecl of sourceFile.getClasses()) {
|
|
50
|
+
const name = classDecl.getName();
|
|
51
|
+
if (name && (isDTO(classDecl) || referencedDTOs.has(name))) {
|
|
52
|
+
allDTOClasses.push({ classDecl, sourceFile });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Second pass: extract DTO definitions
|
|
57
|
+
for (const { classDecl, sourceFile } of allDTOClasses) {
|
|
58
|
+
const name = classDecl.getName();
|
|
59
|
+
if (name && !processedDTOs.has(name)) {
|
|
60
|
+
const dto = extractDTO(classDecl, sourceFile, referencedDTOs);
|
|
61
|
+
dtos[name] = dto;
|
|
62
|
+
processedDTOs.add(name);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Mark shared DTOs (referenced by multiple controllers)
|
|
66
|
+
// This will be done by the caller who has knowledge of controller references
|
|
67
|
+
return dtos;
|
|
68
|
+
}
|
|
69
|
+
function isDTO(classDecl) {
|
|
70
|
+
// Check if class has validation decorators on properties
|
|
71
|
+
for (const prop of classDecl.getProperties()) {
|
|
72
|
+
const decorators = prop.getDecorators();
|
|
73
|
+
if (decorators.some(d => VALIDATION_DECORATORS.includes(d.getName()))) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Also check if class name ends with 'Dto' or 'DTO'
|
|
78
|
+
const name = classDecl.getName() || '';
|
|
79
|
+
return name.endsWith('Dto') || name.endsWith('DTO');
|
|
80
|
+
}
|
|
81
|
+
function extractDTO(classDecl, sourceFile, referencedDTOs) {
|
|
82
|
+
const name = classDecl.getName() || 'AnonymousDTO';
|
|
83
|
+
const fields = extractFields(classDecl, referencedDTOs);
|
|
84
|
+
const extendsClass = extractExtends(classDecl);
|
|
85
|
+
return {
|
|
86
|
+
name,
|
|
87
|
+
sourceFile: getRelativePath(sourceFile.getFilePath()),
|
|
88
|
+
extends: extendsClass,
|
|
89
|
+
fields,
|
|
90
|
+
isShared: false, // Will be determined by caller
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function extractExtends(classDecl) {
|
|
94
|
+
const ext = classDecl.getExtends();
|
|
95
|
+
if (ext) {
|
|
96
|
+
const text = ext.getText();
|
|
97
|
+
// Extract just the class name, not the full path
|
|
98
|
+
return text.split('.').pop() || text;
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
function extractFields(classDecl, referencedDTOs) {
|
|
103
|
+
const fields = [];
|
|
104
|
+
for (const prop of classDecl.getProperties()) {
|
|
105
|
+
const typeText = prop.getType().getText();
|
|
106
|
+
const isArray = typeText.includes('[]') || typeText.includes('Array<');
|
|
107
|
+
const baseType = extractBaseType(typeText);
|
|
108
|
+
// Check for unmappable custom types
|
|
109
|
+
let unmappableType = null;
|
|
110
|
+
if (!isPrimitiveType(baseType) && !isBuiltInType(baseType) && !referencedDTOs.has(baseType)) {
|
|
111
|
+
// Check if it's a known framework type
|
|
112
|
+
if (!isKnownType(baseType)) {
|
|
113
|
+
unmappableType = baseType;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const field = {
|
|
117
|
+
name: prop.getName(),
|
|
118
|
+
tsType: typeText,
|
|
119
|
+
isOptional: prop.hasQuestionToken() || hasOptionalDecorator(prop),
|
|
120
|
+
isArray,
|
|
121
|
+
nestedDto: null,
|
|
122
|
+
validationRules: extractValidation(prop),
|
|
123
|
+
};
|
|
124
|
+
// Check for nested DTO
|
|
125
|
+
if (!isPrimitiveType(baseType) && referencedDTOs.has(baseType)) {
|
|
126
|
+
field.nestedDto = baseType;
|
|
127
|
+
}
|
|
128
|
+
else if (!isPrimitiveType(baseType) && !isBuiltInType(baseType)) {
|
|
129
|
+
// Potentially a nested DTO we haven't seen yet
|
|
130
|
+
field.nestedDto = baseType;
|
|
131
|
+
}
|
|
132
|
+
// Add validation rule for unmappable types (will be flagged during generation)
|
|
133
|
+
if (unmappableType) {
|
|
134
|
+
field.validationRules.push({
|
|
135
|
+
decorator: 'UnmappableType',
|
|
136
|
+
args: [unmappableType],
|
|
137
|
+
message: `Custom TypeScript type '${unmappableType}' cannot be automatically mapped to Go`,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
fields.push(field);
|
|
141
|
+
}
|
|
142
|
+
return fields;
|
|
143
|
+
}
|
|
144
|
+
// Check if type is a known NestJS/framework type that can be mapped
|
|
145
|
+
function isKnownType(type) {
|
|
146
|
+
const knownTypes = [
|
|
147
|
+
// NestJS common types
|
|
148
|
+
'Request', 'Response', 'Next', 'ExecutionContext',
|
|
149
|
+
// Common third-party types
|
|
150
|
+
'ObjectId', 'Buffer',
|
|
151
|
+
];
|
|
152
|
+
return knownTypes.includes(type);
|
|
153
|
+
}
|
|
154
|
+
function extractValidation(prop) {
|
|
155
|
+
const rules = [];
|
|
156
|
+
const isOptional = prop.hasQuestionToken();
|
|
157
|
+
for (const decorator of prop.getDecorators()) {
|
|
158
|
+
const name = decorator.getName();
|
|
159
|
+
if (VALIDATION_DECORATORS.includes(name)) {
|
|
160
|
+
const rule = {
|
|
161
|
+
decorator: name,
|
|
162
|
+
args: extractDecoratorArgs(decorator),
|
|
163
|
+
message: extractValidationMessage(decorator),
|
|
164
|
+
};
|
|
165
|
+
rules.push(rule);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Add required validation if not optional and no IsOptional decorator
|
|
169
|
+
if (!isOptional && !rules.some(r => r.decorator === 'IsOptional')) {
|
|
170
|
+
// Don't add required - Go validator uses required by default
|
|
171
|
+
// unless omitempty is present
|
|
172
|
+
}
|
|
173
|
+
return rules;
|
|
174
|
+
}
|
|
175
|
+
function extractDecoratorArgs(decorator) {
|
|
176
|
+
const args = [];
|
|
177
|
+
const decoratorArgs = decorator.getArguments();
|
|
178
|
+
for (const arg of decoratorArgs) {
|
|
179
|
+
const argText = arg.getText();
|
|
180
|
+
// Try to parse as number
|
|
181
|
+
const num = parseFloat(argText);
|
|
182
|
+
if (!isNaN(num)) {
|
|
183
|
+
args.push(num);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
// Try to parse as string literal
|
|
187
|
+
if (ts_morph_1.Node.isStringLiteral(arg)) {
|
|
188
|
+
args.push(argText.replace(/['"]/g, ''));
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
// Try to parse as object (simplified)
|
|
192
|
+
if (argText.startsWith('{')) {
|
|
193
|
+
try {
|
|
194
|
+
args.push(JSON.parse(argText));
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
args.push(argText);
|
|
198
|
+
}
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
// Default: keep as string
|
|
202
|
+
args.push(argText);
|
|
203
|
+
}
|
|
204
|
+
return args;
|
|
205
|
+
}
|
|
206
|
+
function extractValidationMessage(decorator) {
|
|
207
|
+
const args = decorator.getArguments();
|
|
208
|
+
if (args.length > 0) {
|
|
209
|
+
const lastArg = args[args.length - 1];
|
|
210
|
+
const argText = lastArg.getText();
|
|
211
|
+
// Check if it's an options object with message
|
|
212
|
+
const messageMatch = argText.match(/message:\s*['"]([^'"]+)['"]/);
|
|
213
|
+
if (messageMatch) {
|
|
214
|
+
return messageMatch[1];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
function hasOptionalDecorator(prop) {
|
|
220
|
+
return prop.getDecorators().some(d => d.getName() === 'IsOptional');
|
|
221
|
+
}
|
|
222
|
+
function extractBaseType(typeText) {
|
|
223
|
+
// Remove array notation
|
|
224
|
+
let base = typeText.replace(/\[\]$/, '').replace(/Array</, '').replace(/>$/, '');
|
|
225
|
+
// Remove generics
|
|
226
|
+
const genericMatch = base.match(/^(\w+)</);
|
|
227
|
+
if (genericMatch) {
|
|
228
|
+
base = genericMatch[1];
|
|
229
|
+
}
|
|
230
|
+
// Remove import paths
|
|
231
|
+
base = base.split('.').pop() || base;
|
|
232
|
+
return base.trim();
|
|
233
|
+
}
|
|
234
|
+
function isPrimitiveType(type) {
|
|
235
|
+
const primitives = [
|
|
236
|
+
'string', 'number', 'boolean', 'Date', 'any', 'unknown',
|
|
237
|
+
'null', 'undefined', 'void', 'object', 'Buffer'
|
|
238
|
+
];
|
|
239
|
+
return primitives.includes(type);
|
|
240
|
+
}
|
|
241
|
+
function isBuiltInType(type) {
|
|
242
|
+
const builtIns = [
|
|
243
|
+
'Promise', 'Observable', 'Array', 'Map', 'Set', 'Record',
|
|
244
|
+
'Partial', 'Required', 'Readonly', 'Pick', 'Omit', 'Exclude', 'Extract'
|
|
245
|
+
];
|
|
246
|
+
return builtIns.includes(type);
|
|
247
|
+
}
|
|
248
|
+
function getRelativePath(filePath) {
|
|
249
|
+
const parts = filePath.split(/[\\/]/);
|
|
250
|
+
const srcIndex = parts.indexOf('src');
|
|
251
|
+
if (srcIndex !== -1) {
|
|
252
|
+
return parts.slice(srcIndex).join('/');
|
|
253
|
+
}
|
|
254
|
+
return filePath;
|
|
255
|
+
}
|
|
256
|
+
// Helper to collect all referenced DTOs from controllers
|
|
257
|
+
function collectReferencedDTOs(controllers) {
|
|
258
|
+
const referenced = new Set();
|
|
259
|
+
for (const controller of controllers) {
|
|
260
|
+
for (const route of controller.routes) {
|
|
261
|
+
if (route.requestBody?.$ref) {
|
|
262
|
+
referenced.add(route.requestBody.$ref);
|
|
263
|
+
}
|
|
264
|
+
if (route.responseBody?.$ref) {
|
|
265
|
+
referenced.add(route.responseBody.$ref);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return referenced;
|
|
270
|
+
}
|
|
271
|
+
// Mark DTOs that are shared across multiple controllers
|
|
272
|
+
function markSharedDTOs(dtos, controllers) {
|
|
273
|
+
const referenceCounts = {};
|
|
274
|
+
for (const controller of controllers) {
|
|
275
|
+
const controllerDTOs = new Set();
|
|
276
|
+
for (const route of controller.routes) {
|
|
277
|
+
if (route.requestBody?.$ref) {
|
|
278
|
+
controllerDTOs.add(route.requestBody.$ref);
|
|
279
|
+
}
|
|
280
|
+
if (route.responseBody?.$ref) {
|
|
281
|
+
controllerDTOs.add(route.responseBody.$ref);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
for (const dtoName of controllerDTOs) {
|
|
285
|
+
referenceCounts[dtoName] = (referenceCounts[dtoName] || 0) + 1;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
for (const [name, count] of Object.entries(referenceCounts)) {
|
|
289
|
+
if (count > 1 && dtos[name]) {
|
|
290
|
+
dtos[name].isShared = true;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
//# sourceMappingURL=dto-extractor.js.map
|