cloesce 0.0.5-unstable.2 → 0.0.5-unstable.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/dist/ast.d.ts +100 -80
- package/dist/ast.js +12 -12
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +368 -331
- package/dist/extractor/err.d.ts +26 -26
- package/dist/extractor/err.js +129 -100
- package/dist/extractor/extract.d.ts +60 -17
- package/dist/extractor/extract.js +826 -764
- package/dist/router/crud.d.ts +1 -1
- package/dist/router/crud.js +42 -43
- package/dist/router/router.d.ts +135 -98
- package/dist/router/router.js +424 -381
- package/dist/router/validator.d.ts +11 -6
- package/dist/router/validator.js +158 -144
- package/dist/router/wasm.d.ts +56 -22
- package/dist/router/wasm.js +91 -79
- package/dist/ui/backend.d.ts +214 -181
- package/dist/ui/backend.js +258 -245
- package/dist/ui/client.d.ts +1 -1
- package/dist/ui/common.d.ts +54 -31
- package/dist/ui/common.js +171 -159
- package/package.json +1 -1
|
@@ -1,820 +1,882 @@
|
|
|
1
|
-
import { Node as MorphNode, SyntaxKind, Scope
|
|
2
|
-
import { HttpVerb, defaultMediaType
|
|
1
|
+
import { Node as MorphNode, SyntaxKind, Scope } from "ts-morph";
|
|
2
|
+
import { HttpVerb, defaultMediaType } from "../ast.js";
|
|
3
3
|
import { TypeFormatFlags } from "typescript";
|
|
4
4
|
import { ExtractorError, ExtractorErrorCode } from "./err.js";
|
|
5
5
|
import { Either } from "../ui/common.js";
|
|
6
6
|
var AttributeDecoratorKind;
|
|
7
7
|
(function (AttributeDecoratorKind) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
AttributeDecoratorKind["PrimaryKey"] = "PrimaryKey";
|
|
9
|
+
AttributeDecoratorKind["ForeignKey"] = "ForeignKey";
|
|
10
|
+
AttributeDecoratorKind["OneToOne"] = "OneToOne";
|
|
11
|
+
AttributeDecoratorKind["OneToMany"] = "OneToMany";
|
|
12
|
+
AttributeDecoratorKind["ManyToMany"] = "ManyToMany";
|
|
13
|
+
AttributeDecoratorKind["DataSource"] = "DataSource";
|
|
14
14
|
})(AttributeDecoratorKind || (AttributeDecoratorKind = {}));
|
|
15
15
|
var ClassDecoratorKind;
|
|
16
16
|
(function (ClassDecoratorKind) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
ClassDecoratorKind["D1"] = "D1";
|
|
18
|
+
ClassDecoratorKind["WranglerEnv"] = "WranglerEnv";
|
|
19
|
+
ClassDecoratorKind["PlainOldObject"] = "PlainOldObject";
|
|
20
|
+
ClassDecoratorKind["Service"] = "Service";
|
|
21
|
+
ClassDecoratorKind["CRUD"] = "CRUD";
|
|
22
22
|
})(ClassDecoratorKind || (ClassDecoratorKind = {}));
|
|
23
23
|
var ParameterDecoratorKind;
|
|
24
24
|
(function (ParameterDecoratorKind) {
|
|
25
|
-
|
|
25
|
+
ParameterDecoratorKind["Inject"] = "Inject";
|
|
26
26
|
})(ParameterDecoratorKind || (ParameterDecoratorKind = {}));
|
|
27
27
|
export class CidlExtractor {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (hasDecorator(classDecl, ClassDecoratorKind.D1)) {
|
|
56
|
-
if (!classDecl.isExported())
|
|
57
|
-
return notExportedErr;
|
|
58
|
-
const result = CidlExtractor.model(classDecl, sourceFile);
|
|
59
|
-
// Error: propogate from models
|
|
60
|
-
if (result.isLeft()) {
|
|
61
|
-
result.value.addContext((prev) => `${classDecl.getName()}.${prev}`);
|
|
62
|
-
return result;
|
|
63
|
-
}
|
|
64
|
-
const model = result.unwrap();
|
|
65
|
-
models[model.name] = model;
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
if (hasDecorator(classDecl, ClassDecoratorKind.Service)) {
|
|
69
|
-
if (!classDecl.isExported())
|
|
70
|
-
return notExportedErr;
|
|
71
|
-
const result = CidlExtractor.service(classDecl, sourceFile);
|
|
72
|
-
// Error: propogate from service
|
|
73
|
-
if (result.isLeft()) {
|
|
74
|
-
result.value.addContext((prev) => `${classDecl.getName()}.${prev}`);
|
|
75
|
-
return result;
|
|
76
|
-
}
|
|
77
|
-
const service = result.unwrap();
|
|
78
|
-
services[service.name] = service;
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
if (hasDecorator(classDecl, ClassDecoratorKind.PlainOldObject)) {
|
|
82
|
-
if (!classDecl.isExported())
|
|
83
|
-
return notExportedErr;
|
|
84
|
-
const result = CidlExtractor.poo(classDecl, sourceFile);
|
|
85
|
-
// Error: propogate from models
|
|
86
|
-
if (result.isLeft()) {
|
|
87
|
-
result.value.addContext((prev) => `${classDecl.getName()}.${prev}`);
|
|
88
|
-
return result;
|
|
89
|
-
}
|
|
90
|
-
poos[result.unwrap().name] = result.unwrap();
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
if (hasDecorator(classDecl, ClassDecoratorKind.WranglerEnv)) {
|
|
94
|
-
// Error: invalid attribute modifier
|
|
95
|
-
for (const prop of classDecl.getProperties()) {
|
|
96
|
-
const modifierRes = checkAttributeModifier(prop);
|
|
97
|
-
if (modifierRes.isLeft()) {
|
|
98
|
-
return modifierRes;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
const result = CidlExtractor.env(classDecl, sourceFile);
|
|
102
|
-
if (result.isLeft()) {
|
|
103
|
-
return result;
|
|
104
|
-
}
|
|
105
|
-
wranglerEnvs.push(result.unwrap());
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
// Error: Only one wrangler environment can exist
|
|
110
|
-
if (wranglerEnvs.length > 1) {
|
|
111
|
-
return err(ExtractorErrorCode.TooManyWranglerEnvs, (e) => (e.context = wranglerEnvs.map((w) => w.name).toString()));
|
|
112
|
-
}
|
|
113
|
-
return Either.right({
|
|
114
|
-
version: this.version,
|
|
115
|
-
project_name: this.projectName,
|
|
116
|
-
language: "TypeScript",
|
|
117
|
-
wrangler_env: wranglerEnvs[0],
|
|
118
|
-
models,
|
|
119
|
-
poos,
|
|
120
|
-
services,
|
|
121
|
-
app_source,
|
|
28
|
+
projectName;
|
|
29
|
+
version;
|
|
30
|
+
constructor(projectName, version) {
|
|
31
|
+
this.projectName = projectName;
|
|
32
|
+
this.version = version;
|
|
33
|
+
}
|
|
34
|
+
extract(project) {
|
|
35
|
+
const models = {};
|
|
36
|
+
const poos = {};
|
|
37
|
+
const wranglerEnvs = [];
|
|
38
|
+
const services = {};
|
|
39
|
+
let app_source = null;
|
|
40
|
+
for (const sourceFile of project.getSourceFiles()) {
|
|
41
|
+
if (
|
|
42
|
+
sourceFile.getBaseName() === "app.cloesce.ts" ||
|
|
43
|
+
sourceFile.getBaseName() === "seed__app.cloesce.ts" // hardcoding for tests
|
|
44
|
+
) {
|
|
45
|
+
const app = CidlExtractor.app(sourceFile);
|
|
46
|
+
if (app.isLeft()) {
|
|
47
|
+
return app;
|
|
48
|
+
}
|
|
49
|
+
app_source = app.unwrap();
|
|
50
|
+
}
|
|
51
|
+
for (const classDecl of sourceFile.getClasses()) {
|
|
52
|
+
const notExportedErr = err(ExtractorErrorCode.MissingExport, (e) => {
|
|
53
|
+
e.context = classDecl.getName();
|
|
54
|
+
e.snippet = classDecl.getText();
|
|
122
55
|
});
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const typeRes = CidlExtractor.cidlType(prop.getType());
|
|
165
|
-
// Error: invalid property type
|
|
166
|
-
if (typeRes.isLeft()) {
|
|
167
|
-
typeRes.value.context = prop.getName();
|
|
168
|
-
typeRes.value.snippet = prop.getText();
|
|
169
|
-
return typeRes;
|
|
170
|
-
}
|
|
171
|
-
const checkModifierRes = checkAttributeModifier(prop);
|
|
172
|
-
// No decorators means this is a standard attribute
|
|
173
|
-
if (decorators.length === 0) {
|
|
174
|
-
// Error: invalid attribute modifier
|
|
175
|
-
if (checkModifierRes.isLeft()) {
|
|
176
|
-
return checkModifierRes;
|
|
177
|
-
}
|
|
178
|
-
const cidl_type = typeRes.unwrap();
|
|
179
|
-
attributes.push({
|
|
180
|
-
foreign_key_reference: null,
|
|
181
|
-
value: {
|
|
182
|
-
name: prop.getName(),
|
|
183
|
-
cidl_type,
|
|
184
|
-
},
|
|
185
|
-
});
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
// TODO: Limiting to one decorator. Can't get too fancy on us.
|
|
189
|
-
const decorator = decorators[0];
|
|
190
|
-
const decoratorName = getDecoratorName(decorator);
|
|
191
|
-
// Error: invalid attribute modifier
|
|
192
|
-
if (checkModifierRes.isLeft() &&
|
|
193
|
-
decoratorName !== AttributeDecoratorKind.DataSource) {
|
|
194
|
-
return checkModifierRes;
|
|
195
|
-
}
|
|
196
|
-
// Process decorator
|
|
197
|
-
const cidl_type = typeRes.unwrap();
|
|
198
|
-
switch (decoratorName) {
|
|
199
|
-
case AttributeDecoratorKind.PrimaryKey: {
|
|
200
|
-
primary_key = {
|
|
201
|
-
name: prop.getName(),
|
|
202
|
-
cidl_type,
|
|
203
|
-
};
|
|
204
|
-
break;
|
|
205
|
-
}
|
|
206
|
-
case AttributeDecoratorKind.ForeignKey: {
|
|
207
|
-
attributes.push({
|
|
208
|
-
foreign_key_reference: getDecoratorArgument(decorator, 0) ?? null,
|
|
209
|
-
value: {
|
|
210
|
-
name: prop.getName(),
|
|
211
|
-
cidl_type,
|
|
212
|
-
},
|
|
213
|
-
});
|
|
214
|
-
break;
|
|
215
|
-
}
|
|
216
|
-
case AttributeDecoratorKind.OneToOne: {
|
|
217
|
-
const reference = getDecoratorArgument(decorator, 0);
|
|
218
|
-
// Error: One to one navigation properties requre a reference
|
|
219
|
-
if (!reference) {
|
|
220
|
-
return err(ExtractorErrorCode.MissingNavigationPropertyReference, (e) => {
|
|
221
|
-
e.snippet = prop.getText();
|
|
222
|
-
e.context = prop.getName();
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
let model_name = getObjectName(cidl_type);
|
|
226
|
-
// Error: navigation properties require a model reference
|
|
227
|
-
if (!model_name) {
|
|
228
|
-
return err(ExtractorErrorCode.MissingNavigationPropertyReference, (e) => {
|
|
229
|
-
e.snippet = prop.getText();
|
|
230
|
-
e.context = prop.getName();
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
navigation_properties.push({
|
|
234
|
-
var_name: prop.getName(),
|
|
235
|
-
model_name,
|
|
236
|
-
kind: { OneToOne: { reference } },
|
|
237
|
-
});
|
|
238
|
-
break;
|
|
239
|
-
}
|
|
240
|
-
case AttributeDecoratorKind.OneToMany: {
|
|
241
|
-
const reference = getDecoratorArgument(decorator, 0);
|
|
242
|
-
// Error: One to one navigation properties requre a reference
|
|
243
|
-
if (!reference) {
|
|
244
|
-
return err(ExtractorErrorCode.MissingNavigationPropertyReference, (e) => {
|
|
245
|
-
e.snippet = prop.getText();
|
|
246
|
-
e.context = prop.getName();
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
let model_name = getObjectName(cidl_type);
|
|
250
|
-
// Error: navigation properties require a model reference
|
|
251
|
-
if (!model_name) {
|
|
252
|
-
return err(ExtractorErrorCode.MissingNavigationPropertyReference, (e) => {
|
|
253
|
-
e.snippet = prop.getText();
|
|
254
|
-
e.context = prop.getName();
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
navigation_properties.push({
|
|
258
|
-
var_name: prop.getName(),
|
|
259
|
-
model_name,
|
|
260
|
-
kind: { OneToMany: { reference } },
|
|
261
|
-
});
|
|
262
|
-
break;
|
|
263
|
-
}
|
|
264
|
-
case AttributeDecoratorKind.ManyToMany: {
|
|
265
|
-
const unique_id = getDecoratorArgument(decorator, 0);
|
|
266
|
-
// Error: many to many attribtues require a unique id
|
|
267
|
-
if (!unique_id)
|
|
268
|
-
return err(ExtractorErrorCode.MissingManyToManyUniqueId, (e) => {
|
|
269
|
-
e.snippet = prop.getText();
|
|
270
|
-
e.context = prop.getName();
|
|
271
|
-
});
|
|
272
|
-
// Error: navigation properties require a model reference
|
|
273
|
-
let model_name = getObjectName(cidl_type);
|
|
274
|
-
if (!model_name) {
|
|
275
|
-
return err(ExtractorErrorCode.MissingNavigationPropertyReference, (e) => {
|
|
276
|
-
e.snippet = prop.getText();
|
|
277
|
-
e.context = prop.getName();
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
navigation_properties.push({
|
|
281
|
-
var_name: prop.getName(),
|
|
282
|
-
model_name,
|
|
283
|
-
kind: { ManyToMany: { unique_id } },
|
|
284
|
-
});
|
|
285
|
-
break;
|
|
286
|
-
}
|
|
287
|
-
case AttributeDecoratorKind.DataSource: {
|
|
288
|
-
const isIncludeTree = prop
|
|
289
|
-
.getType()
|
|
290
|
-
.getText(undefined, TypeFormatFlags.UseAliasDefinedOutsideCurrentScope) === `IncludeTree<${name}>`;
|
|
291
|
-
// Error: data sources must be static include trees
|
|
292
|
-
if (!prop.isStatic() || !isIncludeTree) {
|
|
293
|
-
return err(ExtractorErrorCode.InvalidDataSourceDefinition, (e) => {
|
|
294
|
-
e.snippet = prop.getText();
|
|
295
|
-
e.context = prop.getName();
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
const initializer = prop.getInitializer();
|
|
299
|
-
const treeRes = CidlExtractor.includeTree(initializer, classDecl, sourceFile);
|
|
300
|
-
if (treeRes.isLeft()) {
|
|
301
|
-
treeRes.value.addContext((prev) => `${prop.getName()} ${prev}`);
|
|
302
|
-
treeRes.value.snippet = prop.getText();
|
|
303
|
-
return treeRes;
|
|
304
|
-
}
|
|
305
|
-
data_sources[prop.getName()] = {
|
|
306
|
-
name: prop.getName(),
|
|
307
|
-
tree: treeRes.unwrap(),
|
|
308
|
-
};
|
|
309
|
-
break;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
if (primary_key == undefined) {
|
|
314
|
-
return err(ExtractorErrorCode.MissingPrimaryKey, (e) => {
|
|
315
|
-
e.snippet = classDecl.getText();
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
// Process methods
|
|
319
|
-
for (const m of classDecl.getMethods()) {
|
|
320
|
-
const httpVerb = m
|
|
321
|
-
.getDecorators()
|
|
322
|
-
.map((d) => getDecoratorName(d))
|
|
323
|
-
.find((name) => Object.values(HttpVerb).includes(name));
|
|
324
|
-
if (!httpVerb) {
|
|
325
|
-
continue;
|
|
326
|
-
}
|
|
327
|
-
const result = CidlExtractor.modelMethod(name, m, httpVerb);
|
|
328
|
-
if (result.isLeft()) {
|
|
329
|
-
result.value.addContext((prev) => `${m.getName()} ${prev}`);
|
|
330
|
-
return result;
|
|
56
|
+
if (hasDecorator(classDecl, ClassDecoratorKind.D1)) {
|
|
57
|
+
if (!classDecl.isExported()) return notExportedErr;
|
|
58
|
+
const result = CidlExtractor.model(classDecl, sourceFile);
|
|
59
|
+
// Error: propogate from models
|
|
60
|
+
if (result.isLeft()) {
|
|
61
|
+
result.value.addContext((prev) => `${classDecl.getName()}.${prev}`);
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
const model = result.unwrap();
|
|
65
|
+
models[model.name] = model;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (hasDecorator(classDecl, ClassDecoratorKind.Service)) {
|
|
69
|
+
if (!classDecl.isExported()) return notExportedErr;
|
|
70
|
+
const result = CidlExtractor.service(classDecl, sourceFile);
|
|
71
|
+
// Error: propogate from service
|
|
72
|
+
if (result.isLeft()) {
|
|
73
|
+
result.value.addContext((prev) => `${classDecl.getName()}.${prev}`);
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
const service = result.unwrap();
|
|
77
|
+
services[service.name] = service;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (hasDecorator(classDecl, ClassDecoratorKind.PlainOldObject)) {
|
|
81
|
+
if (!classDecl.isExported()) return notExportedErr;
|
|
82
|
+
const result = CidlExtractor.poo(classDecl, sourceFile);
|
|
83
|
+
// Error: propogate from models
|
|
84
|
+
if (result.isLeft()) {
|
|
85
|
+
result.value.addContext((prev) => `${classDecl.getName()}.${prev}`);
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
poos[result.unwrap().name] = result.unwrap();
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (hasDecorator(classDecl, ClassDecoratorKind.WranglerEnv)) {
|
|
92
|
+
// Error: invalid attribute modifier
|
|
93
|
+
for (const prop of classDecl.getProperties()) {
|
|
94
|
+
const modifierRes = checkAttributeModifier(prop);
|
|
95
|
+
if (modifierRes.isLeft()) {
|
|
96
|
+
return modifierRes;
|
|
331
97
|
}
|
|
332
|
-
|
|
98
|
+
}
|
|
99
|
+
const result = CidlExtractor.env(classDecl, sourceFile);
|
|
100
|
+
if (result.isLeft()) {
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
wranglerEnvs.push(result.unwrap());
|
|
333
104
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Error: Only one wrangler environment can exist
|
|
108
|
+
if (wranglerEnvs.length > 1) {
|
|
109
|
+
return err(
|
|
110
|
+
ExtractorErrorCode.TooManyWranglerEnvs,
|
|
111
|
+
(e) => (e.context = wranglerEnvs.map((w) => w.name).toString()),
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return Either.right({
|
|
115
|
+
version: this.version,
|
|
116
|
+
project_name: this.projectName,
|
|
117
|
+
language: "TypeScript",
|
|
118
|
+
wrangler_env: wranglerEnvs[0],
|
|
119
|
+
models,
|
|
120
|
+
poos,
|
|
121
|
+
services,
|
|
122
|
+
app_source,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
static app(sourceFile) {
|
|
126
|
+
const symbol = sourceFile.getDefaultExportSymbol();
|
|
127
|
+
const decl = symbol?.getDeclarations()[0];
|
|
128
|
+
if (!decl) {
|
|
129
|
+
return err(ExtractorErrorCode.AppMissingDefaultExport);
|
|
344
130
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
131
|
+
const getTypeText = () => {
|
|
132
|
+
let type = undefined;
|
|
133
|
+
if (MorphNode.isExportAssignment(decl)) {
|
|
134
|
+
type = decl.getExpression()?.getType();
|
|
135
|
+
}
|
|
136
|
+
if (MorphNode.isVariableDeclaration(decl)) {
|
|
137
|
+
type = decl.getInitializer()?.getType();
|
|
138
|
+
}
|
|
139
|
+
return type?.getText(
|
|
140
|
+
undefined,
|
|
141
|
+
TypeFormatFlags.UseAliasDefinedOutsideCurrentScope,
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
const typeText = getTypeText();
|
|
145
|
+
if (typeText === "CloesceApp") {
|
|
146
|
+
return Either.right(sourceFile.getFilePath().toString());
|
|
147
|
+
}
|
|
148
|
+
return err(ExtractorErrorCode.AppMissingDefaultExport);
|
|
149
|
+
}
|
|
150
|
+
static model(classDecl, sourceFile) {
|
|
151
|
+
const name = classDecl.getName();
|
|
152
|
+
const attributes = [];
|
|
153
|
+
const navigation_properties = [];
|
|
154
|
+
const data_sources = {};
|
|
155
|
+
const methods = {};
|
|
156
|
+
const cruds = new Set();
|
|
157
|
+
let primary_key = undefined;
|
|
158
|
+
// Extract crud methods
|
|
159
|
+
const crudDecorator = classDecl
|
|
160
|
+
.getDecorators()
|
|
161
|
+
.find((d) => getDecoratorName(d) === ClassDecoratorKind.CRUD);
|
|
162
|
+
if (crudDecorator) {
|
|
163
|
+
setCrudKinds(crudDecorator, cruds);
|
|
164
|
+
}
|
|
165
|
+
// Iterate attribtutes
|
|
166
|
+
for (const prop of classDecl.getProperties()) {
|
|
167
|
+
const decorators = prop.getDecorators();
|
|
168
|
+
const typeRes = CidlExtractor.cidlType(prop.getType());
|
|
169
|
+
// Error: invalid property type
|
|
170
|
+
if (typeRes.isLeft()) {
|
|
171
|
+
typeRes.value.context = prop.getName();
|
|
172
|
+
typeRes.value.snippet = prop.getText();
|
|
173
|
+
return typeRes;
|
|
174
|
+
}
|
|
175
|
+
const checkModifierRes = checkAttributeModifier(prop);
|
|
176
|
+
// No decorators means this is a standard attribute
|
|
177
|
+
if (decorators.length === 0) {
|
|
178
|
+
// Error: invalid attribute modifier
|
|
179
|
+
if (checkModifierRes.isLeft()) {
|
|
180
|
+
return checkModifierRes;
|
|
181
|
+
}
|
|
182
|
+
const cidl_type = typeRes.unwrap();
|
|
183
|
+
attributes.push({
|
|
184
|
+
foreign_key_reference: null,
|
|
185
|
+
value: {
|
|
186
|
+
name: prop.getName(),
|
|
187
|
+
cidl_type,
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
// TODO: Limiting to one decorator. Can't get too fancy on us.
|
|
193
|
+
const decorator = decorators[0];
|
|
194
|
+
const decoratorName = getDecoratorName(decorator);
|
|
195
|
+
// Error: invalid attribute modifier
|
|
196
|
+
if (
|
|
197
|
+
checkModifierRes.isLeft() &&
|
|
198
|
+
decoratorName !== AttributeDecoratorKind.DataSource
|
|
199
|
+
) {
|
|
200
|
+
return checkModifierRes;
|
|
201
|
+
}
|
|
202
|
+
// Process decorator
|
|
203
|
+
const cidl_type = typeRes.unwrap();
|
|
204
|
+
switch (decoratorName) {
|
|
205
|
+
case AttributeDecoratorKind.PrimaryKey: {
|
|
206
|
+
primary_key = {
|
|
207
|
+
name: prop.getName(),
|
|
208
|
+
cidl_type,
|
|
209
|
+
};
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
case AttributeDecoratorKind.ForeignKey: {
|
|
213
|
+
attributes.push({
|
|
214
|
+
foreign_key_reference: getDecoratorArgument(decorator, 0) ?? null,
|
|
215
|
+
value: {
|
|
216
|
+
name: prop.getName(),
|
|
217
|
+
cidl_type,
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
case AttributeDecoratorKind.OneToOne: {
|
|
223
|
+
const reference = getDecoratorArgument(decorator, 0);
|
|
224
|
+
// Error: One to one navigation properties requre a reference
|
|
225
|
+
if (!reference) {
|
|
226
|
+
return err(
|
|
227
|
+
ExtractorErrorCode.MissingNavigationPropertyReference,
|
|
228
|
+
(e) => {
|
|
229
|
+
e.snippet = prop.getText();
|
|
230
|
+
e.context = prop.getName();
|
|
231
|
+
},
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
let model_name = getObjectName(cidl_type);
|
|
235
|
+
// Error: navigation properties require a model reference
|
|
236
|
+
if (!model_name) {
|
|
237
|
+
return err(
|
|
238
|
+
ExtractorErrorCode.MissingNavigationPropertyReference,
|
|
239
|
+
(e) => {
|
|
240
|
+
e.snippet = prop.getText();
|
|
241
|
+
e.context = prop.getName();
|
|
242
|
+
},
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
navigation_properties.push({
|
|
246
|
+
var_name: prop.getName(),
|
|
247
|
+
model_name,
|
|
248
|
+
kind: { OneToOne: { reference } },
|
|
249
|
+
});
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
case AttributeDecoratorKind.OneToMany: {
|
|
253
|
+
const reference = getDecoratorArgument(decorator, 0);
|
|
254
|
+
// Error: One to one navigation properties requre a reference
|
|
255
|
+
if (!reference) {
|
|
256
|
+
return err(
|
|
257
|
+
ExtractorErrorCode.MissingNavigationPropertyReference,
|
|
258
|
+
(e) => {
|
|
259
|
+
e.snippet = prop.getText();
|
|
260
|
+
e.context = prop.getName();
|
|
261
|
+
},
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
let model_name = getObjectName(cidl_type);
|
|
265
|
+
// Error: navigation properties require a model reference
|
|
266
|
+
if (!model_name) {
|
|
267
|
+
return err(
|
|
268
|
+
ExtractorErrorCode.MissingNavigationPropertyReference,
|
|
269
|
+
(e) => {
|
|
270
|
+
e.snippet = prop.getText();
|
|
271
|
+
e.context = prop.getName();
|
|
272
|
+
},
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
navigation_properties.push({
|
|
276
|
+
var_name: prop.getName(),
|
|
277
|
+
model_name,
|
|
278
|
+
kind: { OneToMany: { reference } },
|
|
279
|
+
});
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
case AttributeDecoratorKind.ManyToMany: {
|
|
283
|
+
const unique_id = getDecoratorArgument(decorator, 0);
|
|
284
|
+
// Error: many to many attribtues require a unique id
|
|
285
|
+
if (!unique_id)
|
|
286
|
+
return err(ExtractorErrorCode.MissingManyToManyUniqueId, (e) => {
|
|
287
|
+
e.snippet = prop.getText();
|
|
288
|
+
e.context = prop.getName();
|
|
351
289
|
});
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
name: param.getName(),
|
|
384
|
-
cidl_type: typeRes.unwrap(),
|
|
290
|
+
// Error: navigation properties require a model reference
|
|
291
|
+
let model_name = getObjectName(cidl_type);
|
|
292
|
+
if (!model_name) {
|
|
293
|
+
return err(
|
|
294
|
+
ExtractorErrorCode.MissingNavigationPropertyReference,
|
|
295
|
+
(e) => {
|
|
296
|
+
e.snippet = prop.getText();
|
|
297
|
+
e.context = prop.getName();
|
|
298
|
+
},
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
navigation_properties.push({
|
|
302
|
+
var_name: prop.getName(),
|
|
303
|
+
model_name,
|
|
304
|
+
kind: { ManyToMany: { unique_id } },
|
|
305
|
+
});
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
case AttributeDecoratorKind.DataSource: {
|
|
309
|
+
const isIncludeTree =
|
|
310
|
+
prop
|
|
311
|
+
.getType()
|
|
312
|
+
.getText(
|
|
313
|
+
undefined,
|
|
314
|
+
TypeFormatFlags.UseAliasDefinedOutsideCurrentScope,
|
|
315
|
+
) === `IncludeTree<${name}>`;
|
|
316
|
+
// Error: data sources must be static include trees
|
|
317
|
+
if (!prop.isStatic() || !isIncludeTree) {
|
|
318
|
+
return err(ExtractorErrorCode.InvalidDataSourceDefinition, (e) => {
|
|
319
|
+
e.snippet = prop.getText();
|
|
320
|
+
e.context = prop.getName();
|
|
385
321
|
});
|
|
386
|
-
|
|
387
|
-
|
|
322
|
+
}
|
|
323
|
+
const initializer = prop.getInitializer();
|
|
324
|
+
const treeRes = CidlExtractor.includeTree(
|
|
325
|
+
initializer,
|
|
326
|
+
classDecl,
|
|
327
|
+
sourceFile,
|
|
328
|
+
);
|
|
329
|
+
if (treeRes.isLeft()) {
|
|
330
|
+
treeRes.value.addContext((prev) => `${prop.getName()} ${prev}`);
|
|
331
|
+
treeRes.value.snippet = prop.getText();
|
|
332
|
+
return treeRes;
|
|
333
|
+
}
|
|
334
|
+
data_sources[prop.getName()] = {
|
|
335
|
+
name: prop.getName(),
|
|
336
|
+
tree: treeRes.unwrap(),
|
|
337
|
+
};
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (primary_key == undefined) {
|
|
343
|
+
return err(ExtractorErrorCode.MissingPrimaryKey, (e) => {
|
|
344
|
+
e.snippet = classDecl.getText();
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
// Process methods
|
|
348
|
+
for (const m of classDecl.getMethods()) {
|
|
349
|
+
const httpVerb = m
|
|
350
|
+
.getDecorators()
|
|
351
|
+
.map((d) => getDecoratorName(d))
|
|
352
|
+
.find((name) => Object.values(HttpVerb).includes(name));
|
|
353
|
+
if (!httpVerb) {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
const result = CidlExtractor.modelMethod(name, m, httpVerb);
|
|
357
|
+
if (result.isLeft()) {
|
|
358
|
+
result.value.addContext((prev) => `${m.getName()} ${prev}`);
|
|
359
|
+
return result;
|
|
360
|
+
}
|
|
361
|
+
methods[result.unwrap().name] = result.unwrap();
|
|
362
|
+
}
|
|
363
|
+
return Either.right({
|
|
364
|
+
name,
|
|
365
|
+
attributes,
|
|
366
|
+
primary_key,
|
|
367
|
+
navigation_properties,
|
|
368
|
+
methods,
|
|
369
|
+
data_sources,
|
|
370
|
+
cruds: Array.from(cruds).sort(),
|
|
371
|
+
source_path: sourceFile.getFilePath().toString(),
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
static modelMethod(modelName, method, verb) {
|
|
375
|
+
// Error: invalid method scope, must be public
|
|
376
|
+
if (method.getScope() != Scope.Public) {
|
|
377
|
+
return err(ExtractorErrorCode.InvalidApiMethodModifier, (e) => {
|
|
378
|
+
e.context = method.getName();
|
|
379
|
+
e.snippet = method.getText();
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
let needsDataSource = !method.isStatic();
|
|
383
|
+
const parameters = [];
|
|
384
|
+
for (const param of method.getParameters()) {
|
|
385
|
+
// Handle injected param
|
|
386
|
+
if (param.getDecorator(ParameterDecoratorKind.Inject)) {
|
|
387
|
+
const typeRes = CidlExtractor.cidlType(param.getType(), true);
|
|
388
388
|
// Error: invalid type
|
|
389
389
|
if (typeRes.isLeft()) {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
// Sugaring: add data source
|
|
394
|
-
if (needsDataSource) {
|
|
395
|
-
parameters.push({
|
|
396
|
-
name: "__dataSource",
|
|
397
|
-
cidl_type: { DataSource: modelName },
|
|
398
|
-
});
|
|
390
|
+
typeRes.value.snippet = method.getText();
|
|
391
|
+
typeRes.value.context = param.getName();
|
|
392
|
+
return typeRes;
|
|
399
393
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
http_verb: verb,
|
|
404
|
-
return_media: defaultMediaType(),
|
|
405
|
-
return_type: typeRes.unwrap(),
|
|
406
|
-
parameters_media: defaultMediaType(),
|
|
407
|
-
parameters,
|
|
394
|
+
parameters.push({
|
|
395
|
+
name: param.getName(),
|
|
396
|
+
cidl_type: typeRes.unwrap(),
|
|
408
397
|
});
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
// Handle all other params
|
|
401
|
+
const typeRes = CidlExtractor.cidlType(param.getType());
|
|
402
|
+
// Error: invalid type
|
|
403
|
+
if (typeRes.isLeft()) {
|
|
404
|
+
typeRes.value.snippet = method.getText();
|
|
405
|
+
typeRes.value.context = param.getName();
|
|
406
|
+
return typeRes;
|
|
407
|
+
}
|
|
408
|
+
if (typeof typeRes.value !== "string" && "DataSource" in typeRes.value) {
|
|
409
|
+
needsDataSource = false;
|
|
410
|
+
}
|
|
411
|
+
parameters.push({
|
|
412
|
+
name: param.getName(),
|
|
413
|
+
cidl_type: typeRes.unwrap(),
|
|
414
|
+
});
|
|
409
415
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
}
|
|
450
|
-
const serviceMethod = res.unwrap();
|
|
451
|
-
methods[serviceMethod.name] = serviceMethod;
|
|
452
|
-
}
|
|
453
|
-
return Either.right({
|
|
454
|
-
name: classDecl.getName(),
|
|
455
|
-
attributes,
|
|
456
|
-
methods,
|
|
457
|
-
source_path: sourceFile.getFilePath().toString(),
|
|
416
|
+
const typeRes = CidlExtractor.cidlType(method.getReturnType());
|
|
417
|
+
// Error: invalid type
|
|
418
|
+
if (typeRes.isLeft()) {
|
|
419
|
+
typeRes.value.snippet = method.getText();
|
|
420
|
+
return typeRes;
|
|
421
|
+
}
|
|
422
|
+
// Sugaring: add data source
|
|
423
|
+
if (needsDataSource) {
|
|
424
|
+
parameters.push({
|
|
425
|
+
name: "__dataSource",
|
|
426
|
+
cidl_type: { DataSource: modelName },
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
return Either.right({
|
|
430
|
+
name: method.getName(),
|
|
431
|
+
is_static: method.isStatic(),
|
|
432
|
+
http_verb: verb,
|
|
433
|
+
return_media: defaultMediaType(),
|
|
434
|
+
return_type: typeRes.unwrap(),
|
|
435
|
+
parameters_media: defaultMediaType(),
|
|
436
|
+
parameters,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
static service(classDecl, sourceFile) {
|
|
440
|
+
const attributes = [];
|
|
441
|
+
const methods = {};
|
|
442
|
+
// Attributes
|
|
443
|
+
for (const prop of classDecl.getProperties()) {
|
|
444
|
+
const typeRes = CidlExtractor.cidlType(prop.getType(), true);
|
|
445
|
+
// Error: invalid property type
|
|
446
|
+
if (typeRes.isLeft()) {
|
|
447
|
+
typeRes.value.context = prop.getName();
|
|
448
|
+
typeRes.value.snippet = prop.getText();
|
|
449
|
+
return typeRes;
|
|
450
|
+
}
|
|
451
|
+
if (typeof typeRes.value === "string" || !("Inject" in typeRes.value)) {
|
|
452
|
+
return err(ExtractorErrorCode.InvalidServiceAttribute, (e) => {
|
|
453
|
+
e.context = prop.getName();
|
|
454
|
+
e.snippet = prop.getText();
|
|
458
455
|
});
|
|
456
|
+
}
|
|
457
|
+
const checkModifierRes = checkAttributeModifier(prop);
|
|
458
|
+
if (checkModifierRes.isLeft()) {
|
|
459
|
+
return checkModifierRes;
|
|
460
|
+
}
|
|
461
|
+
attributes.push({
|
|
462
|
+
var_name: prop.getName(),
|
|
463
|
+
injected: typeRes.value.Inject,
|
|
464
|
+
});
|
|
459
465
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
const typeRes = CidlExtractor.cidlType(method.getReturnType());
|
|
466
|
+
// Methods
|
|
467
|
+
for (const m of classDecl.getMethods()) {
|
|
468
|
+
const httpVerb = m
|
|
469
|
+
.getDecorators()
|
|
470
|
+
.map((d) => getDecoratorName(d))
|
|
471
|
+
.find((name) => Object.values(HttpVerb).includes(name));
|
|
472
|
+
if (!httpVerb) {
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
const res = CidlExtractor.serviceMethod(m, httpVerb);
|
|
476
|
+
if (res.isLeft()) {
|
|
477
|
+
return res;
|
|
478
|
+
}
|
|
479
|
+
const serviceMethod = res.unwrap();
|
|
480
|
+
methods[serviceMethod.name] = serviceMethod;
|
|
481
|
+
}
|
|
482
|
+
return Either.right({
|
|
483
|
+
name: classDecl.getName(),
|
|
484
|
+
attributes,
|
|
485
|
+
methods,
|
|
486
|
+
source_path: sourceFile.getFilePath().toString(),
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
static serviceMethod(method, verb) {
|
|
490
|
+
// Error: invalid method scope, must be public
|
|
491
|
+
if (method.getScope() != Scope.Public) {
|
|
492
|
+
return err(ExtractorErrorCode.InvalidApiMethodModifier, (e) => {
|
|
493
|
+
e.context = method.getName();
|
|
494
|
+
e.snippet = method.getText();
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
const parameters = [];
|
|
498
|
+
for (const param of method.getParameters()) {
|
|
499
|
+
// Handle injected param
|
|
500
|
+
if (param.getDecorator(ParameterDecoratorKind.Inject)) {
|
|
501
|
+
const typeRes = CidlExtractor.cidlType(param.getType(), true);
|
|
499
502
|
// Error: invalid type
|
|
500
503
|
if (typeRes.isLeft()) {
|
|
501
|
-
|
|
502
|
-
|
|
504
|
+
typeRes.value.snippet = method.getText();
|
|
505
|
+
typeRes.value.context = param.getName();
|
|
506
|
+
return typeRes;
|
|
503
507
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
is_static: method.isStatic(),
|
|
508
|
-
return_media: defaultMediaType(),
|
|
509
|
-
return_type: typeRes.unwrap(),
|
|
510
|
-
parameters_media: defaultMediaType(),
|
|
511
|
-
parameters,
|
|
508
|
+
parameters.push({
|
|
509
|
+
name: param.getName(),
|
|
510
|
+
cidl_type: typeRes.unwrap(),
|
|
512
511
|
});
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
// Handle all other params
|
|
515
|
+
const typeRes = CidlExtractor.cidlType(param.getType());
|
|
516
|
+
// Error: invalid type
|
|
517
|
+
if (typeRes.isLeft()) {
|
|
518
|
+
typeRes.value.snippet = method.getText();
|
|
519
|
+
typeRes.value.context = param.getName();
|
|
520
|
+
return typeRes;
|
|
521
|
+
}
|
|
522
|
+
parameters.push({
|
|
523
|
+
name: param.getName(),
|
|
524
|
+
cidl_type: typeRes.unwrap(),
|
|
525
|
+
});
|
|
513
526
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
// Error: invalid property type
|
|
520
|
-
if (typeRes.isLeft()) {
|
|
521
|
-
typeRes.value.context = prop.getName();
|
|
522
|
-
typeRes.value.snippet = prop.getText();
|
|
523
|
-
return typeRes;
|
|
524
|
-
}
|
|
525
|
-
// Error: invalid attribute modifier
|
|
526
|
-
const modifierRes = checkAttributeModifier(prop);
|
|
527
|
-
if (modifierRes.isLeft()) {
|
|
528
|
-
return modifierRes;
|
|
529
|
-
}
|
|
530
|
-
const cidl_type = typeRes.unwrap();
|
|
531
|
-
attributes.push({
|
|
532
|
-
name: prop.getName(),
|
|
533
|
-
cidl_type,
|
|
534
|
-
});
|
|
535
|
-
continue;
|
|
536
|
-
}
|
|
537
|
-
return Either.right({
|
|
538
|
-
name,
|
|
539
|
-
attributes,
|
|
540
|
-
source_path: sourceFile.getFilePath().toString(),
|
|
541
|
-
});
|
|
527
|
+
const typeRes = CidlExtractor.cidlType(method.getReturnType());
|
|
528
|
+
// Error: invalid type
|
|
529
|
+
if (typeRes.isLeft()) {
|
|
530
|
+
typeRes.value.snippet = method.getText();
|
|
531
|
+
return typeRes;
|
|
542
532
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
533
|
+
return Either.right({
|
|
534
|
+
name: method.getName(),
|
|
535
|
+
http_verb: verb,
|
|
536
|
+
is_static: method.isStatic(),
|
|
537
|
+
return_media: defaultMediaType(),
|
|
538
|
+
return_type: typeRes.unwrap(),
|
|
539
|
+
parameters_media: defaultMediaType(),
|
|
540
|
+
parameters,
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
static poo(classDecl, sourceFile) {
|
|
544
|
+
const name = classDecl.getName();
|
|
545
|
+
const attributes = [];
|
|
546
|
+
for (const prop of classDecl.getProperties()) {
|
|
547
|
+
const typeRes = CidlExtractor.cidlType(prop.getType());
|
|
548
|
+
// Error: invalid property type
|
|
549
|
+
if (typeRes.isLeft()) {
|
|
550
|
+
typeRes.value.context = prop.getName();
|
|
551
|
+
typeRes.value.snippet = prop.getText();
|
|
552
|
+
return typeRes;
|
|
553
|
+
}
|
|
554
|
+
// Error: invalid attribute modifier
|
|
555
|
+
const modifierRes = checkAttributeModifier(prop);
|
|
556
|
+
if (modifierRes.isLeft()) {
|
|
557
|
+
return modifierRes;
|
|
558
|
+
}
|
|
559
|
+
const cidl_type = typeRes.unwrap();
|
|
560
|
+
attributes.push({
|
|
561
|
+
name: prop.getName(),
|
|
562
|
+
cidl_type,
|
|
563
|
+
});
|
|
564
|
+
continue;
|
|
570
565
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
const prim = this.primTypeMap[tyText];
|
|
600
|
-
if (prim) {
|
|
601
|
-
return Either.right(wrapNullable(prim, nullable));
|
|
602
|
-
}
|
|
603
|
-
const generics = [
|
|
604
|
-
...unwrappedType.getAliasTypeArguments(),
|
|
605
|
-
...unwrappedType.getTypeArguments(),
|
|
606
|
-
];
|
|
607
|
-
// Error: can't handle multiple generics
|
|
608
|
-
if (generics.length > 1) {
|
|
609
|
-
return err(ExtractorErrorCode.MultipleGenericType);
|
|
610
|
-
}
|
|
611
|
-
// No generics -> inject or object
|
|
612
|
-
if (generics.length === 0) {
|
|
613
|
-
const base = inject ? { Inject: tyText } : { Object: tyText };
|
|
614
|
-
return Either.right(wrapNullable(base, nullable));
|
|
615
|
-
}
|
|
616
|
-
// Single generic
|
|
617
|
-
const genericTy = generics[0];
|
|
618
|
-
const symbolName = unwrappedType.getSymbol()?.getName();
|
|
619
|
-
const aliasName = unwrappedType.getAliasSymbol()?.getName();
|
|
620
|
-
if (aliasName === "DataSourceOf") {
|
|
621
|
-
return Either.right(wrapNullable({
|
|
622
|
-
DataSource: genericTy.getText(undefined, TypeFormatFlags.UseAliasDefinedOutsideCurrentScope),
|
|
623
|
-
}, nullable));
|
|
624
|
-
}
|
|
625
|
-
if (aliasName === "DeepPartial") {
|
|
626
|
-
const [_, genericTyNullable] = unwrapNullable(genericTy);
|
|
627
|
-
const genericTyGenerics = [
|
|
628
|
-
...genericTy.getAliasTypeArguments(),
|
|
629
|
-
...genericTy.getTypeArguments(),
|
|
630
|
-
];
|
|
631
|
-
// Expect partials to be of the exact form DeepPartial<Model>
|
|
632
|
-
if (genericTyNullable ||
|
|
633
|
-
genericTy.isUnion() ||
|
|
634
|
-
genericTyGenerics.length > 0) {
|
|
635
|
-
return err(ExtractorErrorCode.InvalidPartialType);
|
|
636
|
-
}
|
|
637
|
-
return Either.right(wrapNullable({
|
|
638
|
-
Partial: genericTy
|
|
639
|
-
.getText(undefined, TypeFormatFlags.UseAliasDefinedOutsideCurrentScope)
|
|
640
|
-
.split("|")[0]
|
|
641
|
-
.trim(),
|
|
642
|
-
}, nullable));
|
|
643
|
-
}
|
|
644
|
-
if (symbolName === "Promise" || aliasName === "IncludeTree") {
|
|
645
|
-
// Unwrap promises
|
|
646
|
-
return wrapGeneric(genericTy, nullable, (inner) => inner);
|
|
647
|
-
}
|
|
648
|
-
if (unwrappedType.isArray()) {
|
|
649
|
-
return wrapGeneric(genericTy, nullable, (inner) => ({ Array: inner }));
|
|
650
|
-
}
|
|
651
|
-
if (symbolName === "HttpResult") {
|
|
652
|
-
return wrapGeneric(genericTy, nullable, (inner) => ({
|
|
653
|
-
HttpResult: inner,
|
|
654
|
-
}));
|
|
655
|
-
}
|
|
656
|
-
// Error: unknown type
|
|
657
|
-
return err(ExtractorErrorCode.UnknownType);
|
|
658
|
-
function wrapNullable(inner, isNullable) {
|
|
659
|
-
if (isNullable) {
|
|
660
|
-
return { Nullable: inner };
|
|
661
|
-
}
|
|
662
|
-
else {
|
|
663
|
-
return inner;
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
function wrapGeneric(t, isNullable, wrapper) {
|
|
667
|
-
const res = CidlExtractor.cidlType(t, inject);
|
|
668
|
-
// Error: propogated from `cidlType`
|
|
669
|
-
return res.map((inner) => wrapNullable(wrapper(inner), isNullable));
|
|
670
|
-
}
|
|
671
|
-
function unwrapNullable(ty) {
|
|
672
|
-
if (!ty.isUnion())
|
|
673
|
-
return [ty, false];
|
|
674
|
-
const unions = ty.getUnionTypes();
|
|
675
|
-
const nonNulls = unions.filter((t) => !t.isNull() && !t.isUndefined());
|
|
676
|
-
const hasNullable = nonNulls.length < unions.length;
|
|
677
|
-
// Booleans seperate into [null, true, false] from the `getUnionTypes` call
|
|
678
|
-
if (nonNulls.length === 2 &&
|
|
679
|
-
nonNulls.every((t) => t.isBooleanLiteral())) {
|
|
680
|
-
return [nonNulls[0].getApparentType(), hasNullable];
|
|
681
|
-
}
|
|
682
|
-
return [nonNulls[0] ?? ty, hasNullable];
|
|
683
|
-
}
|
|
566
|
+
return Either.right({
|
|
567
|
+
name,
|
|
568
|
+
attributes,
|
|
569
|
+
source_path: sourceFile.getFilePath().toString(),
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
static env(classDecl, sourceFile) {
|
|
573
|
+
const vars = {};
|
|
574
|
+
let binding;
|
|
575
|
+
for (const prop of classDecl.getProperties()) {
|
|
576
|
+
if (
|
|
577
|
+
prop
|
|
578
|
+
.getType()
|
|
579
|
+
.getText(
|
|
580
|
+
undefined,
|
|
581
|
+
TypeFormatFlags.UseAliasDefinedOutsideCurrentScope,
|
|
582
|
+
) === "D1Database"
|
|
583
|
+
) {
|
|
584
|
+
binding = prop.getName();
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
const ty = CidlExtractor.cidlType(prop.getType());
|
|
588
|
+
if (ty.isLeft()) {
|
|
589
|
+
ty.value.context = prop.getName();
|
|
590
|
+
ty.value.snippet = prop.getText();
|
|
591
|
+
return ty;
|
|
592
|
+
}
|
|
593
|
+
vars[prop.getName()] = ty.unwrap();
|
|
684
594
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
595
|
+
if (!binding) {
|
|
596
|
+
return err(ExtractorErrorCode.MissingDatabaseBinding);
|
|
597
|
+
}
|
|
598
|
+
return Either.right({
|
|
599
|
+
name: classDecl.getName(),
|
|
600
|
+
source_path: sourceFile.getFilePath().toString(),
|
|
601
|
+
db_binding: binding,
|
|
602
|
+
vars,
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
static primTypeMap = {
|
|
606
|
+
number: "Real",
|
|
607
|
+
Number: "Real",
|
|
608
|
+
Integer: "Integer",
|
|
609
|
+
string: "Text",
|
|
610
|
+
String: "Text",
|
|
611
|
+
boolean: "Boolean",
|
|
612
|
+
Boolean: "Boolean",
|
|
613
|
+
Date: "DateIso",
|
|
614
|
+
Uint8Array: "Blob",
|
|
615
|
+
Stream: "Stream",
|
|
616
|
+
};
|
|
617
|
+
static cidlType(type, inject = false) {
|
|
618
|
+
// Void
|
|
619
|
+
if (type.isVoid()) {
|
|
620
|
+
return Either.right("Void");
|
|
621
|
+
}
|
|
622
|
+
// Null
|
|
623
|
+
if (type.isNull()) {
|
|
624
|
+
return Either.right({ Nullable: "Void" });
|
|
625
|
+
}
|
|
626
|
+
// Nullable via union
|
|
627
|
+
const [unwrappedType, nullable] = unwrapNullable(type);
|
|
628
|
+
const tyText = unwrappedType
|
|
629
|
+
.getText(undefined, TypeFormatFlags.UseAliasDefinedOutsideCurrentScope)
|
|
630
|
+
.split("|")[0]
|
|
631
|
+
.trim();
|
|
632
|
+
// Primitives
|
|
633
|
+
const prim = this.primTypeMap[tyText];
|
|
634
|
+
if (prim) {
|
|
635
|
+
return Either.right(wrapNullable(prim, nullable));
|
|
636
|
+
}
|
|
637
|
+
const generics = [
|
|
638
|
+
...unwrappedType.getAliasTypeArguments(),
|
|
639
|
+
...unwrappedType.getTypeArguments(),
|
|
640
|
+
];
|
|
641
|
+
// Error: can't handle multiple generics
|
|
642
|
+
if (generics.length > 1) {
|
|
643
|
+
return err(ExtractorErrorCode.MultipleGenericType);
|
|
644
|
+
}
|
|
645
|
+
// No generics -> inject or object
|
|
646
|
+
if (generics.length === 0) {
|
|
647
|
+
const base = inject ? { Inject: tyText } : { Object: tyText };
|
|
648
|
+
return Either.right(wrapNullable(base, nullable));
|
|
649
|
+
}
|
|
650
|
+
// Single generic
|
|
651
|
+
const genericTy = generics[0];
|
|
652
|
+
const symbolName = unwrappedType.getSymbol()?.getName();
|
|
653
|
+
const aliasName = unwrappedType.getAliasSymbol()?.getName();
|
|
654
|
+
if (aliasName === "DataSourceOf") {
|
|
655
|
+
return Either.right(
|
|
656
|
+
wrapNullable(
|
|
657
|
+
{
|
|
658
|
+
DataSource: genericTy.getText(
|
|
659
|
+
undefined,
|
|
660
|
+
TypeFormatFlags.UseAliasDefinedOutsideCurrentScope,
|
|
661
|
+
),
|
|
662
|
+
},
|
|
663
|
+
nullable,
|
|
664
|
+
),
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
if (aliasName === "DeepPartial") {
|
|
668
|
+
const [_, genericTyNullable] = unwrapNullable(genericTy);
|
|
669
|
+
const genericTyGenerics = [
|
|
670
|
+
...genericTy.getAliasTypeArguments(),
|
|
671
|
+
...genericTy.getTypeArguments(),
|
|
672
|
+
];
|
|
673
|
+
// Expect partials to be of the exact form DeepPartial<Model>
|
|
674
|
+
if (
|
|
675
|
+
genericTyNullable ||
|
|
676
|
+
genericTy.isUnion() ||
|
|
677
|
+
genericTyGenerics.length > 0
|
|
678
|
+
) {
|
|
679
|
+
return err(ExtractorErrorCode.InvalidPartialType);
|
|
680
|
+
}
|
|
681
|
+
return Either.right(
|
|
682
|
+
wrapNullable(
|
|
683
|
+
{
|
|
684
|
+
Partial: genericTy
|
|
685
|
+
.getText(
|
|
686
|
+
undefined,
|
|
687
|
+
TypeFormatFlags.UseAliasDefinedOutsideCurrentScope,
|
|
688
|
+
)
|
|
689
|
+
.split("|")[0]
|
|
690
|
+
.trim(),
|
|
691
|
+
},
|
|
692
|
+
nullable,
|
|
693
|
+
),
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
if (symbolName === "Promise" || aliasName === "IncludeTree") {
|
|
697
|
+
// Unwrap promises
|
|
698
|
+
return wrapGeneric(genericTy, nullable, (inner) => inner);
|
|
699
|
+
}
|
|
700
|
+
if (unwrappedType.isArray()) {
|
|
701
|
+
return wrapGeneric(genericTy, nullable, (inner) => ({ Array: inner }));
|
|
702
|
+
}
|
|
703
|
+
if (symbolName === "HttpResult") {
|
|
704
|
+
return wrapGeneric(genericTy, nullable, (inner) => ({
|
|
705
|
+
HttpResult: inner,
|
|
706
|
+
}));
|
|
707
|
+
}
|
|
708
|
+
// Error: unknown type
|
|
709
|
+
return err(ExtractorErrorCode.UnknownType);
|
|
710
|
+
function wrapNullable(inner, isNullable) {
|
|
711
|
+
if (isNullable) {
|
|
712
|
+
return { Nullable: inner };
|
|
713
|
+
} else {
|
|
714
|
+
return inner;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
function wrapGeneric(t, isNullable, wrapper) {
|
|
718
|
+
const res = CidlExtractor.cidlType(t, inject);
|
|
719
|
+
// Error: propogated from `cidlType`
|
|
720
|
+
return res.map((inner) => wrapNullable(wrapper(inner), isNullable));
|
|
721
|
+
}
|
|
722
|
+
function unwrapNullable(ty) {
|
|
723
|
+
if (!ty.isUnion()) return [ty, false];
|
|
724
|
+
const unions = ty.getUnionTypes();
|
|
725
|
+
const nonNulls = unions.filter((t) => !t.isNull() && !t.isUndefined());
|
|
726
|
+
const hasNullable = nonNulls.length < unions.length;
|
|
727
|
+
// Booleans seperate into [null, true, false] from the `getUnionTypes` call
|
|
728
|
+
if (
|
|
729
|
+
nonNulls.length === 2 &&
|
|
730
|
+
nonNulls.every((t) => t.isBooleanLiteral())
|
|
731
|
+
) {
|
|
732
|
+
return [nonNulls[0].getApparentType(), hasNullable];
|
|
733
|
+
}
|
|
734
|
+
return [nonNulls[0] ?? ty, hasNullable];
|
|
743
735
|
}
|
|
736
|
+
}
|
|
737
|
+
static includeTree(expr, currentClass, sf) {
|
|
738
|
+
// Include trees must be of the expected form
|
|
739
|
+
if (
|
|
740
|
+
!expr ||
|
|
741
|
+
!expr.isKind ||
|
|
742
|
+
!expr.isKind(SyntaxKind.ObjectLiteralExpression)
|
|
743
|
+
) {
|
|
744
|
+
return err(ExtractorErrorCode.InvalidIncludeTree);
|
|
745
|
+
}
|
|
746
|
+
const result = {};
|
|
747
|
+
for (const prop of expr.getProperties()) {
|
|
748
|
+
if (!prop.isKind(SyntaxKind.PropertyAssignment)) continue;
|
|
749
|
+
// Error: navigation property not found
|
|
750
|
+
const navProp = findPropertyByName(currentClass, prop.getName());
|
|
751
|
+
if (!navProp) {
|
|
752
|
+
return err(
|
|
753
|
+
ExtractorErrorCode.UnknownNavigationPropertyReference,
|
|
754
|
+
(e) => {
|
|
755
|
+
e.snippet = expr.getText();
|
|
756
|
+
e.context = prop.getName();
|
|
757
|
+
},
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
const typeRes = CidlExtractor.cidlType(navProp.getType());
|
|
761
|
+
// Error: invalid referenced nav prop type
|
|
762
|
+
if (typeRes.isLeft()) {
|
|
763
|
+
typeRes.value.snippet = navProp.getText();
|
|
764
|
+
typeRes.value.context = prop.getName();
|
|
765
|
+
return typeRes;
|
|
766
|
+
}
|
|
767
|
+
// Error: invalid referenced nav prop type
|
|
768
|
+
const cidl_type = typeRes.unwrap();
|
|
769
|
+
if (typeof cidl_type === "string") {
|
|
770
|
+
return err(
|
|
771
|
+
ExtractorErrorCode.InvalidNavigationPropertyReference,
|
|
772
|
+
(e) => {
|
|
773
|
+
e.snippet = navProp.getText();
|
|
774
|
+
e.context = prop.getName();
|
|
775
|
+
},
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
// Recurse for nested includes
|
|
779
|
+
const initializer = prop.getInitializer?.();
|
|
780
|
+
let nestedTree = {};
|
|
781
|
+
if (initializer?.isKind?.(SyntaxKind.ObjectLiteralExpression)) {
|
|
782
|
+
const targetModel = getObjectName(cidl_type);
|
|
783
|
+
const targetClass = currentClass
|
|
784
|
+
.getSourceFile()
|
|
785
|
+
.getProject()
|
|
786
|
+
.getSourceFiles()
|
|
787
|
+
.flatMap((f) => f.getClasses())
|
|
788
|
+
.find((c) => c.getName() === targetModel);
|
|
789
|
+
if (targetClass) {
|
|
790
|
+
const treeRes = CidlExtractor.includeTree(
|
|
791
|
+
initializer,
|
|
792
|
+
targetClass,
|
|
793
|
+
sf,
|
|
794
|
+
);
|
|
795
|
+
// Error: Propogated from `includeTree`
|
|
796
|
+
if (treeRes.isLeft()) {
|
|
797
|
+
treeRes.value.snippet = expr.getText();
|
|
798
|
+
return treeRes;
|
|
799
|
+
}
|
|
800
|
+
nestedTree = treeRes.unwrap();
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
result[navProp.getName()] = nestedTree;
|
|
804
|
+
}
|
|
805
|
+
return Either.right(result);
|
|
806
|
+
}
|
|
744
807
|
}
|
|
745
808
|
function err(code, fn) {
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
809
|
+
let e = new ExtractorError(code);
|
|
810
|
+
if (fn) {
|
|
811
|
+
fn(e);
|
|
812
|
+
}
|
|
813
|
+
return Either.left(e);
|
|
751
814
|
}
|
|
752
815
|
function getDecoratorName(decorator) {
|
|
753
|
-
|
|
754
|
-
|
|
816
|
+
const name = decorator.getName() ?? decorator.getExpression().getText();
|
|
817
|
+
return String(name).replace(/\(.*\)$/, "");
|
|
755
818
|
}
|
|
756
819
|
function getDecoratorArgument(decorator, index) {
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
return arg.getLiteralValue();
|
|
820
|
+
const args = decorator.getArguments();
|
|
821
|
+
if (!args[index]) return undefined;
|
|
822
|
+
const arg = args[index];
|
|
823
|
+
if (arg.getKind?.() === SyntaxKind.Identifier) {
|
|
824
|
+
return arg.getText();
|
|
825
|
+
}
|
|
826
|
+
return arg.getLiteralValue();
|
|
765
827
|
}
|
|
766
828
|
function getRootType(t) {
|
|
767
|
-
|
|
768
|
-
return t;
|
|
769
|
-
}
|
|
770
|
-
if ("Nullable" in t) {
|
|
771
|
-
return getRootType(t.Nullable);
|
|
772
|
-
}
|
|
773
|
-
if ("Array" in t) {
|
|
774
|
-
return getRootType(t.Array);
|
|
775
|
-
}
|
|
776
|
-
if ("HttpResult" in t) {
|
|
777
|
-
return getRootType(t.HttpResult);
|
|
778
|
-
}
|
|
829
|
+
if (typeof t === "string") {
|
|
779
830
|
return t;
|
|
831
|
+
}
|
|
832
|
+
if ("Nullable" in t) {
|
|
833
|
+
return getRootType(t.Nullable);
|
|
834
|
+
}
|
|
835
|
+
if ("Array" in t) {
|
|
836
|
+
return getRootType(t.Array);
|
|
837
|
+
}
|
|
838
|
+
if ("HttpResult" in t) {
|
|
839
|
+
return getRootType(t.HttpResult);
|
|
840
|
+
}
|
|
841
|
+
return t;
|
|
780
842
|
}
|
|
781
843
|
function getObjectName(t) {
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
844
|
+
const root = getRootType(t);
|
|
845
|
+
if (typeof root !== "string" && "Object" in root) {
|
|
846
|
+
return root["Object"];
|
|
847
|
+
}
|
|
848
|
+
return undefined;
|
|
787
849
|
}
|
|
788
850
|
function setCrudKinds(d, cruds) {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
}
|
|
851
|
+
const arg = d.getArguments()[0];
|
|
852
|
+
if (!arg) {
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
if (MorphNode.isArrayLiteralExpression(arg)) {
|
|
856
|
+
for (const a of arg.getElements()) {
|
|
857
|
+
cruds.add(
|
|
858
|
+
MorphNode.isStringLiteral(a) ? a.getLiteralValue() : a.getText(),
|
|
859
|
+
);
|
|
799
860
|
}
|
|
861
|
+
}
|
|
800
862
|
}
|
|
801
863
|
function findPropertyByName(cls, name) {
|
|
802
|
-
|
|
803
|
-
|
|
864
|
+
const exactMatch = cls.getProperties().find((p) => p.getName() === name);
|
|
865
|
+
return exactMatch;
|
|
804
866
|
}
|
|
805
867
|
function hasDecorator(node, name) {
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
868
|
+
return node.getDecorators().some((d) => {
|
|
869
|
+
const decoratorName = getDecoratorName(d);
|
|
870
|
+
return decoratorName === name || decoratorName.endsWith("." + name);
|
|
871
|
+
});
|
|
810
872
|
}
|
|
811
873
|
function checkAttributeModifier(prop) {
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
874
|
+
// Error: attributes must be just 'public'
|
|
875
|
+
if (prop.getScope() != Scope.Public || prop.isReadonly() || prop.isStatic()) {
|
|
876
|
+
return err(ExtractorErrorCode.InvalidAttributeModifier, (e) => {
|
|
877
|
+
e.context = prop.getName();
|
|
878
|
+
e.snippet = prop.getText();
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
return Either.right(null);
|
|
820
882
|
}
|