@nestia/sdk 2.3.0-dev.20231019 → 2.3.0
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/assets/config/nestia.config.ts +3 -3
- package/lib/INestiaConfig.d.ts +1 -1
- package/lib/NestiaSdkApplication.js +41 -4
- package/lib/NestiaSdkApplication.js.map +1 -1
- package/lib/analyses/ControllerAnalyzer.d.ts +2 -2
- package/lib/analyses/ControllerAnalyzer.js +107 -65
- package/lib/analyses/ControllerAnalyzer.js.map +1 -1
- package/lib/analyses/ExceptionAnalyzer.d.ts +2 -1
- package/lib/analyses/ExceptionAnalyzer.js +23 -9
- package/lib/analyses/ExceptionAnalyzer.js.map +1 -1
- package/lib/analyses/ReflectAnalyzer.d.ts +2 -1
- package/lib/analyses/ReflectAnalyzer.js +51 -37
- package/lib/analyses/ReflectAnalyzer.js.map +1 -1
- package/lib/executable/sdk.js +1 -0
- package/lib/executable/sdk.js.map +1 -1
- package/lib/structures/IErrorReport.d.ts +6 -0
- package/lib/structures/IErrorReport.js +3 -0
- package/lib/structures/IErrorReport.js.map +1 -0
- package/lib/structures/INestiaProject.d.ts +10 -0
- package/lib/structures/INestiaProject.js +3 -0
- package/lib/structures/INestiaProject.js.map +1 -0
- package/package.json +5 -5
- package/src/INestiaConfig.ts +1 -1
- package/src/NestiaSdkApplication.ts +45 -9
- package/src/analyses/ControllerAnalyzer.ts +336 -307
- package/src/analyses/ExceptionAnalyzer.ts +31 -15
- package/src/analyses/ReflectAnalyzer.ts +235 -211
- package/src/executable/sdk.ts +1 -0
- package/src/structures/IErrorReport.ts +6 -0
- package/src/structures/INestiaProject.ts +12 -0
|
@@ -5,13 +5,12 @@ import ts from "typescript";
|
|
|
5
5
|
|
|
6
6
|
import { CommentFactory } from "typia/lib/factories/CommentFactory";
|
|
7
7
|
|
|
8
|
-
import { INestiaConfig } from "../INestiaConfig";
|
|
9
8
|
import { IController } from "../structures/IController";
|
|
10
|
-
import {
|
|
9
|
+
import { IErrorReport } from "../structures/IErrorReport";
|
|
10
|
+
import { INestiaProject } from "../structures/INestiaProject";
|
|
11
11
|
import { IRoute } from "../structures/IRoute";
|
|
12
12
|
import { ITypeTuple } from "../structures/ITypeTuple";
|
|
13
13
|
import { PathUtil } from "../utils/PathUtil";
|
|
14
|
-
import { ConfigAnalyzer } from "./ConfigAnalyzer";
|
|
15
14
|
import { ExceptionAnalyzer } from "./ExceptionAnalyzer";
|
|
16
15
|
import { GenericAnalyzer } from "./GenericAnalyzer";
|
|
17
16
|
import { ImportAnalyzer } from "./ImportAnalyzer";
|
|
@@ -19,340 +18,370 @@ import { PathAnalyzer } from "./PathAnalyzer";
|
|
|
19
18
|
import { SecurityAnalyzer } from "./SecurityAnalyzer";
|
|
20
19
|
|
|
21
20
|
export namespace ControllerAnalyzer {
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
checker,
|
|
43
|
-
controller,
|
|
44
|
-
node,
|
|
45
|
-
),
|
|
46
|
-
);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
return ret;
|
|
51
|
-
}
|
|
21
|
+
export const analyze =
|
|
22
|
+
(project: INestiaProject) =>
|
|
23
|
+
async (
|
|
24
|
+
sourceFile: ts.SourceFile,
|
|
25
|
+
controller: IController,
|
|
26
|
+
): Promise<IRoute[]> => {
|
|
27
|
+
// FIND CONTROLLER CLASS
|
|
28
|
+
const ret: IRoute[] = [];
|
|
29
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
30
|
+
if (
|
|
31
|
+
ts.isClassDeclaration(node) &&
|
|
32
|
+
node.name?.escapedText === controller.name
|
|
33
|
+
) {
|
|
34
|
+
// ANALYZE THE CONTROLLER
|
|
35
|
+
ret.push(..._Analyze_controller(project)(controller, node));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return ret;
|
|
40
|
+
};
|
|
52
41
|
|
|
53
42
|
/* ---------------------------------------------------------
|
|
54
43
|
CLASS
|
|
55
44
|
--------------------------------------------------------- */
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
) as ts.InterfaceType;
|
|
66
|
-
const genericDict: GenericAnalyzer.Dictionary = GenericAnalyzer.analyze(
|
|
67
|
-
checker,
|
|
68
|
-
classNode,
|
|
69
|
-
);
|
|
45
|
+
const _Analyze_controller =
|
|
46
|
+
(project: INestiaProject) =>
|
|
47
|
+
(controller: IController, classNode: ts.ClassDeclaration): IRoute[] => {
|
|
48
|
+
const classType: ts.InterfaceType =
|
|
49
|
+
project.checker.getTypeAtLocation(
|
|
50
|
+
classNode,
|
|
51
|
+
) as ts.InterfaceType;
|
|
52
|
+
const genericDict: GenericAnalyzer.Dictionary =
|
|
53
|
+
GenericAnalyzer.analyze(project.checker, classNode);
|
|
70
54
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
55
|
+
const ret: IRoute[] = [];
|
|
56
|
+
for (const property of classType.getProperties()) {
|
|
57
|
+
// GET METHOD DECLARATION
|
|
58
|
+
const declaration: ts.Declaration | undefined =
|
|
59
|
+
(property.declarations || [])[0];
|
|
60
|
+
if (!declaration || !ts.isMethodDeclaration(declaration))
|
|
61
|
+
continue;
|
|
77
62
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
63
|
+
// IDENTIFIER MUST BE
|
|
64
|
+
const identifier = declaration.name;
|
|
65
|
+
if (!ts.isIdentifier(identifier)) continue;
|
|
81
66
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
67
|
+
// ANALYZED WITH THE REFLECTED-FUNCTION
|
|
68
|
+
const runtime: IController.IFunction | undefined =
|
|
69
|
+
controller.functions.find(
|
|
70
|
+
(f) => f.name === identifier.escapedText,
|
|
71
|
+
);
|
|
72
|
+
if (runtime === undefined) continue;
|
|
88
73
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
return ret;
|
|
102
|
-
}
|
|
74
|
+
const routes: IRoute[] = _Analyze_function(project)(
|
|
75
|
+
controller,
|
|
76
|
+
genericDict,
|
|
77
|
+
runtime,
|
|
78
|
+
declaration,
|
|
79
|
+
property,
|
|
80
|
+
);
|
|
81
|
+
ret.push(...routes);
|
|
82
|
+
}
|
|
83
|
+
return ret;
|
|
84
|
+
};
|
|
103
85
|
|
|
104
86
|
/* ---------------------------------------------------------
|
|
105
87
|
FUNCTION
|
|
106
88
|
--------------------------------------------------------- */
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
symbol.valueDeclaration!,
|
|
121
|
-
);
|
|
122
|
-
const signature: ts.Signature | undefined = checker.getSignaturesOfType(
|
|
123
|
-
type,
|
|
124
|
-
ts.SignatureKind.Call,
|
|
125
|
-
)[0];
|
|
126
|
-
if (signature === undefined)
|
|
127
|
-
throw new Error(
|
|
128
|
-
`Error on ControllerAnalyzer.analyze(): unable to get the signature from the ${controller.name}.${func.name}().`,
|
|
89
|
+
const _Analyze_function =
|
|
90
|
+
(project: INestiaProject) =>
|
|
91
|
+
(
|
|
92
|
+
controller: IController,
|
|
93
|
+
genericDict: GenericAnalyzer.Dictionary,
|
|
94
|
+
func: IController.IFunction,
|
|
95
|
+
declaration: ts.MethodDeclaration,
|
|
96
|
+
symbol: ts.Symbol,
|
|
97
|
+
): IRoute[] => {
|
|
98
|
+
// PREPARE ASSETS
|
|
99
|
+
const type: ts.Type = project.checker.getTypeOfSymbolAtLocation(
|
|
100
|
+
symbol,
|
|
101
|
+
symbol.valueDeclaration!,
|
|
129
102
|
);
|
|
103
|
+
const signature: ts.Signature | undefined =
|
|
104
|
+
project.checker.getSignaturesOfType(
|
|
105
|
+
type,
|
|
106
|
+
ts.SignatureKind.Call,
|
|
107
|
+
)[0];
|
|
108
|
+
if (signature === undefined) {
|
|
109
|
+
project.errors.push({
|
|
110
|
+
file: controller.file,
|
|
111
|
+
controller: controller.name,
|
|
112
|
+
function: func.name,
|
|
113
|
+
message: "unable to get the type signature.",
|
|
114
|
+
});
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
130
117
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
118
|
+
// EXPLORE CHILDREN TYPES
|
|
119
|
+
const importDict: ImportAnalyzer.Dictionary = new HashMap();
|
|
120
|
+
const parameters: Array<IRoute.IParameter | null> =
|
|
121
|
+
func.parameters.map(
|
|
122
|
+
(param) =>
|
|
123
|
+
_Analyze_parameter(project)(
|
|
124
|
+
genericDict,
|
|
125
|
+
importDict,
|
|
126
|
+
controller,
|
|
127
|
+
func.name,
|
|
128
|
+
param,
|
|
129
|
+
signature.getParameters()[param.index],
|
|
130
|
+
)!,
|
|
131
|
+
);
|
|
132
|
+
const outputType: ITypeTuple | null = ImportAnalyzer.analyze(
|
|
133
|
+
project.checker,
|
|
136
134
|
genericDict,
|
|
137
135
|
importDict,
|
|
138
|
-
|
|
139
|
-
func.name,
|
|
140
|
-
param,
|
|
141
|
-
signature.getParameters()[param.index],
|
|
142
|
-
),
|
|
143
|
-
);
|
|
144
|
-
const outputType: ITypeTuple | null = ImportAnalyzer.analyze(
|
|
145
|
-
checker,
|
|
146
|
-
genericDict,
|
|
147
|
-
importDict,
|
|
148
|
-
signature.getReturnType(),
|
|
149
|
-
);
|
|
150
|
-
if (outputType === null)
|
|
151
|
-
throw new Error(
|
|
152
|
-
`Error on ControllerAnalyzer.analyze(): unnamed return type from ${controller.name}.${func.name}().`,
|
|
136
|
+
signature.getReturnType(),
|
|
153
137
|
);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
138
|
+
if (outputType === null || outputType.typeName === "__type") {
|
|
139
|
+
project.errors.push({
|
|
140
|
+
file: controller.file,
|
|
141
|
+
controller: controller.name,
|
|
142
|
+
function: func.name,
|
|
143
|
+
message: "implicit (unnamed) return type.",
|
|
144
|
+
});
|
|
145
|
+
return [];
|
|
146
|
+
} else if (
|
|
147
|
+
func.method === "HEAD" &&
|
|
148
|
+
outputType.typeName !== "void" &&
|
|
149
|
+
outputType.typeName !== "undefined"
|
|
150
|
+
) {
|
|
151
|
+
project.errors.push({
|
|
152
|
+
file: controller.file,
|
|
153
|
+
controller: controller.name,
|
|
154
|
+
function: func.name,
|
|
155
|
+
message: `HEAD method must return void type.`,
|
|
156
|
+
});
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const exceptions = ExceptionAnalyzer.analyze(project)(
|
|
161
|
+
genericDict,
|
|
162
|
+
project.config.propagate === true ? importDict : new HashMap(),
|
|
163
|
+
)(
|
|
164
|
+
controller,
|
|
165
|
+
func,
|
|
166
|
+
)(declaration);
|
|
167
|
+
const imports: [string, string[]][] = importDict
|
|
168
|
+
.toJSON()
|
|
169
|
+
.map((pair) => [pair.first, pair.second.toJSON()]);
|
|
170
|
+
|
|
171
|
+
// PARSE COMMENT TAGS
|
|
172
|
+
const jsDocTags = signature.getJsDocTags();
|
|
173
|
+
const security: Record<string, string[]>[] = SecurityAnalyzer.merge(
|
|
174
|
+
...controller.security,
|
|
175
|
+
...func.security,
|
|
176
|
+
...jsDocTags
|
|
177
|
+
.filter((tag) => tag.name === "security")
|
|
178
|
+
.map((tag) =>
|
|
179
|
+
(tag.text ?? []).map((text) => {
|
|
180
|
+
const line: string[] = text.text
|
|
181
|
+
.split(" ")
|
|
182
|
+
.filter((s) => s.trim())
|
|
183
|
+
.filter((s) => !!s.length);
|
|
184
|
+
if (line.length === 0) return {};
|
|
185
|
+
return {
|
|
186
|
+
[line[0]]: line.slice(1),
|
|
187
|
+
};
|
|
188
|
+
}),
|
|
189
|
+
)
|
|
190
|
+
.flat(),
|
|
161
191
|
);
|
|
162
192
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
193
|
+
// CONSTRUCT COMMON DATA
|
|
194
|
+
const common: Omit<IRoute, "path" | "accessors"> = {
|
|
195
|
+
...func,
|
|
196
|
+
parameters: parameters.filter(
|
|
197
|
+
(p) => p !== null,
|
|
198
|
+
) as IRoute.IParameter[],
|
|
199
|
+
output: {
|
|
200
|
+
type: outputType.type,
|
|
201
|
+
typeName: outputType.typeName,
|
|
202
|
+
contentType: func.contentType,
|
|
203
|
+
},
|
|
204
|
+
imports,
|
|
205
|
+
status: func.status,
|
|
206
|
+
symbol: {
|
|
207
|
+
class: controller.name,
|
|
208
|
+
function: func.name,
|
|
209
|
+
},
|
|
210
|
+
location: (() => {
|
|
211
|
+
const file = declaration.getSourceFile();
|
|
212
|
+
const { line, character } =
|
|
213
|
+
file.getLineAndCharacterOfPosition(declaration.pos);
|
|
214
|
+
return `${path.relative(process.cwd(), file.fileName)}:${
|
|
215
|
+
line + 1
|
|
216
|
+
}:${character + 1}`;
|
|
217
|
+
})(),
|
|
218
|
+
description: CommentFactory.description(symbol),
|
|
219
|
+
operationId: jsDocTags
|
|
220
|
+
.find(({ name }) => name === "operationId")
|
|
221
|
+
?.text?.[0].text.split(" ")[0]
|
|
222
|
+
.trim(),
|
|
223
|
+
jsDocTags: jsDocTags,
|
|
224
|
+
setHeaders: jsDocTags
|
|
225
|
+
.filter(
|
|
226
|
+
(t) =>
|
|
227
|
+
t.text?.length &&
|
|
228
|
+
t.text[0].text &&
|
|
229
|
+
(t.name === "setHeader" ||
|
|
230
|
+
t.name === "assignHeaders"),
|
|
231
|
+
)
|
|
232
|
+
.map((t) =>
|
|
233
|
+
t.name === "setHeader"
|
|
234
|
+
? {
|
|
235
|
+
type: "setter",
|
|
236
|
+
source: t.text![0].text.split(" ")[0].trim(),
|
|
237
|
+
target: t.text![0].text.split(" ")[1]?.trim(),
|
|
238
|
+
}
|
|
239
|
+
: {
|
|
240
|
+
type: "assigner",
|
|
241
|
+
source: t.text![0].text,
|
|
242
|
+
},
|
|
243
|
+
),
|
|
244
|
+
security,
|
|
245
|
+
exceptions,
|
|
246
|
+
};
|
|
170
247
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
|
|
248
|
+
// CONFIGURE PATHS
|
|
249
|
+
const pathList: Set<string> = new Set();
|
|
250
|
+
const versions: Array<string | null> = _Analyze_versions(
|
|
251
|
+
project.input.versioning === undefined
|
|
252
|
+
? undefined
|
|
253
|
+
: func.versions ??
|
|
254
|
+
controller.versions ??
|
|
255
|
+
(project.input.versioning?.defaultVersion !==
|
|
256
|
+
undefined
|
|
257
|
+
? Array.isArray(
|
|
258
|
+
project.input.versioning?.defaultVersion,
|
|
259
|
+
)
|
|
260
|
+
? project.input.versioning?.defaultVersion
|
|
261
|
+
: [project.input.versioning?.defaultVersion]
|
|
262
|
+
: undefined) ??
|
|
263
|
+
undefined,
|
|
264
|
+
);
|
|
265
|
+
for (const prefix of controller.prefixes)
|
|
266
|
+
for (const cPath of controller.paths)
|
|
267
|
+
for (const filePath of func.paths)
|
|
268
|
+
pathList.add(
|
|
269
|
+
PathAnalyzer.join(prefix, cPath, filePath),
|
|
270
|
+
);
|
|
192
271
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
},
|
|
208
|
-
location: (() => {
|
|
209
|
-
const file = declaration.getSourceFile();
|
|
210
|
-
const { line, character } = file.getLineAndCharacterOfPosition(
|
|
211
|
-
declaration.pos,
|
|
212
|
-
);
|
|
213
|
-
return `${path.relative(process.cwd(), file.fileName)}:${
|
|
214
|
-
line + 1
|
|
215
|
-
}:${character + 1}`;
|
|
216
|
-
})(),
|
|
217
|
-
description: CommentFactory.description(symbol),
|
|
218
|
-
operationId: jsDocTags
|
|
219
|
-
.find(({ name }) => name === "operationId")
|
|
220
|
-
?.text?.[0].text.split(" ")[0]
|
|
221
|
-
.trim(),
|
|
222
|
-
jsDocTags: jsDocTags,
|
|
223
|
-
setHeaders: jsDocTags
|
|
224
|
-
.filter(
|
|
225
|
-
(t) =>
|
|
226
|
-
t.text?.length &&
|
|
227
|
-
t.text[0].text &&
|
|
228
|
-
(t.name === "setHeader" || t.name === "assignHeaders"),
|
|
272
|
+
return [...pathList]
|
|
273
|
+
.map((individual) =>
|
|
274
|
+
PathAnalyzer.combinate(project.input.globalPrefix)(
|
|
275
|
+
[...versions].map((v) =>
|
|
276
|
+
v === null
|
|
277
|
+
? null
|
|
278
|
+
: project.input.versioning?.prefix?.length
|
|
279
|
+
? `${project.input.versioning.prefix}${v}`
|
|
280
|
+
: v,
|
|
281
|
+
),
|
|
282
|
+
)({
|
|
283
|
+
method: func.method,
|
|
284
|
+
path: individual,
|
|
285
|
+
}),
|
|
229
286
|
)
|
|
230
|
-
.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
source: t.text![0].text,
|
|
240
|
-
},
|
|
241
|
-
),
|
|
242
|
-
security,
|
|
243
|
-
exceptions,
|
|
287
|
+
.flat()
|
|
288
|
+
.map((path) => ({
|
|
289
|
+
...common,
|
|
290
|
+
path: PathAnalyzer.escape(
|
|
291
|
+
path,
|
|
292
|
+
() => "ControllerAnalyzer.analyze()",
|
|
293
|
+
),
|
|
294
|
+
accessors: [...PathUtil.accessors(path), func.name],
|
|
295
|
+
}));
|
|
244
296
|
};
|
|
245
297
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
for (const cPath of controller.paths)
|
|
262
|
-
for (const filePath of func.paths)
|
|
263
|
-
pathList.add(PathAnalyzer.join(prefix, cPath, filePath));
|
|
264
|
-
|
|
265
|
-
return [...pathList]
|
|
266
|
-
.map((individual) =>
|
|
267
|
-
PathAnalyzer.combinate(input.globalPrefix)(
|
|
268
|
-
[...versions].map((v) =>
|
|
269
|
-
v === null
|
|
270
|
-
? null
|
|
271
|
-
: input.versioning?.prefix?.length
|
|
272
|
-
? `${input.versioning.prefix}${v}`
|
|
273
|
-
: v,
|
|
274
|
-
),
|
|
275
|
-
)({
|
|
276
|
-
method: func.method,
|
|
277
|
-
path: individual,
|
|
278
|
-
}),
|
|
279
|
-
)
|
|
280
|
-
.flat()
|
|
281
|
-
.map((path) => ({
|
|
282
|
-
...common,
|
|
283
|
-
path: PathAnalyzer.escape(
|
|
284
|
-
path,
|
|
285
|
-
() => "ControllerAnalyzer.analyze()",
|
|
286
|
-
),
|
|
287
|
-
accessors: [...PathUtil.accessors(path), func.name],
|
|
288
|
-
}));
|
|
289
|
-
}
|
|
298
|
+
const _Analyze_parameter =
|
|
299
|
+
(project: INestiaProject) =>
|
|
300
|
+
(
|
|
301
|
+
genericDict: GenericAnalyzer.Dictionary,
|
|
302
|
+
importDict: ImportAnalyzer.Dictionary,
|
|
303
|
+
controller: IController,
|
|
304
|
+
funcName: string,
|
|
305
|
+
param: IController.IParameter,
|
|
306
|
+
symbol: ts.Symbol,
|
|
307
|
+
): IRoute.IParameter | null => {
|
|
308
|
+
const type: ts.Type = project.checker.getTypeOfSymbolAtLocation(
|
|
309
|
+
symbol,
|
|
310
|
+
symbol.valueDeclaration!,
|
|
311
|
+
);
|
|
312
|
+
const name: string = symbol.getEscapedName().toString();
|
|
290
313
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
param: IController.IParameter,
|
|
298
|
-
symbol: ts.Symbol,
|
|
299
|
-
): IRoute.IParameter {
|
|
300
|
-
const type: ts.Type = checker.getTypeOfSymbolAtLocation(
|
|
301
|
-
symbol,
|
|
302
|
-
symbol.valueDeclaration!,
|
|
303
|
-
);
|
|
304
|
-
const name: string = symbol.getEscapedName().toString();
|
|
305
|
-
const method: string = `${controller.name}.${funcName}()`;
|
|
314
|
+
const optional: boolean =
|
|
315
|
+
!!project.checker.symbolToParameterDeclaration(
|
|
316
|
+
symbol,
|
|
317
|
+
undefined,
|
|
318
|
+
undefined,
|
|
319
|
+
)?.questionToken;
|
|
306
320
|
|
|
307
|
-
|
|
308
|
-
symbol,
|
|
309
|
-
undefined,
|
|
310
|
-
undefined,
|
|
311
|
-
)?.questionToken;
|
|
321
|
+
const errors: IErrorReport[] = [];
|
|
312
322
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
323
|
+
// DO NOT SUPPORT BODY PARAMETER
|
|
324
|
+
if (param.category === "body" && param.field !== undefined)
|
|
325
|
+
errors.push({
|
|
326
|
+
file: controller.file,
|
|
327
|
+
controller: controller.name,
|
|
328
|
+
function: funcName,
|
|
329
|
+
message:
|
|
330
|
+
`nestia does not support body field specification. ` +
|
|
331
|
+
`Therefore, erase the "${name}" parameter and ` +
|
|
332
|
+
`re-define a new body decorator accepting full structured message.`,
|
|
333
|
+
});
|
|
334
|
+
if (optional === true && param.category !== "query")
|
|
335
|
+
errors.push({
|
|
336
|
+
file: controller.file,
|
|
337
|
+
controller: controller.name,
|
|
338
|
+
function: funcName,
|
|
339
|
+
message:
|
|
340
|
+
`nestia does not support optional parameter except query parameter. ` +
|
|
341
|
+
`Therefore, erase question mark on the "${name}" parameter, ` +
|
|
342
|
+
`or re-define a new method without the "${name}" parameter.`,
|
|
343
|
+
});
|
|
344
|
+
if (
|
|
345
|
+
optional === true &&
|
|
346
|
+
param.category === "query" &&
|
|
347
|
+
param.field === undefined
|
|
348
|
+
)
|
|
349
|
+
errors.push({
|
|
350
|
+
file: controller.file,
|
|
351
|
+
controller: controller.name,
|
|
352
|
+
function: funcName,
|
|
353
|
+
message:
|
|
354
|
+
`nestia does not support optional query parameter without field specification. ` +
|
|
355
|
+
`Therefore, erase question mark on the "${name}" parameter, ` +
|
|
356
|
+
`or re-define re-define parameters for each query parameters.`,
|
|
357
|
+
});
|
|
336
358
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
);
|
|
344
|
-
if (tuple === null)
|
|
345
|
-
throw new Error(
|
|
346
|
-
`Error on ${method}: unnamed parameter type from ${method}#${name}.`,
|
|
359
|
+
// GET TYPE NAME
|
|
360
|
+
const tuple: ITypeTuple | null = ImportAnalyzer.analyze(
|
|
361
|
+
project.checker,
|
|
362
|
+
genericDict,
|
|
363
|
+
importDict,
|
|
364
|
+
type,
|
|
347
365
|
);
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
366
|
+
if (tuple === null || tuple.typeName === "__type")
|
|
367
|
+
errors.push({
|
|
368
|
+
file: controller.file,
|
|
369
|
+
controller: controller.name,
|
|
370
|
+
function: funcName,
|
|
371
|
+
message: `implicit (unnamed) parameter type from "${name}".`,
|
|
372
|
+
});
|
|
373
|
+
if (errors.length) {
|
|
374
|
+
project.errors.push(...errors);
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
...param,
|
|
379
|
+
name,
|
|
380
|
+
optional,
|
|
381
|
+
type: tuple!.type,
|
|
382
|
+
typeName: tuple!.typeName,
|
|
383
|
+
};
|
|
354
384
|
};
|
|
355
|
-
}
|
|
356
385
|
|
|
357
386
|
function _Analyze_versions(
|
|
358
387
|
value:
|