@saptools/service-flow 0.1.24 → 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: {
|
|
@@ -704,6 +704,40 @@ function isObjectFunction(node) {
|
|
|
704
704
|
var commonTerminalMembers = /* @__PURE__ */ new Set(["push", "includes", "find", "findIndex", "map", "filter", "reduce", "forEach", "some", "every", "toUpperCase", "toLowerCase", "trim", "split", "join", "get", "set", "has"]);
|
|
705
705
|
var loggerMembers = /* @__PURE__ */ new Set(["trace", "debug", "info", "warn", "error", "fatal", "log"]);
|
|
706
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
|
+
]);
|
|
707
741
|
var capDslRoots = /* @__PURE__ */ new Set(["SELECT", "INSERT", "UPSERT", "UPDATE", "DELETE"]);
|
|
708
742
|
var requestHelpers = /* @__PURE__ */ new Set(["reject", "error", "info", "warn", "notify"]);
|
|
709
743
|
var transportMembers = /* @__PURE__ */ new Set(["emit", "publish", "send", "on"]);
|
|
@@ -750,6 +784,25 @@ function argumentEvidence(args, source) {
|
|
|
750
784
|
return { kind: "unsupported", expression: arg.getText(source) };
|
|
751
785
|
});
|
|
752
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
|
+
}
|
|
753
806
|
async function parseExecutableSymbols(repoPath, filePath) {
|
|
754
807
|
const text = await fs4.readFile(path4.join(repoPath, filePath), "utf8");
|
|
755
808
|
const source = ts.createSourceFile(filePath, text, ts.ScriptTarget.Latest, true, filePath.endsWith(".ts") ? ts.ScriptKind.TS : ts.ScriptKind.JS);
|
|
@@ -760,6 +813,7 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
760
813
|
const exportNames = exportDeclarations(source);
|
|
761
814
|
const objectExports = /* @__PURE__ */ new Set();
|
|
762
815
|
const exportedClasses = /* @__PURE__ */ new Set();
|
|
816
|
+
const declaredClasses = /* @__PURE__ */ new Set();
|
|
763
817
|
const proxyVariables = /* @__PURE__ */ new Map();
|
|
764
818
|
const classInstances = /* @__PURE__ */ new Map();
|
|
765
819
|
const addSymbol = (kind, localName, node, parentName, exportedName, evidence) => {
|
|
@@ -769,9 +823,11 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
769
823
|
const objectExported = parentName ? objectExports.has(parentRoot) : false;
|
|
770
824
|
const classMemberExported = kind === "method" && parentName ? exportedClasses.has(parentRoot) && ts.isMethodDeclaration(node) && (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Static) !== 0 && isPublicClassMethod(node) : false;
|
|
771
825
|
const effectiveExportedName = classMemberExported || objectExported ? qualifiedName : declaredExportName;
|
|
772
|
-
const
|
|
826
|
+
const bindings = isFunctionLike(node) ? parameterBindings(node.parameters) : void 0;
|
|
827
|
+
const params = bindings?.flatMap((binding) => binding.kind === "identifier" ? [binding.name] : []);
|
|
773
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);
|
|
774
|
-
|
|
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 });
|
|
775
831
|
};
|
|
776
832
|
const addAliasSymbol = (objectName, propertyName, node, targetImportSource) => {
|
|
777
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 } });
|
|
@@ -799,6 +855,7 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
799
855
|
visitImports(source);
|
|
800
856
|
const visitSymbols = (node, parentClass) => {
|
|
801
857
|
if (ts.isClassDeclaration(node) && node.name) {
|
|
858
|
+
declaredClasses.add(node.name.text);
|
|
802
859
|
if (exported(node) || exportNames.has(node.name.text)) exportedClasses.add(node.name.text);
|
|
803
860
|
for (const member of node.members) visitSymbols(member, node.name.text);
|
|
804
861
|
return;
|
|
@@ -872,11 +929,17 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
872
929
|
ts.forEachChild(node, visitProxyVariables);
|
|
873
930
|
};
|
|
874
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
|
+
};
|
|
875
936
|
const visitClassInstances = (node) => {
|
|
876
937
|
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && ts.isNewExpression(node.initializer) && ts.isIdentifier(node.initializer.expression)) {
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
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);
|
|
880
943
|
}
|
|
881
944
|
ts.forEachChild(node, visitClassInstances);
|
|
882
945
|
};
|
|
@@ -889,11 +952,12 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
889
952
|
if (caller) {
|
|
890
953
|
const callee = callName(node.expression);
|
|
891
954
|
const proxy = callee.local ? proxyVariables.get(callee.local) : void 0;
|
|
892
|
-
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);
|
|
893
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);
|
|
894
|
-
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;
|
|
895
959
|
const className = caller.qualifiedName.includes(".") ? caller.qualifiedName.split(".")[0] : void 0;
|
|
896
|
-
const thisTarget = className && callee.member ? `${className}.${callee.member}` : void 0;
|
|
960
|
+
const thisTarget = directThisMethod && className && callee.member ? `${className}.${callee.member}` : void 0;
|
|
897
961
|
const loggerLike = callee.receiver?.endsWith(".logger") || callee.local === "logger" || (callee.expression.startsWith("this.logger.") && callee.member ? loggerMembers.has(callee.member) : false);
|
|
898
962
|
const terminalMember = callee.member ? commonTerminalMembers.has(callee.member) || loggerMembers.has(callee.member) : false;
|
|
899
963
|
const provenLocal = Boolean(targetName) && localCallables.has(String(targetName));
|
|
@@ -904,7 +968,7 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
904
968
|
const ignored = loggerLike || terminalMember || importedFromPackage || ignoredFrameworkCall(callee);
|
|
905
969
|
const resolvedTarget = provenThisMethod ? thisTarget : targetName;
|
|
906
970
|
const keep = Boolean(resolvedTarget) && !ignored && (provenLocal || provenThisMethod || provenRelativeImport || provenClassInstance);
|
|
907
|
-
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 } });
|
|
908
972
|
}
|
|
909
973
|
}
|
|
910
974
|
ts.forEachChild(node, visitCalls);
|
|
@@ -1221,12 +1285,18 @@ function parserQualityDiagnostics(db, strict) {
|
|
|
1221
1285
|
const aliasQuality = identityAliasBindingQuality(db);
|
|
1222
1286
|
const noBindingQuality = remoteActionNoBindingQuality(db);
|
|
1223
1287
|
const contextualQuality = contextualImplementationQuality(db);
|
|
1288
|
+
const classInstanceQuality = classInstanceNoiseQuality(db);
|
|
1289
|
+
const bindingPropagationQuality = contextualBindingPropagationQuality(db);
|
|
1224
1290
|
const wrapperQuality = wrapperPathPropagationQuality(db);
|
|
1291
|
+
const nestedThisQuality = nestedThisReceiverQuality(db);
|
|
1225
1292
|
return [
|
|
1226
1293
|
aliasQuality,
|
|
1227
1294
|
noBindingQuality,
|
|
1228
1295
|
contextualQuality,
|
|
1296
|
+
classInstanceQuality,
|
|
1297
|
+
bindingPropagationQuality,
|
|
1229
1298
|
wrapperQuality,
|
|
1299
|
+
nestedThisQuality,
|
|
1230
1300
|
remoteQuery,
|
|
1231
1301
|
invocation,
|
|
1232
1302
|
remoteAction,
|
|
@@ -1253,7 +1323,16 @@ function remoteActionNoBindingQuality(db) {
|
|
|
1253
1323
|
WHEN c.unresolved_reason='dynamic_operation_path_identifier' THEN 'dynamic_path_identifier'
|
|
1254
1324
|
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'
|
|
1255
1325
|
WHEN json_extract(c.evidence_json,'$.receiver') LIKE '%.%' THEN 'likely_parameter_context_needed'
|
|
1256
|
-
WHEN EXISTS (
|
|
1326
|
+
WHEN EXISTS (
|
|
1327
|
+
SELECT 1 FROM symbol_calls sc
|
|
1328
|
+
JOIN symbols caller ON caller.id=sc.caller_symbol_id
|
|
1329
|
+
JOIN symbols callee ON callee.id=sc.callee_symbol_id
|
|
1330
|
+
WHERE sc.status='resolved'
|
|
1331
|
+
AND sc.source_file=c.source_file
|
|
1332
|
+
AND caller.id=c.source_symbol_id
|
|
1333
|
+
AND json_extract(sc.evidence_json,'$.relation')='class_instance_method'
|
|
1334
|
+
AND (callee.evidence_json IS NULL OR json_extract(callee.evidence_json,'$.parameterBindings') IS NULL)
|
|
1335
|
+
) THEN 'likely_instance_method_parameter_metadata_needed'
|
|
1257
1336
|
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'
|
|
1258
1337
|
WHEN e.status='unresolved' AND COALESCE(e.unresolved_reason,'') LIKE '%No indexed target operation%' THEN 'no_indexed_target_operation'
|
|
1259
1338
|
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'
|
|
@@ -1268,6 +1347,46 @@ function remoteActionNoBindingQuality(db) {
|
|
|
1268
1347
|
const total = rows.reduce((sum, row) => sum + Number(row.count ?? 0), 0);
|
|
1269
1348
|
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 };
|
|
1270
1349
|
}
|
|
1350
|
+
function classInstanceNoiseQuality(db) {
|
|
1351
|
+
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"];
|
|
1352
|
+
const placeholders = builtIns.map(() => "?").join(",");
|
|
1353
|
+
const aggregate = db.prepare(`SELECT COUNT(*) total,
|
|
1354
|
+
SUM(CASE WHEN status='unresolved' THEN 1 ELSE 0 END) unresolved,
|
|
1355
|
+
SUM(CASE WHEN status='unresolved' AND json_extract(evidence_json,'$.className') IN (${placeholders}) THEN 1 ELSE 0 END) unresolvedBuiltIn
|
|
1356
|
+
FROM symbol_calls WHERE json_extract(evidence_json,'$.relation')='class_instance_method'`).get(...builtIns);
|
|
1357
|
+
const byConstructor = db.prepare(`SELECT json_extract(evidence_json,'$.className') constructorName,COUNT(*) unresolvedCount
|
|
1358
|
+
FROM symbol_calls WHERE status='unresolved' AND json_extract(evidence_json,'$.relation')='class_instance_method'
|
|
1359
|
+
GROUP BY constructorName ORDER BY unresolvedCount DESC,constructorName LIMIT 10`).all();
|
|
1360
|
+
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 };
|
|
1361
|
+
}
|
|
1362
|
+
function contextualBindingPropagationQuality(db) {
|
|
1363
|
+
const serviceClientCalls = db.prepare(`SELECT COUNT(*) count FROM symbol_calls sc
|
|
1364
|
+
WHERE json_extract(sc.evidence_json,'$.callArguments[0].kind') IN ('identifier','object_literal')`).get();
|
|
1365
|
+
const missingMetadata = db.prepare(`SELECT COUNT(*) count FROM symbol_calls sc JOIN symbols s ON s.id=sc.callee_symbol_id
|
|
1366
|
+
WHERE sc.status='resolved' AND json_extract(sc.evidence_json,'$.callArguments[0].kind') IN ('identifier','object_literal')
|
|
1367
|
+
AND (s.evidence_json IS NULL OR json_extract(s.evidence_json,'$.parameterBindings') IS NULL)`).get();
|
|
1368
|
+
const destructuredUnmapped = db.prepare(`SELECT COUNT(*) count FROM symbol_calls sc JOIN symbols s ON s.id=sc.callee_symbol_id
|
|
1369
|
+
WHERE json_extract(sc.evidence_json,'$.callArguments[0].kind')='object_literal'
|
|
1370
|
+
AND json_extract(s.evidence_json,'$.parameterBindings[0].kind')='object_pattern'
|
|
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();
|
|
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 };
|
|
1389
|
+
}
|
|
1271
1390
|
function contextualImplementationQuality(db) {
|
|
1272
1391
|
const rows = db.prepare(`SELECT status,COALESCE(unresolved_reason,status) reason,COUNT(*) count
|
|
1273
1392
|
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();
|