@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 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 candidates = rankedImplementationCandidates(db, workspaceId, operation);
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-7L4QKKGN.js.map
3295
+ //# sourceMappingURL=chunk-RP3BJ64F.js.map