@saptools/service-flow 0.1.50 → 0.1.52
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/CHANGELOG.md +14 -0
- package/README.md +16 -3
- package/TECHNICAL-NOTE.md +10 -1
- package/dist/{chunk-52OUS3MO.js → chunk-PTLDSHRC.js} +2663 -363
- package/dist/chunk-PTLDSHRC.js.map +1 -0
- package/dist/cli.js +318 -510
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +59 -17
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/000-clean.ts +82 -0
- package/src/cli.ts +97 -57
- package/src/db/connection.ts +1 -1
- package/src/db/migrations.ts +5 -3
- package/src/db/repositories.ts +120 -22
- package/src/db/schema.ts +7 -2
- package/src/index.ts +1 -1
- package/src/indexer/repository-indexer.ts +57 -29
- package/src/indexer/workspace-indexer.ts +84 -6
- package/src/linker/cross-repo-linker.ts +22 -2
- package/src/linker/service-resolver.ts +15 -4
- package/src/output/table-output.ts +56 -3
- package/src/parsers/cds-parser.ts +8 -2
- package/src/parsers/decorator-parser.ts +382 -48
- package/src/parsers/handler-registration-parser.ts +38 -21
- package/src/parsers/imported-wrapper-parser.ts +18 -5
- package/src/parsers/outbound-call-parser.ts +25 -8
- package/src/parsers/package-json-parser.ts +36 -11
- package/src/parsers/service-binding-parser-helpers.ts +8 -1
- package/src/parsers/service-binding-parser.ts +6 -3
- package/src/parsers/symbol-parser.ts +13 -3
- package/src/parsers/ts-project.ts +54 -0
- package/src/trace/000-dynamic-target-types.ts +84 -0
- package/src/trace/001-dynamic-identity.ts +280 -0
- package/src/trace/002-trace-diagnostics.ts +54 -0
- package/src/trace/003-dynamic-references.ts +82 -0
- package/src/trace/dynamic-branches.ts +45 -0
- package/src/trace/dynamic-targets.ts +654 -0
- package/src/trace/evidence.ts +391 -22
- package/src/trace/selectors.ts +483 -0
- package/src/trace/trace-engine.ts +101 -94
- package/src/types.ts +39 -1
- package/dist/chunk-52OUS3MO.js.map +0 -1
|
@@ -1,5 +1,468 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// src/db/repositories.ts
|
|
4
|
+
function upsertWorkspace(db, rootPath, dbPath) {
|
|
5
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6
|
+
db.prepare(
|
|
7
|
+
"INSERT INTO workspaces(root_path,db_path,created_at,updated_at) VALUES(?,?,?,?) ON CONFLICT(root_path) DO UPDATE SET db_path=excluded.db_path,updated_at=excluded.updated_at"
|
|
8
|
+
).run(rootPath, dbPath, now, now);
|
|
9
|
+
return Number(
|
|
10
|
+
db.prepare("SELECT id FROM workspaces WHERE root_path=?").get(rootPath)?.id
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
function getWorkspace(db, rootPath) {
|
|
14
|
+
return db.prepare("SELECT * FROM workspaces WHERE root_path=?").get(rootPath);
|
|
15
|
+
}
|
|
16
|
+
function upsertRepository(db, workspaceId, r) {
|
|
17
|
+
db.prepare(
|
|
18
|
+
`INSERT INTO repositories(workspace_id,name,absolute_path,relative_path,package_name,package_version,dependencies_json,kind,is_git_repo) VALUES(?,?,?,?,?,?,?,?,?) ON CONFLICT(workspace_id,absolute_path) DO UPDATE SET name=excluded.name,relative_path=excluded.relative_path,package_name=excluded.package_name,package_version=excluded.package_version,dependencies_json=excluded.dependencies_json,kind=excluded.kind`
|
|
19
|
+
).run(
|
|
20
|
+
workspaceId,
|
|
21
|
+
r.name,
|
|
22
|
+
r.absolutePath,
|
|
23
|
+
r.relativePath,
|
|
24
|
+
r.packageName,
|
|
25
|
+
r.packageVersion,
|
|
26
|
+
JSON.stringify(r.dependencies ?? {}),
|
|
27
|
+
r.kind ?? "unknown",
|
|
28
|
+
r.isGitRepo ? 1 : 0
|
|
29
|
+
);
|
|
30
|
+
return Number(
|
|
31
|
+
db.prepare(
|
|
32
|
+
"SELECT id FROM repositories WHERE workspace_id=? AND absolute_path=?"
|
|
33
|
+
).get(workspaceId, r.absolutePath)?.id
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
function listRepositories(db, workspaceId) {
|
|
37
|
+
return db.prepare("SELECT * FROM repositories WHERE (? IS NULL OR workspace_id=?) ORDER BY name,absolute_path,id").all(workspaceId, workspaceId);
|
|
38
|
+
}
|
|
39
|
+
function reposByName(db, name, workspaceId) {
|
|
40
|
+
return db.prepare(`SELECT * FROM repositories
|
|
41
|
+
WHERE (? IS NULL OR workspace_id=?) AND (name=? OR package_name=?)
|
|
42
|
+
ORDER BY name,absolute_path,id`).all(workspaceId, workspaceId, name, name);
|
|
43
|
+
}
|
|
44
|
+
function clearRepoFacts(db, repoId) {
|
|
45
|
+
for (const t of [
|
|
46
|
+
"cds_requires",
|
|
47
|
+
"cds_services",
|
|
48
|
+
"handler_classes",
|
|
49
|
+
"outbound_calls",
|
|
50
|
+
"symbol_calls",
|
|
51
|
+
"handler_registrations",
|
|
52
|
+
"service_bindings",
|
|
53
|
+
"symbols",
|
|
54
|
+
"diagnostics",
|
|
55
|
+
"files"
|
|
56
|
+
])
|
|
57
|
+
db.prepare(`DELETE FROM ${t} WHERE repo_id=?`).run(repoId);
|
|
58
|
+
db.prepare("DELETE FROM search_index WHERE repo=?").run(String(repoId));
|
|
59
|
+
}
|
|
60
|
+
function insertRequires(db, repoId, rows2) {
|
|
61
|
+
const stmt = db.prepare(
|
|
62
|
+
"INSERT INTO cds_requires(repo_id,alias,kind,model,destination,service_path,request_timeout,raw_json) VALUES(?,?,?,?,?,?,?,?)"
|
|
63
|
+
);
|
|
64
|
+
for (const r of rows2)
|
|
65
|
+
stmt.run(
|
|
66
|
+
repoId,
|
|
67
|
+
r.alias,
|
|
68
|
+
r.kind,
|
|
69
|
+
r.model,
|
|
70
|
+
r.destination,
|
|
71
|
+
r.servicePath,
|
|
72
|
+
r.requestTimeout,
|
|
73
|
+
r.rawJson
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
function insertService(db, repoId, s) {
|
|
77
|
+
const id = Number(
|
|
78
|
+
db.prepare(
|
|
79
|
+
"INSERT INTO cds_services(repo_id,namespace,service_name,qualified_name,service_path,is_extend,source_file,source_line,extension_local_ref,extension_imported_symbol,extension_local_alias,extension_module_specifier,extension_import_kind) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?) RETURNING id"
|
|
80
|
+
).get(
|
|
81
|
+
repoId,
|
|
82
|
+
s.namespace,
|
|
83
|
+
s.serviceName,
|
|
84
|
+
s.qualifiedName,
|
|
85
|
+
s.servicePath,
|
|
86
|
+
s.isExtend ? 1 : 0,
|
|
87
|
+
s.sourceFile,
|
|
88
|
+
s.sourceLine,
|
|
89
|
+
s.extension?.localReference,
|
|
90
|
+
s.extension?.importedSymbol,
|
|
91
|
+
s.extension?.localAlias,
|
|
92
|
+
s.extension?.moduleSpecifier,
|
|
93
|
+
s.extension?.importKind
|
|
94
|
+
)?.id
|
|
95
|
+
);
|
|
96
|
+
const stmt = db.prepare(
|
|
97
|
+
"INSERT INTO cds_operations(service_id,operation_type,operation_name,operation_path,params_json,return_type,source_file,source_line,provenance,base_operation_id) VALUES(?,?,?,?,?,?,?,?,?,?)"
|
|
98
|
+
);
|
|
99
|
+
db.prepare(
|
|
100
|
+
"INSERT INTO search_index(kind,name,path,repo) VALUES(?,?,?,?)"
|
|
101
|
+
).run("service", s.qualifiedName, s.servicePath, String(repoId));
|
|
102
|
+
for (const o of s.operations)
|
|
103
|
+
stmt.run(
|
|
104
|
+
id,
|
|
105
|
+
o.operationType,
|
|
106
|
+
o.operationName,
|
|
107
|
+
o.operationPath,
|
|
108
|
+
o.paramsJson,
|
|
109
|
+
o.returnType,
|
|
110
|
+
o.sourceFile,
|
|
111
|
+
o.sourceLine,
|
|
112
|
+
o.provenance ?? "direct",
|
|
113
|
+
o.baseOperationId ?? null
|
|
114
|
+
);
|
|
115
|
+
const search = db.prepare(
|
|
116
|
+
"INSERT INTO search_index(kind,name,path,repo) VALUES(?,?,?,?)"
|
|
117
|
+
);
|
|
118
|
+
for (const o of s.operations)
|
|
119
|
+
search.run("operation", o.operationName, o.operationPath, String(repoId));
|
|
120
|
+
return id;
|
|
121
|
+
}
|
|
122
|
+
function insertHandler(db, repoId, h) {
|
|
123
|
+
const sid = insertHandlerClassSymbol(db, repoId, h);
|
|
124
|
+
const hid = Number(
|
|
125
|
+
db.prepare(
|
|
126
|
+
"INSERT INTO handler_classes(repo_id,symbol_id,class_name,source_file,source_line) VALUES(?,?,?,?,?) RETURNING id"
|
|
127
|
+
).get(repoId, sid, h.className, h.sourceFile, h.sourceLine)?.id
|
|
128
|
+
);
|
|
129
|
+
const stmt = db.prepare(
|
|
130
|
+
"INSERT INTO handler_methods(handler_class_id,method_name,decorator_kind,decorator_value,decorator_raw_expression,decorator_resolution_json,source_file,source_line) VALUES(?,?,?,?,?,?,?,?)"
|
|
131
|
+
);
|
|
132
|
+
for (const m of h.methods)
|
|
133
|
+
stmt.run(
|
|
134
|
+
hid,
|
|
135
|
+
m.methodName,
|
|
136
|
+
m.decoratorKind,
|
|
137
|
+
m.decoratorValue,
|
|
138
|
+
m.decoratorRawExpression,
|
|
139
|
+
JSON.stringify(canonicalHandlerMethodResolution(m)),
|
|
140
|
+
m.sourceFile,
|
|
141
|
+
m.sourceLine
|
|
142
|
+
);
|
|
143
|
+
insertHandlerIndexDiagnostic(db, repoId, h);
|
|
144
|
+
return hid;
|
|
145
|
+
}
|
|
146
|
+
function insertHandlerClassSymbol(db, repoId, h) {
|
|
147
|
+
const classEvidence = {
|
|
148
|
+
hasHandlerDecorator: h.hasHandlerDecorator ?? false,
|
|
149
|
+
classDecoratorNames: h.classDecoratorNames ?? [],
|
|
150
|
+
observedDecoratorNames: h.observedDecoratorNames ?? [],
|
|
151
|
+
unsupportedDecoratorNames: h.unsupportedDecoratorNames ?? [],
|
|
152
|
+
unsupportedMethods: h.methods.filter((method) => !handlerMethodIsExecutable(method)).map((method) => ({
|
|
153
|
+
methodName: method.methodName,
|
|
154
|
+
decoratorKind: method.decoratorKind,
|
|
155
|
+
sourceFile: method.sourceFile,
|
|
156
|
+
sourceLine: method.sourceLine,
|
|
157
|
+
reason: method.decoratorResolution.unresolvedReason
|
|
158
|
+
}))
|
|
159
|
+
};
|
|
160
|
+
return Number(
|
|
161
|
+
db.prepare(
|
|
162
|
+
"INSERT INTO symbols(repo_id,kind,name,qualified_name,exported,start_line,end_line,source_file,evidence_json) VALUES(?,?,?,?,?,?,?,?,?) RETURNING id"
|
|
163
|
+
).get(
|
|
164
|
+
repoId,
|
|
165
|
+
"class",
|
|
166
|
+
h.className,
|
|
167
|
+
h.className,
|
|
168
|
+
1,
|
|
169
|
+
h.sourceLine,
|
|
170
|
+
h.sourceLine,
|
|
171
|
+
h.sourceFile,
|
|
172
|
+
JSON.stringify(classEvidence)
|
|
173
|
+
)?.id
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
function insertHandlerIndexDiagnostic(db, repoId, h) {
|
|
177
|
+
if (!h.hasHandlerDecorator) return;
|
|
178
|
+
const hasExecutable = h.methods.some(handlerMethodIsExecutable);
|
|
179
|
+
const unsupported = h.methods.filter((method) => !handlerMethodIsExecutable(method));
|
|
180
|
+
if (hasExecutable && unsupported.length === 0) return;
|
|
181
|
+
const code = hasExecutable ? "handler_decorators_not_indexed" : "handler_methods_not_indexed";
|
|
182
|
+
const names = unsupported.map((method) => method.decoratorKind).sort();
|
|
183
|
+
const detail = names.length > 0 ? ` Unsupported decorators: ${[...new Set(names)].join(", ")}.` : "";
|
|
184
|
+
db.prepare(
|
|
185
|
+
"INSERT INTO diagnostics(repo_id,severity,code,message,source_file,source_line) VALUES(?,?,?,?,?,?)"
|
|
186
|
+
).run(
|
|
187
|
+
repoId,
|
|
188
|
+
"warning",
|
|
189
|
+
code,
|
|
190
|
+
hasExecutable ? `Handler class ${h.className} contains methods that were not indexed.${detail}` : `Handler class ${h.className} has no indexed executable methods; use a supported CAP handler decorator and re-index.${detail}`,
|
|
191
|
+
h.sourceFile,
|
|
192
|
+
h.sourceLine
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
function canonicalHandlerMethodResolution(method) {
|
|
196
|
+
const handlerKind = method.handlerKind ?? method.decoratorResolution.handlerKind ?? legacyHandlerKind(method.decoratorKind);
|
|
197
|
+
const executable = method.executable ?? method.decoratorResolution.executable ?? (handlerKind === "operation" || handlerKind === "event" || handlerKind === "entity_lifecycle");
|
|
198
|
+
return {
|
|
199
|
+
...method.decoratorResolution,
|
|
200
|
+
handlerKind,
|
|
201
|
+
executable,
|
|
202
|
+
lifecyclePhase: method.lifecyclePhase ?? method.decoratorResolution.lifecyclePhase,
|
|
203
|
+
lifecycleEvent: method.lifecycleEvent ?? method.decoratorResolution.lifecycleEvent
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function handlerMethodIsExecutable(method) {
|
|
207
|
+
return canonicalHandlerMethodResolution(method).executable === true;
|
|
208
|
+
}
|
|
209
|
+
function legacyHandlerKind(kind) {
|
|
210
|
+
if (kind === "Event") return "event";
|
|
211
|
+
if (["Action", "Func", "On"].includes(kind)) return "operation";
|
|
212
|
+
return "unsupported_decorator";
|
|
213
|
+
}
|
|
214
|
+
function insertRegistrations(db, repoId, rows2) {
|
|
215
|
+
const stmt = db.prepare(
|
|
216
|
+
"INSERT INTO handler_registrations(repo_id,handler_class_id,class_name,import_source,registration_file,registration_line,registration_kind,confidence) VALUES(?,?,?,?,?,?,?,?)"
|
|
217
|
+
);
|
|
218
|
+
for (const r of rows2) {
|
|
219
|
+
const handlerClass = r.className ? db.prepare(
|
|
220
|
+
"SELECT id FROM handler_classes WHERE repo_id=? AND class_name=? ORDER BY id"
|
|
221
|
+
).all(repoId, r.className) : [];
|
|
222
|
+
stmt.run(
|
|
223
|
+
repoId,
|
|
224
|
+
handlerClass.length === 1 ? handlerClass[0]?.id : null,
|
|
225
|
+
r.className,
|
|
226
|
+
r.importSource,
|
|
227
|
+
r.registrationFile,
|
|
228
|
+
r.registrationLine,
|
|
229
|
+
r.registrationKind,
|
|
230
|
+
r.confidence
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function insertBindings(db, repoId, rows2) {
|
|
235
|
+
const stmt = db.prepare(
|
|
236
|
+
"INSERT INTO service_bindings(repo_id,symbol_id,variable_name,alias,alias_expr,destination_expr,service_path_expr,is_dynamic,placeholders_json,source_file,source_line,helper_chain_json) VALUES(?,(SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND start_line<=? AND end_line>=? ORDER BY (end_line-start_line),id LIMIT 1),?,?,?,?,?,?,?,?,?,?)"
|
|
237
|
+
);
|
|
238
|
+
for (const r of rows2)
|
|
239
|
+
stmt.run(
|
|
240
|
+
repoId,
|
|
241
|
+
repoId,
|
|
242
|
+
r.sourceFile,
|
|
243
|
+
r.sourceLine,
|
|
244
|
+
r.sourceLine,
|
|
245
|
+
r.variableName,
|
|
246
|
+
r.alias,
|
|
247
|
+
r.aliasExpr,
|
|
248
|
+
r.destinationExpr,
|
|
249
|
+
r.servicePathExpr,
|
|
250
|
+
r.isDynamic ? 1 : 0,
|
|
251
|
+
JSON.stringify(r.placeholders),
|
|
252
|
+
r.sourceFile,
|
|
253
|
+
r.sourceLine,
|
|
254
|
+
r.helperChain ? JSON.stringify(r.helperChain) : null
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
function insertExecutableSymbols(db, repoId, rows2) {
|
|
258
|
+
const stmt = db.prepare("INSERT INTO symbols(repo_id,file_id,kind,name,qualified_name,exported,start_line,end_line,start_offset,end_offset,source_file,exported_name,evidence_json) VALUES(?,(SELECT id FROM files WHERE repo_id=? AND relative_path=?),?,?,?,?,?,?,?,?,?,?,?)");
|
|
259
|
+
for (const r of rows2) stmt.run(repoId, repoId, r.sourceFile, r.kind, r.localName, r.qualifiedName, r.exported ? 1 : 0, r.startLine, r.endLine, r.startOffset, r.endOffset, r.sourceFile, r.exportedName, r.importExportEvidence ? JSON.stringify(r.importExportEvidence) : null);
|
|
260
|
+
}
|
|
261
|
+
function insertSymbolCalls(db, repoId, rows2) {
|
|
262
|
+
const callerStmt = db.prepare("SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND qualified_name=? ORDER BY id LIMIT 1");
|
|
263
|
+
const insertStmt = db.prepare("INSERT INTO symbol_calls(repo_id,caller_symbol_id,callee_symbol_id,callee_expression,import_source,source_file,source_line,status,confidence,evidence_json,unresolved_reason) VALUES(?,?,?,?,?,?,?,?,?,?,?)");
|
|
264
|
+
for (const r of rows2) {
|
|
265
|
+
const caller = callerStmt.get(repoId, r.sourceFile, r.callerQualifiedName);
|
|
266
|
+
const target = resolveSymbolCallTarget(db, repoId, r);
|
|
267
|
+
insertStmt.run(repoId, caller?.id, target.id, r.calleeExpression, r.importSource, r.sourceFile, r.sourceLine, target.status, 0.8, JSON.stringify({ ...r.evidence, candidateStrategy: target.strategy, candidateCount: target.candidateCount }), target.reason);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function isRelativeImportedSymbolCall(r) {
|
|
271
|
+
return Boolean(r.importSource?.startsWith("."));
|
|
272
|
+
}
|
|
273
|
+
function resolveSymbolCallTarget(db, repoId, r) {
|
|
274
|
+
const evidence = r.evidence;
|
|
275
|
+
const localRows = db.prepare("SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND (name=? OR qualified_name=?) ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName, r.calleeLocalName);
|
|
276
|
+
if (localRows.length === 1) return { id: localRows[0]?.id ?? null, status: "resolved", reason: null, strategy: "same_file_exact", candidateCount: 1 };
|
|
277
|
+
if (localRows.length > 1) return { id: null, status: "ambiguous", reason: "Multiple same-file symbol targets matched exactly", strategy: "same_file_exact", candidateCount: localRows.length };
|
|
278
|
+
if (evidence.relation === "class_instance_method" && isRelativeImportedSymbolCall(r)) {
|
|
279
|
+
const classRows = db.prepare("SELECT id FROM symbols WHERE repo_id=? AND source_file<>? AND qualified_name=? ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName);
|
|
280
|
+
if (classRows.length === 1) return { id: classRows[0]?.id ?? null, status: "resolved", reason: null, strategy: "relative_import_class_instance_method", candidateCount: 1 };
|
|
281
|
+
if (classRows.length > 1) return { id: null, status: "ambiguous", reason: "Multiple relative class instance method targets matched exactly", strategy: "relative_import_class_instance_method", candidateCount: classRows.length };
|
|
282
|
+
}
|
|
283
|
+
const rows2 = db.prepare("SELECT id,kind,evidence_json evidenceJson FROM symbols WHERE repo_id=? AND source_file<>? AND exported=1 AND (exported_name=? OR name=? OR qualified_name=?) ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName, r.calleeLocalName, r.calleeLocalName);
|
|
284
|
+
if (evidence.relation === "relative_import_proxy_member" && rows2.length > 1) {
|
|
285
|
+
const objectMapRows = rows2.filter((row) => String(row.evidenceJson ?? "").includes("exported_object_shorthand") || String(row.evidenceJson ?? "").includes("exported_object_literal"));
|
|
286
|
+
if (objectMapRows.length > 0) {
|
|
287
|
+
const concrete = rows2.find((row) => row.kind !== "object_alias") ?? objectMapRows[0];
|
|
288
|
+
return { id: concrete?.id ?? null, status: "resolved", reason: null, strategy: "proxy_member_exported_object_map", candidateCount: rows2.length };
|
|
289
|
+
}
|
|
290
|
+
return { id: null, status: "ambiguous", reason: "Proxy member target requires explicit factory/module/type evidence; global member name is ambiguous", strategy: "proxy_member_no_global_name_fallback", candidateCount: rows2.length };
|
|
291
|
+
}
|
|
292
|
+
if (rows2.length === 1) return { id: rows2[0]?.id ?? null, status: "resolved", reason: null, strategy: evidence.relation === "relative_import_proxy_member" ? "proxy_member_unique_exported_candidate" : "relative_import_exported_exact", candidateCount: 1 };
|
|
293
|
+
if (rows2.length > 1) return { id: null, status: "ambiguous", reason: "Multiple exported symbol targets matched exactly", strategy: "exported_exact", candidateCount: rows2.length };
|
|
294
|
+
return { id: null, status: "unresolved", reason: "No local symbol target matched exactly", strategy: evidence.relation === "relative_import_proxy_member" ? "proxy_member_no_global_name_fallback" : "exact_symbol_match", candidateCount: 0 };
|
|
295
|
+
}
|
|
296
|
+
function insertCalls(db, repoId, rows2) {
|
|
297
|
+
const stmt = db.prepare(
|
|
298
|
+
"INSERT INTO outbound_calls(repo_id,source_symbol_id,call_type,method,operation_path_expr,query_entity,event_name_expr,payload_summary,source_file,source_line,confidence,unresolved_reason,local_service_name,local_service_lookup,alias_chain_json,evidence_json,external_target_kind,external_target_id,external_target_label,external_target_dynamic,service_binding_id) VALUES(?,COALESCE((SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND qualified_name=? ORDER BY id LIMIT 1),(SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND start_line<=? AND end_line>=? ORDER BY (end_line-start_line),id LIMIT 1)),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
|
|
299
|
+
);
|
|
300
|
+
for (const r of rows2) {
|
|
301
|
+
const binding = resolvePersistedBinding(db, repoId, r);
|
|
302
|
+
const evidence = {
|
|
303
|
+
...r.evidence ?? {},
|
|
304
|
+
serviceBindingResolution: binding.evidence
|
|
305
|
+
};
|
|
306
|
+
stmt.run(
|
|
307
|
+
repoId,
|
|
308
|
+
repoId,
|
|
309
|
+
r.sourceFile,
|
|
310
|
+
r.sourceSymbolQualifiedName,
|
|
311
|
+
repoId,
|
|
312
|
+
r.sourceFile,
|
|
313
|
+
r.sourceLine,
|
|
314
|
+
r.sourceLine,
|
|
315
|
+
r.callType,
|
|
316
|
+
r.method,
|
|
317
|
+
r.operationPathExpr,
|
|
318
|
+
r.queryEntity,
|
|
319
|
+
r.eventNameExpr,
|
|
320
|
+
r.payloadSummary,
|
|
321
|
+
r.sourceFile,
|
|
322
|
+
r.sourceLine,
|
|
323
|
+
r.confidence,
|
|
324
|
+
r.unresolvedReason ?? binding.unresolvedReason,
|
|
325
|
+
r.localServiceName,
|
|
326
|
+
r.localServiceLookup,
|
|
327
|
+
r.aliasChain ? JSON.stringify(r.aliasChain) : null,
|
|
328
|
+
JSON.stringify(evidence),
|
|
329
|
+
r.externalTarget?.kind ?? null,
|
|
330
|
+
r.externalTarget?.stableId ?? null,
|
|
331
|
+
r.externalTarget?.label ?? null,
|
|
332
|
+
r.externalTarget?.dynamic ? 1 : 0,
|
|
333
|
+
binding.bindingId
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function resolvePersistedBinding(db, repoId, call) {
|
|
338
|
+
if (!call.serviceVariableName)
|
|
339
|
+
return { bindingId: null, evidence: { status: "not_applicable", candidateCount: 0 } };
|
|
340
|
+
const candidates = bindingCandidates(db, repoId, call);
|
|
341
|
+
const prior = candidates.filter((candidate) => candidate.sourceLine <= call.sourceLine);
|
|
342
|
+
const families = new Set(prior.map(bindingSignature));
|
|
343
|
+
if (prior.length > 0 && families.size === 1) {
|
|
344
|
+
const selected = prior.at(-1);
|
|
345
|
+
return {
|
|
346
|
+
bindingId: selected?.id ?? null,
|
|
347
|
+
evidence: bindingEvidence("selected", prior, selected)
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
if (prior.length > 1) {
|
|
351
|
+
return {
|
|
352
|
+
bindingId: null,
|
|
353
|
+
unresolvedReason: "ambiguous_service_binding_candidates",
|
|
354
|
+
evidence: bindingEvidence("ambiguous", prior)
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
if (candidates.length > 0) {
|
|
358
|
+
return {
|
|
359
|
+
bindingId: null,
|
|
360
|
+
unresolvedReason: "service_binding_declared_after_call",
|
|
361
|
+
evidence: bindingEvidence("rejected_future_binding", candidates)
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
bindingId: null,
|
|
366
|
+
evidence: bindingEvidence("unrecoverable", [])
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function bindingCandidates(db, repoId, call) {
|
|
370
|
+
const ownerId = callSymbolId(db, repoId, call);
|
|
371
|
+
const rows2 = db.prepare(`
|
|
372
|
+
SELECT id,symbol_id symbolId,variable_name variableName,alias,alias_expr aliasExpr,
|
|
373
|
+
destination_expr destinationExpr,service_path_expr servicePathExpr,
|
|
374
|
+
source_file sourceFile,source_line sourceLine,helper_chain_json helperChainJson
|
|
375
|
+
FROM service_bindings
|
|
376
|
+
WHERE repo_id=? AND variable_name=? AND source_file=?
|
|
377
|
+
AND (? IS NULL OR symbol_id IS NULL OR symbol_id=?)
|
|
378
|
+
ORDER BY source_line,id
|
|
379
|
+
`).all(
|
|
380
|
+
repoId,
|
|
381
|
+
call.serviceVariableName,
|
|
382
|
+
call.sourceFile,
|
|
383
|
+
ownerId,
|
|
384
|
+
ownerId
|
|
385
|
+
);
|
|
386
|
+
return rows2.flatMap((row) => {
|
|
387
|
+
if (typeof row.id !== "number" || typeof row.variableName !== "string" || typeof row.sourceFile !== "string" || typeof row.sourceLine !== "number")
|
|
388
|
+
return [];
|
|
389
|
+
return [{
|
|
390
|
+
id: row.id,
|
|
391
|
+
symbolId: nullableNumber(row.symbolId),
|
|
392
|
+
variableName: row.variableName,
|
|
393
|
+
alias: nullableString(row.alias),
|
|
394
|
+
aliasExpr: nullableString(row.aliasExpr),
|
|
395
|
+
destinationExpr: nullableString(row.destinationExpr),
|
|
396
|
+
servicePathExpr: nullableString(row.servicePathExpr),
|
|
397
|
+
sourceFile: row.sourceFile,
|
|
398
|
+
sourceLine: row.sourceLine,
|
|
399
|
+
helperChainJson: nullableString(row.helperChainJson)
|
|
400
|
+
}];
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
function nullableString(value) {
|
|
404
|
+
return value === null || typeof value === "string" ? value : void 0;
|
|
405
|
+
}
|
|
406
|
+
function nullableNumber(value) {
|
|
407
|
+
return value === null || typeof value === "number" ? value : void 0;
|
|
408
|
+
}
|
|
409
|
+
function callSymbolId(db, repoId, call) {
|
|
410
|
+
const row = db.prepare(`
|
|
411
|
+
SELECT id FROM symbols
|
|
412
|
+
WHERE repo_id=? AND source_file=?
|
|
413
|
+
AND ((? IS NOT NULL AND qualified_name=?)
|
|
414
|
+
OR (start_line<=? AND end_line>=?))
|
|
415
|
+
ORDER BY CASE WHEN qualified_name=? THEN 0 ELSE 1 END,
|
|
416
|
+
(end_line-start_line),id
|
|
417
|
+
LIMIT 1
|
|
418
|
+
`).get(
|
|
419
|
+
repoId,
|
|
420
|
+
call.sourceFile,
|
|
421
|
+
call.sourceSymbolQualifiedName,
|
|
422
|
+
call.sourceSymbolQualifiedName,
|
|
423
|
+
call.sourceLine,
|
|
424
|
+
call.sourceLine,
|
|
425
|
+
call.sourceSymbolQualifiedName
|
|
426
|
+
);
|
|
427
|
+
return typeof row?.id === "number" ? row.id : void 0;
|
|
428
|
+
}
|
|
429
|
+
function bindingEvidence(status, candidates, selected) {
|
|
430
|
+
return {
|
|
431
|
+
status,
|
|
432
|
+
candidateCount: candidates.length,
|
|
433
|
+
selectedBindingId: selected?.id,
|
|
434
|
+
sourceOrderRule: "binding_source_line_must_not_follow_call",
|
|
435
|
+
candidates: candidates.map((candidate) => ({
|
|
436
|
+
bindingId: candidate.id,
|
|
437
|
+
symbolId: candidate.symbolId,
|
|
438
|
+
variableName: candidate.variableName,
|
|
439
|
+
alias: candidate.alias,
|
|
440
|
+
aliasExpr: candidate.aliasExpr,
|
|
441
|
+
destinationExpr: candidate.destinationExpr,
|
|
442
|
+
servicePathExpr: candidate.servicePathExpr,
|
|
443
|
+
sourceFile: candidate.sourceFile,
|
|
444
|
+
sourceLine: candidate.sourceLine,
|
|
445
|
+
helperChain: parseBindingChain(candidate.helperChainJson)
|
|
446
|
+
}))
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
function bindingSignature(candidate) {
|
|
450
|
+
return JSON.stringify([
|
|
451
|
+
candidate.alias,
|
|
452
|
+
candidate.aliasExpr,
|
|
453
|
+
candidate.destinationExpr,
|
|
454
|
+
candidate.servicePathExpr
|
|
455
|
+
]);
|
|
456
|
+
}
|
|
457
|
+
function parseBindingChain(value) {
|
|
458
|
+
if (!value) return void 0;
|
|
459
|
+
try {
|
|
460
|
+
return JSON.parse(value);
|
|
461
|
+
} catch {
|
|
462
|
+
return void 0;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
3
466
|
// src/discovery/discover-repositories.ts
|
|
4
467
|
import fs from "fs/promises";
|
|
5
468
|
import path2 from "path";
|
|
@@ -74,6 +537,9 @@ async function discoverRepositories(rootPath, ignore) {
|
|
|
74
537
|
// src/parsers/package-json-parser.ts
|
|
75
538
|
import fs2 from "fs/promises";
|
|
76
539
|
import path3 from "path";
|
|
540
|
+
function emptyPackageFacts() {
|
|
541
|
+
return { dependencies: {}, cdsRequires: [], scripts: {} };
|
|
542
|
+
}
|
|
77
543
|
function recordOfString(value) {
|
|
78
544
|
if (!value || typeof value !== "object") return {};
|
|
79
545
|
return Object.fromEntries(
|
|
@@ -100,22 +566,31 @@ function readRequires(cds) {
|
|
|
100
566
|
];
|
|
101
567
|
});
|
|
102
568
|
}
|
|
103
|
-
async function parsePackageJson(repoPath) {
|
|
569
|
+
async function parsePackageJson(repoPath, options = {}) {
|
|
570
|
+
return (await loadPackageJsonSnapshot(repoPath, options)).facts;
|
|
571
|
+
}
|
|
572
|
+
async function loadPackageJsonSnapshot(repoPath, options = {}) {
|
|
104
573
|
try {
|
|
105
574
|
const raw = await fs2.readFile(path3.join(repoPath, "package.json"), "utf8");
|
|
106
575
|
const json = JSON.parse(raw);
|
|
107
576
|
return {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
577
|
+
rawText: raw,
|
|
578
|
+
facts: {
|
|
579
|
+
packageName: typeof json.name === "string" ? json.name : void 0,
|
|
580
|
+
packageVersion: typeof json.version === "string" ? json.version : void 0,
|
|
581
|
+
dependencies: {
|
|
582
|
+
...recordOfString(json.dependencies),
|
|
583
|
+
...recordOfString(json.devDependencies)
|
|
584
|
+
},
|
|
585
|
+
cdsRequires: readRequires(json.cds),
|
|
586
|
+
scripts: recordOfString(json.scripts)
|
|
587
|
+
}
|
|
116
588
|
};
|
|
117
|
-
} catch {
|
|
118
|
-
|
|
589
|
+
} catch (error) {
|
|
590
|
+
const missing = typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
591
|
+
if (!options.strict || options.allowMissing && missing)
|
|
592
|
+
return { facts: emptyPackageFacts(), rawText: "" };
|
|
593
|
+
throw error;
|
|
119
594
|
}
|
|
120
595
|
}
|
|
121
596
|
|
|
@@ -259,9 +734,9 @@ function operationsFromBody(text, maskedBody, bodyOffset, filePath) {
|
|
|
259
734
|
sourceLine: lineOf(text, bodyOffset + (m.index ?? 0))
|
|
260
735
|
}));
|
|
261
736
|
}
|
|
262
|
-
async function parseCdsFile(repoPath, filePath) {
|
|
737
|
+
async function parseCdsFile(repoPath, filePath, context) {
|
|
263
738
|
const absolute = path4.join(repoPath, filePath);
|
|
264
|
-
const text = await fs3.readFile(absolute, "utf8");
|
|
739
|
+
const text = context?.get(filePath)?.text ?? await fs3.readFile(absolute, "utf8");
|
|
265
740
|
const masked = maskCommentsAndStrings(text);
|
|
266
741
|
const namespace = /namespace\s+([\w.]+)\s*;/.exec(masked)?.[1];
|
|
267
742
|
const services = [];
|
|
@@ -311,8 +786,8 @@ async function parseCdsFile(repoPath, filePath) {
|
|
|
311
786
|
}
|
|
312
787
|
|
|
313
788
|
// src/parsers/decorator-parser.ts
|
|
314
|
-
import
|
|
315
|
-
import
|
|
789
|
+
import fs5 from "fs/promises";
|
|
790
|
+
import path6 from "path";
|
|
316
791
|
import ts2 from "typescript";
|
|
317
792
|
|
|
318
793
|
// src/linker/operation-decorator-normalizer.ts
|
|
@@ -356,7 +831,34 @@ function normalizeDecoratorOperationSignal(value, raw, candidateOperation) {
|
|
|
356
831
|
}
|
|
357
832
|
|
|
358
833
|
// src/parsers/ts-project.ts
|
|
834
|
+
import fs4 from "fs/promises";
|
|
835
|
+
import path5 from "path";
|
|
359
836
|
import ts from "typescript";
|
|
837
|
+
async function loadRepositorySourceContext(repoPath, filePaths, instrumentation) {
|
|
838
|
+
const snapshots = /* @__PURE__ */ new Map();
|
|
839
|
+
for (const inputPath of filePaths) {
|
|
840
|
+
const filePath = normalizePath(inputPath);
|
|
841
|
+
await instrumentation?.onSourceRead?.(repoPath, filePath);
|
|
842
|
+
const text = await fs4.readFile(path5.join(repoPath, filePath), "utf8");
|
|
843
|
+
let ast;
|
|
844
|
+
snapshots.set(filePath, {
|
|
845
|
+
repoPath,
|
|
846
|
+
filePath,
|
|
847
|
+
text,
|
|
848
|
+
sizeBytes: Buffer.byteLength(text),
|
|
849
|
+
sourceFile: () => {
|
|
850
|
+
if (ast) return ast;
|
|
851
|
+
instrumentation?.onAstCreated?.(repoPath, filePath);
|
|
852
|
+
ast = createSourceFile(filePath, text);
|
|
853
|
+
return ast;
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
return {
|
|
858
|
+
get: (filePath) => snapshots.get(normalizePath(filePath)),
|
|
859
|
+
entries: () => [...snapshots.values()]
|
|
860
|
+
};
|
|
861
|
+
}
|
|
360
862
|
function createSourceFile(filePath, text) {
|
|
361
863
|
return ts.createSourceFile(
|
|
362
864
|
filePath,
|
|
@@ -368,6 +870,22 @@ function createSourceFile(filePath, text) {
|
|
|
368
870
|
}
|
|
369
871
|
|
|
370
872
|
// src/parsers/decorator-parser.ts
|
|
873
|
+
var OPERATION_DECORATORS = /* @__PURE__ */ new Set(["Action", "Func", "On"]);
|
|
874
|
+
var EVENT_DECORATORS = /* @__PURE__ */ new Set(["Event"]);
|
|
875
|
+
var LIFECYCLE_DECORATORS = /* @__PURE__ */ new Map([
|
|
876
|
+
["BeforeCreate", { phase: "before", event: "CREATE" }],
|
|
877
|
+
["OnCreate", { phase: "on", event: "CREATE" }],
|
|
878
|
+
["AfterCreate", { phase: "after", event: "CREATE" }],
|
|
879
|
+
["BeforeRead", { phase: "before", event: "READ" }],
|
|
880
|
+
["OnRead", { phase: "on", event: "READ" }],
|
|
881
|
+
["AfterRead", { phase: "after", event: "READ" }],
|
|
882
|
+
["BeforeUpdate", { phase: "before", event: "UPDATE" }],
|
|
883
|
+
["OnUpdate", { phase: "on", event: "UPDATE" }],
|
|
884
|
+
["AfterUpdate", { phase: "after", event: "UPDATE" }],
|
|
885
|
+
["BeforeDelete", { phase: "before", event: "DELETE" }],
|
|
886
|
+
["OnDelete", { phase: "on", event: "DELETE" }],
|
|
887
|
+
["AfterDelete", { phase: "after", event: "DELETE" }]
|
|
888
|
+
]);
|
|
371
889
|
function line(sf, pos) {
|
|
372
890
|
return sf.getLineAndCharacterOfPosition(pos).line + 1;
|
|
373
891
|
}
|
|
@@ -382,6 +900,12 @@ function firstArg(d) {
|
|
|
382
900
|
const e = d.expression;
|
|
383
901
|
return ts2.isCallExpression(e) ? e.arguments[0] : void 0;
|
|
384
902
|
}
|
|
903
|
+
function decoratorArguments(d) {
|
|
904
|
+
return ts2.isCallExpression(d.expression) ? d.expression.arguments : void 0;
|
|
905
|
+
}
|
|
906
|
+
function methodName(name) {
|
|
907
|
+
return ts2.isIdentifier(name) || ts2.isStringLiteralLike(name) || ts2.isNumericLiteral(name) ? name.text : name.getText();
|
|
908
|
+
}
|
|
385
909
|
function unwrapExpression(expression) {
|
|
386
910
|
let current = expression;
|
|
387
911
|
while (ts2.isParenthesizedExpression(current) || ts2.isAsExpression(current) || ts2.isTypeAssertionExpression(current) || ts2.isSatisfiesExpression(current)) current = current.expression;
|
|
@@ -419,9 +943,12 @@ function collectStringLookups(source) {
|
|
|
419
943
|
const lookups = {
|
|
420
944
|
identifiers: /* @__PURE__ */ new Map(),
|
|
421
945
|
enumMembers: /* @__PURE__ */ new Map(),
|
|
422
|
-
objectProperties: /* @__PURE__ */ new Map()
|
|
946
|
+
objectProperties: /* @__PURE__ */ new Map(),
|
|
947
|
+
capDecoratorNames: /* @__PURE__ */ new Map(),
|
|
948
|
+
capDecoratorNamespaces: /* @__PURE__ */ new Set()
|
|
423
949
|
};
|
|
424
950
|
for (const statement of source.statements) {
|
|
951
|
+
collectCapDecoratorImports(statement, lookups);
|
|
425
952
|
if (ts2.isEnumDeclaration(statement)) collectEnumMembers(statement, lookups);
|
|
426
953
|
if (!ts2.isVariableStatement(statement) || !(statement.declarationList.flags & ts2.NodeFlags.Const)) continue;
|
|
427
954
|
for (const declaration of statement.declarationList.declarations) {
|
|
@@ -433,75 +960,277 @@ function collectStringLookups(source) {
|
|
|
433
960
|
}
|
|
434
961
|
return lookups;
|
|
435
962
|
}
|
|
436
|
-
function
|
|
437
|
-
|
|
963
|
+
function collectCapDecoratorImports(statement, lookups) {
|
|
964
|
+
if (!ts2.isImportDeclaration(statement) || !ts2.isStringLiteral(statement.moduleSpecifier) || statement.moduleSpecifier.text !== "cds-routing-handlers") return;
|
|
965
|
+
const clause = statement.importClause;
|
|
966
|
+
if (!clause || clause.isTypeOnly) return;
|
|
967
|
+
const bindings = clause.namedBindings;
|
|
968
|
+
if (bindings && ts2.isNamedImports(bindings)) {
|
|
969
|
+
for (const element of bindings.elements) {
|
|
970
|
+
if (element.isTypeOnly) continue;
|
|
971
|
+
lookups.capDecoratorNames.set(
|
|
972
|
+
element.name.text,
|
|
973
|
+
element.propertyName?.text ?? element.name.text
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
if (bindings && ts2.isNamespaceImport(bindings))
|
|
978
|
+
lookups.capDecoratorNamespaces.add(bindings.name.text);
|
|
979
|
+
}
|
|
980
|
+
function capDecoratorName(decorator, lookups) {
|
|
981
|
+
const expression = ts2.isCallExpression(decorator.expression) ? decorator.expression.expression : decorator.expression;
|
|
982
|
+
if (ts2.isIdentifier(expression))
|
|
983
|
+
return lookups.capDecoratorNames.get(expression.text);
|
|
984
|
+
if (ts2.isPropertyAccessExpression(expression) && ts2.isIdentifier(expression.expression) && lookups.capDecoratorNamespaces.has(expression.expression.text))
|
|
985
|
+
return expression.name.text;
|
|
986
|
+
return void 0;
|
|
987
|
+
}
|
|
988
|
+
function unresolved(rawExpression, reason, argumentExpression) {
|
|
989
|
+
return {
|
|
990
|
+
rawExpression,
|
|
991
|
+
argumentExpression,
|
|
992
|
+
resolutionKind: "unresolved",
|
|
993
|
+
unresolvedReason: reason
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
function resolved2(rawExpression, argumentExpression, resolvedValue, resolutionKind) {
|
|
997
|
+
return { rawExpression, argumentExpression, resolvedValue, resolutionKind };
|
|
438
998
|
}
|
|
439
999
|
function generatedConstant(rawExpression) {
|
|
440
1000
|
const match = /(?:^|\.)(Action[A-Z][\w$]*|Func[A-Z][\w$]*)\.name$/.exec(rawExpression.trim());
|
|
441
1001
|
return match?.[1] ? generatedOperationNameFromConstant(match[1]) : void 0;
|
|
442
1002
|
}
|
|
443
|
-
function resolveDecoratorArgument(argument, lookups) {
|
|
444
|
-
if (!argument) return unresolved(
|
|
445
|
-
const
|
|
1003
|
+
function resolveDecoratorArgument(argument, lookups, rawExpression) {
|
|
1004
|
+
if (!argument) return unresolved(rawExpression, "decorator_argument_missing");
|
|
1005
|
+
const argumentExpression = argument.getText();
|
|
446
1006
|
const expression = unwrapExpression(argument);
|
|
447
1007
|
if (ts2.isStringLiteralLike(expression))
|
|
448
|
-
return
|
|
1008
|
+
return resolved2(rawExpression, argumentExpression, expression.text, "literal");
|
|
449
1009
|
if (ts2.isIdentifier(expression)) {
|
|
450
1010
|
const value = lookups.identifiers.get(expression.text);
|
|
451
|
-
return value === void 0 ? unresolved(rawExpression, "identifier_not_resolved_to_local_const_string") :
|
|
1011
|
+
return value === void 0 ? unresolved(rawExpression, "identifier_not_resolved_to_local_const_string", argumentExpression) : resolved2(rawExpression, argumentExpression, value, "const_identifier");
|
|
452
1012
|
}
|
|
453
1013
|
if (ts2.isPropertyAccessExpression(expression) && ts2.isIdentifier(expression.expression)) {
|
|
454
1014
|
const key = `${expression.expression.text}.${expression.name.text}`;
|
|
455
1015
|
const enumValue = lookups.enumMembers.get(key);
|
|
456
1016
|
if (enumValue !== void 0)
|
|
457
|
-
return
|
|
1017
|
+
return resolved2(rawExpression, argumentExpression, enumValue, "enum_member");
|
|
458
1018
|
const objectValue2 = lookups.objectProperties.get(key);
|
|
459
1019
|
if (objectValue2 !== void 0)
|
|
460
|
-
return
|
|
1020
|
+
return resolved2(rawExpression, argumentExpression, objectValue2, "const_object_property");
|
|
461
1021
|
}
|
|
462
|
-
const generatedValue = generatedConstant(
|
|
1022
|
+
const generatedValue = generatedConstant(argumentExpression);
|
|
463
1023
|
if (generatedValue !== void 0)
|
|
464
|
-
return
|
|
1024
|
+
return resolved2(
|
|
465
1025
|
rawExpression,
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
1026
|
+
argumentExpression,
|
|
1027
|
+
generatedValue,
|
|
1028
|
+
"generated_constant_name"
|
|
1029
|
+
);
|
|
469
1030
|
if (ts2.isPropertyAccessExpression(expression))
|
|
470
|
-
return unresolved(
|
|
471
|
-
|
|
1031
|
+
return unresolved(
|
|
1032
|
+
rawExpression,
|
|
1033
|
+
"property_access_not_resolved_to_local_string",
|
|
1034
|
+
argumentExpression
|
|
1035
|
+
);
|
|
1036
|
+
return unresolved(rawExpression, "unsupported_decorator_expression", argumentExpression);
|
|
1037
|
+
}
|
|
1038
|
+
function classificationFor(name) {
|
|
1039
|
+
if (OPERATION_DECORATORS.has(name))
|
|
1040
|
+
return { handlerKind: "operation", executable: true };
|
|
1041
|
+
if (EVENT_DECORATORS.has(name))
|
|
1042
|
+
return { handlerKind: "event", executable: true };
|
|
1043
|
+
const lifecycle = LIFECYCLE_DECORATORS.get(name);
|
|
1044
|
+
if (!lifecycle) return void 0;
|
|
1045
|
+
return {
|
|
1046
|
+
handlerKind: "entity_lifecycle",
|
|
1047
|
+
executable: true,
|
|
1048
|
+
lifecyclePhase: lifecycle.phase,
|
|
1049
|
+
lifecycleEvent: lifecycle.event
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
function lifecycleLikePhase(name) {
|
|
1053
|
+
if (/^On[A-Z]/.test(name)) return "on";
|
|
1054
|
+
if (/^Before[A-Z]/.test(name)) return "before";
|
|
1055
|
+
if (/^After[A-Z]/.test(name)) return "after";
|
|
1056
|
+
return void 0;
|
|
1057
|
+
}
|
|
1058
|
+
function withClassification(resolution, classification) {
|
|
1059
|
+
return {
|
|
1060
|
+
...resolution,
|
|
1061
|
+
handlerKind: classification.handlerKind,
|
|
1062
|
+
executable: classification.executable,
|
|
1063
|
+
lifecyclePhase: classification.lifecyclePhase,
|
|
1064
|
+
lifecycleEvent: classification.lifecycleEvent
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
function withDecoratorEvidence(resolution, decorator, resolvedDecoratorKind) {
|
|
1068
|
+
return {
|
|
1069
|
+
...resolution,
|
|
1070
|
+
decoratorExpression: decorator.expression.getText(),
|
|
1071
|
+
resolvedDecoratorKind,
|
|
1072
|
+
decoratorImportSource: resolvedDecoratorKind ? "cds-routing-handlers" : void 0
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
function lifecycleDecoratorResolution(decorator, lookups, classification) {
|
|
1076
|
+
const rawExpression = decorator.expression.getText();
|
|
1077
|
+
const args = decoratorArguments(decorator);
|
|
1078
|
+
if (args?.length === 0)
|
|
1079
|
+
return {
|
|
1080
|
+
classification,
|
|
1081
|
+
resolution: withClassification({
|
|
1082
|
+
rawExpression,
|
|
1083
|
+
resolutionKind: "lifecycle_implicit"
|
|
1084
|
+
}, classification)
|
|
1085
|
+
};
|
|
1086
|
+
const resolution = args?.length === 1 ? resolveDecoratorArgument(args[0], lookups, rawExpression) : unresolved(rawExpression, args ? "unsupported_lifecycle_argument_count" : "lifecycle_decorator_call_required");
|
|
1087
|
+
const unsupported = { ...classification, handlerKind: "unsupported_lifecycle", executable: false };
|
|
1088
|
+
const unsupportedResolution = args?.length === 1 ? { ...resolution, unresolvedReason: "lifecycle_decorator_arguments_not_supported" } : resolution;
|
|
1089
|
+
return {
|
|
1090
|
+
classification: unsupported,
|
|
1091
|
+
resolution: withClassification(unsupportedResolution, unsupported)
|
|
1092
|
+
};
|
|
472
1093
|
}
|
|
473
|
-
|
|
474
|
-
const
|
|
475
|
-
|
|
1094
|
+
function unsupportedLifecycleResolution(decorator, phase) {
|
|
1095
|
+
const classification = {
|
|
1096
|
+
handlerKind: "unsupported_lifecycle",
|
|
1097
|
+
executable: false,
|
|
1098
|
+
lifecyclePhase: phase
|
|
1099
|
+
};
|
|
1100
|
+
const resolution = unresolved(
|
|
1101
|
+
decorator.expression.getText(),
|
|
1102
|
+
"lifecycle_decorator_not_allowlisted"
|
|
1103
|
+
);
|
|
1104
|
+
return { classification, resolution: withClassification(resolution, classification) };
|
|
1105
|
+
}
|
|
1106
|
+
function unsupportedDecoratorResolution(decorator, reason, phase) {
|
|
1107
|
+
const classification = {
|
|
1108
|
+
handlerKind: phase ? "unsupported_lifecycle" : "unsupported_decorator",
|
|
1109
|
+
executable: false,
|
|
1110
|
+
lifecyclePhase: phase
|
|
1111
|
+
};
|
|
1112
|
+
return {
|
|
1113
|
+
classification,
|
|
1114
|
+
resolution: withClassification(
|
|
1115
|
+
unresolved(decorator.expression.getText(), reason),
|
|
1116
|
+
classification
|
|
1117
|
+
)
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
function parseMethodDecorator(decorator, lookups, handlerClass, allowLifecycle) {
|
|
1121
|
+
const decoratorKind = callName(decorator);
|
|
1122
|
+
const importedKind = capDecoratorName(decorator, lookups);
|
|
1123
|
+
const resolvedKind = importedKind ?? decoratorKind;
|
|
1124
|
+
const base = classificationFor(resolvedKind);
|
|
1125
|
+
const phase = lifecycleLikePhase(resolvedKind);
|
|
1126
|
+
const isLifecycle = base?.handlerKind === "entity_lifecycle" || Boolean(phase && !base);
|
|
1127
|
+
if (!handlerClass && isLifecycle) return void 0;
|
|
1128
|
+
const parsed = isLifecycle && (!allowLifecycle || !importedKind) ? unsupportedDecoratorResolution(
|
|
1129
|
+
decorator,
|
|
1130
|
+
"lifecycle_decorator_import_not_supported",
|
|
1131
|
+
phase
|
|
1132
|
+
) : base?.handlerKind === "entity_lifecycle" ? lifecycleDecoratorResolution(decorator, lookups, base) : phase && !base ? unsupportedLifecycleResolution(decorator, phase) : !base && handlerClass ? unsupportedDecoratorResolution(decorator, "decorator_not_allowlisted") : {
|
|
1133
|
+
classification: base,
|
|
1134
|
+
resolution: resolveDecoratorArgument(
|
|
1135
|
+
firstArg(decorator),
|
|
1136
|
+
lookups,
|
|
1137
|
+
firstArg(decorator)?.getText() ?? decorator.expression.getText()
|
|
1138
|
+
)
|
|
1139
|
+
};
|
|
1140
|
+
if (!parsed.classification) return void 0;
|
|
1141
|
+
return {
|
|
1142
|
+
classification: parsed.classification,
|
|
1143
|
+
resolution: parsed.resolution,
|
|
1144
|
+
decoratorKind,
|
|
1145
|
+
importedKind
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
function methodDecoratorFact(method, decorator, lookups, source, filePath, handlerClass, allowLifecycle) {
|
|
1149
|
+
const parsed = parseMethodDecorator(decorator, lookups, handlerClass, allowLifecycle);
|
|
1150
|
+
if (!parsed) return void 0;
|
|
1151
|
+
const classification = method.body ? parsed.classification : { ...parsed.classification, executable: false };
|
|
1152
|
+
const resolution = method.body ? parsed.resolution : { ...parsed.resolution, unresolvedReason: "handler_method_body_missing" };
|
|
1153
|
+
const argumentExpression = firstArg(decorator)?.getText();
|
|
1154
|
+
return {
|
|
1155
|
+
methodName: methodName(method.name),
|
|
1156
|
+
decoratorKind: parsed.decoratorKind,
|
|
1157
|
+
decoratorValue: parsed.resolution.resolvedValue,
|
|
1158
|
+
decoratorRawExpression: argumentExpression ?? decorator.expression.getText(),
|
|
1159
|
+
handlerKind: classification.handlerKind,
|
|
1160
|
+
executable: classification.executable,
|
|
1161
|
+
lifecyclePhase: classification.lifecyclePhase,
|
|
1162
|
+
lifecycleEvent: classification.lifecycleEvent,
|
|
1163
|
+
decoratorResolution: withDecoratorEvidence(
|
|
1164
|
+
withClassification(resolution, classification),
|
|
1165
|
+
decorator,
|
|
1166
|
+
parsed.importedKind
|
|
1167
|
+
),
|
|
1168
|
+
sourceFile: normalizePath(filePath),
|
|
1169
|
+
sourceLine: line(source, method.getStart())
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
function unique(values) {
|
|
1173
|
+
return [...new Set(values)];
|
|
1174
|
+
}
|
|
1175
|
+
function parseClassMethods(node, lookups, source, filePath, handlerClass, allowLifecycle) {
|
|
1176
|
+
const methods = [];
|
|
1177
|
+
const observed = [];
|
|
1178
|
+
const unsupported = [];
|
|
1179
|
+
for (const method of node.members.filter(ts2.isMethodDeclaration)) {
|
|
1180
|
+
for (const decorator of decs(method)) {
|
|
1181
|
+
observed.push(callName(decorator));
|
|
1182
|
+
const fact = methodDecoratorFact(
|
|
1183
|
+
method,
|
|
1184
|
+
decorator,
|
|
1185
|
+
lookups,
|
|
1186
|
+
source,
|
|
1187
|
+
filePath,
|
|
1188
|
+
handlerClass,
|
|
1189
|
+
allowLifecycle
|
|
1190
|
+
);
|
|
1191
|
+
if (fact) methods.push(fact);
|
|
1192
|
+
if (!fact?.executable) unsupported.push(callName(decorator));
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
return {
|
|
1196
|
+
methods,
|
|
1197
|
+
observedDecoratorNames: unique(observed),
|
|
1198
|
+
unsupportedDecoratorNames: unique(unsupported)
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
function parseHandlerClass(node, lookups, source, filePath) {
|
|
1202
|
+
const classDecoratorNames = unique(decs(node).map(callName));
|
|
1203
|
+
const classDecorators = decs(node);
|
|
1204
|
+
const hasImportedHandler = classDecorators.some((decorator) => capDecoratorName(decorator, lookups) === "Handler");
|
|
1205
|
+
const hasHandlerDecorator = hasImportedHandler || classDecoratorNames.includes("Handler");
|
|
1206
|
+
const parsed = parseClassMethods(
|
|
1207
|
+
node,
|
|
1208
|
+
lookups,
|
|
1209
|
+
source,
|
|
1210
|
+
filePath,
|
|
1211
|
+
hasHandlerDecorator,
|
|
1212
|
+
hasImportedHandler
|
|
1213
|
+
);
|
|
1214
|
+
if (!hasHandlerDecorator && parsed.methods.length === 0) return void 0;
|
|
1215
|
+
return {
|
|
1216
|
+
className: node.name?.text ?? "AnonymousHandler",
|
|
1217
|
+
sourceFile: normalizePath(filePath),
|
|
1218
|
+
sourceLine: line(source, node.getStart()),
|
|
1219
|
+
...parsed,
|
|
1220
|
+
hasHandlerDecorator,
|
|
1221
|
+
classDecoratorNames
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
async function parseDecorators(repoPath, filePath, context) {
|
|
1225
|
+
const snapshot = context?.get(filePath);
|
|
1226
|
+
const text = snapshot?.text ?? await fs5.readFile(path6.join(repoPath, filePath), "utf8");
|
|
1227
|
+
const sf = snapshot?.sourceFile() ?? createSourceFile(filePath, text);
|
|
476
1228
|
const lookups = collectStringLookups(sf);
|
|
477
1229
|
const handlers = [];
|
|
478
1230
|
function visit(node) {
|
|
479
1231
|
if (ts2.isClassDeclaration(node)) {
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
const methods = node.members.filter(ts2.isMethodDeclaration).flatMap(
|
|
483
|
-
(m) => decs(m).filter(
|
|
484
|
-
(d) => ["Func", "Action", "On", "Event"].includes(callName(d))
|
|
485
|
-
).map((d) => {
|
|
486
|
-
const decoratorResolution = resolveDecoratorArgument(firstArg(d), lookups);
|
|
487
|
-
return {
|
|
488
|
-
methodName: m.name.getText(),
|
|
489
|
-
decoratorKind: callName(d),
|
|
490
|
-
decoratorValue: decoratorResolution.resolvedValue,
|
|
491
|
-
decoratorRawExpression: decoratorResolution.rawExpression,
|
|
492
|
-
decoratorResolution,
|
|
493
|
-
sourceFile: normalizePath(filePath),
|
|
494
|
-
sourceLine: line(sf, m.getStart())
|
|
495
|
-
};
|
|
496
|
-
})
|
|
497
|
-
);
|
|
498
|
-
if (hasHandler || methods.length > 0)
|
|
499
|
-
handlers.push({
|
|
500
|
-
className,
|
|
501
|
-
sourceFile: normalizePath(filePath),
|
|
502
|
-
sourceLine: line(sf, node.getStart()),
|
|
503
|
-
methods
|
|
504
|
-
});
|
|
1232
|
+
const handler = parseHandlerClass(node, lookups, sf, filePath);
|
|
1233
|
+
if (handler) handlers.push(handler);
|
|
505
1234
|
}
|
|
506
1235
|
ts2.forEachChild(node, visit);
|
|
507
1236
|
}
|
|
@@ -511,8 +1240,8 @@ async function parseDecorators(repoPath, filePath) {
|
|
|
511
1240
|
|
|
512
1241
|
// src/parsers/handler-registration-parser.ts
|
|
513
1242
|
import fsSync from "fs";
|
|
514
|
-
import
|
|
515
|
-
import
|
|
1243
|
+
import fs6 from "fs/promises";
|
|
1244
|
+
import path7 from "path";
|
|
516
1245
|
import ts3 from "typescript";
|
|
517
1246
|
var MAX_EXPORT_DEPTH = 5;
|
|
518
1247
|
function lineOf2(sourceFile, node) {
|
|
@@ -529,15 +1258,37 @@ function importSourceFor(identifier, imports) {
|
|
|
529
1258
|
const evidence = imports.get(identifier);
|
|
530
1259
|
return evidence ? `${evidence.source}#${evidence.importedName}` : void 0;
|
|
531
1260
|
}
|
|
532
|
-
async function parseHandlerRegistrations(repoPath, filePath) {
|
|
533
|
-
const absolutePath =
|
|
534
|
-
const
|
|
535
|
-
const
|
|
1261
|
+
async function parseHandlerRegistrations(repoPath, filePath, context) {
|
|
1262
|
+
const absolutePath = path7.join(repoPath, filePath);
|
|
1263
|
+
const snapshot = context?.get(filePath);
|
|
1264
|
+
const text = snapshot?.text ?? await fs6.readFile(absolutePath, "utf8");
|
|
1265
|
+
const sourceFile = snapshot?.sourceFile() ?? ts3.createSourceFile(
|
|
1266
|
+
filePath,
|
|
1267
|
+
text,
|
|
1268
|
+
ts3.ScriptTarget.Latest,
|
|
1269
|
+
true,
|
|
1270
|
+
ts3.ScriptKind.TS
|
|
1271
|
+
);
|
|
536
1272
|
const imports = collectImports(sourceFile);
|
|
537
|
-
const localArrays = collectLocalArrays(
|
|
1273
|
+
const localArrays = collectLocalArrays(
|
|
1274
|
+
sourceFile,
|
|
1275
|
+
imports,
|
|
1276
|
+
/* @__PURE__ */ new Map(),
|
|
1277
|
+
repoPath,
|
|
1278
|
+
filePath,
|
|
1279
|
+
context
|
|
1280
|
+
);
|
|
538
1281
|
const out = [];
|
|
539
1282
|
function emitFromExpression(expression, call) {
|
|
540
|
-
const classes = resolveArrayExpression(
|
|
1283
|
+
const classes = resolveArrayExpression(
|
|
1284
|
+
expression,
|
|
1285
|
+
localArrays,
|
|
1286
|
+
imports,
|
|
1287
|
+
repoPath,
|
|
1288
|
+
filePath,
|
|
1289
|
+
/* @__PURE__ */ new Set(),
|
|
1290
|
+
context
|
|
1291
|
+
);
|
|
541
1292
|
for (const cls of classes) {
|
|
542
1293
|
out.push({
|
|
543
1294
|
className: cls.className,
|
|
@@ -598,70 +1349,86 @@ function collectImports(sourceFile) {
|
|
|
598
1349
|
}
|
|
599
1350
|
return imports;
|
|
600
1351
|
}
|
|
601
|
-
function collectLocalArrays(sourceFile, imports, seed, repoPath = "", fromFile = "") {
|
|
1352
|
+
function collectLocalArrays(sourceFile, imports, seed, repoPath = "", fromFile = "", context) {
|
|
602
1353
|
const arrays = new Map(seed);
|
|
603
1354
|
for (const statement of sourceFile.statements) {
|
|
604
1355
|
if (ts3.isVariableStatement(statement)) {
|
|
605
1356
|
for (const decl of statement.declarationList.declarations) {
|
|
606
1357
|
if (ts3.isIdentifier(decl.name) && decl.initializer && ts3.isArrayLiteralExpression(decl.initializer)) {
|
|
607
|
-
arrays.set(decl.name.text, resolveArrayLiteral(
|
|
1358
|
+
arrays.set(decl.name.text, resolveArrayLiteral(
|
|
1359
|
+
decl.initializer,
|
|
1360
|
+
arrays,
|
|
1361
|
+
imports,
|
|
1362
|
+
repoPath,
|
|
1363
|
+
fromFile,
|
|
1364
|
+
/* @__PURE__ */ new Set(),
|
|
1365
|
+
context
|
|
1366
|
+
));
|
|
608
1367
|
}
|
|
609
1368
|
}
|
|
610
1369
|
}
|
|
611
1370
|
}
|
|
612
1371
|
return arrays;
|
|
613
1372
|
}
|
|
614
|
-
function resolveArrayExpression(expr, arrays, imports, repoPath, fromFile, seen) {
|
|
615
|
-
if (ts3.isArrayLiteralExpression(expr)) return resolveArrayLiteral(expr, arrays, imports, repoPath, fromFile, seen);
|
|
1373
|
+
function resolveArrayExpression(expr, arrays, imports, repoPath, fromFile, seen, context) {
|
|
1374
|
+
if (ts3.isArrayLiteralExpression(expr)) return resolveArrayLiteral(expr, arrays, imports, repoPath, fromFile, seen, context);
|
|
616
1375
|
if (ts3.isIdentifier(expr)) {
|
|
617
1376
|
const local = arrays.get(expr.text);
|
|
618
1377
|
if (local) return local;
|
|
619
1378
|
const evidence = imports.get(expr.text);
|
|
620
|
-
if (evidence && isRelative(evidence.source)) return resolveImportedArray(repoPath, fromFile, evidence, seen);
|
|
1379
|
+
if (evidence && isRelative(evidence.source)) return resolveImportedArray(repoPath, fromFile, evidence, seen, context);
|
|
621
1380
|
if (evidence) return [{ className: evidence.importedName === "default" ? expr.text : evidence.importedName, importSource: `${evidence.source}#${evidence.importedName}` }];
|
|
622
1381
|
}
|
|
623
1382
|
return [];
|
|
624
1383
|
}
|
|
625
|
-
function resolveArrayLiteral(array, arrays, imports, repoPath, fromFile, seen) {
|
|
1384
|
+
function resolveArrayLiteral(array, arrays, imports, repoPath, fromFile, seen, context) {
|
|
626
1385
|
const out = [];
|
|
627
1386
|
for (const element of array.elements) {
|
|
628
|
-
if (ts3.isSpreadElement(element)) out.push(...resolveArrayExpression(element.expression, arrays, imports, repoPath, fromFile, seen));
|
|
1387
|
+
if (ts3.isSpreadElement(element)) out.push(...resolveArrayExpression(element.expression, arrays, imports, repoPath, fromFile, seen, context));
|
|
629
1388
|
else if (ts3.isIdentifier(element)) out.push({ className: element.text, importSource: importSourceFor(element.text, imports) });
|
|
630
1389
|
}
|
|
631
1390
|
return out;
|
|
632
1391
|
}
|
|
633
|
-
function resolveImportedArray(repoPath, fromFile, evidence, seen) {
|
|
1392
|
+
function resolveImportedArray(repoPath, fromFile, evidence, seen, context) {
|
|
634
1393
|
const moduleFile = resolveRelativeModule(repoPath, fromFile, evidence.source);
|
|
635
1394
|
if (!moduleFile) return [];
|
|
636
1395
|
const key = `${moduleFile}:${evidence.importedName}`;
|
|
637
1396
|
if (seen.has(key) || seen.size > MAX_EXPORT_DEPTH) return [];
|
|
638
1397
|
seen.add(key);
|
|
639
|
-
const exports = readExports(repoPath, moduleFile, seen);
|
|
1398
|
+
const exports = readExports(repoPath, moduleFile, seen, context);
|
|
640
1399
|
if (evidence.importedName === "default") return exports.defaultArray ?? [];
|
|
641
1400
|
return exports.arrays.get(evidence.importedName) ?? exports.arrays.get(exports.aliases.get(evidence.importedName) ?? evidence.importedName) ?? [];
|
|
642
1401
|
}
|
|
643
1402
|
function resolveRelativeModule(repoPath, fromFile, specifier) {
|
|
644
|
-
const base =
|
|
645
|
-
for (const candidate of [base, `${base}.ts`, `${base}.js`,
|
|
1403
|
+
const base = path7.resolve(repoPath, path7.dirname(fromFile), specifier);
|
|
1404
|
+
for (const candidate of [base, `${base}.ts`, `${base}.js`, path7.join(base, "index.ts"), path7.join(base, "index.js")]) {
|
|
646
1405
|
try {
|
|
647
1406
|
const stat = fsSync.statSync(candidate);
|
|
648
|
-
if (stat.isFile()) return normalizePath(
|
|
1407
|
+
if (stat.isFile()) return normalizePath(path7.relative(repoPath, candidate));
|
|
649
1408
|
} catch {
|
|
650
1409
|
}
|
|
651
1410
|
}
|
|
652
1411
|
return void 0;
|
|
653
1412
|
}
|
|
654
|
-
function readExports(repoPath, filePath, seen) {
|
|
655
|
-
const absolute =
|
|
1413
|
+
function readExports(repoPath, filePath, seen, context) {
|
|
1414
|
+
const absolute = path7.join(repoPath, filePath);
|
|
656
1415
|
let text;
|
|
1416
|
+
const snapshot = context?.get(filePath);
|
|
657
1417
|
try {
|
|
658
|
-
text = fsSync.readFileSync(absolute, "utf8");
|
|
1418
|
+
text = snapshot?.text ?? fsSync.readFileSync(absolute, "utf8");
|
|
659
1419
|
} catch {
|
|
660
1420
|
return { arrays: /* @__PURE__ */ new Map(), aliases: /* @__PURE__ */ new Map() };
|
|
661
1421
|
}
|
|
662
|
-
const sourceFile = ts3.createSourceFile(filePath, text, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TS);
|
|
1422
|
+
const sourceFile = snapshot?.sourceFile() ?? ts3.createSourceFile(filePath, text, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TS);
|
|
663
1423
|
const imports = collectImports(sourceFile);
|
|
664
|
-
const arrays = collectLocalArrays(
|
|
1424
|
+
const arrays = collectLocalArrays(
|
|
1425
|
+
sourceFile,
|
|
1426
|
+
imports,
|
|
1427
|
+
/* @__PURE__ */ new Map(),
|
|
1428
|
+
repoPath,
|
|
1429
|
+
filePath,
|
|
1430
|
+
context
|
|
1431
|
+
);
|
|
665
1432
|
const aliases = /* @__PURE__ */ new Map();
|
|
666
1433
|
let defaultArray;
|
|
667
1434
|
for (const statement of sourceFile.statements) {
|
|
@@ -672,7 +1439,13 @@ function readExports(repoPath, filePath, seen) {
|
|
|
672
1439
|
const local = element.propertyName?.text ?? element.name.text;
|
|
673
1440
|
aliases.set(element.name.text, local);
|
|
674
1441
|
if (module && isRelative(module)) {
|
|
675
|
-
const imported = resolveImportedArray(
|
|
1442
|
+
const imported = resolveImportedArray(
|
|
1443
|
+
repoPath,
|
|
1444
|
+
filePath,
|
|
1445
|
+
{ source: module, importedName: local },
|
|
1446
|
+
seen,
|
|
1447
|
+
context
|
|
1448
|
+
);
|
|
676
1449
|
if (imported.length > 0) arrays.set(element.name.text, imported);
|
|
677
1450
|
}
|
|
678
1451
|
}
|
|
@@ -704,12 +1477,12 @@ function summarizeExpression(text) {
|
|
|
704
1477
|
}
|
|
705
1478
|
|
|
706
1479
|
// src/parsers/service-binding-parser.ts
|
|
707
|
-
import
|
|
1480
|
+
import path9 from "path";
|
|
708
1481
|
import ts5 from "typescript";
|
|
709
1482
|
|
|
710
1483
|
// src/parsers/service-binding-parser-helpers.ts
|
|
711
|
-
import
|
|
712
|
-
import
|
|
1484
|
+
import fs7 from "fs/promises";
|
|
1485
|
+
import path8 from "path";
|
|
713
1486
|
import ts4 from "typescript";
|
|
714
1487
|
function lineOf3(sf, node) {
|
|
715
1488
|
return sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
@@ -795,9 +1568,11 @@ function findConnectInExpression(expr) {
|
|
|
795
1568
|
visit(expr);
|
|
796
1569
|
return found;
|
|
797
1570
|
}
|
|
798
|
-
async function readSource(abs) {
|
|
1571
|
+
async function readSource(abs, context, filePath) {
|
|
1572
|
+
const snapshot = filePath ? context?.get(filePath) : void 0;
|
|
1573
|
+
if (snapshot) return snapshot.sourceFile();
|
|
799
1574
|
try {
|
|
800
|
-
const text = await
|
|
1575
|
+
const text = await fs7.readFile(abs, "utf8");
|
|
801
1576
|
return ts4.createSourceFile(abs, text, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TS);
|
|
802
1577
|
} catch {
|
|
803
1578
|
return void 0;
|
|
@@ -805,12 +1580,12 @@ async function readSource(abs) {
|
|
|
805
1580
|
}
|
|
806
1581
|
async function resolveImport(repoPath, fromFile, spec) {
|
|
807
1582
|
if (!spec.startsWith(".")) return void 0;
|
|
808
|
-
const rawBase =
|
|
809
|
-
const parsed =
|
|
810
|
-
const base = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"].includes(parsed.ext) ?
|
|
811
|
-
for (const candidate of [base, `${base}.ts`, `${base}.js`,
|
|
812
|
-
const stat = await
|
|
813
|
-
if (stat?.isFile()) return normalizePath(
|
|
1583
|
+
const rawBase = path8.resolve(repoPath, path8.dirname(fromFile), spec);
|
|
1584
|
+
const parsed = path8.parse(rawBase);
|
|
1585
|
+
const base = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"].includes(parsed.ext) ? path8.join(parsed.dir, parsed.name) : rawBase;
|
|
1586
|
+
for (const candidate of [base, `${base}.ts`, `${base}.js`, path8.join(base, "index.ts"), path8.join(base, "index.js")]) {
|
|
1587
|
+
const stat = await fs7.stat(candidate).catch(() => void 0);
|
|
1588
|
+
if (stat?.isFile()) return normalizePath(path8.relative(repoPath, candidate));
|
|
814
1589
|
}
|
|
815
1590
|
return void 0;
|
|
816
1591
|
}
|
|
@@ -918,8 +1693,8 @@ function exportedLocalNames(sf) {
|
|
|
918
1693
|
}
|
|
919
1694
|
return exports;
|
|
920
1695
|
}
|
|
921
|
-
async function helperBindings(repoPath, filePath) {
|
|
922
|
-
const sf = await readSource(
|
|
1696
|
+
async function helperBindings(repoPath, filePath, context) {
|
|
1697
|
+
const sf = await readSource(path9.join(repoPath, filePath), context, filePath);
|
|
923
1698
|
if (!sf) return [];
|
|
924
1699
|
const sourceFileAst = sf;
|
|
925
1700
|
const out = [];
|
|
@@ -979,8 +1754,8 @@ async function helperBindings(repoPath, filePath) {
|
|
|
979
1754
|
}
|
|
980
1755
|
return out;
|
|
981
1756
|
}
|
|
982
|
-
async function parseServiceBindings(repoPath, filePath) {
|
|
983
|
-
const sf = await readSource(
|
|
1757
|
+
async function parseServiceBindings(repoPath, filePath, context) {
|
|
1758
|
+
const sf = await readSource(path9.join(repoPath, filePath), context, filePath);
|
|
984
1759
|
if (!sf) return [];
|
|
985
1760
|
const sourceFileAst = sf;
|
|
986
1761
|
const out = [];
|
|
@@ -1019,7 +1794,7 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1019
1794
|
if (!helperCache.has(imp.sourceFile))
|
|
1020
1795
|
helperCache.set(
|
|
1021
1796
|
imp.sourceFile,
|
|
1022
|
-
await helperBindings(repoPath, imp.sourceFile)
|
|
1797
|
+
await helperBindings(repoPath, imp.sourceFile, context)
|
|
1023
1798
|
);
|
|
1024
1799
|
return (helperCache.get(imp.sourceFile) ?? []).filter((h) => h.exportedName === imp.exportedName).map((helper) => ({ imp, helper }));
|
|
1025
1800
|
}
|
|
@@ -1069,28 +1844,28 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1069
1844
|
});
|
|
1070
1845
|
else if (ts5.isIdentifier(call.expression)) {
|
|
1071
1846
|
const localDirect = localDirectHelpers.get(call.expression.text);
|
|
1072
|
-
const
|
|
1073
|
-
if (
|
|
1847
|
+
const resolved3 = localDirect ? { helper: localDirect, imp: void 0 } : await importedHelper(call.expression.text);
|
|
1848
|
+
if (resolved3)
|
|
1074
1849
|
out.push({
|
|
1075
1850
|
variableName: targetName,
|
|
1076
|
-
alias:
|
|
1077
|
-
aliasExpr:
|
|
1078
|
-
destinationExpr:
|
|
1079
|
-
servicePathExpr:
|
|
1080
|
-
isDynamic:
|
|
1081
|
-
placeholders:
|
|
1851
|
+
alias: resolved3.helper.alias,
|
|
1852
|
+
aliasExpr: resolved3.helper.aliasExpr,
|
|
1853
|
+
destinationExpr: resolved3.helper.destinationExpr,
|
|
1854
|
+
servicePathExpr: resolved3.helper.servicePathExpr,
|
|
1855
|
+
isDynamic: resolved3.helper.isDynamic,
|
|
1856
|
+
placeholders: resolved3.helper.placeholders,
|
|
1082
1857
|
sourceFile: normalizePath(filePath),
|
|
1083
1858
|
sourceLine: lineOf3(sourceFileAst, node),
|
|
1084
1859
|
helperChain: [
|
|
1085
|
-
...
|
|
1860
|
+
...resolved3.helper.helperChain ?? [],
|
|
1086
1861
|
{
|
|
1087
1862
|
callerVariable: targetName,
|
|
1088
1863
|
...aliasKind === "assignment" ? { assignedFrom: call.expression.text, aliasKind, scopeRule: "same-file-source-order" } : {},
|
|
1089
1864
|
importedHelper: call.expression.text,
|
|
1090
|
-
importSource:
|
|
1091
|
-
exportedSymbol:
|
|
1092
|
-
helperSourceFile:
|
|
1093
|
-
helperSourceLine:
|
|
1865
|
+
importSource: resolved3.imp?.sourceFile,
|
|
1866
|
+
exportedSymbol: resolved3.imp?.exportedName ?? resolved3.helper.exportedName,
|
|
1867
|
+
helperSourceFile: resolved3.helper.sourceFile,
|
|
1868
|
+
helperSourceLine: resolved3.helper.sourceLine
|
|
1094
1869
|
}
|
|
1095
1870
|
]
|
|
1096
1871
|
});
|
|
@@ -1119,28 +1894,28 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1119
1894
|
const helpers = objectHelperVariables.get(unwrapped.expression.text) ?? [];
|
|
1120
1895
|
const matches2 = helpers.filter((row) => row.helper.returnedProperty === unwrapped.name.text);
|
|
1121
1896
|
if (matches2.length !== 1) return false;
|
|
1122
|
-
const
|
|
1897
|
+
const resolved3 = matches2[0];
|
|
1123
1898
|
out.push({
|
|
1124
1899
|
variableName: targetName,
|
|
1125
|
-
alias:
|
|
1126
|
-
aliasExpr:
|
|
1127
|
-
destinationExpr:
|
|
1128
|
-
servicePathExpr:
|
|
1129
|
-
isDynamic:
|
|
1130
|
-
placeholders:
|
|
1900
|
+
alias: resolved3.helper.alias,
|
|
1901
|
+
aliasExpr: resolved3.helper.aliasExpr,
|
|
1902
|
+
destinationExpr: resolved3.helper.destinationExpr,
|
|
1903
|
+
servicePathExpr: resolved3.helper.servicePathExpr,
|
|
1904
|
+
isDynamic: resolved3.helper.isDynamic,
|
|
1905
|
+
placeholders: resolved3.helper.placeholders,
|
|
1131
1906
|
sourceFile: normalizePath(filePath),
|
|
1132
1907
|
sourceLine: lineOf3(sourceFileAst, node),
|
|
1133
1908
|
helperChain: [
|
|
1134
|
-
...
|
|
1909
|
+
...resolved3.helper.helperChain ?? [],
|
|
1135
1910
|
{
|
|
1136
1911
|
callerVariable: targetName,
|
|
1137
1912
|
sourceVariable: unwrapped.expression.text,
|
|
1138
1913
|
returnedProperty: unwrapped.name.text,
|
|
1139
1914
|
assignedFromProperty: unwrapped.getText(sourceFileAst),
|
|
1140
|
-
importSource:
|
|
1141
|
-
exportedSymbol:
|
|
1142
|
-
helperSourceFile:
|
|
1143
|
-
helperSourceLine:
|
|
1915
|
+
importSource: resolved3.imp?.sourceFile,
|
|
1916
|
+
exportedSymbol: resolved3.imp?.exportedName,
|
|
1917
|
+
helperSourceFile: resolved3.helper.sourceFile,
|
|
1918
|
+
helperSourceLine: resolved3.helper.sourceLine
|
|
1144
1919
|
}
|
|
1145
1920
|
]
|
|
1146
1921
|
});
|
|
@@ -1157,18 +1932,18 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1157
1932
|
const propertyName3 = el.propertyName && ts5.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
|
|
1158
1933
|
const matches2 = helpers.filter((row) => row.helper.returnedProperty === propertyName3);
|
|
1159
1934
|
if (matches2.length !== 1) continue;
|
|
1160
|
-
const
|
|
1935
|
+
const resolved3 = matches2[0];
|
|
1161
1936
|
out.push({
|
|
1162
1937
|
variableName: el.name.text,
|
|
1163
|
-
alias:
|
|
1164
|
-
aliasExpr:
|
|
1165
|
-
destinationExpr:
|
|
1166
|
-
servicePathExpr:
|
|
1167
|
-
isDynamic:
|
|
1168
|
-
placeholders:
|
|
1938
|
+
alias: resolved3.helper.alias,
|
|
1939
|
+
aliasExpr: resolved3.helper.aliasExpr,
|
|
1940
|
+
destinationExpr: resolved3.helper.destinationExpr,
|
|
1941
|
+
servicePathExpr: resolved3.helper.servicePathExpr,
|
|
1942
|
+
isDynamic: resolved3.helper.isDynamic,
|
|
1943
|
+
placeholders: resolved3.helper.placeholders,
|
|
1169
1944
|
sourceFile: normalizePath(filePath),
|
|
1170
1945
|
sourceLine: lineOf3(sourceFileAst, decl),
|
|
1171
|
-
helperChain: [...
|
|
1946
|
+
helperChain: [...resolved3.helper.helperChain ?? [], { callerVariable: el.name.text, helperFunction: call.expression.getText(sourceFileAst), returnedProperty: propertyName3, importSource: resolved3.imp?.sourceFile, exportedSymbol: resolved3.imp?.exportedName, helperSourceFile: resolved3.helper.sourceFile, helperSourceLine: resolved3.helper.sourceLine }]
|
|
1172
1947
|
});
|
|
1173
1948
|
}
|
|
1174
1949
|
}
|
|
@@ -1190,18 +1965,18 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1190
1965
|
if (!propertyName3 || !targetName) continue;
|
|
1191
1966
|
const matches2 = helpers.filter((row) => row.helper.returnedProperty === propertyName3);
|
|
1192
1967
|
if (matches2.length !== 1) continue;
|
|
1193
|
-
const
|
|
1968
|
+
const resolved3 = matches2[0];
|
|
1194
1969
|
out.push({
|
|
1195
1970
|
variableName: targetName,
|
|
1196
|
-
alias:
|
|
1197
|
-
aliasExpr:
|
|
1198
|
-
destinationExpr:
|
|
1199
|
-
servicePathExpr:
|
|
1200
|
-
isDynamic:
|
|
1201
|
-
placeholders:
|
|
1971
|
+
alias: resolved3.helper.alias,
|
|
1972
|
+
aliasExpr: resolved3.helper.aliasExpr,
|
|
1973
|
+
destinationExpr: resolved3.helper.destinationExpr,
|
|
1974
|
+
servicePathExpr: resolved3.helper.servicePathExpr,
|
|
1975
|
+
isDynamic: resolved3.helper.isDynamic,
|
|
1976
|
+
placeholders: resolved3.helper.placeholders,
|
|
1202
1977
|
sourceFile: normalizePath(filePath),
|
|
1203
1978
|
sourceLine: lineOf3(sourceFileAst, node),
|
|
1204
|
-
helperChain: [...
|
|
1979
|
+
helperChain: [...resolved3.helper.helperChain ?? [], { callerVariable: targetName, assignedFrom: call.expression.getText(sourceFileAst), aliasKind: "assignment", scopeRule: "same-file-source-order", returnedProperty: propertyName3, importSource: resolved3.imp?.sourceFile, exportedSymbol: resolved3.imp?.exportedName, helperSourceFile: resolved3.helper.sourceFile, helperSourceLine: resolved3.helper.sourceLine }]
|
|
1205
1980
|
});
|
|
1206
1981
|
}
|
|
1207
1982
|
}
|
|
@@ -1400,8 +2175,8 @@ function collectClassHelpers(sf) {
|
|
|
1400
2175
|
}
|
|
1401
2176
|
|
|
1402
2177
|
// src/parsers/outbound-call-parser.ts
|
|
1403
|
-
import
|
|
1404
|
-
import
|
|
2178
|
+
import fs8 from "fs/promises";
|
|
2179
|
+
import path11 from "path";
|
|
1405
2180
|
import ts8 from "typescript";
|
|
1406
2181
|
|
|
1407
2182
|
// src/linker/external-http-target.ts
|
|
@@ -1419,8 +2194,8 @@ function redactUrl(value) {
|
|
|
1419
2194
|
url.username = "";
|
|
1420
2195
|
url.password = "";
|
|
1421
2196
|
for (const key of [...url.searchParams.keys()]) url.searchParams.set(key, sensitiveKeys.has(key.toLowerCase()) ? "<redacted>" : "<redacted>");
|
|
1422
|
-
const
|
|
1423
|
-
return value.startsWith("/") ?
|
|
2197
|
+
const path12 = `${url.pathname}${url.search ? url.search : ""}`;
|
|
2198
|
+
return value.startsWith("/") ? path12 : `${url.origin}${path12}`;
|
|
1424
2199
|
} catch {
|
|
1425
2200
|
return value.replace(/([?&][^=;&]*(?:token|key|password|secret|cookie|authorization)[^=;&]*=)[^&]*/gi, "$1<redacted>");
|
|
1426
2201
|
}
|
|
@@ -1454,9 +2229,9 @@ function safeParse(value) {
|
|
|
1454
2229
|
}
|
|
1455
2230
|
|
|
1456
2231
|
// src/linker/odata-path-normalizer.ts
|
|
1457
|
-
function normalizeODataOperationInvocationPath(
|
|
1458
|
-
if (
|
|
1459
|
-
const raw =
|
|
2232
|
+
function normalizeODataOperationInvocationPath(path12) {
|
|
2233
|
+
if (path12 === void 0) return void 0;
|
|
2234
|
+
const raw = path12.trim();
|
|
1460
2235
|
if (!raw) return void 0;
|
|
1461
2236
|
const rejected = (reason) => ({ rawOperationPath: raw, normalizedOperationPath: raw, wasInvocation: false, invocationArgumentPlaceholderKeys: [], normalizationRejectedReason: reason });
|
|
1462
2237
|
const open = raw.indexOf("(");
|
|
@@ -1479,8 +2254,8 @@ function normalizeODataOperationInvocationPath(path11) {
|
|
|
1479
2254
|
normalizationReason: "balanced_top_level_operation_invocation"
|
|
1480
2255
|
};
|
|
1481
2256
|
}
|
|
1482
|
-
function classifyODataPathIntent(
|
|
1483
|
-
const rawPath = (
|
|
2257
|
+
function classifyODataPathIntent(path12, method) {
|
|
2258
|
+
const rawPath = (path12 ?? "").trim();
|
|
1484
2259
|
const normalizedMethod = (method ?? "GET").trim().toUpperCase() || "GET";
|
|
1485
2260
|
const queryIndex = rawPath.indexOf("?");
|
|
1486
2261
|
const pathWithoutQuery = queryIndex >= 0 ? rawPath.slice(0, queryIndex) : rawPath;
|
|
@@ -1528,8 +2303,8 @@ function classifyODataPathIntent(path11, method) {
|
|
|
1528
2303
|
if (/^[A-Z][A-Za-z0-9_]*$/.test(firstSegment)) return { ...base, kind: "entity_candidate", reason: "uppercase_collection_segment_without_indexed_entity_evidence" };
|
|
1529
2304
|
return { ...base, kind: "unknown", reason: "get_path_has_no_query_key_or_navigation_signal" };
|
|
1530
2305
|
}
|
|
1531
|
-
function entitySegmentFromPath(
|
|
1532
|
-
const first =
|
|
2306
|
+
function entitySegmentFromPath(path12) {
|
|
2307
|
+
const first = path12.replace(/^\//, "").split("/")[0]?.trim();
|
|
1533
2308
|
if (!first) return void 0;
|
|
1534
2309
|
const open = first.indexOf("(");
|
|
1535
2310
|
const entity = (open >= 0 ? first.slice(0, open) : first).trim();
|
|
@@ -1677,7 +2452,7 @@ function topLevelQueryIndex(text) {
|
|
|
1677
2452
|
}
|
|
1678
2453
|
|
|
1679
2454
|
// src/parsers/imported-wrapper-parser.ts
|
|
1680
|
-
import
|
|
2455
|
+
import path10 from "path";
|
|
1681
2456
|
import ts7 from "typescript";
|
|
1682
2457
|
|
|
1683
2458
|
// src/parsers/operation-path-analysis.ts
|
|
@@ -1686,8 +2461,8 @@ var maxAliasDepth = 6;
|
|
|
1686
2461
|
function analyzeOperationPath(expression, use, method = "POST") {
|
|
1687
2462
|
if (!expression) return emptyAnalysis();
|
|
1688
2463
|
const state = collectExpressionState(expression, use, 0, /* @__PURE__ */ new Set());
|
|
1689
|
-
const paths =
|
|
1690
|
-
const normalized =
|
|
2464
|
+
const paths = unique2(state.paths.map(normalizeRawPath));
|
|
2465
|
+
const normalized = unique2(paths.flatMap((value) => normalizedCandidate(value, method)));
|
|
1691
2466
|
const status = pathStatus(paths, state.placeholders, state.dynamic);
|
|
1692
2467
|
const runtimeIdentifier = state.dynamic.at(-1)?.expression;
|
|
1693
2468
|
return {
|
|
@@ -1696,8 +2471,8 @@ function analyzeOperationPath(expression, use, method = "POST") {
|
|
|
1696
2471
|
normalizedOperationPath: status === "static" && normalized.length === 1 ? normalized[0] : void 0,
|
|
1697
2472
|
candidateRawPaths: paths,
|
|
1698
2473
|
candidateNormalizedOperationPaths: normalized,
|
|
1699
|
-
placeholderKeys:
|
|
1700
|
-
sourceKind:
|
|
2474
|
+
placeholderKeys: unique2([...state.placeholders, ...runtimeIdentifier ? [runtimeIdentifier] : []]),
|
|
2475
|
+
sourceKind: unique2(state.sourceKinds).join("+") || "unknown",
|
|
1701
2476
|
candidateIdentifier: ts6.isIdentifier(expression) ? expression.text : void 0,
|
|
1702
2477
|
runtimeIdentifier,
|
|
1703
2478
|
dynamicReassignments: state.dynamic,
|
|
@@ -1828,8 +2603,8 @@ function templateState(expression) {
|
|
|
1828
2603
|
sourceKinds: ["template_with_placeholders"]
|
|
1829
2604
|
};
|
|
1830
2605
|
}
|
|
1831
|
-
function staticState(
|
|
1832
|
-
return { paths: [
|
|
2606
|
+
function staticState(path12, sourceKind) {
|
|
2607
|
+
return { paths: [path12], placeholders: [], dynamic: [], sourceKinds: [sourceKind] };
|
|
1833
2608
|
}
|
|
1834
2609
|
function dynamicState(expression, sourceKind = "dynamic_expression") {
|
|
1835
2610
|
return {
|
|
@@ -1898,7 +2673,7 @@ function sourceLine(node) {
|
|
|
1898
2673
|
const source = node.getSourceFile();
|
|
1899
2674
|
return source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
|
|
1900
2675
|
}
|
|
1901
|
-
function
|
|
2676
|
+
function unique2(values) {
|
|
1902
2677
|
return [...new Set(values)].sort();
|
|
1903
2678
|
}
|
|
1904
2679
|
function emptyAnalysis() {
|
|
@@ -1914,7 +2689,7 @@ function emptyAnalysis() {
|
|
|
1914
2689
|
}
|
|
1915
2690
|
|
|
1916
2691
|
// src/parsers/imported-wrapper-parser.ts
|
|
1917
|
-
async function parseImportedWrapperCalls(repoPath, filePath, source, serviceBindings) {
|
|
2692
|
+
async function parseImportedWrapperCalls(repoPath, filePath, source, serviceBindings, context) {
|
|
1918
2693
|
const imports = await importsFor(repoPath, filePath, source);
|
|
1919
2694
|
const importedByLocal = new Map(imports.filter((item) => item.sourceFile).map((item) => [item.localName, item]));
|
|
1920
2695
|
const calls = collectImportedCalls(source, importedByLocal);
|
|
@@ -1924,7 +2699,7 @@ async function parseImportedWrapperCalls(repoPath, filePath, source, serviceBind
|
|
|
1924
2699
|
if (!ts7.isIdentifier(call.expression)) continue;
|
|
1925
2700
|
const imported = importedByLocal.get(call.expression.text);
|
|
1926
2701
|
if (!imported?.sourceFile) continue;
|
|
1927
|
-
const spec = await loadWrapperSpec(repoPath, imported, cache, 0);
|
|
2702
|
+
const spec = await loadWrapperSpec(repoPath, imported, cache, 0, context);
|
|
1928
2703
|
const fact = spec ? wrapperCallFact(source, filePath, call, spec, serviceBindings) : void 0;
|
|
1929
2704
|
if (fact) out.push(fact);
|
|
1930
2705
|
}
|
|
@@ -1939,23 +2714,43 @@ function collectImportedCalls(source, imports) {
|
|
|
1939
2714
|
visit(source);
|
|
1940
2715
|
return calls;
|
|
1941
2716
|
}
|
|
1942
|
-
async function loadWrapperSpec(repoPath, imported, cache, depth) {
|
|
2717
|
+
async function loadWrapperSpec(repoPath, imported, cache, depth, context) {
|
|
1943
2718
|
if (!imported.sourceFile || depth > 5) return void 0;
|
|
1944
2719
|
const key = `${imported.sourceFile}#${imported.exportedName}`;
|
|
1945
2720
|
const existing = cache.get(key);
|
|
1946
2721
|
if (existing) return existing;
|
|
1947
|
-
const pending = inspectWrapper(
|
|
2722
|
+
const pending = inspectWrapper(
|
|
2723
|
+
repoPath,
|
|
2724
|
+
imported.sourceFile,
|
|
2725
|
+
imported.exportedName,
|
|
2726
|
+
cache,
|
|
2727
|
+
depth,
|
|
2728
|
+
context
|
|
2729
|
+
);
|
|
1948
2730
|
cache.set(key, pending);
|
|
1949
2731
|
return pending;
|
|
1950
2732
|
}
|
|
1951
|
-
async function inspectWrapper(repoPath, sourceFile, exportedName, cache, depth) {
|
|
1952
|
-
const source = await readSource(
|
|
2733
|
+
async function inspectWrapper(repoPath, sourceFile, exportedName, cache, depth, context) {
|
|
2734
|
+
const source = await readSource(
|
|
2735
|
+
path10.join(repoPath, sourceFile),
|
|
2736
|
+
context,
|
|
2737
|
+
sourceFile
|
|
2738
|
+
);
|
|
1953
2739
|
if (!source) return void 0;
|
|
1954
2740
|
const named = findFunction(source, exportedName);
|
|
1955
2741
|
if (!named) return void 0;
|
|
1956
2742
|
const direct = directSendSpec(source, sourceFile, named.name, named.fn);
|
|
1957
2743
|
if (direct) return direct;
|
|
1958
|
-
return nestedSendSpec(
|
|
2744
|
+
return nestedSendSpec(
|
|
2745
|
+
repoPath,
|
|
2746
|
+
sourceFile,
|
|
2747
|
+
source,
|
|
2748
|
+
named.name,
|
|
2749
|
+
named.fn,
|
|
2750
|
+
cache,
|
|
2751
|
+
depth,
|
|
2752
|
+
context
|
|
2753
|
+
);
|
|
1959
2754
|
}
|
|
1960
2755
|
function directSendSpec(source, sourceFile, name, fn) {
|
|
1961
2756
|
const params = parameterNames(fn);
|
|
@@ -1974,11 +2769,11 @@ function directSendSpec(source, sourceFile, name, fn) {
|
|
|
1974
2769
|
const clientIndex = params.indexOf(receiver);
|
|
1975
2770
|
const pathIndex = pathName ? params.indexOf(pathName) : -1;
|
|
1976
2771
|
if (clientIndex < 0 || pathIndex < 0) return void 0;
|
|
1977
|
-
const
|
|
1978
|
-
const methodIndex =
|
|
2772
|
+
const methodName2 = methodExpr && ts7.isIdentifier(methodExpr) ? methodExpr.text : void 0;
|
|
2773
|
+
const methodIndex = methodName2 ? params.indexOf(methodName2) : -1;
|
|
1979
2774
|
return { clientIndex, pathIndex, methodIndex: methodIndex >= 0 ? methodIndex : void 0, methodLiteral: literal(methodExpr), sourceFile, sourceLine: lineOf3(source, fn), chain: [name] };
|
|
1980
2775
|
}
|
|
1981
|
-
async function nestedSendSpec(repoPath, sourceFile, source, name, fn, cache, depth) {
|
|
2776
|
+
async function nestedSendSpec(repoPath, sourceFile, source, name, fn, cache, depth, context) {
|
|
1982
2777
|
const imports = await importsFor(repoPath, sourceFile, source);
|
|
1983
2778
|
const byLocal = new Map(imports.filter((item) => item.sourceFile).map((item) => [item.localName, item]));
|
|
1984
2779
|
const calls = [];
|
|
@@ -1988,7 +2783,7 @@ async function nestedSendSpec(repoPath, sourceFile, source, name, fn, cache, dep
|
|
|
1988
2783
|
if (calls.length !== 1) return void 0;
|
|
1989
2784
|
const call = calls[0];
|
|
1990
2785
|
const imported = call && ts7.isIdentifier(call.expression) ? byLocal.get(call.expression.text) : void 0;
|
|
1991
|
-
const nested = imported ? await loadWrapperSpec(repoPath, imported, cache, depth + 1) : void 0;
|
|
2786
|
+
const nested = imported ? await loadWrapperSpec(repoPath, imported, cache, depth + 1, context) : void 0;
|
|
1992
2787
|
if (!call || !nested) return void 0;
|
|
1993
2788
|
const params = parameterNames(fn);
|
|
1994
2789
|
const clientIndex = mappedParameterIndex(call.arguments[nested.clientIndex], params);
|
|
@@ -2235,8 +3030,8 @@ function resolveExpression(expr, use, policy, depth = 0, seen = /* @__PURE__ */
|
|
|
2235
3030
|
if (!binding.declaration || !binding.initializer || !binding.immutable) return { status: "dynamic", sourceKind: "dynamic_expression", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: binding.evidence, constName: expr.text };
|
|
2236
3031
|
if (seen.has(binding.declaration)) return { status: "unknown", sourceKind: "const_alias", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: ["alias_cycle_detected"], constName: expr.text };
|
|
2237
3032
|
seen.add(binding.declaration);
|
|
2238
|
-
const
|
|
2239
|
-
return { ...
|
|
3033
|
+
const resolved3 = resolveExpression(binding.initializer, binding.declaration, policy, depth + 1, seen);
|
|
3034
|
+
return { ...resolved3, sourceKind: "const_alias", rawExpression: safeRaw(expr), constName: expr.text, evidence: [...binding.evidence, ...resolved3.evidence] };
|
|
2240
3035
|
}
|
|
2241
3036
|
return { status: "dynamic", sourceKind: "dynamic_expression", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: [`unsupported_${ts8.SyntaxKind[expr.kind] ?? "expression"}`] };
|
|
2242
3037
|
}
|
|
@@ -2260,10 +3055,10 @@ function destinationExpressionShape(expr) {
|
|
|
2260
3055
|
return ts8.SyntaxKind[expr.kind] ?? "expression";
|
|
2261
3056
|
}
|
|
2262
3057
|
function staticConditionalCandidates(expr, initializers) {
|
|
2263
|
-
const
|
|
2264
|
-
if (!
|
|
2265
|
-
const left = staticExpressionText(
|
|
2266
|
-
const right = staticExpressionText(
|
|
3058
|
+
const resolved3 = expr && ts8.isIdentifier(expr) && initializers.has(expr.text) ? initializers.get(expr.text) : expr;
|
|
3059
|
+
if (!resolved3 || !ts8.isConditionalExpression(resolved3)) return void 0;
|
|
3060
|
+
const left = staticExpressionText(resolved3.whenTrue, initializers);
|
|
3061
|
+
const right = staticExpressionText(resolved3.whenFalse, initializers);
|
|
2267
3062
|
if (!left || !right) return void 0;
|
|
2268
3063
|
return [.../* @__PURE__ */ new Set([left, right])];
|
|
2269
3064
|
}
|
|
@@ -2311,15 +3106,15 @@ function hasTemplatePlaceholder(value) {
|
|
|
2311
3106
|
return /\$\{|%7B|%7D/i.test(value);
|
|
2312
3107
|
}
|
|
2313
3108
|
function urlTargetFromExpression(expr, use) {
|
|
2314
|
-
const
|
|
2315
|
-
if (
|
|
2316
|
-
if (expr) return { kind: "url_expression", dynamic: true, expression: `${
|
|
3109
|
+
const resolved3 = resolveExpression(expr, use, "external");
|
|
3110
|
+
if (resolved3.status === "static" && resolved3.value && !hasTemplatePlaceholder(resolved3.value)) return { kind: "static_url", expression: resolved3.value, dynamic: false, sourceKind: resolved3.sourceKind };
|
|
3111
|
+
if (expr) return { kind: "url_expression", dynamic: true, expression: `${resolved3.sourceKind}:${resolved3.placeholderKeys.join("|")}`, expressionShape: resolved3.sourceKind, placeholderKeys: resolved3.placeholderKeys };
|
|
2317
3112
|
return { kind: "unknown", dynamic: false };
|
|
2318
3113
|
}
|
|
2319
3114
|
function destinationTargetFromExpression(expr, use) {
|
|
2320
|
-
const
|
|
2321
|
-
const text =
|
|
2322
|
-
if (
|
|
3115
|
+
const resolved3 = resolveExpression(expr, use, "external");
|
|
3116
|
+
const text = resolved3.value;
|
|
3117
|
+
if (resolved3.status === "static" && text && !hasTemplatePlaceholder(text)) return { kind: "destination", expression: text, dynamic: false, sourceKind: resolved3.sourceKind };
|
|
2323
3118
|
const candidates = staticConditionalCandidates(expr, /* @__PURE__ */ new Map());
|
|
2324
3119
|
if (candidates) return { kind: "destination", dynamic: true, expressionShape: "conditional", candidateLiterals: candidates };
|
|
2325
3120
|
const shape = destinationExpressionShape(expr);
|
|
@@ -2419,9 +3214,9 @@ function collectWrapperSpecs(source) {
|
|
|
2419
3214
|
const pathProp = propertyInitializer(objectArg, "path");
|
|
2420
3215
|
const methodProp = propertyInitializer(objectArg, "method");
|
|
2421
3216
|
const pathName = pathProp && ts8.isIdentifier(pathProp) ? pathProp.text : void 0;
|
|
2422
|
-
const
|
|
3217
|
+
const methodName2 = methodProp && ts8.isIdentifier(methodProp) ? methodProp.text : void 0;
|
|
2423
3218
|
const methodLiteral = resolveExpression(methodProp, node, "literal").value;
|
|
2424
|
-
if (pathName) sends.push({ client: node.expression.expression.text, path: pathName, method:
|
|
3219
|
+
if (pathName) sends.push({ client: node.expression.expression.text, path: pathName, method: methodName2, methodLiteral, start: node.getStart(source), end: node.getEnd() });
|
|
2425
3220
|
}
|
|
2426
3221
|
}
|
|
2427
3222
|
if (ts8.isCallExpression(node) && ts8.isIdentifier(node.expression) && specs.has(node.expression.text)) {
|
|
@@ -2558,15 +3353,37 @@ function containsSupportedOutboundCall(node) {
|
|
|
2558
3353
|
const end = node.getEnd();
|
|
2559
3354
|
return classifyOutboundCallsInSource(source, source.fileName).some((call) => call.node.getStart(source) >= start && call.node.getEnd() <= end);
|
|
2560
3355
|
}
|
|
2561
|
-
async function parseOutboundCalls(repoPath, filePath) {
|
|
2562
|
-
const
|
|
2563
|
-
const
|
|
2564
|
-
const
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
3356
|
+
async function parseOutboundCalls(repoPath, filePath, context) {
|
|
3357
|
+
const snapshot = context?.get(filePath);
|
|
3358
|
+
const text = snapshot?.text ?? await fs8.readFile(path11.join(repoPath, filePath), "utf8");
|
|
3359
|
+
const source = snapshot?.sourceFile() ?? ts8.createSourceFile(
|
|
3360
|
+
filePath,
|
|
3361
|
+
text,
|
|
3362
|
+
ts8.ScriptTarget.Latest,
|
|
3363
|
+
true,
|
|
3364
|
+
filePath.endsWith(".ts") ? ts8.ScriptKind.TS : ts8.ScriptKind.JS
|
|
3365
|
+
);
|
|
3366
|
+
const bindingNames = new Set((await parseServiceBindings(
|
|
3367
|
+
repoPath,
|
|
3368
|
+
filePath,
|
|
3369
|
+
context
|
|
3370
|
+
)).map((binding) => binding.variableName));
|
|
3371
|
+
const importedWrappers = await parseImportedWrapperCalls(
|
|
3372
|
+
repoPath,
|
|
3373
|
+
filePath,
|
|
3374
|
+
source,
|
|
3375
|
+
bindingNames,
|
|
3376
|
+
context
|
|
3377
|
+
);
|
|
3378
|
+
return [...classifyOutboundCallsInSource(source, filePath).map((call) => call.fact), ...importedWrappers, ...parseLocalServiceCalls(text, filePath, source)];
|
|
3379
|
+
}
|
|
3380
|
+
function parseLocalServiceCalls(text, filePath, source = ts8.createSourceFile(
|
|
3381
|
+
filePath,
|
|
3382
|
+
text,
|
|
3383
|
+
ts8.ScriptTarget.Latest,
|
|
3384
|
+
true,
|
|
3385
|
+
filePath.endsWith(".ts") ? ts8.ScriptKind.TS : ts8.ScriptKind.JS
|
|
3386
|
+
)) {
|
|
2570
3387
|
const aliases = /* @__PURE__ */ new Map();
|
|
2571
3388
|
const calls = [];
|
|
2572
3389
|
const visit = (node) => {
|
|
@@ -2842,8 +3659,8 @@ function buildRemoteQueryTarget(input) {
|
|
|
2842
3659
|
// src/linker/service-resolver.ts
|
|
2843
3660
|
function rows(db, operationPath, workspaceId) {
|
|
2844
3661
|
const names = operationLookupNames(operationPath);
|
|
2845
|
-
|
|
2846
|
-
`SELECT o.id operationId,r.id repoId,r.name repoName,r.package_name packageName,s.service_name serviceName,s.qualified_name qualifiedName,s.service_path servicePath,o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,o.source_line sourceLine,0 score
|
|
3662
|
+
const result = db.prepare(
|
|
3663
|
+
`SELECT o.id operationId,r.id repoId,r.name repoName,r.package_name packageName,s.service_name serviceName,s.qualified_name qualifiedName,s.service_path servicePath,o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,o.source_line sourceLine,0 score
|
|
2847
3664
|
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
|
|
2848
3665
|
WHERE (? IS NULL OR r.workspace_id=?) AND (o.operation_path IN (?,?) OR o.operation_name IN (?,?)) ORDER BY r.name,s.service_path,o.operation_name`
|
|
2849
3666
|
).all(
|
|
@@ -2854,6 +3671,11 @@ function rows(db, operationPath, workspaceId) {
|
|
|
2854
3671
|
names.name,
|
|
2855
3672
|
names.simpleName
|
|
2856
3673
|
);
|
|
3674
|
+
return result.map((row) => ({
|
|
3675
|
+
...row,
|
|
3676
|
+
score: Number(row.score ?? 0),
|
|
3677
|
+
reasons: []
|
|
3678
|
+
}));
|
|
2857
3679
|
}
|
|
2858
3680
|
function operationLookupNames(operationPath) {
|
|
2859
3681
|
const name = operationPath.replace(/^\//, "");
|
|
@@ -2870,7 +3692,11 @@ function resolveOperation(db, signals, workspaceId) {
|
|
|
2870
3692
|
if (missing.length > 0)
|
|
2871
3693
|
return {
|
|
2872
3694
|
status: "dynamic",
|
|
2873
|
-
candidates: signals.operationPath ? rows(db, signals.operationPath, workspaceId)
|
|
3695
|
+
candidates: signals.operationPath ? rows(db, signals.operationPath, workspaceId).map((candidate) => ({
|
|
3696
|
+
...candidate,
|
|
3697
|
+
score: 0.2,
|
|
3698
|
+
reasons: ["operation_path_match"]
|
|
3699
|
+
})) : [],
|
|
2874
3700
|
reasons: [...new Set(missing)].map((name) => `missing_variable:${name}`)
|
|
2875
3701
|
};
|
|
2876
3702
|
if (!signals.operationPath)
|
|
@@ -3233,7 +4059,13 @@ function compactCandidateScores(candidates) {
|
|
|
3233
4059
|
return candidates.flatMap((candidate) => {
|
|
3234
4060
|
const row = objectValue(candidate);
|
|
3235
4061
|
if (!row) return [];
|
|
3236
|
-
return [{
|
|
4062
|
+
return [{
|
|
4063
|
+
repo: row.repoName,
|
|
4064
|
+
servicePath: row.servicePath,
|
|
4065
|
+
operationPath: row.operationPath,
|
|
4066
|
+
score: row.score,
|
|
4067
|
+
reasons: Array.isArray(row.reasons) ? row.reasons.filter((reason) => typeof reason === "string") : ["operation_path_match"]
|
|
4068
|
+
}];
|
|
3237
4069
|
});
|
|
3238
4070
|
}
|
|
3239
4071
|
function placeholderKeys(values) {
|
|
@@ -3262,7 +4094,7 @@ function linkImplementations(db, workspaceId, generation) {
|
|
|
3262
4094
|
const duplicateFamilies = duplicatePackageFamilies(accepted);
|
|
3263
4095
|
const duplicatePackageAmbiguous = duplicateFamilies.length > 0 && !accepted.some(hasDirectOwnershipEvidence);
|
|
3264
4096
|
const selected = duplicatePackageAmbiguous ? accepted : winners;
|
|
3265
|
-
const
|
|
4097
|
+
const unique3 = !duplicatePackageAmbiguous && winners.length === 1 ? winners[0] : void 0;
|
|
3266
4098
|
const ambiguityReasons = duplicatePackageAmbiguous ? ["duplicate_package_name_candidates"] : winners.length > 1 ? ["multiple_equal_score_implementation_candidates"] : [];
|
|
3267
4099
|
const evidence = {
|
|
3268
4100
|
servicePath: operation.servicePath,
|
|
@@ -3276,16 +4108,16 @@ function linkImplementations(db, workspaceId, generation) {
|
|
|
3276
4108
|
candidateFamilies: duplicateFamilies,
|
|
3277
4109
|
candidates: candidates.map((candidate, index) => candidateEvidence(candidate, index + 1))
|
|
3278
4110
|
};
|
|
3279
|
-
const evidenceWithHints =
|
|
4111
|
+
const evidenceWithHints = unique3 ? evidence : { ...evidence, implementationHintSuggestions: implementationHintSuggestions(evidence) };
|
|
3280
4112
|
if (accepted.length === 0) {
|
|
3281
4113
|
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "OPERATION_IMPLEMENTED_BY_HANDLER", "unresolved", "operation", graphId(operation.operationId), "handler_method_candidates", candidates.map((row) => graphId(row.methodId)).join(","), 0, JSON.stringify(evidenceWithHints), 0, "No implementation candidate passed policy", generation);
|
|
3282
4114
|
edgeCount += 1;
|
|
3283
4115
|
unresolvedCount += 1;
|
|
3284
4116
|
continue;
|
|
3285
4117
|
}
|
|
3286
|
-
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "OPERATION_IMPLEMENTED_BY_HANDLER",
|
|
4118
|
+
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "OPERATION_IMPLEMENTED_BY_HANDLER", unique3 ? "resolved" : "ambiguous", "operation", graphId(operation.operationId), unique3 ? "handler_method" : "handler_method_candidates", unique3 ? graphId(unique3.methodId) : selected.map((row) => graphId(row.methodId)).join(","), unique3 ? 0.95 : 0.5, JSON.stringify(evidenceWithHints), 0, unique3 ? null : "Ambiguous registered handler implementation candidates", generation);
|
|
3287
4119
|
edgeCount += 1;
|
|
3288
|
-
if (
|
|
4120
|
+
if (unique3) resolvedCount += 1;
|
|
3289
4121
|
else ambiguousCount += 1;
|
|
3290
4122
|
}
|
|
3291
4123
|
return { edgeCount, resolvedCount, ambiguousCount, unresolvedCount };
|
|
@@ -3428,7 +4260,7 @@ function implementationCandidates(db, workspaceId, operation) {
|
|
|
3428
4260
|
CASE WHEN appRepo.id=handlerRepo.id THEN 1 ELSE 0 END sameRepoRegistration,
|
|
3429
4261
|
CASE WHEN EXISTS (SELECT 1 FROM cds_services localService WHERE localService.repo_id=appRepo.id AND localService.service_path=?) THEN 1 ELSE 0 END localServicePathMatch,
|
|
3430
4262
|
CASE WHEN EXISTS (SELECT 1 FROM cds_services localService WHERE localService.repo_id=appRepo.id) THEN 1 ELSE 0 END applicationHasLocalServices,
|
|
3431
|
-
CASE WHEN EXISTS (SELECT 1 FROM handler_registrations localReg JOIN handler_classes localClass ON ((localReg.handler_class_id IS NOT NULL AND localClass.id=localReg.handler_class_id) OR (localReg.handler_class_id IS NULL AND localClass.class_name=localReg.class_name AND localClass.repo_id=localReg.repo_id)) JOIN handler_methods localMethod ON localMethod.handler_class_id=localClass.id WHERE localReg.repo_id=appRepo.id AND (localMethod.decorator_value=? OR localMethod.decorator_value=? OR localMethod.method_name=? OR localMethod.decorator_raw_expression LIKE ?)) THEN 1 ELSE 0 END applicationHasLocalRegistrationForOperation,
|
|
4263
|
+
CASE WHEN EXISTS (SELECT 1 FROM handler_registrations localReg JOIN handler_classes localClass ON ((localReg.handler_class_id IS NOT NULL AND localClass.id=localReg.handler_class_id) OR (localReg.handler_class_id IS NULL AND localClass.class_name=localReg.class_name AND localClass.repo_id=localReg.repo_id)) JOIN handler_methods localMethod ON localMethod.handler_class_id=localClass.id WHERE localReg.repo_id=appRepo.id AND COALESCE(json_extract(localMethod.decorator_resolution_json,'$.handlerKind'),CASE WHEN localMethod.decorator_kind='Event' THEN 'event' WHEN localMethod.decorator_kind IN ('Action','Func','On') THEN 'operation' ELSE 'unsupported' END)='operation' AND COALESCE(json_extract(localMethod.decorator_resolution_json,'$.executable'),CASE WHEN localMethod.decorator_kind IN ('Action','Func','On') THEN 1 ELSE 0 END)=1 AND (localMethod.decorator_value=? OR localMethod.decorator_value=? OR localMethod.method_name=? OR localMethod.decorator_raw_expression LIKE ?)) THEN 1 ELSE 0 END applicationHasLocalRegistrationForOperation,
|
|
3432
4264
|
CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(appRepo.id AS TEXT) AND dep.to_id=?) THEN 1 ELSE 0 END appDependsOnModel,
|
|
3433
4265
|
CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(appRepo.id AS TEXT) AND dep.to_id=CAST(handlerRepo.id AS TEXT)) THEN 1 ELSE 0 END appDependsOnHandler,
|
|
3434
4266
|
CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(handlerRepo.id AS TEXT) AND dep.to_id=?) THEN 1 ELSE 0 END handlerDependsOnModel
|
|
@@ -3460,6 +4292,13 @@ function implementationCandidates(db, workspaceId, operation) {
|
|
|
3460
4292
|
)
|
|
3461
4293
|
JOIN repositories appRepo ON appRepo.id=hr.repo_id
|
|
3462
4294
|
WHERE appRepo.workspace_id=?
|
|
4295
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.handlerKind'),
|
|
4296
|
+
CASE WHEN hm.decorator_kind='Event' THEN 'event'
|
|
4297
|
+
WHEN hm.decorator_kind IN ('Action','Func','On') THEN 'operation'
|
|
4298
|
+
ELSE 'unsupported' END)='operation'
|
|
4299
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
|
|
4300
|
+
CASE WHEN hm.decorator_kind IN ('Action','Func','On')
|
|
4301
|
+
THEN 1 ELSE 0 END)=1
|
|
3463
4302
|
AND (hm.decorator_value=? OR hm.decorator_value=? OR hm.method_name=? OR hm.decorator_raw_expression LIKE ?)`).all(
|
|
3464
4303
|
operation.modelRepoId,
|
|
3465
4304
|
operation.modelRepo,
|
|
@@ -3588,6 +4427,11 @@ function candidateEvidence(candidate, rank) {
|
|
|
3588
4427
|
};
|
|
3589
4428
|
}
|
|
3590
4429
|
function implementationMethodSignal(row, operation) {
|
|
4430
|
+
const resolution = objectJson(row.decoratorResolutionJson) ?? {};
|
|
4431
|
+
if (resolution.handlerKind && resolution.handlerKind !== "operation")
|
|
4432
|
+
return { matches: false, contradicted: true, acceptedReasons: [], rejectedReasons: ["non_operation_handler_kind"] };
|
|
4433
|
+
if (resolution.executable === false)
|
|
4434
|
+
return { matches: false, contradicted: true, acceptedReasons: [], rejectedReasons: ["handler_method_not_executable"] };
|
|
3591
4435
|
const op = normalizedOperationName(String(operation.operationPath ?? operation.operationName ?? ""));
|
|
3592
4436
|
const decorator = normalizeDecoratorOperationSignal(typeof row.decoratorValue === "string" ? row.decoratorValue : void 0, typeof row.decoratorRawExpression === "string" ? row.decoratorRawExpression : void 0, op);
|
|
3593
4437
|
if (decorator.status === "resolved" && decorator.operationName === op) return { matches: true, contradicted: false, acceptedReasons: ["decorator targets operation"], rejectedReasons: [] };
|
|
@@ -3617,101 +4461,1337 @@ function parseVars(values) {
|
|
|
3617
4461
|
}
|
|
3618
4462
|
return out;
|
|
3619
4463
|
}
|
|
3620
|
-
function
|
|
3621
|
-
const serviceSuggestions = [...new Set(candidates.flatMap((row) => typeof row.servicePath === "string" ? [`--service ${row.servicePath}`] : []))].sort();
|
|
4464
|
+
function selectorRepoNotFoundDiagnostic(requested) {
|
|
3622
4465
|
return {
|
|
3623
4466
|
severity: "warning",
|
|
3624
|
-
code: "
|
|
3625
|
-
message
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
resolutionStatus: "ambiguous_operation",
|
|
3629
|
-
candidates,
|
|
3630
|
-
serviceSuggestions,
|
|
3631
|
-
selectorSuggestions: fullSelectorSuggestions(candidates)
|
|
4467
|
+
code: "selector_repo_not_found",
|
|
4468
|
+
message: `No indexed repository matched selector: ${requested}`,
|
|
4469
|
+
selectorKind: "repo",
|
|
4470
|
+
requestedRepository: requested
|
|
3632
4471
|
};
|
|
3633
4472
|
}
|
|
3634
|
-
function
|
|
3635
|
-
const
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
];
|
|
3642
|
-
})
|
|
4473
|
+
function selectorRepoAmbiguousDiagnostic(requested, candidates) {
|
|
4474
|
+
const uniqueName = (value) => candidates.filter((candidate) => candidate.name === value).length === 1;
|
|
4475
|
+
const uniquePackage = (value) => candidates.filter((candidate) => candidate.packageName === value).length === 1;
|
|
4476
|
+
const suggestions = candidates.flatMap((candidate) => {
|
|
4477
|
+
if (uniqueName(candidate.name)) return [`--repo ${candidate.name}`];
|
|
4478
|
+
if (candidate.packageName && uniquePackage(candidate.packageName))
|
|
4479
|
+
return [`--repo ${candidate.packageName}`];
|
|
4480
|
+
return [];
|
|
4481
|
+
});
|
|
4482
|
+
return {
|
|
4483
|
+
severity: "warning",
|
|
4484
|
+
code: "selector_repo_ambiguous",
|
|
4485
|
+
message: `Repository selector matched multiple indexed repositories: ${requested}`,
|
|
4486
|
+
selectorKind: "repo",
|
|
4487
|
+
requestedRepository: requested,
|
|
4488
|
+
candidates,
|
|
4489
|
+
selectorSuggestions: [...new Set(suggestions)].sort(),
|
|
4490
|
+
remediation: suggestions.length > 0 ? "Use one of the unique --repo selectors shown." : "Repository names and package names must be unique before this selector can be traced safely."
|
|
4491
|
+
};
|
|
3643
4492
|
}
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
function baseTraceEvidence(row, call, persistedEvidence, contextualEvidence) {
|
|
3647
|
-
const evidence = { ...persistedEvidence, ...contextualEvidence ?? {} };
|
|
4493
|
+
function selectorNotFoundDiagnostic(start) {
|
|
4494
|
+
const serviceOnly = start.servicePath && !start.operation && !start.operationPath && !start.handler;
|
|
3648
4495
|
return {
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
callSite: { sourceFile: call.source_file, sourceLine: call.source_line },
|
|
3654
|
-
sourceFile: call.source_file,
|
|
3655
|
-
sourceLine: call.source_line,
|
|
3656
|
-
file: call.source_file,
|
|
3657
|
-
line: call.source_line,
|
|
3658
|
-
persistedTarget: { kind: row.to_kind, id: row.to_id },
|
|
3659
|
-
contextualResolutionParticipated: Boolean(contextualEvidence?.contextualServiceBindingAttempted),
|
|
3660
|
-
persistedResolution: persistedResolution(row)
|
|
4496
|
+
severity: "warning",
|
|
4497
|
+
code: "trace_start_not_found",
|
|
4498
|
+
message: serviceOnly ? "Service-only trace requires --operation or --path and will not broaden to the whole workspace" : "No handler source matched the requested trace start selector",
|
|
4499
|
+
selectorKind: start.handler ? "handler" : start.operation || start.operationPath ? "operation" : start.servicePath ? "service" : void 0
|
|
3661
4500
|
};
|
|
3662
4501
|
}
|
|
3663
|
-
function
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
const
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
const resolution = resolveRuntimeOperation(db, substituted, workspaceId);
|
|
3671
|
-
if (resolution.target) {
|
|
3672
|
-
const resolvedRow = { ...row, status: "resolved", to_kind: "operation", to_id: String(resolution.target.operationId), unresolved_reason: void 0, confidence: Math.max(0, Math.min(1, resolution.target.score)) };
|
|
3673
|
-
return { row: resolvedRow, evidence: withEffectiveResolution(substituted, resolvedRow, void 0, resolution), target: resolution.target };
|
|
4502
|
+
function sourceScopeForSelector(db, repoId, start, workspaceId) {
|
|
4503
|
+
if (start.handler) {
|
|
4504
|
+
const classRows = handlerClassRows(db, repoId, start.handler, workspaceId);
|
|
4505
|
+
if (classRows.length > 0) return handlerClassScope(classRows, start.handler);
|
|
4506
|
+
const methodRows = handlerMethodRows(db, repoId, start.handler, workspaceId);
|
|
4507
|
+
if (methodRows.length > 0)
|
|
4508
|
+
return handlerMethodScope(methodRows, repoId, start.handler);
|
|
3674
4509
|
}
|
|
3675
|
-
const
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
4510
|
+
const operation = normalizeOperation(start.operation ?? start.operationPath);
|
|
4511
|
+
if (!operation) return void 0;
|
|
4512
|
+
const operationRows = operationHandlerRows(
|
|
4513
|
+
db,
|
|
4514
|
+
repoId,
|
|
4515
|
+
operation,
|
|
4516
|
+
start.servicePath,
|
|
4517
|
+
workspaceId
|
|
4518
|
+
);
|
|
4519
|
+
if (operationRows.length > 0)
|
|
4520
|
+
return operationHandlerScope(operationRows, repoId, operation);
|
|
4521
|
+
if (!start.servicePath) return void 0;
|
|
4522
|
+
const implementationRows = implementationHandlerRows(
|
|
4523
|
+
db,
|
|
4524
|
+
repoId,
|
|
4525
|
+
start.servicePath,
|
|
4526
|
+
operation,
|
|
4527
|
+
workspaceId
|
|
4528
|
+
);
|
|
4529
|
+
return implementationRows.length > 0 ? executableScope(implementationRows, repoId) : void 0;
|
|
4530
|
+
}
|
|
4531
|
+
function handlerClassRows(db, repoId, handler, workspaceId) {
|
|
4532
|
+
return db.prepare(`SELECT hc.id handlerClassId,hc.repo_id repoId,
|
|
4533
|
+
r.name repoName,hc.class_name className,
|
|
4534
|
+
hc.source_file sourceFile,hc.source_line sourceLine,hm.id methodId,
|
|
4535
|
+
sym.id symbolId,COALESCE(classSym.evidence_json,
|
|
4536
|
+
(SELECT fallback.evidence_json FROM symbols fallback
|
|
4537
|
+
WHERE fallback.repo_id=hc.repo_id AND fallback.kind='class'
|
|
4538
|
+
AND fallback.name=hc.class_name AND fallback.source_file=hc.source_file
|
|
4539
|
+
ORDER BY fallback.id LIMIT 1)) classEvidence
|
|
4540
|
+
FROM handler_classes hc
|
|
4541
|
+
JOIN repositories r ON r.id=hc.repo_id
|
|
4542
|
+
LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id
|
|
4543
|
+
AND sym.source_file=hc.source_file
|
|
4544
|
+
AND sym.kind='method'
|
|
4545
|
+
AND substr(sym.qualified_name,1,length(hc.class_name)+1)=hc.class_name || '.'
|
|
4546
|
+
AND (NOT EXISTS (SELECT 1 FROM handler_methods declared
|
|
4547
|
+
WHERE declared.handler_class_id=hc.id
|
|
4548
|
+
AND declared.method_name=sym.name)
|
|
4549
|
+
OR EXISTS (SELECT 1 FROM handler_methods executable
|
|
4550
|
+
WHERE executable.handler_class_id=hc.id
|
|
4551
|
+
AND executable.method_name=sym.name
|
|
4552
|
+
AND COALESCE(json_extract(executable.decorator_resolution_json,
|
|
4553
|
+
'$.executable'),CASE WHEN executable.decorator_kind IN
|
|
4554
|
+
('Action','Func','On','Event') THEN 1 ELSE 0 END)=1))
|
|
4555
|
+
LEFT JOIN handler_methods hm ON hm.handler_class_id=hc.id
|
|
4556
|
+
AND hm.method_name=sym.name
|
|
4557
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
|
|
4558
|
+
CASE WHEN hm.decorator_kind IN ('Action','Func','On','Event')
|
|
4559
|
+
THEN 1 ELSE 0 END)=1
|
|
4560
|
+
LEFT JOIN symbols classSym ON classSym.id=hc.symbol_id
|
|
4561
|
+
WHERE (? IS NULL OR r.workspace_id=?)
|
|
4562
|
+
AND (? IS NULL OR hc.repo_id=?) AND hc.class_name=?
|
|
4563
|
+
ORDER BY hc.repo_id,hc.id,hm.id`).all(
|
|
4564
|
+
workspaceId,
|
|
4565
|
+
workspaceId,
|
|
4566
|
+
repoId,
|
|
4567
|
+
repoId,
|
|
4568
|
+
handler
|
|
4569
|
+
);
|
|
4570
|
+
}
|
|
4571
|
+
function handlerMethodRows(db, repoId, handler, workspaceId) {
|
|
4572
|
+
return db.prepare(`SELECT hc.id handlerClassId,hc.repo_id repoId,
|
|
4573
|
+
r.name repoName,hc.class_name className,hc.source_file sourceFile,
|
|
4574
|
+
hm.source_line sourceLine,hm.id methodId,sym.id symbolId
|
|
4575
|
+
FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
4576
|
+
JOIN repositories r ON r.id=hc.repo_id
|
|
4577
|
+
LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id
|
|
4578
|
+
AND sym.source_file=hc.source_file
|
|
4579
|
+
AND sym.qualified_name=hc.class_name || '.' || hm.method_name
|
|
4580
|
+
AND sym.start_line=hm.source_line
|
|
4581
|
+
WHERE (? IS NULL OR r.workspace_id=?)
|
|
4582
|
+
AND (? IS NULL OR hc.repo_id=?) AND hm.method_name=?
|
|
4583
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
|
|
4584
|
+
CASE WHEN hm.decorator_kind IN ('Action','Func','On','Event')
|
|
4585
|
+
THEN 1 ELSE 0 END)=1
|
|
4586
|
+
ORDER BY hc.repo_id,hc.id,hm.id`).all(
|
|
4587
|
+
workspaceId,
|
|
4588
|
+
workspaceId,
|
|
4589
|
+
repoId,
|
|
4590
|
+
repoId,
|
|
4591
|
+
handler
|
|
4592
|
+
);
|
|
4593
|
+
}
|
|
4594
|
+
function operationHandlerRows(db, repoId, operation, servicePath, workspaceId) {
|
|
4595
|
+
return db.prepare(`SELECT DISTINCT hc.id handlerClassId,hc.repo_id repoId,
|
|
4596
|
+
r.name repoName,hc.class_name className,hc.source_file sourceFile,
|
|
4597
|
+
hm.source_line sourceLine,hm.id methodId,sym.id symbolId
|
|
4598
|
+
FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
4599
|
+
JOIN repositories r ON r.id=hc.repo_id
|
|
4600
|
+
LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id
|
|
4601
|
+
AND sym.source_file=hc.source_file
|
|
4602
|
+
AND sym.qualified_name=hc.class_name || '.' || hm.method_name
|
|
4603
|
+
AND sym.start_line=hm.source_line
|
|
4604
|
+
WHERE (? IS NULL OR r.workspace_id=?) AND (? IS NULL OR hc.repo_id=?)
|
|
4605
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.handlerKind'),
|
|
4606
|
+
CASE WHEN hm.decorator_kind='Event' THEN 'event'
|
|
4607
|
+
WHEN hm.decorator_kind IN ('Action','Func','On') THEN 'operation'
|
|
4608
|
+
ELSE 'unsupported' END)='operation'
|
|
4609
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
|
|
4610
|
+
CASE WHEN hm.decorator_kind IN ('Action','Func','On')
|
|
4611
|
+
THEN 1 ELSE 0 END)=1
|
|
4612
|
+
AND (hm.decorator_value=? OR hm.method_name=?)
|
|
4613
|
+
AND (? IS NULL OR EXISTS (
|
|
4614
|
+
SELECT 1 FROM cds_services svc JOIN cds_operations op ON op.service_id=svc.id
|
|
4615
|
+
WHERE svc.repo_id=hc.repo_id AND svc.service_path=?
|
|
4616
|
+
AND (op.operation_path=? OR op.operation_name=?)))
|
|
4617
|
+
ORDER BY hc.repo_id,hc.id,hm.id`).all(
|
|
4618
|
+
workspaceId,
|
|
4619
|
+
workspaceId,
|
|
4620
|
+
repoId,
|
|
4621
|
+
repoId,
|
|
4622
|
+
operation,
|
|
4623
|
+
operation,
|
|
4624
|
+
servicePath,
|
|
4625
|
+
servicePath,
|
|
4626
|
+
operation,
|
|
4627
|
+
operation
|
|
4628
|
+
);
|
|
4629
|
+
}
|
|
4630
|
+
function operationHandlerScope(rows2, fallbackRepoId, requested) {
|
|
4631
|
+
const candidates = handlerSelectorCandidates(rows2, "method");
|
|
4632
|
+
if (candidates.length < 2) return executableScope(rows2, fallbackRepoId);
|
|
4633
|
+
const classes = /* @__PURE__ */ new Map();
|
|
4634
|
+
for (const candidate of candidates) {
|
|
4635
|
+
const key = `${String(candidate.repoName)}:${String(candidate.className)}`;
|
|
4636
|
+
classes.set(key, /* @__PURE__ */ new Set([
|
|
4637
|
+
...classes.get(key) ?? [],
|
|
4638
|
+
String(candidate.handlerClassId)
|
|
4639
|
+
]));
|
|
4640
|
+
}
|
|
4641
|
+
const suggestions = candidates.flatMap((candidate) => {
|
|
4642
|
+
if (typeof candidate.repoName !== "string" || typeof candidate.className !== "string") return [];
|
|
4643
|
+
const key = `${candidate.repoName}:${candidate.className}`;
|
|
4644
|
+
return classes.get(key)?.size === 1 ? [`--repo ${candidate.repoName} --handler ${candidate.className}`] : [];
|
|
4645
|
+
});
|
|
4646
|
+
return { diagnostics: [{
|
|
4647
|
+
severity: "warning",
|
|
4648
|
+
code: "trace_start_ambiguous",
|
|
4649
|
+
message: "Operation selector matched multiple handler-only executable scopes",
|
|
4650
|
+
selectorKind: "operation",
|
|
4651
|
+
normalizedSelectorValue: requested,
|
|
4652
|
+
resolutionStage: "handler",
|
|
4653
|
+
resolutionStatus: "ambiguous_handler_operation",
|
|
4654
|
+
candidates,
|
|
4655
|
+
selectorSuggestions: [...new Set(suggestions)].sort(),
|
|
4656
|
+
remediation: "Select one handler class explicitly; no operation was chosen automatically."
|
|
4657
|
+
}] };
|
|
4658
|
+
}
|
|
4659
|
+
function implementationHandlerRows(db, repoId, servicePath, operation, workspaceId) {
|
|
4660
|
+
return db.prepare(`SELECT DISTINCT hc.repo_id repoId,
|
|
4661
|
+
hc.source_file sourceFile,hm.id methodId,sym.id symbolId
|
|
4662
|
+
FROM cds_services svc JOIN cds_operations op ON op.service_id=svc.id
|
|
4663
|
+
JOIN repositories r ON r.id=svc.repo_id
|
|
4664
|
+
JOIN graph_edges edge ON edge.edge_type='OPERATION_IMPLEMENTED_BY_HANDLER'
|
|
4665
|
+
AND edge.status='resolved' AND edge.from_kind='operation'
|
|
4666
|
+
AND edge.from_id=CAST(op.id AS TEXT)
|
|
4667
|
+
JOIN handler_methods hm ON hm.id=CAST(edge.to_id AS INTEGER)
|
|
4668
|
+
JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
4669
|
+
LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id
|
|
4670
|
+
AND sym.source_file=hc.source_file
|
|
4671
|
+
AND sym.qualified_name=hc.class_name || '.' || hm.method_name
|
|
4672
|
+
AND sym.start_line=hm.source_line
|
|
4673
|
+
WHERE (? IS NULL OR r.workspace_id=?)
|
|
4674
|
+
AND (? IS NULL OR svc.repo_id=?) AND svc.service_path=?
|
|
4675
|
+
AND (op.operation_path=? OR op.operation_name=?)
|
|
4676
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.handlerKind'),
|
|
4677
|
+
CASE WHEN hm.decorator_kind='Event' THEN 'event'
|
|
4678
|
+
WHEN hm.decorator_kind IN ('Action','Func','On') THEN 'operation'
|
|
4679
|
+
ELSE 'unsupported' END)='operation'
|
|
4680
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
|
|
4681
|
+
CASE WHEN hm.decorator_kind IN ('Action','Func','On')
|
|
4682
|
+
THEN 1 ELSE 0 END)=1
|
|
4683
|
+
ORDER BY hc.repo_id,hc.id,hm.id`).all(
|
|
4684
|
+
workspaceId,
|
|
4685
|
+
workspaceId,
|
|
4686
|
+
repoId,
|
|
4687
|
+
repoId,
|
|
4688
|
+
servicePath,
|
|
4689
|
+
operation,
|
|
4690
|
+
operation
|
|
4691
|
+
);
|
|
4692
|
+
}
|
|
4693
|
+
function handlerClassScope(rows2, requested) {
|
|
4694
|
+
const ambiguity = handlerSelectorAmbiguity(rows2, requested, "class");
|
|
4695
|
+
if (ambiguity) return { diagnostics: [ambiguity] };
|
|
4696
|
+
const executable = rows2.filter((row) => typeof row.symbolId === "number");
|
|
4697
|
+
const repoId = numericValue(rows2[0]?.repoId);
|
|
4698
|
+
if (executable.length > 0) {
|
|
4699
|
+
const scope = executableScope(executable, repoId);
|
|
4700
|
+
const warning = executable.some((row) => typeof row.methodId === "number") ? handlerDecoratorsNotIndexedDiagnostic(rows2[0]) : handlerMethodsNotIndexedDiagnostic(rows2[0]);
|
|
4701
|
+
return warning ? { ...scope, diagnostics: [warning] } : scope;
|
|
4702
|
+
}
|
|
4703
|
+
const first = rows2[0];
|
|
4704
|
+
return {
|
|
4705
|
+
repoId,
|
|
4706
|
+
diagnostics: [handlerMethodsNotIndexedDiagnostic(first)]
|
|
4707
|
+
};
|
|
4708
|
+
}
|
|
4709
|
+
function handlerMethodScope(rows2, fallbackRepoId, requested) {
|
|
4710
|
+
const ambiguity = handlerSelectorAmbiguity(rows2, requested, "method");
|
|
4711
|
+
return ambiguity ? { diagnostics: [ambiguity] } : executableScope(rows2, fallbackRepoId);
|
|
4712
|
+
}
|
|
4713
|
+
function handlerSelectorAmbiguity(rows2, requested, matchKind) {
|
|
4714
|
+
const candidates = handlerSelectorCandidates(rows2, matchKind);
|
|
4715
|
+
if (candidates.length < 2) return void 0;
|
|
4716
|
+
const repoCounts = /* @__PURE__ */ new Map();
|
|
4717
|
+
for (const candidate of candidates) {
|
|
4718
|
+
if (typeof candidate.repoName !== "string") continue;
|
|
4719
|
+
repoCounts.set(
|
|
4720
|
+
candidate.repoName,
|
|
4721
|
+
(repoCounts.get(candidate.repoName) ?? 0) + 1
|
|
4722
|
+
);
|
|
4723
|
+
}
|
|
4724
|
+
const suggestions = candidates.flatMap((candidate) => {
|
|
4725
|
+
const repoName = typeof candidate.repoName === "string" ? candidate.repoName : void 0;
|
|
4726
|
+
if (repoName && repoCounts.get(repoName) === 1)
|
|
4727
|
+
return [`--repo ${repoName} --handler ${requested}`];
|
|
4728
|
+
if (matchKind === "method" && typeof candidate.className === "string")
|
|
4729
|
+
return [`${repoName ? `--repo ${repoName} ` : ""}--handler ${candidate.className}`];
|
|
4730
|
+
return [];
|
|
4731
|
+
});
|
|
4732
|
+
return {
|
|
4733
|
+
severity: "warning",
|
|
4734
|
+
code: "trace_start_ambiguous",
|
|
4735
|
+
message: "Handler selector matched multiple executable scopes and was not selected automatically",
|
|
4736
|
+
selectorKind: "handler",
|
|
4737
|
+
requestedHandler: requested,
|
|
4738
|
+
resolutionStage: "handler",
|
|
4739
|
+
resolutionStatus: "ambiguous_handler",
|
|
4740
|
+
candidates,
|
|
4741
|
+
selectorSuggestions: [...new Set(suggestions)].sort(),
|
|
4742
|
+
remediation: suggestions.length > 0 ? "Use one of the scoped handler selectors shown." : "No current CLI selector uniquely identifies these duplicate handler classes."
|
|
4743
|
+
};
|
|
4744
|
+
}
|
|
4745
|
+
function handlerSelectorCandidates(rows2, matchKind) {
|
|
4746
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
4747
|
+
for (const row of rows2) {
|
|
4748
|
+
const identity = matchKind === "class" ? `class:${String(row.handlerClassId)}` : `method:${String(row.repoId)}:${String(row.symbolId ?? row.methodId)}`;
|
|
4749
|
+
candidates.set(identity, {
|
|
4750
|
+
handlerClassId: row.handlerClassId,
|
|
4751
|
+
repoId: row.repoId,
|
|
4752
|
+
repoName: row.repoName,
|
|
4753
|
+
className: row.className,
|
|
4754
|
+
sourceFile: row.sourceFile,
|
|
4755
|
+
sourceLine: row.sourceLine,
|
|
4756
|
+
matchKind
|
|
4757
|
+
});
|
|
4758
|
+
}
|
|
4759
|
+
return [...candidates.values()].sort((left, right) => String(left.repoName ?? "").localeCompare(String(right.repoName ?? "")) || String(left.className ?? "").localeCompare(String(right.className ?? "")) || String(left.sourceFile ?? "").localeCompare(String(right.sourceFile ?? "")));
|
|
4760
|
+
}
|
|
4761
|
+
function executableScope(rows2, fallbackRepoId) {
|
|
4762
|
+
const files = rows2.flatMap((row) => row.sourceFile ? [row.sourceFile] : []);
|
|
4763
|
+
const symbols = rows2.flatMap((row) => typeof row.symbolId === "number" ? [row.symbolId] : []);
|
|
4764
|
+
if (files.length === 0 || symbols.length === 0) return { repoId: fallbackRepoId };
|
|
4765
|
+
return {
|
|
4766
|
+
files: new Set(files),
|
|
4767
|
+
symbols: new Set(symbols),
|
|
4768
|
+
repoId: numericValue(rows2[0]?.repoId) ?? fallbackRepoId
|
|
4769
|
+
};
|
|
4770
|
+
}
|
|
4771
|
+
function handlerMethodsNotIndexedDiagnostic(row) {
|
|
4772
|
+
return {
|
|
4773
|
+
severity: "warning",
|
|
4774
|
+
code: "handler_methods_not_indexed",
|
|
4775
|
+
message: `Handler class ${row?.className ?? "unknown"} has no indexed executable methods`,
|
|
4776
|
+
selectorKind: "handler",
|
|
4777
|
+
className: row?.className,
|
|
4778
|
+
sourceFile: row?.sourceFile,
|
|
4779
|
+
sourceLine: row?.sourceLine,
|
|
4780
|
+
observedDecoratorNames: stringEvidenceArray(
|
|
4781
|
+
row?.classEvidence,
|
|
4782
|
+
"observedDecoratorNames"
|
|
4783
|
+
),
|
|
4784
|
+
unsupportedDecoratorNames: stringEvidenceArray(
|
|
4785
|
+
row?.classEvidence,
|
|
4786
|
+
"unsupportedDecoratorNames"
|
|
4787
|
+
),
|
|
4788
|
+
remediation: "Use a supported CAP handler decorator on at least one class method and re-index the workspace."
|
|
4789
|
+
};
|
|
4790
|
+
}
|
|
4791
|
+
function handlerDecoratorsNotIndexedDiagnostic(row) {
|
|
4792
|
+
const names = stringEvidenceArray(
|
|
4793
|
+
row?.classEvidence,
|
|
4794
|
+
"unsupportedDecoratorNames"
|
|
4795
|
+
);
|
|
4796
|
+
const methods = arrayEvidence(row?.classEvidence, "unsupportedMethods");
|
|
4797
|
+
if (names.length === 0 && methods.length === 0) return void 0;
|
|
4798
|
+
return {
|
|
4799
|
+
severity: "warning",
|
|
4800
|
+
code: "handler_decorators_not_indexed",
|
|
4801
|
+
message: `Handler class ${row?.className ?? "unknown"} contains methods that were not indexed`,
|
|
4802
|
+
selectorKind: "handler",
|
|
4803
|
+
className: row?.className,
|
|
4804
|
+
sourceFile: row?.sourceFile,
|
|
4805
|
+
sourceLine: row?.sourceLine,
|
|
4806
|
+
unsupportedDecoratorNames: names,
|
|
4807
|
+
unsupportedMethods: methods,
|
|
4808
|
+
remediation: "Use a supported CAP handler decorator shape and re-index the workspace."
|
|
4809
|
+
};
|
|
4810
|
+
}
|
|
4811
|
+
function evidenceRecord(evidenceJson) {
|
|
4812
|
+
if (!evidenceJson) return {};
|
|
4813
|
+
try {
|
|
4814
|
+
const parsed = JSON.parse(evidenceJson);
|
|
4815
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
4816
|
+
} catch {
|
|
4817
|
+
return {};
|
|
4818
|
+
}
|
|
4819
|
+
}
|
|
4820
|
+
function stringEvidenceArray(evidenceJson, key) {
|
|
4821
|
+
const value = evidenceRecord(evidenceJson)[key];
|
|
4822
|
+
return Array.isArray(value) ? [...new Set(value.filter((item) => typeof item === "string"))].sort() : [];
|
|
4823
|
+
}
|
|
4824
|
+
function arrayEvidence(evidenceJson, key) {
|
|
4825
|
+
const value = evidenceRecord(evidenceJson)[key];
|
|
4826
|
+
return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
4827
|
+
}
|
|
4828
|
+
function numericValue(value) {
|
|
4829
|
+
return typeof value === "number" ? value : void 0;
|
|
4830
|
+
}
|
|
4831
|
+
function normalizeOperation(value) {
|
|
4832
|
+
if (!value) return void 0;
|
|
4833
|
+
return value.startsWith("/") ? value.slice(1) : value;
|
|
4834
|
+
}
|
|
4835
|
+
function ambiguousStartDiagnostic(requested, candidates, message) {
|
|
4836
|
+
const serviceSuggestions = [...new Set(candidates.flatMap((row) => typeof row.servicePath === "string" ? [`--service ${row.servicePath}`] : []))].sort();
|
|
4837
|
+
return {
|
|
4838
|
+
severity: "warning",
|
|
4839
|
+
code: "trace_start_ambiguous",
|
|
4840
|
+
message,
|
|
4841
|
+
normalizedSelectorValue: requested,
|
|
4842
|
+
resolutionStage: "operation",
|
|
4843
|
+
resolutionStatus: "ambiguous_operation",
|
|
4844
|
+
candidates,
|
|
4845
|
+
serviceSuggestions,
|
|
4846
|
+
selectorSuggestions: fullSelectorSuggestions(candidates)
|
|
4847
|
+
};
|
|
4848
|
+
}
|
|
4849
|
+
function fullSelectorSuggestions(candidates) {
|
|
4850
|
+
const includeRepo = new Set(candidates.map((row) => row.repoName)).size > 1;
|
|
4851
|
+
return [...new Set(candidates.flatMap((row) => {
|
|
4852
|
+
if (typeof row.servicePath !== "string" || typeof row.operationPath !== "string") return [];
|
|
4853
|
+
const repoSelector = includeRepo && typeof row.repoName === "string" ? `--repo ${row.repoName} ` : "";
|
|
4854
|
+
return [
|
|
4855
|
+
`${repoSelector}--service ${row.servicePath} --path ${row.operationPath}`
|
|
4856
|
+
];
|
|
4857
|
+
}))].sort();
|
|
4858
|
+
}
|
|
4859
|
+
|
|
4860
|
+
// src/trace/001-dynamic-identity.ts
|
|
4861
|
+
function uniqueIdentityDerivations(db, candidates, templates) {
|
|
4862
|
+
const identities = workspaceIdentities(db, candidates);
|
|
4863
|
+
const proposals = candidates.flatMap((candidate) => {
|
|
4864
|
+
const owner = implementationOwner(db, candidate.candidateOperationId);
|
|
4865
|
+
const identity = identities.find((item) => item.repoId === candidate.repoId);
|
|
4866
|
+
return ownerAgrees(candidate, owner) && identity ? identityProposals(candidate, identity, templates) : [];
|
|
4867
|
+
});
|
|
4868
|
+
const matches2 = workspaceIdentityMatches(identities, templates);
|
|
4869
|
+
const competing = competingIdentityKeys(matches2);
|
|
4870
|
+
const duplicates = duplicateNormalizedIdentities(identities);
|
|
4871
|
+
return proposals.filter((proposal) => !competing.has(`${proposal.key}:${proposal.value}`)).filter((proposal) => !duplicates.has(proposal.normalizedIdentity)).map((proposal) => ({
|
|
4872
|
+
operationId: proposal.operationId,
|
|
4873
|
+
key: proposal.key,
|
|
4874
|
+
value: proposal.value,
|
|
4875
|
+
provenance: proposal.provenance
|
|
4876
|
+
}));
|
|
4877
|
+
}
|
|
4878
|
+
function identityProposals(candidate, identity, templates) {
|
|
4879
|
+
const routeTemplates = [templates.alias, templates.destination].filter((value) => Boolean(value));
|
|
4880
|
+
const identities = [
|
|
4881
|
+
{ name: identity.packageName, sourceKind: "package_identity", npmPackage: true },
|
|
4882
|
+
{ name: identity.repoName, sourceKind: "repository_identity", npmPackage: false }
|
|
4883
|
+
].filter((item) => Boolean(item.name));
|
|
4884
|
+
const proposals = routeTemplates.flatMap((template) => identities.flatMap((identity2) => proposalForIdentity(
|
|
4885
|
+
candidate,
|
|
4886
|
+
template,
|
|
4887
|
+
identity2.name,
|
|
4888
|
+
identity2.sourceKind,
|
|
4889
|
+
identity2.npmPackage
|
|
4890
|
+
)));
|
|
4891
|
+
return deduplicateProposals(proposals);
|
|
4892
|
+
}
|
|
4893
|
+
function proposalForIdentity(candidate, template, identity, sourceKind, npmPackage) {
|
|
4894
|
+
const match = matchIdentityTemplate(template, identity, npmPackage);
|
|
4895
|
+
if (!match) return [];
|
|
4896
|
+
return [{
|
|
4897
|
+
operationId: candidate.candidateOperationId,
|
|
4898
|
+
key: match.key,
|
|
4899
|
+
value: match.value,
|
|
4900
|
+
normalizedIdentity: match.normalizedIdentity,
|
|
4901
|
+
provenance: {
|
|
4902
|
+
sourceKind,
|
|
4903
|
+
value: match.value,
|
|
4904
|
+
rule: "exact_normalized_identity_template_match",
|
|
4905
|
+
template,
|
|
4906
|
+
matchedName: identity,
|
|
4907
|
+
normalizedForm: match.normalizedIdentity,
|
|
4908
|
+
sourceRepo: candidate.repoName
|
|
4909
|
+
}
|
|
4910
|
+
}];
|
|
4911
|
+
}
|
|
4912
|
+
function matchIdentityTemplate(template, identity, npmPackage) {
|
|
4913
|
+
const matches2 = [...template.matchAll(/\$\{([^}]*)\}/g)];
|
|
4914
|
+
if (matches2.length !== 1 || !matches2[0]?.[1]) return void 0;
|
|
4915
|
+
const placeholder = matches2[0][0];
|
|
4916
|
+
const sentinel = "dynamicplaceholdertoken";
|
|
4917
|
+
const normalizedTemplate = normalizeIdentity(template.replace(placeholder, sentinel));
|
|
4918
|
+
const [prefix, suffix, extra] = normalizedTemplate.split(sentinel);
|
|
4919
|
+
if (!prefix || !suffix || extra !== void 0) return void 0;
|
|
4920
|
+
const normalizedIdentity = normalizeIdentity(identity, npmPackage);
|
|
4921
|
+
const match = new RegExp(`^${escapeRegex(prefix)}([a-z0-9]+)${escapeRegex(suffix)}$`).exec(normalizedIdentity);
|
|
4922
|
+
if (!match?.[1]) return void 0;
|
|
4923
|
+
return { key: matches2[0][1].trim(), value: match[1], normalizedIdentity };
|
|
4924
|
+
}
|
|
4925
|
+
function competingIdentityKeys(proposals) {
|
|
4926
|
+
const owners = /* @__PURE__ */ new Map();
|
|
4927
|
+
for (const proposal of proposals) {
|
|
4928
|
+
const key = `${proposal.key}:${proposal.value}`;
|
|
4929
|
+
owners.set(key, /* @__PURE__ */ new Set([...owners.get(key) ?? [], proposal.ownerKey]));
|
|
4930
|
+
}
|
|
4931
|
+
return new Set([...owners.entries()].filter(([, repos]) => repos.size > 1).map(([key]) => key));
|
|
4932
|
+
}
|
|
4933
|
+
function duplicateNormalizedIdentities(identities) {
|
|
4934
|
+
const owners = /* @__PURE__ */ new Map();
|
|
4935
|
+
for (const identity of identities) {
|
|
4936
|
+
for (const [name, npmPackage] of [
|
|
4937
|
+
[identity.packageName, true],
|
|
4938
|
+
[identity.repoName, false]
|
|
4939
|
+
]) {
|
|
4940
|
+
if (!name) continue;
|
|
4941
|
+
const normalized = normalizeIdentity(name, npmPackage);
|
|
4942
|
+
owners.set(normalized, /* @__PURE__ */ new Set([
|
|
4943
|
+
...owners.get(normalized) ?? [],
|
|
4944
|
+
`repository:${identity.repoId}`
|
|
4945
|
+
]));
|
|
4946
|
+
}
|
|
4947
|
+
}
|
|
4948
|
+
return new Set([...owners.entries()].filter(([, repos]) => repos.size > 1).map(([identity]) => identity));
|
|
4949
|
+
}
|
|
4950
|
+
function workspaceIdentityMatches(identities, templates) {
|
|
4951
|
+
const routeTemplates = [templates.alias, templates.destination].filter((value) => Boolean(value));
|
|
4952
|
+
return identities.flatMap((identity) => {
|
|
4953
|
+
const names = [
|
|
4954
|
+
{ name: identity.packageName, npmPackage: true },
|
|
4955
|
+
{ name: identity.repoName, npmPackage: false }
|
|
4956
|
+
];
|
|
4957
|
+
return routeTemplates.flatMap((template) => names.flatMap(({ name, npmPackage }) => {
|
|
4958
|
+
if (!name) return [];
|
|
4959
|
+
const match = matchIdentityTemplate(template, name, npmPackage);
|
|
4960
|
+
return match ? [{
|
|
4961
|
+
ownerKey: `repository:${identity.repoId}`,
|
|
4962
|
+
key: match.key,
|
|
4963
|
+
value: match.value,
|
|
4964
|
+
normalizedIdentity: match.normalizedIdentity
|
|
4965
|
+
}] : [];
|
|
4966
|
+
}));
|
|
4967
|
+
});
|
|
4968
|
+
}
|
|
4969
|
+
function workspaceIdentities(db, candidates) {
|
|
4970
|
+
const repoIds = [...new Set(candidates.flatMap((candidate) => candidate.repoId === void 0 ? [] : [candidate.repoId]))].sort((a, b) => a - b);
|
|
4971
|
+
if (repoIds.length === 0) return [];
|
|
4972
|
+
const placeholders3 = repoIds.map(() => "?").join(",");
|
|
4973
|
+
const rows2 = db.prepare(`SELECT id repoId,name repoName,package_name packageName
|
|
4974
|
+
FROM repositories WHERE workspace_id IN (
|
|
4975
|
+
SELECT DISTINCT workspace_id FROM repositories WHERE id IN (${placeholders3})
|
|
4976
|
+
) ORDER BY workspace_id,name,absolute_path,id`).all(...repoIds);
|
|
4977
|
+
return rows2.flatMap((row) => {
|
|
4978
|
+
const repoId = numberValue(row.repoId);
|
|
4979
|
+
const repoName = stringValue4(row.repoName);
|
|
4980
|
+
return repoId === void 0 || !repoName ? [] : [{
|
|
4981
|
+
repoId,
|
|
4982
|
+
repoName,
|
|
4983
|
+
packageName: stringValue4(row.packageName)
|
|
4984
|
+
}];
|
|
4985
|
+
});
|
|
4986
|
+
}
|
|
4987
|
+
function deduplicateProposals(rows2) {
|
|
4988
|
+
const sorted = [...rows2].sort((left, right) => left.operationId - right.operationId || left.key.localeCompare(right.key) || left.value.localeCompare(right.value) || left.provenance.sourceKind.localeCompare(right.provenance.sourceKind));
|
|
4989
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4990
|
+
return sorted.filter((row) => {
|
|
4991
|
+
const key = [row.operationId, row.key, row.value, row.normalizedIdentity].join(":");
|
|
4992
|
+
if (seen.has(key)) return false;
|
|
4993
|
+
seen.add(key);
|
|
4994
|
+
return true;
|
|
4995
|
+
});
|
|
4996
|
+
}
|
|
4997
|
+
function ownerAgrees(candidate, owner) {
|
|
4998
|
+
return candidate.repoId !== void 0 && owner?.repoId !== void 0 && owner.repoId === candidate.repoId;
|
|
4999
|
+
}
|
|
5000
|
+
function implementationOwner(db, operationId) {
|
|
5001
|
+
const rows2 = db.prepare(
|
|
5002
|
+
`SELECT r.id repoId
|
|
5003
|
+
FROM graph_edges e JOIN handler_methods hm ON hm.id=CAST(e.to_id AS INTEGER)
|
|
5004
|
+
JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
5005
|
+
JOIN repositories r ON r.id=hc.repo_id
|
|
5006
|
+
WHERE e.edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND e.status='resolved'
|
|
5007
|
+
AND e.from_kind='operation' AND e.from_id=?
|
|
5008
|
+
ORDER BY r.id,hm.id,e.id`
|
|
5009
|
+
).all(String(operationId));
|
|
5010
|
+
if (rows2.length !== 1) return void 0;
|
|
5011
|
+
const row = rows2[0];
|
|
5012
|
+
if (!row) return void 0;
|
|
5013
|
+
return {
|
|
5014
|
+
repoId: numberValue(row.repoId)
|
|
5015
|
+
};
|
|
5016
|
+
}
|
|
5017
|
+
function normalizeIdentity(value, npmPackage = false) {
|
|
5018
|
+
const unscoped = npmPackage && /^@[^/]+\/[^/]+$/.test(value) ? value.slice(value.indexOf("/") + 1) : value;
|
|
5019
|
+
return unscoped.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
5020
|
+
}
|
|
5021
|
+
function stringValue4(value) {
|
|
5022
|
+
return typeof value === "string" ? value : void 0;
|
|
5023
|
+
}
|
|
5024
|
+
function numberValue(value) {
|
|
5025
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
5026
|
+
}
|
|
5027
|
+
function escapeRegex(value) {
|
|
5028
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5029
|
+
}
|
|
5030
|
+
|
|
5031
|
+
// src/trace/003-dynamic-references.ts
|
|
5032
|
+
function dynamicReferenceRows(db, workspaceId, callerRepoId, callerRepo) {
|
|
5033
|
+
if (callerRepoId === void 0 && callerRepo === void 0) return [];
|
|
5034
|
+
const rows2 = db.prepare(
|
|
5035
|
+
`SELECT COALESCE(b.alias,b.alias_expr) alias,b.destination_expr destination,
|
|
5036
|
+
b.service_path_expr servicePath,'service_binding' sourceKind,r.name repoName,
|
|
5037
|
+
b.source_file sourceFile,b.source_line sourceLine,0 sourcePriority
|
|
5038
|
+
FROM service_bindings b JOIN repositories r ON r.id=b.repo_id
|
|
5039
|
+
WHERE (? IS NULL OR r.workspace_id=?)
|
|
5040
|
+
AND ((? IS NOT NULL AND r.id=?) OR (? IS NULL AND r.name=?))
|
|
5041
|
+
UNION ALL
|
|
5042
|
+
SELECT req.alias,req.destination,req.service_path,'cds_require',r.name,
|
|
5043
|
+
'package.json',1,1 FROM cds_requires req JOIN repositories r ON r.id=req.repo_id
|
|
5044
|
+
WHERE (? IS NULL OR r.workspace_id=?)
|
|
5045
|
+
AND ((? IS NOT NULL AND r.id=?) OR (? IS NULL AND r.name=?))
|
|
5046
|
+
ORDER BY sourcePriority,repoName,sourceFile,sourceLine`
|
|
5047
|
+
).all(
|
|
5048
|
+
workspaceId,
|
|
5049
|
+
workspaceId,
|
|
5050
|
+
callerRepoId,
|
|
5051
|
+
callerRepoId,
|
|
5052
|
+
callerRepoId,
|
|
5053
|
+
callerRepo,
|
|
5054
|
+
workspaceId,
|
|
5055
|
+
workspaceId,
|
|
5056
|
+
callerRepoId,
|
|
5057
|
+
callerRepoId,
|
|
5058
|
+
callerRepoId,
|
|
5059
|
+
callerRepo
|
|
5060
|
+
);
|
|
5061
|
+
return rows2.flatMap(referenceFromRow);
|
|
5062
|
+
}
|
|
5063
|
+
function dynamicReferenceProvenance(reference, kind, template, value) {
|
|
5064
|
+
return {
|
|
5065
|
+
sourceKind: `${reference.sourceKind}.${kind}`,
|
|
5066
|
+
value,
|
|
5067
|
+
rule: "exact_indexed_reference_template_match",
|
|
5068
|
+
template,
|
|
5069
|
+
sourceRepo: reference.repoName,
|
|
5070
|
+
sourceFile: reference.sourceFile,
|
|
5071
|
+
sourceLine: reference.sourceLine
|
|
5072
|
+
};
|
|
5073
|
+
}
|
|
5074
|
+
function referenceFromRow(row) {
|
|
5075
|
+
const sourceKind = row.sourceKind;
|
|
5076
|
+
const repoName = stringValue5(row.repoName);
|
|
5077
|
+
if (sourceKind !== "service_binding" && sourceKind !== "cds_require" || !repoName) return [];
|
|
5078
|
+
return [{
|
|
5079
|
+
alias: stringValue5(row.alias),
|
|
5080
|
+
destination: stringValue5(row.destination),
|
|
5081
|
+
servicePath: stringValue5(row.servicePath),
|
|
5082
|
+
sourceKind,
|
|
5083
|
+
repoName,
|
|
5084
|
+
sourceFile: stringValue5(row.sourceFile),
|
|
5085
|
+
sourceLine: numberValue2(row.sourceLine)
|
|
5086
|
+
}];
|
|
5087
|
+
}
|
|
5088
|
+
function stringValue5(value) {
|
|
5089
|
+
return typeof value === "string" ? value : void 0;
|
|
5090
|
+
}
|
|
5091
|
+
function numberValue2(value) {
|
|
5092
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
5093
|
+
}
|
|
5094
|
+
|
|
5095
|
+
// src/trace/dynamic-targets.ts
|
|
5096
|
+
function analyzeDynamicTargetCandidates(db, evidence, workspaceId, mode, maxCandidates) {
|
|
5097
|
+
const inputs = analysisInputs(evidence);
|
|
5098
|
+
if (inputs.required.length === 0) return void 0;
|
|
5099
|
+
const targets = candidateTargets(db, evidence, workspaceId);
|
|
5100
|
+
const references = dynamicReferenceRows(
|
|
5101
|
+
db,
|
|
5102
|
+
workspaceId,
|
|
5103
|
+
inputs.callerRepoId,
|
|
5104
|
+
inputs.callerRepo
|
|
5105
|
+
);
|
|
5106
|
+
const candidates = buildCandidates(db, targets, references, inputs);
|
|
5107
|
+
applyUniqueIdentityEvidence(db, candidates, inputs);
|
|
5108
|
+
finalizeCandidates(candidates, inputs.order);
|
|
5109
|
+
const ranked = stableRank(candidates);
|
|
5110
|
+
const inference = inferenceDecision(ranked);
|
|
5111
|
+
applyModeState(ranked, mode, inference);
|
|
5112
|
+
const viable = ranked.filter((candidate) => candidate.viable);
|
|
5113
|
+
const rejected = ranked.filter((candidate) => candidate.rejected);
|
|
5114
|
+
const shown = viable.slice(0, maxCandidates).map((candidate) => boundedCandidate(candidate, maxCandidates));
|
|
5115
|
+
const shownRejected = rejected.slice(0, maxCandidates).map((candidate) => boundedCandidate(candidate, maxCandidates));
|
|
5116
|
+
return {
|
|
5117
|
+
mode,
|
|
5118
|
+
maxCandidates,
|
|
5119
|
+
candidateCount: ranked.length,
|
|
5120
|
+
viableCandidateCount: viable.length,
|
|
5121
|
+
rejectedCandidateCount: rejected.length,
|
|
5122
|
+
shownCandidateCount: shown.length,
|
|
5123
|
+
omittedCandidateCount: Math.max(0, viable.length - shown.length),
|
|
5124
|
+
shownRejectedCandidateCount: shownRejected.length,
|
|
5125
|
+
omittedRejectedCandidateCount: Math.max(0, rejected.length - shownRejected.length),
|
|
5126
|
+
missingVariables: inputs.required.filter((key) => inputs.supplied[key] === void 0),
|
|
5127
|
+
requiredVariables: inputs.required,
|
|
5128
|
+
suppliedVariables: inputs.supplied,
|
|
5129
|
+
appliedSuppliedVariables: requiredSuppliedVariables(inputs),
|
|
5130
|
+
substitutedSignals: inputs.effective,
|
|
5131
|
+
candidates: shown,
|
|
5132
|
+
shownCandidates: shown,
|
|
5133
|
+
rejectedCandidates: shownRejected,
|
|
5134
|
+
suggestedVarSets: suggestedVarSets(viable, inputs.order, maxCandidates),
|
|
5135
|
+
inference
|
|
5136
|
+
};
|
|
5137
|
+
}
|
|
5138
|
+
function analysisInputs(evidence) {
|
|
5139
|
+
const original = templatesFromEvidence(evidence, "original");
|
|
5140
|
+
const effective = templatesFromEvidence(evidence, "effective");
|
|
5141
|
+
const requiredSources = placeholderSources(original);
|
|
5142
|
+
const required = Object.keys(requiredSources);
|
|
5143
|
+
const supplied = stringRecord(evidence.suppliedRuntimeVariables);
|
|
5144
|
+
return {
|
|
5145
|
+
original,
|
|
5146
|
+
effective,
|
|
5147
|
+
required,
|
|
5148
|
+
requiredSources,
|
|
5149
|
+
supplied,
|
|
5150
|
+
order: variableOrder(original, required),
|
|
5151
|
+
callerRepo: stringValue6(evidence.repo),
|
|
5152
|
+
callerRepoId: numberValue3(evidence.repoId)
|
|
5153
|
+
};
|
|
5154
|
+
}
|
|
5155
|
+
function candidateTargets(db, evidence, workspaceId) {
|
|
5156
|
+
const embedded = rowsFromEvidence(evidence.candidates);
|
|
5157
|
+
if (embedded.length > 0) return embedded;
|
|
5158
|
+
const operationPath = effectiveSignal(evidence, "operationPath");
|
|
5159
|
+
if (!operationPath || extractPlaceholders(operationPath).length > 0) return [];
|
|
5160
|
+
return queryOperationTargets(db, operationPath, workspaceId);
|
|
5161
|
+
}
|
|
5162
|
+
function rowsFromEvidence(value) {
|
|
5163
|
+
if (!Array.isArray(value)) return [];
|
|
5164
|
+
return value.flatMap((item) => {
|
|
5165
|
+
const row = record(item);
|
|
5166
|
+
const operationId = numberValue3(row.operationId);
|
|
5167
|
+
const repoName = stringValue6(row.repoName);
|
|
5168
|
+
const servicePath = stringValue6(row.servicePath);
|
|
5169
|
+
const operationPath = stringValue6(row.operationPath);
|
|
5170
|
+
const operationName = stringValue6(row.operationName) ?? operationPath?.replace(/^\//, "");
|
|
5171
|
+
if (operationId === void 0 || !repoName || !servicePath || !operationPath || !operationName) return [];
|
|
5172
|
+
return [{
|
|
5173
|
+
operationId,
|
|
5174
|
+
repoId: numberValue3(row.repoId),
|
|
5175
|
+
repoName,
|
|
5176
|
+
packageName: stringValue6(row.packageName),
|
|
5177
|
+
serviceName: stringValue6(row.serviceName) ?? "",
|
|
5178
|
+
qualifiedName: stringValue6(row.qualifiedName) ?? "",
|
|
5179
|
+
servicePath,
|
|
5180
|
+
operationPath,
|
|
5181
|
+
operationName,
|
|
5182
|
+
sourceFile: stringValue6(row.sourceFile) ?? "",
|
|
5183
|
+
sourceLine: numberValue3(row.sourceLine) ?? 0,
|
|
5184
|
+
score: numberValue3(row.score) ?? 0,
|
|
5185
|
+
reasons: stringArray(row.reasons)
|
|
5186
|
+
}];
|
|
5187
|
+
});
|
|
5188
|
+
}
|
|
5189
|
+
function queryOperationTargets(db, operationPath, workspaceId) {
|
|
5190
|
+
const simple = operationPath.replace(/^\//, "").split(".").at(-1) ?? operationPath;
|
|
5191
|
+
const rows2 = db.prepare(
|
|
5192
|
+
`SELECT o.id operationId,r.id repoId,r.name repoName,r.package_name packageName,
|
|
5193
|
+
s.service_name serviceName,s.qualified_name qualifiedName,s.service_path servicePath,
|
|
5194
|
+
o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,
|
|
5195
|
+
o.source_line sourceLine FROM cds_operations o
|
|
5196
|
+
JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
|
|
5197
|
+
WHERE (? IS NULL OR r.workspace_id=?)
|
|
5198
|
+
AND (o.operation_path IN (?,?) OR o.operation_name=?)
|
|
5199
|
+
ORDER BY r.name,s.service_path,o.operation_name,o.id`
|
|
5200
|
+
).all(workspaceId, workspaceId, operationPath, `/${simple}`, simple);
|
|
5201
|
+
return rows2.flatMap(operationTargetFromRow);
|
|
5202
|
+
}
|
|
5203
|
+
function operationTargetFromRow(row) {
|
|
5204
|
+
const operationId = numberValue3(row.operationId);
|
|
5205
|
+
const repoName = stringValue6(row.repoName);
|
|
5206
|
+
const servicePath = stringValue6(row.servicePath);
|
|
5207
|
+
const operationPath = stringValue6(row.operationPath);
|
|
5208
|
+
const operationName = stringValue6(row.operationName);
|
|
5209
|
+
if (operationId === void 0 || !repoName || !servicePath || !operationPath || !operationName) return [];
|
|
5210
|
+
return [{
|
|
5211
|
+
operationId,
|
|
5212
|
+
repoId: numberValue3(row.repoId),
|
|
5213
|
+
repoName,
|
|
5214
|
+
packageName: stringValue6(row.packageName),
|
|
5215
|
+
serviceName: stringValue6(row.serviceName) ?? "",
|
|
5216
|
+
qualifiedName: stringValue6(row.qualifiedName) ?? "",
|
|
5217
|
+
servicePath,
|
|
5218
|
+
operationPath,
|
|
5219
|
+
operationName,
|
|
5220
|
+
sourceFile: stringValue6(row.sourceFile) ?? "",
|
|
5221
|
+
sourceLine: numberValue3(row.sourceLine) ?? 0,
|
|
5222
|
+
score: 0.2,
|
|
5223
|
+
reasons: ["operation_path_match"]
|
|
5224
|
+
}];
|
|
5225
|
+
}
|
|
5226
|
+
function buildCandidates(db, targets, references, inputs) {
|
|
5227
|
+
return targets.map((target) => {
|
|
5228
|
+
const state = emptyCandidate(target, inputs);
|
|
5229
|
+
applyDirectSignal(state, inputs, "operationPath", target.operationPath, 0.25);
|
|
5230
|
+
applyDirectSignal(state, inputs, "servicePath", target.servicePath, 0.35);
|
|
5231
|
+
const matchingReferences = references.filter((reference) => reference.servicePath === target.servicePath);
|
|
5232
|
+
applyReferenceSignal(state, inputs, matchingReferences, "alias");
|
|
5233
|
+
applyReferenceSignal(state, inputs, matchingReferences, "destination");
|
|
5234
|
+
if (hasResolvedImplementation(db, target.operationId))
|
|
5235
|
+
addScore(state, 0.1, "implementation_edge_resolved");
|
|
5236
|
+
return state;
|
|
5237
|
+
});
|
|
5238
|
+
}
|
|
5239
|
+
function emptyCandidate(target, inputs) {
|
|
5240
|
+
return {
|
|
5241
|
+
candidateOperationId: target.operationId,
|
|
5242
|
+
repoId: target.repoId,
|
|
5243
|
+
repoName: target.repoName,
|
|
5244
|
+
packageName: target.packageName ?? void 0,
|
|
5245
|
+
serviceName: target.serviceName,
|
|
5246
|
+
qualifiedName: target.qualifiedName,
|
|
5247
|
+
servicePath: target.servicePath,
|
|
5248
|
+
operationPath: target.operationPath,
|
|
5249
|
+
operationName: target.operationName,
|
|
5250
|
+
sourceFile: target.sourceFile,
|
|
5251
|
+
sourceLine: target.sourceLine,
|
|
5252
|
+
originalTemplates: inputs.original,
|
|
5253
|
+
effectiveValues: inputs.effective,
|
|
5254
|
+
requiredVariables: inputs.required,
|
|
5255
|
+
requiredVariableSources: inputs.requiredSources,
|
|
5256
|
+
suppliedVariables: inputs.supplied,
|
|
5257
|
+
completeVariables: { ...inputs.supplied },
|
|
5258
|
+
derivedVariables: {},
|
|
5259
|
+
derivedVariableSources: {},
|
|
5260
|
+
derivationProvenance: {},
|
|
5261
|
+
missingVariables: [],
|
|
5262
|
+
conflicts: [],
|
|
5263
|
+
score: Math.max(0.2, Number(target.score ?? 0)),
|
|
5264
|
+
explicitSignalStrength: 0,
|
|
5265
|
+
reasons: nonEmptyStrings(target.reasons, ["operation_path_match"]),
|
|
5266
|
+
rejectedReasons: [],
|
|
5267
|
+
inferenceBlockReasons: [],
|
|
5268
|
+
viable: true,
|
|
5269
|
+
rejected: false,
|
|
5270
|
+
selected: false,
|
|
5271
|
+
exploratory: false
|
|
5272
|
+
};
|
|
5273
|
+
}
|
|
5274
|
+
function applyDirectSignal(state, inputs, kind, concrete, score) {
|
|
5275
|
+
const effective = inputs.effective[kind];
|
|
5276
|
+
const original = inputs.original[kind];
|
|
5277
|
+
if (effective && !matchTemplate(effective, concrete)) {
|
|
5278
|
+
reject(state, `${signalCode(kind)}_contradicts_runtime_substitution`);
|
|
5279
|
+
return;
|
|
5280
|
+
}
|
|
5281
|
+
if (!effective) return;
|
|
5282
|
+
const suppliedKeys = extractPlaceholders(original).filter((key) => inputs.supplied[key] !== void 0);
|
|
5283
|
+
state.explicitSignalStrength += suppliedKeys.length;
|
|
5284
|
+
const matched = matchTemplate(original, concrete) ?? {};
|
|
5285
|
+
for (const [key, value] of Object.entries(matched)) {
|
|
5286
|
+
addDerivation(state, key, value, {
|
|
5287
|
+
sourceKind: `${signalCode(kind)}_template`,
|
|
5288
|
+
value,
|
|
5289
|
+
rule: "exact_template_match",
|
|
5290
|
+
template: original
|
|
5291
|
+
});
|
|
5292
|
+
}
|
|
5293
|
+
addScore(state, score, `${signalCode(kind)}_template_match`);
|
|
5294
|
+
}
|
|
5295
|
+
function applyReferenceSignal(state, inputs, references, kind) {
|
|
5296
|
+
const original = inputs.original[kind];
|
|
5297
|
+
const effective = inputs.effective[kind];
|
|
5298
|
+
if (!original || extractPlaceholders(original).length === 0) return;
|
|
5299
|
+
const values = references.flatMap((reference) => {
|
|
5300
|
+
const concrete = kind === "alias" ? reference.alias : reference.destination;
|
|
5301
|
+
return isConcrete(concrete) ? [{ reference, concrete }] : [];
|
|
5302
|
+
});
|
|
5303
|
+
if (effective && extractPlaceholders(effective).length === 0 && values.length > 0 && !values.some(({ concrete }) => concrete === effective)) {
|
|
5304
|
+
reject(state, `${kind}_contradicts_runtime_substitution`);
|
|
5305
|
+
}
|
|
5306
|
+
let matchedSignal = false;
|
|
5307
|
+
for (const { reference, concrete } of values) {
|
|
5308
|
+
const matched = matchTemplate(original, concrete);
|
|
5309
|
+
if (!matched) continue;
|
|
5310
|
+
matchedSignal = true;
|
|
5311
|
+
for (const [key, value] of Object.entries(matched)) {
|
|
5312
|
+
addDerivation(
|
|
5313
|
+
state,
|
|
5314
|
+
key,
|
|
5315
|
+
value,
|
|
5316
|
+
dynamicReferenceProvenance(reference, kind, original, value)
|
|
5317
|
+
);
|
|
5318
|
+
}
|
|
3688
5319
|
}
|
|
3689
|
-
|
|
5320
|
+
if (matchedSignal) {
|
|
5321
|
+
state.explicitSignalStrength += extractPlaceholders(original).filter((key) => inputs.supplied[key] !== void 0).length;
|
|
5322
|
+
addScore(state, 0.2, `${kind}_template_match`);
|
|
5323
|
+
}
|
|
5324
|
+
}
|
|
5325
|
+
function applyUniqueIdentityEvidence(db, candidates, inputs) {
|
|
5326
|
+
for (const derivation of uniqueIdentityDerivations(db, candidates, inputs.original)) {
|
|
5327
|
+
const candidate = candidates.find((item) => item.candidateOperationId === derivation.operationId);
|
|
5328
|
+
if (!candidate) continue;
|
|
5329
|
+
addDerivation(candidate, derivation.key, derivation.value, derivation.provenance);
|
|
5330
|
+
addScore(candidate, 0.2, "exact_identity_template_match");
|
|
5331
|
+
}
|
|
5332
|
+
}
|
|
5333
|
+
function addDerivation(state, key, value, provenance) {
|
|
5334
|
+
const priorProvenance = state.derivationProvenance[key] ?? [];
|
|
5335
|
+
state.derivationProvenance[key] = uniqueProvenance([...priorProvenance, provenance]);
|
|
5336
|
+
const supplied = state.suppliedVariables[key];
|
|
5337
|
+
if (supplied !== void 0 && supplied !== value) {
|
|
5338
|
+
addConflict(state, key, [supplied, value], "explicit_value_conflicts_with_derived_value");
|
|
5339
|
+
return;
|
|
5340
|
+
}
|
|
5341
|
+
const prior = state.derivedVariables[key];
|
|
5342
|
+
if (prior !== void 0 && prior !== value) {
|
|
5343
|
+
addConflict(state, key, [prior, value], "conflicting_strong_derivations");
|
|
5344
|
+
return;
|
|
5345
|
+
}
|
|
5346
|
+
if (supplied === void 0) state.derivedVariables[key] = value;
|
|
5347
|
+
state.completeVariables[key] = supplied ?? value;
|
|
5348
|
+
state.derivedVariableSources[key] ??= provenance;
|
|
5349
|
+
}
|
|
5350
|
+
function addConflict(state, key, values, reason) {
|
|
5351
|
+
const sources = (state.derivationProvenance[key] ?? []).map((item) => item.sourceKind).sort();
|
|
5352
|
+
state.conflicts.push({ key, values: [...new Set(values)].sort(), reason, sources });
|
|
5353
|
+
reject(state, reason);
|
|
5354
|
+
}
|
|
5355
|
+
function uniqueProvenance(rows2) {
|
|
5356
|
+
const sorted = [...rows2].sort((left, right) => left.sourceKind.localeCompare(right.sourceKind) || String(left.matchedName ?? "").localeCompare(String(right.matchedName ?? "")) || left.value.localeCompare(right.value));
|
|
5357
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5358
|
+
return sorted.filter((row) => {
|
|
5359
|
+
const key = JSON.stringify(row);
|
|
5360
|
+
if (seen.has(key)) return false;
|
|
5361
|
+
seen.add(key);
|
|
5362
|
+
return true;
|
|
5363
|
+
});
|
|
5364
|
+
}
|
|
5365
|
+
function finalizeCandidates(candidates, order) {
|
|
5366
|
+
for (const candidate of candidates) {
|
|
5367
|
+
candidate.missingVariables = order.filter((key) => candidate.completeVariables[key] === void 0);
|
|
5368
|
+
candidate.viable = candidate.rejectedReasons.length === 0;
|
|
5369
|
+
candidate.rejected = !candidate.viable;
|
|
5370
|
+
if (candidate.missingVariables.length === 0 && candidate.viable)
|
|
5371
|
+
addScore(candidate, 0.15, "all_runtime_variables_derived");
|
|
5372
|
+
else if (candidate.missingVariables.length > 0)
|
|
5373
|
+
addReason(candidate, "missing_required_runtime_variable");
|
|
5374
|
+
candidate.score = Math.max(0, Math.min(1, candidate.score));
|
|
5375
|
+
candidate.cli = candidate.missingVariables.length === 0 && candidate.viable ? cliFor(candidate.completeVariables, order) : void 0;
|
|
5376
|
+
}
|
|
5377
|
+
}
|
|
5378
|
+
function stableRank(candidates) {
|
|
5379
|
+
return [...candidates].sort((left, right) => Number(right.viable) - Number(left.viable) || right.score - left.score || right.explicitSignalStrength - left.explicitSignalStrength || left.repoName.localeCompare(right.repoName) || String(left.packageName ?? "").localeCompare(String(right.packageName ?? "")) || left.servicePath.localeCompare(right.servicePath) || left.operationPath.localeCompare(right.operationPath) || left.operationName.localeCompare(right.operationName) || left.candidateOperationId - right.candidateOperationId);
|
|
5380
|
+
}
|
|
5381
|
+
function inferenceDecision(candidates) {
|
|
5382
|
+
const viable = candidates.filter((candidate) => candidate.viable);
|
|
5383
|
+
const first = viable[0];
|
|
5384
|
+
const second = viable[1];
|
|
5385
|
+
if (!first || first.missingVariables.length > 0)
|
|
5386
|
+
return { status: "unresolved", reason: "missing_required_runtime_variable" };
|
|
5387
|
+
if (first.score < 0.85)
|
|
5388
|
+
return { status: "unresolved", reason: "candidate_score_below_inference_threshold" };
|
|
5389
|
+
const scoreGap = second ? Number((first.score - second.score).toFixed(12)) : void 0;
|
|
5390
|
+
if (second && scoreGap !== void 0 && scoreGap <= 0.05) {
|
|
5391
|
+
const reason = scoreGap === 0 ? "candidate_tied_with_equal_score" : "candidate_within_inference_margin";
|
|
5392
|
+
for (const candidate of viable.filter((item) => first.score - item.score <= 0.05))
|
|
5393
|
+
addInferenceBlock(candidate, reason);
|
|
5394
|
+
return { status: "ambiguous", reason, scoreGap, requiredMargin: 0.05 };
|
|
5395
|
+
}
|
|
5396
|
+
return {
|
|
5397
|
+
status: "resolved",
|
|
5398
|
+
candidateOperationId: first.candidateOperationId,
|
|
5399
|
+
inferredVariables: first.completeVariables,
|
|
5400
|
+
score: first.score,
|
|
5401
|
+
reasons: first.reasons
|
|
5402
|
+
};
|
|
5403
|
+
}
|
|
5404
|
+
function boundedCandidate(candidate, limit) {
|
|
5405
|
+
const derivationProvenance = Object.fromEntries(
|
|
5406
|
+
Object.entries(candidate.derivationProvenance).sort(([left], [right]) => left.localeCompare(right)).map(([key, rows2]) => [key, rows2.slice(0, limit)])
|
|
5407
|
+
);
|
|
5408
|
+
const conflicts = candidate.conflicts.slice(0, limit).map((conflict) => ({
|
|
5409
|
+
...conflict,
|
|
5410
|
+
sources: conflict.sources.slice(0, limit)
|
|
5411
|
+
}));
|
|
5412
|
+
return { ...candidate, derivationProvenance, conflicts };
|
|
5413
|
+
}
|
|
5414
|
+
function applyModeState(candidates, mode, inference) {
|
|
5415
|
+
const selectedId = mode === "infer" && inference.status === "resolved" ? numberValue3(inference.candidateOperationId) : void 0;
|
|
5416
|
+
for (const candidate of candidates) {
|
|
5417
|
+
candidate.selected = selectedId === candidate.candidateOperationId;
|
|
5418
|
+
candidate.exploratory = mode === "candidates" && candidate.viable;
|
|
5419
|
+
}
|
|
5420
|
+
}
|
|
5421
|
+
function suggestedVarSets(candidates, order, limit) {
|
|
5422
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5423
|
+
const rows2 = [];
|
|
5424
|
+
for (const candidate of candidates) {
|
|
5425
|
+
if (!candidate.cli || candidate.missingVariables.length > 0) continue;
|
|
5426
|
+
if (seen.has(candidate.cli)) continue;
|
|
5427
|
+
seen.add(candidate.cli);
|
|
5428
|
+
rows2.push({ variables: orderedVariables(candidate.completeVariables, order), cli: candidate.cli });
|
|
5429
|
+
if (rows2.length >= limit) break;
|
|
5430
|
+
}
|
|
5431
|
+
return rows2;
|
|
5432
|
+
}
|
|
5433
|
+
function hasResolvedImplementation(db, operationId) {
|
|
5434
|
+
return Boolean(db.prepare(
|
|
5435
|
+
"SELECT 1 FROM graph_edges WHERE edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND status='resolved' AND from_kind='operation' AND from_id=? LIMIT 1"
|
|
5436
|
+
).get(String(operationId)));
|
|
5437
|
+
}
|
|
5438
|
+
function templatesFromEvidence(evidence, phase) {
|
|
5439
|
+
return {
|
|
5440
|
+
servicePath: substitutionSignal(evidence, "servicePath", phase),
|
|
5441
|
+
operationPath: substitutionSignal(evidence, "operationPath", phase),
|
|
5442
|
+
alias: substitutionSignal(
|
|
5443
|
+
evidence,
|
|
5444
|
+
evidence.serviceAliasExpr !== void 0 ? "serviceAliasExpr" : "serviceAlias",
|
|
5445
|
+
phase
|
|
5446
|
+
),
|
|
5447
|
+
destination: substitutionSignal(evidence, "destination", phase)
|
|
5448
|
+
};
|
|
5449
|
+
}
|
|
5450
|
+
function substitutionSignal(evidence, key, phase) {
|
|
5451
|
+
const substitution = record(record(evidence.runtimeSubstitutions)[key]);
|
|
5452
|
+
return stringValue6(substitution[phase]) ?? stringValue6(evidence[key]);
|
|
5453
|
+
}
|
|
5454
|
+
function effectiveSignal(evidence, key) {
|
|
5455
|
+
return substitutionSignal(evidence, key, "effective");
|
|
5456
|
+
}
|
|
5457
|
+
function placeholderSources(templates) {
|
|
5458
|
+
const sources = {};
|
|
5459
|
+
for (const [kind, template] of Object.entries(templates)) {
|
|
5460
|
+
if (typeof template !== "string") continue;
|
|
5461
|
+
for (const key of extractPlaceholders(template))
|
|
5462
|
+
sources[key] = [.../* @__PURE__ */ new Set([...sources[key] ?? [], `${kind}:${template}`])].sort();
|
|
5463
|
+
}
|
|
5464
|
+
return Object.fromEntries(Object.entries(sources).sort(([left], [right]) => left.localeCompare(right)));
|
|
5465
|
+
}
|
|
5466
|
+
function variableOrder(templates, required) {
|
|
5467
|
+
const ordered = [
|
|
5468
|
+
templates.servicePath,
|
|
5469
|
+
templates.operationPath,
|
|
5470
|
+
templates.alias,
|
|
5471
|
+
templates.destination
|
|
5472
|
+
].flatMap((value) => extractPlaceholders(value));
|
|
5473
|
+
return [.../* @__PURE__ */ new Set([...ordered, ...required])];
|
|
5474
|
+
}
|
|
5475
|
+
function matchTemplate(template, concrete) {
|
|
5476
|
+
if (!template || !concrete) return void 0;
|
|
5477
|
+
const keys = extractPlaceholders(template);
|
|
5478
|
+
if (keys.length === 0) return template === concrete ? {} : void 0;
|
|
5479
|
+
const match = new RegExp(`^${templateToPattern(template)}$`).exec(concrete);
|
|
5480
|
+
if (!match) return void 0;
|
|
5481
|
+
const values = {};
|
|
5482
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
5483
|
+
const key = keys[index];
|
|
5484
|
+
const value = match[index + 1];
|
|
5485
|
+
if (!key || value === void 0) return void 0;
|
|
5486
|
+
if (values[key] !== void 0 && values[key] !== value) return void 0;
|
|
5487
|
+
values[key] = value;
|
|
5488
|
+
}
|
|
5489
|
+
return values;
|
|
5490
|
+
}
|
|
5491
|
+
function templateToPattern(template) {
|
|
5492
|
+
let pattern = "";
|
|
5493
|
+
let lastIndex = 0;
|
|
5494
|
+
for (const match of template.matchAll(/\$\{([^}]*)\}/g)) {
|
|
5495
|
+
pattern += escapeRegex2(template.slice(lastIndex, match.index));
|
|
5496
|
+
pattern += "([^/]+?)";
|
|
5497
|
+
lastIndex = (match.index ?? 0) + match[0].length;
|
|
5498
|
+
}
|
|
5499
|
+
return `${pattern}${escapeRegex2(template.slice(lastIndex))}`;
|
|
5500
|
+
}
|
|
5501
|
+
function cliFor(variables, order) {
|
|
5502
|
+
return order.filter((key) => variables[key] !== void 0).map((key) => `--var ${shellArgument(`${key}=${variables[key]}`)}`).join(" ");
|
|
5503
|
+
}
|
|
5504
|
+
function shellArgument(value) {
|
|
5505
|
+
return /^[A-Za-z0-9_./:=+-]+$/.test(value) ? value : `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
5506
|
+
}
|
|
5507
|
+
function orderedVariables(variables, order) {
|
|
5508
|
+
return Object.fromEntries(order.flatMap((key) => variables[key] === void 0 ? [] : [[key, variables[key]]]));
|
|
5509
|
+
}
|
|
5510
|
+
function addScore(state, amount, reason) {
|
|
5511
|
+
state.score += amount;
|
|
5512
|
+
addReason(state, reason);
|
|
5513
|
+
}
|
|
5514
|
+
function addReason(state, reason) {
|
|
5515
|
+
if (!state.reasons.includes(reason)) state.reasons.push(reason);
|
|
5516
|
+
}
|
|
5517
|
+
function reject(state, reason) {
|
|
5518
|
+
rejectReasonOnly(state, reason);
|
|
5519
|
+
state.viable = false;
|
|
5520
|
+
state.rejected = true;
|
|
5521
|
+
}
|
|
5522
|
+
function rejectReasonOnly(state, reason) {
|
|
5523
|
+
if (!state.rejectedReasons.includes(reason)) state.rejectedReasons.push(reason);
|
|
5524
|
+
}
|
|
5525
|
+
function addInferenceBlock(state, reason) {
|
|
5526
|
+
addReason(state, reason);
|
|
5527
|
+
if (!state.inferenceBlockReasons.includes(reason))
|
|
5528
|
+
state.inferenceBlockReasons.push(reason);
|
|
5529
|
+
}
|
|
5530
|
+
function requiredSuppliedVariables(inputs) {
|
|
5531
|
+
return Object.fromEntries(inputs.required.flatMap((key) => inputs.supplied[key] === void 0 ? [] : [[key, inputs.supplied[key]]]));
|
|
5532
|
+
}
|
|
5533
|
+
function signalCode(kind) {
|
|
5534
|
+
return kind === "servicePath" ? "service_path" : "operation_path";
|
|
5535
|
+
}
|
|
5536
|
+
function record(value) {
|
|
5537
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
5538
|
+
}
|
|
5539
|
+
function stringRecord(value) {
|
|
5540
|
+
const entries = Object.entries(record(value)).filter((entry) => typeof entry[1] === "string").sort(([left], [right]) => left.localeCompare(right));
|
|
5541
|
+
return Object.fromEntries(entries);
|
|
5542
|
+
}
|
|
5543
|
+
function stringValue6(value) {
|
|
5544
|
+
return typeof value === "string" ? value : void 0;
|
|
5545
|
+
}
|
|
5546
|
+
function numberValue3(value) {
|
|
5547
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
5548
|
+
}
|
|
5549
|
+
function stringArray(value) {
|
|
5550
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
5551
|
+
}
|
|
5552
|
+
function nonEmptyStrings(value, fallback) {
|
|
5553
|
+
const values = stringArray(value).filter((item) => item.length > 0);
|
|
5554
|
+
return values.length > 0 ? values : fallback;
|
|
5555
|
+
}
|
|
5556
|
+
function isConcrete(value) {
|
|
5557
|
+
return typeof value === "string" && value.length > 0 && extractPlaceholders(value).length === 0;
|
|
5558
|
+
}
|
|
5559
|
+
function escapeRegex2(value) {
|
|
5560
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5561
|
+
}
|
|
5562
|
+
|
|
5563
|
+
// src/trace/evidence.ts
|
|
5564
|
+
function baseTraceEvidence(row, call, persistedEvidence, contextualEvidence) {
|
|
5565
|
+
const evidence = { ...persistedEvidence, ...contextualEvidence ?? {} };
|
|
5566
|
+
return {
|
|
5567
|
+
...evidence,
|
|
5568
|
+
graphEdgeId: row.id,
|
|
5569
|
+
persistedGraphEdgeId: row.id > 0 ? row.id : void 0,
|
|
5570
|
+
outboundCallId: call.id,
|
|
5571
|
+
callSite: { sourceFile: call.source_file, sourceLine: call.source_line },
|
|
5572
|
+
callType: call.call_type,
|
|
5573
|
+
repoId: call.repo_id,
|
|
5574
|
+
sourceFile: call.source_file,
|
|
5575
|
+
sourceLine: call.source_line,
|
|
5576
|
+
file: call.source_file,
|
|
5577
|
+
line: call.source_line,
|
|
5578
|
+
persistedTarget: { kind: row.to_kind, id: row.to_id },
|
|
5579
|
+
contextualResolutionParticipated: Boolean(contextualEvidence?.contextualServiceBindingAttempted),
|
|
5580
|
+
persistedResolution: persistedResolution(row)
|
|
5581
|
+
};
|
|
5582
|
+
}
|
|
5583
|
+
function runtimeResolution(db, row, evidence, options, workspaceId, contextualUnresolvedReason) {
|
|
5584
|
+
const dynamicMode = options.dynamicMode ?? "strict";
|
|
5585
|
+
const candidateCap = positiveCandidateCap(options.maxDynamicCandidates);
|
|
5586
|
+
if (!isDynamicRemoteOperationEdge(row, evidence))
|
|
5587
|
+
return unchangedRuntimeResolution(
|
|
5588
|
+
row,
|
|
5589
|
+
boundDynamicEvidence(evidence, candidateCap),
|
|
5590
|
+
contextualUnresolvedReason
|
|
5591
|
+
);
|
|
5592
|
+
const substituted = evidenceWithRuntimeVariables(evidence, options.vars);
|
|
5593
|
+
const analysis = analyzeDynamicTargetCandidates(
|
|
5594
|
+
db,
|
|
5595
|
+
substituted,
|
|
5596
|
+
workspaceId,
|
|
5597
|
+
dynamicMode,
|
|
5598
|
+
candidateCap
|
|
5599
|
+
);
|
|
5600
|
+
const enriched = boundDynamicEvidence(
|
|
5601
|
+
analysis ? evidenceWithDynamicAnalysis(substituted, analysis) : substituted,
|
|
5602
|
+
candidateCap
|
|
5603
|
+
);
|
|
5604
|
+
if (analysis && analysis.candidateCount > 0 && analysis.viableCandidateCount === 0 && Object.keys(analysis.appliedSuppliedVariables).length > 0)
|
|
5605
|
+
return noCandidateRuntimeResolution(row, enriched);
|
|
5606
|
+
if (dynamicMode === "infer") {
|
|
5607
|
+
const inferred = inferredTarget(analysis);
|
|
5608
|
+
if (inferred)
|
|
5609
|
+
return resolvedRuntimeResolution(row, enriched, inferred, inferred.reasons);
|
|
5610
|
+
}
|
|
5611
|
+
if (!hasApplicableRuntimeVariables(evidence, options.vars)) {
|
|
5612
|
+
const unresolvedReason2 = contextualUnresolvedReason ?? row.unresolved_reason;
|
|
5613
|
+
const withSections = withEffectiveResolution(enriched, row, unresolvedReason2);
|
|
5614
|
+
return { row, evidence: withSections, unresolvedReason: unresolvedReason2 };
|
|
5615
|
+
}
|
|
5616
|
+
const resolution = resolveRuntimeOperation(db, enriched, workspaceId);
|
|
5617
|
+
if (resolution.target)
|
|
5618
|
+
return resolvedRuntimeResolution(
|
|
5619
|
+
row,
|
|
5620
|
+
enriched,
|
|
5621
|
+
resolution.target,
|
|
5622
|
+
resolution.reasons
|
|
5623
|
+
);
|
|
5624
|
+
if (analysis && analysis.viableCandidateCount === 0 && Object.keys(analysis.appliedSuppliedVariables).length > 0)
|
|
5625
|
+
return noCandidateRuntimeResolution(row, enriched);
|
|
5626
|
+
const unresolvedReason = runtimeUnresolvedReason(resolution);
|
|
5627
|
+
return { row, evidence: withEffectiveResolution(enriched, row, unresolvedReason, resolution), unresolvedReason };
|
|
5628
|
+
}
|
|
5629
|
+
function unchangedRuntimeResolution(row, evidence, contextualUnresolvedReason) {
|
|
5630
|
+
const unresolvedReason = contextualUnresolvedReason ?? row.unresolved_reason;
|
|
5631
|
+
return {
|
|
5632
|
+
row,
|
|
5633
|
+
evidence: withEffectiveResolution(evidence, row, unresolvedReason),
|
|
5634
|
+
unresolvedReason
|
|
5635
|
+
};
|
|
5636
|
+
}
|
|
5637
|
+
function noCandidateRuntimeResolution(row, evidence) {
|
|
5638
|
+
const unresolvedReason = "No candidate remained after runtime substitution";
|
|
5639
|
+
return {
|
|
5640
|
+
row,
|
|
5641
|
+
evidence: withEffectiveResolution(evidence, row, unresolvedReason),
|
|
5642
|
+
unresolvedReason
|
|
5643
|
+
};
|
|
5644
|
+
}
|
|
5645
|
+
function resolvedRuntimeResolution(row, evidence, target, reasons) {
|
|
5646
|
+
const resolvedRow = {
|
|
5647
|
+
...row,
|
|
5648
|
+
status: "resolved",
|
|
5649
|
+
to_kind: "operation",
|
|
5650
|
+
to_id: String(target.operationId),
|
|
5651
|
+
unresolved_reason: void 0,
|
|
5652
|
+
confidence: Math.max(0, Math.min(1, target.score))
|
|
5653
|
+
};
|
|
5654
|
+
const resolution = {
|
|
5655
|
+
status: "resolved",
|
|
5656
|
+
target,
|
|
5657
|
+
candidates: [],
|
|
5658
|
+
reasons
|
|
5659
|
+
};
|
|
5660
|
+
return {
|
|
5661
|
+
row: resolvedRow,
|
|
5662
|
+
evidence: withEffectiveResolution(evidence, resolvedRow, void 0, resolution),
|
|
5663
|
+
target
|
|
5664
|
+
};
|
|
5665
|
+
}
|
|
5666
|
+
function runtimeVariableDiagnostic(edges) {
|
|
5667
|
+
const totals = runtimeDiagnosticTotals(edges);
|
|
5668
|
+
const missingVariables = [...totals.missing].sort();
|
|
3690
5669
|
if (missingVariables.length === 0) return void 0;
|
|
5670
|
+
const shownSuggestions = totals.candidateSuggestions.slice(0, totals.maxCandidates);
|
|
5671
|
+
const shownRejected = totals.rejectedCandidates.slice(0, totals.maxCandidates);
|
|
5672
|
+
const shownCandidateCount = shownSuggestions.length;
|
|
3691
5673
|
return {
|
|
3692
5674
|
severity: "warning",
|
|
3693
5675
|
code: "trace_runtime_variables_missing",
|
|
3694
5676
|
message: `Runtime variables are required to resolve dynamic trace targets: ${missingVariables.join(", ")}`,
|
|
3695
5677
|
missingVariables,
|
|
3696
|
-
suggestions: missingVariables.map((key) => `--var ${key}=<value>`)
|
|
5678
|
+
suggestions: missingVariables.map((key) => `--var ${key}=<value>`),
|
|
5679
|
+
candidateCount: totals.candidateCount,
|
|
5680
|
+
viableCandidateCount: totals.viableCandidateCount,
|
|
5681
|
+
rejectedCandidateCount: totals.rejectedCandidateCount,
|
|
5682
|
+
shownCandidateCount,
|
|
5683
|
+
omittedCandidateCount: Math.max(0, totals.viableCandidateCount - shownCandidateCount),
|
|
5684
|
+
maxDynamicCandidates: totals.maxCandidates,
|
|
5685
|
+
shownRejectedCandidateCount: shownRejected.length,
|
|
5686
|
+
omittedRejectedCandidateCount: Math.max(0, totals.rejectedCandidateCount - shownRejected.length),
|
|
5687
|
+
candidateSuggestions: shownSuggestions,
|
|
5688
|
+
rejectedCandidates: shownRejected,
|
|
5689
|
+
suggestedVarSets: uniqueCliRows(totals.suggestedVarSets).slice(0, totals.maxCandidates),
|
|
5690
|
+
copyableExamples: copyableExamples(totals.suggestedVarSets, totals.candidateCount, totals.maxCandidates)
|
|
5691
|
+
};
|
|
5692
|
+
}
|
|
5693
|
+
function runtimeDiagnosticTotals(edges) {
|
|
5694
|
+
const missing = /* @__PURE__ */ new Set();
|
|
5695
|
+
let candidateCount = 0;
|
|
5696
|
+
let viableCandidateCount = 0;
|
|
5697
|
+
let rejectedCandidateCount = 0;
|
|
5698
|
+
let maxCandidates = 5;
|
|
5699
|
+
const candidateSuggestions = [];
|
|
5700
|
+
const rejectedCandidates = [];
|
|
5701
|
+
const suggestedVarSets2 = [];
|
|
5702
|
+
for (const edge of edges) {
|
|
5703
|
+
const effective = parseObject(edge.evidence.effectiveResolution);
|
|
5704
|
+
if (!["dynamic", "unresolved", "ambiguous"].includes(String(effective.status ?? "")))
|
|
5705
|
+
continue;
|
|
5706
|
+
const substitutions = edge.evidence.runtimeSubstitutions;
|
|
5707
|
+
if (!substitutions || typeof substitutions !== "object" || Array.isArray(substitutions)) continue;
|
|
5708
|
+
for (const value of Object.values(substitutions))
|
|
5709
|
+
for (const key of value.missing ?? []) missing.add(key);
|
|
5710
|
+
const exploration = parseObject(edge.evidence.dynamicTargetExploration);
|
|
5711
|
+
maxCandidates = positiveCandidateCap(numeric(exploration.maxCandidates) || maxCandidates);
|
|
5712
|
+
candidateCount += numeric(exploration.candidateCount);
|
|
5713
|
+
viableCandidateCount += numeric(exploration.viableCandidateCount);
|
|
5714
|
+
rejectedCandidateCount += numeric(exploration.rejectedCandidateCount);
|
|
5715
|
+
appendBounded(
|
|
5716
|
+
candidateSuggestions,
|
|
5717
|
+
recordArray(edge.evidence.dynamicTargetCandidateSuggestions),
|
|
5718
|
+
maxCandidates
|
|
5719
|
+
);
|
|
5720
|
+
appendBounded(
|
|
5721
|
+
rejectedCandidates,
|
|
5722
|
+
recordArray(exploration.rejectedCandidates),
|
|
5723
|
+
maxCandidates
|
|
5724
|
+
);
|
|
5725
|
+
appendBounded(
|
|
5726
|
+
suggestedVarSets2,
|
|
5727
|
+
recordArray(exploration.suggestedVarSets),
|
|
5728
|
+
maxCandidates
|
|
5729
|
+
);
|
|
5730
|
+
}
|
|
5731
|
+
return {
|
|
5732
|
+
missing,
|
|
5733
|
+
candidateCount,
|
|
5734
|
+
viableCandidateCount,
|
|
5735
|
+
rejectedCandidateCount,
|
|
5736
|
+
maxCandidates,
|
|
5737
|
+
candidateSuggestions,
|
|
5738
|
+
rejectedCandidates,
|
|
5739
|
+
suggestedVarSets: suggestedVarSets2
|
|
3697
5740
|
};
|
|
3698
5741
|
}
|
|
5742
|
+
function runtimeNoCandidateDiagnostics(edges) {
|
|
5743
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5744
|
+
return edges.flatMap((edge) => {
|
|
5745
|
+
const exploration = parseObject(edge.evidence.dynamicTargetExploration);
|
|
5746
|
+
const suppliedVariables = parseObject(exploration.suppliedVariables);
|
|
5747
|
+
const appliedSuppliedVariables = parseObject(
|
|
5748
|
+
exploration.appliedSuppliedVariables
|
|
5749
|
+
);
|
|
5750
|
+
if (numeric(exploration.viableCandidateCount) !== 0 || Object.keys(appliedSuppliedVariables).length === 0) return [];
|
|
5751
|
+
const callSite = parseObject(edge.evidence.callSite);
|
|
5752
|
+
const key = JSON.stringify([callSite, suppliedVariables]);
|
|
5753
|
+
if (seen.has(key)) return [];
|
|
5754
|
+
seen.add(key);
|
|
5755
|
+
const maxCandidates = positiveCandidateCap(numeric(exploration.maxCandidates));
|
|
5756
|
+
return [{
|
|
5757
|
+
severity: "warning",
|
|
5758
|
+
code: "no_candidate_after_runtime_substitution",
|
|
5759
|
+
message: "No dynamic target candidate remained after applying runtime variables",
|
|
5760
|
+
suppliedVariables,
|
|
5761
|
+
appliedSuppliedVariables,
|
|
5762
|
+
substitutedSignals: parseObject(exploration.substitutedSignals),
|
|
5763
|
+
candidateCount: numeric(exploration.candidateCount),
|
|
5764
|
+
viableCandidateCount: 0,
|
|
5765
|
+
rejectedCandidateCount: numeric(exploration.rejectedCandidateCount),
|
|
5766
|
+
shownCandidateCount: numeric(exploration.shownCandidateCount),
|
|
5767
|
+
omittedCandidateCount: numeric(exploration.omittedCandidateCount),
|
|
5768
|
+
shownRejectedCandidateCount: numeric(
|
|
5769
|
+
exploration.shownRejectedCandidateCount
|
|
5770
|
+
),
|
|
5771
|
+
omittedRejectedCandidateCount: numeric(
|
|
5772
|
+
exploration.omittedRejectedCandidateCount
|
|
5773
|
+
),
|
|
5774
|
+
rejectedCandidates: recordArray(exploration.rejectedCandidates).slice(0, maxCandidates),
|
|
5775
|
+
callSite
|
|
5776
|
+
}];
|
|
5777
|
+
});
|
|
5778
|
+
}
|
|
3699
5779
|
function edgeTarget(row, evidence) {
|
|
3700
5780
|
const effective = parseObject(evidence.effectiveResolution);
|
|
3701
|
-
const targetServicePath =
|
|
3702
|
-
const targetOperationPath =
|
|
5781
|
+
const targetServicePath = stringValue7(effective.targetServicePath ?? evidence.targetServicePath);
|
|
5782
|
+
const targetOperationPath = stringValue7(effective.targetOperationPath ?? evidence.targetOperationPath);
|
|
3703
5783
|
if (targetServicePath && targetOperationPath) return `${targetServicePath}${targetOperationPath}`;
|
|
3704
5784
|
const runtimeCandidate = evidence.runtimeResolvedCandidate;
|
|
3705
5785
|
if (runtimeCandidate?.servicePath && runtimeCandidate.operationPath) return `${runtimeCandidate.servicePath}${runtimeCandidate.operationPath}`;
|
|
3706
|
-
const servicePath =
|
|
3707
|
-
const operationPath =
|
|
3708
|
-
const targetOperation =
|
|
3709
|
-
const targetRepo =
|
|
5786
|
+
const servicePath = stringValue7(evidence.servicePath);
|
|
5787
|
+
const operationPath = stringValue7(evidence.operationPath);
|
|
5788
|
+
const targetOperation = stringValue7(evidence.targetOperation);
|
|
5789
|
+
const targetRepo = stringValue7(evidence.targetRepo) ?? "";
|
|
3710
5790
|
if (row.edge_type === "HANDLER_RUNS_DB_QUERY") return `Entity: ${row.to_id || "unknown"}`;
|
|
3711
|
-
if (row.edge_type === "HANDLER_RUNS_REMOTE_QUERY") return
|
|
5791
|
+
if (row.edge_type === "HANDLER_RUNS_REMOTE_QUERY") return stringValue7(evidence.remoteQueryTarget) ?? `Remote query: ${row.to_id || "unknown"}`;
|
|
3712
5792
|
if (row.edge_type === "HANDLER_CALLS_EXTERNAL_HTTP") {
|
|
3713
5793
|
const target = parseObject(evidence.externalTarget);
|
|
3714
|
-
return
|
|
5794
|
+
return stringValue7(target.label) ?? `External endpoint: ${row.to_id || "unknown"}`;
|
|
3715
5795
|
}
|
|
3716
5796
|
if (servicePath && operationPath) return `${servicePath}${operationPath}`;
|
|
3717
5797
|
return targetOperation ? `${targetRepo}:${targetOperation}` : row.to_id;
|
|
@@ -3750,23 +5830,102 @@ function withEffectiveResolution(evidence, row, unresolvedReason, resolution) {
|
|
|
3750
5830
|
return { ...rest, effectiveResolution: current, linker: { status: current.status, confidence: current.confidence, reason: unresolvedReason, edgeType: current.edgeType } };
|
|
3751
5831
|
}
|
|
3752
5832
|
function resolveRuntimeOperation(db, evidence, workspaceId) {
|
|
3753
|
-
const servicePath =
|
|
3754
|
-
const rawOperationPath =
|
|
5833
|
+
const servicePath = stringValue7(evidence.servicePath);
|
|
5834
|
+
const rawOperationPath = stringValue7(evidence.operationPath);
|
|
3755
5835
|
const normalized = normalizeODataOperationInvocationPath(rawOperationPath);
|
|
3756
|
-
const operationPath = normalized?.wasInvocation ? normalized.normalizedOperationPath :
|
|
3757
|
-
const alias =
|
|
3758
|
-
const destination =
|
|
5836
|
+
const operationPath = normalized?.wasInvocation ? normalized.normalizedOperationPath : stringValue7(evidence.normalizedOperationPath) ?? rawOperationPath;
|
|
5837
|
+
const alias = stringValue7(evidence.serviceAliasExpr ?? evidence.serviceAlias);
|
|
5838
|
+
const destination = stringValue7(evidence.destination);
|
|
3759
5839
|
return resolveOperation(db, { servicePath, operationPath, alias, destination, hasExplicitOverride: true, isDynamic: true }, workspaceId);
|
|
3760
5840
|
}
|
|
3761
5841
|
function evidenceWithRuntimeVariables(evidence, vars) {
|
|
3762
5842
|
const substitutions = runtimeSubstitutions(evidence, vars ?? {});
|
|
3763
|
-
const
|
|
5843
|
+
const suppliedRuntimeVariables = Object.fromEntries(
|
|
5844
|
+
Object.entries(vars ?? {}).sort(([left], [right]) => left.localeCompare(right))
|
|
5845
|
+
);
|
|
5846
|
+
const next = {
|
|
5847
|
+
...evidence,
|
|
5848
|
+
runtimeSubstitutions: substitutions,
|
|
5849
|
+
suppliedRuntimeVariables
|
|
5850
|
+
};
|
|
3764
5851
|
for (const [key, value] of Object.entries(substitutions)) if (value.effective) next[key] = value.effective;
|
|
3765
5852
|
const missing = Object.values(substitutions).flatMap((value) => value.missing);
|
|
3766
5853
|
if (missing.length > 0) next.missingRuntimeVariables = [...new Set(missing)].sort();
|
|
3767
|
-
if (Object.keys(
|
|
5854
|
+
if (Object.keys(suppliedRuntimeVariables).length > 0) next.runtimeVariablesApplied = true;
|
|
3768
5855
|
return next;
|
|
3769
5856
|
}
|
|
5857
|
+
function evidenceWithDynamicAnalysis(evidence, analysis) {
|
|
5858
|
+
const persistedCandidates = recordArray(evidence.candidates);
|
|
5859
|
+
const persistedScores = recordArray(evidence.candidateScores);
|
|
5860
|
+
return {
|
|
5861
|
+
...evidence,
|
|
5862
|
+
candidates: persistedCandidates.slice(0, analysis.maxCandidates),
|
|
5863
|
+
candidateScores: persistedScores.slice(0, analysis.maxCandidates),
|
|
5864
|
+
persistedCandidateCount: persistedCandidates.length,
|
|
5865
|
+
persistedCandidateOmittedCount: Math.max(
|
|
5866
|
+
0,
|
|
5867
|
+
persistedCandidates.length - analysis.maxCandidates
|
|
5868
|
+
),
|
|
5869
|
+
dynamicTargetExploration: {
|
|
5870
|
+
mode: analysis.mode,
|
|
5871
|
+
maxCandidates: analysis.maxCandidates,
|
|
5872
|
+
missingVariables: analysis.missingVariables,
|
|
5873
|
+
requiredVariables: analysis.requiredVariables,
|
|
5874
|
+
suppliedVariables: analysis.suppliedVariables,
|
|
5875
|
+
appliedSuppliedVariables: analysis.appliedSuppliedVariables,
|
|
5876
|
+
substitutedSignals: analysis.substitutedSignals,
|
|
5877
|
+
candidateCount: analysis.candidateCount,
|
|
5878
|
+
viableCandidateCount: analysis.viableCandidateCount,
|
|
5879
|
+
rejectedCandidateCount: analysis.rejectedCandidateCount,
|
|
5880
|
+
shownCandidateCount: analysis.shownCandidateCount,
|
|
5881
|
+
omittedCandidateCount: analysis.omittedCandidateCount,
|
|
5882
|
+
shownRejectedCandidateCount: analysis.shownRejectedCandidateCount,
|
|
5883
|
+
omittedRejectedCandidateCount: analysis.omittedRejectedCandidateCount,
|
|
5884
|
+
rejectedCandidates: analysis.rejectedCandidates,
|
|
5885
|
+
suggestedVarSets: analysis.suggestedVarSets
|
|
5886
|
+
},
|
|
5887
|
+
dynamicTargetCandidates: analysis.candidates,
|
|
5888
|
+
dynamicTargetCandidateSuggestions: analysis.shownCandidates,
|
|
5889
|
+
rejectedCandidates: analysis.rejectedCandidates,
|
|
5890
|
+
dynamicTargetInference: analysis.inference
|
|
5891
|
+
};
|
|
5892
|
+
}
|
|
5893
|
+
var boundedDynamicListKeys = /* @__PURE__ */ new Set([
|
|
5894
|
+
"candidates",
|
|
5895
|
+
"candidateScores",
|
|
5896
|
+
"dynamicTargetCandidates",
|
|
5897
|
+
"dynamicTargetCandidateSuggestions",
|
|
5898
|
+
"candidateSuggestions",
|
|
5899
|
+
"suggestedVarSets",
|
|
5900
|
+
"rejectedCandidates",
|
|
5901
|
+
"rejectedCandidateSuggestions",
|
|
5902
|
+
"copyableExamples",
|
|
5903
|
+
"conflicts"
|
|
5904
|
+
]);
|
|
5905
|
+
function boundDynamicEvidence(evidence, limit) {
|
|
5906
|
+
const candidateCount = numeric(evidence.persistedCandidateCount) || (Array.isArray(evidence.candidates) ? evidence.candidates.length : 0);
|
|
5907
|
+
const projected = boundDynamicValue(evidence, limit);
|
|
5908
|
+
const next = parseObject(projected);
|
|
5909
|
+
if (candidateCount === 0) return next;
|
|
5910
|
+
return {
|
|
5911
|
+
...next,
|
|
5912
|
+
persistedCandidateCount: candidateCount,
|
|
5913
|
+
persistedCandidateOmittedCount: Math.max(0, candidateCount - limit)
|
|
5914
|
+
};
|
|
5915
|
+
}
|
|
5916
|
+
function boundDynamicValue(value, limit, key, parentKey) {
|
|
5917
|
+
if (Array.isArray(value)) {
|
|
5918
|
+
const bounded = Boolean(key && (boundedDynamicListKeys.has(key) || parentKey === "derivationProvenance" || parentKey === "conflicts" && key === "sources"));
|
|
5919
|
+
const input = bounded ? value.slice(0, limit) : value;
|
|
5920
|
+
const projected = input.map((item) => boundDynamicValue(item, limit, void 0, key ?? parentKey));
|
|
5921
|
+
return projected;
|
|
5922
|
+
}
|
|
5923
|
+
if (!value || typeof value !== "object") return value;
|
|
5924
|
+
return Object.fromEntries(Object.entries(value).map(([childKey, child]) => [
|
|
5925
|
+
childKey,
|
|
5926
|
+
boundDynamicValue(child, limit, childKey, key ?? parentKey)
|
|
5927
|
+
]));
|
|
5928
|
+
}
|
|
3770
5929
|
function runtimeSubstitutions(evidence, vars) {
|
|
3771
5930
|
const substitutions = {};
|
|
3772
5931
|
for (const key of ["servicePath", "operationPath", "serviceAliasExpr", "serviceAlias", "destination"]) {
|
|
@@ -3776,17 +5935,44 @@ function runtimeSubstitutions(evidence, vars) {
|
|
|
3776
5935
|
return substitutions;
|
|
3777
5936
|
}
|
|
3778
5937
|
function substitutionValue(evidence, key) {
|
|
3779
|
-
const value =
|
|
5938
|
+
const value = stringValue7(evidence[key]);
|
|
3780
5939
|
if (key !== "operationPath") return value;
|
|
3781
5940
|
const normalized = normalizeODataOperationInvocationPath(value);
|
|
3782
5941
|
return normalized?.wasInvocation ? normalized.normalizedOperationPath : value;
|
|
3783
5942
|
}
|
|
3784
|
-
function
|
|
3785
|
-
if (
|
|
5943
|
+
function isDynamicRemoteOperationEdge(row, evidence) {
|
|
5944
|
+
if (evidence.callType !== "remote_action") return false;
|
|
3786
5945
|
if (!["dynamic", "ambiguous", "unresolved"].includes(String(row.status ?? ""))) return false;
|
|
3787
|
-
|
|
5946
|
+
return ["DYNAMIC_EDGE_CANDIDATE", "UNRESOLVED_EDGE", "REMOTE_CALL_RESOLVES_TO_OPERATION"].includes(row.edge_type);
|
|
5947
|
+
}
|
|
5948
|
+
function hasApplicableRuntimeVariables(evidence, vars) {
|
|
5949
|
+
if (!vars || Object.keys(vars).length === 0) return false;
|
|
3788
5950
|
return ["servicePath", "operationPath", "serviceAliasExpr", "serviceAlias", "destination"].some((key) => hasRuntimeVariable(evidence[key], vars));
|
|
3789
5951
|
}
|
|
5952
|
+
function inferredTarget(analysis) {
|
|
5953
|
+
if (analysis?.inference.status !== "resolved") return void 0;
|
|
5954
|
+
const id = Number(analysis.inference.candidateOperationId);
|
|
5955
|
+
const candidate = analysis.candidates.find((item) => item.candidateOperationId === id);
|
|
5956
|
+
if (!candidate) return void 0;
|
|
5957
|
+
return targetFromCandidate(candidate);
|
|
5958
|
+
}
|
|
5959
|
+
function targetFromCandidate(candidate) {
|
|
5960
|
+
return {
|
|
5961
|
+
operationId: candidate.candidateOperationId,
|
|
5962
|
+
repoName: candidate.repoName,
|
|
5963
|
+
serviceName: "",
|
|
5964
|
+
qualifiedName: "",
|
|
5965
|
+
servicePath: candidate.servicePath,
|
|
5966
|
+
operationPath: candidate.operationPath,
|
|
5967
|
+
operationName: candidate.operationName,
|
|
5968
|
+
repoId: candidate.repoId,
|
|
5969
|
+
packageName: candidate.packageName,
|
|
5970
|
+
score: candidate.score,
|
|
5971
|
+
reasons: candidate.reasons,
|
|
5972
|
+
sourceFile: candidate.sourceFile,
|
|
5973
|
+
sourceLine: candidate.sourceLine
|
|
5974
|
+
};
|
|
5975
|
+
}
|
|
3790
5976
|
function hasRuntimeVariable(value, vars) {
|
|
3791
5977
|
return typeof value === "string" && extractPlaceholders(value).some((key) => Object.hasOwn(vars, key));
|
|
3792
5978
|
}
|
|
@@ -3798,25 +5984,118 @@ function runtimeUnresolvedReason(resolution) {
|
|
|
3798
5984
|
function parseObject(value) {
|
|
3799
5985
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
3800
5986
|
}
|
|
3801
|
-
function
|
|
5987
|
+
function stringValue7(value) {
|
|
3802
5988
|
return typeof value === "string" ? value : void 0;
|
|
3803
5989
|
}
|
|
5990
|
+
function numeric(value) {
|
|
5991
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
5992
|
+
}
|
|
5993
|
+
function recordArray(value) {
|
|
5994
|
+
return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
5995
|
+
}
|
|
5996
|
+
function appendBounded(target, values, limit) {
|
|
5997
|
+
const remaining = Math.max(0, limit - target.length);
|
|
5998
|
+
if (remaining > 0) target.push(...values.slice(0, remaining));
|
|
5999
|
+
}
|
|
6000
|
+
function uniqueCliRows(rows2) {
|
|
6001
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6002
|
+
return rows2.filter((row) => {
|
|
6003
|
+
const cli = typeof row.cli === "string" ? row.cli : JSON.stringify(row);
|
|
6004
|
+
if (seen.has(cli)) return false;
|
|
6005
|
+
seen.add(cli);
|
|
6006
|
+
return true;
|
|
6007
|
+
});
|
|
6008
|
+
}
|
|
6009
|
+
function copyableExamples(suggestedVarSets2, candidateCount, limit) {
|
|
6010
|
+
const variableExamples = uniqueCliRows(suggestedVarSets2).flatMap((row) => typeof row.cli === "string" ? [row.cli] : []);
|
|
6011
|
+
const exploration = candidateCount > 0 ? ["--dynamic-mode candidates --max-dynamic-candidates 20"] : [];
|
|
6012
|
+
return [...variableExamples, ...exploration].slice(0, limit);
|
|
6013
|
+
}
|
|
6014
|
+
function positiveCandidateCap(value) {
|
|
6015
|
+
return Number.isFinite(value) && Number(value) > 0 ? Math.floor(Number(value)) : 5;
|
|
6016
|
+
}
|
|
6017
|
+
|
|
6018
|
+
// src/trace/dynamic-branches.ts
|
|
6019
|
+
function dynamicCandidateBranches(depth, call, evidence) {
|
|
6020
|
+
const exploration = objectRecord(evidence.dynamicTargetExploration);
|
|
6021
|
+
return recordArray2(evidence.dynamicTargetCandidateSuggestions).map((candidate) => ({
|
|
6022
|
+
step: depth,
|
|
6023
|
+
type: "dynamic_candidate_branch",
|
|
6024
|
+
from: `${call.repoName}:${call.source_file}:${call.source_line}`,
|
|
6025
|
+
to: `${String(candidate.servicePath ?? "")}${String(candidate.operationPath ?? "")}`,
|
|
6026
|
+
evidence: {
|
|
6027
|
+
...candidate,
|
|
6028
|
+
exploratory: true,
|
|
6029
|
+
dynamicMode: String(exploration.mode ?? "candidates"),
|
|
6030
|
+
selected: false,
|
|
6031
|
+
omittedCandidateCount: numericValue2(exploration.omittedCandidateCount)
|
|
6032
|
+
},
|
|
6033
|
+
confidence: numericValue2(candidate.score),
|
|
6034
|
+
unresolvedReason: "Exploratory dynamic target candidate; provide runtime variables to select it"
|
|
6035
|
+
}));
|
|
6036
|
+
}
|
|
6037
|
+
function objectRecord(value) {
|
|
6038
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
6039
|
+
}
|
|
6040
|
+
function recordArray2(value) {
|
|
6041
|
+
return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
6042
|
+
}
|
|
6043
|
+
function numericValue2(value) {
|
|
6044
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
6045
|
+
}
|
|
6046
|
+
|
|
6047
|
+
// src/trace/002-trace-diagnostics.ts
|
|
6048
|
+
function loadTraceDiagnostics(db, repoId, includeWorkspaceDiagnostics, workspaceId) {
|
|
6049
|
+
if (repoId === void 0 && !includeWorkspaceDiagnostics) return [];
|
|
6050
|
+
return db.prepare(`SELECT d.repo_id repoId,d.severity,d.code,d.message,
|
|
6051
|
+
d.source_file sourceFile,d.source_line sourceLine
|
|
6052
|
+
FROM diagnostics d LEFT JOIN repositories r ON r.id=d.repo_id
|
|
6053
|
+
WHERE (? IS NULL OR d.repo_id=?)
|
|
6054
|
+
AND (? IS NULL OR d.repo_id IS NULL OR r.workspace_id=?)
|
|
6055
|
+
ORDER BY severity,code,COALESCE(source_file,''),
|
|
6056
|
+
COALESCE(source_line,0),d.id`).all(
|
|
6057
|
+
repoId,
|
|
6058
|
+
repoId,
|
|
6059
|
+
workspaceId,
|
|
6060
|
+
workspaceId
|
|
6061
|
+
);
|
|
6062
|
+
}
|
|
6063
|
+
function prependTraceDiagnostic(diagnostics, diagnostic) {
|
|
6064
|
+
const duplicate = diagnostics.findIndex((item) => sameDiagnosticLocation(item, diagnostic));
|
|
6065
|
+
if (duplicate >= 0) diagnostics.splice(duplicate, 1);
|
|
6066
|
+
diagnostics.unshift(diagnostic);
|
|
6067
|
+
}
|
|
6068
|
+
function sameDiagnosticLocation(left, right) {
|
|
6069
|
+
if (left.code !== right.code) return false;
|
|
6070
|
+
const leftFile = stringValue8(left.sourceFile);
|
|
6071
|
+
const rightFile = stringValue8(right.sourceFile);
|
|
6072
|
+
if (leftFile || rightFile)
|
|
6073
|
+
return leftFile === rightFile && numericValue3(left.sourceLine) === numericValue3(right.sourceLine);
|
|
6074
|
+
return String(left.message ?? "") === String(right.message ?? "");
|
|
6075
|
+
}
|
|
6076
|
+
function stringValue8(value) {
|
|
6077
|
+
return typeof value === "string" ? value : void 0;
|
|
6078
|
+
}
|
|
6079
|
+
function numericValue3(value) {
|
|
6080
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
6081
|
+
}
|
|
3804
6082
|
|
|
3805
6083
|
// src/trace/trace-engine.ts
|
|
3806
|
-
function
|
|
6084
|
+
function normalizeOperation2(value) {
|
|
3807
6085
|
if (!value) return void 0;
|
|
3808
6086
|
return value.startsWith("/") ? value.slice(1) : value;
|
|
3809
6087
|
}
|
|
3810
6088
|
function positiveDepth(value) {
|
|
3811
6089
|
return Number.isFinite(value) && value > 0 ? Math.floor(value) : 25;
|
|
3812
6090
|
}
|
|
3813
|
-
function operationStartScope(db, repoId, start, hintOptions) {
|
|
3814
|
-
const requested =
|
|
6091
|
+
function operationStartScope(db, repoId, start, hintOptions, workspaceId) {
|
|
6092
|
+
const requested = normalizeOperation2(start.operationPath ?? start.operation);
|
|
3815
6093
|
if (!requested) return void 0;
|
|
3816
6094
|
const rows2 = db.prepare(`SELECT o.id operationId,o.operation_name operationName,o.operation_path operationPath,o.source_file sourceFile,o.source_line sourceLine,s.service_path servicePath,r.id repoId,r.name repoName
|
|
3817
6095
|
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
|
|
3818
|
-
WHERE (? IS NULL OR r.
|
|
3819
|
-
|
|
6096
|
+
WHERE (? IS NULL OR r.workspace_id=?) AND (? IS NULL OR r.id=?)
|
|
6097
|
+
AND (? IS NULL OR s.service_path=?) AND (o.operation_name=? OR o.operation_path=? OR o.operation_path=?)
|
|
6098
|
+
ORDER BY r.name,s.service_path,o.operation_name,o.id`).all(workspaceId, workspaceId, repoId, repoId, start.servicePath, start.servicePath, requested, requested, requested.startsWith("/") ? requested : `/${requested}`);
|
|
3820
6099
|
if (rows2.length === 0) return void 0;
|
|
3821
6100
|
const repoCount = new Set(rows2.map((row) => String(row.repoName))).size;
|
|
3822
6101
|
const serviceCount = new Set(rows2.map((row) => `${String(row.repoName)}:${String(row.servicePath)}`)).size;
|
|
@@ -3847,54 +6126,34 @@ function implementationRejectionReasons(evidence) {
|
|
|
3847
6126
|
const reasons = candidates.flatMap((candidate) => Array.isArray(candidate.rejectedReasons) ? candidate.rejectedReasons.filter((reason) => typeof reason === "string") : []);
|
|
3848
6127
|
return [...new Set(reasons)].sort();
|
|
3849
6128
|
}
|
|
3850
|
-
function sourceFilesForStart(db, repoId, start) {
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
const
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
operation,
|
|
3875
|
-
operation
|
|
6129
|
+
function sourceFilesForStart(db, repoId, start, workspaceId) {
|
|
6130
|
+
return sourceScopeForSelector(db, repoId, start, workspaceId);
|
|
6131
|
+
}
|
|
6132
|
+
function startScope(db, start, hintOptions, workspaceId) {
|
|
6133
|
+
const repos = start.repo ? reposByName(db, start.repo, workspaceId).map((row) => ({
|
|
6134
|
+
id: row.id,
|
|
6135
|
+
name: row.name,
|
|
6136
|
+
packageName: row.package_name ?? void 0
|
|
6137
|
+
})) : [];
|
|
6138
|
+
if (start.repo && repos.length === 0) return {
|
|
6139
|
+
selectorMatched: false,
|
|
6140
|
+
startDiagnostics: [selectorRepoNotFoundDiagnostic(start.repo)]
|
|
6141
|
+
};
|
|
6142
|
+
if (start.repo && repos.length > 1) return {
|
|
6143
|
+
selectorMatched: false,
|
|
6144
|
+
startDiagnostics: [selectorRepoAmbiguousDiagnostic(start.repo, repos)]
|
|
6145
|
+
};
|
|
6146
|
+
const repo = repos[0];
|
|
6147
|
+
const operationScope = operationStartScope(
|
|
6148
|
+
db,
|
|
6149
|
+
repo?.id,
|
|
6150
|
+
start,
|
|
6151
|
+
hintOptions,
|
|
6152
|
+
workspaceId
|
|
3876
6153
|
);
|
|
3877
|
-
if (rows2.length > 0) return { files: new Set(rows2.map((row) => row.sourceFile).filter(Boolean)), symbols: new Set(rows2.map((row) => Number(row.symbolId)).filter(Boolean)), repoId };
|
|
3878
|
-
if (start.servicePath && operation) {
|
|
3879
|
-
const implRows = db.prepare(`SELECT DISTINCT hc.source_file sourceFile,sym.id symbolId
|
|
3880
|
-
FROM cds_services s JOIN cds_operations o ON o.service_id=s.id
|
|
3881
|
-
JOIN graph_edges e ON e.edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND e.status='resolved' AND e.from_kind='operation' AND e.from_id=CAST(o.id AS TEXT)
|
|
3882
|
-
JOIN handler_methods hm ON hm.id=CAST(e.to_id AS INTEGER)
|
|
3883
|
-
JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
3884
|
-
LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id AND sym.source_file=hc.source_file AND sym.name=hm.method_name
|
|
3885
|
-
WHERE (? IS NULL OR s.repo_id=?) AND s.service_path=? AND (o.operation_path=? OR o.operation_name=?)`).all(repoId, repoId, start.servicePath, operation, operation);
|
|
3886
|
-
if (implRows.length > 0) return { files: new Set(implRows.map((row) => row.sourceFile).filter(Boolean)), symbols: new Set(implRows.map((row) => Number(row.symbolId)).filter(Boolean)), repoId };
|
|
3887
|
-
}
|
|
3888
|
-
return void 0;
|
|
3889
|
-
}
|
|
3890
|
-
function startScope(db, start, hintOptions) {
|
|
3891
|
-
const repo = start.repo ? db.prepare(
|
|
3892
|
-
"SELECT id,name FROM repositories WHERE name=? OR package_name=?"
|
|
3893
|
-
).get(start.repo, start.repo) : void 0;
|
|
3894
|
-
if (start.repo && !repo) return { repo, selectorMatched: false };
|
|
3895
|
-
const operationScope = operationStartScope(db, repo?.id, start, hintOptions);
|
|
3896
6154
|
const terminalOperationScope = operationScope && !operationScope.files && (operationScope.diagnostics ?? []).some((d) => d.resolutionStage === "operation" || d.resolutionStage === "implementation");
|
|
3897
|
-
const sourceScope = operationScope?.files || terminalOperationScope ? operationScope : sourceFilesForStart(db, repo?.id, start);
|
|
6155
|
+
const sourceScope = operationScope?.files || terminalOperationScope ? operationScope : sourceFilesForStart(db, repo?.id, start, workspaceId);
|
|
6156
|
+
const terminalSelectorScope = Boolean(sourceScope?.diagnostics?.length && !sourceScope.files);
|
|
3898
6157
|
const sourceFiles = sourceScope?.files;
|
|
3899
6158
|
const hasSelector = Boolean(
|
|
3900
6159
|
start.handler ?? start.operation ?? start.operationPath ?? start.servicePath
|
|
@@ -3906,9 +6165,9 @@ function startScope(db, start, hintOptions) {
|
|
|
3906
6165
|
executionRepoId: sourceScope?.repoId ?? repo?.id,
|
|
3907
6166
|
sourceFiles,
|
|
3908
6167
|
symbolIds: sourceScope?.symbols,
|
|
3909
|
-
selectorMatched: !terminalOperationScope && (!hasSelector || sourceFiles !== void 0),
|
|
6168
|
+
selectorMatched: !terminalOperationScope && !terminalSelectorScope && (!hasSelector || sourceFiles !== void 0),
|
|
3910
6169
|
startOperationId: operationScope?.operationId,
|
|
3911
|
-
startDiagnostics: operationScope?.diagnostics
|
|
6170
|
+
startDiagnostics: operationScope?.diagnostics?.length ? operationScope.diagnostics : sourceScope?.diagnostics
|
|
3912
6171
|
};
|
|
3913
6172
|
}
|
|
3914
6173
|
function handlerFilesForOperation(db, operationId) {
|
|
@@ -3917,12 +6176,23 @@ function handlerFilesForOperation(db, operationId) {
|
|
|
3917
6176
|
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE o.id=?`
|
|
3918
6177
|
).get(operationId);
|
|
3919
6178
|
if (!op) return /* @__PURE__ */ new Set();
|
|
3920
|
-
const operation =
|
|
6179
|
+
const operation = normalizeOperation2(op.operationPath ?? op.operationName);
|
|
3921
6180
|
const rows2 = db.prepare(
|
|
3922
6181
|
`SELECT DISTINCT hc.source_file sourceFile,sym.id symbolId FROM handler_classes hc
|
|
3923
6182
|
JOIN handler_methods hm ON hm.handler_class_id=hc.id
|
|
3924
|
-
LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id
|
|
3925
|
-
|
|
6183
|
+
LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id
|
|
6184
|
+
AND sym.source_file=hc.source_file
|
|
6185
|
+
AND sym.qualified_name=hc.class_name || '.' || hm.method_name
|
|
6186
|
+
AND sym.start_line=hm.source_line
|
|
6187
|
+
WHERE hc.repo_id=?
|
|
6188
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.handlerKind'),
|
|
6189
|
+
CASE WHEN hm.decorator_kind='Event' THEN 'event'
|
|
6190
|
+
WHEN hm.decorator_kind IN ('Action','Func','On') THEN 'operation'
|
|
6191
|
+
ELSE 'unsupported' END)='operation'
|
|
6192
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
|
|
6193
|
+
CASE WHEN hm.decorator_kind IN ('Action','Func','On')
|
|
6194
|
+
THEN 1 ELSE 0 END)=1
|
|
6195
|
+
AND (hm.decorator_value=? OR hm.method_name=? OR hm.decorator_value=?)`
|
|
3926
6196
|
).all(op.repoId, operation, operation, op.operationName);
|
|
3927
6197
|
return new Set(rows2.map((row) => row.sourceFile).filter(Boolean));
|
|
3928
6198
|
}
|
|
@@ -3937,7 +6207,9 @@ function handlerMethodNode(db, methodId) {
|
|
|
3937
6207
|
function implementationScope(db, operationId) {
|
|
3938
6208
|
const edge = implementationEdge(db, operationId);
|
|
3939
6209
|
if (!edge || edge.status !== "resolved") return { files: /* @__PURE__ */ new Set(), edge };
|
|
3940
|
-
const row = db.prepare("SELECT hc.repo_id repoId,hc.source_file sourceFile,s.id symbolId FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id LEFT JOIN symbols s ON s.repo_id=hc.repo_id AND s.source_file=hc.source_file AND s.
|
|
6210
|
+
const row = db.prepare("SELECT hc.repo_id repoId,hc.source_file sourceFile,s.id symbolId FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id LEFT JOIN symbols s ON s.repo_id=hc.repo_id AND s.source_file=hc.source_file AND s.qualified_name=hc.class_name || '.' || hm.method_name AND s.start_line=hm.source_line WHERE hm.id=?").get(edge.to_id);
|
|
6211
|
+
if (!row || typeof row.symbolId !== "number")
|
|
6212
|
+
return { repoId: row?.repoId, files: /* @__PURE__ */ new Set(), edge };
|
|
3941
6213
|
return { repoId: row?.repoId, files: new Set(row?.sourceFile ? [row.sourceFile] : []), symbolId: row?.symbolId, edge };
|
|
3942
6214
|
}
|
|
3943
6215
|
function implementationMethodIdFromHint(edge, options) {
|
|
@@ -3973,8 +6245,8 @@ function contextImplementationMethodId(edge, callerRepoId, remoteEvidence, hintO
|
|
|
3973
6245
|
return { blocksAutomatic: false, evidence: hinted.evidence.reason === "no_scoped_hint_matched_edge" ? hinted.evidence : { status: "tied", tieReason: scores.length > 1 ? "duplicate_helper_implementation_candidates" : "no_unique_materially_stronger_candidate", candidateScores: scores } };
|
|
3974
6246
|
}
|
|
3975
6247
|
function handlerScope(db, methodId) {
|
|
3976
|
-
const row = db.prepare("SELECT hc.repo_id repoId,hc.source_file sourceFile,s.id symbolId FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id LEFT JOIN symbols s ON s.repo_id=hc.repo_id AND s.source_file=hc.source_file AND s.
|
|
3977
|
-
if (!row) return void 0;
|
|
6248
|
+
const row = db.prepare("SELECT hc.repo_id repoId,hc.source_file sourceFile,s.id symbolId FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id LEFT JOIN symbols s ON s.repo_id=hc.repo_id AND s.source_file=hc.source_file AND s.qualified_name=hc.class_name || '.' || hm.method_name AND s.start_line=hm.source_line WHERE hm.id=?").get(methodId);
|
|
6249
|
+
if (!row || typeof row.symbolId !== "number") return void 0;
|
|
3978
6250
|
return { repoId: row.repoId, files: new Set(row.sourceFile ? [row.sourceFile] : []), symbolId: row.symbolId };
|
|
3979
6251
|
}
|
|
3980
6252
|
function traceEdgeType(call, row) {
|
|
@@ -4181,20 +6453,22 @@ function contextualRuntimeResolution(db, call, binding, workspaceId, persistedRo
|
|
|
4181
6453
|
}
|
|
4182
6454
|
function trace(db, start, options) {
|
|
4183
6455
|
const hintOptions = { implementationRepo: options.implementationRepo, implementationHints: options.implementationHints };
|
|
4184
|
-
const scope = startScope(db, start, hintOptions);
|
|
4185
|
-
const
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
6456
|
+
const scope = startScope(db, start, hintOptions, options.workspaceId);
|
|
6457
|
+
const hasSelector = Boolean(start.repo || start.handler || start.operation || start.operationPath || start.servicePath);
|
|
6458
|
+
const diagnosticRepoId = scope.executionRepoId ?? scope.repo?.id;
|
|
6459
|
+
const diagnostics = loadTraceDiagnostics(
|
|
6460
|
+
db,
|
|
6461
|
+
diagnosticRepoId,
|
|
6462
|
+
!hasSelector,
|
|
6463
|
+
options.workspaceId
|
|
6464
|
+
);
|
|
6465
|
+
const stale = diagnosticRepoId !== void 0 || !hasSelector ? db.prepare("SELECT name,graph_stale_reason reason FROM repositories WHERE graph_stale_reason IS NOT NULL AND (? IS NULL OR id=?) AND (? IS NULL OR workspace_id=?) ORDER BY name,id").all(diagnosticRepoId, diagnosticRepoId, options.workspaceId, options.workspaceId) : [];
|
|
4189
6466
|
for (const row of stale)
|
|
4190
|
-
diagnostics
|
|
4191
|
-
for (const diagnostic of scope.startDiagnostics ?? [])
|
|
6467
|
+
prependTraceDiagnostic(diagnostics, { severity: "warning", code: "graph_stale", message: `Graph is stale for ${row.name ?? "repository"}: ${row.reason ?? "facts_changed"}. Run service-flow link.` });
|
|
6468
|
+
for (const diagnostic of scope.startDiagnostics ?? [])
|
|
6469
|
+
prependTraceDiagnostic(diagnostics, diagnostic);
|
|
4192
6470
|
if (!scope.selectorMatched && !scope.startDiagnostics?.length)
|
|
4193
|
-
diagnostics
|
|
4194
|
-
severity: "warning",
|
|
4195
|
-
code: "trace_start_not_found",
|
|
4196
|
-
message: start.servicePath && !start.operation && !start.operationPath && !start.handler ? "Service-only trace requires --operation or --path and will not broaden to the whole workspace" : "No handler source matched the requested trace start selector"
|
|
4197
|
-
});
|
|
6471
|
+
prependTraceDiagnostic(diagnostics, selectorNotFoundDiagnostic(start));
|
|
4198
6472
|
const maxDepth = positiveDepth(options.depth);
|
|
4199
6473
|
const edges = [];
|
|
4200
6474
|
const nodes = /* @__PURE__ */ new Map();
|
|
@@ -4223,8 +6497,8 @@ function trace(db, start, options) {
|
|
|
4223
6497
|
if (seenScopes.has(key)) continue;
|
|
4224
6498
|
seenScopes.add(key);
|
|
4225
6499
|
const calls = db.prepare(
|
|
4226
|
-
`SELECT c.*,r.name repoName FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id WHERE (? IS NULL OR c.repo_id=?) ORDER BY c.source_file,c.source_line`
|
|
4227
|
-
).all(current.repoId, current.repoId);
|
|
6500
|
+
`SELECT c.*,r.name repoName FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id WHERE (? IS NULL OR c.repo_id=?) AND (? IS NULL OR r.workspace_id=?) ORDER BY c.source_file,c.source_line`
|
|
6501
|
+
).all(current.repoId, current.repoId, options.workspaceId, options.workspaceId);
|
|
4228
6502
|
const filtered = calls.filter(
|
|
4229
6503
|
(c) => (!current.symbolIds || current.symbolIds.has(Number(c.source_symbol_id))) && (!current.files || current.files.has(String(c.source_file))) && includeCall(String(c.call_type), options)
|
|
4230
6504
|
);
|
|
@@ -4269,7 +6543,11 @@ function trace(db, start, options) {
|
|
|
4269
6543
|
String(row.evidence_json || "{}")
|
|
4270
6544
|
);
|
|
4271
6545
|
const rawEvidence = baseTraceEvidence(row, call, persistedEvidence, contextual.evidence);
|
|
4272
|
-
const effective = runtimeResolution(db, row, rawEvidence,
|
|
6546
|
+
const effective = runtimeResolution(db, row, rawEvidence, {
|
|
6547
|
+
vars: options.vars,
|
|
6548
|
+
dynamicMode: options.dynamicMode ?? "strict",
|
|
6549
|
+
maxDynamicCandidates: options.maxDynamicCandidates
|
|
6550
|
+
}, workspaceIdForCall(db, String(call.id)), contextual.unresolvedReason);
|
|
4273
6551
|
const evidence = effective.evidence;
|
|
4274
6552
|
const effectiveRow = effective.row;
|
|
4275
6553
|
const targetNode = `${effectiveRow.to_kind}:${effectiveRow.to_id}`;
|
|
@@ -4289,6 +6567,8 @@ function trace(db, start, options) {
|
|
|
4289
6567
|
confidence: Number(effectiveRow.confidence ?? call.confidence),
|
|
4290
6568
|
unresolvedReason: effective.unresolvedReason
|
|
4291
6569
|
});
|
|
6570
|
+
if ((options.dynamicMode ?? "strict") === "candidates")
|
|
6571
|
+
edges.push(...dynamicCandidateBranches(current.depth, call, evidence));
|
|
4292
6572
|
if (effectiveRow.to_kind === "operation") {
|
|
4293
6573
|
const implementation = implementationScope(db, effectiveRow.to_id);
|
|
4294
6574
|
const contextSelection = contextImplementationMethodId(implementation.edge, current.repoId, evidence, hintOptions);
|
|
@@ -4297,7 +6577,7 @@ function trace(db, start, options) {
|
|
|
4297
6577
|
if (implementation.edge) {
|
|
4298
6578
|
const implEvidence = JSON.parse(String(implementation.edge.evidence_json || "{}"));
|
|
4299
6579
|
const hintDiagnostic = implementationHintDiagnostic(contextSelection, implEvidence.implementationHintSuggestions);
|
|
4300
|
-
if (hintDiagnostic) diagnostics
|
|
6580
|
+
if (hintDiagnostic) prependTraceDiagnostic(diagnostics, hintDiagnostic);
|
|
4301
6581
|
const handlerNode = implementation.edge.status === "resolved" ? handlerMethodNode(db, implementation.edge.to_id) : contextNode;
|
|
4302
6582
|
const implTo = handlerNode?.label ? String(handlerNode.label) : `${implementation.edge.to_kind}:${implementation.edge.to_id}`;
|
|
4303
6583
|
if (handlerNode) nodes.set(String(handlerNode.id), handlerNode);
|
|
@@ -4343,16 +6623,35 @@ function trace(db, start, options) {
|
|
|
4343
6623
|
}
|
|
4344
6624
|
}
|
|
4345
6625
|
const runtimeDiagnostic = runtimeVariableDiagnostic(edges);
|
|
4346
|
-
if (runtimeDiagnostic) diagnostics
|
|
6626
|
+
if (runtimeDiagnostic) prependTraceDiagnostic(diagnostics, runtimeDiagnostic);
|
|
6627
|
+
for (const diagnostic of runtimeNoCandidateDiagnostics(edges))
|
|
6628
|
+
prependTraceDiagnostic(diagnostics, diagnostic);
|
|
4347
6629
|
return { start, nodes: [...nodes.values()], edges, diagnostics };
|
|
4348
6630
|
}
|
|
4349
6631
|
|
|
4350
6632
|
export {
|
|
6633
|
+
upsertWorkspace,
|
|
6634
|
+
getWorkspace,
|
|
6635
|
+
upsertRepository,
|
|
6636
|
+
listRepositories,
|
|
6637
|
+
reposByName,
|
|
6638
|
+
clearRepoFacts,
|
|
6639
|
+
insertRequires,
|
|
6640
|
+
insertService,
|
|
6641
|
+
insertHandler,
|
|
6642
|
+
handlerMethodIsExecutable,
|
|
6643
|
+
insertRegistrations,
|
|
6644
|
+
insertBindings,
|
|
6645
|
+
insertExecutableSymbols,
|
|
6646
|
+
insertSymbolCalls,
|
|
6647
|
+
insertCalls,
|
|
4351
6648
|
normalizePath,
|
|
4352
6649
|
stripQuotes,
|
|
4353
6650
|
discoverRepositories,
|
|
4354
6651
|
parsePackageJson,
|
|
6652
|
+
loadPackageJsonSnapshot,
|
|
4355
6653
|
parseCdsFile,
|
|
6654
|
+
loadRepositorySourceContext,
|
|
4356
6655
|
parseDecorators,
|
|
4357
6656
|
parseHandlerRegistrations,
|
|
4358
6657
|
redactText,
|
|
@@ -4369,6 +6668,7 @@ export {
|
|
|
4369
6668
|
implementationHintSuggestions,
|
|
4370
6669
|
linkWorkspace,
|
|
4371
6670
|
parseVars,
|
|
6671
|
+
selectorRepoAmbiguousDiagnostic,
|
|
4372
6672
|
trace
|
|
4373
6673
|
};
|
|
4374
|
-
//# sourceMappingURL=chunk-
|
|
6674
|
+
//# sourceMappingURL=chunk-PTLDSHRC.js.map
|