@nestia/sdk 2.4.2 → 2.4.3

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