@saptools/service-flow 0.1.10 → 0.1.11
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 +10 -0
- package/README.md +8 -2
- package/dist/{chunk-LQT67AU5.js → chunk-GG4XJGES.js} +179 -87
- package/dist/chunk-GG4XJGES.js.map +1 -0
- package/dist/cli.js +176 -45
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-LQT67AU5.js.map +0 -1
|
@@ -524,6 +524,7 @@ function summarizeExpression(text) {
|
|
|
524
524
|
// src/parsers/outbound-call-parser.ts
|
|
525
525
|
import fs6 from "fs/promises";
|
|
526
526
|
import path7 from "path";
|
|
527
|
+
import ts4 from "typescript";
|
|
527
528
|
function lineOf3(text, idx) {
|
|
528
529
|
return text.slice(0, idx).split("\n").length;
|
|
529
530
|
}
|
|
@@ -627,32 +628,63 @@ async function parseOutboundCalls(repoPath, filePath) {
|
|
|
627
628
|
confidence: 0.7,
|
|
628
629
|
unresolvedReason: "External HTTP destination is outside indexed CAP services"
|
|
629
630
|
});
|
|
630
|
-
|
|
631
|
-
/cds\.services(?:\[['"]([^'"]+)['"]\]|\.(\w+))\.(\w+)\s*\(/g
|
|
632
|
-
))
|
|
633
|
-
out.push({
|
|
634
|
-
callType: "local_service_call",
|
|
635
|
-
operationPathExpr: `/${m[3] ?? ""}`,
|
|
636
|
-
payloadSummary: m[1] ?? m[2],
|
|
637
|
-
sourceFile: normalizePath(filePath),
|
|
638
|
-
sourceLine: lineOf3(text, m.index ?? 0),
|
|
639
|
-
confidence: 0.75
|
|
640
|
-
});
|
|
631
|
+
out.push(...parseLocalServiceCalls(text, filePath));
|
|
641
632
|
return out;
|
|
642
633
|
}
|
|
634
|
+
function parseLocalServiceCalls(text, filePath) {
|
|
635
|
+
const source = ts4.createSourceFile(filePath, text, ts4.ScriptTarget.Latest, true, filePath.endsWith(".ts") ? ts4.ScriptKind.TS : ts4.ScriptKind.JS);
|
|
636
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
637
|
+
const calls = [];
|
|
638
|
+
const visit = (node) => {
|
|
639
|
+
if (ts4.isVariableDeclaration(node) && ts4.isIdentifier(node.name) && node.initializer) {
|
|
640
|
+
const origin = serviceLookup(node.initializer, aliases);
|
|
641
|
+
if (origin) aliases.set(node.name.text, { ...origin, chain: [...origin.chain, node.name.text] });
|
|
642
|
+
}
|
|
643
|
+
if (ts4.isCallExpression(node)) {
|
|
644
|
+
const parsed = serviceOperationCall(node.expression, aliases);
|
|
645
|
+
if (parsed && parsed.operation !== "entities") calls.push({
|
|
646
|
+
callType: "local_service_call",
|
|
647
|
+
operationPathExpr: `/${parsed.operation}`,
|
|
648
|
+
payloadSummary: parsed.service,
|
|
649
|
+
localServiceName: parsed.service,
|
|
650
|
+
localServiceLookup: parsed.lookup,
|
|
651
|
+
aliasChain: parsed.chain,
|
|
652
|
+
sourceFile: normalizePath(filePath),
|
|
653
|
+
sourceLine: lineOf3(text, node.getStart(source)),
|
|
654
|
+
confidence: 0.9
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
ts4.forEachChild(node, visit);
|
|
658
|
+
};
|
|
659
|
+
visit(source);
|
|
660
|
+
return calls;
|
|
661
|
+
}
|
|
662
|
+
function serviceLookup(expr, aliases) {
|
|
663
|
+
if (ts4.isIdentifier(expr)) return aliases.get(expr.text);
|
|
664
|
+
if (ts4.isPropertyAccessExpression(expr) && expr.expression.getText() === "cds.services") return { service: expr.name.text, lookup: expr.getText(), chain: [expr.getText()] };
|
|
665
|
+
if (ts4.isElementAccessExpression(expr) && expr.expression.getText() === "cds.services" && ts4.isStringLiteral(expr.argumentExpression)) return { service: expr.argumentExpression.text, lookup: expr.getText(), chain: [expr.getText()] };
|
|
666
|
+
return void 0;
|
|
667
|
+
}
|
|
668
|
+
function serviceOperationCall(expr, aliases) {
|
|
669
|
+
if (!ts4.isPropertyAccessExpression(expr)) return void 0;
|
|
670
|
+
const operation = expr.name.text;
|
|
671
|
+
const origin = serviceLookup(expr.expression, aliases);
|
|
672
|
+
if (!origin) return void 0;
|
|
673
|
+
return { ...origin, operation };
|
|
674
|
+
}
|
|
643
675
|
|
|
644
676
|
// src/parsers/service-binding-parser.ts
|
|
645
677
|
import fs7 from "fs/promises";
|
|
646
678
|
import path8 from "path";
|
|
647
|
-
import
|
|
679
|
+
import ts5 from "typescript";
|
|
648
680
|
function lineOf4(sf, node) {
|
|
649
681
|
return sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
650
682
|
}
|
|
651
683
|
function stringValue(node) {
|
|
652
684
|
if (!node) return void 0;
|
|
653
|
-
if (
|
|
685
|
+
if (ts5.isStringLiteralLike(node) || ts5.isNoSubstitutionTemplateLiteral(node))
|
|
654
686
|
return node.text;
|
|
655
|
-
if (
|
|
687
|
+
if (ts5.isTemplateExpression(node))
|
|
656
688
|
return node.getText().replace(/^`|`$/g, "");
|
|
657
689
|
return node.getText();
|
|
658
690
|
}
|
|
@@ -661,22 +693,22 @@ function placeholders(value) {
|
|
|
661
693
|
}
|
|
662
694
|
function connectFactFromCall(call) {
|
|
663
695
|
const expr = call.expression;
|
|
664
|
-
if (!
|
|
696
|
+
if (!ts5.isPropertyAccessExpression(expr) || expr.name.text !== "to")
|
|
665
697
|
return void 0;
|
|
666
698
|
const inner = expr.expression;
|
|
667
|
-
if (!
|
|
699
|
+
if (!ts5.isPropertyAccessExpression(inner) || inner.name.text !== "connect" || inner.expression.getText() !== "cds")
|
|
668
700
|
return void 0;
|
|
669
701
|
const first = call.arguments[0];
|
|
670
702
|
if (!first) return void 0;
|
|
671
703
|
const second = call.arguments[1];
|
|
672
|
-
const objectArg =
|
|
704
|
+
const objectArg = ts5.isObjectLiteralExpression(first) ? first : second && ts5.isObjectLiteralExpression(second) ? second : void 0;
|
|
673
705
|
let alias;
|
|
674
706
|
let aliasExpr;
|
|
675
|
-
if (
|
|
707
|
+
if (ts5.isStringLiteralLike(first) || ts5.isNoSubstitutionTemplateLiteral(first))
|
|
676
708
|
alias = first.text;
|
|
677
|
-
else if (!
|
|
709
|
+
else if (!ts5.isObjectLiteralExpression(first))
|
|
678
710
|
aliasExpr = stringValue(first);
|
|
679
|
-
if ((
|
|
711
|
+
if ((ts5.isStringLiteralLike(first) || ts5.isNoSubstitutionTemplateLiteral(first)) && !objectArg)
|
|
680
712
|
return { alias: first.text, isDynamic: false, placeholders: [] };
|
|
681
713
|
if (!objectArg && aliasExpr)
|
|
682
714
|
return {
|
|
@@ -688,13 +720,13 @@ function connectFactFromCall(call) {
|
|
|
688
720
|
let servicePathExpr;
|
|
689
721
|
function visitObject(obj) {
|
|
690
722
|
for (const prop of obj.properties) {
|
|
691
|
-
if (!
|
|
692
|
-
const name =
|
|
723
|
+
if (!ts5.isPropertyAssignment(prop)) continue;
|
|
724
|
+
const name = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
|
|
693
725
|
if (name === "destination")
|
|
694
726
|
destinationExpr = stringValue(prop.initializer);
|
|
695
727
|
if (name === "path" || name === "servicePath")
|
|
696
728
|
servicePathExpr = stringValue(prop.initializer);
|
|
697
|
-
if (
|
|
729
|
+
if (ts5.isObjectLiteralExpression(prop.initializer))
|
|
698
730
|
visitObject(prop.initializer);
|
|
699
731
|
}
|
|
700
732
|
}
|
|
@@ -714,8 +746,8 @@ function connectFactFromCall(call) {
|
|
|
714
746
|
};
|
|
715
747
|
}
|
|
716
748
|
function unwrapCall(expr) {
|
|
717
|
-
if (
|
|
718
|
-
if (
|
|
749
|
+
if (ts5.isAwaitExpression(expr)) return unwrapCall(expr.expression);
|
|
750
|
+
if (ts5.isCallExpression(expr)) return expr;
|
|
719
751
|
return void 0;
|
|
720
752
|
}
|
|
721
753
|
function findConnectInExpression(expr) {
|
|
@@ -727,8 +759,8 @@ function findConnectInExpression(expr) {
|
|
|
727
759
|
let found;
|
|
728
760
|
function visit(node) {
|
|
729
761
|
if (found) return;
|
|
730
|
-
if (
|
|
731
|
-
if (!found)
|
|
762
|
+
if (ts5.isCallExpression(node)) found = connectFactFromCall(node);
|
|
763
|
+
if (!found) ts5.forEachChild(node, visit);
|
|
732
764
|
}
|
|
733
765
|
visit(expr);
|
|
734
766
|
return found;
|
|
@@ -736,12 +768,12 @@ function findConnectInExpression(expr) {
|
|
|
736
768
|
async function readSource(abs) {
|
|
737
769
|
try {
|
|
738
770
|
const text = await fs7.readFile(abs, "utf8");
|
|
739
|
-
return
|
|
771
|
+
return ts5.createSourceFile(
|
|
740
772
|
abs,
|
|
741
773
|
text,
|
|
742
|
-
|
|
774
|
+
ts5.ScriptTarget.Latest,
|
|
743
775
|
true,
|
|
744
|
-
|
|
776
|
+
ts5.ScriptKind.TS
|
|
745
777
|
);
|
|
746
778
|
} catch {
|
|
747
779
|
return void 0;
|
|
@@ -770,7 +802,7 @@ async function resolveImport(repoPath, fromFile, spec) {
|
|
|
770
802
|
async function importsFor(repoPath, filePath, sf) {
|
|
771
803
|
const imports = [];
|
|
772
804
|
for (const stmt of sf.statements) {
|
|
773
|
-
if (!
|
|
805
|
+
if (!ts5.isImportDeclaration(stmt) || !ts5.isStringLiteralLike(stmt.moduleSpecifier))
|
|
774
806
|
continue;
|
|
775
807
|
const sourceFile = await resolveImport(
|
|
776
808
|
repoPath,
|
|
@@ -786,7 +818,7 @@ async function importsFor(repoPath, filePath, sf) {
|
|
|
786
818
|
sourceFile
|
|
787
819
|
});
|
|
788
820
|
const bindings = clause.namedBindings;
|
|
789
|
-
if (bindings &&
|
|
821
|
+
if (bindings && ts5.isNamedImports(bindings))
|
|
790
822
|
for (const el of bindings.elements)
|
|
791
823
|
imports.push({
|
|
792
824
|
localName: el.name.text,
|
|
@@ -799,17 +831,17 @@ async function importsFor(repoPath, filePath, sf) {
|
|
|
799
831
|
function exportedLocalNames(sf) {
|
|
800
832
|
const exports = /* @__PURE__ */ new Map();
|
|
801
833
|
for (const stmt of sf.statements) {
|
|
802
|
-
const direct =
|
|
803
|
-
(m) => m.kind ===
|
|
834
|
+
const direct = ts5.canHaveModifiers(stmt) ? ts5.getModifiers(stmt)?.some(
|
|
835
|
+
(m) => m.kind === ts5.SyntaxKind.ExportKeyword
|
|
804
836
|
) ?? false : false;
|
|
805
|
-
if (direct &&
|
|
837
|
+
if (direct && ts5.isFunctionDeclaration(stmt) && stmt.name)
|
|
806
838
|
exports.set(stmt.name.text, stmt.name.text);
|
|
807
|
-
if (direct &&
|
|
839
|
+
if (direct && ts5.isVariableStatement(stmt)) {
|
|
808
840
|
for (const decl of stmt.declarationList.declarations)
|
|
809
|
-
if (
|
|
841
|
+
if (ts5.isIdentifier(decl.name)) exports.set(decl.name.text, decl.name.text);
|
|
810
842
|
}
|
|
811
|
-
if (!
|
|
812
|
-
if (!
|
|
843
|
+
if (!ts5.isExportDeclaration(stmt) || !stmt.exportClause) continue;
|
|
844
|
+
if (!ts5.isNamedExports(stmt.exportClause)) continue;
|
|
813
845
|
for (const el of stmt.exportClause.elements)
|
|
814
846
|
exports.set(el.name.text, el.propertyName?.text ?? el.name.text);
|
|
815
847
|
}
|
|
@@ -823,18 +855,18 @@ async function helperBindings(repoPath, filePath) {
|
|
|
823
855
|
const exportedLocals = exportedLocalNames(sf);
|
|
824
856
|
const factsByLocal = /* @__PURE__ */ new Map();
|
|
825
857
|
for (const stmt of sf.statements) {
|
|
826
|
-
if (
|
|
858
|
+
if (ts5.isFunctionDeclaration(stmt) && stmt.name) {
|
|
827
859
|
let fact;
|
|
828
860
|
stmt.forEachChild(function visit(node) {
|
|
829
|
-
if (!fact &&
|
|
861
|
+
if (!fact && ts5.isReturnStatement(node) && node.expression)
|
|
830
862
|
fact = findConnectInExpression(node.expression);
|
|
831
|
-
if (!fact)
|
|
863
|
+
if (!fact) ts5.forEachChild(node, visit);
|
|
832
864
|
});
|
|
833
865
|
if (fact) factsByLocal.set(stmt.name.text, { ...fact, sourceLine: lineOf4(sf, stmt) });
|
|
834
866
|
}
|
|
835
|
-
if (
|
|
867
|
+
if (ts5.isVariableStatement(stmt))
|
|
836
868
|
for (const decl of stmt.declarationList.declarations) {
|
|
837
|
-
if (!
|
|
869
|
+
if (!ts5.isIdentifier(decl.name) || !decl.initializer) continue;
|
|
838
870
|
const fact = findConnectInExpression(decl.initializer);
|
|
839
871
|
if (fact)
|
|
840
872
|
factsByLocal.set(decl.name.text, {
|
|
@@ -875,7 +907,7 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
875
907
|
return helper ? { imp, helper } : void 0;
|
|
876
908
|
}
|
|
877
909
|
async function recordVariable(decl) {
|
|
878
|
-
if (!
|
|
910
|
+
if (!ts5.isIdentifier(decl.name) || !decl.initializer) return;
|
|
879
911
|
const call = unwrapCall(decl.initializer);
|
|
880
912
|
if (!call) return;
|
|
881
913
|
const direct = connectFactFromCall(call);
|
|
@@ -886,7 +918,7 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
886
918
|
sourceFile: normalizePath(filePath),
|
|
887
919
|
sourceLine: lineOf4(sourceFileAst, decl)
|
|
888
920
|
});
|
|
889
|
-
else if (
|
|
921
|
+
else if (ts5.isIdentifier(call.expression)) {
|
|
890
922
|
const resolved = await importedHelper(call.expression.text);
|
|
891
923
|
if (resolved)
|
|
892
924
|
out.push({
|
|
@@ -913,14 +945,14 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
913
945
|
}
|
|
914
946
|
}
|
|
915
947
|
function recordDestructuredClassHelper(decl) {
|
|
916
|
-
if (!
|
|
948
|
+
if (!ts5.isObjectBindingPattern(decl.name) || !decl.initializer) return;
|
|
917
949
|
const call = unwrapCall(decl.initializer);
|
|
918
|
-
if (!call || !
|
|
950
|
+
if (!call || !ts5.isPropertyAccessExpression(call.expression)) return;
|
|
919
951
|
const target = call.expression;
|
|
920
|
-
if (target.expression.kind !==
|
|
952
|
+
if (target.expression.kind !== ts5.SyntaxKind.ThisKeyword) return;
|
|
921
953
|
for (const el of decl.name.elements) {
|
|
922
|
-
if (!
|
|
923
|
-
const propertyName = el.propertyName &&
|
|
954
|
+
if (!ts5.isIdentifier(el.name)) continue;
|
|
955
|
+
const propertyName = el.propertyName && ts5.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
|
|
924
956
|
const helper = classHelpers.find(
|
|
925
957
|
(h) => h.helperName === target.name.text && h.propertyName === propertyName
|
|
926
958
|
);
|
|
@@ -946,8 +978,8 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
946
978
|
}
|
|
947
979
|
const declarations = [];
|
|
948
980
|
function collectDeclarations(node) {
|
|
949
|
-
if (
|
|
950
|
-
|
|
981
|
+
if (ts5.isVariableDeclaration(node)) declarations.push(node);
|
|
982
|
+
ts5.forEachChild(node, collectDeclarations);
|
|
951
983
|
}
|
|
952
984
|
collectDeclarations(sourceFileAst);
|
|
953
985
|
for (const decl of declarations) {
|
|
@@ -959,16 +991,16 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
959
991
|
function collectClassHelpers(sf) {
|
|
960
992
|
const helpers = [];
|
|
961
993
|
for (const stmt of sf.statements) {
|
|
962
|
-
if (!
|
|
994
|
+
if (!ts5.isClassDeclaration(stmt) || !stmt.name) continue;
|
|
963
995
|
for (const member of stmt.members) {
|
|
964
996
|
let visit2 = function(node) {
|
|
965
|
-
if (
|
|
997
|
+
if (ts5.isVariableDeclaration(node) && ts5.isIdentifier(node.name) && node.initializer) {
|
|
966
998
|
const fact = findConnectInExpression(node.initializer);
|
|
967
999
|
if (fact) bindings.set(node.name.text, fact);
|
|
968
1000
|
}
|
|
969
|
-
if (
|
|
1001
|
+
if (ts5.isReturnStatement(node) && node.expression && ts5.isObjectLiteralExpression(node.expression)) {
|
|
970
1002
|
for (const prop of node.expression.properties) {
|
|
971
|
-
if (
|
|
1003
|
+
if (ts5.isShorthandPropertyAssignment(prop)) {
|
|
972
1004
|
const fact = bindings.get(prop.name.text);
|
|
973
1005
|
if (fact)
|
|
974
1006
|
helpers.push({
|
|
@@ -980,8 +1012,8 @@ function collectClassHelpers(sf) {
|
|
|
980
1012
|
sourceLine: lineOf4(sf, prop)
|
|
981
1013
|
});
|
|
982
1014
|
}
|
|
983
|
-
if (
|
|
984
|
-
const propertyName =
|
|
1015
|
+
if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.initializer)) {
|
|
1016
|
+
const propertyName = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
|
|
985
1017
|
const fact = propertyName ? bindings.get(prop.initializer.text) : void 0;
|
|
986
1018
|
if (propertyName && fact)
|
|
987
1019
|
helpers.push({
|
|
@@ -995,15 +1027,15 @@ function collectClassHelpers(sf) {
|
|
|
995
1027
|
}
|
|
996
1028
|
}
|
|
997
1029
|
}
|
|
998
|
-
|
|
1030
|
+
ts5.forEachChild(node, visit2);
|
|
999
1031
|
};
|
|
1000
1032
|
var visit = visit2;
|
|
1001
|
-
if (!
|
|
1002
|
-
if (!
|
|
1033
|
+
if (!ts5.isPropertyDeclaration(member) || !member.initializer) continue;
|
|
1034
|
+
if (!ts5.isIdentifier(member.name)) continue;
|
|
1003
1035
|
const className = stmt.name.text;
|
|
1004
1036
|
const helperName = member.name.text;
|
|
1005
1037
|
const initializer = member.initializer;
|
|
1006
|
-
if (!
|
|
1038
|
+
if (!ts5.isArrowFunction(initializer) && !ts5.isFunctionExpression(initializer))
|
|
1007
1039
|
continue;
|
|
1008
1040
|
const bindings = /* @__PURE__ */ new Map();
|
|
1009
1041
|
visit2(initializer);
|
|
@@ -1066,7 +1098,7 @@ function resolveOperation(db, signals, workspaceId) {
|
|
|
1066
1098
|
candidates: [],
|
|
1067
1099
|
reasons: ["missing_operation_path"]
|
|
1068
1100
|
};
|
|
1069
|
-
const candidates = rows(db, signals.operationPath, workspaceId).map((c) => ({
|
|
1101
|
+
const candidates = rows(db, signals.operationPath, workspaceId).filter((c) => matchesLocalRepo(db, c.operationId, signals.repoId)).map((c) => ({
|
|
1070
1102
|
...c,
|
|
1071
1103
|
score: 0.2,
|
|
1072
1104
|
reasons: ["operation_path_match"]
|
|
@@ -1089,6 +1121,11 @@ function resolveOperation(db, signals, workspaceId) {
|
|
|
1089
1121
|
c.score -= 0.1;
|
|
1090
1122
|
c.reasons.push("service_path_mismatch");
|
|
1091
1123
|
}
|
|
1124
|
+
if (signals.serviceName && (c.servicePath === `/${signals.serviceName}` || c.servicePath.endsWith(`/${signals.serviceName}`))) {
|
|
1125
|
+
c.score += 0.7;
|
|
1126
|
+
c.reasons.push("exact_local_service_name");
|
|
1127
|
+
}
|
|
1128
|
+
if (signals.serviceName && c.servicePath !== `/${signals.serviceName}` && !c.servicePath.endsWith(`/${signals.serviceName}`)) c.reasons.push("local_service_name_mismatch");
|
|
1092
1129
|
if (signals.hasExplicitOverride) {
|
|
1093
1130
|
c.score += 0.2;
|
|
1094
1131
|
c.reasons.push("explicit_dynamic_override");
|
|
@@ -1112,7 +1149,7 @@ function resolveOperation(db, signals, workspaceId) {
|
|
|
1112
1149
|
candidates,
|
|
1113
1150
|
reasons: ["operation_path_only_has_no_strong_target_signal"]
|
|
1114
1151
|
};
|
|
1115
|
-
if (best && best.score >= 0.9 && best.servicePath === signals.servicePath && (best.operationPath === signals.operationPath || best.operationName === signals.operationPath.replace(/^\//, "")) && (!second || best.score - second.score >= 0.25))
|
|
1152
|
+
if (best && best.score >= 0.9 && (best.servicePath === signals.servicePath || Boolean(signals.serviceName && (best.servicePath === `/${signals.serviceName}` || best.servicePath.endsWith(`/${signals.serviceName}`)))) && (best.operationPath === signals.operationPath || best.operationName === signals.operationPath.replace(/^\//, "")) && (!second || best.score - second.score >= 0.25))
|
|
1116
1153
|
return {
|
|
1117
1154
|
status: "resolved",
|
|
1118
1155
|
target: best,
|
|
@@ -1125,6 +1162,11 @@ function resolveOperation(db, signals, workspaceId) {
|
|
|
1125
1162
|
reasons: ["candidate_score_below_resolution_threshold"]
|
|
1126
1163
|
};
|
|
1127
1164
|
}
|
|
1165
|
+
function matchesLocalRepo(db, operationId, repoId) {
|
|
1166
|
+
if (repoId === void 0) return true;
|
|
1167
|
+
const row = db.prepare("SELECT s.repo_id repoId FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE o.id=?").get(operationId);
|
|
1168
|
+
return row?.repoId === repoId;
|
|
1169
|
+
}
|
|
1128
1170
|
|
|
1129
1171
|
// src/linker/helper-package-linker.ts
|
|
1130
1172
|
function normalizeName(value) {
|
|
@@ -1209,10 +1251,11 @@ function insertCallEdge(db, workspaceId, call, vars, generation) {
|
|
|
1209
1251
|
const servicePath = applyVariables(call.servicePathExpr ?? call.requireServicePath, vars);
|
|
1210
1252
|
const destination = call.destinationExpr ?? call.requireDestination;
|
|
1211
1253
|
const isDynamic = Boolean(Number(call.isDynamic ?? 0));
|
|
1212
|
-
const
|
|
1254
|
+
const isOperationCall = callType.startsWith("remote") || callType === "local_service_call";
|
|
1255
|
+
const resolution = isOperationCall ? resolveOperation(db, { servicePath, operationPath: op, serviceName: call.local_service_name, repoId: callType === "local_service_call" ? Number(call.repo_id) : void 0, alias: applyVariables(call.aliasExpr ?? call.alias, vars), destination: destination ? applyVariables(destination, vars) : void 0, isDynamic, hasExplicitOverride: Object.keys(vars).length > 0 || callType === "local_service_call" }, workspaceId) : { status: "unresolved", candidates: [], reasons: [] };
|
|
1213
1256
|
const evidence = callEvidence(call, resolution, servicePath, op, destination ? applyVariables(destination, vars) : void 0);
|
|
1214
1257
|
if (resolution.target) {
|
|
1215
|
-
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "REMOTE_CALL_RESOLVES_TO_OPERATION", "resolved", "call", String(call.id), "operation", String(resolution.target.operationId), resolution.target.score, JSON.stringify(evidence), isDynamic ? 1 : 0, generation);
|
|
1258
|
+
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, callType === "local_service_call" ? "LOCAL_CALL_RESOLVES_TO_OPERATION" : "REMOTE_CALL_RESOLVES_TO_OPERATION", "resolved", "call", String(call.id), "operation", String(resolution.target.operationId), resolution.target.score, JSON.stringify(evidence), isDynamic ? 1 : 0, generation);
|
|
1216
1259
|
return { status: "resolved" };
|
|
1217
1260
|
}
|
|
1218
1261
|
const edgeType = callType === "local_db_query" ? "HANDLER_RUNS_DB_QUERY" : callType === "external_http" ? "HANDLER_CALLS_EXTERNAL_HTTP" : callType === "async_emit" ? "HANDLER_EMITS_EVENT" : callType === "async_subscribe" ? "EVENT_CONSUMED_BY_HANDLER" : resolution.status === "dynamic" ? "DYNAMIC_EDGE_CANDIDATE" : "UNRESOLVED_EDGE";
|
|
@@ -1222,7 +1265,7 @@ function insertCallEdge(db, workspaceId, call, vars, generation) {
|
|
|
1222
1265
|
return { status };
|
|
1223
1266
|
}
|
|
1224
1267
|
function callEvidence(call, resolution, servicePath, op, destination) {
|
|
1225
|
-
return { sourceFile: call.source_file, sourceLine: call.source_line, file: call.source_file, line: call.source_line, repo: call.repoName, serviceAlias: call.alias, serviceAliasExpr: call.aliasExpr, destination, servicePath, operationPath: op, targetRepo: resolution.target?.repoName, targetOperation: resolution.target?.operationName, helperChain: call.helperChainJson ? JSON.parse(String(call.helperChainJson)) : void 0, candidates: resolution.candidates, candidateCount: resolution.candidates.length, resolutionStatus: resolution.status, resolutionReasons: resolution.reasons, analysisCompleteness: call.unresolved_reason ? "partial" : "complete", parserWarning: call.unresolved_reason ? { code: "parser_warning", message: call.unresolved_reason } : void 0 };
|
|
1268
|
+
return { sourceFile: call.source_file, sourceLine: call.source_line, file: call.source_file, line: call.source_line, repo: call.repoName, serviceAlias: call.alias, serviceAliasExpr: call.aliasExpr, destination, servicePath, operationPath: op, localServiceName: call.local_service_name, localServiceLookup: call.local_service_lookup, aliasChain: call.alias_chain_json ? JSON.parse(String(call.alias_chain_json)) : void 0, transport: call.call_type === "local_service_call" ? "local" : void 0, targetRepo: resolution.target?.repoName, targetOperation: resolution.target?.operationName, helperChain: call.helperChainJson ? JSON.parse(String(call.helperChainJson)) : void 0, candidates: resolution.candidates, candidateCount: resolution.candidates.length, resolutionStatus: resolution.status, resolutionReasons: resolution.reasons, analysisCompleteness: call.unresolved_reason ? "partial" : "complete", parserWarning: call.unresolved_reason ? { code: "parser_warning", message: call.unresolved_reason } : void 0 };
|
|
1226
1269
|
}
|
|
1227
1270
|
function linkImplementations(db, workspaceId, generation) {
|
|
1228
1271
|
const operations = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,o.operation_name operationName,s.service_path servicePath,s.repo_id modelRepoId,r.name modelRepo,r.package_name modelPackage,r.kind modelKind FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=?`).all(workspaceId);
|
|
@@ -1259,7 +1302,34 @@ function linkImplementations(db, workspaceId, generation) {
|
|
|
1259
1302
|
}
|
|
1260
1303
|
function rankedImplementationCandidates(db, workspaceId, operation) {
|
|
1261
1304
|
const rows2 = implementationCandidates(db, workspaceId, operation);
|
|
1262
|
-
return rows2.map((row) => scoreImplementationCandidate(row, operation)).sort((a, b) => b.score - a.score || String(a.className).localeCompare(String(b.className)) || a.methodId - b.methodId);
|
|
1305
|
+
return deduplicateCandidates(rows2.map((row) => scoreImplementationCandidate(row, operation))).sort((a, b) => b.score - a.score || String(a.className).localeCompare(String(b.className)) || a.methodId - b.methodId);
|
|
1306
|
+
}
|
|
1307
|
+
function deduplicateCandidates(rows2) {
|
|
1308
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1309
|
+
for (const row of rows2) {
|
|
1310
|
+
const key = [row.methodId, row.classId, row.applicationRepoId, row.importSource ?? "", row.registrationKind ?? ""].join(":");
|
|
1311
|
+
const registration = { file: row.registrationFile, line: row.registrationLine, kind: row.registrationKind, importSource: row.importSource };
|
|
1312
|
+
const existing = merged.get(key);
|
|
1313
|
+
if (!existing) {
|
|
1314
|
+
merged.set(key, { ...row, registrations: [registration] });
|
|
1315
|
+
continue;
|
|
1316
|
+
}
|
|
1317
|
+
existing.registrations = uniqueRegistrations([...existing.registrations ?? [], registration]);
|
|
1318
|
+
existing.score = Math.max(existing.score, row.score);
|
|
1319
|
+
existing.accepted = existing.accepted || row.accepted;
|
|
1320
|
+
existing.acceptedReasons = [.../* @__PURE__ */ new Set([...existing.acceptedReasons, ...row.acceptedReasons])];
|
|
1321
|
+
existing.rejectedReasons = [.../* @__PURE__ */ new Set([...existing.rejectedReasons, ...row.rejectedReasons])];
|
|
1322
|
+
}
|
|
1323
|
+
return [...merged.values()];
|
|
1324
|
+
}
|
|
1325
|
+
function uniqueRegistrations(rows2) {
|
|
1326
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1327
|
+
return rows2.filter((row) => {
|
|
1328
|
+
const key = JSON.stringify(row);
|
|
1329
|
+
if (seen.has(key)) return false;
|
|
1330
|
+
seen.add(key);
|
|
1331
|
+
return true;
|
|
1332
|
+
});
|
|
1263
1333
|
}
|
|
1264
1334
|
function implementationCandidates(db, workspaceId, operation) {
|
|
1265
1335
|
const modelRepoGraphId = graphId(operation.modelRepoId);
|
|
@@ -1396,6 +1466,7 @@ function candidateEvidence(candidate, rank) {
|
|
|
1396
1466
|
sourceFile: candidate.sourceFile,
|
|
1397
1467
|
sourceLine: candidate.sourceLine,
|
|
1398
1468
|
registration: { file: candidate.registrationFile, line: candidate.registrationLine, kind: candidate.registrationKind, importSource: candidate.importSource },
|
|
1469
|
+
registrations: candidate.registrations ?? [],
|
|
1399
1470
|
applicationPackage: { id: candidate.applicationRepoId, name: candidate.applicationRepo, packageName: candidate.applicationPackage },
|
|
1400
1471
|
handlerPackage: { id: candidate.handlerRepoId, name: candidate.handlerRepo, packageName: candidate.handlerPackage },
|
|
1401
1472
|
modelPackage: { id: candidate.modelRepoId, name: candidate.modelRepo, packageName: candidate.modelPackage },
|
|
@@ -1437,8 +1508,8 @@ function sourceFilesForStart(db, repoId, start) {
|
|
|
1437
1508
|
const operation = normalizeOperation(start.operation ?? start.operationPath);
|
|
1438
1509
|
if (!handler && !operation) return void 0;
|
|
1439
1510
|
const rows2 = db.prepare(
|
|
1440
|
-
`SELECT DISTINCT hc.source_file sourceFile
|
|
1441
|
-
FROM handler_classes hc LEFT JOIN handler_methods hm ON hm.handler_class_id=hc.id
|
|
1511
|
+
`SELECT DISTINCT hc.source_file sourceFile,s.id symbolId
|
|
1512
|
+
FROM handler_classes hc LEFT JOIN handler_methods hm ON hm.handler_class_id=hc.id LEFT JOIN symbols s ON s.repo_id=hc.repo_id AND s.source_file=hc.source_file AND s.name=hm.method_name
|
|
1442
1513
|
WHERE (? IS NULL OR hc.repo_id=?) AND (? IS NULL OR hc.class_name=? OR hm.method_name=?)
|
|
1443
1514
|
AND (? IS NULL OR hm.decorator_value=? OR hm.method_name=?)
|
|
1444
1515
|
AND (? IS NULL OR EXISTS (SELECT 1 FROM cds_services s JOIN cds_operations o ON o.service_id=s.id WHERE s.repo_id=hc.repo_id AND s.service_path=? AND (? IS NULL OR o.operation_path=? OR o.operation_name=? OR hm.decorator_value=? OR hm.method_name=?)))`
|
|
@@ -1459,15 +1530,16 @@ function sourceFilesForStart(db, repoId, start) {
|
|
|
1459
1530
|
operation,
|
|
1460
1531
|
operation
|
|
1461
1532
|
);
|
|
1462
|
-
if (rows2.length > 0) return new Set(rows2.map((row) => row.sourceFile).filter(Boolean));
|
|
1533
|
+
if (rows2.length > 0) return { files: new Set(rows2.map((row) => row.sourceFile).filter(Boolean)), symbols: new Set(rows2.map((row) => Number(row.symbolId)).filter(Boolean)) };
|
|
1463
1534
|
if (start.servicePath && operation) {
|
|
1464
|
-
const implRows = db.prepare(`SELECT DISTINCT hc.source_file sourceFile
|
|
1535
|
+
const implRows = db.prepare(`SELECT DISTINCT hc.source_file sourceFile,sym.id symbolId
|
|
1465
1536
|
FROM cds_services s JOIN cds_operations o ON o.service_id=s.id
|
|
1466
1537
|
JOIN graph_edges e ON e.edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND e.status='resolved' AND e.from_kind='operation' AND e.from_id=CAST(o.id AS TEXT)
|
|
1467
1538
|
JOIN handler_methods hm ON hm.id=CAST(e.to_id AS INTEGER)
|
|
1468
1539
|
JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
1540
|
+
LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id AND sym.source_file=hc.source_file AND sym.name=hm.method_name
|
|
1469
1541
|
WHERE (? IS NULL OR s.repo_id=?) AND s.service_path=? AND (o.operation_path=? OR o.operation_name=?)`).all(repoId, repoId, start.servicePath, operation, operation);
|
|
1470
|
-
if (implRows.length > 0) return new Set(implRows.map((row) => row.sourceFile).filter(Boolean));
|
|
1542
|
+
if (implRows.length > 0) return { files: new Set(implRows.map((row) => row.sourceFile).filter(Boolean)), symbols: new Set(implRows.map((row) => Number(row.symbolId)).filter(Boolean)) };
|
|
1471
1543
|
}
|
|
1472
1544
|
return void 0;
|
|
1473
1545
|
}
|
|
@@ -1476,7 +1548,8 @@ function startScope(db, start) {
|
|
|
1476
1548
|
"SELECT id,name FROM repositories WHERE name=? OR package_name=?"
|
|
1477
1549
|
).get(start.repo, start.repo) : void 0;
|
|
1478
1550
|
if (start.repo && !repo) return { repo, selectorMatched: false };
|
|
1479
|
-
const
|
|
1551
|
+
const sourceScope = sourceFilesForStart(db, repo?.id, start);
|
|
1552
|
+
const sourceFiles = sourceScope?.files;
|
|
1480
1553
|
const hasSelector = Boolean(
|
|
1481
1554
|
start.handler ?? start.operation ?? start.operationPath ?? start.servicePath
|
|
1482
1555
|
);
|
|
@@ -1485,6 +1558,7 @@ function startScope(db, start) {
|
|
|
1485
1558
|
return {
|
|
1486
1559
|
repo,
|
|
1487
1560
|
sourceFiles,
|
|
1561
|
+
symbolIds: sourceScope?.symbols,
|
|
1488
1562
|
selectorMatched: !hasSelector || sourceFiles !== void 0
|
|
1489
1563
|
};
|
|
1490
1564
|
}
|
|
@@ -1496,8 +1570,9 @@ function handlerFilesForOperation(db, operationId) {
|
|
|
1496
1570
|
if (!op) return /* @__PURE__ */ new Set();
|
|
1497
1571
|
const operation = normalizeOperation(op.operationPath ?? op.operationName);
|
|
1498
1572
|
const rows2 = db.prepare(
|
|
1499
|
-
`SELECT DISTINCT hc.source_file sourceFile FROM handler_classes hc
|
|
1573
|
+
`SELECT DISTINCT hc.source_file sourceFile,sym.id symbolId FROM handler_classes hc
|
|
1500
1574
|
JOIN handler_methods hm ON hm.handler_class_id=hc.id
|
|
1575
|
+
LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id AND sym.source_file=hc.source_file AND sym.name=hm.method_name
|
|
1501
1576
|
WHERE hc.repo_id=? AND (hm.decorator_value=? OR hm.method_name=? OR hm.decorator_value=?)`
|
|
1502
1577
|
).all(op.repoId, operation, operation, op.operationName);
|
|
1503
1578
|
return new Set(rows2.map((row) => row.sourceFile).filter(Boolean));
|
|
@@ -1513,8 +1588,8 @@ function handlerMethodNode(db, methodId) {
|
|
|
1513
1588
|
function implementationScope(db, operationId) {
|
|
1514
1589
|
const edge = implementationEdge(db, operationId);
|
|
1515
1590
|
if (!edge || edge.status !== "resolved") return { files: /* @__PURE__ */ new Set(), edge };
|
|
1516
|
-
const row = db.prepare("SELECT hc.repo_id repoId,hc.source_file sourceFile FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id WHERE hm.id=?").get(edge.to_id);
|
|
1517
|
-
return { repoId: row?.repoId, files: new Set(row?.sourceFile ? [row.sourceFile] : []), edge };
|
|
1591
|
+
const row = db.prepare("SELECT hc.repo_id repoId,hc.source_file sourceFile,s.id symbolId FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id LEFT JOIN symbols s ON s.repo_id=hc.repo_id AND s.source_file=hc.source_file AND s.name=hm.method_name WHERE hm.id=?").get(edge.to_id);
|
|
1592
|
+
return { repoId: row?.repoId, files: new Set(row?.sourceFile ? [row.sourceFile] : []), symbolId: row?.symbolId, edge };
|
|
1518
1593
|
}
|
|
1519
1594
|
function includeCall(type, options) {
|
|
1520
1595
|
if (!options.includeDb && type === "local_db_query") return false;
|
|
@@ -1612,10 +1687,12 @@ function trace(db, start, options) {
|
|
|
1612
1687
|
const maxDepth = positiveDepth(options.depth);
|
|
1613
1688
|
const edges = [];
|
|
1614
1689
|
const nodes = /* @__PURE__ */ new Map();
|
|
1615
|
-
const queue = scope.selectorMatched ? [{ repoId: scope.repo?.id, files: scope.sourceFiles, depth: 1 }] : [];
|
|
1690
|
+
const queue = scope.selectorMatched ? [{ repoId: scope.repo?.id, files: scope.sourceFiles, symbolIds: scope.symbolIds, depth: 1 }] : [];
|
|
1616
1691
|
if (start.servicePath && (start.operation ?? start.operationPath)) {
|
|
1617
1692
|
const startOperation = normalizeOperation(start.operation ?? start.operationPath);
|
|
1618
|
-
const
|
|
1693
|
+
const startRows = db.prepare(`SELECT o.id operationId,r.name repoName,s.service_path servicePath,o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,o.source_line sourceLine FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE (? IS NULL OR s.repo_id=?) AND s.service_path=? AND (o.operation_path=? OR o.operation_name=?) ORDER BY r.name,o.id LIMIT 2`).all(scope.repo?.id, scope.repo?.id, start.servicePath, startOperation, startOperation);
|
|
1694
|
+
if (!scope.repo && startRows.length > 1) diagnostics.unshift({ severity: "warning", code: "trace_start_ambiguous", message: "Service/path trace start matched multiple repositories; add --repo to disambiguate", candidates: startRows });
|
|
1695
|
+
const row = startRows.length === 1 ? startRows[0] : void 0;
|
|
1619
1696
|
if (row?.operationId !== void 0) {
|
|
1620
1697
|
const opId = String(row.operationId);
|
|
1621
1698
|
const op = operationNode(db, opId);
|
|
@@ -1634,15 +1711,28 @@ function trace(db, start, options) {
|
|
|
1634
1711
|
while (queue.length > 0) {
|
|
1635
1712
|
const current = queue.shift();
|
|
1636
1713
|
if (!current || current.depth > maxDepth) continue;
|
|
1637
|
-
const key = `${current.repoId ?? "*"}:${[...current.files ?? /* @__PURE__ */ new Set(["*"])].sort().join(",")}`;
|
|
1714
|
+
const key = `${current.repoId ?? "*"}:${[...current.symbolIds ?? /* @__PURE__ */ new Set(["*"])].sort().join(",")}:${[...current.files ?? /* @__PURE__ */ new Set(["*"])].sort().join(",")}`;
|
|
1638
1715
|
if (seenScopes.has(key)) continue;
|
|
1639
1716
|
seenScopes.add(key);
|
|
1640
1717
|
const calls = db.prepare(
|
|
1641
1718
|
`SELECT c.*,r.name repoName FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id WHERE (? IS NULL OR c.repo_id=?) ORDER BY c.source_file,c.source_line`
|
|
1642
1719
|
).all(current.repoId, current.repoId);
|
|
1643
1720
|
const filtered = calls.filter(
|
|
1644
|
-
(c) => (!current.files || current.files.has(String(c.source_file))) && includeCall(String(c.call_type), options)
|
|
1721
|
+
(c) => (!current.symbolIds || current.symbolIds.has(Number(c.source_symbol_id))) && (!current.files || current.files.has(String(c.source_file))) && includeCall(String(c.call_type), options)
|
|
1645
1722
|
);
|
|
1723
|
+
if (current.symbolIds && current.symbolIds.size > 0 && current.depth < maxDepth) {
|
|
1724
|
+
const symbolRows = db.prepare(`SELECT sc.*,s.repo_id calleeRepoId,s.source_file calleeFile FROM symbol_calls sc LEFT JOIN symbols s ON s.id=sc.callee_symbol_id WHERE sc.caller_symbol_id IN (${[...current.symbolIds].map(() => "?").join(",")}) ORDER BY sc.source_file,sc.source_line`).all(...current.symbolIds);
|
|
1725
|
+
for (const symbolCall of symbolRows) {
|
|
1726
|
+
if (!symbolCall.callee_symbol_id) continue;
|
|
1727
|
+
const nextSymbols = /* @__PURE__ */ new Set([Number(symbolCall.callee_symbol_id)]);
|
|
1728
|
+
const nextFiles = /* @__PURE__ */ new Set([String(symbolCall.calleeFile)]);
|
|
1729
|
+
const nextRepoId = Number(symbolCall.calleeRepoId);
|
|
1730
|
+
const nextKey = `${nextRepoId}:${[...nextSymbols].join(",")}:${[...nextFiles].join(",")}`;
|
|
1731
|
+
edges.push({ step: current.depth, type: "local_symbol_call", from: String(symbolCall.callee_expression), to: `symbol:${String(symbolCall.callee_symbol_id)}`, evidence: JSON.parse(String(symbolCall.evidence_json || "{}")), confidence: Number(symbolCall.confidence ?? 0.8), unresolvedReason: symbolCall.unresolved_reason ? String(symbolCall.unresolved_reason) : void 0 });
|
|
1732
|
+
if (seenScopes.has(nextKey)) edges.push({ step: current.depth, type: "cycle", from: String(symbolCall.callee_expression), to: nextKey, evidence: { cycle: true, symbolCallId: symbolCall.id }, confidence: 1, unresolvedReason: "Cycle detected; downstream symbol already visited" });
|
|
1733
|
+
else queue.push({ repoId: nextRepoId, files: nextFiles, symbolIds: nextSymbols, depth: current.depth + 1 });
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1646
1736
|
const graph = graphForCalls(
|
|
1647
1737
|
db,
|
|
1648
1738
|
filtered.map((c) => Number(c.id))
|
|
@@ -1678,7 +1768,7 @@ function trace(db, start, options) {
|
|
|
1678
1768
|
edges.push({
|
|
1679
1769
|
step: current.depth,
|
|
1680
1770
|
type: String(call.call_type),
|
|
1681
|
-
from: `${call.repoName}:${call.source_file}`,
|
|
1771
|
+
from: `${call.repoName}:${call.source_file}:${call.source_line}`,
|
|
1682
1772
|
to,
|
|
1683
1773
|
evidence,
|
|
1684
1774
|
confidence: Number(effectiveRow.confidence ?? call.confidence),
|
|
@@ -1692,7 +1782,7 @@ function trace(db, start, options) {
|
|
|
1692
1782
|
const implTo = handlerNode?.label ? String(handlerNode.label) : `${implementation.edge.to_kind}:${implementation.edge.to_id}`;
|
|
1693
1783
|
if (handlerNode) nodes.set(String(handlerNode.id), handlerNode);
|
|
1694
1784
|
edges.push({
|
|
1695
|
-
step: current.depth
|
|
1785
|
+
step: current.depth,
|
|
1696
1786
|
type: "operation_implemented_by_handler",
|
|
1697
1787
|
from: to,
|
|
1698
1788
|
to: implTo,
|
|
@@ -1703,14 +1793,15 @@ function trace(db, start, options) {
|
|
|
1703
1793
|
}
|
|
1704
1794
|
if (current.depth >= maxDepth) continue;
|
|
1705
1795
|
const files = implementation.files.size > 0 ? implementation.files : handlerFilesForOperation(db, effectiveRow.to_id);
|
|
1796
|
+
const symbolIds = implementation.symbolId ? /* @__PURE__ */ new Set([implementation.symbolId]) : void 0;
|
|
1706
1797
|
if (implementation.edge?.status === "resolved" && files.size > 0) {
|
|
1707
1798
|
const targetRepoId = implementation.repoId ?? db.prepare(
|
|
1708
1799
|
"SELECT s.repo_id repoId FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE o.id=?"
|
|
1709
1800
|
).get(effectiveRow.to_id)?.repoId;
|
|
1710
|
-
const nextKey = `${targetRepoId ?? "*"}:${[...files].sort().join(",")}`;
|
|
1801
|
+
const nextKey = `${targetRepoId ?? "*"}:${[...symbolIds ?? /* @__PURE__ */ new Set(["*"])].sort().join(",")}:${[...files].sort().join(",")}`;
|
|
1711
1802
|
if (seenScopes.has(nextKey))
|
|
1712
1803
|
edges.push({
|
|
1713
|
-
step: current.depth
|
|
1804
|
+
step: current.depth,
|
|
1714
1805
|
type: "cycle",
|
|
1715
1806
|
from: to,
|
|
1716
1807
|
to: nextKey,
|
|
@@ -1722,6 +1813,7 @@ function trace(db, start, options) {
|
|
|
1722
1813
|
queue.push({
|
|
1723
1814
|
repoId: targetRepoId,
|
|
1724
1815
|
files,
|
|
1816
|
+
symbolIds,
|
|
1725
1817
|
depth: current.depth + 1
|
|
1726
1818
|
});
|
|
1727
1819
|
}
|
|
@@ -1750,4 +1842,4 @@ export {
|
|
|
1750
1842
|
linkWorkspace,
|
|
1751
1843
|
trace
|
|
1752
1844
|
};
|
|
1753
|
-
//# sourceMappingURL=chunk-
|
|
1845
|
+
//# sourceMappingURL=chunk-GG4XJGES.js.map
|