@saptools/service-flow 0.1.25 → 0.1.26
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/dist/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
parsePackageJson,
|
|
13
13
|
parseServiceBindings,
|
|
14
14
|
trace
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-Q3J2QDNX.js";
|
|
16
16
|
|
|
17
17
|
// src/cli.ts
|
|
18
18
|
import { Command } from "commander";
|
|
@@ -188,7 +188,7 @@ function migrate(db) {
|
|
|
188
188
|
// package.json
|
|
189
189
|
var package_default = {
|
|
190
190
|
name: "@saptools/service-flow",
|
|
191
|
-
version: "0.1.
|
|
191
|
+
version: "0.1.26",
|
|
192
192
|
description: "Trace SAP CAP service-to-service flows across multi-repository workspaces with runtime-aware graph resolution",
|
|
193
193
|
type: "module",
|
|
194
194
|
publishConfig: {
|
|
@@ -929,11 +929,17 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
929
929
|
ts.forEachChild(node, visitProxyVariables);
|
|
930
930
|
};
|
|
931
931
|
visitProxyVariables(source);
|
|
932
|
+
const rememberClassInstance = (variableName, className, propertyName) => {
|
|
933
|
+
const importSource = imports.get(className);
|
|
934
|
+
if (!builtInConstructors.has(className) && (importSource && isRelativeImport(importSource) || declaredClasses.has(className))) classInstances.set(variableName, { className, importSource, propertyName });
|
|
935
|
+
};
|
|
932
936
|
const visitClassInstances = (node) => {
|
|
933
937
|
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && ts.isNewExpression(node.initializer) && ts.isIdentifier(node.initializer.expression)) {
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
938
|
+
rememberClassInstance(node.name.text, node.initializer.expression.text);
|
|
939
|
+
}
|
|
940
|
+
if (ts.isPropertyDeclaration(node) && node.initializer && ts.isNewExpression(node.initializer) && ts.isIdentifier(node.initializer.expression)) {
|
|
941
|
+
const propertyName = nameOf(node.name);
|
|
942
|
+
if (propertyName) rememberClassInstance(`this.${propertyName}`, node.initializer.expression.text, propertyName);
|
|
937
943
|
}
|
|
938
944
|
ts.forEachChild(node, visitClassInstances);
|
|
939
945
|
};
|
|
@@ -946,11 +952,12 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
946
952
|
if (caller) {
|
|
947
953
|
const callee = callName(node.expression);
|
|
948
954
|
const proxy = callee.local ? proxyVariables.get(callee.local) : void 0;
|
|
949
|
-
const instance = callee.local ? classInstances.get(callee.local) : void 0;
|
|
955
|
+
const instance = (callee.local ? classInstances.get(callee.local) : void 0) ?? (callee.receiver ? classInstances.get(callee.receiver) : void 0);
|
|
950
956
|
const importSource = instance?.importSource ?? proxy?.importSource ?? (callee.local ? imports.get(callee.local) : void 0) ?? (callee.member && callee.local ? imports.get(callee.local) : void 0);
|
|
951
|
-
const
|
|
957
|
+
const directThisMethod = callee.receiver === "this";
|
|
958
|
+
const targetName = instance && callee.member ? `${instance.className}.${callee.member}` : proxy && callee.member ? callee.member : directThisMethod ? callee.member : callee.member && callee.local ? `${callee.local}.${callee.member}` : callee.local;
|
|
952
959
|
const className = caller.qualifiedName.includes(".") ? caller.qualifiedName.split(".")[0] : void 0;
|
|
953
|
-
const thisTarget = className && callee.member ? `${className}.${callee.member}` : void 0;
|
|
960
|
+
const thisTarget = directThisMethod && className && callee.member ? `${className}.${callee.member}` : void 0;
|
|
954
961
|
const loggerLike = callee.receiver?.endsWith(".logger") || callee.local === "logger" || (callee.expression.startsWith("this.logger.") && callee.member ? loggerMembers.has(callee.member) : false);
|
|
955
962
|
const terminalMember = callee.member ? commonTerminalMembers.has(callee.member) || loggerMembers.has(callee.member) : false;
|
|
956
963
|
const provenLocal = Boolean(targetName) && localCallables.has(String(targetName));
|
|
@@ -961,7 +968,7 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
961
968
|
const ignored = loggerLike || terminalMember || importedFromPackage || ignoredFrameworkCall(callee);
|
|
962
969
|
const resolvedTarget = provenThisMethod ? thisTarget : targetName;
|
|
963
970
|
const keep = Boolean(resolvedTarget) && !ignored && (provenLocal || provenThisMethod || provenRelativeImport || provenClassInstance);
|
|
964
|
-
if (keep) calls.push({ callerQualifiedName: caller.qualifiedName, calleeExpression: callee.expression, calleeLocalName: resolvedTarget, receiverLocalName: callee.member ? callee.local : void 0, importSource, sourceFile, sourceLine: line, evidence: { relation: instance ? "class_instance_method" : proxy ? "relative_import_proxy_member" : importSource ? "relative_import" : provenThisMethod ? "indexed_this_method" : "indexed_local_symbol", caller: caller.qualifiedName, targetName: resolvedTarget, instanceVariable: instance ? callee.local : void 0, className: instance?.className, methodName: instance ? callee.member : void 0, classImportSource: instance?.importSource, callArguments: argumentEvidence(node.arguments, source), proxyVariableName: proxy?.variableName, factory: proxy?.factory, factoryExpression: proxy?.factory, factoryImportSource: proxy?.importSource, candidateStrategy: instance ? instance.importSource ? "relative_import_class_instance_method" : "same_file_class_instance_method" : proxy ? "proxy_member_exact_export_or_unique_member" : void 0 } });
|
|
971
|
+
if (keep) calls.push({ callerQualifiedName: caller.qualifiedName, calleeExpression: callee.expression, calleeLocalName: resolvedTarget, receiverLocalName: callee.member ? callee.local ?? callee.receiver : void 0, importSource, sourceFile, sourceLine: line, evidence: { relation: instance ? "class_instance_method" : proxy ? "relative_import_proxy_member" : importSource ? "relative_import" : provenThisMethod ? "indexed_this_method" : "indexed_local_symbol", caller: caller.qualifiedName, targetName: resolvedTarget, instanceVariable: instance ? instance.propertyName ?? callee.local : void 0, className: instance?.className, methodName: instance ? callee.member : void 0, classImportSource: instance?.importSource, callArguments: argumentEvidence(node.arguments, source), proxyVariableName: proxy?.variableName, factory: proxy?.factory, factoryExpression: proxy?.factory, factoryImportSource: proxy?.importSource, candidateStrategy: instance ? instance.importSource ? "relative_import_class_instance_method" : "same_file_class_instance_method" : proxy ? "proxy_member_exact_export_or_unique_member" : void 0 } });
|
|
965
972
|
}
|
|
966
973
|
}
|
|
967
974
|
ts.forEachChild(node, visitCalls);
|
|
@@ -1281,6 +1288,7 @@ function parserQualityDiagnostics(db, strict) {
|
|
|
1281
1288
|
const classInstanceQuality = classInstanceNoiseQuality(db);
|
|
1282
1289
|
const bindingPropagationQuality = contextualBindingPropagationQuality(db);
|
|
1283
1290
|
const wrapperQuality = wrapperPathPropagationQuality(db);
|
|
1291
|
+
const nestedThisQuality = nestedThisReceiverQuality(db);
|
|
1284
1292
|
return [
|
|
1285
1293
|
aliasQuality,
|
|
1286
1294
|
noBindingQuality,
|
|
@@ -1288,6 +1296,7 @@ function parserQualityDiagnostics(db, strict) {
|
|
|
1288
1296
|
classInstanceQuality,
|
|
1289
1297
|
bindingPropagationQuality,
|
|
1290
1298
|
wrapperQuality,
|
|
1299
|
+
nestedThisQuality,
|
|
1291
1300
|
remoteQuery,
|
|
1292
1301
|
invocation,
|
|
1293
1302
|
remoteAction,
|
|
@@ -1360,8 +1369,23 @@ function contextualBindingPropagationQuality(db) {
|
|
|
1360
1369
|
WHERE json_extract(sc.evidence_json,'$.callArguments[0].kind')='object_literal'
|
|
1361
1370
|
AND json_extract(s.evidence_json,'$.parameterBindings[0].kind')='object_pattern'
|
|
1362
1371
|
AND json_array_length(json_extract(sc.evidence_json,'$.callArguments[0].properties')) > json_array_length(json_extract(s.evidence_json,'$.parameterBindings[0].properties'))`).get();
|
|
1363
|
-
const
|
|
1364
|
-
|
|
1372
|
+
const opportunities = db.prepare(`SELECT c.source_file sourceFile,c.source_line sourceLine,json_extract(c.evidence_json,'$.receiver') receiverName,c.operation_path_expr operationPath,'trace_time_contextual_binding_candidate' opportunity
|
|
1373
|
+
FROM outbound_calls c LEFT JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT)
|
|
1374
|
+
WHERE c.call_type='remote_action' AND c.service_binding_id IS NULL AND json_extract(c.evidence_json,'$.receiver') IS NOT NULL
|
|
1375
|
+
AND (e.status IS NULL OR e.status!='resolved')
|
|
1376
|
+
AND EXISTS (SELECT 1 FROM symbol_calls sc WHERE sc.status='resolved' AND sc.source_file=c.source_file)
|
|
1377
|
+
ORDER BY c.source_file,c.source_line LIMIT 8`).all();
|
|
1378
|
+
return { severity: Number(missingMetadata.count ?? 0) + Number(destructuredUnmapped.count ?? 0) + opportunities.length > 0 ? "warning" : "info", code: "strict_contextual_binding_propagation_quality", message: "Contextual service-client propagation opportunities for trace-time helper resolution", localSymbolCallsWithServiceClientArguments: Number(serviceClientCalls.count ?? 0), calleeSymbolsMissingParameterMetadata: Number(missingMetadata.count ?? 0), destructuredObjectParametersPossiblyUnmapped: Number(destructuredUnmapped.count ?? 0), traceTimeContextualOpportunities: opportunities.length, examples: opportunities };
|
|
1379
|
+
}
|
|
1380
|
+
function nestedThisReceiverQuality(db) {
|
|
1381
|
+
const aggregate = db.prepare(`SELECT COUNT(*) total,
|
|
1382
|
+
SUM(CASE WHEN json_extract(evidence_json,'$.relation')='indexed_this_method' THEN 1 ELSE 0 END) resolvedToCurrentClass,
|
|
1383
|
+
SUM(CASE WHEN json_extract(evidence_json,'$.relation')='class_instance_method' THEN 1 ELSE 0 END) withExplicitHelperInstanceEvidence
|
|
1384
|
+
FROM symbol_calls WHERE callee_expression LIKE 'this.%.%'`).get();
|
|
1385
|
+
const examples = db.prepare(`SELECT source_file sourceFile,source_line sourceLine,callee_expression calleeExpression,json_extract(evidence_json,'$.relation') relation,json_extract(evidence_json,'$.targetName') targetName
|
|
1386
|
+
FROM symbol_calls WHERE callee_expression LIKE 'this.%.%' AND json_extract(evidence_json,'$.relation')='indexed_this_method'
|
|
1387
|
+
ORDER BY source_file,source_line LIMIT 8`).all();
|
|
1388
|
+
return { severity: Number(aggregate.resolvedToCurrentClass ?? 0) > 0 ? "warning" : "info", code: "strict_nested_this_receiver_quality", message: "Nested this receiver symbol-call aggregate", nestedThisReceiverCallsConsidered: Number(aggregate.total ?? 0), nestedThisResolvedToCurrentClass: Number(aggregate.resolvedToCurrentClass ?? 0), nestedThisWithExplicitHelperInstanceEvidence: Number(aggregate.withExplicitHelperInstanceEvidence ?? 0), warningExamples: examples };
|
|
1365
1389
|
}
|
|
1366
1390
|
function contextualImplementationQuality(db) {
|
|
1367
1391
|
const rows = db.prepare(`SELECT status,COALESCE(unresolved_reason,status) reason,COUNT(*) count
|