@saptools/service-flow 0.1.36 → 0.1.37

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.
@@ -998,47 +998,51 @@ function isSupportedEventReceiver(receiver, rootReceiver, serviceVariables) {
998
998
  }
999
999
  function collectWrapperSpecs(source) {
1000
1000
  const specs = /* @__PURE__ */ new Map();
1001
+ const serviceVariables = collectServiceVariables(source);
1002
+ const calledNames = /* @__PURE__ */ new Set();
1003
+ const collectCalls = (node) => {
1004
+ if (ts4.isCallExpression(node)) {
1005
+ if (ts4.isIdentifier(node.expression)) calledNames.add(node.expression.text);
1006
+ if (ts4.isCallExpression(node.expression) && ts4.isIdentifier(node.expression.expression)) calledNames.add(node.expression.expression.text);
1007
+ }
1008
+ ts4.forEachChild(node, collectCalls);
1009
+ };
1010
+ collectCalls(source);
1001
1011
  const scanFunction = (name, fn) => {
1012
+ if (!calledNames.has(name)) return;
1002
1013
  const params = fn.parameters.map((param) => ts4.isIdentifier(param.name) ? param.name.text : void 0);
1003
- const closure = returnedClosure(fn);
1004
- if (!closure) return;
1005
1014
  const sends = [];
1006
1015
  const visit = (node) => {
1007
1016
  if (ts4.isCallExpression(node) && ts4.isPropertyAccessExpression(node.expression) && node.expression.name.text === "send" && ts4.isIdentifier(node.expression.expression)) {
1008
1017
  const objectArg = node.arguments[0];
1009
1018
  if (objectArg && ts4.isObjectLiteralExpression(objectArg)) {
1010
- const pathProp = objectArg.properties.find((property) => ts4.isShorthandPropertyAssignment(property) && property.name.text === "path");
1011
- if (pathProp) sends.push({ client: node.expression.expression.text, path: pathProp.name.text, method: objectPropertyText(objectArg, "method") });
1019
+ const pathProp = propertyInitializer(objectArg, "path");
1020
+ const methodProp = propertyInitializer(objectArg, "method");
1021
+ const pathName = pathProp && ts4.isIdentifier(pathProp) ? pathProp.text : void 0;
1022
+ const methodName = methodProp && ts4.isIdentifier(methodProp) ? methodProp.text : void 0;
1023
+ const methodLiteral = staticExpressionText(methodProp, variableInitializers(source));
1024
+ if (pathName) sends.push({ client: node.expression.expression.text, path: pathName, method: methodName, methodLiteral, start: node.getStart(source), end: node.getEnd() });
1012
1025
  }
1013
1026
  }
1014
1027
  ts4.forEachChild(node, visit);
1015
1028
  };
1016
- visit(closure);
1029
+ visit(fn);
1017
1030
  if (sends.length !== 1) return;
1018
1031
  const found = sends[0];
1019
1032
  const clientIndex = params.indexOf(found.client);
1020
1033
  const pathIndex = params.indexOf(found.path);
1021
- const methodIndex = found.method && params.includes(found.method) ? params.indexOf(found.method) : void 0;
1022
- if (clientIndex >= 0 && pathIndex >= 0) specs.set(name, { clientIndex, pathIndex, methodIndex, definitionLine: lineOf3(source.text, fn.getStart(source)), internalStart: closure.getStart(source), internalEnd: closure.getEnd() });
1034
+ const methodIndex = found.method ? params.indexOf(found.method) : -1;
1035
+ const capturesKnownClient = serviceVariables.has(found.client) || /^(srv|service|serviceClient|client|.*Client)$/.test(found.client);
1036
+ if (pathIndex >= 0 && (clientIndex >= 0 || capturesKnownClient)) specs.set(name, { clientIndex: clientIndex >= 0 ? clientIndex : void 0, clientName: clientIndex >= 0 ? void 0 : found.client, pathIndex, methodIndex: methodIndex >= 0 ? methodIndex : void 0, methodName: found.method, methodLiteral: found.methodLiteral, definitionLine: lineOf3(source.text, fn.getStart(source)), internalStart: found.start, internalEnd: found.end });
1023
1037
  };
1024
- for (const stmt of source.statements) {
1025
- if (ts4.isFunctionDeclaration(stmt) && stmt.name) scanFunction(stmt.name.text, stmt);
1026
- if (ts4.isVariableStatement(stmt)) {
1027
- for (const decl of stmt.declarationList.declarations) if (ts4.isIdentifier(decl.name) && decl.initializer && (ts4.isArrowFunction(decl.initializer) || ts4.isFunctionExpression(decl.initializer))) scanFunction(decl.name.text, decl.initializer);
1028
- }
1029
- }
1038
+ const visitTop = (node) => {
1039
+ if (ts4.isFunctionDeclaration(node) && node.name) scanFunction(node.name.text, node);
1040
+ if (ts4.isVariableDeclaration(node) && ts4.isIdentifier(node.name) && node.initializer && (ts4.isArrowFunction(node.initializer) || ts4.isFunctionExpression(node.initializer))) scanFunction(node.name.text, node.initializer);
1041
+ ts4.forEachChild(node, visitTop);
1042
+ };
1043
+ visitTop(source);
1030
1044
  return specs;
1031
1045
  }
1032
- function returnedClosure(fn) {
1033
- const body = fn.body;
1034
- if (!body) return void 0;
1035
- if (ts4.isArrowFunction(fn) && !ts4.isBlock(body)) return ts4.isArrowFunction(body) || ts4.isFunctionExpression(body) ? body : void 0;
1036
- if (!ts4.isBlock(body)) return void 0;
1037
- const returns = body.statements.filter(ts4.isReturnStatement);
1038
- if (returns.length !== 1) return void 0;
1039
- const expr = returns[0]?.expression;
1040
- return expr && (ts4.isArrowFunction(expr) || ts4.isFunctionExpression(expr)) ? expr : void 0;
1041
- }
1042
1046
  function classifyOutboundCallsInSource(source, filePath) {
1043
1047
  const calls = [];
1044
1048
  const sourceFile = normalizePath(filePath);
@@ -1067,26 +1071,31 @@ function classifyOutboundCallsInSource(source, filePath) {
1067
1071
  const receiver = receiverName(expr.expression);
1068
1072
  const query = objectPropertyText(objectArg, "query");
1069
1073
  const method = stripQuotes(objectPropertyText(objectArg, "method") ?? "POST");
1070
- const op = objectPropertyText(objectArg, "path") ?? objectPropertyText(objectArg, "event");
1074
+ const pathExpr = propertyInitializer(objectArg, "path") ?? propertyInitializer(objectArg, "event");
1075
+ const op = pathExpr ? staticExpressionText(pathExpr, initializers) ?? pathExpr.getText(source) : void 0;
1071
1076
  const shorthandPath = objectPropertyIsShorthand(objectArg, "path");
1072
- const operationPathExpr = op && !shorthandPath ? `/${stripQuotes(op).replace(/^\//, "")}` : void 0;
1077
+ const staticPath = staticExpressionText(pathExpr, initializers);
1078
+ const literalPath = isStringLike(pathExpr) ? pathExpr.text : pathExpr && ts4.isTemplateExpression(pathExpr) ? pathExpr.getText(source) : void 0;
1079
+ const operationPathExpr = staticPath ? `/${stripQuotes(staticPath).replace(/^\//, "")}` : literalPath ? `/${stripQuotes(literalPath).replace(/^\//, "")}` : void 0;
1073
1080
  const intent = classifyODataPathIntent(operationPathExpr, method);
1074
1081
  const entityCallTypes = { entity_mutation: "remote_entity_mutation", entity_delete: "remote_entity_delete", entity_media: "remote_entity_media", entity_candidate: "remote_entity_candidate" };
1075
1082
  const entityCallType = entityCallTypes[intent.kind];
1076
1083
  const isODataQueryRead = method.toUpperCase() === "GET" && ["entity_query", "entity_key_read", "entity_navigation_query"].includes(intent.kind);
1077
- 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 && shorthandPath ? "dynamic_operation_path_identifier" : void 0 }, { receiver, classifier: "service_client_send_object", operationPathExpression: shorthandPath ? op : void 0, odataPathIntent: operationPathExpr ? intent : void 0, parserWarning: shorthandPath ? "dynamic_operation_path_identifier" : void 0 });
1084
+ 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, literalPathSource: shorthandPath && staticPath ? "same_scope_const_initializer" : void 0, odataPathIntent: operationPathExpr ? intent : void 0, parserWarning: !query && pathExpr && !operationPathExpr ? "dynamic_operation_path_identifier" : void 0 });
1078
1085
  }
1079
1086
  } else if (ts4.isCallExpression(expr) && ts4.isIdentifier(expr.expression) && wrapperSpecs.has(expr.expression.text) || ts4.isIdentifier(expr) && wrapperSpecs.has(expr.text)) {
1080
1087
  const wrapperName = ts4.isIdentifier(expr) ? expr.text : ts4.isCallExpression(expr) && ts4.isIdentifier(expr.expression) ? expr.expression.text : "";
1081
1088
  const wrapperArgs = ts4.isIdentifier(expr) ? node.arguments : ts4.isCallExpression(expr) ? expr.arguments : node.arguments;
1082
1089
  const spec = wrapperSpecs.get(wrapperName);
1083
- const clientArg = spec ? wrapperArgs[spec.clientIndex] : void 0;
1090
+ const clientArg = spec?.clientIndex === void 0 ? void 0 : wrapperArgs[spec.clientIndex];
1084
1091
  const pathArg = spec ? wrapperArgs[spec.pathIndex] : void 0;
1085
1092
  const methodArg = spec?.methodIndex === void 0 ? void 0 : wrapperArgs[spec.methodIndex];
1086
- if (spec && clientArg && ts4.isIdentifier(clientArg) && isStringLike(pathArg)) {
1087
- add(node, { callType: "remote_action", serviceVariableName: clientArg.text, method: stripQuotes(methodArg?.getText(source) ?? "POST"), operationPathExpr: `/${pathArg.text.replace(/^\//, "")}`, payloadSummary: summarizeExpression(node.getText(source)), confidence: 0.75 }, { receiver: clientArg.text, classifier: "higher_order_wrapper_literal_path", wrapperFunction: wrapperName, wrapperDefinitionLine: spec.definitionLine, literalCallerArgumentDetected: true });
1088
- } else if (spec && clientArg && ts4.isIdentifier(clientArg)) {
1089
- add(node, { callType: "remote_action", serviceVariableName: clientArg.text, method: stripQuotes(methodArg?.getText(source) ?? "POST"), payloadSummary: summarizeExpression(node.getText(source)), confidence: 0.45, unresolvedReason: "dynamic_operation_path_identifier" }, { receiver: clientArg.text, classifier: "higher_order_wrapper_dynamic_path", wrapperFunction: wrapperName, wrapperDefinitionLine: spec.definitionLine, parserWarning: "dynamic_operation_path_identifier" });
1093
+ const receiver = clientArg && ts4.isIdentifier(clientArg) ? clientArg.text : spec?.clientName;
1094
+ const method = stripQuotes(staticExpressionText(methodArg, initializers) ?? spec?.methodLiteral ?? "POST");
1095
+ if (spec && receiver && isStringLike(pathArg)) {
1096
+ add(node, { callType: "remote_action", serviceVariableName: receiver, method, operationPathExpr: `/${pathArg.text.replace(/^\//, "")}`, payloadSummary: summarizeExpression(node.getText(source)), confidence: 0.75 }, { receiver, classifier: "higher_order_wrapper_literal_path", wrapperFunction: wrapperName, wrapperDefinitionLine: spec.definitionLine, callerLine: lineOf3(source.text, node.getStart(source)), literalPathSource: "wrapper_call_argument", literalCallerArgumentDetected: true });
1097
+ } else if (spec && receiver) {
1098
+ add(node, { callType: "remote_action", serviceVariableName: receiver, method, payloadSummary: summarizeExpression(node.getText(source)), confidence: 0.45, unresolvedReason: "dynamic_operation_path_identifier" }, { receiver, classifier: "higher_order_wrapper_dynamic_path", wrapperFunction: wrapperName, wrapperDefinitionLine: spec.definitionLine, callerLine: lineOf3(source.text, node.getStart(source)), parserWarning: "dynamic_operation_path_identifier" });
1090
1099
  }
1091
1100
  } else if (ts4.isPropertyAccessExpression(expr) && ["emit", "publish", "on"].includes(expr.name.text)) {
1092
1101
  const receiver = receiverName(expr.expression);
@@ -1257,6 +1266,17 @@ function unwrapIdentityExpression(expr) {
1257
1266
  if (ts5.isTypeAssertionExpression(expr)) return unwrapIdentityExpression(expr.expression);
1258
1267
  return expr;
1259
1268
  }
1269
+ function transactionReceiverName(expr) {
1270
+ const call = unwrapCall(expr);
1271
+ if (call && ts5.isPropertyAccessExpression(call.expression) && ["tx", "transaction"].includes(call.expression.name.text) && ts5.isIdentifier(call.expression.expression)) return call.expression.expression.text;
1272
+ const unwrapped = unwrapIdentityExpression(expr);
1273
+ if (ts5.isConditionalExpression(unwrapped)) {
1274
+ const left = transactionReceiverName(unwrapped.whenTrue);
1275
+ const right = transactionReceiverName(unwrapped.whenFalse);
1276
+ return left && left === right ? left : void 0;
1277
+ }
1278
+ return void 0;
1279
+ }
1260
1280
  function findConnectInExpression(expr) {
1261
1281
  const direct = unwrapCall(expr);
1262
1282
  if (direct) {
@@ -1343,9 +1363,8 @@ function collectLocalBindingFacts(fn) {
1343
1363
  if (ts5.isVariableDeclaration(node) && ts5.isIdentifier(node.name) && node.initializer) {
1344
1364
  const fact = findConnectInExpression(node.initializer);
1345
1365
  if (fact) bindings.set(node.name.text, fact);
1346
- const call = unwrapCall(node.initializer);
1347
- if (call && ts5.isPropertyAccessExpression(call.expression) && call.expression.name.text === "tx" && ts5.isIdentifier(call.expression.expression)) {
1348
- const sourceName = call.expression.expression.text;
1366
+ const sourceName = transactionReceiverName(node.initializer);
1367
+ if (sourceName) {
1349
1368
  const source = bindings.get(sourceName);
1350
1369
  if (source) bindings.set(node.name.text, { ...source, helperChain: [...source.helperChain ?? [], { aliasOf: sourceName, callerVariable: node.name.text, aliasKind: "transaction", transactionAliasSource: sourceName }] });
1351
1370
  }
@@ -1548,7 +1567,8 @@ async function parseServiceBindings(repoPath, filePath) {
1548
1567
  callerVariable: targetName,
1549
1568
  aliasOf: sourceName,
1550
1569
  aliasKind,
1551
- scopeRule: "same-file-source-order"
1570
+ scopeRule: "same-file-source-order",
1571
+ ...aliasKind === "transaction" ? { transactionAliasSource: sourceName } : {}
1552
1572
  }
1553
1573
  ]
1554
1574
  });
@@ -1780,8 +1800,9 @@ async function parseServiceBindings(repoPath, filePath) {
1780
1800
  recordDestructuredClassHelper(decl);
1781
1801
  await recordVariable(decl);
1782
1802
  recordIdentityAlias(decl);
1783
- if (ts5.isIdentifier(decl.name) && decl.initializer && ts5.isCallExpression(decl.initializer) && ts5.isPropertyAccessExpression(decl.initializer.expression) && decl.initializer.expression.name.text === "tx" && ts5.isIdentifier(decl.initializer.expression.expression)) {
1784
- cloneAliasBinding(decl.name.text, decl.initializer.expression.expression.text, "transaction", decl);
1803
+ if (ts5.isIdentifier(decl.name) && decl.initializer) {
1804
+ const sourceName = transactionReceiverName(decl.initializer);
1805
+ if (sourceName) cloneAliasBinding(decl.name.text, sourceName, "transaction", decl);
1785
1806
  }
1786
1807
  continue;
1787
1808
  }
@@ -3058,4 +3079,4 @@ export {
3058
3079
  linkWorkspace,
3059
3080
  trace
3060
3081
  };
3061
- //# sourceMappingURL=chunk-2UOIY2NI.js.map
3082
+ //# sourceMappingURL=chunk-WE3A6TOJ.js.map