@valkyrjaio/sindri 26.0.1
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/LICENSE.md +21 -0
- package/README.md +111 -0
- package/package.json +63 -0
- package/src/Sindri/Ast/Abstract/AstReader.ts +710 -0
- package/src/Sindri/Ast/Abstract/RouteAttributeReader.ts +50 -0
- package/src/Sindri/Ast/CliRouteAttributeReader.ts +219 -0
- package/src/Sindri/Ast/CliRouteParameterReader.ts +204 -0
- package/src/Sindri/Ast/ComponentProviderReader.ts +41 -0
- package/src/Sindri/Ast/ConfigReader.ts +213 -0
- package/src/Sindri/Ast/Contract/CliRouteAttributeReaderContract.ts +14 -0
- package/src/Sindri/Ast/Contract/CliRouteParameterReaderContract.ts +34 -0
- package/src/Sindri/Ast/Contract/ComponentProviderReaderContract.ts +14 -0
- package/src/Sindri/Ast/Contract/ConfigReaderContract.ts +14 -0
- package/src/Sindri/Ast/Contract/HttpRouteAttributeReaderContract.ts +14 -0
- package/src/Sindri/Ast/Contract/HttpRouteMiddlewareReaderContract.ts +61 -0
- package/src/Sindri/Ast/Contract/HttpRouteParameterReaderContract.ts +26 -0
- package/src/Sindri/Ast/Contract/ListenerAttributeReaderContract.ts +14 -0
- package/src/Sindri/Ast/Contract/ListenerProviderReaderContract.ts +14 -0
- package/src/Sindri/Ast/Contract/RouteProviderReaderContract.ts +14 -0
- package/src/Sindri/Ast/Contract/ServiceProviderReaderContract.ts +14 -0
- package/src/Sindri/Ast/Data/CliArgumentParameterData.ts +20 -0
- package/src/Sindri/Ast/Data/CliOptionParameterData.ts +24 -0
- package/src/Sindri/Ast/Data/CliRouteData.ts +32 -0
- package/src/Sindri/Ast/Data/ConfigData.ts +20 -0
- package/src/Sindri/Ast/Data/Contract/CliArgumentParameterDataContract.ts +18 -0
- package/src/Sindri/Ast/Data/Contract/CliOptionParameterDataContract.ts +22 -0
- package/src/Sindri/Ast/Data/Contract/CliRouteDataContract.ts +25 -0
- package/src/Sindri/Ast/Data/Contract/ConfigDataContract.ts +16 -0
- package/src/Sindri/Ast/Data/Contract/HandlerDataContract.ts +13 -0
- package/src/Sindri/Ast/Data/Contract/HttpParameterDataContract.ts +17 -0
- package/src/Sindri/Ast/Data/Contract/HttpRouteDataContract.ts +28 -0
- package/src/Sindri/Ast/Data/Contract/ListenerDataContract.ts +16 -0
- package/src/Sindri/Ast/Data/HandlerData.ts +20 -0
- package/src/Sindri/Ast/Data/HttpParameterData.ts +20 -0
- package/src/Sindri/Ast/Data/HttpRouteData.ts +30 -0
- package/src/Sindri/Ast/Data/ListenerData.ts +19 -0
- package/src/Sindri/Ast/Data/Result/CliRouteAttributeResult.ts +14 -0
- package/src/Sindri/Ast/Data/Result/ComponentProviderResult.ts +28 -0
- package/src/Sindri/Ast/Data/Result/ConfigResult.ts +18 -0
- package/src/Sindri/Ast/Data/Result/HttpRouteAttributeResult.ts +19 -0
- package/src/Sindri/Ast/Data/Result/ListenerAttributeResult.ts +14 -0
- package/src/Sindri/Ast/Data/Result/ListenerProviderResult.ts +24 -0
- package/src/Sindri/Ast/Data/Result/RouteProviderResult.ts +24 -0
- package/src/Sindri/Ast/Data/Result/ServiceProviderResult.ts +22 -0
- package/src/Sindri/Ast/HttpRouteAttributeReader.ts +315 -0
- package/src/Sindri/Ast/HttpRouteMiddlewareReader.ts +214 -0
- package/src/Sindri/Ast/HttpRouteParameterReader.ts +193 -0
- package/src/Sindri/Ast/ListenerAttributeReader.ts +131 -0
- package/src/Sindri/Ast/ListenerProviderReader.ts +45 -0
- package/src/Sindri/Ast/RouteProviderReader.ts +45 -0
- package/src/Sindri/Ast/ServiceProviderReader.ts +124 -0
- package/src/Sindri/Ast/Throwable/Contract/AstThrowable.ts +12 -0
- package/src/Sindri/Ast/Throwable/Exception/Abstract/AstInvalidArgumentException.ts +14 -0
- package/src/Sindri/Ast/Throwable/Exception/Abstract/AstRuntimeException.ts +14 -0
- package/src/Sindri/Ast/Throwable/Exception/AstFileReadException.ts +12 -0
- package/src/Sindri/Cli/Command/GenerateDataFromConfigCommand.ts +83 -0
- package/src/Sindri/Constant/CommandName.ts +12 -0
- package/src/Sindri/Constant/SindriInfo.ts +20 -0
- package/src/Sindri/Constant/SindriServiceId.ts +32 -0
- package/src/Sindri/Generate/Abstract/GenerateDataFromAst.ts +391 -0
- package/src/Sindri/Generator/Abstract/AstFileGenerator.ts +54 -0
- package/src/Sindri/Generator/Abstract/FileGenerator.ts +59 -0
- package/src/Sindri/Generator/Ast/Cli/AstCliDataFileGenerator.ts +88 -0
- package/src/Sindri/Generator/Ast/Container/AstContainerDataFileGenerator.ts +86 -0
- package/src/Sindri/Generator/Ast/Event/AstEventDataFileGenerator.ts +79 -0
- package/src/Sindri/Generator/Ast/Http/AstHttpDataFileGenerator.ts +306 -0
- package/src/Sindri/Generator/Cli/Contract/CliDataFileGeneratorContract.ts +23 -0
- package/src/Sindri/Generator/Container/Contract/ContainerDataFileGeneratorContract.ts +21 -0
- package/src/Sindri/Generator/Contract/FileGeneratorContract.ts +15 -0
- package/src/Sindri/Generator/Enum/GenerateStatus.ts +14 -0
- package/src/Sindri/Generator/Event/Contract/EventDataFileGeneratorContract.ts +23 -0
- package/src/Sindri/Generator/Http/Contract/HttpDataFileGeneratorContract.ts +25 -0
- package/src/Sindri/Generator/Throwable/Contract/GeneratorThrowable.ts +12 -0
- package/src/Sindri/Generator/Throwable/Exception/Abstract/GeneratorInvalidArgumentException.ts +16 -0
- package/src/Sindri/Generator/Throwable/Exception/Abstract/GeneratorRuntimeException.ts +14 -0
- package/src/Sindri/Generator/Throwable/Exception/GeneratorUnreachableException.ts +12 -0
- package/src/Sindri/Provider/SindriAstServiceProvider.ts +182 -0
- package/src/Sindri/Provider/SindriCliRouteProvider.ts +32 -0
- package/src/Sindri/Provider/SindriCommandServiceProvider.ts +71 -0
- package/src/Sindri/Provider/SindriComponentProvider.ts +41 -0
- package/src/Sindri/Throwable/Contract/SindriThrowable.ts +12 -0
- package/src/Sindri/Throwable/Exception/Abstract/SindriInvalidArgumentException.ts +16 -0
- package/src/Sindri/Throwable/Exception/Abstract/SindriRuntimeException.ts +14 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Sindri package.
|
|
3
|
+
*
|
|
4
|
+
* (c) Melech Mizrachi <melechmizrachi@gmail.com>
|
|
5
|
+
*
|
|
6
|
+
* For the full copyright and license information, please view the LICENSE
|
|
7
|
+
* file that was distributed with this source code.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { ts } from 'ts-morph';
|
|
11
|
+
|
|
12
|
+
import { AstReader } from './Abstract/AstReader.ts';
|
|
13
|
+
|
|
14
|
+
import type { MethodDeclaration } from 'ts-morph';
|
|
15
|
+
|
|
16
|
+
import type { HttpRouteData } from './Data/HttpRouteData.ts';
|
|
17
|
+
import type { HttpRouteMiddlewareReaderContract } from './Contract/HttpRouteMiddlewareReaderContract.ts';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Reads middleware, request methods, and struct decorators for HTTP routes, and builds
|
|
21
|
+
* their corresponding AST expressions.
|
|
22
|
+
*
|
|
23
|
+
* Extracted from HttpRouteAttributeReader to keep each class under the
|
|
24
|
+
* complexity threshold; injected as a constructor argument.
|
|
25
|
+
*/
|
|
26
|
+
export class HttpRouteMiddlewareReader extends AstReader implements HttpRouteMiddlewareReaderContract {
|
|
27
|
+
extractInlineRequestMethods(
|
|
28
|
+
decoratorArgs: ts.NodeArray<ts.Expression> | ts.Expression[],
|
|
29
|
+
useMap: Record<string, string>,
|
|
30
|
+
namespace: string,
|
|
31
|
+
currentClass: string,
|
|
32
|
+
): string[] {
|
|
33
|
+
const requestMethodsArg = decoratorArgs[3];
|
|
34
|
+
|
|
35
|
+
if (requestMethodsArg === undefined || !ts.isArrayLiteralExpression(requestMethodsArg)) {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return this.extractClassListFromArrayExpr(requestMethodsArg, useMap, namespace, currentClass);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
updateRequestMethods(
|
|
43
|
+
requestMethods: string[],
|
|
44
|
+
method: MethodDeclaration,
|
|
45
|
+
useMap: Record<string, string>,
|
|
46
|
+
namespace: string,
|
|
47
|
+
currentClass: string,
|
|
48
|
+
): string[] {
|
|
49
|
+
for (const decorator of this.findDecoratorsOnNode(method, 'RequestMethod', useMap, namespace)) {
|
|
50
|
+
for (const arg of decorator.getArguments()) {
|
|
51
|
+
const value = this.extractExprValue(arg, useMap, namespace, currentClass);
|
|
52
|
+
|
|
53
|
+
if (typeof value === 'string' && value !== '') {
|
|
54
|
+
requestMethods = [...requestMethods, value];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (requestMethods.length === 0) {
|
|
60
|
+
requestMethods = [
|
|
61
|
+
'Valkyrja\\Http\\Message\\Enum\\RequestMethod::HEAD',
|
|
62
|
+
'Valkyrja\\Http\\Message\\Enum\\RequestMethod::GET',
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return requestMethods;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
updateMiddleware(
|
|
70
|
+
method: MethodDeclaration,
|
|
71
|
+
useMap: Record<string, string>,
|
|
72
|
+
namespace: string,
|
|
73
|
+
currentClass: string,
|
|
74
|
+
routeMatchedMiddleware: string[],
|
|
75
|
+
routeDispatchedMiddleware: string[],
|
|
76
|
+
throwableCaughtMiddleware: string[],
|
|
77
|
+
sendingResponseMiddleware: string[],
|
|
78
|
+
terminatedMiddleware: string[],
|
|
79
|
+
): [string[], string[], string[], string[], string[]] {
|
|
80
|
+
for (const decorator of this.findDecoratorsOnNode(method, 'Middleware', useMap, namespace)) {
|
|
81
|
+
const mwName = this.extractExprValue(this.getDecoratorArg(decorator, 0), useMap, namespace, currentClass);
|
|
82
|
+
|
|
83
|
+
if (typeof mwName !== 'string' || mwName === '') {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
[
|
|
88
|
+
routeMatchedMiddleware,
|
|
89
|
+
routeDispatchedMiddleware,
|
|
90
|
+
throwableCaughtMiddleware,
|
|
91
|
+
sendingResponseMiddleware,
|
|
92
|
+
terminatedMiddleware,
|
|
93
|
+
] = this.classifyMiddleware(
|
|
94
|
+
mwName,
|
|
95
|
+
useMap,
|
|
96
|
+
namespace,
|
|
97
|
+
routeMatchedMiddleware,
|
|
98
|
+
routeDispatchedMiddleware,
|
|
99
|
+
throwableCaughtMiddleware,
|
|
100
|
+
sendingResponseMiddleware,
|
|
101
|
+
terminatedMiddleware,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return [
|
|
106
|
+
routeMatchedMiddleware,
|
|
107
|
+
routeDispatchedMiddleware,
|
|
108
|
+
throwableCaughtMiddleware,
|
|
109
|
+
sendingResponseMiddleware,
|
|
110
|
+
terminatedMiddleware,
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
updateRequestStruct(
|
|
115
|
+
method: MethodDeclaration,
|
|
116
|
+
useMap: Record<string, string>,
|
|
117
|
+
namespace: string,
|
|
118
|
+
currentClass: string,
|
|
119
|
+
): string | null {
|
|
120
|
+
for (const decorator of this.findDecoratorsOnNode(method, 'RequestStruct', useMap, namespace)) {
|
|
121
|
+
const value = this.extractExprValue(this.getDecoratorArg(decorator, 0), useMap, namespace, currentClass);
|
|
122
|
+
|
|
123
|
+
if (typeof value === 'string' && value !== '') {
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
updateResponseStruct(
|
|
132
|
+
method: MethodDeclaration,
|
|
133
|
+
useMap: Record<string, string>,
|
|
134
|
+
namespace: string,
|
|
135
|
+
currentClass: string,
|
|
136
|
+
): string | null {
|
|
137
|
+
for (const decorator of this.findDecoratorsOnNode(method, 'ResponseStruct', useMap, namespace)) {
|
|
138
|
+
const value = this.extractExprValue(this.getDecoratorArg(decorator, 0), useMap, namespace, currentClass);
|
|
139
|
+
|
|
140
|
+
if (typeof value === 'string' && value !== '') {
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
buildRouteMiddlewareArgs(data: HttpRouteData): ts.Expression[] {
|
|
149
|
+
return [
|
|
150
|
+
this.buildClassIdentifierArrayExpr(data.routeMatchedMiddleware),
|
|
151
|
+
this.buildClassIdentifierArrayExpr(data.routeDispatchedMiddleware),
|
|
152
|
+
this.buildClassIdentifierArrayExpr(data.throwableCaughtMiddleware),
|
|
153
|
+
this.buildClassIdentifierArrayExpr(data.sendingResponseMiddleware),
|
|
154
|
+
this.buildClassIdentifierArrayExpr(data.terminatedMiddleware),
|
|
155
|
+
];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
buildRouteStructArgs(data: HttpRouteData): ts.Expression[] {
|
|
159
|
+
const args: ts.Expression[] = [];
|
|
160
|
+
|
|
161
|
+
if (data.requestStruct !== null) {
|
|
162
|
+
args.push(this.buildClassConstExpr(data.requestStruct));
|
|
163
|
+
} else {
|
|
164
|
+
args.push(this.buildNullExpr());
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (data.responseStruct !== null) {
|
|
168
|
+
args.push(this.buildClassConstExpr(data.responseStruct));
|
|
169
|
+
} else {
|
|
170
|
+
args.push(this.buildNullExpr());
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return args;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
protected classifyMiddleware(
|
|
177
|
+
mwName: string,
|
|
178
|
+
useMap: Record<string, string>,
|
|
179
|
+
currentFilePath: string,
|
|
180
|
+
routeMatchedMiddleware: string[],
|
|
181
|
+
routeDispatchedMiddleware: string[],
|
|
182
|
+
throwableCaughtMiddleware: string[],
|
|
183
|
+
sendingResponseMiddleware: string[],
|
|
184
|
+
terminatedMiddleware: string[],
|
|
185
|
+
): [string[], string[], string[], string[], string[]] {
|
|
186
|
+
if (this.classImplementsInterface(mwName, 'RouteMatchedMiddlewareContract', useMap, currentFilePath)) {
|
|
187
|
+
routeMatchedMiddleware = [...routeMatchedMiddleware, mwName];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (this.classImplementsInterface(mwName, 'RouteDispatchedMiddlewareContract', useMap, currentFilePath)) {
|
|
191
|
+
routeDispatchedMiddleware = [...routeDispatchedMiddleware, mwName];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (this.classImplementsInterface(mwName, 'ThrowableCaughtMiddlewareContract', useMap, currentFilePath)) {
|
|
195
|
+
throwableCaughtMiddleware = [...throwableCaughtMiddleware, mwName];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (this.classImplementsInterface(mwName, 'SendingResponseMiddlewareContract', useMap, currentFilePath)) {
|
|
199
|
+
sendingResponseMiddleware = [...sendingResponseMiddleware, mwName];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (this.classImplementsInterface(mwName, 'TerminatedMiddlewareContract', useMap, currentFilePath)) {
|
|
203
|
+
terminatedMiddleware = [...terminatedMiddleware, mwName];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return [
|
|
207
|
+
routeMatchedMiddleware,
|
|
208
|
+
routeDispatchedMiddleware,
|
|
209
|
+
throwableCaughtMiddleware,
|
|
210
|
+
sendingResponseMiddleware,
|
|
211
|
+
terminatedMiddleware,
|
|
212
|
+
];
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Sindri package.
|
|
3
|
+
*
|
|
4
|
+
* (c) Melech Mizrachi <melechmizrachi@gmail.com>
|
|
5
|
+
*
|
|
6
|
+
* For the full copyright and license information, please view the LICENSE
|
|
7
|
+
* file that was distributed with this source code.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { ts } from 'ts-morph';
|
|
11
|
+
|
|
12
|
+
import { AstReader } from './Abstract/AstReader.ts';
|
|
13
|
+
import { HttpParameterData } from './Data/HttpParameterData.ts';
|
|
14
|
+
|
|
15
|
+
import type { Decorator, MethodDeclaration } from 'ts-morph';
|
|
16
|
+
|
|
17
|
+
import type { HttpRouteParameterReaderContract } from './Contract/HttpRouteParameterReaderContract.ts';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Reads and builds AST expressions for HTTP dynamic route parameters.
|
|
21
|
+
*
|
|
22
|
+
* Extracted from HttpRouteAttributeReader to keep each class under the
|
|
23
|
+
* complexity threshold; injected as a constructor argument.
|
|
24
|
+
*/
|
|
25
|
+
export class HttpRouteParameterReader extends AstReader implements HttpRouteParameterReaderContract {
|
|
26
|
+
updateParameters(
|
|
27
|
+
decoratorArgs: ts.NodeArray<ts.Expression> | ts.Expression[],
|
|
28
|
+
method: MethodDeclaration,
|
|
29
|
+
useMap: Record<string, string>,
|
|
30
|
+
namespace: string,
|
|
31
|
+
currentClass: string,
|
|
32
|
+
): HttpParameterData[] {
|
|
33
|
+
return [
|
|
34
|
+
...this.collectInlineParameters(decoratorArgs, useMap, namespace, currentClass),
|
|
35
|
+
...this.collectAttributeParameters(method, useMap, namespace, currentClass),
|
|
36
|
+
...this.collectMethodParamParameters(method, useMap, namespace, currentClass),
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
buildParameterListExpr(parameters: HttpParameterData[]): ts.ArrayLiteralExpression {
|
|
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)) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const parameters: HttpParameterData[] = [];
|
|
58
|
+
|
|
59
|
+
for (const element of inlineArg.elements) {
|
|
60
|
+
if (!ts.isNewExpression(element) || element.arguments === undefined) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const param = this.buildParameterDataFromTsArgs(element.arguments, useMap, currentFilePath, currentClass);
|
|
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);
|
|
84
|
+
|
|
85
|
+
if (param !== null) {
|
|
86
|
+
parameters.push(param);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return parameters;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
protected collectMethodParamParameters(
|
|
94
|
+
method: MethodDeclaration,
|
|
95
|
+
useMap: Record<string, string>,
|
|
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;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
protected buildParameterDataFromDecorator(
|
|
115
|
+
decorator: Decorator,
|
|
116
|
+
useMap: Record<string, string>,
|
|
117
|
+
currentFilePath: string,
|
|
118
|
+
currentClass: string,
|
|
119
|
+
): HttpParameterData | null {
|
|
120
|
+
const name = this.extractStringArg(decorator, 0, useMap, currentFilePath, currentClass);
|
|
121
|
+
const regex = this.extractStringArg(decorator, 1, useMap, currentFilePath, currentClass);
|
|
122
|
+
|
|
123
|
+
if (name === '' || regex === '') {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return new HttpParameterData(
|
|
128
|
+
name,
|
|
129
|
+
regex,
|
|
130
|
+
this.extractStringArg(decorator, 2, useMap, currentFilePath, currentClass) || null,
|
|
131
|
+
this.extractBoolArg(decorator, 3, useMap, currentFilePath, currentClass, false),
|
|
132
|
+
this.extractBoolArg(decorator, 4, useMap, currentFilePath, currentClass, true),
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
protected buildParameterDataFromTsArgs(
|
|
137
|
+
args: ts.NodeArray<ts.Expression>,
|
|
138
|
+
useMap: Record<string, string>,
|
|
139
|
+
currentFilePath: string,
|
|
140
|
+
currentClass: string,
|
|
141
|
+
): HttpParameterData | null {
|
|
142
|
+
const nameNode = args[0];
|
|
143
|
+
const regexNode = args[1];
|
|
144
|
+
|
|
145
|
+
if (nameNode === undefined || regexNode === undefined) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const name = this.extractExprValue(nameNode, useMap, currentFilePath, currentClass);
|
|
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);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
protected buildParameterExpr(data: HttpParameterData): ts.Expression {
|
|
179
|
+
const regexExpr = data.regex.includes('::')
|
|
180
|
+
? this.buildEnumCaseExpr(data.regex)
|
|
181
|
+
: this.buildStringExpr(data.regex);
|
|
182
|
+
|
|
183
|
+
const args: ts.Expression[] = [
|
|
184
|
+
this.buildStringExpr(data.name),
|
|
185
|
+
regexExpr,
|
|
186
|
+
data.cast !== null ? this.buildEnumCaseExpr(data.cast) : this.buildNullExpr(),
|
|
187
|
+
this.buildBoolExpr(data.isOptional),
|
|
188
|
+
this.buildBoolExpr(data.shouldCapture),
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
return this.buildNewExpr('Valkyrja\\Http\\Routing\\Data\\Parameter', args);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Sindri package.
|
|
3
|
+
*
|
|
4
|
+
* (c) Melech Mizrachi <melechmizrachi@gmail.com>
|
|
5
|
+
*
|
|
6
|
+
* For the full copyright and license information, please view the LICENSE
|
|
7
|
+
* file that was distributed with this source code.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { ts } from 'ts-morph';
|
|
11
|
+
|
|
12
|
+
import { AstReader } from './Abstract/AstReader.ts';
|
|
13
|
+
import { HandlerData } from './Data/HandlerData.ts';
|
|
14
|
+
import { ListenerData } from './Data/ListenerData.ts';
|
|
15
|
+
import { ListenerAttributeResult } from './Data/Result/ListenerAttributeResult.ts';
|
|
16
|
+
|
|
17
|
+
import type { ClassDeclaration, Decorator, MethodDeclaration } from 'ts-morph';
|
|
18
|
+
|
|
19
|
+
import type { ListenerAttributeReaderContract } from './Contract/ListenerAttributeReaderContract.ts';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Scans a listener class file for @Listener and @ListenerHandler decorators
|
|
23
|
+
* and returns TypeScript compiler API Expr nodes ready for the data cache generator.
|
|
24
|
+
*
|
|
25
|
+
* Mirrors the logic of the framework's runtime attribute collector but operates
|
|
26
|
+
* entirely on AST without executing any TypeScript code.
|
|
27
|
+
*/
|
|
28
|
+
export class ListenerAttributeReader extends AstReader implements ListenerAttributeReaderContract {
|
|
29
|
+
protected static readonly DEFAULT_HANDLE_METHOD = 'handle';
|
|
30
|
+
|
|
31
|
+
readFile(filePath: string): ListenerAttributeResult {
|
|
32
|
+
const context = this.parseClassFile(filePath);
|
|
33
|
+
|
|
34
|
+
if (context === undefined) {
|
|
35
|
+
return new ListenerAttributeResult();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const { classDecl, useMap, currentClass } = context;
|
|
39
|
+
const listeners: Record<string, ts.Expression> = {};
|
|
40
|
+
|
|
41
|
+
for (const decorator of this.findDecoratorsOnNode(classDecl, 'Listener', useMap, filePath)) {
|
|
42
|
+
const data = this.buildListenerData(decorator, useMap, filePath, currentClass, classDecl, undefined);
|
|
43
|
+
|
|
44
|
+
if (data !== null) {
|
|
45
|
+
listeners[data.name] = this.buildListenerExpr(data);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const method of classDecl.getMethods()) {
|
|
50
|
+
for (const decorator of this.findDecoratorsOnNode(method, 'Listener', useMap, filePath)) {
|
|
51
|
+
const data = this.buildListenerData(decorator, useMap, filePath, currentClass, classDecl, method);
|
|
52
|
+
|
|
53
|
+
if (data !== null) {
|
|
54
|
+
listeners[data.name] = this.buildListenerExpr(data);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return new ListenerAttributeResult(listeners);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
protected buildListenerData(
|
|
63
|
+
decorator: Decorator,
|
|
64
|
+
useMap: Record<string, string>,
|
|
65
|
+
currentFilePath: string,
|
|
66
|
+
currentClass: string,
|
|
67
|
+
classDecl: ClassDeclaration,
|
|
68
|
+
method: MethodDeclaration | undefined,
|
|
69
|
+
): ListenerData | null {
|
|
70
|
+
const eventId = this.extractStringArg(decorator, 0, useMap, currentFilePath, currentClass);
|
|
71
|
+
const name = this.extractStringArg(decorator, 1, useMap, currentFilePath, currentClass);
|
|
72
|
+
|
|
73
|
+
if (eventId === '' || name === '') {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const handlerRaw = this.extractExprValue(
|
|
78
|
+
this.getDecoratorArg(decorator, 2),
|
|
79
|
+
useMap,
|
|
80
|
+
currentFilePath,
|
|
81
|
+
currentClass,
|
|
82
|
+
);
|
|
83
|
+
const handler =
|
|
84
|
+
handlerRaw instanceof HandlerData
|
|
85
|
+
? handlerRaw
|
|
86
|
+
: this.resolveListenerHandler(useMap, currentFilePath, currentClass, classDecl, method);
|
|
87
|
+
|
|
88
|
+
return new ListenerData(eventId, name, handler);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
protected resolveListenerHandler(
|
|
92
|
+
useMap: Record<string, string>,
|
|
93
|
+
currentFilePath: string,
|
|
94
|
+
currentClass: string,
|
|
95
|
+
classDecl: ClassDeclaration,
|
|
96
|
+
method: MethodDeclaration | undefined,
|
|
97
|
+
): HandlerData {
|
|
98
|
+
const node = method ?? classDecl;
|
|
99
|
+
|
|
100
|
+
for (const decorator of this.findDecoratorsOnNode(node, 'ListenerHandler', useMap, currentFilePath)) {
|
|
101
|
+
const handlerRaw = this.extractExprValue(
|
|
102
|
+
this.getDecoratorArg(decorator, 0),
|
|
103
|
+
useMap,
|
|
104
|
+
currentFilePath,
|
|
105
|
+
currentClass,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (handlerRaw instanceof HandlerData) {
|
|
109
|
+
return handlerRaw;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (method !== undefined) {
|
|
114
|
+
return new HandlerData(currentClass, method.getName());
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return new HandlerData(currentClass, ListenerAttributeReader.DEFAULT_HANDLE_METHOD);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
protected buildListenerExpr(data: ListenerData): ts.Expression {
|
|
121
|
+
const args: ts.Expression[] = [this.buildClassConstExpr(data.eventId), this.buildStringExpr(data.name)];
|
|
122
|
+
|
|
123
|
+
if (data.handler !== null) {
|
|
124
|
+
args.push(this.buildHandlerExpr(data.handler));
|
|
125
|
+
} else {
|
|
126
|
+
args.push(this.buildNullExpr());
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return this.buildNewExpr('Valkyrja\\Event\\Data\\Listener', args);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Sindri package.
|
|
3
|
+
*
|
|
4
|
+
* (c) Melech Mizrachi <melechmizrachi@gmail.com>
|
|
5
|
+
*
|
|
6
|
+
* For the full copyright and license information, please view the LICENSE
|
|
7
|
+
* file that was distributed with this source code.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ts } from 'ts-morph';
|
|
11
|
+
|
|
12
|
+
import { AstReader } from './Abstract/AstReader.ts';
|
|
13
|
+
import { ListenerProviderResult } from './Data/Result/ListenerProviderResult.ts';
|
|
14
|
+
|
|
15
|
+
import type { ListenerProviderReaderContract } from './Contract/ListenerProviderReaderContract.ts';
|
|
16
|
+
|
|
17
|
+
export class ListenerProviderReader extends AstReader implements ListenerProviderReaderContract {
|
|
18
|
+
protected static readonly METHOD_LISTENER_CLASSES = 'getListenerClasses';
|
|
19
|
+
protected static readonly METHOD_LISTENERS = 'getListeners';
|
|
20
|
+
|
|
21
|
+
readFile(filePath: string): ListenerProviderResult {
|
|
22
|
+
const sourceFile = this.parseFileToSourceFile(filePath);
|
|
23
|
+
const classDecl = this.findClass(sourceFile);
|
|
24
|
+
|
|
25
|
+
if (classDecl === undefined) {
|
|
26
|
+
return new ListenerProviderResult();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const useMap = this.buildUseMap(sourceFile);
|
|
30
|
+
const methods = this.indexMethods(classDecl);
|
|
31
|
+
|
|
32
|
+
return new ListenerProviderResult(
|
|
33
|
+
this.extractClassListFromValues(methods[ListenerProviderReader.METHOD_LISTENER_CLASSES], useMap, filePath),
|
|
34
|
+
this.extractListeners(methods[ListenerProviderReader.METHOD_LISTENERS], useMap, filePath),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected extractListeners(
|
|
39
|
+
_method: ReturnType<typeof this.indexMethods>[string] | undefined,
|
|
40
|
+
_useMap: Record<string, string>,
|
|
41
|
+
_filePath: string,
|
|
42
|
+
): ts.Expression[] {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Sindri package.
|
|
3
|
+
*
|
|
4
|
+
* (c) Melech Mizrachi <melechmizrachi@gmail.com>
|
|
5
|
+
*
|
|
6
|
+
* For the full copyright and license information, please view the LICENSE
|
|
7
|
+
* file that was distributed with this source code.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ts } from 'ts-morph';
|
|
11
|
+
|
|
12
|
+
import { AstReader } from './Abstract/AstReader.ts';
|
|
13
|
+
import { RouteProviderResult } from './Data/Result/RouteProviderResult.ts';
|
|
14
|
+
|
|
15
|
+
import type { RouteProviderReaderContract } from './Contract/RouteProviderReaderContract.ts';
|
|
16
|
+
|
|
17
|
+
export class RouteProviderReader extends AstReader implements RouteProviderReaderContract {
|
|
18
|
+
protected static readonly METHOD_CONTROLLER_CLASSES = 'getControllerClasses';
|
|
19
|
+
protected static readonly METHOD_ROUTES = 'getRoutes';
|
|
20
|
+
|
|
21
|
+
readFile(filePath: string): RouteProviderResult {
|
|
22
|
+
const sourceFile = this.parseFileToSourceFile(filePath);
|
|
23
|
+
const classDecl = this.findClass(sourceFile);
|
|
24
|
+
|
|
25
|
+
if (classDecl === undefined) {
|
|
26
|
+
return new RouteProviderResult();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const useMap = this.buildUseMap(sourceFile);
|
|
30
|
+
const methods = this.indexMethods(classDecl);
|
|
31
|
+
|
|
32
|
+
return new RouteProviderResult(
|
|
33
|
+
this.extractClassListFromValues(methods[RouteProviderReader.METHOD_CONTROLLER_CLASSES], useMap, filePath),
|
|
34
|
+
this.extractRoutes(methods[RouteProviderReader.METHOD_ROUTES], useMap, filePath),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected extractRoutes(
|
|
39
|
+
_method: ReturnType<typeof this.indexMethods>[string] | undefined,
|
|
40
|
+
_useMap: Record<string, string>,
|
|
41
|
+
_filePath: string,
|
|
42
|
+
): ts.Expression[] {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
}
|