@valkyrjaio/sindri 26.5.0 → 26.6.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/dist/bin/sindri.cjs +574 -268
- package/package.json +2 -2
- package/src/Sindri/Ast/Abstract/AstReader.ts +185 -2
- package/src/Sindri/Ast/CliRouteAttributeReader.ts +100 -14
- package/src/Sindri/Ast/CliRouteParameterReader.ts +42 -23
- package/src/Sindri/Ast/Contract/HttpRouteMiddlewareReaderContract.ts +3 -7
- package/src/Sindri/Ast/Contract/HttpRouteParameterReaderContract.ts +1 -4
- package/src/Sindri/Ast/Data/Contract/HttpParameterDataContract.ts +2 -0
- package/src/Sindri/Ast/Data/HttpParameterData.ts +1 -0
- package/src/Sindri/Ast/Data/Result/CliRouteAttributeResult.ts +5 -1
- package/src/Sindri/Ast/Data/Result/HttpRouteAttributeResult.ts +2 -0
- package/src/Sindri/Ast/HttpRouteAttributeReader.ts +178 -48
- package/src/Sindri/Ast/HttpRouteMiddlewareReader.ts +36 -15
- package/src/Sindri/Ast/HttpRouteParameterReader.ts +48 -106
- package/src/Sindri/Constant/SindriInfo.ts +2 -2
- package/src/Sindri/Generate/Abstract/GenerateDataFromAst.ts +37 -24
- package/src/Sindri/Generator/Ast/Cli/AstCliDataFileGenerator.ts +19 -3
- package/src/Sindri/Generator/Ast/Http/AstHttpDataFileGenerator.ts +81 -42
- package/src/Sindri/Generator/Cli/Contract/CliDataFileGeneratorContract.ts +8 -0
- package/src/Sindri/Generator/Http/Contract/HttpDataFileGeneratorContract.ts +9 -0
|
@@ -15,18 +15,22 @@ import { HttpRouteAttributeResult } from './Data/Result/HttpRouteAttributeResult
|
|
|
15
15
|
import { HttpRouteMiddlewareReader } from './HttpRouteMiddlewareReader.ts';
|
|
16
16
|
import { HttpRouteParameterReader } from './HttpRouteParameterReader.ts';
|
|
17
17
|
|
|
18
|
-
import type { ClassDeclaration, Decorator, MethodDeclaration
|
|
18
|
+
import type { ClassDeclaration, Decorator, MethodDeclaration } from 'ts-morph';
|
|
19
19
|
|
|
20
20
|
import type { HttpRouteAttributeReaderContract } from './Contract/HttpRouteAttributeReaderContract.ts';
|
|
21
21
|
import type { HttpRouteMiddlewareReaderContract } from './Contract/HttpRouteMiddlewareReaderContract.ts';
|
|
22
22
|
import type { HttpRouteParameterReaderContract } from './Contract/HttpRouteParameterReaderContract.ts';
|
|
23
|
+
import type { HandlerData } from './Data/HandlerData.ts';
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Scans an HTTP controller class file for @Route / @DynamicRoute and related
|
|
26
27
|
* sub-decorators and returns TypeScript compiler API Expr nodes ready for the data cache generator.
|
|
27
28
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
29
|
+
* The shipped decorators accept a single options object
|
|
30
|
+
* (`@Route({ path, name, ... })`), so this reader parses object-literal
|
|
31
|
+
* properties rather than positional arguments. It mirrors the logic of the
|
|
32
|
+
* framework's runtime AttributeRouteCollector but operates entirely on AST
|
|
33
|
+
* without executing any TypeScript code.
|
|
30
34
|
*/
|
|
31
35
|
export class HttpRouteAttributeReader extends RouteAttributeReader implements HttpRouteAttributeReaderContract {
|
|
32
36
|
constructor(
|
|
@@ -112,7 +116,7 @@ export class HttpRouteAttributeReader extends RouteAttributeReader implements Ht
|
|
|
112
116
|
|
|
113
117
|
for (const method of classDecl.getMethods()) {
|
|
114
118
|
for (const decorator of this.findDecoratorsOnNode(method, 'Route', useMap, currentFilePath)) {
|
|
115
|
-
|
|
119
|
+
this.collectRoute(
|
|
116
120
|
decorator,
|
|
117
121
|
method,
|
|
118
122
|
useMap,
|
|
@@ -121,16 +125,13 @@ export class HttpRouteAttributeReader extends RouteAttributeReader implements Ht
|
|
|
121
125
|
classPathPrefix,
|
|
122
126
|
classNamePrefix,
|
|
123
127
|
false,
|
|
128
|
+
routes,
|
|
129
|
+
routeData,
|
|
124
130
|
);
|
|
125
|
-
|
|
126
|
-
if (data !== null) {
|
|
127
|
-
routes[data.name] = this.buildRouteExpr(data);
|
|
128
|
-
routeData[data.name] = data;
|
|
129
|
-
}
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
for (const decorator of this.findDecoratorsOnNode(method, 'DynamicRoute', useMap, currentFilePath)) {
|
|
133
|
-
|
|
134
|
+
this.collectRoute(
|
|
134
135
|
decorator,
|
|
135
136
|
method,
|
|
136
137
|
useMap,
|
|
@@ -139,16 +140,44 @@ export class HttpRouteAttributeReader extends RouteAttributeReader implements Ht
|
|
|
139
140
|
classPathPrefix,
|
|
140
141
|
classNamePrefix,
|
|
141
142
|
true,
|
|
143
|
+
routes,
|
|
144
|
+
routeData,
|
|
142
145
|
);
|
|
143
|
-
|
|
144
|
-
if (data !== null) {
|
|
145
|
-
routes[data.name] = this.buildRouteExpr(data);
|
|
146
|
-
routeData[data.name] = data;
|
|
147
|
-
}
|
|
148
146
|
}
|
|
149
147
|
}
|
|
150
148
|
|
|
151
|
-
|
|
149
|
+
const importMap = this.buildImportMap(routeData, useMap, currentFilePath, currentClass);
|
|
150
|
+
|
|
151
|
+
return new HttpRouteAttributeResult(routes, routeData, importMap);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
protected collectRoute(
|
|
155
|
+
decorator: Decorator,
|
|
156
|
+
method: MethodDeclaration,
|
|
157
|
+
useMap: Record<string, string>,
|
|
158
|
+
currentFilePath: string,
|
|
159
|
+
currentClass: string,
|
|
160
|
+
classPathPrefix: string,
|
|
161
|
+
classNamePrefix: string,
|
|
162
|
+
isDynamic: boolean,
|
|
163
|
+
routes: Record<string, ts.Expression>,
|
|
164
|
+
routeData: Record<string, HttpRouteData>,
|
|
165
|
+
): void {
|
|
166
|
+
const data = this.buildRouteData(
|
|
167
|
+
decorator,
|
|
168
|
+
method,
|
|
169
|
+
useMap,
|
|
170
|
+
currentFilePath,
|
|
171
|
+
currentClass,
|
|
172
|
+
classPathPrefix,
|
|
173
|
+
classNamePrefix,
|
|
174
|
+
isDynamic,
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
if (data !== null) {
|
|
178
|
+
routes[data.name] = this.buildRouteExpr(data);
|
|
179
|
+
routeData[data.name] = data;
|
|
180
|
+
}
|
|
152
181
|
}
|
|
153
182
|
|
|
154
183
|
protected buildRouteData(
|
|
@@ -161,8 +190,14 @@ export class HttpRouteAttributeReader extends RouteAttributeReader implements Ht
|
|
|
161
190
|
classNamePrefix: string,
|
|
162
191
|
isDynamic: boolean,
|
|
163
192
|
): HttpRouteData | null {
|
|
164
|
-
const
|
|
165
|
-
|
|
193
|
+
const obj = this.getDecoratorObjectArg(decorator);
|
|
194
|
+
|
|
195
|
+
if (obj === undefined) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const path = this.getObjectStringProp(obj, 'path', useMap, currentFilePath, currentClass);
|
|
200
|
+
const name = this.getObjectStringProp(obj, 'name', useMap, currentFilePath, currentClass);
|
|
166
201
|
|
|
167
202
|
if (path === '' || name === '') {
|
|
168
203
|
return null;
|
|
@@ -173,10 +208,8 @@ export class HttpRouteAttributeReader extends RouteAttributeReader implements Ht
|
|
|
173
208
|
|
|
174
209
|
const resolvedIsDynamic = isDynamic || updatedPath.includes('{');
|
|
175
210
|
|
|
176
|
-
const decoratorArgs = decorator.getArguments().map((a: Node) => a.compilerNode as ts.Expression);
|
|
177
|
-
|
|
178
211
|
const requestMethods = this.middlewareReader.updateRequestMethods(
|
|
179
|
-
this.middlewareReader.
|
|
212
|
+
this.middlewareReader.extractObjectRequestMethods(obj, useMap, currentFilePath, currentClass),
|
|
180
213
|
method,
|
|
181
214
|
useMap,
|
|
182
215
|
currentFilePath,
|
|
@@ -194,32 +227,84 @@ export class HttpRouteAttributeReader extends RouteAttributeReader implements Ht
|
|
|
194
227
|
useMap,
|
|
195
228
|
currentFilePath,
|
|
196
229
|
currentClass,
|
|
197
|
-
this.
|
|
198
|
-
this.extractClassListArgFromDecorator(decorator, 6, useMap, currentFilePath, currentClass),
|
|
199
|
-
this.extractClassListArgFromDecorator(decorator, 7, useMap, currentFilePath, currentClass),
|
|
200
|
-
this.extractClassListArgFromDecorator(decorator, 8, useMap, currentFilePath, currentClass),
|
|
201
|
-
this.extractClassListArgFromDecorator(decorator, 9, useMap, currentFilePath, currentClass),
|
|
230
|
+
this.getObjectClassListProp(obj, 'middleware', useMap, currentFilePath, currentClass),
|
|
202
231
|
);
|
|
203
232
|
|
|
204
233
|
return new HttpRouteData(
|
|
205
234
|
updatedPath,
|
|
206
235
|
updatedName,
|
|
207
|
-
this.
|
|
236
|
+
this.resolveHandler(obj, method, useMap, currentFilePath, currentClass),
|
|
208
237
|
requestMethods,
|
|
209
238
|
routeMatchedMiddleware,
|
|
210
239
|
routeDispatchedMiddleware,
|
|
211
240
|
throwableCaughtMiddleware,
|
|
212
241
|
sendingResponseMiddleware,
|
|
213
242
|
responseSentMiddleware,
|
|
214
|
-
this.
|
|
215
|
-
this.
|
|
243
|
+
this.resolveRequestStruct(obj, method, useMap, currentFilePath, currentClass),
|
|
244
|
+
this.resolveResponseStruct(obj, method, useMap, currentFilePath, currentClass),
|
|
216
245
|
resolvedIsDynamic,
|
|
217
|
-
resolvedIsDynamic
|
|
218
|
-
? this.updateParameters(decoratorArgs, method, useMap, currentFilePath, currentClass)
|
|
219
|
-
: [],
|
|
246
|
+
resolvedIsDynamic ? this.parameterReader.updateParameters(obj, useMap, currentFilePath, currentClass) : [],
|
|
220
247
|
);
|
|
221
248
|
}
|
|
222
249
|
|
|
250
|
+
protected resolveHandler(
|
|
251
|
+
obj: ts.ObjectLiteralExpression,
|
|
252
|
+
method: MethodDeclaration,
|
|
253
|
+
useMap: Record<string, string>,
|
|
254
|
+
currentFilePath: string,
|
|
255
|
+
currentClass: string,
|
|
256
|
+
): HandlerData {
|
|
257
|
+
const fromObj = this.getObjectHandlerProp(obj, 'handler', useMap, currentFilePath, currentClass);
|
|
258
|
+
|
|
259
|
+
if (fromObj !== undefined) {
|
|
260
|
+
return fromObj;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return this.updateHandler(method, useMap, currentFilePath, currentClass);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
protected resolveRequestStruct(
|
|
267
|
+
obj: ts.ObjectLiteralExpression,
|
|
268
|
+
method: MethodDeclaration,
|
|
269
|
+
useMap: Record<string, string>,
|
|
270
|
+
currentFilePath: string,
|
|
271
|
+
currentClass: string,
|
|
272
|
+
): string | null {
|
|
273
|
+
const value = this.extractExprValue(
|
|
274
|
+
this.getObjectProp(obj, 'requestStruct'),
|
|
275
|
+
useMap,
|
|
276
|
+
currentFilePath,
|
|
277
|
+
currentClass,
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
if (typeof value === 'string' && value !== '') {
|
|
281
|
+
return value;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return this.middlewareReader.updateRequestStruct(method, useMap, currentFilePath, currentClass);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
protected resolveResponseStruct(
|
|
288
|
+
obj: ts.ObjectLiteralExpression,
|
|
289
|
+
method: MethodDeclaration,
|
|
290
|
+
useMap: Record<string, string>,
|
|
291
|
+
currentFilePath: string,
|
|
292
|
+
currentClass: string,
|
|
293
|
+
): string | null {
|
|
294
|
+
const value = this.extractExprValue(
|
|
295
|
+
this.getObjectProp(obj, 'responseStruct'),
|
|
296
|
+
useMap,
|
|
297
|
+
currentFilePath,
|
|
298
|
+
currentClass,
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
if (typeof value === 'string' && value !== '') {
|
|
302
|
+
return value;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return this.middlewareReader.updateResponseStruct(method, useMap, currentFilePath, currentClass);
|
|
306
|
+
}
|
|
307
|
+
|
|
223
308
|
protected updatePath(
|
|
224
309
|
path: string,
|
|
225
310
|
classPathPrefix: string,
|
|
@@ -276,23 +361,27 @@ export class HttpRouteAttributeReader extends RouteAttributeReader implements Ht
|
|
|
276
361
|
return name;
|
|
277
362
|
}
|
|
278
363
|
|
|
364
|
+
/**
|
|
365
|
+
* Emit `new DynamicRoute(path, name, regex, parameters, handler,
|
|
366
|
+
* requestMethods, ...5 middleware, requestStruct, responseStruct)` or
|
|
367
|
+
* `new Route(path, name, handler, requestMethods, ...5 middleware,
|
|
368
|
+
* requestStruct, responseStruct)`, matching the framework constructor order.
|
|
369
|
+
*
|
|
370
|
+
* For dynamic routes the regex slot is emitted as an empty-string
|
|
371
|
+
* placeholder; the generator computes and injects the real regex (it owns
|
|
372
|
+
* the routing `Processor`), and the parameters array is always emitted (`[]`
|
|
373
|
+
* when empty).
|
|
374
|
+
*/
|
|
279
375
|
protected buildRouteExpr(data: HttpRouteData): ts.Expression {
|
|
280
376
|
const args: ts.Expression[] = [this.buildEnumCaseExpr(data.path), this.buildEnumCaseExpr(data.name)];
|
|
281
377
|
|
|
282
|
-
if (data.isDynamic
|
|
378
|
+
if (data.isDynamic) {
|
|
379
|
+
args.push(this.buildStringExpr(''));
|
|
283
380
|
args.push(this.parameterReader.buildParameterListExpr([...data.parameters]));
|
|
284
381
|
}
|
|
285
382
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
} else {
|
|
289
|
-
args.push(this.buildNullExpr());
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (data.requestMethods.length > 0) {
|
|
293
|
-
args.push(this.buildEnumCaseArrayExpr(data.requestMethods));
|
|
294
|
-
}
|
|
295
|
-
|
|
383
|
+
args.push(data.handler !== null ? this.buildHandlerExpr(data.handler) : this.buildNullExpr());
|
|
384
|
+
args.push(this.buildEnumCaseArrayExpr(data.requestMethods));
|
|
296
385
|
args.push(...this.middlewareReader.buildRouteMiddlewareArgs(data));
|
|
297
386
|
args.push(...this.middlewareReader.buildRouteStructArgs(data));
|
|
298
387
|
|
|
@@ -303,13 +392,54 @@ export class HttpRouteAttributeReader extends RouteAttributeReader implements Ht
|
|
|
303
392
|
return this.buildNewExpr(targetClass, args);
|
|
304
393
|
}
|
|
305
394
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
395
|
+
/**
|
|
396
|
+
* Build the class-name → absolute-file-path map for every handler and
|
|
397
|
+
* middleware class the generated data cache references, so the generator can
|
|
398
|
+
* emit the matching import statements.
|
|
399
|
+
*/
|
|
400
|
+
protected buildImportMap(
|
|
401
|
+
routeData: Record<string, HttpRouteData>,
|
|
309
402
|
useMap: Record<string, string>,
|
|
310
403
|
currentFilePath: string,
|
|
311
404
|
currentClass: string,
|
|
312
|
-
):
|
|
313
|
-
|
|
405
|
+
): Record<string, string> {
|
|
406
|
+
const importMap: Record<string, string> = {};
|
|
407
|
+
|
|
408
|
+
for (const data of Object.values(routeData)) {
|
|
409
|
+
if (data.handler !== null) {
|
|
410
|
+
this.addClassImport(importMap, data.handler.class, useMap, currentFilePath, currentClass);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
for (const middleware of [
|
|
414
|
+
...data.routeMatchedMiddleware,
|
|
415
|
+
...data.routeDispatchedMiddleware,
|
|
416
|
+
...data.throwableCaughtMiddleware,
|
|
417
|
+
...data.sendingResponseMiddleware,
|
|
418
|
+
...data.responseSentMiddleware,
|
|
419
|
+
]) {
|
|
420
|
+
this.addClassImport(importMap, middleware, useMap, currentFilePath, currentClass);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return importMap;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
protected addClassImport(
|
|
428
|
+
importMap: Record<string, string>,
|
|
429
|
+
className: string,
|
|
430
|
+
useMap: Record<string, string>,
|
|
431
|
+
currentFilePath: string,
|
|
432
|
+
currentClass: string,
|
|
433
|
+
): void {
|
|
434
|
+
const shortName = className.slice(className.lastIndexOf('\\') + 1);
|
|
435
|
+
|
|
436
|
+
const filePath =
|
|
437
|
+
shortName === currentClass
|
|
438
|
+
? currentFilePath
|
|
439
|
+
: this.resolveImportToFilePath(shortName, useMap, currentFilePath);
|
|
440
|
+
|
|
441
|
+
if (filePath !== '') {
|
|
442
|
+
importMap[shortName] = filePath;
|
|
443
|
+
}
|
|
314
444
|
}
|
|
315
445
|
}
|
|
@@ -24,19 +24,13 @@ import type { HttpRouteMiddlewareReaderContract } from './Contract/HttpRouteMidd
|
|
|
24
24
|
* complexity threshold; injected as a constructor argument.
|
|
25
25
|
*/
|
|
26
26
|
export class HttpRouteMiddlewareReader extends AstReader implements HttpRouteMiddlewareReaderContract {
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
extractObjectRequestMethods(
|
|
28
|
+
obj: ts.ObjectLiteralExpression,
|
|
29
29
|
useMap: Record<string, string>,
|
|
30
30
|
namespace: string,
|
|
31
31
|
currentClass: string,
|
|
32
32
|
): string[] {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (requestMethodsArg === undefined || !ts.isArrayLiteralExpression(requestMethodsArg)) {
|
|
36
|
-
return [];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return this.extractClassListFromArrayExpr(requestMethodsArg, useMap, namespace, currentClass);
|
|
33
|
+
return this.getObjectClassListProp(obj, 'requestMethods', useMap, namespace, currentClass);
|
|
40
34
|
}
|
|
41
35
|
|
|
42
36
|
updateRequestMethods(
|
|
@@ -71,14 +65,41 @@ export class HttpRouteMiddlewareReader extends AstReader implements HttpRouteMid
|
|
|
71
65
|
useMap: Record<string, string>,
|
|
72
66
|
namespace: string,
|
|
73
67
|
currentClass: string,
|
|
74
|
-
|
|
75
|
-
routeDispatchedMiddleware: string[],
|
|
76
|
-
throwableCaughtMiddleware: string[],
|
|
77
|
-
sendingResponseMiddleware: string[],
|
|
78
|
-
responseSentMiddleware: string[],
|
|
68
|
+
middleware: string[],
|
|
79
69
|
): [string[], string[], string[], string[], string[]] {
|
|
70
|
+
let routeMatchedMiddleware: string[] = [];
|
|
71
|
+
let routeDispatchedMiddleware: string[] = [];
|
|
72
|
+
let throwableCaughtMiddleware: string[] = [];
|
|
73
|
+
let sendingResponseMiddleware: string[] = [];
|
|
74
|
+
let responseSentMiddleware: string[] = [];
|
|
75
|
+
|
|
76
|
+
for (const mwName of middleware) {
|
|
77
|
+
[
|
|
78
|
+
routeMatchedMiddleware,
|
|
79
|
+
routeDispatchedMiddleware,
|
|
80
|
+
throwableCaughtMiddleware,
|
|
81
|
+
sendingResponseMiddleware,
|
|
82
|
+
responseSentMiddleware,
|
|
83
|
+
] = this.classifyMiddleware(
|
|
84
|
+
mwName,
|
|
85
|
+
useMap,
|
|
86
|
+
namespace,
|
|
87
|
+
routeMatchedMiddleware,
|
|
88
|
+
routeDispatchedMiddleware,
|
|
89
|
+
throwableCaughtMiddleware,
|
|
90
|
+
sendingResponseMiddleware,
|
|
91
|
+
responseSentMiddleware,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
80
95
|
for (const decorator of this.findDecoratorsOnNode(method, 'Middleware', useMap, namespace)) {
|
|
81
|
-
|
|
96
|
+
// `@Middleware` takes a thunked class reference (`() => AuthMiddleware`).
|
|
97
|
+
const mwName = this.extractExprValue(
|
|
98
|
+
this.unwrapClassThunk(this.getDecoratorArg(decorator, 0)),
|
|
99
|
+
useMap,
|
|
100
|
+
namespace,
|
|
101
|
+
currentClass,
|
|
102
|
+
);
|
|
82
103
|
|
|
83
104
|
if (typeof mwName !== 'string' || mwName === '') {
|
|
84
105
|
continue;
|
|
@@ -12,75 +12,41 @@ import { ts } from 'ts-morph';
|
|
|
12
12
|
import { AstReader } from './Abstract/AstReader.ts';
|
|
13
13
|
import { HttpParameterData } from './Data/HttpParameterData.ts';
|
|
14
14
|
|
|
15
|
-
import type { Decorator, MethodDeclaration } from 'ts-morph';
|
|
16
|
-
|
|
17
15
|
import type { HttpRouteParameterReaderContract } from './Contract/HttpRouteParameterReaderContract.ts';
|
|
18
16
|
|
|
19
17
|
/**
|
|
20
18
|
* Reads and builds AST expressions for HTTP dynamic route parameters.
|
|
21
19
|
*
|
|
20
|
+
* The shipped `@DynamicRoute` decorator folds parameter definitions into its
|
|
21
|
+
* options object as `parameters: [{ name, regex, cast?, isOptional?,
|
|
22
|
+
* shouldCapture?, default? }]` (TC39 Stage-3 has no parameter decorators), so
|
|
23
|
+
* this reader parses that array of object literals rather than a separate
|
|
24
|
+
* `@Parameter` decorator.
|
|
25
|
+
*
|
|
22
26
|
* Extracted from HttpRouteAttributeReader to keep each class under the
|
|
23
27
|
* complexity threshold; injected as a constructor argument.
|
|
24
28
|
*/
|
|
25
29
|
export class HttpRouteParameterReader extends AstReader implements HttpRouteParameterReaderContract {
|
|
26
30
|
updateParameters(
|
|
27
|
-
|
|
28
|
-
method: MethodDeclaration,
|
|
31
|
+
obj: ts.ObjectLiteralExpression,
|
|
29
32
|
useMap: Record<string, string>,
|
|
30
33
|
namespace: string,
|
|
31
34
|
currentClass: string,
|
|
32
35
|
): HttpParameterData[] {
|
|
33
|
-
|
|
34
|
-
...this.collectInlineParameters(decoratorArgs, useMap, namespace, currentClass),
|
|
35
|
-
...this.collectAttributeParameters(method, useMap, namespace, currentClass),
|
|
36
|
-
...this.collectMethodParamParameters(method, useMap, namespace, currentClass),
|
|
37
|
-
];
|
|
38
|
-
}
|
|
36
|
+
const node = this.getObjectProp(obj, 'parameters');
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
const elements = parameters.map((p) => this.buildParameterExpr(p));
|
|
42
|
-
return ts.factory.createArrayLiteralExpression(elements);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
protected collectInlineParameters(
|
|
46
|
-
decoratorArgs: ts.NodeArray<ts.Expression> | ts.Expression[],
|
|
47
|
-
useMap: Record<string, string>,
|
|
48
|
-
currentFilePath: string,
|
|
49
|
-
currentClass: string,
|
|
50
|
-
): HttpParameterData[] {
|
|
51
|
-
const inlineArg = decoratorArgs[2];
|
|
52
|
-
|
|
53
|
-
if (inlineArg === undefined || !ts.isArrayLiteralExpression(inlineArg)) {
|
|
38
|
+
if (node === undefined || !ts.isArrayLiteralExpression(node)) {
|
|
54
39
|
return [];
|
|
55
40
|
}
|
|
56
41
|
|
|
57
42
|
const parameters: HttpParameterData[] = [];
|
|
58
43
|
|
|
59
|
-
for (const element of
|
|
60
|
-
if (!ts.
|
|
44
|
+
for (const element of node.elements) {
|
|
45
|
+
if (!ts.isObjectLiteralExpression(element)) {
|
|
61
46
|
continue;
|
|
62
47
|
}
|
|
63
48
|
|
|
64
|
-
const param = this.
|
|
65
|
-
|
|
66
|
-
if (param !== null) {
|
|
67
|
-
parameters.push(param);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return parameters;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
protected collectAttributeParameters(
|
|
75
|
-
method: MethodDeclaration,
|
|
76
|
-
useMap: Record<string, string>,
|
|
77
|
-
currentFilePath: string,
|
|
78
|
-
currentClass: string,
|
|
79
|
-
): HttpParameterData[] {
|
|
80
|
-
const parameters: HttpParameterData[] = [];
|
|
81
|
-
|
|
82
|
-
for (const decorator of this.findDecoratorsOnNode(method, 'Parameter', useMap, currentFilePath)) {
|
|
83
|
-
const param = this.buildParameterDataFromDecorator(decorator, useMap, currentFilePath, currentClass);
|
|
49
|
+
const param = this.buildParameterData(element, useMap, namespace, currentClass);
|
|
84
50
|
|
|
85
51
|
if (param !== null) {
|
|
86
52
|
parameters.push(param);
|
|
@@ -90,89 +56,49 @@ export class HttpRouteParameterReader extends AstReader implements HttpRoutePara
|
|
|
90
56
|
return parameters;
|
|
91
57
|
}
|
|
92
58
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
currentFilePath: string,
|
|
97
|
-
currentClass: string,
|
|
98
|
-
): HttpParameterData[] {
|
|
99
|
-
const parameters: HttpParameterData[] = [];
|
|
100
|
-
|
|
101
|
-
for (const methodParam of method.getParameters()) {
|
|
102
|
-
for (const decorator of this.findDecoratorsOnNode(methodParam, 'Parameter', useMap, currentFilePath)) {
|
|
103
|
-
const param = this.buildParameterDataFromDecorator(decorator, useMap, currentFilePath, currentClass);
|
|
104
|
-
|
|
105
|
-
if (param !== null) {
|
|
106
|
-
parameters.push(param);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return parameters;
|
|
59
|
+
buildParameterListExpr(parameters: HttpParameterData[]): ts.ArrayLiteralExpression {
|
|
60
|
+
const elements = parameters.map((p) => this.buildParameterExpr(p));
|
|
61
|
+
return ts.factory.createArrayLiteralExpression(elements);
|
|
112
62
|
}
|
|
113
63
|
|
|
114
|
-
protected
|
|
115
|
-
|
|
64
|
+
protected buildParameterData(
|
|
65
|
+
obj: ts.ObjectLiteralExpression,
|
|
116
66
|
useMap: Record<string, string>,
|
|
117
67
|
currentFilePath: string,
|
|
118
68
|
currentClass: string,
|
|
119
69
|
): HttpParameterData | null {
|
|
120
|
-
const name = this.
|
|
121
|
-
const regex = this.
|
|
70
|
+
const name = this.getObjectStringProp(obj, 'name', useMap, currentFilePath, currentClass);
|
|
71
|
+
const regex = this.getObjectStringProp(obj, 'regex', useMap, currentFilePath, currentClass);
|
|
122
72
|
|
|
123
73
|
if (name === '' || regex === '') {
|
|
124
74
|
return null;
|
|
125
75
|
}
|
|
126
76
|
|
|
77
|
+
const cast = this.getObjectStringProp(obj, 'cast', useMap, currentFilePath, currentClass) || null;
|
|
78
|
+
|
|
127
79
|
return new HttpParameterData(
|
|
128
80
|
name,
|
|
129
81
|
regex,
|
|
130
|
-
|
|
131
|
-
this.
|
|
132
|
-
this.
|
|
82
|
+
cast,
|
|
83
|
+
this.getObjectBoolProp(obj, 'isOptional', useMap, currentFilePath, currentClass, false),
|
|
84
|
+
this.getObjectBoolProp(obj, 'shouldCapture', useMap, currentFilePath, currentClass, true),
|
|
85
|
+
this.extractDefault(obj, useMap, currentFilePath, currentClass),
|
|
133
86
|
);
|
|
134
87
|
}
|
|
135
88
|
|
|
136
|
-
protected
|
|
137
|
-
|
|
89
|
+
protected extractDefault(
|
|
90
|
+
obj: ts.ObjectLiteralExpression,
|
|
138
91
|
useMap: Record<string, string>,
|
|
139
92
|
currentFilePath: string,
|
|
140
93
|
currentClass: string,
|
|
141
|
-
):
|
|
142
|
-
const
|
|
143
|
-
const regexNode = args[1];
|
|
94
|
+
): string | number | boolean | null {
|
|
95
|
+
const value = this.extractExprValue(this.getObjectProp(obj, 'default'), useMap, currentFilePath, currentClass);
|
|
144
96
|
|
|
145
|
-
if (
|
|
146
|
-
return
|
|
97
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
98
|
+
return value;
|
|
147
99
|
}
|
|
148
100
|
|
|
149
|
-
|
|
150
|
-
const regex = this.extractExprValue(regexNode, useMap, currentFilePath, currentClass);
|
|
151
|
-
|
|
152
|
-
if (typeof name !== 'string' || name === '' || typeof regex !== 'string' || regex === '') {
|
|
153
|
-
return null;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const castNode = args[2];
|
|
157
|
-
const castRaw =
|
|
158
|
-
castNode !== undefined ? this.extractExprValue(castNode, useMap, currentFilePath, currentClass) : null;
|
|
159
|
-
const cast = typeof castRaw === 'string' && castRaw !== '' ? castRaw : null;
|
|
160
|
-
|
|
161
|
-
const isOptionalNode = args[3];
|
|
162
|
-
const isOptionalRaw =
|
|
163
|
-
isOptionalNode !== undefined
|
|
164
|
-
? this.extractExprValue(isOptionalNode, useMap, currentFilePath, currentClass)
|
|
165
|
-
: false;
|
|
166
|
-
const isOptional = typeof isOptionalRaw === 'boolean' ? isOptionalRaw : false;
|
|
167
|
-
|
|
168
|
-
const shouldCaptureNode = args[4];
|
|
169
|
-
const shouldCaptureRaw =
|
|
170
|
-
shouldCaptureNode !== undefined
|
|
171
|
-
? this.extractExprValue(shouldCaptureNode, useMap, currentFilePath, currentClass)
|
|
172
|
-
: true;
|
|
173
|
-
const shouldCapture = typeof shouldCaptureRaw === 'boolean' ? shouldCaptureRaw : true;
|
|
174
|
-
|
|
175
|
-
return new HttpParameterData(name, regex, cast, isOptional, shouldCapture);
|
|
101
|
+
return null;
|
|
176
102
|
}
|
|
177
103
|
|
|
178
104
|
protected buildParameterExpr(data: HttpParameterData): ts.Expression {
|
|
@@ -188,6 +114,22 @@ export class HttpRouteParameterReader extends AstReader implements HttpRoutePara
|
|
|
188
114
|
this.buildBoolExpr(data.shouldCapture),
|
|
189
115
|
];
|
|
190
116
|
|
|
117
|
+
if (data.defaultValue !== null) {
|
|
118
|
+
args.push(this.buildScalarExpr(data.defaultValue));
|
|
119
|
+
}
|
|
120
|
+
|
|
191
121
|
return this.buildNewExpr('Valkyrja\\Http\\Routing\\Data\\Parameter', args);
|
|
192
122
|
}
|
|
123
|
+
|
|
124
|
+
protected buildScalarExpr(value: string | number | boolean): ts.Expression {
|
|
125
|
+
if (typeof value === 'string') {
|
|
126
|
+
return this.buildStringExpr(value);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (typeof value === 'boolean') {
|
|
130
|
+
return this.buildBoolExpr(value);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return ts.factory.createNumericLiteral(value);
|
|
134
|
+
}
|
|
193
135
|
}
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
export class SindriInfo {
|
|
11
|
-
static readonly VERSION = '26.
|
|
12
|
-
static readonly VERSION_BUILD_DATE_TIME = '
|
|
11
|
+
static readonly VERSION = '26.6.0' as const;
|
|
12
|
+
static readonly VERSION_BUILD_DATE_TIME = 'August 1 2026 18:38:34 MST' as const;
|
|
13
13
|
static readonly ICON = `\
|
|
14
14
|
██████████
|
|
15
15
|
████ ████
|