@saptools/service-flow 0.1.24 → 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/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: {
|
|
@@ -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;
|
|
@@ -876,7 +933,7 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
876
933
|
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && ts.isNewExpression(node.initializer) && ts.isIdentifier(node.initializer.expression)) {
|
|
877
934
|
const className = node.initializer.expression.text;
|
|
878
935
|
const importSource = imports.get(className);
|
|
879
|
-
if (!importSource
|
|
936
|
+
if (!builtInConstructors.has(className) && (importSource && isRelativeImport(importSource) || declaredClasses.has(className))) classInstances.set(node.name.text, { className, importSource });
|
|
880
937
|
}
|
|
881
938
|
ts.forEachChild(node, visitClassInstances);
|
|
882
939
|
};
|
|
@@ -1221,11 +1278,15 @@ function parserQualityDiagnostics(db, strict) {
|
|
|
1221
1278
|
const aliasQuality = identityAliasBindingQuality(db);
|
|
1222
1279
|
const noBindingQuality = remoteActionNoBindingQuality(db);
|
|
1223
1280
|
const contextualQuality = contextualImplementationQuality(db);
|
|
1281
|
+
const classInstanceQuality = classInstanceNoiseQuality(db);
|
|
1282
|
+
const bindingPropagationQuality = contextualBindingPropagationQuality(db);
|
|
1224
1283
|
const wrapperQuality = wrapperPathPropagationQuality(db);
|
|
1225
1284
|
return [
|
|
1226
1285
|
aliasQuality,
|
|
1227
1286
|
noBindingQuality,
|
|
1228
1287
|
contextualQuality,
|
|
1288
|
+
classInstanceQuality,
|
|
1289
|
+
bindingPropagationQuality,
|
|
1229
1290
|
wrapperQuality,
|
|
1230
1291
|
remoteQuery,
|
|
1231
1292
|
invocation,
|
|
@@ -1253,7 +1314,16 @@ function remoteActionNoBindingQuality(db) {
|
|
|
1253
1314
|
WHEN c.unresolved_reason='dynamic_operation_path_identifier' THEN 'dynamic_path_identifier'
|
|
1254
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'
|
|
1255
1316
|
WHEN json_extract(c.evidence_json,'$.receiver') LIKE '%.%' THEN 'likely_parameter_context_needed'
|
|
1256
|
-
WHEN EXISTS (
|
|
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'
|
|
1257
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'
|
|
1258
1328
|
WHEN e.status='unresolved' AND COALESCE(e.unresolved_reason,'') LIKE '%No indexed target operation%' THEN 'no_indexed_target_operation'
|
|
1259
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'
|
|
@@ -1268,6 +1338,31 @@ function remoteActionNoBindingQuality(db) {
|
|
|
1268
1338
|
const total = rows.reduce((sum, row) => sum + Number(row.count ?? 0), 0);
|
|
1269
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 };
|
|
1270
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
|
+
}
|
|
1271
1366
|
function contextualImplementationQuality(db) {
|
|
1272
1367
|
const rows = db.prepare(`SELECT status,COALESCE(unresolved_reason,status) reason,COUNT(*) count
|
|
1273
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();
|