@vertz/compiler 0.2.14 → 0.2.16
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/index.d.ts +53 -2
- package/dist/index.js +418 -113
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
type DiagnosticSeverity = "error" | "warning" | "info";
|
|
2
|
-
type DiagnosticCode = "VERTZ_SCHEMA_NAMING" | "VERTZ_SCHEMA_PLACEMENT" | "VERTZ_SCHEMA_EXECUTION" | "VERTZ_SCHEMA_MISSING_ID" | "VERTZ_SCHEMA_DYNAMIC_NAME" | "VERTZ_MODULE_CIRCULAR" | "VERTZ_MODULE_EXPORT_INVALID" | "VERTZ_MODULE_IMPORT_MISSING" | "VERTZ_MODULE_DUPLICATE_NAME" | "VERTZ_MODULE_DYNAMIC_NAME" | "VERTZ_MODULE_OPTIONS_INVALID" | "VERTZ_MODULE_WRONG_OWNERSHIP" | "VERTZ_SERVICE_INJECT_MISSING" | "VERTZ_SERVICE_UNUSED" | "VERTZ_SERVICE_DYNAMIC_NAME" | "VERTZ_ENV_MISSING_DEFAULT" | "VERTZ_ENV_DUPLICATE" | "VERTZ_ENV_DYNAMIC_CONFIG" | "VERTZ_MW_MISSING_NAME" | "VERTZ_MW_MISSING_HANDLER" | "VERTZ_MW_DYNAMIC_NAME" | "VERTZ_MW_NON_OBJECT_CONFIG" | "VERTZ_MW_REQUIRES_UNSATISFIED" | "VERTZ_MW_PROVIDES_COLLISION" | "VERTZ_MW_ORDER_INVALID" | "VERTZ_RT_UNKNOWN_MODULE_DEF" | "VERTZ_RT_DYNAMIC_PATH" | "VERTZ_RT_MISSING_HANDLER" | "VERTZ_RT_MISSING_PREFIX" | "VERTZ_RT_DYNAMIC_CONFIG" | "VERTZ_RT_INVALID_PATH" | "VERTZ_ROUTE_DUPLICATE" | "VERTZ_ROUTE_PARAM_MISMATCH" | "VERTZ_ROUTE_MISSING_RESPONSE" | "VERTZ_APP_MISSING" | "VERTZ_APP_NOT_FOUND" | "VERTZ_APP_DUPLICATE" | "VERTZ_APP_BASEPATH_FORMAT" | "VERTZ_APP_INLINE_MODULE" | "VERTZ_DEP_CYCLE" | "VERTZ_DEP_CIRCULAR" | "VERTZ_DEP_UNRESOLVED_INJECT" | "VERTZ_DEP_INIT_ORDER" | "VERTZ_CTX_COLLISION" | "VERTZ_DEAD_CODE" | "ENTITY_MISSING_ARGS" | "ENTITY_NON_LITERAL_NAME" | "ENTITY_INVALID_NAME" | "ENTITY_DUPLICATE_NAME" | "ENTITY_CONFIG_NOT_OBJECT" | "ENTITY_MISSING_MODEL" | "ENTITY_MODEL_UNRESOLVABLE" | "ENTITY_ACTION_NAME_COLLISION" | "ENTITY_ACTION_MISSING_SCHEMA" | "ENTITY_ACTION_INVALID_METHOD" | "ENTITY_UNKNOWN_ACCESS_OP" | "ENTITY_UNRESOLVED_IMPORT" | "ENTITY_ROUTE_COLLISION" | "ENTITY_NO_ROUTES" | "ENTITY_MODEL_NOT_REGISTERED";
|
|
2
|
+
type DiagnosticCode = "VERTZ_SCHEMA_NAMING" | "VERTZ_SCHEMA_PLACEMENT" | "VERTZ_SCHEMA_EXECUTION" | "VERTZ_SCHEMA_MISSING_ID" | "VERTZ_SCHEMA_DYNAMIC_NAME" | "VERTZ_MODULE_CIRCULAR" | "VERTZ_MODULE_EXPORT_INVALID" | "VERTZ_MODULE_IMPORT_MISSING" | "VERTZ_MODULE_DUPLICATE_NAME" | "VERTZ_MODULE_DYNAMIC_NAME" | "VERTZ_MODULE_OPTIONS_INVALID" | "VERTZ_MODULE_WRONG_OWNERSHIP" | "VERTZ_SERVICE_INJECT_MISSING" | "VERTZ_SERVICE_UNUSED" | "VERTZ_SERVICE_DYNAMIC_NAME" | "VERTZ_ENV_MISSING_DEFAULT" | "VERTZ_ENV_DUPLICATE" | "VERTZ_ENV_DYNAMIC_CONFIG" | "VERTZ_MW_MISSING_NAME" | "VERTZ_MW_MISSING_HANDLER" | "VERTZ_MW_DYNAMIC_NAME" | "VERTZ_MW_NON_OBJECT_CONFIG" | "VERTZ_MW_REQUIRES_UNSATISFIED" | "VERTZ_MW_PROVIDES_COLLISION" | "VERTZ_MW_ORDER_INVALID" | "VERTZ_RT_UNKNOWN_MODULE_DEF" | "VERTZ_RT_DYNAMIC_PATH" | "VERTZ_RT_MISSING_HANDLER" | "VERTZ_RT_MISSING_PREFIX" | "VERTZ_RT_DYNAMIC_CONFIG" | "VERTZ_RT_INVALID_PATH" | "VERTZ_ROUTE_DUPLICATE" | "VERTZ_ROUTE_PARAM_MISMATCH" | "VERTZ_ROUTE_MISSING_RESPONSE" | "VERTZ_APP_MISSING" | "VERTZ_APP_NOT_FOUND" | "VERTZ_APP_DUPLICATE" | "VERTZ_APP_BASEPATH_FORMAT" | "VERTZ_APP_INLINE_MODULE" | "VERTZ_DEP_CYCLE" | "VERTZ_DEP_CIRCULAR" | "VERTZ_DEP_UNRESOLVED_INJECT" | "VERTZ_DEP_INIT_ORDER" | "VERTZ_CTX_COLLISION" | "VERTZ_DEAD_CODE" | "ENTITY_MISSING_ARGS" | "ENTITY_NON_LITERAL_NAME" | "ENTITY_INVALID_NAME" | "ENTITY_DUPLICATE_NAME" | "ENTITY_CONFIG_NOT_OBJECT" | "ENTITY_MISSING_MODEL" | "ENTITY_MODEL_UNRESOLVABLE" | "ENTITY_ACTION_NAME_COLLISION" | "ENTITY_ACTION_MISSING_SCHEMA" | "ENTITY_ACTION_INVALID_METHOD" | "ENTITY_UNKNOWN_ACCESS_OP" | "ENTITY_UNRESOLVED_IMPORT" | "ENTITY_ROUTE_COLLISION" | "ENTITY_NO_ROUTES" | "ENTITY_MODEL_NOT_REGISTERED" | "ACCESS_MULTIPLE_DEFINITIONS" | "ACCESS_NON_LITERAL_KEY" | "ACCESS_NON_LITERAL_ROLE" | "ACCESS_DUPLICATE_ENTITLEMENT" | "ACCESS_WHERE_NOT_TRANSLATABLE";
|
|
3
3
|
interface SourceContext {
|
|
4
4
|
lines: {
|
|
5
5
|
number: number;
|
|
@@ -39,9 +39,32 @@ interface AppIR {
|
|
|
39
39
|
schemas: SchemaIR[];
|
|
40
40
|
entities: EntityIR[];
|
|
41
41
|
databases: DatabaseIR[];
|
|
42
|
+
access?: AccessIR;
|
|
42
43
|
dependencyGraph: DependencyGraphIR;
|
|
43
44
|
diagnostics: Diagnostic[];
|
|
44
45
|
}
|
|
46
|
+
interface AccessIR extends SourceLocation {
|
|
47
|
+
entities: AccessEntityIR[];
|
|
48
|
+
entitlements: string[];
|
|
49
|
+
whereClauses: AccessWhereClauseIR[];
|
|
50
|
+
}
|
|
51
|
+
interface AccessEntityIR {
|
|
52
|
+
name: string;
|
|
53
|
+
roles: string[];
|
|
54
|
+
}
|
|
55
|
+
interface AccessWhereClauseIR {
|
|
56
|
+
entitlement: string;
|
|
57
|
+
conditions: AccessWhereCondition[];
|
|
58
|
+
}
|
|
59
|
+
type AccessWhereCondition = {
|
|
60
|
+
kind: "marker";
|
|
61
|
+
column: string;
|
|
62
|
+
marker: "user.id" | "user.tenantId";
|
|
63
|
+
} | {
|
|
64
|
+
kind: "literal";
|
|
65
|
+
column: string;
|
|
66
|
+
value: string | number | boolean;
|
|
67
|
+
};
|
|
45
68
|
interface AppDefinition extends SourceLocation {
|
|
46
69
|
basePath: string;
|
|
47
70
|
version?: string;
|
|
@@ -174,12 +197,16 @@ interface EntityIR extends SourceLocation {
|
|
|
174
197
|
hooks: EntityHooksIR;
|
|
175
198
|
actions: EntityActionIR[];
|
|
176
199
|
relations: EntityRelationIR[];
|
|
200
|
+
tenantScoped?: boolean;
|
|
201
|
+
table?: string;
|
|
177
202
|
}
|
|
178
203
|
interface EntityModelRef {
|
|
179
204
|
variableName: string;
|
|
180
205
|
importSource?: string;
|
|
181
206
|
tableName?: string;
|
|
182
207
|
schemaRefs: EntityModelSchemaRefs;
|
|
208
|
+
primaryKey?: string;
|
|
209
|
+
hiddenFields?: string[];
|
|
183
210
|
}
|
|
184
211
|
interface EntityModelSchemaRefs {
|
|
185
212
|
response?: SchemaRef;
|
|
@@ -215,6 +242,9 @@ interface EntityRelationIR {
|
|
|
215
242
|
type?: "one" | "many";
|
|
216
243
|
entity?: string;
|
|
217
244
|
selection: "all" | string[];
|
|
245
|
+
allowWhere?: string[];
|
|
246
|
+
allowOrderBy?: string[];
|
|
247
|
+
maxLimit?: number;
|
|
218
248
|
}
|
|
219
249
|
interface ModuleDefContext {
|
|
220
250
|
moduleDefVariables: Map<string, string>;
|
|
@@ -287,6 +317,21 @@ declare abstract class BaseAnalyzer<T> implements Analyzer<T> {
|
|
|
287
317
|
protected addDiagnostic(diagnostic: Diagnostic): void;
|
|
288
318
|
getDiagnostics(): Diagnostic[];
|
|
289
319
|
}
|
|
320
|
+
interface AccessAnalyzerResult {
|
|
321
|
+
access?: AccessIR;
|
|
322
|
+
}
|
|
323
|
+
declare class AccessAnalyzer extends BaseAnalyzer<AccessAnalyzerResult> {
|
|
324
|
+
analyze(): Promise<AccessAnalyzerResult>;
|
|
325
|
+
private findDefineAccessCalls;
|
|
326
|
+
private extractAccess;
|
|
327
|
+
private extractWhereClauses;
|
|
328
|
+
/** Find all .where() calls within a node — handles both r.where() and rules.where() */
|
|
329
|
+
private findWhereCalls;
|
|
330
|
+
/** Extract conditions from a where({ column: value }) call argument */
|
|
331
|
+
private extractWhereConditions;
|
|
332
|
+
/** Resolve a condition value to a marker or literal */
|
|
333
|
+
private extractConditionValue;
|
|
334
|
+
}
|
|
290
335
|
interface AppAnalyzerResult {
|
|
291
336
|
app: AppDefinition;
|
|
292
337
|
}
|
|
@@ -379,6 +424,11 @@ declare class EntityAnalyzer extends BaseAnalyzer<EntityAnalyzerResult> {
|
|
|
379
424
|
private resolveSchemaFromExpression;
|
|
380
425
|
private extractRelations;
|
|
381
426
|
/**
|
|
427
|
+
* Extract primaryKey and hiddenFields from the model's table type.
|
|
428
|
+
* Navigates ModelDef.table._columns to inspect column metadata.
|
|
429
|
+
*/
|
|
430
|
+
private extractModelTableMetadata;
|
|
431
|
+
/**
|
|
382
432
|
* Resolve relation types from the model's TypeScript type.
|
|
383
433
|
* Navigates ModelDef.relations to extract _type ('one'/'many') for each relation.
|
|
384
434
|
*/
|
|
@@ -474,6 +524,7 @@ interface CompilerDependencies {
|
|
|
474
524
|
app: Analyzer<AppAnalyzerResult>;
|
|
475
525
|
entity: Analyzer<EntityAnalyzerResult>;
|
|
476
526
|
database: Analyzer<DatabaseAnalyzerResult>;
|
|
527
|
+
access: Analyzer<AccessAnalyzerResult>;
|
|
477
528
|
dependencyGraph: Analyzer<DependencyGraphResult>;
|
|
478
529
|
};
|
|
479
530
|
validators: Validator[];
|
|
@@ -860,4 +911,4 @@ declare class PlacementValidator implements Validator {
|
|
|
860
911
|
private checkFileLocation;
|
|
861
912
|
private checkMixedExports;
|
|
862
913
|
}
|
|
863
|
-
export { typecheckWatch, typecheck, resolveImportPath, resolveIdentifier, resolveExport, resolveConfig, renderSchemaRegistryFile, renderRouteTableFile, renderBootFile, parseWatchBlock, parseTscOutput, parseSchemaName2 as parseSchemaName, parseInjectRefs, parseImports, mergeIR, mergeDiagnostics, isSchemaFile, isSchemaExpression, isFromImport, injectEntityRoutes, hasErrors, getVariableNameForCall, getStringValue, getSourceLocation, getPropertyValue, getProperties, getNumberValue, getBooleanValue, getArrayElements, findMethodCallsOnVariable, findCallExpressions, findAffectedModules, filterBySeverity, extractSchemaId, extractObjectLiteral, extractMethodSignatures, extractIdentifierNames, detectRouteCollisions, defineConfig, createSchemaExecutor, createNamedSchemaRef, createInlineSchemaRef, createEmptyDependencyGraph, createEmptyAppIR, createDiagnosticFromLocation, createDiagnostic, createCompiler, categorizeChanges, buildSchemaRegistry, buildRouteTable, buildManifest, buildBootManifest, addDiagnosticsToIR, VertzConfig, Validator, ValidationConfig, ValidPart, ValidOperation, TypecheckWatchOptions, TypecheckResult, TypecheckOptions, TypecheckDiagnostic, SourceLocation, SourceContext, ServiceMethodParam, ServiceMethodIR, ServiceIR, ServiceAnalyzerResult, ServiceAnalyzer, SchemaRegistryManifest, SchemaRegistryGenerator, SchemaRegistryEntry, SchemaRef, SchemaNameParts, SchemaIR, SchemaExecutor, SchemaExecutionResult, SchemaConfig, SchemaAnalyzerResult, SchemaAnalyzer, RouterIR, RouteTableSchemas, RouteTableManifest, RouteTableGenerator, RouteTableEntry, RouteIR, RouteAnalyzerResult, RouteAnalyzer, ResolvedImport, ResolvedConfig, PlacementValidator, ParsedSchemaName, OpenAPITag, OpenAPIServer, OpenAPIResponse, OpenAPIRequestBody, OpenAPIPathItem, OpenAPIParameter, OpenAPIOperation, OpenAPIInfo, OpenAPIGenerator, OpenAPIDocument, OpenAPIConfig, NamingValidator, NamedSchemaRef, ModuleValidator, ModuleRegistration, ModuleIR, ModuleDefContext, ModuleAnalyzerResult, ModuleAnalyzer, MiddlewareRef, MiddlewareIR, MiddlewareAnalyzerResult, MiddlewareAnalyzer, ManifestRoute, ManifestModule, ManifestMiddleware, ManifestGenerator, ManifestDiagnostic, ManifestDependencyEdge, JSONSchemaObject, InlineSchemaRef, InjectRef, IncrementalResult, IncrementalCompiler, ImportRef, HttpMethod, Generator, FileChange, FileCategory, EnvVariableIR, EnvIR, EnvAnalyzerResult, EnvAnalyzer, EntityRelationIR, EntityModelSchemaRefs, EntityModelRef, EntityIR, EntityHooksIR, EntityAnalyzerResult, EntityAnalyzer, EntityActionIR, EntityAccessRuleKind, EntityAccessIR, DiagnosticSeverity, DiagnosticCode, Diagnostic, DependencyNodeKind, DependencyNode, DependencyGraphResult, DependencyGraphInput, DependencyGraphIR, DependencyGraphAnalyzer, DependencyEdgeKind, DependencyEdge, DatabaseIR, DatabaseAnalyzerResult, DatabaseAnalyzer, CreateDiagnosticOptions, CompletenessValidator, CompilerDependencies, CompilerConfig, Compiler, CompileResult, CategorizedChanges, CategorizeOptions, BootModuleEntry, BootMiddlewareEntry, BootManifest, BootGenerator, BaseGenerator, BaseAnalyzer, AppManifest, AppIR, AppDefinition, AppAnalyzerResult, AppAnalyzer, Analyzer };
|
|
914
|
+
export { typecheckWatch, typecheck, resolveImportPath, resolveIdentifier, resolveExport, resolveConfig, renderSchemaRegistryFile, renderRouteTableFile, renderBootFile, parseWatchBlock, parseTscOutput, parseSchemaName2 as parseSchemaName, parseInjectRefs, parseImports, mergeIR, mergeDiagnostics, isSchemaFile, isSchemaExpression, isFromImport, injectEntityRoutes, hasErrors, getVariableNameForCall, getStringValue, getSourceLocation, getPropertyValue, getProperties, getNumberValue, getBooleanValue, getArrayElements, findMethodCallsOnVariable, findCallExpressions, findAffectedModules, filterBySeverity, extractSchemaId, extractObjectLiteral, extractMethodSignatures, extractIdentifierNames, detectRouteCollisions, defineConfig, createSchemaExecutor, createNamedSchemaRef, createInlineSchemaRef, createEmptyDependencyGraph, createEmptyAppIR, createDiagnosticFromLocation, createDiagnostic, createCompiler, categorizeChanges, buildSchemaRegistry, buildRouteTable, buildManifest, buildBootManifest, addDiagnosticsToIR, VertzConfig, Validator, ValidationConfig, ValidPart, ValidOperation, TypecheckWatchOptions, TypecheckResult, TypecheckOptions, TypecheckDiagnostic, SourceLocation, SourceContext, ServiceMethodParam, ServiceMethodIR, ServiceIR, ServiceAnalyzerResult, ServiceAnalyzer, SchemaRegistryManifest, SchemaRegistryGenerator, SchemaRegistryEntry, SchemaRef, SchemaNameParts, SchemaIR, SchemaExecutor, SchemaExecutionResult, SchemaConfig, SchemaAnalyzerResult, SchemaAnalyzer, RouterIR, RouteTableSchemas, RouteTableManifest, RouteTableGenerator, RouteTableEntry, RouteIR, RouteAnalyzerResult, RouteAnalyzer, ResolvedImport, ResolvedConfig, PlacementValidator, ParsedSchemaName, OpenAPITag, OpenAPIServer, OpenAPIResponse, OpenAPIRequestBody, OpenAPIPathItem, OpenAPIParameter, OpenAPIOperation, OpenAPIInfo, OpenAPIGenerator, OpenAPIDocument, OpenAPIConfig, NamingValidator, NamedSchemaRef, ModuleValidator, ModuleRegistration, ModuleIR, ModuleDefContext, ModuleAnalyzerResult, ModuleAnalyzer, MiddlewareRef, MiddlewareIR, MiddlewareAnalyzerResult, MiddlewareAnalyzer, ManifestRoute, ManifestModule, ManifestMiddleware, ManifestGenerator, ManifestDiagnostic, ManifestDependencyEdge, JSONSchemaObject, InlineSchemaRef, InjectRef, IncrementalResult, IncrementalCompiler, ImportRef, HttpMethod, Generator, FileChange, FileCategory, EnvVariableIR, EnvIR, EnvAnalyzerResult, EnvAnalyzer, EntityRelationIR, EntityModelSchemaRefs, EntityModelRef, EntityIR, EntityHooksIR, EntityAnalyzerResult, EntityAnalyzer, EntityActionIR, EntityAccessRuleKind, EntityAccessIR, DiagnosticSeverity, DiagnosticCode, Diagnostic, DependencyNodeKind, DependencyNode, DependencyGraphResult, DependencyGraphInput, DependencyGraphIR, DependencyGraphAnalyzer, DependencyEdgeKind, DependencyEdge, DatabaseIR, DatabaseAnalyzerResult, DatabaseAnalyzer, CreateDiagnosticOptions, CompletenessValidator, CompilerDependencies, CompilerConfig, Compiler, CompileResult, CategorizedChanges, CategorizeOptions, BootModuleEntry, BootMiddlewareEntry, BootManifest, BootGenerator, BaseGenerator, BaseAnalyzer, AppManifest, AppIR, AppDefinition, AppAnalyzerResult, AppAnalyzer, Analyzer, AccessWhereCondition, AccessWhereClauseIR, AccessIR, AccessEntityIR, AccessAnalyzerResult, AccessAnalyzer };
|
package/dist/index.js
CHANGED
|
@@ -1,28 +1,6 @@
|
|
|
1
|
-
// src/analyzers/
|
|
1
|
+
// src/analyzers/access-analyzer.ts
|
|
2
2
|
import { SyntaxKind as SyntaxKind2 } from "ts-morph";
|
|
3
3
|
|
|
4
|
-
// src/errors.ts
|
|
5
|
-
function createDiagnostic(options) {
|
|
6
|
-
return { ...options };
|
|
7
|
-
}
|
|
8
|
-
function createDiagnosticFromLocation(location, options) {
|
|
9
|
-
return {
|
|
10
|
-
...options,
|
|
11
|
-
file: location.sourceFile,
|
|
12
|
-
line: location.sourceLine,
|
|
13
|
-
column: location.sourceColumn
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
function hasErrors(diagnostics) {
|
|
17
|
-
return diagnostics.some((d) => d.severity === "error");
|
|
18
|
-
}
|
|
19
|
-
function filterBySeverity(diagnostics, severity) {
|
|
20
|
-
return diagnostics.filter((d) => d.severity === severity);
|
|
21
|
-
}
|
|
22
|
-
function mergeDiagnostics(a, b) {
|
|
23
|
-
return [...a, ...b];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
4
|
// src/utils/ast-helpers.ts
|
|
27
5
|
import {
|
|
28
6
|
SyntaxKind
|
|
@@ -210,6 +188,254 @@ class BaseAnalyzer {
|
|
|
210
188
|
}
|
|
211
189
|
}
|
|
212
190
|
|
|
191
|
+
// src/analyzers/access-analyzer.ts
|
|
192
|
+
class AccessAnalyzer extends BaseAnalyzer {
|
|
193
|
+
async analyze() {
|
|
194
|
+
const files = this.project.getSourceFiles();
|
|
195
|
+
const calls = [];
|
|
196
|
+
for (const file of files) {
|
|
197
|
+
const found = this.findDefineAccessCalls(file);
|
|
198
|
+
for (const call2 of found) {
|
|
199
|
+
calls.push({ call: call2, file });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (calls.length === 0) {
|
|
203
|
+
return { access: undefined };
|
|
204
|
+
}
|
|
205
|
+
if (calls.length > 1) {
|
|
206
|
+
for (const { call: call2 } of calls.slice(1)) {
|
|
207
|
+
this.addDiagnostic({
|
|
208
|
+
code: "ACCESS_MULTIPLE_DEFINITIONS",
|
|
209
|
+
severity: "error",
|
|
210
|
+
message: "Only one defineAccess() call is allowed per application",
|
|
211
|
+
...getSourceLocation(call2)
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const first = calls[0];
|
|
216
|
+
if (!first)
|
|
217
|
+
return { access: undefined };
|
|
218
|
+
const { call } = first;
|
|
219
|
+
const access = this.extractAccess(call);
|
|
220
|
+
return { access };
|
|
221
|
+
}
|
|
222
|
+
findDefineAccessCalls(file) {
|
|
223
|
+
const validCalls = [];
|
|
224
|
+
for (const call of file.getDescendantsOfKind(SyntaxKind2.CallExpression)) {
|
|
225
|
+
const expr = call.getExpression();
|
|
226
|
+
if (expr.isKind(SyntaxKind2.Identifier)) {
|
|
227
|
+
if (isFromImport(expr, "@vertz/server")) {
|
|
228
|
+
const name = expr.getText();
|
|
229
|
+
const sourceFile = expr.getSourceFile();
|
|
230
|
+
const importDecls = sourceFile.getImportDeclarations();
|
|
231
|
+
for (const decl of importDecls) {
|
|
232
|
+
if (decl.getModuleSpecifierValue() !== "@vertz/server")
|
|
233
|
+
continue;
|
|
234
|
+
for (const specifier of decl.getNamedImports()) {
|
|
235
|
+
const importedName = specifier.getName();
|
|
236
|
+
const alias = specifier.getAliasNode()?.getText();
|
|
237
|
+
if (importedName === "defineAccess" && (alias === name || !alias && importedName === name)) {
|
|
238
|
+
validCalls.push(call);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (expr.isKind(SyntaxKind2.PropertyAccessExpression)) {
|
|
246
|
+
const propName = expr.getName();
|
|
247
|
+
if (propName !== "defineAccess")
|
|
248
|
+
continue;
|
|
249
|
+
const obj = expr.getExpression();
|
|
250
|
+
if (!obj.isKind(SyntaxKind2.Identifier))
|
|
251
|
+
continue;
|
|
252
|
+
const sourceFile = obj.getSourceFile();
|
|
253
|
+
const importDecl = sourceFile.getImportDeclarations().find((d) => d.getModuleSpecifierValue() === "@vertz/server" && d.getNamespaceImport()?.getText() === obj.getText());
|
|
254
|
+
if (importDecl) {
|
|
255
|
+
validCalls.push(call);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return validCalls;
|
|
260
|
+
}
|
|
261
|
+
extractAccess(call) {
|
|
262
|
+
const configObj = extractObjectLiteral(call, 0);
|
|
263
|
+
if (!configObj)
|
|
264
|
+
return;
|
|
265
|
+
const loc = getSourceLocation(call);
|
|
266
|
+
const entities = [];
|
|
267
|
+
const entitiesExpr = getPropertyValue(configObj, "entities");
|
|
268
|
+
if (entitiesExpr?.isKind(SyntaxKind2.ObjectLiteralExpression)) {
|
|
269
|
+
for (const prop of getProperties(entitiesExpr)) {
|
|
270
|
+
const roles = [];
|
|
271
|
+
if (prop.value.isKind(SyntaxKind2.ObjectLiteralExpression)) {
|
|
272
|
+
const rolesExpr = getPropertyValue(prop.value, "roles");
|
|
273
|
+
if (rolesExpr) {
|
|
274
|
+
for (const el of getArrayElements(rolesExpr)) {
|
|
275
|
+
const role = getStringValue(el);
|
|
276
|
+
if (role !== null) {
|
|
277
|
+
roles.push(role);
|
|
278
|
+
} else {
|
|
279
|
+
this.addDiagnostic({
|
|
280
|
+
code: "ACCESS_NON_LITERAL_ROLE",
|
|
281
|
+
severity: "warning",
|
|
282
|
+
message: `Role in entity "${prop.name}" must be a string literal`,
|
|
283
|
+
...getSourceLocation(el)
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
entities.push({ name: prop.name, roles });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const entitlements = [];
|
|
293
|
+
const seenEntitlements = new Set;
|
|
294
|
+
const entitlementsExpr = getPropertyValue(configObj, "entitlements");
|
|
295
|
+
if (entitlementsExpr?.isKind(SyntaxKind2.ObjectLiteralExpression)) {
|
|
296
|
+
for (const prop of entitlementsExpr.getProperties()) {
|
|
297
|
+
if (prop.isKind(SyntaxKind2.PropertyAssignment) || prop.isKind(SyntaxKind2.MethodDeclaration)) {
|
|
298
|
+
const name = prop.getName();
|
|
299
|
+
const cleanName = name.replace(/^['"]|['"]$/g, "");
|
|
300
|
+
if (seenEntitlements.has(cleanName)) {
|
|
301
|
+
this.addDiagnostic({
|
|
302
|
+
code: "ACCESS_DUPLICATE_ENTITLEMENT",
|
|
303
|
+
severity: "warning",
|
|
304
|
+
message: `Duplicate entitlement "${cleanName}" — only the first occurrence is used`,
|
|
305
|
+
...getSourceLocation(prop)
|
|
306
|
+
});
|
|
307
|
+
} else {
|
|
308
|
+
seenEntitlements.add(cleanName);
|
|
309
|
+
entitlements.push(cleanName);
|
|
310
|
+
}
|
|
311
|
+
} else if (prop.isKind(SyntaxKind2.SpreadAssignment)) {
|
|
312
|
+
this.addDiagnostic({
|
|
313
|
+
code: "ACCESS_NON_LITERAL_KEY",
|
|
314
|
+
severity: "warning",
|
|
315
|
+
message: "Spread in entitlements object cannot be statically analyzed — these entitlements will not be type-checked",
|
|
316
|
+
...getSourceLocation(prop)
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const whereClauses = this.extractWhereClauses(configObj);
|
|
322
|
+
return {
|
|
323
|
+
entities,
|
|
324
|
+
entitlements,
|
|
325
|
+
whereClauses,
|
|
326
|
+
sourceFile: loc.sourceFile,
|
|
327
|
+
sourceLine: loc.sourceLine,
|
|
328
|
+
sourceColumn: loc.sourceColumn
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
extractWhereClauses(configObj) {
|
|
332
|
+
const clauses = [];
|
|
333
|
+
const entitlementsExpr = getPropertyValue(configObj, "entitlements");
|
|
334
|
+
if (!entitlementsExpr?.isKind(SyntaxKind2.ObjectLiteralExpression))
|
|
335
|
+
return clauses;
|
|
336
|
+
for (const prop of entitlementsExpr.getProperties()) {
|
|
337
|
+
if (!prop.isKind(SyntaxKind2.PropertyAssignment) && !prop.isKind(SyntaxKind2.MethodDeclaration)) {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const entName = prop.getName().replace(/^['"]|['"]$/g, "");
|
|
341
|
+
const whereCalls = this.findWhereCalls(prop);
|
|
342
|
+
const allConditions = [];
|
|
343
|
+
for (const whereCall of whereCalls) {
|
|
344
|
+
const conditions = this.extractWhereConditions(whereCall);
|
|
345
|
+
allConditions.push(...conditions);
|
|
346
|
+
}
|
|
347
|
+
if (allConditions.length > 0) {
|
|
348
|
+
clauses.push({ entitlement: entName, conditions: allConditions });
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return clauses;
|
|
352
|
+
}
|
|
353
|
+
findWhereCalls(expr) {
|
|
354
|
+
const calls = [];
|
|
355
|
+
for (const call of expr.getDescendantsOfKind(SyntaxKind2.CallExpression)) {
|
|
356
|
+
const callExpr = call.getExpression();
|
|
357
|
+
if (callExpr.isKind(SyntaxKind2.PropertyAccessExpression) && callExpr.getName() === "where") {
|
|
358
|
+
calls.push(call);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return calls;
|
|
362
|
+
}
|
|
363
|
+
extractWhereConditions(call) {
|
|
364
|
+
const conditions = [];
|
|
365
|
+
const arg = call.getArguments()[0];
|
|
366
|
+
if (!arg?.isKind(SyntaxKind2.ObjectLiteralExpression))
|
|
367
|
+
return conditions;
|
|
368
|
+
for (const prop of arg.getProperties()) {
|
|
369
|
+
if (!prop.isKind(SyntaxKind2.PropertyAssignment))
|
|
370
|
+
continue;
|
|
371
|
+
const column = prop.getName().replace(/^['"]|['"]$/g, "");
|
|
372
|
+
const init = prop.getInitializer();
|
|
373
|
+
if (!init)
|
|
374
|
+
continue;
|
|
375
|
+
const condition = this.extractConditionValue(column, init);
|
|
376
|
+
if (condition) {
|
|
377
|
+
conditions.push(condition);
|
|
378
|
+
} else {
|
|
379
|
+
this.addDiagnostic({
|
|
380
|
+
code: "ACCESS_WHERE_NOT_TRANSLATABLE",
|
|
381
|
+
severity: "warning",
|
|
382
|
+
message: `Where condition for column "${column}" cannot be statically analyzed — no RLS policy will be generated`,
|
|
383
|
+
...getSourceLocation(init)
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return conditions;
|
|
388
|
+
}
|
|
389
|
+
extractConditionValue(column, expr) {
|
|
390
|
+
if (expr.isKind(SyntaxKind2.PropertyAccessExpression)) {
|
|
391
|
+
const text = expr.getText();
|
|
392
|
+
const markerMatch = text.match(/\.\buser\.(id|tenantId)$/);
|
|
393
|
+
if (markerMatch) {
|
|
394
|
+
const marker = `user.${markerMatch[1]}`;
|
|
395
|
+
return { kind: "marker", column, marker };
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
const strVal = getStringValue(expr);
|
|
399
|
+
if (strVal !== null) {
|
|
400
|
+
return { kind: "literal", column, value: strVal };
|
|
401
|
+
}
|
|
402
|
+
if (expr.isKind(SyntaxKind2.TrueKeyword)) {
|
|
403
|
+
return { kind: "literal", column, value: true };
|
|
404
|
+
}
|
|
405
|
+
if (expr.isKind(SyntaxKind2.FalseKeyword)) {
|
|
406
|
+
return { kind: "literal", column, value: false };
|
|
407
|
+
}
|
|
408
|
+
if (expr.isKind(SyntaxKind2.NumericLiteral)) {
|
|
409
|
+
return { kind: "literal", column, value: Number(expr.getText()) };
|
|
410
|
+
}
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// src/analyzers/app-analyzer.ts
|
|
415
|
+
import { SyntaxKind as SyntaxKind3 } from "ts-morph";
|
|
416
|
+
|
|
417
|
+
// src/errors.ts
|
|
418
|
+
function createDiagnostic(options) {
|
|
419
|
+
return { ...options };
|
|
420
|
+
}
|
|
421
|
+
function createDiagnosticFromLocation(location, options) {
|
|
422
|
+
return {
|
|
423
|
+
...options,
|
|
424
|
+
file: location.sourceFile,
|
|
425
|
+
line: location.sourceLine,
|
|
426
|
+
column: location.sourceColumn
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
function hasErrors(diagnostics) {
|
|
430
|
+
return diagnostics.some((d) => d.severity === "error");
|
|
431
|
+
}
|
|
432
|
+
function filterBySeverity(diagnostics, severity) {
|
|
433
|
+
return diagnostics.filter((d) => d.severity === severity);
|
|
434
|
+
}
|
|
435
|
+
function mergeDiagnostics(a, b) {
|
|
436
|
+
return [...a, ...b];
|
|
437
|
+
}
|
|
438
|
+
|
|
213
439
|
// src/analyzers/app-analyzer.ts
|
|
214
440
|
class AppAnalyzer extends BaseAnalyzer {
|
|
215
441
|
async analyze() {
|
|
@@ -278,13 +504,13 @@ class AppAnalyzer extends BaseAnalyzer {
|
|
|
278
504
|
collectChainedCalls(appCall) {
|
|
279
505
|
const results = [];
|
|
280
506
|
let current = appCall;
|
|
281
|
-
while (current.getParent()?.isKind(
|
|
507
|
+
while (current.getParent()?.isKind(SyntaxKind3.PropertyAccessExpression)) {
|
|
282
508
|
const propAccess = current.getParentOrThrow();
|
|
283
|
-
if (!propAccess.isKind(
|
|
509
|
+
if (!propAccess.isKind(SyntaxKind3.PropertyAccessExpression))
|
|
284
510
|
break;
|
|
285
511
|
const methodName = propAccess.getName();
|
|
286
512
|
const parentCall = propAccess.getParent();
|
|
287
|
-
if (!parentCall?.isKind(
|
|
513
|
+
if (!parentCall?.isKind(SyntaxKind3.CallExpression))
|
|
288
514
|
break;
|
|
289
515
|
results.push({ methodName, call: parentCall });
|
|
290
516
|
current = parentCall;
|
|
@@ -301,7 +527,7 @@ class AppAnalyzer extends BaseAnalyzer {
|
|
|
301
527
|
continue;
|
|
302
528
|
const elements = getArrayElements(arrArg);
|
|
303
529
|
for (const el of elements) {
|
|
304
|
-
if (el.isKind(
|
|
530
|
+
if (el.isKind(SyntaxKind3.Identifier)) {
|
|
305
531
|
const resolved = resolveIdentifier(el, this.project);
|
|
306
532
|
middleware.push({
|
|
307
533
|
name: el.getText(),
|
|
@@ -321,7 +547,7 @@ class AppAnalyzer extends BaseAnalyzer {
|
|
|
321
547
|
const moduleArg = args.at(0);
|
|
322
548
|
if (!moduleArg)
|
|
323
549
|
continue;
|
|
324
|
-
const moduleName = moduleArg.isKind(
|
|
550
|
+
const moduleName = moduleArg.isKind(SyntaxKind3.Identifier) ? moduleArg.getText() : undefined;
|
|
325
551
|
if (!moduleName) {
|
|
326
552
|
this.addDiagnostic(createDiagnosticFromLocation(getSourceLocation(call), {
|
|
327
553
|
severity: "warning",
|
|
@@ -339,20 +565,20 @@ class AppAnalyzer extends BaseAnalyzer {
|
|
|
339
565
|
extractOptions(obj) {
|
|
340
566
|
const result = {};
|
|
341
567
|
for (const prop of obj.getProperties()) {
|
|
342
|
-
if (!prop.isKind(
|
|
568
|
+
if (!prop.isKind(SyntaxKind3.PropertyAssignment))
|
|
343
569
|
continue;
|
|
344
570
|
const name = prop.getName();
|
|
345
571
|
const init = prop.getInitializerOrThrow();
|
|
346
572
|
const strValue = getStringValue(init);
|
|
347
573
|
if (strValue !== null) {
|
|
348
574
|
result[name] = strValue;
|
|
349
|
-
} else if (init.isKind(
|
|
575
|
+
} else if (init.isKind(SyntaxKind3.TrueKeyword)) {
|
|
350
576
|
result[name] = true;
|
|
351
|
-
} else if (init.isKind(
|
|
577
|
+
} else if (init.isKind(SyntaxKind3.FalseKeyword)) {
|
|
352
578
|
result[name] = false;
|
|
353
|
-
} else if (init.isKind(
|
|
579
|
+
} else if (init.isKind(SyntaxKind3.NumericLiteral)) {
|
|
354
580
|
result[name] = Number(init.getText());
|
|
355
|
-
} else if (init.isKind(
|
|
581
|
+
} else if (init.isKind(SyntaxKind3.ObjectLiteralExpression)) {
|
|
356
582
|
result[name] = this.extractOptions(init);
|
|
357
583
|
}
|
|
358
584
|
}
|
|
@@ -360,7 +586,7 @@ class AppAnalyzer extends BaseAnalyzer {
|
|
|
360
586
|
}
|
|
361
587
|
}
|
|
362
588
|
// src/analyzers/database-analyzer.ts
|
|
363
|
-
import { SyntaxKind as
|
|
589
|
+
import { SyntaxKind as SyntaxKind4 } from "ts-morph";
|
|
364
590
|
class DatabaseAnalyzer extends BaseAnalyzer {
|
|
365
591
|
async analyze() {
|
|
366
592
|
const databases = [];
|
|
@@ -376,20 +602,20 @@ class DatabaseAnalyzer extends BaseAnalyzer {
|
|
|
376
602
|
}
|
|
377
603
|
findCreateDbCalls(file) {
|
|
378
604
|
const validCalls = [];
|
|
379
|
-
for (const call of file.getDescendantsOfKind(
|
|
605
|
+
for (const call of file.getDescendantsOfKind(SyntaxKind4.CallExpression)) {
|
|
380
606
|
const expr = call.getExpression();
|
|
381
|
-
if (expr.isKind(
|
|
607
|
+
if (expr.isKind(SyntaxKind4.Identifier)) {
|
|
382
608
|
if (isFromImport(expr, "@vertz/db")) {
|
|
383
609
|
validCalls.push(call);
|
|
384
610
|
}
|
|
385
611
|
continue;
|
|
386
612
|
}
|
|
387
|
-
if (expr.isKind(
|
|
613
|
+
if (expr.isKind(SyntaxKind4.PropertyAccessExpression)) {
|
|
388
614
|
const propName = expr.getName();
|
|
389
615
|
if (propName !== "createDb")
|
|
390
616
|
continue;
|
|
391
617
|
const obj = expr.getExpression();
|
|
392
|
-
if (!obj.isKind(
|
|
618
|
+
if (!obj.isKind(SyntaxKind4.Identifier))
|
|
393
619
|
continue;
|
|
394
620
|
const sourceFile = obj.getSourceFile();
|
|
395
621
|
const importDecl = sourceFile.getImportDeclarations().find((d) => d.getModuleSpecifierValue() === "@vertz/db" && d.getNamespaceImport()?.getText() === obj.getText());
|
|
@@ -415,14 +641,14 @@ class DatabaseAnalyzer extends BaseAnalyzer {
|
|
|
415
641
|
return { modelKeys, ...loc };
|
|
416
642
|
}
|
|
417
643
|
resolveObjectLiteral(expr) {
|
|
418
|
-
if (expr.isKind(
|
|
644
|
+
if (expr.isKind(SyntaxKind4.ObjectLiteralExpression))
|
|
419
645
|
return expr;
|
|
420
|
-
if (expr.isKind(
|
|
646
|
+
if (expr.isKind(SyntaxKind4.Identifier)) {
|
|
421
647
|
const defs = expr.getDefinitionNodes();
|
|
422
648
|
for (const def of defs) {
|
|
423
|
-
if (def.isKind(
|
|
649
|
+
if (def.isKind(SyntaxKind4.VariableDeclaration)) {
|
|
424
650
|
const init = def.getInitializer();
|
|
425
|
-
if (init?.isKind(
|
|
651
|
+
if (init?.isKind(SyntaxKind4.ObjectLiteralExpression))
|
|
426
652
|
return init;
|
|
427
653
|
}
|
|
428
654
|
}
|
|
@@ -432,8 +658,8 @@ class DatabaseAnalyzer extends BaseAnalyzer {
|
|
|
432
658
|
extractModelKeys(obj, loc) {
|
|
433
659
|
const keys = [];
|
|
434
660
|
for (const prop of obj.getProperties()) {
|
|
435
|
-
if (prop.isKind(
|
|
436
|
-
if (prop.getNameNode().isKind(
|
|
661
|
+
if (prop.isKind(SyntaxKind4.PropertyAssignment)) {
|
|
662
|
+
if (prop.getNameNode().isKind(SyntaxKind4.ComputedPropertyName)) {
|
|
437
663
|
this.addDiagnostic({
|
|
438
664
|
severity: "warning",
|
|
439
665
|
code: "ENTITY_MODEL_NOT_REGISTERED",
|
|
@@ -443,9 +669,9 @@ class DatabaseAnalyzer extends BaseAnalyzer {
|
|
|
443
669
|
} else {
|
|
444
670
|
keys.push(prop.getName());
|
|
445
671
|
}
|
|
446
|
-
} else if (prop.isKind(
|
|
672
|
+
} else if (prop.isKind(SyntaxKind4.ShorthandPropertyAssignment)) {
|
|
447
673
|
keys.push(prop.getName());
|
|
448
|
-
} else if (prop.isKind(
|
|
674
|
+
} else if (prop.isKind(SyntaxKind4.SpreadAssignment)) {
|
|
449
675
|
this.addDiagnostic({
|
|
450
676
|
severity: "warning",
|
|
451
677
|
code: "ENTITY_MODEL_NOT_REGISTERED",
|
|
@@ -706,7 +932,7 @@ class DependencyGraphAnalyzer extends BaseAnalyzer {
|
|
|
706
932
|
}
|
|
707
933
|
}
|
|
708
934
|
// src/analyzers/entity-analyzer.ts
|
|
709
|
-
import { SyntaxKind as
|
|
935
|
+
import { SyntaxKind as SyntaxKind5 } from "ts-morph";
|
|
710
936
|
var ENTITY_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
711
937
|
var CRUD_OPS = ["list", "get", "create", "update", "delete"];
|
|
712
938
|
|
|
@@ -763,9 +989,9 @@ class EntityAnalyzer extends BaseAnalyzer {
|
|
|
763
989
|
}
|
|
764
990
|
findEntityCalls(file) {
|
|
765
991
|
const validCalls = [];
|
|
766
|
-
for (const call of file.getDescendantsOfKind(
|
|
992
|
+
for (const call of file.getDescendantsOfKind(SyntaxKind5.CallExpression)) {
|
|
767
993
|
const expr = call.getExpression();
|
|
768
|
-
if (expr.isKind(
|
|
994
|
+
if (expr.isKind(SyntaxKind5.Identifier)) {
|
|
769
995
|
const isValid = isFromImport(expr, "@vertz/server");
|
|
770
996
|
if (!isValid && expr.getText() === "entity") {
|
|
771
997
|
this.addDiagnostic({
|
|
@@ -781,12 +1007,12 @@ class EntityAnalyzer extends BaseAnalyzer {
|
|
|
781
1007
|
}
|
|
782
1008
|
continue;
|
|
783
1009
|
}
|
|
784
|
-
if (expr.isKind(
|
|
1010
|
+
if (expr.isKind(SyntaxKind5.PropertyAccessExpression)) {
|
|
785
1011
|
const propName = expr.getName();
|
|
786
1012
|
if (propName !== "entity")
|
|
787
1013
|
continue;
|
|
788
1014
|
const obj = expr.getExpression();
|
|
789
|
-
if (!obj.isKind(
|
|
1015
|
+
if (!obj.isKind(SyntaxKind5.Identifier))
|
|
790
1016
|
continue;
|
|
791
1017
|
const sourceFile = obj.getSourceFile();
|
|
792
1018
|
const importDecl = sourceFile.getImportDeclarations().find((d) => d.getModuleSpecifierValue() === "@vertz/server" && d.getNamespaceImport()?.getText() === obj.getText());
|
|
@@ -846,6 +1072,13 @@ class EntityAnalyzer extends BaseAnalyzer {
|
|
|
846
1072
|
const actions = this.extractActions(configObj);
|
|
847
1073
|
const modelExpr = getPropertyValue(configObj, "model");
|
|
848
1074
|
const relations = this.extractRelations(configObj, modelExpr ?? undefined);
|
|
1075
|
+
const tenantScopedExpr = getPropertyValue(configObj, "tenantScoped");
|
|
1076
|
+
const tenantScoped = tenantScopedExpr ? getBooleanValue(tenantScopedExpr) : undefined;
|
|
1077
|
+
const tableExpr = getPropertyValue(configObj, "table");
|
|
1078
|
+
const table = tableExpr ? getStringValue(tableExpr) : undefined;
|
|
1079
|
+
if (modelExpr) {
|
|
1080
|
+
this.extractModelTableMetadata(modelExpr, modelRef);
|
|
1081
|
+
}
|
|
849
1082
|
for (const action of actions) {
|
|
850
1083
|
if (CRUD_OPS.includes(action.name)) {
|
|
851
1084
|
this.addDiagnostic({
|
|
@@ -867,7 +1100,17 @@ class EntityAnalyzer extends BaseAnalyzer {
|
|
|
867
1100
|
}
|
|
868
1101
|
}
|
|
869
1102
|
return {
|
|
870
|
-
entity: {
|
|
1103
|
+
entity: {
|
|
1104
|
+
name,
|
|
1105
|
+
modelRef,
|
|
1106
|
+
access,
|
|
1107
|
+
hooks,
|
|
1108
|
+
actions,
|
|
1109
|
+
relations,
|
|
1110
|
+
...tenantScoped !== undefined && tenantScoped !== null ? { tenantScoped } : {},
|
|
1111
|
+
...table !== undefined && table !== null ? { table } : {},
|
|
1112
|
+
...loc
|
|
1113
|
+
},
|
|
871
1114
|
modelExpr: modelExpr ?? undefined
|
|
872
1115
|
};
|
|
873
1116
|
}
|
|
@@ -882,9 +1125,9 @@ class EntityAnalyzer extends BaseAnalyzer {
|
|
|
882
1125
|
});
|
|
883
1126
|
return null;
|
|
884
1127
|
}
|
|
885
|
-
const variableName = modelExpr.isKind(
|
|
1128
|
+
const variableName = modelExpr.isKind(SyntaxKind5.Identifier) ? modelExpr.getText() : modelExpr.getText();
|
|
886
1129
|
let importSource;
|
|
887
|
-
if (modelExpr.isKind(
|
|
1130
|
+
if (modelExpr.isKind(SyntaxKind5.Identifier)) {
|
|
888
1131
|
const importInfo = this.findImportForIdentifier(modelExpr);
|
|
889
1132
|
if (importInfo) {
|
|
890
1133
|
importSource = importInfo.importDecl.getModuleSpecifierValue();
|
|
@@ -1053,7 +1296,7 @@ class EntityAnalyzer extends BaseAnalyzer {
|
|
|
1053
1296
|
custom: {}
|
|
1054
1297
|
};
|
|
1055
1298
|
const accessExpr = getPropertyValue(configObj, "access");
|
|
1056
|
-
if (!accessExpr || !accessExpr.isKind(
|
|
1299
|
+
if (!accessExpr || !accessExpr.isKind(SyntaxKind5.ObjectLiteralExpression))
|
|
1057
1300
|
return defaults;
|
|
1058
1301
|
const result = { ...defaults };
|
|
1059
1302
|
const knownOps = new Set([...CRUD_OPS]);
|
|
@@ -1078,14 +1321,14 @@ class EntityAnalyzer extends BaseAnalyzer {
|
|
|
1078
1321
|
extractHooks(configObj) {
|
|
1079
1322
|
const hooks = { before: [], after: [] };
|
|
1080
1323
|
const beforeExpr = getPropertyValue(configObj, "before");
|
|
1081
|
-
if (beforeExpr?.isKind(
|
|
1324
|
+
if (beforeExpr?.isKind(SyntaxKind5.ObjectLiteralExpression)) {
|
|
1082
1325
|
for (const { name } of getProperties(beforeExpr)) {
|
|
1083
1326
|
if (name === "create" || name === "update")
|
|
1084
1327
|
hooks.before.push(name);
|
|
1085
1328
|
}
|
|
1086
1329
|
}
|
|
1087
1330
|
const afterExpr = getPropertyValue(configObj, "after");
|
|
1088
|
-
if (afterExpr?.isKind(
|
|
1331
|
+
if (afterExpr?.isKind(SyntaxKind5.ObjectLiteralExpression)) {
|
|
1089
1332
|
for (const { name } of getProperties(afterExpr)) {
|
|
1090
1333
|
if (name === "create" || name === "update" || name === "delete")
|
|
1091
1334
|
hooks.after.push(name);
|
|
@@ -1095,10 +1338,10 @@ class EntityAnalyzer extends BaseAnalyzer {
|
|
|
1095
1338
|
}
|
|
1096
1339
|
extractActions(configObj) {
|
|
1097
1340
|
const actionsExpr = getPropertyValue(configObj, "actions");
|
|
1098
|
-
if (!actionsExpr?.isKind(
|
|
1341
|
+
if (!actionsExpr?.isKind(SyntaxKind5.ObjectLiteralExpression))
|
|
1099
1342
|
return [];
|
|
1100
1343
|
return getProperties(actionsExpr).map(({ name, value }) => {
|
|
1101
|
-
const actionObj = value.isKind(
|
|
1344
|
+
const actionObj = value.isKind(SyntaxKind5.ObjectLiteralExpression) ? value : null;
|
|
1102
1345
|
const loc = getSourceLocation(value);
|
|
1103
1346
|
const bodyExpr = actionObj ? getPropertyValue(actionObj, "body") : null;
|
|
1104
1347
|
const responseExpr = actionObj ? getPropertyValue(actionObj, "response") : null;
|
|
@@ -1158,7 +1401,7 @@ class EntityAnalyzer extends BaseAnalyzer {
|
|
|
1158
1401
|
});
|
|
1159
1402
|
}
|
|
1160
1403
|
resolveSchemaFromExpression(expr, loc) {
|
|
1161
|
-
if (expr.isKind(
|
|
1404
|
+
if (expr.isKind(SyntaxKind5.Identifier)) {
|
|
1162
1405
|
const varName = expr.getText();
|
|
1163
1406
|
return { kind: "named", schemaName: varName, sourceFile: loc.sourceFile };
|
|
1164
1407
|
}
|
|
@@ -1171,7 +1414,7 @@ class EntityAnalyzer extends BaseAnalyzer {
|
|
|
1171
1414
|
}
|
|
1172
1415
|
extractRelations(configObj, modelExpr) {
|
|
1173
1416
|
const relExpr = getPropertyValue(configObj, "relations");
|
|
1174
|
-
if (!relExpr?.isKind(
|
|
1417
|
+
if (!relExpr?.isKind(SyntaxKind5.ObjectLiteralExpression))
|
|
1175
1418
|
return [];
|
|
1176
1419
|
const modelRelTypes = modelExpr ? this.resolveModelRelationTypes(modelExpr) : new Map;
|
|
1177
1420
|
return getProperties(relExpr).filter(({ value }) => {
|
|
@@ -1179,16 +1422,74 @@ class EntityAnalyzer extends BaseAnalyzer {
|
|
|
1179
1422
|
return boolVal !== false;
|
|
1180
1423
|
}).map(({ name, value }) => {
|
|
1181
1424
|
const boolVal = getBooleanValue(value);
|
|
1182
|
-
const selection = boolVal === true ? "all" : value.isKind(SyntaxKind4.ObjectLiteralExpression) ? getProperties(value).map((p) => p.name) : "all";
|
|
1183
1425
|
const relType = modelRelTypes.get(name);
|
|
1426
|
+
if (boolVal === true || !value.isKind(SyntaxKind5.ObjectLiteralExpression)) {
|
|
1427
|
+
return { name, type: relType?.type, entity: relType?.entity, selection: "all" };
|
|
1428
|
+
}
|
|
1429
|
+
const selectExpr = getPropertyValue(value, "select");
|
|
1430
|
+
const selection = selectExpr?.isKind(SyntaxKind5.ObjectLiteralExpression) ? getProperties(selectExpr).map((p) => p.name) : "all";
|
|
1431
|
+
const allowWhereExpr = getPropertyValue(value, "allowWhere");
|
|
1432
|
+
const allowWhere = allowWhereExpr ? getArrayElements(allowWhereExpr).map((el) => getStringValue(el)).filter((s) => s !== null) : undefined;
|
|
1433
|
+
const allowOrderByExpr = getPropertyValue(value, "allowOrderBy");
|
|
1434
|
+
const allowOrderBy = allowOrderByExpr ? getArrayElements(allowOrderByExpr).map((el) => getStringValue(el)).filter((s) => s !== null) : undefined;
|
|
1435
|
+
const maxLimitExpr = getPropertyValue(value, "maxLimit");
|
|
1436
|
+
const maxLimit = maxLimitExpr ? getNumberValue(maxLimitExpr) ?? undefined : undefined;
|
|
1184
1437
|
return {
|
|
1185
1438
|
name,
|
|
1186
1439
|
type: relType?.type,
|
|
1187
1440
|
entity: relType?.entity,
|
|
1188
|
-
selection
|
|
1441
|
+
selection,
|
|
1442
|
+
...allowWhere ? { allowWhere } : {},
|
|
1443
|
+
...allowOrderBy ? { allowOrderBy } : {},
|
|
1444
|
+
...maxLimit !== undefined ? { maxLimit } : {}
|
|
1189
1445
|
};
|
|
1190
1446
|
});
|
|
1191
1447
|
}
|
|
1448
|
+
extractModelTableMetadata(modelExpr, modelRef) {
|
|
1449
|
+
try {
|
|
1450
|
+
const modelType = modelExpr.getType();
|
|
1451
|
+
const tableProp = modelType.getProperty("table");
|
|
1452
|
+
if (!tableProp)
|
|
1453
|
+
return;
|
|
1454
|
+
const tableType = tableProp.getTypeAtLocation(modelExpr);
|
|
1455
|
+
const columnsProp = tableType.getProperty("_columns");
|
|
1456
|
+
if (!columnsProp)
|
|
1457
|
+
return;
|
|
1458
|
+
const columnsType = columnsProp.getTypeAtLocation(modelExpr);
|
|
1459
|
+
const hiddenFields = [];
|
|
1460
|
+
for (const colProp of columnsType.getProperties()) {
|
|
1461
|
+
const colName = colProp.getName();
|
|
1462
|
+
const colType = colProp.getTypeAtLocation(modelExpr);
|
|
1463
|
+
const metaProp = colType.getProperty("_meta");
|
|
1464
|
+
if (!metaProp)
|
|
1465
|
+
continue;
|
|
1466
|
+
const metaType = metaProp.getTypeAtLocation(modelExpr);
|
|
1467
|
+
const primaryProp = metaType.getProperty("primary");
|
|
1468
|
+
if (primaryProp) {
|
|
1469
|
+
const primaryType = primaryProp.getTypeAtLocation(modelExpr);
|
|
1470
|
+
if (primaryType.getText() === "true") {
|
|
1471
|
+
modelRef.primaryKey = colName;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
const annotationsProp = metaType.getProperty("_annotations");
|
|
1475
|
+
if (annotationsProp) {
|
|
1476
|
+
const annotationsType = annotationsProp.getTypeAtLocation(modelExpr);
|
|
1477
|
+
const hiddenProp = annotationsType.getProperty("hidden");
|
|
1478
|
+
if (hiddenProp) {
|
|
1479
|
+
const hiddenType = hiddenProp.getTypeAtLocation(modelExpr);
|
|
1480
|
+
if (hiddenType.getText() === "true") {
|
|
1481
|
+
hiddenFields.push(colName);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
if (hiddenFields.length > 0 || columnsType.getProperties().length > 0) {
|
|
1487
|
+
modelRef.hiddenFields = hiddenFields;
|
|
1488
|
+
}
|
|
1489
|
+
} catch (e) {
|
|
1490
|
+
this.debug(`extractModelTableMetadata failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1192
1493
|
resolveModelRelationTypes(modelExpr) {
|
|
1193
1494
|
const result = new Map;
|
|
1194
1495
|
try {
|
|
@@ -1266,10 +1567,10 @@ class EntityAnalyzer extends BaseAnalyzer {
|
|
|
1266
1567
|
}
|
|
1267
1568
|
}
|
|
1268
1569
|
// src/analyzers/env-analyzer.ts
|
|
1269
|
-
import { SyntaxKind as
|
|
1570
|
+
import { SyntaxKind as SyntaxKind7 } from "ts-morph";
|
|
1270
1571
|
|
|
1271
1572
|
// src/analyzers/schema-analyzer.ts
|
|
1272
|
-
import { SyntaxKind as
|
|
1573
|
+
import { SyntaxKind as SyntaxKind6 } from "ts-morph";
|
|
1273
1574
|
class SchemaAnalyzer extends BaseAnalyzer {
|
|
1274
1575
|
async analyze() {
|
|
1275
1576
|
const schemas = [];
|
|
@@ -1279,7 +1580,7 @@ class SchemaAnalyzer extends BaseAnalyzer {
|
|
|
1279
1580
|
for (const exportSymbol of file.getExportSymbols()) {
|
|
1280
1581
|
const declarations = exportSymbol.getDeclarations();
|
|
1281
1582
|
for (const decl of declarations) {
|
|
1282
|
-
if (!decl.isKind(
|
|
1583
|
+
if (!decl.isKind(SyntaxKind6.VariableDeclaration))
|
|
1283
1584
|
continue;
|
|
1284
1585
|
const initializer = decl.getInitializer();
|
|
1285
1586
|
if (!initializer)
|
|
@@ -1334,9 +1635,9 @@ function isSchemaExpression(_file, expr) {
|
|
|
1334
1635
|
}
|
|
1335
1636
|
function extractSchemaId(expr) {
|
|
1336
1637
|
let current = expr;
|
|
1337
|
-
while (current.isKind(
|
|
1638
|
+
while (current.isKind(SyntaxKind6.CallExpression)) {
|
|
1338
1639
|
const access = current.getExpression();
|
|
1339
|
-
if (access.isKind(
|
|
1640
|
+
if (access.isKind(SyntaxKind6.PropertyAccessExpression) && access.getName() === "id") {
|
|
1340
1641
|
const args = current.getArguments();
|
|
1341
1642
|
if (args.length === 1) {
|
|
1342
1643
|
const firstArg = args.at(0);
|
|
@@ -1345,7 +1646,7 @@ function extractSchemaId(expr) {
|
|
|
1345
1646
|
return value;
|
|
1346
1647
|
}
|
|
1347
1648
|
}
|
|
1348
|
-
if (access.isKind(
|
|
1649
|
+
if (access.isKind(SyntaxKind6.PropertyAccessExpression)) {
|
|
1349
1650
|
current = access.getExpression();
|
|
1350
1651
|
} else {
|
|
1351
1652
|
break;
|
|
@@ -1363,13 +1664,13 @@ function createInlineSchemaRef(sourceFile) {
|
|
|
1363
1664
|
return { kind: "inline", sourceFile };
|
|
1364
1665
|
}
|
|
1365
1666
|
function findRootIdentifier(expr) {
|
|
1366
|
-
if (expr.isKind(
|
|
1667
|
+
if (expr.isKind(SyntaxKind6.CallExpression)) {
|
|
1367
1668
|
return findRootIdentifier(expr.getExpression());
|
|
1368
1669
|
}
|
|
1369
|
-
if (expr.isKind(
|
|
1670
|
+
if (expr.isKind(SyntaxKind6.PropertyAccessExpression)) {
|
|
1370
1671
|
return findRootIdentifier(expr.getExpression());
|
|
1371
1672
|
}
|
|
1372
|
-
if (expr.isKind(
|
|
1673
|
+
if (expr.isKind(SyntaxKind6.Identifier)) {
|
|
1373
1674
|
return expr;
|
|
1374
1675
|
}
|
|
1375
1676
|
return null;
|
|
@@ -1398,7 +1699,7 @@ class EnvAnalyzer extends BaseAnalyzer {
|
|
|
1398
1699
|
const loadFiles = loadExpr ? getArrayElements(loadExpr).map((e) => getStringValue(e)).filter((v) => v !== null) : [];
|
|
1399
1700
|
const schemaExpr = getPropertyValue(obj, "schema");
|
|
1400
1701
|
let schema;
|
|
1401
|
-
if (schemaExpr?.isKind(
|
|
1702
|
+
if (schemaExpr?.isKind(SyntaxKind7.Identifier)) {
|
|
1402
1703
|
schema = createNamedSchemaRef(schemaExpr.getText(), file.getFilePath());
|
|
1403
1704
|
}
|
|
1404
1705
|
env = {
|
|
@@ -1413,10 +1714,10 @@ class EnvAnalyzer extends BaseAnalyzer {
|
|
|
1413
1714
|
}
|
|
1414
1715
|
}
|
|
1415
1716
|
// src/analyzers/middleware-analyzer.ts
|
|
1416
|
-
import { SyntaxKind as
|
|
1717
|
+
import { SyntaxKind as SyntaxKind9 } from "ts-morph";
|
|
1417
1718
|
|
|
1418
1719
|
// src/analyzers/service-analyzer.ts
|
|
1419
|
-
import { SyntaxKind as
|
|
1720
|
+
import { SyntaxKind as SyntaxKind8 } from "ts-morph";
|
|
1420
1721
|
class ServiceAnalyzer extends BaseAnalyzer {
|
|
1421
1722
|
async analyze() {
|
|
1422
1723
|
return { services: [] };
|
|
@@ -1447,7 +1748,7 @@ class ServiceAnalyzer extends BaseAnalyzer {
|
|
|
1447
1748
|
}
|
|
1448
1749
|
function parseInjectFromObj(obj) {
|
|
1449
1750
|
const injectExpr = getPropertyValue(obj, "inject");
|
|
1450
|
-
if (!injectExpr?.isKind(
|
|
1751
|
+
if (!injectExpr?.isKind(SyntaxKind8.ObjectLiteralExpression))
|
|
1451
1752
|
return [];
|
|
1452
1753
|
return parseInjectRefs(injectExpr);
|
|
1453
1754
|
}
|
|
@@ -1459,27 +1760,27 @@ function parseMethodsFromObj(obj) {
|
|
|
1459
1760
|
}
|
|
1460
1761
|
function parseInjectRefs(obj) {
|
|
1461
1762
|
return getProperties(obj).map(({ name, value }) => {
|
|
1462
|
-
const resolvedToken = value.isKind(
|
|
1763
|
+
const resolvedToken = value.isKind(SyntaxKind8.Identifier) ? value.getText() : name;
|
|
1463
1764
|
return { localName: name, resolvedToken };
|
|
1464
1765
|
});
|
|
1465
1766
|
}
|
|
1466
1767
|
function extractMethodSignatures(expr) {
|
|
1467
|
-
if (!expr.isKind(
|
|
1768
|
+
if (!expr.isKind(SyntaxKind8.ArrowFunction) && !expr.isKind(SyntaxKind8.FunctionExpression)) {
|
|
1468
1769
|
return [];
|
|
1469
1770
|
}
|
|
1470
1771
|
const body = expr.getBody();
|
|
1471
1772
|
let returnObj = null;
|
|
1472
|
-
if (body.isKind(
|
|
1773
|
+
if (body.isKind(SyntaxKind8.ObjectLiteralExpression)) {
|
|
1473
1774
|
returnObj = body;
|
|
1474
|
-
} else if (body.isKind(
|
|
1775
|
+
} else if (body.isKind(SyntaxKind8.ParenthesizedExpression)) {
|
|
1475
1776
|
const inner = body.getExpression();
|
|
1476
|
-
if (inner.isKind(
|
|
1777
|
+
if (inner.isKind(SyntaxKind8.ObjectLiteralExpression)) {
|
|
1477
1778
|
returnObj = inner;
|
|
1478
1779
|
}
|
|
1479
|
-
} else if (body.isKind(
|
|
1480
|
-
const returnStmt = body.getStatements().find((s) => s.isKind(
|
|
1481
|
-
const retExpr = returnStmt?.asKind(
|
|
1482
|
-
if (retExpr?.isKind(
|
|
1780
|
+
} else if (body.isKind(SyntaxKind8.Block)) {
|
|
1781
|
+
const returnStmt = body.getStatements().find((s) => s.isKind(SyntaxKind8.ReturnStatement));
|
|
1782
|
+
const retExpr = returnStmt?.asKind(SyntaxKind8.ReturnStatement)?.getExpression();
|
|
1783
|
+
if (retExpr?.isKind(SyntaxKind8.ObjectLiteralExpression)) {
|
|
1483
1784
|
returnObj = retExpr;
|
|
1484
1785
|
}
|
|
1485
1786
|
}
|
|
@@ -1487,7 +1788,7 @@ function extractMethodSignatures(expr) {
|
|
|
1487
1788
|
return [];
|
|
1488
1789
|
const methods = [];
|
|
1489
1790
|
for (const prop of returnObj.getProperties()) {
|
|
1490
|
-
if (!prop.isKind(
|
|
1791
|
+
if (!prop.isKind(SyntaxKind8.PropertyAssignment))
|
|
1491
1792
|
continue;
|
|
1492
1793
|
const methodName = prop.getName();
|
|
1493
1794
|
const init = prop.getInitializer();
|
|
@@ -1504,7 +1805,7 @@ function extractMethodSignatures(expr) {
|
|
|
1504
1805
|
return methods;
|
|
1505
1806
|
}
|
|
1506
1807
|
function extractFunctionParams(expr) {
|
|
1507
|
-
if (!expr.isKind(
|
|
1808
|
+
if (!expr.isKind(SyntaxKind8.ArrowFunction) && !expr.isKind(SyntaxKind8.FunctionExpression)) {
|
|
1508
1809
|
return [];
|
|
1509
1810
|
}
|
|
1510
1811
|
return expr.getParameters().map((p) => ({
|
|
@@ -1513,7 +1814,7 @@ function extractFunctionParams(expr) {
|
|
|
1513
1814
|
}));
|
|
1514
1815
|
}
|
|
1515
1816
|
function inferReturnType(expr) {
|
|
1516
|
-
if (expr.isKind(
|
|
1817
|
+
if (expr.isKind(SyntaxKind8.ArrowFunction) || expr.isKind(SyntaxKind8.FunctionExpression)) {
|
|
1517
1818
|
const retType = expr.getReturnType();
|
|
1518
1819
|
return retType.getText(expr);
|
|
1519
1820
|
}
|
|
@@ -1570,7 +1871,7 @@ class MiddlewareAnalyzer extends BaseAnalyzer {
|
|
|
1570
1871
|
continue;
|
|
1571
1872
|
}
|
|
1572
1873
|
const injectExpr = getPropertyValue(obj, "inject");
|
|
1573
|
-
const inject = injectExpr?.isKind(
|
|
1874
|
+
const inject = injectExpr?.isKind(SyntaxKind9.ObjectLiteralExpression) ? parseInjectRefs(injectExpr) : [];
|
|
1574
1875
|
const filePath = file.getFilePath();
|
|
1575
1876
|
const headers = this.resolveSchemaRef(obj, "headers", filePath);
|
|
1576
1877
|
const params = this.resolveSchemaRef(obj, "params", filePath);
|
|
@@ -1597,7 +1898,7 @@ class MiddlewareAnalyzer extends BaseAnalyzer {
|
|
|
1597
1898
|
const expr = getPropertyValue(obj, prop);
|
|
1598
1899
|
if (!expr)
|
|
1599
1900
|
return;
|
|
1600
|
-
if (expr.isKind(
|
|
1901
|
+
if (expr.isKind(SyntaxKind9.Identifier)) {
|
|
1601
1902
|
const resolved = resolveIdentifier(expr, this.project);
|
|
1602
1903
|
const resolvedPath = resolved ? resolved.sourceFile.getFilePath() : filePath;
|
|
1603
1904
|
return createNamedSchemaRef(expr.getText(), resolvedPath);
|
|
@@ -1609,7 +1910,7 @@ class MiddlewareAnalyzer extends BaseAnalyzer {
|
|
|
1609
1910
|
}
|
|
1610
1911
|
}
|
|
1611
1912
|
// src/analyzers/module-analyzer.ts
|
|
1612
|
-
import { SyntaxKind as
|
|
1913
|
+
import { SyntaxKind as SyntaxKind10 } from "ts-morph";
|
|
1613
1914
|
class ModuleAnalyzer extends BaseAnalyzer {
|
|
1614
1915
|
async analyze() {
|
|
1615
1916
|
const modules = [];
|
|
@@ -1632,10 +1933,10 @@ class ModuleAnalyzer extends BaseAnalyzer {
|
|
|
1632
1933
|
}
|
|
1633
1934
|
const varName = getVariableNameForCall(call);
|
|
1634
1935
|
const importsExpr = getPropertyValue(obj, "imports");
|
|
1635
|
-
const imports = importsExpr?.isKind(
|
|
1936
|
+
const imports = importsExpr?.isKind(SyntaxKind10.ObjectLiteralExpression) ? parseImports(importsExpr) : [];
|
|
1636
1937
|
const optionsExpr = getPropertyValue(obj, "options");
|
|
1637
1938
|
let options;
|
|
1638
|
-
if (optionsExpr?.isKind(
|
|
1939
|
+
if (optionsExpr?.isKind(SyntaxKind10.Identifier)) {
|
|
1639
1940
|
options = createNamedSchemaRef(optionsExpr.getText(), file.getFilePath());
|
|
1640
1941
|
}
|
|
1641
1942
|
const loc = getSourceLocation(call);
|
|
@@ -1661,7 +1962,7 @@ class ModuleAnalyzer extends BaseAnalyzer {
|
|
|
1661
1962
|
if (args.length < 2)
|
|
1662
1963
|
continue;
|
|
1663
1964
|
const defArg = args.at(0);
|
|
1664
|
-
if (!defArg?.isKind(
|
|
1965
|
+
if (!defArg?.isKind(SyntaxKind10.Identifier))
|
|
1665
1966
|
continue;
|
|
1666
1967
|
const defVarName = defArg.getText();
|
|
1667
1968
|
const idx = defVarToIndex.get(defVarName);
|
|
@@ -1689,12 +1990,12 @@ function parseImports(obj) {
|
|
|
1689
1990
|
}));
|
|
1690
1991
|
}
|
|
1691
1992
|
function extractIdentifierNames(expr) {
|
|
1692
|
-
if (!expr.isKind(
|
|
1993
|
+
if (!expr.isKind(SyntaxKind10.ArrayLiteralExpression))
|
|
1693
1994
|
return [];
|
|
1694
|
-
return expr.getElements().filter((e) => e.isKind(
|
|
1995
|
+
return expr.getElements().filter((e) => e.isKind(SyntaxKind10.Identifier)).map((e) => e.getText());
|
|
1695
1996
|
}
|
|
1696
1997
|
// src/analyzers/route-analyzer.ts
|
|
1697
|
-
import { SyntaxKind as
|
|
1998
|
+
import { SyntaxKind as SyntaxKind11 } from "ts-morph";
|
|
1698
1999
|
var HTTP_METHODS = {
|
|
1699
2000
|
get: "GET",
|
|
1700
2001
|
post: "POST",
|
|
@@ -1731,7 +2032,7 @@ class RouteAnalyzer extends BaseAnalyzer {
|
|
|
1731
2032
|
}
|
|
1732
2033
|
const loc = getSourceLocation(call);
|
|
1733
2034
|
const injectExpr = obj ? getPropertyValue(obj, "inject") : null;
|
|
1734
|
-
const inject = injectExpr?.isKind(
|
|
2035
|
+
const inject = injectExpr?.isKind(SyntaxKind11.ObjectLiteralExpression) ? parseInjectRefs(injectExpr) : [];
|
|
1735
2036
|
const routes = this.extractRoutes(file, varName, prefix, moduleName);
|
|
1736
2037
|
routers.push({
|
|
1737
2038
|
name: varName,
|
|
@@ -1748,15 +2049,15 @@ class RouteAnalyzer extends BaseAnalyzer {
|
|
|
1748
2049
|
return { routers };
|
|
1749
2050
|
}
|
|
1750
2051
|
detectUnknownRouterCalls(file, knownModuleDefVars) {
|
|
1751
|
-
const allCalls = file.getDescendantsOfKind(
|
|
2052
|
+
const allCalls = file.getDescendantsOfKind(SyntaxKind11.CallExpression);
|
|
1752
2053
|
for (const call of allCalls) {
|
|
1753
2054
|
const expr = call.getExpression();
|
|
1754
|
-
if (!expr.isKind(
|
|
2055
|
+
if (!expr.isKind(SyntaxKind11.PropertyAccessExpression))
|
|
1755
2056
|
continue;
|
|
1756
2057
|
if (expr.getName() !== "router")
|
|
1757
2058
|
continue;
|
|
1758
2059
|
const obj = expr.getExpression();
|
|
1759
|
-
if (!obj.isKind(
|
|
2060
|
+
if (!obj.isKind(SyntaxKind11.Identifier))
|
|
1760
2061
|
continue;
|
|
1761
2062
|
if (knownModuleDefVars.has(obj.getText()))
|
|
1762
2063
|
continue;
|
|
@@ -1790,25 +2091,25 @@ class RouteAnalyzer extends BaseAnalyzer {
|
|
|
1790
2091
|
return routes;
|
|
1791
2092
|
}
|
|
1792
2093
|
findChainedHttpCalls(file, routerVarName, methodName) {
|
|
1793
|
-
return file.getDescendantsOfKind(
|
|
2094
|
+
return file.getDescendantsOfKind(SyntaxKind11.CallExpression).filter((call) => {
|
|
1794
2095
|
const expr = call.getExpression();
|
|
1795
|
-
if (!expr.isKind(
|
|
2096
|
+
if (!expr.isKind(SyntaxKind11.PropertyAccessExpression))
|
|
1796
2097
|
return false;
|
|
1797
2098
|
if (expr.getName() !== methodName)
|
|
1798
2099
|
return false;
|
|
1799
2100
|
const obj = expr.getExpression();
|
|
1800
|
-
if (!obj.isKind(
|
|
2101
|
+
if (!obj.isKind(SyntaxKind11.CallExpression))
|
|
1801
2102
|
return false;
|
|
1802
2103
|
return this.chainResolvesToVariable(obj, routerVarName);
|
|
1803
2104
|
});
|
|
1804
2105
|
}
|
|
1805
2106
|
chainResolvesToVariable(expr, varName) {
|
|
1806
|
-
if (expr.isKind(
|
|
2107
|
+
if (expr.isKind(SyntaxKind11.Identifier)) {
|
|
1807
2108
|
return expr.getText() === varName;
|
|
1808
2109
|
}
|
|
1809
|
-
if (expr.isKind(
|
|
2110
|
+
if (expr.isKind(SyntaxKind11.CallExpression)) {
|
|
1810
2111
|
const inner = expr.getExpression();
|
|
1811
|
-
if (inner.isKind(
|
|
2112
|
+
if (inner.isKind(SyntaxKind11.PropertyAccessExpression)) {
|
|
1812
2113
|
return this.chainResolvesToVariable(inner.getExpression(), varName);
|
|
1813
2114
|
}
|
|
1814
2115
|
}
|
|
@@ -1882,7 +2183,7 @@ class RouteAnalyzer extends BaseAnalyzer {
|
|
|
1882
2183
|
const expr = getPropertyValue(obj, prop);
|
|
1883
2184
|
if (!expr)
|
|
1884
2185
|
return;
|
|
1885
|
-
if (expr.isKind(
|
|
2186
|
+
if (expr.isKind(SyntaxKind11.Identifier)) {
|
|
1886
2187
|
const resolved = resolveIdentifier(expr, this.project);
|
|
1887
2188
|
const resolvedPath = resolved ? resolved.sourceFile.getFilePath() : filePath;
|
|
1888
2189
|
return createNamedSchemaRef(expr.getText(), resolvedPath);
|
|
@@ -1897,7 +2198,7 @@ class RouteAnalyzer extends BaseAnalyzer {
|
|
|
1897
2198
|
if (!expr)
|
|
1898
2199
|
return [];
|
|
1899
2200
|
const elements = getArrayElements(expr);
|
|
1900
|
-
return elements.filter((el) => el.isKind(
|
|
2201
|
+
return elements.filter((el) => el.isKind(SyntaxKind11.Identifier)).map((el) => {
|
|
1901
2202
|
const resolved = resolveIdentifier(el, this.project);
|
|
1902
2203
|
return {
|
|
1903
2204
|
name: el.getText(),
|
|
@@ -1907,9 +2208,9 @@ class RouteAnalyzer extends BaseAnalyzer {
|
|
|
1907
2208
|
}
|
|
1908
2209
|
generateOperationId(moduleName, method, path, handlerExpr, usedIds) {
|
|
1909
2210
|
let handlerName = null;
|
|
1910
|
-
if (handlerExpr?.isKind(
|
|
2211
|
+
if (handlerExpr?.isKind(SyntaxKind11.Identifier)) {
|
|
1911
2212
|
handlerName = handlerExpr.getText();
|
|
1912
|
-
} else if (handlerExpr?.isKind(
|
|
2213
|
+
} else if (handlerExpr?.isKind(SyntaxKind11.PropertyAccessExpression)) {
|
|
1913
2214
|
handlerName = handlerExpr.getName();
|
|
1914
2215
|
}
|
|
1915
2216
|
const id = handlerName ? `${moduleName}_${handlerName}` : `${moduleName}_${method.toLowerCase()}_${sanitizePath(path)}`;
|
|
@@ -3270,6 +3571,7 @@ class Compiler {
|
|
|
3270
3571
|
const appResult = await analyzers.app.analyze();
|
|
3271
3572
|
const entityResult = await analyzers.entity.analyze();
|
|
3272
3573
|
const databaseResult = await analyzers.database.analyze();
|
|
3574
|
+
const accessResult = await analyzers.access.analyze();
|
|
3273
3575
|
const depGraphResult = await analyzers.dependencyGraph.analyze();
|
|
3274
3576
|
ir.env = envResult.env;
|
|
3275
3577
|
ir.schemas = schemaResult.schemas;
|
|
@@ -3278,8 +3580,9 @@ class Compiler {
|
|
|
3278
3580
|
ir.app = appResult.app;
|
|
3279
3581
|
ir.entities = entityResult.entities;
|
|
3280
3582
|
ir.databases = databaseResult.databases;
|
|
3583
|
+
ir.access = accessResult.access;
|
|
3281
3584
|
ir.dependencyGraph = depGraphResult.graph;
|
|
3282
|
-
ir.diagnostics.push(...analyzers.env.getDiagnostics(), ...analyzers.schema.getDiagnostics(), ...analyzers.middleware.getDiagnostics(), ...analyzers.module.getDiagnostics(), ...analyzers.app.getDiagnostics(), ...analyzers.entity.getDiagnostics(), ...analyzers.database.getDiagnostics(), ...analyzers.dependencyGraph.getDiagnostics());
|
|
3585
|
+
ir.diagnostics.push(...analyzers.env.getDiagnostics(), ...analyzers.schema.getDiagnostics(), ...analyzers.middleware.getDiagnostics(), ...analyzers.module.getDiagnostics(), ...analyzers.app.getDiagnostics(), ...analyzers.entity.getDiagnostics(), ...analyzers.database.getDiagnostics(), ...analyzers.access.getDiagnostics(), ...analyzers.dependencyGraph.getDiagnostics());
|
|
3283
3586
|
injectEntityRoutes(ir);
|
|
3284
3587
|
const collisionDiags = detectRouteCollisions(ir);
|
|
3285
3588
|
ir.diagnostics.push(...collisionDiags);
|
|
@@ -3324,6 +3627,7 @@ function createCompiler(config) {
|
|
|
3324
3627
|
app: new AppAnalyzer(project, resolved),
|
|
3325
3628
|
entity: new EntityAnalyzer(project, resolved),
|
|
3326
3629
|
database: new DatabaseAnalyzer(project, resolved),
|
|
3630
|
+
access: new AccessAnalyzer(project, resolved),
|
|
3327
3631
|
dependencyGraph: new DependencyGraphAnalyzer(project, resolved)
|
|
3328
3632
|
},
|
|
3329
3633
|
validators: [
|
|
@@ -3693,5 +3997,6 @@ export {
|
|
|
3693
3997
|
BootGenerator,
|
|
3694
3998
|
BaseGenerator,
|
|
3695
3999
|
BaseAnalyzer,
|
|
3696
|
-
AppAnalyzer
|
|
4000
|
+
AppAnalyzer,
|
|
4001
|
+
AccessAnalyzer
|
|
3697
4002
|
};
|