@saptools/service-flow 0.1.14 → 0.1.16
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 +17 -0
- package/README.md +6 -2
- package/TECHNICAL-NOTE.md +14 -0
- package/dist/{chunk-UPXFMLUY.js → chunk-SSSBQIMK.js} +14 -6
- package/dist/chunk-SSSBQIMK.js.map +1 -0
- package/dist/cli.js +115 -17
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-UPXFMLUY.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
parsePackageJson,
|
|
11
11
|
parseServiceBindings,
|
|
12
12
|
trace
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-SSSBQIMK.js";
|
|
14
14
|
|
|
15
15
|
// src/cli.ts
|
|
16
16
|
import { Command } from "commander";
|
|
@@ -185,7 +185,7 @@ function migrate(db) {
|
|
|
185
185
|
// package.json
|
|
186
186
|
var package_default = {
|
|
187
187
|
name: "@saptools/service-flow",
|
|
188
|
-
version: "0.1.
|
|
188
|
+
version: "0.1.16",
|
|
189
189
|
description: "Trace SAP CAP service-to-service flows across multi-repository workspaces with runtime-aware graph resolution",
|
|
190
190
|
type: "module",
|
|
191
191
|
publishConfig: {
|
|
@@ -543,19 +543,44 @@ function insertExecutableSymbols(db, repoId, rows) {
|
|
|
543
543
|
for (const r of rows) stmt.run(repoId, repoId, r.sourceFile, r.kind, r.localName, r.qualifiedName, r.exported ? 1 : 0, r.startLine, r.endLine, r.startOffset, r.endOffset, r.sourceFile, r.exportedName, r.importExportEvidence ? JSON.stringify(r.importExportEvidence) : null);
|
|
544
544
|
}
|
|
545
545
|
function insertSymbolCalls(db, repoId, rows) {
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
|
|
546
|
+
const callerStmt = db.prepare("SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND qualified_name=? ORDER BY id LIMIT 1");
|
|
547
|
+
const insertStmt = db.prepare("INSERT INTO symbol_calls(repo_id,caller_symbol_id,callee_symbol_id,callee_expression,import_source,source_file,source_line,status,confidence,evidence_json,unresolved_reason) VALUES(?,?,?,?,?,?,?,?,?,?,?)");
|
|
548
|
+
for (const r of rows) {
|
|
549
|
+
const caller = callerStmt.get(repoId, r.sourceFile, r.callerQualifiedName);
|
|
550
|
+
const target = resolveSymbolCallTarget(db, repoId, r);
|
|
551
|
+
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);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function resolveSymbolCallTarget(db, repoId, r) {
|
|
555
|
+
const evidence = r.evidence;
|
|
556
|
+
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);
|
|
557
|
+
if (localRows.length === 1) return { id: localRows[0]?.id ?? null, status: "resolved", reason: null, strategy: "same_file_exact", candidateCount: 1 };
|
|
558
|
+
if (localRows.length > 1) return { id: null, status: "ambiguous", reason: "Multiple same-file symbol targets matched exactly", strategy: "same_file_exact", candidateCount: localRows.length };
|
|
559
|
+
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);
|
|
560
|
+
if (evidence.relation === "relative_import_proxy_member" && rows.length > 1) {
|
|
561
|
+
const objectMapRows = rows.filter((row) => String(row.evidenceJson ?? "").includes("exported_object_shorthand") || String(row.evidenceJson ?? "").includes("exported_object_literal"));
|
|
562
|
+
if (objectMapRows.length > 0) {
|
|
563
|
+
const concrete = rows.find((row) => row.kind !== "object_alias") ?? objectMapRows[0];
|
|
564
|
+
return { id: concrete?.id ?? null, status: "resolved", reason: null, strategy: "proxy_member_exported_object_map", candidateCount: rows.length };
|
|
565
|
+
}
|
|
566
|
+
return { id: null, status: "ambiguous", reason: "Proxy member target requires explicit factory/module/type evidence; global member name is ambiguous", strategy: "proxy_member_no_global_name_fallback", candidateCount: rows.length };
|
|
567
|
+
}
|
|
568
|
+
if (rows.length === 1) return { id: rows[0]?.id ?? null, status: "resolved", reason: null, strategy: evidence.relation === "relative_import_proxy_member" ? "proxy_member_unique_exported_candidate" : "relative_import_exported_exact", candidateCount: 1 };
|
|
569
|
+
if (rows.length > 1) return { id: null, status: "ambiguous", reason: "Multiple exported symbol targets matched exactly", strategy: "exported_exact", candidateCount: rows.length };
|
|
570
|
+
return { id: null, status: "unresolved", reason: "No local symbol target matched exactly", strategy: evidence.relation === "relative_import_proxy_member" ? "proxy_member_no_global_name_fallback" : "exact_symbol_match", candidateCount: 0 };
|
|
549
571
|
}
|
|
550
572
|
function insertCalls(db, repoId, rows) {
|
|
551
573
|
const stmt = db.prepare(
|
|
552
|
-
"INSERT INTO outbound_calls(repo_id,source_symbol_id,call_type,method,operation_path_expr,query_entity,event_name_expr,payload_summary,source_file,source_line,confidence,unresolved_reason,local_service_name,local_service_lookup,alias_chain_json,service_binding_id) VALUES(?,(SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND start_line<=? AND end_line>=? ORDER BY (end_line-start_line),id LIMIT 1),?,?,?,?,?,?,?,?,?,?,?,?,?,(SELECT id FROM service_bindings WHERE repo_id=? AND variable_name=? AND source_file=? ORDER BY CASE WHEN source_line<=? THEN 0 ELSE 1 END, ABS(source_line-?) ASC, id DESC LIMIT 1))"
|
|
574
|
+
"INSERT INTO outbound_calls(repo_id,source_symbol_id,call_type,method,operation_path_expr,query_entity,event_name_expr,payload_summary,source_file,source_line,confidence,unresolved_reason,local_service_name,local_service_lookup,alias_chain_json,service_binding_id) VALUES(?,COALESCE((SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND qualified_name=? ORDER BY id LIMIT 1),(SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND start_line<=? AND end_line>=? ORDER BY (end_line-start_line),id LIMIT 1)),?,?,?,?,?,?,?,?,?,?,?,?,?,(SELECT id FROM service_bindings WHERE repo_id=? AND variable_name=? AND source_file=? ORDER BY CASE WHEN source_line<=? THEN 0 ELSE 1 END, ABS(source_line-?) ASC, id DESC LIMIT 1))"
|
|
553
575
|
);
|
|
554
576
|
for (const r of rows)
|
|
555
577
|
stmt.run(
|
|
556
578
|
repoId,
|
|
557
579
|
repoId,
|
|
558
580
|
r.sourceFile,
|
|
581
|
+
r.sourceSymbolQualifiedName,
|
|
582
|
+
repoId,
|
|
583
|
+
r.sourceFile,
|
|
559
584
|
r.sourceLine,
|
|
560
585
|
r.sourceLine,
|
|
561
586
|
r.callType,
|
|
@@ -643,6 +668,10 @@ function isFunctionLike(node) {
|
|
|
643
668
|
function exported(node) {
|
|
644
669
|
return Boolean(ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export);
|
|
645
670
|
}
|
|
671
|
+
function isPublicStaticMethod(node) {
|
|
672
|
+
const flags = ts.getCombinedModifierFlags(node);
|
|
673
|
+
return (flags & ts.ModifierFlags.Static) !== 0 && (flags & ts.ModifierFlags.Private) === 0 && (flags & ts.ModifierFlags.Protected) === 0;
|
|
674
|
+
}
|
|
646
675
|
function exportDeclarations(source) {
|
|
647
676
|
const exports = /* @__PURE__ */ new Map();
|
|
648
677
|
const visit = (node) => {
|
|
@@ -701,11 +730,20 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
701
730
|
const imports = /* @__PURE__ */ new Map();
|
|
702
731
|
const exportNames = exportDeclarations(source);
|
|
703
732
|
const objectExports = /* @__PURE__ */ new Set();
|
|
704
|
-
const
|
|
705
|
-
|
|
733
|
+
const exportedClasses = /* @__PURE__ */ new Set();
|
|
734
|
+
const proxyVariables = /* @__PURE__ */ new Map();
|
|
735
|
+
const addSymbol = (kind, localName, node, parentName, exportedName, evidence) => {
|
|
736
|
+
const parentRoot = parentName?.split(".")[0] ?? "";
|
|
737
|
+
const declaredExportName = exportedName ?? exportNames.get(parentName ? parentRoot : localName);
|
|
706
738
|
const qualifiedName = parentName ? `${parentName}.${localName}` : localName;
|
|
707
|
-
const objectExported = parentName ? objectExports.has(
|
|
708
|
-
|
|
739
|
+
const objectExported = parentName ? objectExports.has(parentRoot) : false;
|
|
740
|
+
const classMemberExported = kind === "method" && parentName ? exportedClasses.has(parentRoot) && ts.isMethodDeclaration(node) && isPublicStaticMethod(node) : false;
|
|
741
|
+
const effectiveExportedName = classMemberExported || objectExported ? qualifiedName : declaredExportName;
|
|
742
|
+
const sourceEvidence = evidence ?? (classMemberExported ? { source: "exported_class_member", exportedClass: parentRoot, memberKind: "static_method" } : declaredExportName ? { exportedName: declaredExportName, source: "export_declaration" } : objectExported ? { exportedName: qualifiedName, source: "exported_object_literal" } : void 0);
|
|
743
|
+
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 });
|
|
744
|
+
};
|
|
745
|
+
const addAliasSymbol = (objectName, propertyName, node, targetImportSource) => {
|
|
746
|
+
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 } });
|
|
709
747
|
};
|
|
710
748
|
const visitImports = (node) => {
|
|
711
749
|
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
@@ -730,12 +768,16 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
730
768
|
visitImports(source);
|
|
731
769
|
const visitSymbols = (node, parentClass) => {
|
|
732
770
|
if (ts.isClassDeclaration(node) && node.name) {
|
|
771
|
+
if (exported(node) || exportNames.has(node.name.text)) exportedClasses.add(node.name.text);
|
|
733
772
|
for (const member of node.members) visitSymbols(member, node.name.text);
|
|
734
773
|
return;
|
|
735
774
|
}
|
|
736
775
|
if (ts.isMethodDeclaration(node)) {
|
|
737
776
|
const localName = nameOf(node.name);
|
|
738
777
|
if (localName) addSymbol("method", localName, node, parentClass);
|
|
778
|
+
} else if (ts.isPropertyDeclaration(node) && parentClass && node.initializer && (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer))) {
|
|
779
|
+
const localName = nameOf(node.name);
|
|
780
|
+
if (localName) addSymbol("method", localName, node.initializer, parentClass, void 0, { source: "class_property_function", memberKind: ts.isArrowFunction(node.initializer) ? "arrow_function_property" : "function_expression_property" });
|
|
739
781
|
} else if (ts.isFunctionDeclaration(node) && node.name) addSymbol("function", node.name.text, node, void 0, exported(node) ? node.name.text : void 0);
|
|
740
782
|
else if (ts.isVariableStatement(node)) {
|
|
741
783
|
for (const d of node.declarationList.declarations) {
|
|
@@ -743,8 +785,10 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
743
785
|
if (!localName || !d.initializer) continue;
|
|
744
786
|
if (isFunctionLike(d.initializer)) addSymbol("function", localName, d.initializer, void 0, exported(node) ? localName : exportNames.get(localName));
|
|
745
787
|
if (ts.isObjectLiteralExpression(d.initializer)) {
|
|
746
|
-
|
|
788
|
+
const objectIsExported = exported(node) || exportNames.has(localName);
|
|
789
|
+
if (objectIsExported) objectExports.add(localName);
|
|
747
790
|
for (const prop of d.initializer.properties) {
|
|
791
|
+
if (objectIsExported && ts.isShorthandPropertyAssignment(prop)) addAliasSymbol(localName, prop.name.text, prop.name, imports.get(prop.name.text));
|
|
748
792
|
if (ts.isPropertyAssignment(prop) && isObjectFunction(prop.initializer)) {
|
|
749
793
|
const propName = nameOf(prop.name);
|
|
750
794
|
if (propName) addSymbol("object_method", propName, prop.initializer, localName);
|
|
@@ -758,6 +802,45 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
758
802
|
} else ts.forEachChild(node, (child) => visitSymbols(child, parentClass));
|
|
759
803
|
};
|
|
760
804
|
visitSymbols(source);
|
|
805
|
+
const outboundCallNames = /* @__PURE__ */ new Set(["cds.run", "emit", "publish", "on", "send", "axios", "executeHttpRequest", "useOrFetchDestination"]);
|
|
806
|
+
const containsSupportedOutbound = (node) => {
|
|
807
|
+
let found = false;
|
|
808
|
+
const visit = (child) => {
|
|
809
|
+
if (found) return;
|
|
810
|
+
if (ts.isCallExpression(child)) {
|
|
811
|
+
const callee = callName(child.expression);
|
|
812
|
+
if (outboundCallNames.has(callee.expression) || (callee.member ? outboundCallNames.has(callee.member) : false)) found = true;
|
|
813
|
+
}
|
|
814
|
+
ts.forEachChild(child, visit);
|
|
815
|
+
};
|
|
816
|
+
visit(node);
|
|
817
|
+
return found;
|
|
818
|
+
};
|
|
819
|
+
const isTopLevelCallback = (node) => {
|
|
820
|
+
if (!ts.isArrowFunction(node) && !ts.isFunctionExpression(node)) return false;
|
|
821
|
+
if (!ts.isCallExpression(node.parent)) return false;
|
|
822
|
+
const callee = callName(node.parent.expression);
|
|
823
|
+
const member = callee.member ?? callee.local;
|
|
824
|
+
return Boolean(member && ["bootstrap", "served", "connect", "on", "once", "use", "get", "post", "put", "patch", "delete", "subscribe"].includes(member));
|
|
825
|
+
};
|
|
826
|
+
const visitCallbackSymbols = (node) => {
|
|
827
|
+
if (isTopLevelCallback(node) && containsSupportedOutbound(node)) {
|
|
828
|
+
const startLine = lineOf(source, node.getStart(source));
|
|
829
|
+
const name = `callback:${startLine}`;
|
|
830
|
+
symbols.push({ kind: "callback", localName: name, qualifiedName: `module:${sourceFile}#${name}`, sourceFile, startLine, endLine: lineOf(source, node.getEnd()), startOffset: node.getStart(source), endOffset: node.getEnd(), exported: false, importExportEvidence: { source: "synthetic_outbound_callback", callbackLine: startLine } });
|
|
831
|
+
}
|
|
832
|
+
ts.forEachChild(node, visitCallbackSymbols);
|
|
833
|
+
};
|
|
834
|
+
visitCallbackSymbols(source);
|
|
835
|
+
const visitProxyVariables = (node) => {
|
|
836
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && ts.isCallExpression(node.initializer) && ts.isPropertyAccessExpression(node.initializer.expression)) {
|
|
837
|
+
const callee = callName(node.initializer.expression);
|
|
838
|
+
const importSource = callee.local ? imports.get(callee.local) : void 0;
|
|
839
|
+
if (callee.member && importSource && isRelativeImport(importSource)) proxyVariables.set(node.name.text, { importSource, factory: callee.expression, variableName: node.name.text });
|
|
840
|
+
}
|
|
841
|
+
ts.forEachChild(node, visitProxyVariables);
|
|
842
|
+
};
|
|
843
|
+
visitProxyVariables(source);
|
|
761
844
|
const localCallables = new Set(symbols.flatMap((sym) => [sym.localName, sym.qualifiedName]));
|
|
762
845
|
const visitCalls = (node) => {
|
|
763
846
|
if (ts.isCallExpression(node)) {
|
|
@@ -765,8 +848,9 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
765
848
|
const caller = nearest(symbols, line);
|
|
766
849
|
if (caller) {
|
|
767
850
|
const callee = callName(node.expression);
|
|
768
|
-
const
|
|
769
|
-
const
|
|
851
|
+
const proxy = callee.local ? proxyVariables.get(callee.local) : void 0;
|
|
852
|
+
const importSource = proxy?.importSource ?? (callee.local ? imports.get(callee.local) : void 0) ?? (callee.member && callee.local ? imports.get(callee.local) : void 0);
|
|
853
|
+
const targetName = proxy && callee.member ? callee.member : callee.expression.startsWith("this.") ? callee.member : callee.member && callee.local ? `${callee.local}.${callee.member}` : callee.local;
|
|
770
854
|
const className = caller.qualifiedName.includes(".") ? caller.qualifiedName.split(".")[0] : void 0;
|
|
771
855
|
const thisTarget = className && callee.member ? `${className}.${callee.member}` : void 0;
|
|
772
856
|
const loggerLike = callee.receiver?.endsWith(".logger") || callee.local === "logger" || (callee.expression.startsWith("this.logger.") && callee.member ? loggerMembers.has(callee.member) : false);
|
|
@@ -778,7 +862,7 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
778
862
|
const ignored = loggerLike || terminalMember || importedFromPackage || ignoredFrameworkCall(callee);
|
|
779
863
|
const resolvedTarget = provenThisMethod ? thisTarget : targetName;
|
|
780
864
|
const keep = Boolean(resolvedTarget) && !ignored && (provenLocal || provenThisMethod || provenRelativeImport);
|
|
781
|
-
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: importSource ? "relative_import" : provenThisMethod ? "indexed_this_method" : "indexed_local_symbol", caller: caller.qualifiedName, targetName: resolvedTarget } });
|
|
865
|
+
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 } });
|
|
782
866
|
}
|
|
783
867
|
}
|
|
784
868
|
ts.forEachChild(node, visitCalls);
|
|
@@ -1047,16 +1131,30 @@ function localServiceDiagnostics(db, strict) {
|
|
|
1047
1131
|
}
|
|
1048
1132
|
function parserQualityDiagnostics(db, strict) {
|
|
1049
1133
|
if (!strict) return [];
|
|
1134
|
+
const symbolUnresolvedThreshold = 0.05;
|
|
1135
|
+
const dbUnknownThreshold = 0.25;
|
|
1136
|
+
const outboundUnownedThreshold = 0.01;
|
|
1050
1137
|
const symbol = db.prepare("SELECT COUNT(*) total, SUM(CASE WHEN status='resolved' THEN 1 ELSE 0 END) resolved, SUM(CASE WHEN status='unresolved' THEN 1 ELSE 0 END) unresolved FROM symbol_calls").get();
|
|
1051
1138
|
const top = db.prepare("SELECT callee_expression calleeExpression,COUNT(*) count FROM symbol_calls WHERE status='unresolved' GROUP BY callee_expression ORDER BY count DESC,callee_expression LIMIT 5").all();
|
|
1139
|
+
const evidence = db.prepare("SELECT COUNT(*) total, SUM(CASE WHEN json_valid(evidence_json)=0 OR json_type(evidence_json) <> 'object' THEN 1 ELSE 0 END) nonObject FROM symbol_calls").get();
|
|
1052
1140
|
const dbq = db.prepare("SELECT COUNT(*) total, SUM(CASE WHEN query_entity IS NOT NULL THEN 1 ELSE 0 END) known, SUM(CASE WHEN query_entity IS NULL THEN 1 ELSE 0 END) unknown FROM outbound_calls WHERE call_type='local_db_query'").get();
|
|
1141
|
+
const outbound = db.prepare("SELECT COUNT(*) total, SUM(CASE WHEN source_symbol_id IS NULL THEN 1 ELSE 0 END) withoutOwnership FROM outbound_calls").get();
|
|
1142
|
+
const ownerlessByType = db.prepare("SELECT call_type callType, COUNT(*) count FROM outbound_calls WHERE source_symbol_id IS NULL GROUP BY call_type ORDER BY count DESC, call_type").all();
|
|
1143
|
+
const ownerlessByGap = db.prepare("SELECT CASE WHEN source_line <= 1 THEN 'module_preamble' ELSE 'no_indexed_enclosing_symbol' END gap, COUNT(*) count FROM outbound_calls WHERE source_symbol_id IS NULL GROUP BY gap ORDER BY count DESC, gap").all();
|
|
1053
1144
|
const symbolTotal = Number(symbol.total ?? 0);
|
|
1054
1145
|
const symbolUnresolved = Number(symbol.unresolved ?? 0);
|
|
1146
|
+
const symbolUnresolvedRatio = symbolTotal === 0 ? 0 : Number((symbolUnresolved / symbolTotal).toFixed(4));
|
|
1055
1147
|
const queryTotal = Number(dbq.total ?? 0);
|
|
1056
1148
|
const queryUnknown = Number(dbq.unknown ?? 0);
|
|
1149
|
+
const queryUnknownRatio = queryTotal === 0 ? 0 : Number((queryUnknown / queryTotal).toFixed(4));
|
|
1150
|
+
const outboundTotal = Number(outbound.total ?? 0);
|
|
1151
|
+
const outboundWithoutOwnership = Number(outbound.withoutOwnership ?? 0);
|
|
1152
|
+
const outboundWithoutOwnershipRatio = outboundTotal === 0 ? 0 : Number((outboundWithoutOwnership / outboundTotal).toFixed(4));
|
|
1057
1153
|
return [
|
|
1058
|
-
{ severity: "info", code: "
|
|
1059
|
-
{ severity: "info", code: "
|
|
1154
|
+
{ severity: Number(evidence.nonObject ?? 0) > 0 ? "warning" : "info", code: "strict_symbol_call_evidence_quality", message: "Symbol-call evidence JSON object aggregate", total: Number(evidence.total ?? 0), nonObject: Number(evidence.nonObject ?? 0) },
|
|
1155
|
+
{ severity: symbolUnresolvedRatio > symbolUnresolvedThreshold ? "warning" : "info", code: "strict_symbol_call_quality", message: "Symbol-call quality aggregate", total: symbolTotal, resolved: Number(symbol.resolved ?? 0), unresolved: symbolUnresolved, unresolvedRatio: symbolUnresolvedRatio, unresolvedRatioThreshold: symbolUnresolvedThreshold, topUnresolvedCallees: top },
|
|
1156
|
+
{ severity: queryUnknownRatio > dbUnknownThreshold ? "warning" : "info", code: "strict_db_query_quality", message: "Local DB query quality aggregate", total: queryTotal, known: Number(dbq.known ?? 0), unknown: queryUnknown, unknownRatio: queryUnknownRatio, unknownRatioThreshold: dbUnknownThreshold },
|
|
1157
|
+
{ severity: outboundWithoutOwnershipRatio > outboundUnownedThreshold ? "warning" : "info", code: "strict_outbound_source_ownership_quality", message: "Outbound call source-symbol ownership aggregate", total: outboundTotal, withoutOwnership: outboundWithoutOwnership, withoutOwnershipRatio: outboundWithoutOwnershipRatio, withoutOwnershipRatioThreshold: outboundUnownedThreshold, ownerlessByType, ownerlessByGap }
|
|
1060
1158
|
];
|
|
1061
1159
|
}
|
|
1062
1160
|
function createProgram() {
|
|
@@ -1083,7 +1181,7 @@ function createProgram() {
|
|
|
1083
1181
|
(opts) => void withWorkspace(opts.workspace, (db, workspaceId) => {
|
|
1084
1182
|
const r = linkWorkspace(db, workspaceId);
|
|
1085
1183
|
process.stdout.write(
|
|
1086
|
-
`Linked ${r.edgeCount} edges: ${r.
|
|
1184
|
+
`Linked ${r.edgeCount} edges: ${r.remoteResolvedCount} remote operation calls resolved, ${r.localResolvedCount} local operation calls resolved, ${r.unresolvedCount} unresolved operation calls, ${r.ambiguousCount} ambiguous operation calls, ${r.dynamicCount} dynamic operation calls, ${r.terminalCount} terminal call edges, ${r.dependencyResolvedCount} dependency resolved, ${r.dependencyAmbiguousCount} dependency ambiguous, ${r.implementationResolvedCount} implementation resolved, ${r.implementationAmbiguousCount} implementation ambiguous, ${r.implementationUnresolvedCount} implementation unresolved
|
|
1087
1185
|
`
|
|
1088
1186
|
);
|
|
1089
1187
|
}).catch(fail)
|