@saptools/service-flow 0.1.41 → 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 +7 -0
- package/dist/{chunk-7L4QKKGN.js → chunk-RP3BJ64F.js} +74 -5
- package/dist/chunk-RP3BJ64F.js.map +1 -0
- package/dist/cli.js +28 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/indexer/cds-extension-resolver.ts +27 -4
- package/src/linker/cross-repo-linker.ts +12 -2
- package/src/parsers/cds-parser.ts +18 -1
- package/src/parsers/outbound-call-parser.ts +29 -1
- package/dist/chunk-7L4QKKGN.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
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
|
+
|
|
3
10
|
## 0.1.41
|
|
4
11
|
|
|
5
12
|
- Added imported CDS extension provenance and materialized inherited operations at concrete extension paths without guessing by simple service name.
|
|
@@ -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;
|
|
@@ -235,7 +266,7 @@ async function parseCdsFile(repoPath, filePath) {
|
|
|
235
266
|
const namespace = /namespace\s+([\w.]+)\s*;/.exec(masked)?.[1];
|
|
236
267
|
const services = [];
|
|
237
268
|
const pendingAnnotations = [];
|
|
238
|
-
const usings = collectUsings(text);
|
|
269
|
+
const usings = collectUsings(maskComments(text));
|
|
239
270
|
for (const a of masked.matchAll(/@\s*\(/g)) pendingAnnotations.push(annotationRawAt(text, masked, a.index ?? 0));
|
|
240
271
|
const serviceRegex = /\b(?:(extend)\s+)?(?:(service)\s+)?([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\b/g;
|
|
241
272
|
let match;
|
|
@@ -926,13 +957,27 @@ function declarationScope(node) {
|
|
|
926
957
|
while (current.parent && !isFunctionLikeScope(current)) current = current.parent;
|
|
927
958
|
return current;
|
|
928
959
|
}
|
|
929
|
-
while (current.parent && !ts4.isBlock(current) && !ts4.isSourceFile(current) && !ts4.isModuleBlock(current) && !ts4.isCaseBlock(current) && !isFunctionLikeScope(current)) current = current.parent;
|
|
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;
|
|
930
961
|
return current;
|
|
931
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;
|
|
968
|
+
current = current.parent;
|
|
969
|
+
}
|
|
970
|
+
return void 0;
|
|
971
|
+
}
|
|
932
972
|
function isAccessibleDeclaration(declaration, use) {
|
|
933
973
|
const source = use.getSourceFile();
|
|
934
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);
|
|
935
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);
|
|
936
981
|
return ts4.isSourceFile(scope) || nodeContains(scope, use);
|
|
937
982
|
}
|
|
938
983
|
function resolveBinding(identifier, use) {
|
|
@@ -1215,6 +1260,20 @@ function classifyOutboundCallsInSource(source, filePath) {
|
|
|
1215
1260
|
const entityCallType = entityCallTypes[intent.kind];
|
|
1216
1261
|
const isODataQueryRead = method.toUpperCase() === "GET" && ["entity_query", "entity_key_read", "entity_navigation_query"].includes(intent.kind);
|
|
1217
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
|
+
}
|
|
1218
1277
|
}
|
|
1219
1278
|
} else if (ts4.isCallExpression(expr) && ts4.isIdentifier(expr.expression) && wrapperSpecs.has(expr.expression.text) || ts4.isIdentifier(expr) && wrapperSpecs.has(expr.text)) {
|
|
1220
1279
|
const wrapperName = ts4.isIdentifier(expr) ? expr.text : ts4.isCallExpression(expr) && ts4.isIdentifier(expr.expression) ? expr.expression.text : "";
|
|
@@ -2448,13 +2507,14 @@ function callEvidence(call, resolution, servicePath, op, destination, normalized
|
|
|
2448
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 };
|
|
2449
2508
|
}
|
|
2450
2509
|
function linkImplementations(db, workspaceId, generation) {
|
|
2451
|
-
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);
|
|
2452
2511
|
let edgeCount = 0;
|
|
2453
2512
|
let resolvedCount = 0;
|
|
2454
2513
|
let ambiguousCount = 0;
|
|
2455
2514
|
let unresolvedCount = 0;
|
|
2456
2515
|
for (const operation of operations) {
|
|
2457
|
-
const
|
|
2516
|
+
const implementationContext = implementationContextForOperation(db, operation);
|
|
2517
|
+
const candidates = rankedImplementationCandidates(db, workspaceId, implementationContext);
|
|
2458
2518
|
if (candidates.length === 0) continue;
|
|
2459
2519
|
const accepted = candidates.filter((candidate) => candidate.accepted);
|
|
2460
2520
|
const topScore = accepted[0]?.score ?? 0;
|
|
@@ -2465,6 +2525,9 @@ function linkImplementations(db, workspaceId, generation) {
|
|
|
2465
2525
|
operationPath: operation.operationPath,
|
|
2466
2526
|
operationName: operation.operationName,
|
|
2467
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,
|
|
2468
2531
|
candidates: candidates.map((candidate, index) => candidateEvidence(candidate, index + 1))
|
|
2469
2532
|
};
|
|
2470
2533
|
if (accepted.length === 0) {
|
|
@@ -2480,6 +2543,12 @@ function linkImplementations(db, workspaceId, generation) {
|
|
|
2480
2543
|
}
|
|
2481
2544
|
return { edgeCount, resolvedCount, ambiguousCount, unresolvedCount };
|
|
2482
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
|
+
}
|
|
2483
2552
|
function rankedImplementationCandidates(db, workspaceId, operation) {
|
|
2484
2553
|
const rows2 = implementationCandidates(db, workspaceId, operation);
|
|
2485
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);
|
|
@@ -3223,4 +3292,4 @@ export {
|
|
|
3223
3292
|
linkWorkspace,
|
|
3224
3293
|
trace
|
|
3225
3294
|
};
|
|
3226
|
-
//# sourceMappingURL=chunk-
|
|
3295
|
+
//# sourceMappingURL=chunk-RP3BJ64F.js.map
|