@saptools/service-flow 0.1.41 → 0.1.43

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,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.43
4
+
5
+ - Reconciled inherited CDS operation search rows from the exact effective-operation set during extension materialization.
6
+ - Invalidated concrete extension repositories when derived inherited operations or base resolution semantics change, while preserving no-op generations.
7
+ - Tightened lexical binding scope so nested block declarations inside catch and loop bodies do not escape their real blocks.
8
+ - Classified proven CAP `send(operationName, payload)` calls, including immutable aliases, without applying the rule to generic `send()` receivers.
9
+
10
+ ## 0.1.42
11
+
12
+ - Continued inherited CDS extension operations into the selected base implementation while retaining concrete routing evidence.
13
+ - Reconciled materialized inherited operations on reindex so renamed or removed base operations do not leave stale effective rows.
14
+ - Kept ambiguous or unresolved extension bases without a selected base id and ignored commented CDS `using` declarations.
15
+ - Added positional remote CAP `Service.send(method, path, ...)` classification for proven CAP clients and tightened catch/loop lexical constant resolution.
16
+
3
17
  ## 0.1.41
4
18
 
5
19
  - 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;
@@ -919,6 +950,7 @@ function nodeContains(parent, child) {
919
950
  }
920
951
  function declarationScope(node) {
921
952
  if (ts4.isParameter(node)) return node.parent;
953
+ if (ts4.isCatchClause(node.parent) && node.parent.variableDeclaration === node) return node.parent;
922
954
  const list = node.parent;
923
955
  const blockScoped = (list.flags & (ts4.NodeFlags.Const | ts4.NodeFlags.Let)) !== 0;
924
956
  let current = list.parent;
@@ -926,13 +958,24 @@ function declarationScope(node) {
926
958
  while (current.parent && !isFunctionLikeScope(current)) current = current.parent;
927
959
  return current;
928
960
  }
929
- while (current.parent && !ts4.isBlock(current) && !ts4.isSourceFile(current) && !ts4.isModuleBlock(current) && !ts4.isCaseBlock(current) && !isFunctionLikeScope(current)) current = current.parent;
961
+ while (current.parent && !ts4.isBlock(current) && !ts4.isSourceFile(current) && !ts4.isModuleBlock(current) && !ts4.isCaseBlock(current) && !isLoopInitializerScope(node, current) && !isFunctionLikeScope(current)) current = current.parent;
930
962
  return current;
931
963
  }
964
+ function isLoopInitializerScope(declaration, scope) {
965
+ const list = declaration.parent;
966
+ return ts4.isForStatement(scope) && scope.initializer === list || (ts4.isForInStatement(scope) || ts4.isForOfStatement(scope)) && scope.initializer === list;
967
+ }
968
+ function catchBindingScope(declaration) {
969
+ if (ts4.isParameter(declaration)) return void 0;
970
+ return ts4.isCatchClause(declaration.parent) && declaration.parent.variableDeclaration === declaration ? declaration.parent : 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 catchScope = catchBindingScope(declaration);
976
+ if (catchScope) return nodeContains(catchScope.block, use);
935
977
  const scope = declarationScope(declaration);
978
+ if (ts4.isForStatement(scope) || ts4.isForInStatement(scope) || ts4.isForOfStatement(scope)) return nodeContains(scope.statement, use);
936
979
  return ts4.isSourceFile(scope) || nodeContains(scope, use);
937
980
  }
938
981
  function resolveBinding(identifier, use) {
@@ -1039,6 +1082,11 @@ function httpMethodFromObject(object, use) {
1039
1082
  const text = resolveExpression(propertyInitializer(object, "method"), use, "literal").value;
1040
1083
  return text ? stripQuotes(text).toUpperCase() : void 0;
1041
1084
  }
1085
+ var supportedHttpMethods = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]);
1086
+ function safeOperationName(value) {
1087
+ if (!value || !/^[A-Za-z_$][\w$]*(?:[./][A-Za-z_$][\w$]*)*$/.test(value)) return void 0;
1088
+ return operationPathFromStatic(value);
1089
+ }
1042
1090
  function hasTemplatePlaceholder(value) {
1043
1091
  return /\$\{|%7B|%7D/i.test(value);
1044
1092
  }
@@ -1215,6 +1263,22 @@ function classifyOutboundCallsInSource(source, filePath) {
1215
1263
  const entityCallType = entityCallTypes[intent.kind];
1216
1264
  const isODataQueryRead = method.toUpperCase() === "GET" && ["entity_query", "entity_key_read", "entity_navigation_query"].includes(intent.kind);
1217
1265
  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 });
1266
+ } else {
1267
+ const receiver = receiverName(expr.expression);
1268
+ const rootReceiver = rootReceiverName(expr.expression);
1269
+ const firstArg2 = resolveExpression(node.arguments[0], node, "literal");
1270
+ const method = firstArg2.value?.toUpperCase();
1271
+ const pathArg = node.arguments[1];
1272
+ const supported = method && supportedHttpMethods.has(method);
1273
+ if (receiver && supported && serviceVariables.has(rootReceiver ?? receiver)) {
1274
+ const resolvedPath = staticPathExpression(pathArg, node);
1275
+ const operationPathExpr = operationPathFromStatic(resolvedPath.text);
1276
+ const intent = classifyODataPathIntent(operationPathExpr, method);
1277
+ 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" });
1278
+ } else if (receiver && serviceVariables.has(rootReceiver ?? receiver)) {
1279
+ const operationPathExpr = safeOperationName(firstArg2.value);
1280
+ add(node, { callType: "remote_action", serviceVariableName: rootReceiver ?? receiver, operationPathExpr, payloadSummary: summarizeExpression(node.getText(source)), confidence: operationPathExpr ? 0.75 : 0.35, unresolvedReason: operationPathExpr ? void 0 : "unsupported_cap_send_signature" }, { receiver, rootReceiver, classifier: operationPathExpr ? "service_client_send_operation_event" : "service_client_send_unsupported_signature", rawOperationExpression: firstArg2.rawExpression, literalOperationSource: firstArg2.value ? firstArg2.sourceKind : void 0, parserWarning: operationPathExpr ? void 0 : "unsupported_cap_send_signature" });
1281
+ }
1218
1282
  }
1219
1283
  } else if (ts4.isCallExpression(expr) && ts4.isIdentifier(expr.expression) && wrapperSpecs.has(expr.expression.text) || ts4.isIdentifier(expr) && wrapperSpecs.has(expr.text)) {
1220
1284
  const wrapperName = ts4.isIdentifier(expr) ? expr.text : ts4.isCallExpression(expr) && ts4.isIdentifier(expr.expression) ? expr.expression.text : "";
@@ -2448,13 +2512,14 @@ function callEvidence(call, resolution, servicePath, op, destination, normalized
2448
2512
  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
2513
  }
2450
2514
  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);
2515
+ 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
2516
  let edgeCount = 0;
2453
2517
  let resolvedCount = 0;
2454
2518
  let ambiguousCount = 0;
2455
2519
  let unresolvedCount = 0;
2456
2520
  for (const operation of operations) {
2457
- const candidates = rankedImplementationCandidates(db, workspaceId, operation);
2521
+ const implementationContext = implementationContextForOperation(db, operation);
2522
+ const candidates = rankedImplementationCandidates(db, workspaceId, implementationContext);
2458
2523
  if (candidates.length === 0) continue;
2459
2524
  const accepted = candidates.filter((candidate) => candidate.accepted);
2460
2525
  const topScore = accepted[0]?.score ?? 0;
@@ -2465,6 +2530,9 @@ function linkImplementations(db, workspaceId, generation) {
2465
2530
  operationPath: operation.operationPath,
2466
2531
  operationName: operation.operationName,
2467
2532
  modelPackage: { id: operation.modelRepoId, name: operation.modelRepo, packageName: operation.modelPackage },
2533
+ implementationSource: implementationContext.operationId === operation.operationId ? "direct_or_concrete_override" : "inherited_from_base_operation",
2534
+ baseOperationId: operation.baseOperationId,
2535
+ implementationOperationId: implementationContext.operationId,
2468
2536
  candidates: candidates.map((candidate, index) => candidateEvidence(candidate, index + 1))
2469
2537
  };
2470
2538
  if (accepted.length === 0) {
@@ -2480,6 +2548,12 @@ function linkImplementations(db, workspaceId, generation) {
2480
2548
  }
2481
2549
  return { edgeCount, resolvedCount, ambiguousCount, unresolvedCount };
2482
2550
  }
2551
+ function implementationContextForOperation(db, operation) {
2552
+ if (operation.provenance !== "inherited" || !operation.baseOperationId) return operation;
2553
+ 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);
2554
+ if (!base) return operation;
2555
+ return { ...base, effectiveOperationId: operation.operationId, effectiveServicePath: operation.servicePath, effectiveOperationPath: operation.operationPath };
2556
+ }
2483
2557
  function rankedImplementationCandidates(db, workspaceId, operation) {
2484
2558
  const rows2 = implementationCandidates(db, workspaceId, operation);
2485
2559
  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 +3297,4 @@ export {
3223
3297
  linkWorkspace,
3224
3298
  trace
3225
3299
  };
3226
- //# sourceMappingURL=chunk-7L4QKKGN.js.map
3300
+ //# sourceMappingURL=chunk-KHQK7CFH.js.map