@saptools/service-flow 0.1.23 → 0.1.25
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 +19 -0
- package/dist/{chunk-LCXDOSWL.js → chunk-HS746OHV.js} +121 -24
- package/dist/chunk-HS746OHV.js.map +1 -0
- package/dist/cli.js +164 -19
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-LCXDOSWL.js.map +0 -1
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-HS746OHV.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.25",
|
|
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: {
|
|
@@ -554,11 +554,19 @@ function insertSymbolCalls(db, repoId, rows) {
|
|
|
554
554
|
insertStmt.run(repoId, caller?.id, target.id, r.calleeExpression, r.importSource, r.sourceFile, r.sourceLine, target.status, 0.8, JSON.stringify({ ...r.evidence, candidateStrategy: target.strategy, candidateCount: target.candidateCount }), target.reason);
|
|
555
555
|
}
|
|
556
556
|
}
|
|
557
|
+
function isRelativeImportedSymbolCall(r) {
|
|
558
|
+
return Boolean(r.importSource?.startsWith("."));
|
|
559
|
+
}
|
|
557
560
|
function resolveSymbolCallTarget(db, repoId, r) {
|
|
558
561
|
const evidence = r.evidence;
|
|
559
562
|
const localRows = db.prepare("SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND (name=? OR qualified_name=?) ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName, r.calleeLocalName);
|
|
560
563
|
if (localRows.length === 1) return { id: localRows[0]?.id ?? null, status: "resolved", reason: null, strategy: "same_file_exact", candidateCount: 1 };
|
|
561
564
|
if (localRows.length > 1) return { id: null, status: "ambiguous", reason: "Multiple same-file symbol targets matched exactly", strategy: "same_file_exact", candidateCount: localRows.length };
|
|
565
|
+
if (evidence.relation === "class_instance_method" && isRelativeImportedSymbolCall(r)) {
|
|
566
|
+
const classRows = db.prepare("SELECT id FROM symbols WHERE repo_id=? AND source_file<>? AND qualified_name=? ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName);
|
|
567
|
+
if (classRows.length === 1) return { id: classRows[0]?.id ?? null, status: "resolved", reason: null, strategy: "relative_import_class_instance_method", candidateCount: 1 };
|
|
568
|
+
if (classRows.length > 1) return { id: null, status: "ambiguous", reason: "Multiple relative class instance method targets matched exactly", strategy: "relative_import_class_instance_method", candidateCount: classRows.length };
|
|
569
|
+
}
|
|
562
570
|
const rows = db.prepare("SELECT id,kind,evidence_json evidenceJson FROM symbols WHERE repo_id=? AND source_file<>? AND exported=1 AND (exported_name=? OR name=? OR qualified_name=?) ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName, r.calleeLocalName, r.calleeLocalName);
|
|
563
571
|
if (evidence.relation === "relative_import_proxy_member" && rows.length > 1) {
|
|
564
572
|
const objectMapRows = rows.filter((row) => String(row.evidenceJson ?? "").includes("exported_object_shorthand") || String(row.evidenceJson ?? "").includes("exported_object_literal"));
|
|
@@ -672,9 +680,9 @@ function isFunctionLike(node) {
|
|
|
672
680
|
function exported(node) {
|
|
673
681
|
return Boolean(ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export);
|
|
674
682
|
}
|
|
675
|
-
function
|
|
683
|
+
function isPublicClassMethod(node) {
|
|
676
684
|
const flags = ts.getCombinedModifierFlags(node);
|
|
677
|
-
return (flags & ts.ModifierFlags.
|
|
685
|
+
return (flags & ts.ModifierFlags.Private) === 0 && (flags & ts.ModifierFlags.Protected) === 0;
|
|
678
686
|
}
|
|
679
687
|
function exportDeclarations(source) {
|
|
680
688
|
const exports = /* @__PURE__ */ new Map();
|
|
@@ -696,6 +704,40 @@ function isObjectFunction(node) {
|
|
|
696
704
|
var commonTerminalMembers = /* @__PURE__ */ new Set(["push", "includes", "find", "findIndex", "map", "filter", "reduce", "forEach", "some", "every", "toUpperCase", "toLowerCase", "trim", "split", "join", "get", "set", "has"]);
|
|
697
705
|
var loggerMembers = /* @__PURE__ */ new Set(["trace", "debug", "info", "warn", "error", "fatal", "log"]);
|
|
698
706
|
var globalObjects = /* @__PURE__ */ new Set(["JSON", "Object", "Array", "String", "Number", "Boolean", "Math", "Date", "Promise", "Reflect"]);
|
|
707
|
+
var builtInConstructors = /* @__PURE__ */ new Set([
|
|
708
|
+
"Set",
|
|
709
|
+
"Map",
|
|
710
|
+
"WeakSet",
|
|
711
|
+
"WeakMap",
|
|
712
|
+
"Date",
|
|
713
|
+
"RegExp",
|
|
714
|
+
"URL",
|
|
715
|
+
"URLSearchParams",
|
|
716
|
+
"Error",
|
|
717
|
+
"EvalError",
|
|
718
|
+
"RangeError",
|
|
719
|
+
"ReferenceError",
|
|
720
|
+
"SyntaxError",
|
|
721
|
+
"TypeError",
|
|
722
|
+
"URIError",
|
|
723
|
+
"AggregateError",
|
|
724
|
+
"ArrayBuffer",
|
|
725
|
+
"SharedArrayBuffer",
|
|
726
|
+
"DataView",
|
|
727
|
+
"Int8Array",
|
|
728
|
+
"Uint8Array",
|
|
729
|
+
"Uint8ClampedArray",
|
|
730
|
+
"Int16Array",
|
|
731
|
+
"Uint16Array",
|
|
732
|
+
"Int32Array",
|
|
733
|
+
"Uint32Array",
|
|
734
|
+
"Float32Array",
|
|
735
|
+
"Float64Array",
|
|
736
|
+
"BigInt64Array",
|
|
737
|
+
"BigUint64Array",
|
|
738
|
+
"Promise",
|
|
739
|
+
"AbortController"
|
|
740
|
+
]);
|
|
699
741
|
var capDslRoots = /* @__PURE__ */ new Set(["SELECT", "INSERT", "UPSERT", "UPDATE", "DELETE"]);
|
|
700
742
|
var requestHelpers = /* @__PURE__ */ new Set(["reject", "error", "info", "warn", "notify"]);
|
|
701
743
|
var transportMembers = /* @__PURE__ */ new Set(["emit", "publish", "send", "on"]);
|
|
@@ -725,6 +767,42 @@ function ignoredFrameworkCall(callee) {
|
|
|
725
767
|
function nearest(symbols, line) {
|
|
726
768
|
return symbols.filter((s) => s.startLine <= line && s.endLine >= line).sort((a, b) => a.endLine - a.startLine - (b.endLine - b.startLine))[0];
|
|
727
769
|
}
|
|
770
|
+
function argumentEvidence(args, source) {
|
|
771
|
+
return args.map((arg) => {
|
|
772
|
+
if (ts.isIdentifier(arg)) return { kind: "identifier", name: arg.text };
|
|
773
|
+
if (ts.isObjectLiteralExpression(arg)) {
|
|
774
|
+
const properties = [];
|
|
775
|
+
for (const prop of arg.properties) {
|
|
776
|
+
if (ts.isShorthandPropertyAssignment(prop)) properties.push({ kind: "shorthand", property: prop.name.text, argument: prop.name.text });
|
|
777
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
778
|
+
const propName = nameOf(prop.name);
|
|
779
|
+
if (propName && ts.isIdentifier(prop.initializer)) properties.push({ kind: "property_assignment", property: propName, argument: prop.initializer.text });
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
return { kind: "object_literal", properties };
|
|
783
|
+
}
|
|
784
|
+
return { kind: "unsupported", expression: arg.getText(source) };
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
function bindingLocalName(name, initializer) {
|
|
788
|
+
if (ts.isIdentifier(name)) return name.text;
|
|
789
|
+
if (initializer && ts.isIdentifier(initializer)) return initializer.text;
|
|
790
|
+
return void 0;
|
|
791
|
+
}
|
|
792
|
+
function parameterBindings(params) {
|
|
793
|
+
return params.flatMap((param, index) => {
|
|
794
|
+
if (ts.isIdentifier(param.name)) return [{ index, kind: "identifier", name: param.name.text }];
|
|
795
|
+
if (!ts.isObjectBindingPattern(param.name)) return [];
|
|
796
|
+
const properties = param.name.elements.flatMap((element) => {
|
|
797
|
+
if (element.dotDotDotToken || ts.isObjectBindingPattern(element.name) || ts.isArrayBindingPattern(element.name)) return [];
|
|
798
|
+
const property = element.propertyName ? nameOf(element.propertyName) : nameOf(element.name);
|
|
799
|
+
if (!property) return [];
|
|
800
|
+
const local = bindingLocalName(element.name, element.initializer);
|
|
801
|
+
return local ? [{ property, local }] : [];
|
|
802
|
+
});
|
|
803
|
+
return properties.length > 0 ? [{ index, kind: "object_pattern", properties }] : [];
|
|
804
|
+
});
|
|
805
|
+
}
|
|
728
806
|
async function parseExecutableSymbols(repoPath, filePath) {
|
|
729
807
|
const text = await fs4.readFile(path4.join(repoPath, filePath), "utf8");
|
|
730
808
|
const source = ts.createSourceFile(filePath, text, ts.ScriptTarget.Latest, true, filePath.endsWith(".ts") ? ts.ScriptKind.TS : ts.ScriptKind.JS);
|
|
@@ -735,16 +813,21 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
735
813
|
const exportNames = exportDeclarations(source);
|
|
736
814
|
const objectExports = /* @__PURE__ */ new Set();
|
|
737
815
|
const exportedClasses = /* @__PURE__ */ new Set();
|
|
816
|
+
const declaredClasses = /* @__PURE__ */ new Set();
|
|
738
817
|
const proxyVariables = /* @__PURE__ */ new Map();
|
|
818
|
+
const classInstances = /* @__PURE__ */ new Map();
|
|
739
819
|
const addSymbol = (kind, localName, node, parentName, exportedName, evidence) => {
|
|
740
820
|
const parentRoot = parentName?.split(".")[0] ?? "";
|
|
741
821
|
const declaredExportName = exportedName ?? exportNames.get(parentName ? parentRoot : localName);
|
|
742
822
|
const qualifiedName = parentName ? `${parentName}.${localName}` : localName;
|
|
743
823
|
const objectExported = parentName ? objectExports.has(parentRoot) : false;
|
|
744
|
-
const classMemberExported = kind === "method" && parentName ? exportedClasses.has(parentRoot) && ts.isMethodDeclaration(node) &&
|
|
824
|
+
const classMemberExported = kind === "method" && parentName ? exportedClasses.has(parentRoot) && ts.isMethodDeclaration(node) && (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Static) !== 0 && isPublicClassMethod(node) : false;
|
|
745
825
|
const effectiveExportedName = classMemberExported || objectExported ? qualifiedName : declaredExportName;
|
|
746
|
-
const
|
|
747
|
-
|
|
826
|
+
const bindings = isFunctionLike(node) ? parameterBindings(node.parameters) : void 0;
|
|
827
|
+
const params = bindings?.flatMap((binding) => binding.kind === "identifier" ? [binding.name] : []);
|
|
828
|
+
const sourceEvidence = evidence ?? (classMemberExported ? { source: "exported_class_member", exportedClass: parentRoot, memberKind: (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Static) !== 0 ? "static_method" : "class_method", parameters: params } : declaredExportName ? { exportedName: declaredExportName, source: "export_declaration" } : objectExported ? { exportedName: qualifiedName, source: "exported_object_literal" } : void 0);
|
|
829
|
+
const parameterEvidence = bindings && bindings.length > 0 ? { parameters: params, parameterBindings: bindings } : {};
|
|
830
|
+
symbols.push({ kind, localName: kind === "object_method" ? qualifiedName : localName, exportedName: effectiveExportedName, qualifiedName, sourceFile, startLine: lineOf(source, node.getStart(source)), endLine: lineOf(source, node.getEnd()), startOffset: node.getStart(source), endOffset: node.getEnd(), exported: exported(node) || Boolean(effectiveExportedName), importExportEvidence: sourceEvidence ? { ...sourceEvidence, ...parameterEvidence } : bindings && bindings.length > 0 ? parameterEvidence : void 0 });
|
|
748
831
|
};
|
|
749
832
|
const addAliasSymbol = (objectName, propertyName, node, targetImportSource) => {
|
|
750
833
|
symbols.push({ kind: "object_alias", localName: propertyName, exportedName: propertyName, qualifiedName: `${objectName}.${propertyName}`, sourceFile, startLine: lineOf(source, node.getStart(source)), endLine: lineOf(source, node.getEnd()), startOffset: node.getStart(source), endOffset: node.getEnd(), exported: true, importExportEvidence: { source: "exported_object_shorthand", objectName, propertyName, targetImportSource } });
|
|
@@ -772,6 +855,7 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
772
855
|
visitImports(source);
|
|
773
856
|
const visitSymbols = (node, parentClass) => {
|
|
774
857
|
if (ts.isClassDeclaration(node) && node.name) {
|
|
858
|
+
declaredClasses.add(node.name.text);
|
|
775
859
|
if (exported(node) || exportNames.has(node.name.text)) exportedClasses.add(node.name.text);
|
|
776
860
|
for (const member of node.members) visitSymbols(member, node.name.text);
|
|
777
861
|
return;
|
|
@@ -845,6 +929,15 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
845
929
|
ts.forEachChild(node, visitProxyVariables);
|
|
846
930
|
};
|
|
847
931
|
visitProxyVariables(source);
|
|
932
|
+
const visitClassInstances = (node) => {
|
|
933
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && ts.isNewExpression(node.initializer) && ts.isIdentifier(node.initializer.expression)) {
|
|
934
|
+
const className = node.initializer.expression.text;
|
|
935
|
+
const importSource = imports.get(className);
|
|
936
|
+
if (!builtInConstructors.has(className) && (importSource && isRelativeImport(importSource) || declaredClasses.has(className))) classInstances.set(node.name.text, { className, importSource });
|
|
937
|
+
}
|
|
938
|
+
ts.forEachChild(node, visitClassInstances);
|
|
939
|
+
};
|
|
940
|
+
visitClassInstances(source);
|
|
848
941
|
const localCallables = new Set(symbols.flatMap((sym) => [sym.localName, sym.qualifiedName]));
|
|
849
942
|
const visitCalls = (node) => {
|
|
850
943
|
if (ts.isCallExpression(node)) {
|
|
@@ -853,8 +946,9 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
853
946
|
if (caller) {
|
|
854
947
|
const callee = callName(node.expression);
|
|
855
948
|
const proxy = callee.local ? proxyVariables.get(callee.local) : void 0;
|
|
856
|
-
const
|
|
857
|
-
const
|
|
949
|
+
const instance = callee.local ? classInstances.get(callee.local) : void 0;
|
|
950
|
+
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 targetName = instance && callee.member ? `${instance.className}.${callee.member}` : proxy && callee.member ? callee.member : callee.expression.startsWith("this.") ? callee.member : callee.member && callee.local ? `${callee.local}.${callee.member}` : callee.local;
|
|
858
952
|
const className = caller.qualifiedName.includes(".") ? caller.qualifiedName.split(".")[0] : void 0;
|
|
859
953
|
const thisTarget = className && callee.member ? `${className}.${callee.member}` : void 0;
|
|
860
954
|
const loggerLike = callee.receiver?.endsWith(".logger") || callee.local === "logger" || (callee.expression.startsWith("this.logger.") && callee.member ? loggerMembers.has(callee.member) : false);
|
|
@@ -862,11 +956,12 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
862
956
|
const provenLocal = Boolean(targetName) && localCallables.has(String(targetName));
|
|
863
957
|
const provenThisMethod = Boolean(thisTarget && localCallables.has(thisTarget));
|
|
864
958
|
const provenRelativeImport = Boolean(isRelativeImport(importSource) && targetName);
|
|
959
|
+
const provenClassInstance = Boolean(instance && callee.member && targetName);
|
|
865
960
|
const importedFromPackage = Boolean(importSource && !isRelativeImport(importSource));
|
|
866
961
|
const ignored = loggerLike || terminalMember || importedFromPackage || ignoredFrameworkCall(callee);
|
|
867
962
|
const resolvedTarget = provenThisMethod ? thisTarget : targetName;
|
|
868
|
-
const keep = Boolean(resolvedTarget) && !ignored && (provenLocal || provenThisMethod || provenRelativeImport);
|
|
869
|
-
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: proxy ? "relative_import_proxy_member" : importSource ? "relative_import" : provenThisMethod ? "indexed_this_method" : "indexed_local_symbol", caller: caller.qualifiedName, targetName: resolvedTarget, proxyVariableName: proxy?.variableName, factory: proxy?.factory, factoryExpression: proxy?.factory, factoryImportSource: proxy?.importSource, candidateStrategy: proxy ? "proxy_member_exact_export_or_unique_member" : void 0 } });
|
|
963
|
+
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 } });
|
|
870
965
|
}
|
|
871
966
|
}
|
|
872
967
|
ts.forEachChild(node, visitCalls);
|
|
@@ -1183,11 +1278,15 @@ function parserQualityDiagnostics(db, strict) {
|
|
|
1183
1278
|
const aliasQuality = identityAliasBindingQuality(db);
|
|
1184
1279
|
const noBindingQuality = remoteActionNoBindingQuality(db);
|
|
1185
1280
|
const contextualQuality = contextualImplementationQuality(db);
|
|
1281
|
+
const classInstanceQuality = classInstanceNoiseQuality(db);
|
|
1282
|
+
const bindingPropagationQuality = contextualBindingPropagationQuality(db);
|
|
1186
1283
|
const wrapperQuality = wrapperPathPropagationQuality(db);
|
|
1187
1284
|
return [
|
|
1188
1285
|
aliasQuality,
|
|
1189
1286
|
noBindingQuality,
|
|
1190
1287
|
contextualQuality,
|
|
1288
|
+
classInstanceQuality,
|
|
1289
|
+
bindingPropagationQuality,
|
|
1191
1290
|
wrapperQuality,
|
|
1192
1291
|
remoteQuery,
|
|
1193
1292
|
invocation,
|
|
@@ -1211,22 +1310,68 @@ function identityAliasBindingQuality(db) {
|
|
|
1211
1310
|
return { severity: examples.length > 0 ? "warning" : "info", code: "strict_identity_alias_binding_quality", message: "Remote sends that look like missed same-file identity aliases", missedAliasBindingCalls: examples.length, examples };
|
|
1212
1311
|
}
|
|
1213
1312
|
function remoteActionNoBindingQuality(db) {
|
|
1214
|
-
const
|
|
1313
|
+
const categoryCase = `CASE
|
|
1314
|
+
WHEN c.unresolved_reason='dynamic_operation_path_identifier' THEN 'dynamic_path_identifier'
|
|
1315
|
+
WHEN json_extract(c.evidence_json,'$.classifier')='higher_order_wrapper_literal_path' OR json_extract(c.evidence_json,'$.operationPathExpression') IS NOT NULL THEN 'likely_higher_order_wrapper_path_needed'
|
|
1316
|
+
WHEN json_extract(c.evidence_json,'$.receiver') LIKE '%.%' THEN 'likely_parameter_context_needed'
|
|
1317
|
+
WHEN EXISTS (
|
|
1318
|
+
SELECT 1 FROM symbol_calls sc
|
|
1319
|
+
JOIN symbols caller ON caller.id=sc.caller_symbol_id
|
|
1320
|
+
JOIN symbols callee ON callee.id=sc.callee_symbol_id
|
|
1321
|
+
WHERE sc.status='resolved'
|
|
1322
|
+
AND sc.source_file=c.source_file
|
|
1323
|
+
AND caller.id=c.source_symbol_id
|
|
1324
|
+
AND json_extract(sc.evidence_json,'$.relation')='class_instance_method'
|
|
1325
|
+
AND (callee.evidence_json IS NULL OR json_extract(callee.evidence_json,'$.parameterBindings') IS NULL)
|
|
1326
|
+
) THEN 'likely_instance_method_parameter_metadata_needed'
|
|
1327
|
+
WHEN EXISTS (SELECT 1 FROM service_bindings b WHERE b.repo_id=c.repo_id AND b.source_file=c.source_file AND ABS(b.source_line-c.source_line) < 50) THEN 'likely_missing_assignment_binding'
|
|
1328
|
+
WHEN e.status='unresolved' AND COALESCE(e.unresolved_reason,'') LIKE '%No indexed target operation%' THEN 'no_indexed_target_operation'
|
|
1329
|
+
WHEN c.operation_path_expr IS NOT NULL AND (c.operation_path_expr LIKE '/%' OR c.operation_path_expr NOT LIKE '%/%') THEN 'operation_path_only_no_static_service_signal'
|
|
1330
|
+
ELSE 'external_or_entity_path_not_action' END`;
|
|
1331
|
+
const rows = db.prepare(`SELECT ${categoryCase} category,COALESCE(e.status,'missing_edge') status,COUNT(*) count
|
|
1215
1332
|
FROM outbound_calls c LEFT JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT)
|
|
1216
1333
|
WHERE c.call_type='remote_action' AND c.operation_path_expr IS NOT NULL AND c.service_binding_id IS NULL
|
|
1217
|
-
GROUP BY
|
|
1218
|
-
const examples = db.prepare(`SELECT c.source_file sourceFile,c.source_line sourceLine,json_extract(c.evidence_json,'$.receiver') receiverName,c.operation_path_expr operationPath,COALESCE(e.status,'missing_edge') status
|
|
1219
|
-
CASE WHEN EXISTS (SELECT 1 FROM service_bindings b WHERE b.repo_id=c.repo_id AND b.source_file=c.source_file AND COALESCE(b.helper_chain_json,'') LIKE '%' || '"aliasOf":"' || json_extract(c.evidence_json,'$.receiver') || '"' || '%') THEN 'likely_missed_alias_or_helper_propagation' ELSE 'operation_path_only_call' END category
|
|
1334
|
+
GROUP BY category,status ORDER BY count DESC,category,status`).all();
|
|
1335
|
+
const examples = db.prepare(`SELECT c.source_file sourceFile,c.source_line sourceLine,json_extract(c.evidence_json,'$.receiver') receiverName,c.operation_path_expr operationPath,COALESCE(e.status,'missing_edge') status,${categoryCase} category
|
|
1220
1336
|
FROM outbound_calls c LEFT JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT)
|
|
1221
|
-
WHERE c.call_type='remote_action' AND c.operation_path_expr IS NOT NULL AND c.service_binding_id IS NULL ORDER BY c.source_file,c.source_line LIMIT
|
|
1337
|
+
WHERE c.call_type='remote_action' AND c.operation_path_expr IS NOT NULL AND c.service_binding_id IS NULL ORDER BY c.source_file,c.source_line LIMIT 8`).all();
|
|
1222
1338
|
const total = rows.reduce((sum, row) => sum + Number(row.count ?? 0), 0);
|
|
1223
1339
|
return { severity: total > 0 ? "warning" : "info", code: "strict_remote_action_no_binding_quality", message: "Remote actions with operation paths but no service binding id", total, breakdown: rows, examples };
|
|
1224
1340
|
}
|
|
1341
|
+
function classInstanceNoiseQuality(db) {
|
|
1342
|
+
const builtIns = ["Set", "Map", "WeakSet", "WeakMap", "Date", "RegExp", "URL", "URLSearchParams", "Error", "EvalError", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError", "AggregateError", "ArrayBuffer", "SharedArrayBuffer", "DataView", "Int8Array", "Uint8Array", "Uint8ClampedArray", "Int16Array", "Uint16Array", "Int32Array", "Uint32Array", "Float32Array", "Float64Array", "BigInt64Array", "BigUint64Array", "Promise", "AbortController"];
|
|
1343
|
+
const placeholders = builtIns.map(() => "?").join(",");
|
|
1344
|
+
const aggregate = db.prepare(`SELECT COUNT(*) total,
|
|
1345
|
+
SUM(CASE WHEN status='unresolved' THEN 1 ELSE 0 END) unresolved,
|
|
1346
|
+
SUM(CASE WHEN status='unresolved' AND json_extract(evidence_json,'$.className') IN (${placeholders}) THEN 1 ELSE 0 END) unresolvedBuiltIn
|
|
1347
|
+
FROM symbol_calls WHERE json_extract(evidence_json,'$.relation')='class_instance_method'`).get(...builtIns);
|
|
1348
|
+
const byConstructor = db.prepare(`SELECT json_extract(evidence_json,'$.className') constructorName,COUNT(*) unresolvedCount
|
|
1349
|
+
FROM symbol_calls WHERE status='unresolved' AND json_extract(evidence_json,'$.relation')='class_instance_method'
|
|
1350
|
+
GROUP BY constructorName ORDER BY unresolvedCount DESC,constructorName LIMIT 10`).all();
|
|
1351
|
+
return { severity: Number(aggregate.unresolvedBuiltIn ?? 0) > 0 ? "warning" : "info", code: "strict_class_instance_noise_quality", message: "Class-instance symbol-call aggregate with built-in constructor guard", totalClassInstanceCalls: Number(aggregate.total ?? 0), unresolvedClassInstanceCalls: Number(aggregate.unresolved ?? 0), unresolvedBuiltInClassInstanceCalls: Number(aggregate.unresolvedBuiltIn ?? 0), unresolvedByConstructor: byConstructor };
|
|
1352
|
+
}
|
|
1353
|
+
function contextualBindingPropagationQuality(db) {
|
|
1354
|
+
const serviceClientCalls = db.prepare(`SELECT COUNT(*) count FROM symbol_calls sc
|
|
1355
|
+
WHERE json_extract(sc.evidence_json,'$.callArguments[0].kind') IN ('identifier','object_literal')`).get();
|
|
1356
|
+
const missingMetadata = db.prepare(`SELECT COUNT(*) count FROM symbol_calls sc JOIN symbols s ON s.id=sc.callee_symbol_id
|
|
1357
|
+
WHERE sc.status='resolved' AND json_extract(sc.evidence_json,'$.callArguments[0].kind') IN ('identifier','object_literal')
|
|
1358
|
+
AND (s.evidence_json IS NULL OR json_extract(s.evidence_json,'$.parameterBindings') IS NULL)`).get();
|
|
1359
|
+
const destructuredUnmapped = db.prepare(`SELECT COUNT(*) count FROM symbol_calls sc JOIN symbols s ON s.id=sc.callee_symbol_id
|
|
1360
|
+
WHERE json_extract(sc.evidence_json,'$.callArguments[0].kind')='object_literal'
|
|
1361
|
+
AND json_extract(s.evidence_json,'$.parameterBindings[0].kind')='object_pattern'
|
|
1362
|
+
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 contextualResolved = db.prepare(`SELECT COUNT(*) count FROM graph_edges WHERE json_extract(evidence_json,'$.contextualServiceBindingSelected')=1 AND status='resolved'`).get();
|
|
1364
|
+
return { severity: Number(missingMetadata.count ?? 0) + Number(destructuredUnmapped.count ?? 0) > 0 ? "warning" : "info", code: "strict_contextual_binding_propagation_quality", message: "Contextual service-client propagation aggregate", localSymbolCallsWithServiceClientArguments: Number(serviceClientCalls.count ?? 0), calleeSymbolsMissingParameterMetadata: Number(missingMetadata.count ?? 0), destructuredObjectParametersPossiblyUnmapped: Number(destructuredUnmapped.count ?? 0), contextualRemoteActionsResolved: Number(contextualResolved.count ?? 0) };
|
|
1365
|
+
}
|
|
1225
1366
|
function contextualImplementationQuality(db) {
|
|
1226
|
-
const rows = db.prepare(`SELECT COALESCE(
|
|
1227
|
-
FROM graph_edges WHERE edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND status IN ('ambiguous','unresolved') GROUP BY status ORDER BY count DESC,
|
|
1367
|
+
const rows = db.prepare(`SELECT status,COALESCE(unresolved_reason,status) reason,COUNT(*) count
|
|
1368
|
+
FROM graph_edges WHERE edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND status IN ('ambiguous','unresolved') GROUP BY status,reason ORDER BY status,count DESC,reason`).all();
|
|
1369
|
+
const examples = db.prepare(`SELECT json_extract(evidence_json,'$.servicePath') servicePath,json_extract(evidence_json,'$.operationPath') operationPath,status,unresolved_reason unresolvedReason,
|
|
1370
|
+
json_extract(evidence_json,'$.candidates[0].rejectedReasons[0]') topRejectedReason,
|
|
1371
|
+
json_extract(evidence_json,'$.candidates[0].acceptedReasons[0]') topAcceptedReason
|
|
1372
|
+
FROM graph_edges WHERE edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND status IN ('ambiguous','unresolved') ORDER BY status,id LIMIT 6`).all();
|
|
1228
1373
|
const total = rows.reduce((sum, row) => sum + Number(row.count ?? 0), 0);
|
|
1229
|
-
return { severity: total > 0 ? "warning" : "info", code: "strict_contextual_implementation_quality", message: "
|
|
1374
|
+
return { severity: total > 0 ? "warning" : "info", code: "strict_contextual_implementation_quality", message: "Implementation hops stopped by ambiguous or unresolved implementation edges", total, breakdown: rows, examples };
|
|
1230
1375
|
}
|
|
1231
1376
|
function wrapperPathPropagationQuality(db) {
|
|
1232
1377
|
const examples = db.prepare(`SELECT source_file sourceFile,source_line sourceLine,json_extract(evidence_json,'$.receiver') receiverName,json_extract(evidence_json,'$.operationPathExpression') pathIdentifier,CASE WHEN json_extract(evidence_json,'$.literalCallerArgumentDetected') IS NOT NULL THEN 1 ELSE 0 END literalCallerArgumentDetected
|