@saptools/service-flow 0.1.40 → 0.1.42
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/dist/{chunk-774PPGGK.js → chunk-RP3BJ64F.js} +134 -33
- package/dist/chunk-RP3BJ64F.js.map +1 -0
- package/dist/cli.js +101 -9
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/db/migrations.ts +14 -1
- package/src/db/repositories.ts +9 -2
- package/src/db/schema.ts +4 -2
- package/src/indexer/cds-extension-resolver.ts +88 -0
- package/src/indexer/workspace-indexer.ts +2 -0
- package/src/linker/cross-repo-linker.ts +12 -2
- package/src/parsers/cds-parser.ts +40 -2
- package/src/parsers/outbound-call-parser.ts +68 -27
- package/src/types.ts +10 -0
- package/dist/chunk-774PPGGK.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.42
|
|
4
|
+
|
|
5
|
+
- Continued inherited CDS extension operations into the selected base implementation while retaining concrete routing evidence.
|
|
6
|
+
- Reconciled materialized inherited operations on reindex so renamed or removed base operations do not leave stale effective rows.
|
|
7
|
+
- Kept ambiguous or unresolved extension bases without a selected base id and ignored commented CDS `using` declarations.
|
|
8
|
+
- Added positional remote CAP `Service.send(method, path, ...)` classification for proven CAP clients and tightened catch/loop lexical constant resolution.
|
|
9
|
+
|
|
10
|
+
## 0.1.41
|
|
11
|
+
|
|
12
|
+
- Added imported CDS extension provenance and materialized inherited operations at concrete extension paths without guessing by simple service name.
|
|
13
|
+
- Resolved TypeScript identifier expressions through lexical bindings so module constants work and inaccessible shadowed block values are excluded.
|
|
14
|
+
- Classified positional CAP `Service.send(...)` dispatch only for proven CAP service receivers while leaving generic `send` calls untouched.
|
|
15
|
+
- Bumped the SQLite schema to persist extension/base and operation provenance metadata.
|
|
16
|
+
|
|
3
17
|
## 0.1.40
|
|
4
18
|
|
|
5
19
|
- Hardened operation path expression analysis to respect lexical scope, declaration order, aliases, and bounded branch candidates.
|
|
@@ -161,6 +161,37 @@ function maskCommentsAndStrings(text) {
|
|
|
161
161
|
}
|
|
162
162
|
return out;
|
|
163
163
|
}
|
|
164
|
+
function maskComments(text) {
|
|
165
|
+
let out = "";
|
|
166
|
+
let mode = "code";
|
|
167
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
168
|
+
const c = text[i] ?? "";
|
|
169
|
+
const n = text[i + 1] ?? "";
|
|
170
|
+
if (mode === "code" && c === "/" && n === "/") {
|
|
171
|
+
mode = "line";
|
|
172
|
+
out += " ";
|
|
173
|
+
i += 1;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (mode === "code" && c === "/" && n === "*") {
|
|
177
|
+
mode = "block";
|
|
178
|
+
out += " ";
|
|
179
|
+
i += 1;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (mode === "line" && c === "\n") mode = "code";
|
|
183
|
+
if (mode === "block" && c === "*" && n === "/") {
|
|
184
|
+
mode = "code";
|
|
185
|
+
out += " ";
|
|
186
|
+
i += 1;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (mode === "code" && (c === "'" || c === '"' || c === "`")) mode = c === "'" ? "single" : c === '"' ? "double" : "template";
|
|
190
|
+
else if (mode === "single" && c === "'" || mode === "double" && c === '"' || mode === "template" && c === "`") mode = "code";
|
|
191
|
+
out += mode === "line" || mode === "block" ? c === "\n" ? "\n" : " " : c;
|
|
192
|
+
}
|
|
193
|
+
return out;
|
|
194
|
+
}
|
|
164
195
|
function readAnnotation(text, index) {
|
|
165
196
|
if (text[index] !== "@") return void 0;
|
|
166
197
|
let i = index + 1;
|
|
@@ -202,6 +233,21 @@ function annotationRawAt(original, masked, index) {
|
|
|
202
233
|
const collected = collectAnnotations(masked, index);
|
|
203
234
|
return { end: collected.end, raw: original.slice(index, collected.end) };
|
|
204
235
|
}
|
|
236
|
+
function collectUsings(masked) {
|
|
237
|
+
const imports = /* @__PURE__ */ new Map();
|
|
238
|
+
for (const m of masked.matchAll(/\busing\s*\{([^}]*)\}\s*from\s*(['"])(.*?)\2\s*;/gs)) {
|
|
239
|
+
const moduleSpecifier = m[3] ?? "";
|
|
240
|
+
for (const part of (m[1] ?? "").split(",")) {
|
|
241
|
+
const text = part.trim();
|
|
242
|
+
if (!text) continue;
|
|
243
|
+
const alias = /^(\w+)\s+as\s+(\w+)$/.exec(text) ?? /^(\w+)\s*:\s*(\w+)$/.exec(text);
|
|
244
|
+
const importedSymbol = alias?.[1] ?? text;
|
|
245
|
+
const localAlias = alias?.[2] ?? importedSymbol;
|
|
246
|
+
imports.set(localAlias, { importedSymbol, localAlias, moduleSpecifier, importKind: moduleSpecifier.startsWith(".") ? "relative" : "package" });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return imports;
|
|
250
|
+
}
|
|
205
251
|
function operationsFromBody(text, maskedBody, bodyOffset, filePath) {
|
|
206
252
|
return [...maskedBody.matchAll(/\b(action|function|event)\s+(\w+)\s*(?:\(([^)]*)\))?\s*(?:returns\s+([^;{]+))?/g)].map((m) => ({
|
|
207
253
|
operationType: m[1] ?? "action",
|
|
@@ -220,6 +266,7 @@ async function parseCdsFile(repoPath, filePath) {
|
|
|
220
266
|
const namespace = /namespace\s+([\w.]+)\s*;/.exec(masked)?.[1];
|
|
221
267
|
const services = [];
|
|
222
268
|
const pendingAnnotations = [];
|
|
269
|
+
const usings = collectUsings(maskComments(text));
|
|
223
270
|
for (const a of masked.matchAll(/@\s*\(/g)) pendingAnnotations.push(annotationRawAt(text, masked, a.index ?? 0));
|
|
224
271
|
const serviceRegex = /\b(?:(extend)\s+)?(?:(service)\s+)?([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\b/g;
|
|
225
272
|
let match;
|
|
@@ -239,6 +286,7 @@ async function parseCdsFile(repoPath, filePath) {
|
|
|
239
286
|
const body = masked.slice(open + 1, end);
|
|
240
287
|
const name = match[3] ?? "UnknownService";
|
|
241
288
|
const serviceName = name.split(".").pop() ?? name;
|
|
289
|
+
const imported = isExtend ? usings.get(name) ?? usings.get(serviceName) : void 0;
|
|
242
290
|
const servicePath = ensureLeadingSlash(pathAnnotation(annotations) ?? serviceName);
|
|
243
291
|
services.push({
|
|
244
292
|
namespace,
|
|
@@ -248,14 +296,16 @@ async function parseCdsFile(repoPath, filePath) {
|
|
|
248
296
|
isExtend,
|
|
249
297
|
sourceFile: normalizePath(filePath),
|
|
250
298
|
sourceLine: lineOf(text, match.index),
|
|
251
|
-
operations: operationsFromBody(text, body, open + 1, filePath)
|
|
299
|
+
operations: operationsFromBody(text, body, open + 1, filePath),
|
|
300
|
+
extension: isExtend ? { localReference: name, importedSymbol: imported?.importedSymbol, localAlias: imported?.localAlias, moduleSpecifier: imported?.moduleSpecifier, importKind: imported?.importKind ?? "none" } : void 0
|
|
252
301
|
});
|
|
253
302
|
serviceRegex.lastIndex = end + 1;
|
|
254
303
|
}
|
|
255
304
|
const baseOps = new Map(services.filter((s) => !s.isExtend).map((s) => [s.qualifiedName, s.operations]));
|
|
256
305
|
for (const service of services.filter((s) => s.isExtend && s.operations.length === 0)) {
|
|
306
|
+
if (service.extension?.moduleSpecifier) continue;
|
|
257
307
|
const inherited = baseOps.get(service.qualifiedName) ?? baseOps.get(service.serviceName);
|
|
258
|
-
if (inherited) service.operations = inherited.map((op) => ({ ...op,
|
|
308
|
+
if (inherited) service.operations = inherited.map((op) => ({ ...op, provenance: "inherited" }));
|
|
259
309
|
}
|
|
260
310
|
return services;
|
|
261
311
|
}
|
|
@@ -894,37 +944,54 @@ function placeholders(expr) {
|
|
|
894
944
|
function isFunctionLikeScope(node) {
|
|
895
945
|
return ts4.isFunctionLike(node) || ts4.isSourceFile(node);
|
|
896
946
|
}
|
|
897
|
-
function containingScope(node) {
|
|
898
|
-
let current = node.parent;
|
|
899
|
-
while (current && !isFunctionLikeScope(current)) current = current.parent;
|
|
900
|
-
return current ?? node.getSourceFile();
|
|
901
|
-
}
|
|
902
947
|
function nodeContains(parent, child) {
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
948
|
+
const source = child.getSourceFile();
|
|
949
|
+
return child.getStart(source) >= parent.getStart(source) && child.getEnd() <= parent.getEnd();
|
|
950
|
+
}
|
|
951
|
+
function declarationScope(node) {
|
|
952
|
+
if (ts4.isParameter(node)) return node.parent;
|
|
953
|
+
const list = node.parent;
|
|
954
|
+
const blockScoped = (list.flags & (ts4.NodeFlags.Const | ts4.NodeFlags.Let)) !== 0;
|
|
955
|
+
let current = list.parent;
|
|
956
|
+
if (!blockScoped) {
|
|
957
|
+
while (current.parent && !isFunctionLikeScope(current)) current = current.parent;
|
|
958
|
+
return current;
|
|
959
|
+
}
|
|
960
|
+
while (current.parent && !ts4.isBlock(current) && !ts4.isSourceFile(current) && !ts4.isModuleBlock(current) && !ts4.isCaseBlock(current) && !ts4.isCatchClause(current) && !ts4.isForStatement(current) && !ts4.isForInStatement(current) && !ts4.isForOfStatement(current) && !isFunctionLikeScope(current)) current = current.parent;
|
|
961
|
+
return current;
|
|
962
|
+
}
|
|
963
|
+
function declarationScopedAncestor(node) {
|
|
964
|
+
let current = node.parent;
|
|
965
|
+
while (current) {
|
|
966
|
+
if (ts4.isCatchClause(current) || ts4.isForStatement(current) || ts4.isForInStatement(current) || ts4.isForOfStatement(current)) return current;
|
|
967
|
+
if (ts4.isSourceFile(current) || ts4.isFunctionLike(current)) return void 0;
|
|
909
968
|
current = current.parent;
|
|
910
969
|
}
|
|
911
|
-
return
|
|
970
|
+
return void 0;
|
|
971
|
+
}
|
|
972
|
+
function isAccessibleDeclaration(declaration, use) {
|
|
973
|
+
const source = use.getSourceFile();
|
|
974
|
+
if (declaration.name.getStart(source) >= use.getStart(source)) return false;
|
|
975
|
+
const scopedAncestor = declarationScopedAncestor(declaration);
|
|
976
|
+
if (scopedAncestor && ts4.isCatchClause(scopedAncestor)) return nodeContains(scopedAncestor.block, use);
|
|
977
|
+
if (scopedAncestor && (ts4.isForStatement(scopedAncestor) || ts4.isForInStatement(scopedAncestor) || ts4.isForOfStatement(scopedAncestor))) return nodeContains(scopedAncestor.statement, use);
|
|
978
|
+
const scope = declarationScope(declaration);
|
|
979
|
+
if (ts4.isCatchClause(scope)) return nodeContains(scope.block, use);
|
|
980
|
+
if (ts4.isForStatement(scope) || ts4.isForInStatement(scope) || ts4.isForOfStatement(scope)) return nodeContains(scope.statement, use);
|
|
981
|
+
return ts4.isSourceFile(scope) || nodeContains(scope, use);
|
|
912
982
|
}
|
|
913
983
|
function resolveBinding(identifier, use) {
|
|
914
|
-
const scope = containingScope(use);
|
|
915
984
|
const source = use.getSourceFile();
|
|
916
985
|
let best;
|
|
917
986
|
const visit = (node) => {
|
|
918
|
-
if (node
|
|
919
|
-
if (node.
|
|
920
|
-
if (ts4.isVariableDeclaration(node) && ts4.isIdentifier(node.name) && node.name.text === identifier.text && !nestedFunctionContains(scope, node, use)) best = node;
|
|
921
|
-
if (ts4.isParameter(node) && ts4.isIdentifier(node.name) && node.name.text === identifier.text && !nestedFunctionContains(scope, node, use)) best = node;
|
|
987
|
+
if (ts4.isVariableDeclaration(node) && ts4.isIdentifier(node.name) && node.name.text === identifier.text && isAccessibleDeclaration(node, use)) best = node;
|
|
988
|
+
if (ts4.isParameter(node) && ts4.isIdentifier(node.name) && node.name.text === identifier.text && isAccessibleDeclaration(node, use)) best = node;
|
|
922
989
|
ts4.forEachChild(node, visit);
|
|
923
990
|
};
|
|
924
|
-
visit(
|
|
991
|
+
visit(source);
|
|
925
992
|
if (!best) return { immutable: false, evidence: ["binding_not_found"] };
|
|
926
993
|
const immutable = ts4.isVariableDeclaration(best) && (best.parent.flags & ts4.NodeFlags.Const) !== 0;
|
|
927
|
-
return { declaration: best, initializer: ts4.isVariableDeclaration(best) ? best.initializer : void 0, immutable, evidence: [immutable ? "
|
|
994
|
+
return { declaration: best, initializer: ts4.isVariableDeclaration(best) ? best.initializer : void 0, immutable, evidence: [immutable ? "lexical_const_binding_before_use" : "lexical_mutable_or_parameter_binding"] };
|
|
928
995
|
}
|
|
929
996
|
function resolveExpression(expr, use, policy, depth = 0, seen = /* @__PURE__ */ new Set()) {
|
|
930
997
|
if (!expr) return { status: "unknown", sourceKind: "dynamic_expression", placeholderKeys: [], evidence: ["expression_missing"] };
|
|
@@ -964,7 +1031,6 @@ function staticPathCandidates(identifier, call, method) {
|
|
|
964
1031
|
const binding = resolveBinding(identifier, call);
|
|
965
1032
|
const declaration = binding.declaration;
|
|
966
1033
|
if (!declaration) return void 0;
|
|
967
|
-
const scope = containingScope(call);
|
|
968
1034
|
const source = call.getSourceFile();
|
|
969
1035
|
const paths = [];
|
|
970
1036
|
let hasDynamicAssignments = false;
|
|
@@ -975,12 +1041,15 @@ function staticPathCandidates(identifier, call, method) {
|
|
|
975
1041
|
};
|
|
976
1042
|
if (ts4.isVariableDeclaration(declaration)) addExpr(declaration.initializer, declaration);
|
|
977
1043
|
const visit = (node) => {
|
|
978
|
-
if (node !==
|
|
1044
|
+
if (node !== source && isFunctionLikeScope(node) && !nodeContains(node, call)) return;
|
|
979
1045
|
if (node.getStart(source) >= call.getStart(source)) return;
|
|
980
|
-
if (ts4.isBinaryExpression(node) && node.operatorToken.kind === ts4.SyntaxKind.EqualsToken && ts4.isIdentifier(node.left)
|
|
1046
|
+
if (ts4.isBinaryExpression(node) && node.operatorToken.kind === ts4.SyntaxKind.EqualsToken && ts4.isIdentifier(node.left)) {
|
|
1047
|
+
const target = resolveBinding(node.left, node);
|
|
1048
|
+
if (target.declaration === declaration) addExpr(node.right, node);
|
|
1049
|
+
}
|
|
981
1050
|
ts4.forEachChild(node, visit);
|
|
982
1051
|
};
|
|
983
|
-
visit(
|
|
1052
|
+
visit(source);
|
|
984
1053
|
const candidatePaths = [...new Set(paths)];
|
|
985
1054
|
if (candidatePaths.length === 0) return void 0;
|
|
986
1055
|
const normalizedCandidateOperations = [...new Set(candidatePaths.map((candidate) => classifyODataPathIntent(candidate, method).topLevelOperationName).filter((value) => Boolean(value)))];
|
|
@@ -1191,6 +1260,20 @@ function classifyOutboundCallsInSource(source, filePath) {
|
|
|
1191
1260
|
const entityCallType = entityCallTypes[intent.kind];
|
|
1192
1261
|
const isODataQueryRead = method.toUpperCase() === "GET" && ["entity_query", "entity_key_read", "entity_navigation_query"].includes(intent.kind);
|
|
1193
1262
|
add(node, { callType: query ? "remote_query" : entityCallType ?? (isODataQueryRead ? "remote_query" : "remote_action"), serviceVariableName: receiver, method, operationPathExpr, queryEntity: query ? extractQueryEntity(query) : isODataQueryRead ? intent.entitySegment : void 0, payloadSummary: summarizeExpression(objectArg.getText(source)), confidence: op || query ? 0.8 : 0.4, unresolvedReason: !query && pathExpr && !operationPathExpr ? "dynamic_operation_path_identifier" : void 0 }, { receiver, classifier: "service_client_send_object", operationPathExpression: shorthandPath ? op : void 0, rawPathExpression: resolvedPath.rawExpression, literalPathSource: resolvedPath.text ? resolvedPath.sourceKind === "const" ? "same_scope_const_initializer" : resolvedPath.sourceKind : void 0, odataPathIntent: operationPathExpr ? intent : void 0, staticPathCandidates: candidateEvidence2, parserWarning: !query && pathExpr && !operationPathExpr ? "dynamic_operation_path_identifier" : void 0 });
|
|
1263
|
+
} else {
|
|
1264
|
+
const receiver = receiverName(expr.expression);
|
|
1265
|
+
const rootReceiver = rootReceiverName(expr.expression);
|
|
1266
|
+
const method = resolveExpression(node.arguments[0], node, "literal").value?.toUpperCase();
|
|
1267
|
+
const pathArg = node.arguments[1];
|
|
1268
|
+
const supported = method && ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"].includes(method);
|
|
1269
|
+
if (receiver && supported && serviceVariables.has(rootReceiver ?? receiver)) {
|
|
1270
|
+
const resolvedPath = staticPathExpression(pathArg, node);
|
|
1271
|
+
const operationPathExpr = operationPathFromStatic(resolvedPath.text);
|
|
1272
|
+
const intent = classifyODataPathIntent(operationPathExpr, method);
|
|
1273
|
+
add(node, { callType: "remote_action", serviceVariableName: rootReceiver ?? receiver, method, operationPathExpr, payloadSummary: summarizeExpression(node.getText(source)), confidence: operationPathExpr ? 0.8 : 0.45, unresolvedReason: operationPathExpr ? void 0 : "dynamic_operation_path_identifier" }, { receiver, rootReceiver, classifier: "service_client_send_method_path", rawPathExpression: resolvedPath.rawExpression, literalPathSource: resolvedPath.text ? resolvedPath.sourceKind === "const" ? "same_scope_const_initializer" : resolvedPath.sourceKind : void 0, odataPathIntent: operationPathExpr ? intent : void 0, parserWarning: operationPathExpr ? void 0 : "dynamic_operation_path_identifier" });
|
|
1274
|
+
} else if (receiver && serviceVariables.has(rootReceiver ?? receiver)) {
|
|
1275
|
+
add(node, { callType: "remote_action", serviceVariableName: rootReceiver ?? receiver, method, payloadSummary: summarizeExpression(node.getText(source)), confidence: 0.35, unresolvedReason: "unsupported_cap_send_signature" }, { receiver, rootReceiver, classifier: "service_client_send_unsupported_signature" });
|
|
1276
|
+
}
|
|
1194
1277
|
}
|
|
1195
1278
|
} else if (ts4.isCallExpression(expr) && ts4.isIdentifier(expr.expression) && wrapperSpecs.has(expr.expression.text) || ts4.isIdentifier(expr) && wrapperSpecs.has(expr.text)) {
|
|
1196
1279
|
const wrapperName = ts4.isIdentifier(expr) ? expr.text : ts4.isCallExpression(expr) && ts4.isIdentifier(expr.expression) ? expr.expression.text : "";
|
|
@@ -1251,7 +1334,7 @@ function parseLocalServiceCalls(text, filePath) {
|
|
|
1251
1334
|
if (origin) aliases.set(node.name.text, { ...origin, chain: [...origin.chain, node.name.text] });
|
|
1252
1335
|
}
|
|
1253
1336
|
if (ts4.isCallExpression(node)) {
|
|
1254
|
-
const parsed = serviceOperationCall(node
|
|
1337
|
+
const parsed = serviceOperationCall(node, aliases);
|
|
1255
1338
|
if (parsed && parsed.operation !== "entities") calls.push({
|
|
1256
1339
|
callType: "local_service_call",
|
|
1257
1340
|
operationPathExpr: `/${parsed.operation}`,
|
|
@@ -1264,7 +1347,8 @@ function parseLocalServiceCalls(text, filePath) {
|
|
|
1264
1347
|
confidence: 0.9,
|
|
1265
1348
|
unresolvedReason: ["send", "emit", "publish", "on"].includes(parsed.operation) ? "transport_client_method" : void 0,
|
|
1266
1349
|
evidence: parserEvidence(source, node, {
|
|
1267
|
-
classifier:
|
|
1350
|
+
classifier: parsed.classifier,
|
|
1351
|
+
parserCallType: parsed.operation === "send" ? "transport_client_method" : parsed.classifier,
|
|
1268
1352
|
localServiceLookup: parsed.lookup,
|
|
1269
1353
|
localServiceName: parsed.service,
|
|
1270
1354
|
operation: parsed.operation,
|
|
@@ -1283,12 +1367,19 @@ function serviceLookup(expr, aliases) {
|
|
|
1283
1367
|
if (ts4.isElementAccessExpression(expr) && expr.expression.getText() === "cds.services" && ts4.isStringLiteral(expr.argumentExpression)) return { service: expr.argumentExpression.text, lookup: expr.getText(), chain: [expr.getText()] };
|
|
1284
1368
|
return void 0;
|
|
1285
1369
|
}
|
|
1286
|
-
function serviceOperationCall(
|
|
1370
|
+
function serviceOperationCall(node, aliases) {
|
|
1371
|
+
const expr = node.expression;
|
|
1287
1372
|
if (!ts4.isPropertyAccessExpression(expr)) return void 0;
|
|
1288
|
-
const operation = expr.name.text;
|
|
1289
1373
|
const origin = serviceLookup(expr.expression, aliases);
|
|
1290
1374
|
if (!origin) return void 0;
|
|
1291
|
-
|
|
1375
|
+
if (expr.name.text === "send") {
|
|
1376
|
+
const first = literalText(node.arguments[0]);
|
|
1377
|
+
const second = literalText(node.arguments[1]);
|
|
1378
|
+
const method = first?.toUpperCase();
|
|
1379
|
+
if (method && ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"].includes(method) && second) return { ...origin, operation: second.replace(/^\//, ""), classifier: "cap_service_send_method_path" };
|
|
1380
|
+
if (first) return { ...origin, operation: first.replace(/^\//, ""), classifier: "cap_service_send_local_dispatch" };
|
|
1381
|
+
}
|
|
1382
|
+
return { ...origin, operation: expr.name.text, classifier: "local_cap_service_call" };
|
|
1292
1383
|
}
|
|
1293
1384
|
|
|
1294
1385
|
// src/parsers/service-binding-parser.ts
|
|
@@ -2416,13 +2507,14 @@ function callEvidence(call, resolution, servicePath, op, destination, normalized
|
|
|
2416
2507
|
return { sourceFile: call.source_file, sourceLine: call.source_line, file: call.source_file, line: call.source_line, callId: call.id, repo: call.repoName, serviceAlias: call.alias, serviceAliasExpr: call.aliasExpr, destination, servicePath, operationPath: op, rawOperationPath: normalized?.wasInvocation ? normalized.rawOperationPath : odataPathIntent?.rawPath, normalizedOperationPath: normalized?.wasInvocation ? normalized.normalizedOperationPath : void 0, invocationArgumentPlaceholderKeys: normalized?.invocationArgumentPlaceholderKeys.length ? normalized.invocationArgumentPlaceholderKeys : void 0, odataOperationNormalizationReason: normalized?.normalizationReason, odataOperationNormalizationRejectedReason: normalized?.normalizationRejectedReason, localServiceName: call.local_service_name, localServiceLookup: call.local_service_lookup, aliasChain: parseJson(call.alias_chain_json), transport: call.call_type === "local_service_call" ? "local" : void 0, targetRepo: resolution.target?.repoName, targetServicePath: resolution.target?.servicePath, targetOperationPath: resolution.target?.operationPath, targetOperation: resolution.target?.operationName, helperChain: parseJson(call.helperChainJson), candidates: resolution.candidates, candidateCount: resolution.candidates.length, resolutionStatus: resolution.status, resolutionReasons: resolution.reasons, odataPathIntent, queryStringPresent: odataPathIntent?.hasQueryString || void 0, queryPlaceholderKeys: odataPathIntent?.placeholderKeys.length ? odataPathIntent.placeholderKeys : void 0, bindingHasDynamicExpression: bindingHasDynamicExpression || void 0, outboundEvidence: objectJson(call.evidence_json), analysisCompleteness: call.unresolved_reason ? "partial" : "complete", parserWarning: call.unresolved_reason ? { code: "parser_warning", message: call.unresolved_reason } : void 0 };
|
|
2417
2508
|
}
|
|
2418
2509
|
function linkImplementations(db, workspaceId, generation) {
|
|
2419
|
-
const operations = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,o.operation_name operationName,s.service_path servicePath,s.repo_id modelRepoId,r.name modelRepo,r.package_name modelPackage,r.kind modelKind FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=?`).all(workspaceId);
|
|
2510
|
+
const operations = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,o.operation_name operationName,o.provenance provenance,o.base_operation_id baseOperationId,s.service_path servicePath,s.repo_id modelRepoId,r.name modelRepo,r.package_name modelPackage,r.kind modelKind FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=?`).all(workspaceId);
|
|
2420
2511
|
let edgeCount = 0;
|
|
2421
2512
|
let resolvedCount = 0;
|
|
2422
2513
|
let ambiguousCount = 0;
|
|
2423
2514
|
let unresolvedCount = 0;
|
|
2424
2515
|
for (const operation of operations) {
|
|
2425
|
-
const
|
|
2516
|
+
const implementationContext = implementationContextForOperation(db, operation);
|
|
2517
|
+
const candidates = rankedImplementationCandidates(db, workspaceId, implementationContext);
|
|
2426
2518
|
if (candidates.length === 0) continue;
|
|
2427
2519
|
const accepted = candidates.filter((candidate) => candidate.accepted);
|
|
2428
2520
|
const topScore = accepted[0]?.score ?? 0;
|
|
@@ -2433,6 +2525,9 @@ function linkImplementations(db, workspaceId, generation) {
|
|
|
2433
2525
|
operationPath: operation.operationPath,
|
|
2434
2526
|
operationName: operation.operationName,
|
|
2435
2527
|
modelPackage: { id: operation.modelRepoId, name: operation.modelRepo, packageName: operation.modelPackage },
|
|
2528
|
+
implementationSource: implementationContext.operationId === operation.operationId ? "direct_or_concrete_override" : "inherited_from_base_operation",
|
|
2529
|
+
baseOperationId: operation.baseOperationId,
|
|
2530
|
+
implementationOperationId: implementationContext.operationId,
|
|
2436
2531
|
candidates: candidates.map((candidate, index) => candidateEvidence(candidate, index + 1))
|
|
2437
2532
|
};
|
|
2438
2533
|
if (accepted.length === 0) {
|
|
@@ -2448,6 +2543,12 @@ function linkImplementations(db, workspaceId, generation) {
|
|
|
2448
2543
|
}
|
|
2449
2544
|
return { edgeCount, resolvedCount, ambiguousCount, unresolvedCount };
|
|
2450
2545
|
}
|
|
2546
|
+
function implementationContextForOperation(db, operation) {
|
|
2547
|
+
if (operation.provenance !== "inherited" || !operation.baseOperationId) return operation;
|
|
2548
|
+
const base = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,o.operation_name operationName,o.provenance provenance,o.base_operation_id baseOperationId,s.service_path servicePath,s.repo_id modelRepoId,r.name modelRepo,r.package_name modelPackage,r.kind modelKind FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE o.id=?`).get(operation.baseOperationId);
|
|
2549
|
+
if (!base) return operation;
|
|
2550
|
+
return { ...base, effectiveOperationId: operation.operationId, effectiveServicePath: operation.servicePath, effectiveOperationPath: operation.operationPath };
|
|
2551
|
+
}
|
|
2451
2552
|
function rankedImplementationCandidates(db, workspaceId, operation) {
|
|
2452
2553
|
const rows2 = implementationCandidates(db, workspaceId, operation);
|
|
2453
2554
|
return deduplicateCandidates(rows2.map((row) => scoreImplementationCandidate(row, operation))).sort((a, b) => b.score - a.score || String(a.className).localeCompare(String(b.className)) || a.methodId - b.methodId);
|
|
@@ -3191,4 +3292,4 @@ export {
|
|
|
3191
3292
|
linkWorkspace,
|
|
3192
3293
|
trace
|
|
3193
3294
|
};
|
|
3194
|
-
//# sourceMappingURL=chunk-
|
|
3295
|
+
//# sourceMappingURL=chunk-RP3BJ64F.js.map
|