@saptools/service-flow 0.1.21 → 0.1.23
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 +8 -0
- package/README.md +3 -3
- package/TECHNICAL-NOTE.md +8 -0
- package/dist/{chunk-TSMDIKML.js → chunk-LCXDOSWL.js} +183 -28
- package/dist/chunk-LCXDOSWL.js.map +1 -0
- package/dist/cli.js +42 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-TSMDIKML.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.22
|
|
4
|
+
|
|
5
|
+
- Propagate conservative same-file identity aliases of connected service clients, including typed, `as`, `satisfies`, helper-returned, and transitive aliases, while preserving `.tx()` alias evidence.
|
|
6
|
+
- Link outbound `send(...)` calls through identity aliases to the closest same-file binding row so destination, service-path, placeholder, and helper-chain evidence remains available.
|
|
7
|
+
- Add narrow same-file wrapper literal-path propagation for wrappers that pass a client parameter directly to `send({ path })`; dynamic wrapper paths remain semantic dynamic targets with parser warnings.
|
|
8
|
+
- Improve trace-time contextual implementation selection evidence and suppress stale ambiguous unresolved reasons when a unique contextual handler is selected.
|
|
9
|
+
- Expand `doctor --strict` with alias-binding, no-binding remote-action, contextual implementation, and wrapper path propagation quality aggregates.
|
|
10
|
+
|
|
3
11
|
## 0.1.21
|
|
4
12
|
|
|
5
13
|
- Propagate helper-returned connected clients from function declarations, arrow-function variables, function-expression variables, named export lists, and aliased exports into caller destructuring and transaction aliases.
|
package/README.md
CHANGED
|
@@ -318,7 +318,7 @@ service-flow trace --workspace /path/to/workspace --repo facade-service --operat
|
|
|
318
318
|
|
|
319
319
|
When a concrete target exists after variable substitution, the trace shows both the parameterized evidence and the resolved match. When it does not, `service-flow` keeps the edge as a dynamic candidate or unresolved edge so the missing link remains visible.
|
|
320
320
|
|
|
321
|
-
Service-binding evidence keeps these fields distinct: service alias, alias expression, destination expression, service-path expression, operation-path expression, and runtime placeholders. Helpers that return concrete connected clients inside object properties are followed through destructuring and
|
|
321
|
+
Service-binding evidence keeps these fields distinct: service alias, alias expression, destination expression, service-path expression, operation-path expression, and runtime placeholders. Helpers that return concrete connected clients inside object properties are followed through destructuring, simple identity aliases, transitive same-file aliases, and transaction aliases while preserving helper-chain evidence. This is important for common CAP helpers such as `cds.connect.to(`remote_${code}`, { credentials: { destination: `remote_${code}`, path: `/${entityType}ProcessService` } })`, where the alias is not the service path.
|
|
322
322
|
|
|
323
323
|
By default, production traces should be built from production source files. Keep generated credentials and local state out of git, and use explicit fixture/test workspaces when validating test-only mocked service clients so they do not pollute production graph interpretation.
|
|
324
324
|
|
|
@@ -418,8 +418,8 @@ Run `service-flow index` after source, CDS, package metadata, or helper-package
|
|
|
418
418
|
<details>
|
|
419
419
|
<summary><b>Why is an expected call unresolved?</b></summary>
|
|
420
420
|
|
|
421
|
-
Check `service-flow doctor`, then inspect the facts with `service-flow list services`, `service-flow list operations`, and `service-flow list calls`. Dynamic destinations may need `--var key=value`; the key is the full trimmed expression inside `${...}` and is matched literally without JavaScript evaluation. Operation-path-only ambiguous remote actions usually mean the call had no service binding id; inspect `list calls`, `inspect operation`, and `doctor --strict` to determine whether helper-return propagation or wrapper support is missing. Contextual implementation selection only continues into a handler when static evidence such as caller repository, resolved service path, destination/alias expression, dependency edges, registration package, and local service ownership makes exactly one candidate stronger; ties remain ambiguous with reasons.
|
|
422
|
-
Default doctor output is intended to focus on actionable indexing or trace-impacting issues; use `--strict` when you need exhaustive model-shape diagnostics for entity-only or extension-heavy CDS models. Strict mode also reports normalized OData invocation ambiguity
|
|
421
|
+
Check `service-flow doctor`, then inspect the facts with `service-flow list services`, `service-flow list operations`, and `service-flow list calls`. Dynamic destinations may need `--var key=value`; the key is the full trimmed expression inside `${...}` and is matched literally without JavaScript evaluation. Operation-path-only ambiguous remote actions usually mean the call had no service binding id; same-file identity aliases are propagated, while property/call-expression aliases and ambiguous wrapper flows remain conservative; inspect `list calls`, `inspect operation`, and `doctor --strict` to determine whether helper-return propagation or wrapper support is missing. Contextual implementation selection only continues into a handler when static evidence such as caller repository, resolved service path, destination/alias expression, dependency edges, registration package, and local service ownership makes exactly one candidate stronger; ties remain ambiguous with reasons.
|
|
422
|
+
Default doctor output is intended to focus on actionable indexing or trace-impacting issues; use `--strict` when you need exhaustive model-shape diagnostics for entity-only or extension-heavy CDS models. Strict mode also reports normalized OData invocation ambiguity, remote-action target quality, likely missed identity aliases, no-binding remote actions, contextual implementation stops, and wrapper dynamic-path candidates, including whether unresolved unknown/dynamic paths are semantic instead of numeric call ids.
|
|
423
423
|
|
|
424
424
|
</details>
|
|
425
425
|
|
package/TECHNICAL-NOTE.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Service Flow Resolution Notes
|
|
2
2
|
|
|
3
|
+
## 0.1.22 alias, wrapper, contextual trace, and strict doctor notes
|
|
4
|
+
|
|
5
|
+
- Same-file service-client identity aliases now create their own service binding rows when the right-hand side is a known connected client variable. The parser accepts direct identifier aliases plus typed, `as`, and `satisfies` forms, supports source-order transitive aliases, and records `aliasKind: identity` helper-chain evidence. It still does not infer aliases from property reads, indexed access, function calls, object metadata, or cross-scope guesses.
|
|
6
|
+
- Outbound calls through identity aliases can attach to the closest alias binding row in the same source file, preserving alias, destination, service path, placeholder, and helper-chain evidence for link and trace.
|
|
7
|
+
- Wrapper path propagation is intentionally narrow: same-file wrappers can resolve literal caller paths only when a wrapper client parameter is passed directly to `.send(...)` and a wrapper path parameter is passed as `send({ path })`. Dynamic caller paths remain dynamic with parser-warning evidence.
|
|
8
|
+
- Contextual implementation selection now records selected/tied score evidence. When trace selects a unique contextual handler, the implementation hop sets `contextualImplementationSelected` and no longer renders the original ambiguous reason as a failure.
|
|
9
|
+
- Strict doctor adds compact aggregates for likely missed identity aliases, remote actions with operation paths but no binding id, contextual implementation stops, and wrapper dynamic-path candidates.
|
|
10
|
+
|
|
3
11
|
## 0.1.21 helper-return propagation and contextual trace notes
|
|
4
12
|
|
|
5
13
|
- Helper-return binding analysis uses the same returned-object scanner for `function` declarations, `async function` declarations, arrow-function variables, async arrow-function variables, and function-expression variables. Named export lists and aliases are resolved, so `export { connectCatalog as createCatalogClient }` can be destructured by callers without losing evidence.
|
|
@@ -638,11 +638,47 @@ function isSupportedEventReceiver(receiver, rootReceiver, serviceVariables) {
|
|
|
638
638
|
if (/^(srv|service|serviceClient|messaging|messageClient|eventClient)$/.test(candidate)) return true;
|
|
639
639
|
return false;
|
|
640
640
|
}
|
|
641
|
+
function collectWrapperSpecs(source) {
|
|
642
|
+
const specs = /* @__PURE__ */ new Map();
|
|
643
|
+
const scanFunction = (name, fn) => {
|
|
644
|
+
const params = fn.parameters.map((param) => ts4.isIdentifier(param.name) ? param.name.text : void 0);
|
|
645
|
+
let found;
|
|
646
|
+
const visit = (node) => {
|
|
647
|
+
if (found) return;
|
|
648
|
+
if (node !== fn && (ts4.isFunctionDeclaration(node) || ts4.isArrowFunction(node) || ts4.isFunctionExpression(node))) {
|
|
649
|
+
ts4.forEachChild(node, visit);
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
if (ts4.isCallExpression(node) && ts4.isPropertyAccessExpression(node.expression) && node.expression.name.text === "send" && ts4.isIdentifier(node.expression.expression)) {
|
|
653
|
+
const objectArg = node.arguments[0];
|
|
654
|
+
if (objectArg && ts4.isObjectLiteralExpression(objectArg)) {
|
|
655
|
+
const pathProp = objectArg.properties.find((property) => ts4.isShorthandPropertyAssignment(property) && property.name.text === "path");
|
|
656
|
+
if (pathProp) found = { client: node.expression.expression.text, path: pathProp.name.text, method: objectPropertyText(objectArg, "method") };
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
ts4.forEachChild(node, visit);
|
|
660
|
+
};
|
|
661
|
+
visit(fn);
|
|
662
|
+
if (!found) return;
|
|
663
|
+
const clientIndex = params.indexOf(found.client);
|
|
664
|
+
const pathIndex = params.indexOf(found.path);
|
|
665
|
+
const methodIndex = found.method && params.includes(found.method) ? params.indexOf(found.method) : void 0;
|
|
666
|
+
if (clientIndex >= 0 && pathIndex >= 0) specs.set(name, { clientIndex, pathIndex, methodIndex });
|
|
667
|
+
};
|
|
668
|
+
for (const stmt of source.statements) {
|
|
669
|
+
if (ts4.isFunctionDeclaration(stmt) && stmt.name) scanFunction(stmt.name.text, stmt);
|
|
670
|
+
if (ts4.isVariableStatement(stmt)) {
|
|
671
|
+
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);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return specs;
|
|
675
|
+
}
|
|
641
676
|
function classifyOutboundCallsInSource(source, filePath) {
|
|
642
677
|
const calls = [];
|
|
643
678
|
const sourceFile = normalizePath(filePath);
|
|
644
679
|
const initializers = variableInitializers(source);
|
|
645
680
|
const serviceVariables = collectServiceVariables(source);
|
|
681
|
+
const wrapperSpecs = collectWrapperSpecs(source);
|
|
646
682
|
const add = (node, fact, extra) => {
|
|
647
683
|
calls.push({ node, fact: { ...fact, sourceFile, sourceLine: lineOf3(source.text, node.getStart(source)), confidence: fact.confidence ?? 0.8, evidence: parserEvidence(source, node, extra) } });
|
|
648
684
|
};
|
|
@@ -664,6 +700,14 @@ function classifyOutboundCallsInSource(source, filePath) {
|
|
|
664
700
|
const shorthandPath = objectPropertyIsShorthand(objectArg, "path");
|
|
665
701
|
add(node, { callType: query ? "remote_query" : "remote_action", serviceVariableName: receiver, method: stripQuotes(objectPropertyText(objectArg, "method") ?? "POST"), operationPathExpr: op && !shorthandPath ? `/${stripQuotes(op).replace(/^\//, "")}` : void 0, queryEntity: query ? extractQueryEntity(query) : 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, parserWarning: shorthandPath ? "dynamic_operation_path_identifier" : void 0 });
|
|
666
702
|
}
|
|
703
|
+
} else if (ts4.isCallExpression(expr) && ts4.isIdentifier(expr.expression) && wrapperSpecs.has(expr.expression.text)) {
|
|
704
|
+
const spec = wrapperSpecs.get(expr.expression.text);
|
|
705
|
+
const clientArg = spec ? expr.arguments[spec.clientIndex] : void 0;
|
|
706
|
+
const pathArg = spec ? expr.arguments[spec.pathIndex] : void 0;
|
|
707
|
+
const methodArg = spec?.methodIndex === void 0 ? void 0 : expr.arguments[spec.methodIndex];
|
|
708
|
+
if (spec && clientArg && ts4.isIdentifier(clientArg) && isStringLike(pathArg)) {
|
|
709
|
+
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: "local_wrapper_literal_path", wrapperFunction: expr.expression.text, literalCallerArgumentDetected: true });
|
|
710
|
+
}
|
|
667
711
|
} else if (ts4.isPropertyAccessExpression(expr) && ["emit", "publish", "on"].includes(expr.name.text)) {
|
|
668
712
|
const receiver = receiverName(expr.expression);
|
|
669
713
|
const rootReceiver = rootReceiverName(expr.expression);
|
|
@@ -815,9 +859,19 @@ function connectFactFromCall(call) {
|
|
|
815
859
|
}
|
|
816
860
|
function unwrapCall(expr) {
|
|
817
861
|
if (ts5.isAwaitExpression(expr)) return unwrapCall(expr.expression);
|
|
862
|
+
if (ts5.isParenthesizedExpression(expr)) return unwrapCall(expr.expression);
|
|
863
|
+
if (ts5.isAsExpression(expr) || ts5.isSatisfiesExpression(expr)) return unwrapCall(expr.expression);
|
|
864
|
+
if (ts5.isTypeAssertionExpression(expr)) return unwrapCall(expr.expression);
|
|
818
865
|
if (ts5.isCallExpression(expr)) return expr;
|
|
819
866
|
return void 0;
|
|
820
867
|
}
|
|
868
|
+
function unwrapIdentityExpression(expr) {
|
|
869
|
+
if (ts5.isAwaitExpression(expr)) return unwrapIdentityExpression(expr.expression);
|
|
870
|
+
if (ts5.isParenthesizedExpression(expr)) return unwrapIdentityExpression(expr.expression);
|
|
871
|
+
if (ts5.isAsExpression(expr) || ts5.isSatisfiesExpression(expr)) return unwrapIdentityExpression(expr.expression);
|
|
872
|
+
if (ts5.isTypeAssertionExpression(expr)) return unwrapIdentityExpression(expr.expression);
|
|
873
|
+
return expr;
|
|
874
|
+
}
|
|
821
875
|
function findConnectInExpression(expr) {
|
|
822
876
|
const direct = unwrapCall(expr);
|
|
823
877
|
if (direct) {
|
|
@@ -1075,23 +1129,51 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1075
1129
|
async function importedHelper(localName) {
|
|
1076
1130
|
return (await importedHelpers(localName)).find((row) => !row.helper.returnedProperty);
|
|
1077
1131
|
}
|
|
1078
|
-
|
|
1132
|
+
function bindingForVariable(variableName) {
|
|
1133
|
+
const sourceFile = normalizePath(filePath);
|
|
1134
|
+
return [...out].reverse().find((row) => row.variableName === variableName && row.sourceFile === sourceFile);
|
|
1135
|
+
}
|
|
1136
|
+
function cloneAliasBinding(targetName, sourceName, aliasKind, node) {
|
|
1137
|
+
const existing = bindingForVariable(sourceName);
|
|
1138
|
+
if (!existing) return;
|
|
1139
|
+
out.push({
|
|
1140
|
+
...existing,
|
|
1141
|
+
variableName: targetName,
|
|
1142
|
+
sourceLine: lineOf4(sourceFileAst, node),
|
|
1143
|
+
helperChain: [
|
|
1144
|
+
...existing.helperChain ?? [],
|
|
1145
|
+
{
|
|
1146
|
+
callerVariable: targetName,
|
|
1147
|
+
aliasOf: sourceName,
|
|
1148
|
+
aliasKind,
|
|
1149
|
+
scopeRule: "same-file-source-order"
|
|
1150
|
+
}
|
|
1151
|
+
]
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
function recordIdentityAlias(decl) {
|
|
1079
1155
|
if (!ts5.isIdentifier(decl.name) || !decl.initializer) return;
|
|
1080
|
-
const
|
|
1156
|
+
const unwrapped = unwrapIdentityExpression(decl.initializer);
|
|
1157
|
+
if (!ts5.isIdentifier(unwrapped)) return;
|
|
1158
|
+
cloneAliasBinding(decl.name.text, unwrapped.text, "identity", decl);
|
|
1159
|
+
}
|
|
1160
|
+
async function recordBindingFromExpression(targetName, expr, node, aliasKind) {
|
|
1161
|
+
const call = unwrapCall(expr);
|
|
1081
1162
|
if (!call) return;
|
|
1082
1163
|
const direct = connectFactFromCall(call);
|
|
1083
1164
|
if (direct)
|
|
1084
1165
|
out.push({
|
|
1085
|
-
variableName:
|
|
1166
|
+
variableName: targetName,
|
|
1086
1167
|
...direct,
|
|
1087
1168
|
sourceFile: normalizePath(filePath),
|
|
1088
|
-
sourceLine: lineOf4(sourceFileAst,
|
|
1169
|
+
sourceLine: lineOf4(sourceFileAst, node),
|
|
1170
|
+
helperChain: aliasKind === "assignment" ? [{ callerVariable: targetName, assignedFrom: call.expression.getText(sourceFileAst), aliasKind, scopeRule: "same-file-source-order" }] : void 0
|
|
1089
1171
|
});
|
|
1090
1172
|
else if (ts5.isIdentifier(call.expression)) {
|
|
1091
1173
|
const resolved = await importedHelper(call.expression.text);
|
|
1092
1174
|
if (resolved)
|
|
1093
1175
|
out.push({
|
|
1094
|
-
variableName:
|
|
1176
|
+
variableName: targetName,
|
|
1095
1177
|
alias: resolved.helper.alias,
|
|
1096
1178
|
aliasExpr: resolved.helper.aliasExpr,
|
|
1097
1179
|
destinationExpr: resolved.helper.destinationExpr,
|
|
@@ -1099,10 +1181,11 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1099
1181
|
isDynamic: resolved.helper.isDynamic,
|
|
1100
1182
|
placeholders: resolved.helper.placeholders,
|
|
1101
1183
|
sourceFile: normalizePath(filePath),
|
|
1102
|
-
sourceLine: lineOf4(sourceFileAst,
|
|
1184
|
+
sourceLine: lineOf4(sourceFileAst, node),
|
|
1103
1185
|
helperChain: [
|
|
1104
1186
|
{
|
|
1105
|
-
callerVariable:
|
|
1187
|
+
callerVariable: targetName,
|
|
1188
|
+
...aliasKind === "assignment" ? { assignedFrom: call.expression.text, aliasKind, scopeRule: "same-file-source-order" } : {},
|
|
1106
1189
|
importedHelper: call.expression.text,
|
|
1107
1190
|
importSource: resolved.imp.sourceFile,
|
|
1108
1191
|
exportedSymbol: resolved.imp.exportedName,
|
|
@@ -1113,6 +1196,10 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1113
1196
|
});
|
|
1114
1197
|
}
|
|
1115
1198
|
}
|
|
1199
|
+
async function recordVariable(decl) {
|
|
1200
|
+
if (!ts5.isIdentifier(decl.name) || !decl.initializer) return;
|
|
1201
|
+
await recordBindingFromExpression(decl.name.text, decl.initializer, decl, "declaration");
|
|
1202
|
+
}
|
|
1116
1203
|
async function helpersForCall(call) {
|
|
1117
1204
|
if (!ts5.isIdentifier(call.expression)) return [];
|
|
1118
1205
|
const local = localObjectHelpers.get(call.expression.text) ?? [];
|
|
@@ -1145,6 +1232,39 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1145
1232
|
});
|
|
1146
1233
|
}
|
|
1147
1234
|
}
|
|
1235
|
+
async function recordDestructuredAssignment(pattern, expr, node) {
|
|
1236
|
+
const call = unwrapCall(expr);
|
|
1237
|
+
if (!call) return;
|
|
1238
|
+
const helpers = await helpersForCall(call);
|
|
1239
|
+
if (helpers.length === 0) return;
|
|
1240
|
+
for (const prop of pattern.properties) {
|
|
1241
|
+
let propertyName;
|
|
1242
|
+
let targetName;
|
|
1243
|
+
if (ts5.isShorthandPropertyAssignment(prop)) {
|
|
1244
|
+
propertyName = prop.name.text;
|
|
1245
|
+
targetName = prop.name.text;
|
|
1246
|
+
} else if (ts5.isPropertyAssignment(prop)) {
|
|
1247
|
+
propertyName = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
|
|
1248
|
+
targetName = ts5.isIdentifier(prop.initializer) ? prop.initializer.text : void 0;
|
|
1249
|
+
}
|
|
1250
|
+
if (!propertyName || !targetName) continue;
|
|
1251
|
+
const matches = helpers.filter((row) => row.helper.returnedProperty === propertyName);
|
|
1252
|
+
if (matches.length !== 1) continue;
|
|
1253
|
+
const resolved = matches[0];
|
|
1254
|
+
out.push({
|
|
1255
|
+
variableName: targetName,
|
|
1256
|
+
alias: resolved.helper.alias,
|
|
1257
|
+
aliasExpr: resolved.helper.aliasExpr,
|
|
1258
|
+
destinationExpr: resolved.helper.destinationExpr,
|
|
1259
|
+
servicePathExpr: resolved.helper.servicePathExpr,
|
|
1260
|
+
isDynamic: resolved.helper.isDynamic,
|
|
1261
|
+
placeholders: resolved.helper.placeholders,
|
|
1262
|
+
sourceFile: normalizePath(filePath),
|
|
1263
|
+
sourceLine: lineOf4(sourceFileAst, node),
|
|
1264
|
+
helperChain: [{ callerVariable: targetName, assignedFrom: call.expression.getText(sourceFileAst), aliasKind: "assignment", scopeRule: "same-file-source-order", returnedProperty: propertyName, importSource: resolved.imp?.sourceFile, exportedSymbol: resolved.imp?.exportedName, helperSourceFile: resolved.helper.sourceFile, helperSourceLine: resolved.helper.sourceLine }]
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1148
1268
|
function recordDestructuredClassHelper(decl) {
|
|
1149
1269
|
if (!ts5.isObjectBindingPattern(decl.name) || !decl.initializer) return;
|
|
1150
1270
|
const call = unwrapCall(decl.initializer);
|
|
@@ -1177,21 +1297,40 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1177
1297
|
});
|
|
1178
1298
|
}
|
|
1179
1299
|
}
|
|
1180
|
-
const
|
|
1181
|
-
function
|
|
1182
|
-
if (ts5.isVariableDeclaration(node))
|
|
1183
|
-
ts5.
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
if (ts5.
|
|
1191
|
-
const
|
|
1192
|
-
|
|
1193
|
-
|
|
1300
|
+
const events = [];
|
|
1301
|
+
function collectEvents(node) {
|
|
1302
|
+
if (ts5.isVariableDeclaration(node)) events.push({ pos: node.getStart(sourceFileAst), node });
|
|
1303
|
+
if (ts5.isBinaryExpression(node) && node.operatorToken.kind === ts5.SyntaxKind.EqualsToken)
|
|
1304
|
+
events.push({ pos: node.getStart(sourceFileAst), node });
|
|
1305
|
+
ts5.forEachChild(node, collectEvents);
|
|
1306
|
+
}
|
|
1307
|
+
collectEvents(sourceFileAst);
|
|
1308
|
+
events.sort((a, b) => a.pos - b.pos);
|
|
1309
|
+
for (const event of events) {
|
|
1310
|
+
if (ts5.isVariableDeclaration(event.node)) {
|
|
1311
|
+
const decl = event.node;
|
|
1312
|
+
await recordDestructuredHelper(decl);
|
|
1313
|
+
recordDestructuredClassHelper(decl);
|
|
1314
|
+
await recordVariable(decl);
|
|
1315
|
+
recordIdentityAlias(decl);
|
|
1316
|
+
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)) {
|
|
1317
|
+
cloneAliasBinding(decl.name.text, decl.initializer.expression.expression.text, "transaction", decl);
|
|
1318
|
+
}
|
|
1319
|
+
continue;
|
|
1194
1320
|
}
|
|
1321
|
+
const assignment = event.node;
|
|
1322
|
+
if (ts5.isIdentifier(assignment.left)) {
|
|
1323
|
+
const rhs = unwrapIdentityExpression(assignment.right);
|
|
1324
|
+
if (ts5.isIdentifier(rhs)) {
|
|
1325
|
+
cloneAliasBinding(assignment.left.text, rhs.text, "identity-assignment", assignment);
|
|
1326
|
+
continue;
|
|
1327
|
+
}
|
|
1328
|
+
await recordBindingFromExpression(assignment.left.text, assignment.right, assignment, "assignment");
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
1331
|
+
const left = ts5.isParenthesizedExpression(assignment.left) ? assignment.left.expression : assignment.left;
|
|
1332
|
+
if (ts5.isObjectLiteralExpression(left))
|
|
1333
|
+
await recordDestructuredAssignment(left, assignment.right, assignment);
|
|
1195
1334
|
}
|
|
1196
1335
|
return out;
|
|
1197
1336
|
}
|
|
@@ -1998,10 +2137,25 @@ function implementationScope(db, operationId) {
|
|
|
1998
2137
|
return { repoId: row?.repoId, files: new Set(row?.sourceFile ? [row.sourceFile] : []), symbolId: row?.symbolId, edge };
|
|
1999
2138
|
}
|
|
2000
2139
|
function contextImplementationMethodId(edge, callerRepoId) {
|
|
2001
|
-
if (!edge || edge.status !== "ambiguous" || callerRepoId === void 0) return
|
|
2140
|
+
if (!edge || edge.status !== "ambiguous" || callerRepoId === void 0) return { evidence: { status: "not_applicable" } };
|
|
2002
2141
|
const evidence = JSON.parse(String(edge.evidence_json || "{}"));
|
|
2003
|
-
const
|
|
2004
|
-
|
|
2142
|
+
const scores = (evidence.candidates ?? []).filter((item) => item.accepted).map((item) => {
|
|
2143
|
+
const reasons = [];
|
|
2144
|
+
let score = Number(item.score ?? 0);
|
|
2145
|
+
if (Number(item.handlerPackage?.id) === callerRepoId) {
|
|
2146
|
+
score += 10;
|
|
2147
|
+
reasons.push("handler_package_matches_caller_repository");
|
|
2148
|
+
}
|
|
2149
|
+
if (Number(item.applicationPackage?.id) === callerRepoId) {
|
|
2150
|
+
score += 10;
|
|
2151
|
+
reasons.push("registration_package_matches_caller_repository");
|
|
2152
|
+
}
|
|
2153
|
+
return { methodId: item.methodId, score, reasons, handlerPackage: item.handlerPackage, applicationPackage: item.applicationPackage };
|
|
2154
|
+
}).sort((a, b) => b.score - a.score);
|
|
2155
|
+
if (scores.length === 0) return { evidence: { status: "not_applicable", candidateScores: [] } };
|
|
2156
|
+
const [first, second] = scores;
|
|
2157
|
+
if (first && first.methodId !== void 0 && first.score > 0 && (!second || first.score > second.score)) return { methodId: String(first.methodId), evidence: { status: "selected", selectedMethodId: first.methodId, candidateScores: scores } };
|
|
2158
|
+
return { evidence: { status: "tied", tieReason: "no_unique_materially_stronger_candidate", candidateScores: scores } };
|
|
2005
2159
|
}
|
|
2006
2160
|
function handlerScope(db, methodId) {
|
|
2007
2161
|
const row = db.prepare("SELECT hc.repo_id repoId,hc.source_file sourceFile,s.id symbolId FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id LEFT JOIN symbols s ON s.repo_id=hc.repo_id AND s.source_file=hc.source_file AND s.name=hm.method_name WHERE hm.id=?").get(methodId);
|
|
@@ -2207,7 +2361,8 @@ function trace(db, start, options) {
|
|
|
2207
2361
|
});
|
|
2208
2362
|
if (effectiveRow.to_kind === "operation") {
|
|
2209
2363
|
const implementation = implementationScope(db, effectiveRow.to_id);
|
|
2210
|
-
const
|
|
2364
|
+
const contextSelection = contextImplementationMethodId(implementation.edge, current.repoId);
|
|
2365
|
+
const contextMethodId = contextSelection.methodId;
|
|
2211
2366
|
const contextNode = contextMethodId ? handlerMethodNode(db, contextMethodId) : void 0;
|
|
2212
2367
|
if (implementation.edge) {
|
|
2213
2368
|
const implEvidence = JSON.parse(String(implementation.edge.evidence_json || "{}"));
|
|
@@ -2219,9 +2374,9 @@ function trace(db, start, options) {
|
|
|
2219
2374
|
type: "operation_implemented_by_handler",
|
|
2220
2375
|
from: to,
|
|
2221
2376
|
to: implTo,
|
|
2222
|
-
evidence: implEvidence,
|
|
2377
|
+
evidence: contextMethodId ? { ...implEvidence, contextualImplementationSelected: true, contextualImplementation: contextSelection.evidence } : { ...implEvidence, contextualImplementation: contextSelection.evidence },
|
|
2223
2378
|
confidence: Number(implementation.edge.confidence ?? 0),
|
|
2224
|
-
unresolvedReason: implementation.edge.status === "resolved" ? void 0 : String(implementation.edge.unresolved_reason ?? implementation.edge.status)
|
|
2379
|
+
unresolvedReason: implementation.edge.status === "resolved" || contextMethodId ? void 0 : String(implementation.edge.unresolved_reason ?? implementation.edge.status)
|
|
2225
2380
|
});
|
|
2226
2381
|
}
|
|
2227
2382
|
if (current.depth >= maxDepth) continue;
|
|
@@ -2278,4 +2433,4 @@ export {
|
|
|
2278
2433
|
linkWorkspace,
|
|
2279
2434
|
trace
|
|
2280
2435
|
};
|
|
2281
|
-
//# sourceMappingURL=chunk-
|
|
2436
|
+
//# sourceMappingURL=chunk-LCXDOSWL.js.map
|