@nestia/sdk 1.3.1 → 1.3.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.
- package/assets/config/nestia.config.ts +70 -70
- package/lib/INestiaConfig.d.ts +13 -0
- package/lib/executable/internal/NestiaSdkConfig.js +6 -2
- package/lib/executable/internal/NestiaSdkConfig.js.map +1 -1
- package/lib/executable/sdk.js +11 -11
- package/lib/generates/SwaggerGenerator.js +9 -9
- package/lib/generates/internal/DistributionComposer.js +1 -1
- package/lib/generates/internal/DistributionComposer.js.map +1 -1
- package/lib/generates/internal/E2eFileProgrammer.js +12 -12
- package/lib/generates/internal/SdkFileProgrammer.js +3 -1
- package/lib/generates/internal/SdkFileProgrammer.js.map +1 -1
- package/lib/generates/internal/SdkFunctionProgrammer.js +24 -43
- package/lib/generates/internal/SdkFunctionProgrammer.js.map +1 -1
- package/package.json +4 -4
- package/src/INestiaConfig.ts +204 -190
- package/src/NestiaSdkApplication.ts +262 -262
- package/src/analyses/ControllerAnalyzer.ts +261 -261
- package/src/analyses/GenericAnalyzer.ts +53 -53
- package/src/analyses/ImportAnalyzer.ts +164 -164
- package/src/analyses/PathAnalyzer.ts +58 -58
- package/src/analyses/ReflectAnalyzer.ts +321 -321
- package/src/executable/internal/CommandParser.ts +15 -15
- package/src/executable/internal/NestiaConfigCompilerOptions.ts +18 -18
- package/src/executable/internal/NestiaSdkCommand.ts +156 -156
- package/src/executable/internal/NestiaSdkConfig.ts +36 -36
- package/src/executable/internal/nestia.config.getter.ts +12 -12
- package/src/executable/sdk.ts +70 -70
- package/src/generates/E2eGenerator.ts +67 -67
- package/src/generates/SdkGenerator.ts +56 -56
- package/src/generates/SwaggerGenerator.ts +504 -504
- package/src/generates/internal/DistributionComposer.ts +98 -97
- package/src/generates/internal/E2eFileProgrammer.ts +135 -135
- package/src/generates/internal/SdkFileProgrammer.ts +148 -144
- package/src/generates/internal/SdkFunctionProgrammer.ts +30 -52
- package/src/generates/internal/SdkRouteDirectory.ts +21 -21
- package/src/index.ts +4 -4
- package/src/module.ts +2 -2
- package/src/structures/IController.ts +31 -31
- package/src/structures/IRoute.ts +39 -39
- package/src/structures/ISwaggerDocument.ts +120 -120
- package/src/structures/ITypeTuple.ts +6 -6
- package/src/structures/MethodType.ts +11 -11
- package/src/structures/ParamCategory.ts +1 -1
- package/src/structures/TypeEntry.ts +22 -22
- package/src/utils/ArrayUtil.ts +26 -26
- package/src/utils/FileRetriever.ts +22 -22
- package/src/utils/ImportDictionary.ts +56 -56
- package/src/utils/MapUtil.ts +14 -14
- package/src/utils/NestiaConfigUtil.ts +21 -21
- package/src/utils/SourceFinder.ts +60 -60
- package/src/utils/StripEnums.ts +10 -10
|
@@ -1,261 +1,261 @@
|
|
|
1
|
-
import { HashMap } from "tstl/container/HashMap";
|
|
2
|
-
import ts from "typescript";
|
|
3
|
-
|
|
4
|
-
import { CommentFactory } from "typia/lib/factories/CommentFactory";
|
|
5
|
-
|
|
6
|
-
import { IController } from "../structures/IController";
|
|
7
|
-
import { IRoute } from "../structures/IRoute";
|
|
8
|
-
import { ITypeTuple } from "../structures/ITypeTuple";
|
|
9
|
-
import { GenericAnalyzer } from "./GenericAnalyzer";
|
|
10
|
-
import { ImportAnalyzer } from "./ImportAnalyzer";
|
|
11
|
-
import { PathAnalyzer } from "./PathAnalyzer";
|
|
12
|
-
|
|
13
|
-
export namespace ControllerAnalyzer {
|
|
14
|
-
export function analyze(
|
|
15
|
-
checker: ts.TypeChecker,
|
|
16
|
-
sourceFile: ts.SourceFile,
|
|
17
|
-
controller: IController,
|
|
18
|
-
): IRoute[] {
|
|
19
|
-
// FIND CONTROLLER CLASS
|
|
20
|
-
const ret: IRoute[] = [];
|
|
21
|
-
ts.forEachChild(sourceFile, (node) => {
|
|
22
|
-
if (
|
|
23
|
-
ts.isClassDeclaration(node) &&
|
|
24
|
-
node.name?.escapedText === controller.name
|
|
25
|
-
) {
|
|
26
|
-
// ANALYZE THE CONTROLLER
|
|
27
|
-
ret.push(..._Analyze_controller(checker, controller, node));
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
return ret;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/* ---------------------------------------------------------
|
|
35
|
-
CLASS
|
|
36
|
-
--------------------------------------------------------- */
|
|
37
|
-
function _Analyze_controller(
|
|
38
|
-
checker: ts.TypeChecker,
|
|
39
|
-
controller: IController,
|
|
40
|
-
classNode: ts.ClassDeclaration,
|
|
41
|
-
): IRoute[] {
|
|
42
|
-
const classType: ts.InterfaceType = checker.getTypeAtLocation(
|
|
43
|
-
classNode,
|
|
44
|
-
) as ts.InterfaceType;
|
|
45
|
-
const genericDict: GenericAnalyzer.Dictionary = GenericAnalyzer.analyze(
|
|
46
|
-
checker,
|
|
47
|
-
classNode,
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
const ret: IRoute[] = [];
|
|
51
|
-
for (const property of classType.getProperties()) {
|
|
52
|
-
// GET METHOD DECLARATION
|
|
53
|
-
const declaration: ts.Declaration | undefined =
|
|
54
|
-
(property.declarations || [])[0];
|
|
55
|
-
if (!declaration || !ts.isMethodDeclaration(declaration)) continue;
|
|
56
|
-
|
|
57
|
-
// IDENTIFIER MUST BE
|
|
58
|
-
const identifier = declaration.name;
|
|
59
|
-
if (!ts.isIdentifier(identifier)) continue;
|
|
60
|
-
|
|
61
|
-
// ANALYZED WITH THE REFLECTED-FUNCTION
|
|
62
|
-
const runtime: IController.IFunction | undefined =
|
|
63
|
-
controller.functions.find(
|
|
64
|
-
(f) => f.name === identifier.escapedText,
|
|
65
|
-
);
|
|
66
|
-
if (runtime === undefined) continue;
|
|
67
|
-
|
|
68
|
-
const routes: IRoute[] = _Analyze_function(
|
|
69
|
-
checker,
|
|
70
|
-
controller,
|
|
71
|
-
genericDict,
|
|
72
|
-
runtime,
|
|
73
|
-
property,
|
|
74
|
-
);
|
|
75
|
-
ret.push(...routes);
|
|
76
|
-
}
|
|
77
|
-
return ret;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/* ---------------------------------------------------------
|
|
81
|
-
FUNCTION
|
|
82
|
-
--------------------------------------------------------- */
|
|
83
|
-
function _Analyze_function(
|
|
84
|
-
checker: ts.TypeChecker,
|
|
85
|
-
controller: IController,
|
|
86
|
-
genericDict: GenericAnalyzer.Dictionary,
|
|
87
|
-
func: IController.IFunction,
|
|
88
|
-
symbol: ts.Symbol,
|
|
89
|
-
): IRoute[] {
|
|
90
|
-
// PREPARE ASSETS
|
|
91
|
-
const type: ts.Type = checker.getTypeOfSymbolAtLocation(
|
|
92
|
-
symbol,
|
|
93
|
-
symbol.valueDeclaration!,
|
|
94
|
-
);
|
|
95
|
-
const signature: ts.Signature | undefined = checker.getSignaturesOfType(
|
|
96
|
-
type,
|
|
97
|
-
ts.SignatureKind.Call,
|
|
98
|
-
)[0];
|
|
99
|
-
|
|
100
|
-
if (signature === undefined)
|
|
101
|
-
throw new Error(
|
|
102
|
-
`Error on ControllerAnalyzer.analyze(): unable to get the signature from the ${controller.name}.${func.name}().`,
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
const importDict: ImportAnalyzer.Dictionary = new HashMap();
|
|
106
|
-
|
|
107
|
-
// EXPLORE CHILDREN TYPES
|
|
108
|
-
const parameters: IRoute.IParameter[] = func.parameters.map((param) =>
|
|
109
|
-
_Analyze_parameter(
|
|
110
|
-
checker,
|
|
111
|
-
genericDict,
|
|
112
|
-
importDict,
|
|
113
|
-
controller,
|
|
114
|
-
func.name,
|
|
115
|
-
param,
|
|
116
|
-
signature.getParameters()[param.index],
|
|
117
|
-
),
|
|
118
|
-
);
|
|
119
|
-
const output: ITypeTuple | null = ImportAnalyzer.analyze(
|
|
120
|
-
checker,
|
|
121
|
-
genericDict,
|
|
122
|
-
importDict,
|
|
123
|
-
signature.getReturnType(),
|
|
124
|
-
);
|
|
125
|
-
if (output === null)
|
|
126
|
-
throw new Error(
|
|
127
|
-
`Error on ControllerAnalyzer.analyze(): unnamed return type from ${controller.name}.${func.name}().`,
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
const imports: [string, string[]][] = importDict
|
|
131
|
-
.toJSON()
|
|
132
|
-
.map((pair) => [pair.first, pair.second.toJSON()]);
|
|
133
|
-
|
|
134
|
-
// CONSTRUCT COMMON DATA
|
|
135
|
-
const tags = signature.getJsDocTags();
|
|
136
|
-
const common: Omit<IRoute, "path"> = {
|
|
137
|
-
...func,
|
|
138
|
-
parameters,
|
|
139
|
-
output,
|
|
140
|
-
imports,
|
|
141
|
-
status: func.status,
|
|
142
|
-
|
|
143
|
-
symbol: `${controller.name}.${func.name}()`,
|
|
144
|
-
description: CommentFactory.description(symbol),
|
|
145
|
-
tags,
|
|
146
|
-
setHeaders: tags
|
|
147
|
-
.filter(
|
|
148
|
-
(t) =>
|
|
149
|
-
t.text?.length &&
|
|
150
|
-
t.text[0].text &&
|
|
151
|
-
(t.name === "setHeader" || t.name === "assignHeaders"),
|
|
152
|
-
)
|
|
153
|
-
.map((t) =>
|
|
154
|
-
t.name === "setHeader"
|
|
155
|
-
? {
|
|
156
|
-
type: "setter",
|
|
157
|
-
source: t.text![0].text.split(" ")[0].trim(),
|
|
158
|
-
target: t.text![0].text.split(" ")[1]?.trim(),
|
|
159
|
-
}
|
|
160
|
-
: {
|
|
161
|
-
type: "assigner",
|
|
162
|
-
source: t.text![0].text,
|
|
163
|
-
},
|
|
164
|
-
),
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
// CONFIGURE PATHS
|
|
168
|
-
const pathList: string[] = [];
|
|
169
|
-
for (const controllerPath of controller.paths)
|
|
170
|
-
for (const filePath of func.paths) {
|
|
171
|
-
const path: string = PathAnalyzer.join(
|
|
172
|
-
controllerPath,
|
|
173
|
-
filePath,
|
|
174
|
-
);
|
|
175
|
-
pathList.push(
|
|
176
|
-
PathAnalyzer.espace(
|
|
177
|
-
path,
|
|
178
|
-
() => "ControllerAnalyzer.analyze()",
|
|
179
|
-
),
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// RETURNS
|
|
184
|
-
return pathList.map((path) => ({
|
|
185
|
-
...common,
|
|
186
|
-
path,
|
|
187
|
-
}));
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/* ---------------------------------------------------------
|
|
191
|
-
PARAMETER
|
|
192
|
-
--------------------------------------------------------- */
|
|
193
|
-
function _Analyze_parameter(
|
|
194
|
-
checker: ts.TypeChecker,
|
|
195
|
-
genericDict: GenericAnalyzer.Dictionary,
|
|
196
|
-
importDict: ImportAnalyzer.Dictionary,
|
|
197
|
-
controller: IController,
|
|
198
|
-
funcName: string,
|
|
199
|
-
param: IController.IParameter,
|
|
200
|
-
symbol: ts.Symbol,
|
|
201
|
-
): IRoute.IParameter {
|
|
202
|
-
const type: ts.Type = checker.getTypeOfSymbolAtLocation(
|
|
203
|
-
symbol,
|
|
204
|
-
symbol.valueDeclaration!,
|
|
205
|
-
);
|
|
206
|
-
const name: string = symbol.getEscapedName().toString();
|
|
207
|
-
const method: string = `${controller.name}.${funcName}()`;
|
|
208
|
-
|
|
209
|
-
const optional: boolean = !!checker.symbolToParameterDeclaration(
|
|
210
|
-
symbol,
|
|
211
|
-
undefined,
|
|
212
|
-
undefined,
|
|
213
|
-
)?.questionToken;
|
|
214
|
-
|
|
215
|
-
// DO NOT SUPPORT BODY PARAMETER
|
|
216
|
-
if (param.category === "body" && param.field !== undefined)
|
|
217
|
-
throw new Error(
|
|
218
|
-
`Error on ${method}: nestia does not support body field specification. ` +
|
|
219
|
-
`Therefore, erase the ${method}#${name} parameter and ` +
|
|
220
|
-
`re-define a new body decorator accepting full structured message.`,
|
|
221
|
-
);
|
|
222
|
-
else if (optional === true && param.category !== "query")
|
|
223
|
-
throw new Error(
|
|
224
|
-
`Error on ${method}: nestia does not support optional parameter except query parameter. ` +
|
|
225
|
-
`Therefore, erase question mark on ${method}#${name} parameter, ` +
|
|
226
|
-
`or re-define a new method without the "name" parameter.`,
|
|
227
|
-
);
|
|
228
|
-
else if (
|
|
229
|
-
optional === true &&
|
|
230
|
-
param.category === "query" &&
|
|
231
|
-
param.field === undefined
|
|
232
|
-
)
|
|
233
|
-
throw new Error(
|
|
234
|
-
`Error on ${method}: nestia does not support optional query parameter without field specification. ` +
|
|
235
|
-
`Therefore, erase question mark on ${method}#${name} parameter, ` +
|
|
236
|
-
`or re-define re-define parameters for each query parameters.`,
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
// GET TYPE NAME
|
|
240
|
-
const tuple: ITypeTuple | null = ImportAnalyzer.analyze(
|
|
241
|
-
checker,
|
|
242
|
-
genericDict,
|
|
243
|
-
importDict,
|
|
244
|
-
type,
|
|
245
|
-
);
|
|
246
|
-
if (tuple === null)
|
|
247
|
-
throw new Error(
|
|
248
|
-
`Error on ${method}: unnamed parameter type from ${method}#${name}.`,
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
return {
|
|
252
|
-
name,
|
|
253
|
-
category: param.category,
|
|
254
|
-
field: param.field,
|
|
255
|
-
encrypted: param.encrypted,
|
|
256
|
-
type: tuple,
|
|
257
|
-
optional,
|
|
258
|
-
meta: param.meta,
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
}
|
|
1
|
+
import { HashMap } from "tstl/container/HashMap";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
|
|
4
|
+
import { CommentFactory } from "typia/lib/factories/CommentFactory";
|
|
5
|
+
|
|
6
|
+
import { IController } from "../structures/IController";
|
|
7
|
+
import { IRoute } from "../structures/IRoute";
|
|
8
|
+
import { ITypeTuple } from "../structures/ITypeTuple";
|
|
9
|
+
import { GenericAnalyzer } from "./GenericAnalyzer";
|
|
10
|
+
import { ImportAnalyzer } from "./ImportAnalyzer";
|
|
11
|
+
import { PathAnalyzer } from "./PathAnalyzer";
|
|
12
|
+
|
|
13
|
+
export namespace ControllerAnalyzer {
|
|
14
|
+
export function analyze(
|
|
15
|
+
checker: ts.TypeChecker,
|
|
16
|
+
sourceFile: ts.SourceFile,
|
|
17
|
+
controller: IController,
|
|
18
|
+
): IRoute[] {
|
|
19
|
+
// FIND CONTROLLER CLASS
|
|
20
|
+
const ret: IRoute[] = [];
|
|
21
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
22
|
+
if (
|
|
23
|
+
ts.isClassDeclaration(node) &&
|
|
24
|
+
node.name?.escapedText === controller.name
|
|
25
|
+
) {
|
|
26
|
+
// ANALYZE THE CONTROLLER
|
|
27
|
+
ret.push(..._Analyze_controller(checker, controller, node));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return ret;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* ---------------------------------------------------------
|
|
35
|
+
CLASS
|
|
36
|
+
--------------------------------------------------------- */
|
|
37
|
+
function _Analyze_controller(
|
|
38
|
+
checker: ts.TypeChecker,
|
|
39
|
+
controller: IController,
|
|
40
|
+
classNode: ts.ClassDeclaration,
|
|
41
|
+
): IRoute[] {
|
|
42
|
+
const classType: ts.InterfaceType = checker.getTypeAtLocation(
|
|
43
|
+
classNode,
|
|
44
|
+
) as ts.InterfaceType;
|
|
45
|
+
const genericDict: GenericAnalyzer.Dictionary = GenericAnalyzer.analyze(
|
|
46
|
+
checker,
|
|
47
|
+
classNode,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const ret: IRoute[] = [];
|
|
51
|
+
for (const property of classType.getProperties()) {
|
|
52
|
+
// GET METHOD DECLARATION
|
|
53
|
+
const declaration: ts.Declaration | undefined =
|
|
54
|
+
(property.declarations || [])[0];
|
|
55
|
+
if (!declaration || !ts.isMethodDeclaration(declaration)) continue;
|
|
56
|
+
|
|
57
|
+
// IDENTIFIER MUST BE
|
|
58
|
+
const identifier = declaration.name;
|
|
59
|
+
if (!ts.isIdentifier(identifier)) continue;
|
|
60
|
+
|
|
61
|
+
// ANALYZED WITH THE REFLECTED-FUNCTION
|
|
62
|
+
const runtime: IController.IFunction | undefined =
|
|
63
|
+
controller.functions.find(
|
|
64
|
+
(f) => f.name === identifier.escapedText,
|
|
65
|
+
);
|
|
66
|
+
if (runtime === undefined) continue;
|
|
67
|
+
|
|
68
|
+
const routes: IRoute[] = _Analyze_function(
|
|
69
|
+
checker,
|
|
70
|
+
controller,
|
|
71
|
+
genericDict,
|
|
72
|
+
runtime,
|
|
73
|
+
property,
|
|
74
|
+
);
|
|
75
|
+
ret.push(...routes);
|
|
76
|
+
}
|
|
77
|
+
return ret;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* ---------------------------------------------------------
|
|
81
|
+
FUNCTION
|
|
82
|
+
--------------------------------------------------------- */
|
|
83
|
+
function _Analyze_function(
|
|
84
|
+
checker: ts.TypeChecker,
|
|
85
|
+
controller: IController,
|
|
86
|
+
genericDict: GenericAnalyzer.Dictionary,
|
|
87
|
+
func: IController.IFunction,
|
|
88
|
+
symbol: ts.Symbol,
|
|
89
|
+
): IRoute[] {
|
|
90
|
+
// PREPARE ASSETS
|
|
91
|
+
const type: ts.Type = checker.getTypeOfSymbolAtLocation(
|
|
92
|
+
symbol,
|
|
93
|
+
symbol.valueDeclaration!,
|
|
94
|
+
);
|
|
95
|
+
const signature: ts.Signature | undefined = checker.getSignaturesOfType(
|
|
96
|
+
type,
|
|
97
|
+
ts.SignatureKind.Call,
|
|
98
|
+
)[0];
|
|
99
|
+
|
|
100
|
+
if (signature === undefined)
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Error on ControllerAnalyzer.analyze(): unable to get the signature from the ${controller.name}.${func.name}().`,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const importDict: ImportAnalyzer.Dictionary = new HashMap();
|
|
106
|
+
|
|
107
|
+
// EXPLORE CHILDREN TYPES
|
|
108
|
+
const parameters: IRoute.IParameter[] = func.parameters.map((param) =>
|
|
109
|
+
_Analyze_parameter(
|
|
110
|
+
checker,
|
|
111
|
+
genericDict,
|
|
112
|
+
importDict,
|
|
113
|
+
controller,
|
|
114
|
+
func.name,
|
|
115
|
+
param,
|
|
116
|
+
signature.getParameters()[param.index],
|
|
117
|
+
),
|
|
118
|
+
);
|
|
119
|
+
const output: ITypeTuple | null = ImportAnalyzer.analyze(
|
|
120
|
+
checker,
|
|
121
|
+
genericDict,
|
|
122
|
+
importDict,
|
|
123
|
+
signature.getReturnType(),
|
|
124
|
+
);
|
|
125
|
+
if (output === null)
|
|
126
|
+
throw new Error(
|
|
127
|
+
`Error on ControllerAnalyzer.analyze(): unnamed return type from ${controller.name}.${func.name}().`,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const imports: [string, string[]][] = importDict
|
|
131
|
+
.toJSON()
|
|
132
|
+
.map((pair) => [pair.first, pair.second.toJSON()]);
|
|
133
|
+
|
|
134
|
+
// CONSTRUCT COMMON DATA
|
|
135
|
+
const tags = signature.getJsDocTags();
|
|
136
|
+
const common: Omit<IRoute, "path"> = {
|
|
137
|
+
...func,
|
|
138
|
+
parameters,
|
|
139
|
+
output,
|
|
140
|
+
imports,
|
|
141
|
+
status: func.status,
|
|
142
|
+
|
|
143
|
+
symbol: `${controller.name}.${func.name}()`,
|
|
144
|
+
description: CommentFactory.description(symbol),
|
|
145
|
+
tags,
|
|
146
|
+
setHeaders: tags
|
|
147
|
+
.filter(
|
|
148
|
+
(t) =>
|
|
149
|
+
t.text?.length &&
|
|
150
|
+
t.text[0].text &&
|
|
151
|
+
(t.name === "setHeader" || t.name === "assignHeaders"),
|
|
152
|
+
)
|
|
153
|
+
.map((t) =>
|
|
154
|
+
t.name === "setHeader"
|
|
155
|
+
? {
|
|
156
|
+
type: "setter",
|
|
157
|
+
source: t.text![0].text.split(" ")[0].trim(),
|
|
158
|
+
target: t.text![0].text.split(" ")[1]?.trim(),
|
|
159
|
+
}
|
|
160
|
+
: {
|
|
161
|
+
type: "assigner",
|
|
162
|
+
source: t.text![0].text,
|
|
163
|
+
},
|
|
164
|
+
),
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// CONFIGURE PATHS
|
|
168
|
+
const pathList: string[] = [];
|
|
169
|
+
for (const controllerPath of controller.paths)
|
|
170
|
+
for (const filePath of func.paths) {
|
|
171
|
+
const path: string = PathAnalyzer.join(
|
|
172
|
+
controllerPath,
|
|
173
|
+
filePath,
|
|
174
|
+
);
|
|
175
|
+
pathList.push(
|
|
176
|
+
PathAnalyzer.espace(
|
|
177
|
+
path,
|
|
178
|
+
() => "ControllerAnalyzer.analyze()",
|
|
179
|
+
),
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// RETURNS
|
|
184
|
+
return pathList.map((path) => ({
|
|
185
|
+
...common,
|
|
186
|
+
path,
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* ---------------------------------------------------------
|
|
191
|
+
PARAMETER
|
|
192
|
+
--------------------------------------------------------- */
|
|
193
|
+
function _Analyze_parameter(
|
|
194
|
+
checker: ts.TypeChecker,
|
|
195
|
+
genericDict: GenericAnalyzer.Dictionary,
|
|
196
|
+
importDict: ImportAnalyzer.Dictionary,
|
|
197
|
+
controller: IController,
|
|
198
|
+
funcName: string,
|
|
199
|
+
param: IController.IParameter,
|
|
200
|
+
symbol: ts.Symbol,
|
|
201
|
+
): IRoute.IParameter {
|
|
202
|
+
const type: ts.Type = checker.getTypeOfSymbolAtLocation(
|
|
203
|
+
symbol,
|
|
204
|
+
symbol.valueDeclaration!,
|
|
205
|
+
);
|
|
206
|
+
const name: string = symbol.getEscapedName().toString();
|
|
207
|
+
const method: string = `${controller.name}.${funcName}()`;
|
|
208
|
+
|
|
209
|
+
const optional: boolean = !!checker.symbolToParameterDeclaration(
|
|
210
|
+
symbol,
|
|
211
|
+
undefined,
|
|
212
|
+
undefined,
|
|
213
|
+
)?.questionToken;
|
|
214
|
+
|
|
215
|
+
// DO NOT SUPPORT BODY PARAMETER
|
|
216
|
+
if (param.category === "body" && param.field !== undefined)
|
|
217
|
+
throw new Error(
|
|
218
|
+
`Error on ${method}: nestia does not support body field specification. ` +
|
|
219
|
+
`Therefore, erase the ${method}#${name} parameter and ` +
|
|
220
|
+
`re-define a new body decorator accepting full structured message.`,
|
|
221
|
+
);
|
|
222
|
+
else if (optional === true && param.category !== "query")
|
|
223
|
+
throw new Error(
|
|
224
|
+
`Error on ${method}: nestia does not support optional parameter except query parameter. ` +
|
|
225
|
+
`Therefore, erase question mark on ${method}#${name} parameter, ` +
|
|
226
|
+
`or re-define a new method without the "name" parameter.`,
|
|
227
|
+
);
|
|
228
|
+
else if (
|
|
229
|
+
optional === true &&
|
|
230
|
+
param.category === "query" &&
|
|
231
|
+
param.field === undefined
|
|
232
|
+
)
|
|
233
|
+
throw new Error(
|
|
234
|
+
`Error on ${method}: nestia does not support optional query parameter without field specification. ` +
|
|
235
|
+
`Therefore, erase question mark on ${method}#${name} parameter, ` +
|
|
236
|
+
`or re-define re-define parameters for each query parameters.`,
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
// GET TYPE NAME
|
|
240
|
+
const tuple: ITypeTuple | null = ImportAnalyzer.analyze(
|
|
241
|
+
checker,
|
|
242
|
+
genericDict,
|
|
243
|
+
importDict,
|
|
244
|
+
type,
|
|
245
|
+
);
|
|
246
|
+
if (tuple === null)
|
|
247
|
+
throw new Error(
|
|
248
|
+
`Error on ${method}: unnamed parameter type from ${method}#${name}.`,
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
name,
|
|
253
|
+
category: param.category,
|
|
254
|
+
field: param.field,
|
|
255
|
+
encrypted: param.encrypted,
|
|
256
|
+
type: tuple,
|
|
257
|
+
optional,
|
|
258
|
+
meta: param.meta,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
import ts from "typescript";
|
|
2
|
-
|
|
3
|
-
export namespace GenericAnalyzer {
|
|
4
|
-
export type Dictionary = WeakMap<ts.Type, ts.Type>;
|
|
5
|
-
|
|
6
|
-
export function analyze(
|
|
7
|
-
checker: ts.TypeChecker,
|
|
8
|
-
classNode: ts.ClassDeclaration,
|
|
9
|
-
): Dictionary {
|
|
10
|
-
const dict: Dictionary = new WeakMap();
|
|
11
|
-
explore(checker, dict, classNode);
|
|
12
|
-
return dict;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function explore(
|
|
16
|
-
checker: ts.TypeChecker,
|
|
17
|
-
dict: Dictionary,
|
|
18
|
-
classNode: ts.ClassDeclaration,
|
|
19
|
-
): void {
|
|
20
|
-
if (classNode.heritageClauses === undefined) return;
|
|
21
|
-
|
|
22
|
-
for (const heritage of classNode.heritageClauses)
|
|
23
|
-
for (const hType of heritage.types) {
|
|
24
|
-
// MUST BE CLASS
|
|
25
|
-
const expression: ts.Type = checker.getTypeAtLocation(
|
|
26
|
-
hType.expression,
|
|
27
|
-
);
|
|
28
|
-
const superNode: ts.Declaration =
|
|
29
|
-
expression.symbol.getDeclarations()![0];
|
|
30
|
-
|
|
31
|
-
if (!ts.isClassDeclaration(superNode)) continue;
|
|
32
|
-
|
|
33
|
-
// SPECIFY GENERICS
|
|
34
|
-
const usages: ReadonlyArray<ts.TypeNode> =
|
|
35
|
-
hType.typeArguments || [];
|
|
36
|
-
const parameters: ReadonlyArray<ts.TypeParameterDeclaration> =
|
|
37
|
-
superNode.typeParameters || [];
|
|
38
|
-
|
|
39
|
-
parameters.forEach((param, index) => {
|
|
40
|
-
const paramType: ts.Type = checker.getTypeAtLocation(param);
|
|
41
|
-
const usageType: ts.Type =
|
|
42
|
-
usages[index] !== undefined
|
|
43
|
-
? checker.getTypeAtLocation(usages[index])
|
|
44
|
-
: checker.getTypeAtLocation(param.default!);
|
|
45
|
-
|
|
46
|
-
dict.set(paramType, usageType);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// RECUSRIVE EXPLORATION
|
|
50
|
-
explore(checker, dict, superNode);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
|
|
3
|
+
export namespace GenericAnalyzer {
|
|
4
|
+
export type Dictionary = WeakMap<ts.Type, ts.Type>;
|
|
5
|
+
|
|
6
|
+
export function analyze(
|
|
7
|
+
checker: ts.TypeChecker,
|
|
8
|
+
classNode: ts.ClassDeclaration,
|
|
9
|
+
): Dictionary {
|
|
10
|
+
const dict: Dictionary = new WeakMap();
|
|
11
|
+
explore(checker, dict, classNode);
|
|
12
|
+
return dict;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function explore(
|
|
16
|
+
checker: ts.TypeChecker,
|
|
17
|
+
dict: Dictionary,
|
|
18
|
+
classNode: ts.ClassDeclaration,
|
|
19
|
+
): void {
|
|
20
|
+
if (classNode.heritageClauses === undefined) return;
|
|
21
|
+
|
|
22
|
+
for (const heritage of classNode.heritageClauses)
|
|
23
|
+
for (const hType of heritage.types) {
|
|
24
|
+
// MUST BE CLASS
|
|
25
|
+
const expression: ts.Type = checker.getTypeAtLocation(
|
|
26
|
+
hType.expression,
|
|
27
|
+
);
|
|
28
|
+
const superNode: ts.Declaration =
|
|
29
|
+
expression.symbol.getDeclarations()![0];
|
|
30
|
+
|
|
31
|
+
if (!ts.isClassDeclaration(superNode)) continue;
|
|
32
|
+
|
|
33
|
+
// SPECIFY GENERICS
|
|
34
|
+
const usages: ReadonlyArray<ts.TypeNode> =
|
|
35
|
+
hType.typeArguments || [];
|
|
36
|
+
const parameters: ReadonlyArray<ts.TypeParameterDeclaration> =
|
|
37
|
+
superNode.typeParameters || [];
|
|
38
|
+
|
|
39
|
+
parameters.forEach((param, index) => {
|
|
40
|
+
const paramType: ts.Type = checker.getTypeAtLocation(param);
|
|
41
|
+
const usageType: ts.Type =
|
|
42
|
+
usages[index] !== undefined
|
|
43
|
+
? checker.getTypeAtLocation(usages[index])
|
|
44
|
+
: checker.getTypeAtLocation(param.default!);
|
|
45
|
+
|
|
46
|
+
dict.set(paramType, usageType);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// RECUSRIVE EXPLORATION
|
|
50
|
+
explore(checker, dict, superNode);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|