@saptools/service-flow 0.1.44 → 0.1.45
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 +7 -0
- package/README.md +9 -1
- package/dist/{chunk-UKNPHTUS.js → chunk-BXSCE5CR.js} +1790 -1460
- package/dist/chunk-BXSCE5CR.js.map +1 -0
- package/dist/cli.js +73 -29
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/doctor.ts +49 -12
- package/src/cli.ts +10 -2
- package/src/index.ts +2 -0
- package/src/linker/cross-repo-linker.ts +23 -1
- package/src/linker/odata-path-normalizer.ts +4 -1
- package/src/parsers/imported-wrapper-parser.ts +262 -0
- package/src/parsers/outbound-call-parser.ts +5 -1
- package/src/parsers/symbol-parser.ts +25 -10
- package/src/trace/evidence.ts +6 -1
- package/src/trace/implementation-hints.ts +151 -0
- package/src/trace/trace-engine.ts +52 -45
- package/src/types.ts +8 -0
- package/dist/chunk-UKNPHTUS.js.map +0 -1
|
@@ -577,1518 +577,1716 @@ function summarizeExpression(text) {
|
|
|
577
577
|
return redactText(text).slice(0, 240);
|
|
578
578
|
}
|
|
579
579
|
|
|
580
|
-
// src/parsers/
|
|
580
|
+
// src/parsers/service-binding-parser.ts
|
|
581
|
+
import path8 from "path";
|
|
582
|
+
import ts5 from "typescript";
|
|
583
|
+
|
|
584
|
+
// src/parsers/service-binding-parser-helpers.ts
|
|
581
585
|
import fs6 from "fs/promises";
|
|
582
586
|
import path7 from "path";
|
|
583
587
|
import ts4 from "typescript";
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
import { createHash } from "crypto";
|
|
587
|
-
var sensitiveKeys = /* @__PURE__ */ new Set(["token", "access_token", "id_token", "api_key", "apikey", "key", "password", "passwd", "pwd", "secret", "client_secret", "authorization", "cookie", "signature"]);
|
|
588
|
-
function hash(value) {
|
|
589
|
-
return createHash("sha256").update(value).digest("hex").slice(0, 12);
|
|
588
|
+
function lineOf3(sf, node) {
|
|
589
|
+
return sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
590
590
|
}
|
|
591
|
-
function
|
|
592
|
-
|
|
591
|
+
function stringValue(node) {
|
|
592
|
+
if (!node) return void 0;
|
|
593
|
+
if (ts4.isStringLiteralLike(node) || ts4.isNoSubstitutionTemplateLiteral(node)) return node.text;
|
|
594
|
+
if (ts4.isTemplateExpression(node)) return node.getText().replace(/^`|`$/g, "");
|
|
595
|
+
return node.getText();
|
|
593
596
|
}
|
|
594
|
-
function
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
597
|
+
function placeholders(value) {
|
|
598
|
+
return [...(value ?? "").matchAll(/\$\{([^}]*)\}/g)].map((m) => (m[1] ?? "").trim()).filter(Boolean);
|
|
599
|
+
}
|
|
600
|
+
function connectFactFromCall(call) {
|
|
601
|
+
const expr = call.expression;
|
|
602
|
+
if (!ts4.isPropertyAccessExpression(expr) || expr.name.text !== "to") return void 0;
|
|
603
|
+
const inner = expr.expression;
|
|
604
|
+
if (!ts4.isPropertyAccessExpression(inner) || inner.name.text !== "connect" || inner.expression.getText() !== "cds") return void 0;
|
|
605
|
+
const first = call.arguments[0];
|
|
606
|
+
if (!first) return void 0;
|
|
607
|
+
const second = call.arguments[1];
|
|
608
|
+
const objectArg = ts4.isObjectLiteralExpression(first) ? first : second && ts4.isObjectLiteralExpression(second) ? second : void 0;
|
|
609
|
+
let alias;
|
|
610
|
+
let aliasExpr;
|
|
611
|
+
if (ts4.isStringLiteralLike(first) || ts4.isNoSubstitutionTemplateLiteral(first)) alias = first.text;
|
|
612
|
+
else if (!ts4.isObjectLiteralExpression(first)) aliasExpr = stringValue(first);
|
|
613
|
+
if ((ts4.isStringLiteralLike(first) || ts4.isNoSubstitutionTemplateLiteral(first)) && !objectArg) return { alias: first.text, isDynamic: false, placeholders: [] };
|
|
614
|
+
if (!objectArg && aliasExpr) return { aliasExpr, isDynamic: true, placeholders: placeholders(aliasExpr) };
|
|
615
|
+
const expressions = objectArg ? objectExpressions(objectArg) : {};
|
|
616
|
+
const ph = [...placeholders(aliasExpr ?? alias), ...placeholders(expressions.destinationExpr), ...placeholders(expressions.servicePathExpr)];
|
|
617
|
+
return { alias, aliasExpr, ...expressions, isDynamic: ph.length > 0 || !expressions.destinationExpr && !expressions.servicePathExpr, placeholders: ph };
|
|
618
|
+
}
|
|
619
|
+
function objectExpressions(objectArg) {
|
|
620
|
+
const out = {};
|
|
621
|
+
function visitObject(obj) {
|
|
622
|
+
for (const prop of obj.properties) {
|
|
623
|
+
if (!ts4.isPropertyAssignment(prop)) continue;
|
|
624
|
+
const name = ts4.isIdentifier(prop.name) || ts4.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
|
|
625
|
+
if (name === "destination") out.destinationExpr = stringValue(prop.initializer);
|
|
626
|
+
if (name === "path" || name === "servicePath") out.servicePathExpr = stringValue(prop.initializer);
|
|
627
|
+
if (ts4.isObjectLiteralExpression(prop.initializer)) visitObject(prop.initializer);
|
|
628
|
+
}
|
|
604
629
|
}
|
|
630
|
+
visitObject(objectArg);
|
|
631
|
+
return out;
|
|
605
632
|
}
|
|
606
|
-
function
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
633
|
+
function unwrapCall(expr) {
|
|
634
|
+
if (ts4.isAwaitExpression(expr)) return unwrapCall(expr.expression);
|
|
635
|
+
if (ts4.isParenthesizedExpression(expr)) return unwrapCall(expr.expression);
|
|
636
|
+
if (ts4.isAsExpression(expr) || ts4.isSatisfiesExpression(expr)) return unwrapCall(expr.expression);
|
|
637
|
+
if (ts4.isTypeAssertionExpression(expr)) return unwrapCall(expr.expression);
|
|
638
|
+
if (ts4.isCallExpression(expr)) return expr;
|
|
639
|
+
return void 0;
|
|
640
|
+
}
|
|
641
|
+
function unwrapIdentityExpression(expr) {
|
|
642
|
+
if (ts4.isAwaitExpression(expr)) return unwrapIdentityExpression(expr.expression);
|
|
643
|
+
if (ts4.isParenthesizedExpression(expr)) return unwrapIdentityExpression(expr.expression);
|
|
644
|
+
if (ts4.isAsExpression(expr) || ts4.isSatisfiesExpression(expr)) return unwrapIdentityExpression(expr.expression);
|
|
645
|
+
if (ts4.isTypeAssertionExpression(expr)) return unwrapIdentityExpression(expr.expression);
|
|
646
|
+
return expr;
|
|
647
|
+
}
|
|
648
|
+
function transactionReceiverName(expr) {
|
|
649
|
+
const call = unwrapCall(expr);
|
|
650
|
+
if (call && ts4.isPropertyAccessExpression(call.expression) && ["tx", "transaction"].includes(call.expression.name.text) && ts4.isIdentifier(call.expression.expression)) return call.expression.expression.text;
|
|
651
|
+
const unwrapped = unwrapIdentityExpression(expr);
|
|
652
|
+
if (!ts4.isConditionalExpression(unwrapped)) return void 0;
|
|
653
|
+
const left = transactionReceiverName(unwrapped.whenTrue);
|
|
654
|
+
const right = transactionReceiverName(unwrapped.whenFalse);
|
|
655
|
+
return left && left === right ? left : void 0;
|
|
656
|
+
}
|
|
657
|
+
function findConnectInExpression(expr) {
|
|
658
|
+
const direct = unwrapCall(expr);
|
|
659
|
+
if (direct) {
|
|
660
|
+
const fact = connectFactFromCall(direct);
|
|
661
|
+
if (fact) return fact;
|
|
616
662
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
663
|
+
let found;
|
|
664
|
+
function visit(node) {
|
|
665
|
+
if (found) return;
|
|
666
|
+
if (ts4.isCallExpression(node)) found = connectFactFromCall(node);
|
|
667
|
+
if (!found) ts4.forEachChild(node, visit);
|
|
621
668
|
}
|
|
622
|
-
|
|
623
|
-
return
|
|
669
|
+
visit(expr);
|
|
670
|
+
return found;
|
|
624
671
|
}
|
|
625
|
-
function
|
|
672
|
+
async function readSource(abs) {
|
|
626
673
|
try {
|
|
627
|
-
const
|
|
628
|
-
return
|
|
674
|
+
const text = await fs6.readFile(abs, "utf8");
|
|
675
|
+
return ts4.createSourceFile(abs, text, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TS);
|
|
629
676
|
} catch {
|
|
630
|
-
return
|
|
677
|
+
return void 0;
|
|
631
678
|
}
|
|
632
679
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
if (query >= 0) return rejected("query_string_paths_are_not_operation_invocations");
|
|
644
|
-
if (!raw.startsWith("/")) return rejected("path_is_not_absolute");
|
|
645
|
-
if (raw.slice(1, open).includes("/")) return rejected("operation_segment_contains_navigation_separator");
|
|
646
|
-
const close = matchingClose(raw, open);
|
|
647
|
-
if (close === void 0) return rejected("top_level_invocation_parenthesis_is_unbalanced");
|
|
648
|
-
if (raw.slice(close + 1).trim().length > 0) return rejected("top_level_invocation_does_not_cover_remaining_path");
|
|
649
|
-
const operationSegment = raw.slice(0, open).trim();
|
|
650
|
-
if (operationSegment.length <= 1) return rejected("operation_segment_is_empty");
|
|
651
|
-
return {
|
|
652
|
-
rawOperationPath: raw,
|
|
653
|
-
normalizedOperationPath: operationSegment,
|
|
654
|
-
wasInvocation: true,
|
|
655
|
-
invocationArgumentPlaceholderKeys: [...new Set(extractTemplatePlaceholders(raw.slice(open + 1, close)))],
|
|
656
|
-
normalizationReason: "balanced_top_level_operation_invocation"
|
|
657
|
-
};
|
|
680
|
+
async function resolveImport(repoPath, fromFile, spec) {
|
|
681
|
+
if (!spec.startsWith(".")) return void 0;
|
|
682
|
+
const rawBase = path7.resolve(repoPath, path7.dirname(fromFile), spec);
|
|
683
|
+
const parsed = path7.parse(rawBase);
|
|
684
|
+
const base = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"].includes(parsed.ext) ? path7.join(parsed.dir, parsed.name) : rawBase;
|
|
685
|
+
for (const candidate of [base, `${base}.ts`, `${base}.js`, path7.join(base, "index.ts"), path7.join(base, "index.js")]) {
|
|
686
|
+
const stat = await fs6.stat(candidate).catch(() => void 0);
|
|
687
|
+
if (stat?.isFile()) return normalizePath(path7.relative(repoPath, candidate));
|
|
688
|
+
}
|
|
689
|
+
return void 0;
|
|
658
690
|
}
|
|
659
|
-
function
|
|
660
|
-
const
|
|
661
|
-
const
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
const firstOpen = firstSegment.indexOf("(");
|
|
671
|
-
const firstClose = firstOpen >= 0 ? matchingClose(firstSegment, firstOpen) : void 0;
|
|
672
|
-
const keyPredicateText = firstOpen >= 0 && firstClose !== void 0 ? firstSegment.slice(firstOpen + 1, firstClose) : "";
|
|
673
|
-
const rawKeyPredicatePlaceholderKeys = [...new Set(extractTemplatePlaceholders(keyPredicateText))];
|
|
674
|
-
const navigationSuffix = hasNavigationSegments ? segments.slice(1).join("/") : void 0;
|
|
675
|
-
const lastSegment = segments.at(-1) ?? "";
|
|
676
|
-
const mediaOrPropertySuffix = hasNavigationSegments ? lastSegment : void 0;
|
|
677
|
-
const invocation = normalizeODataOperationInvocationPath(pathWithoutQuery);
|
|
678
|
-
const operationNameFromInvocation = invocation?.wasInvocation ? invocation.normalizedOperationPath.replace(/^\//, "").split(".").at(-1) : void 0;
|
|
679
|
-
const topLevelName = firstSegment.split("(")[0]?.replace(/^\//, "");
|
|
680
|
-
const topLevelOperationName = operationNameFromInvocation ?? (!hasNavigationSegments && !firstSegment.includes("(") ? topLevelName : void 0);
|
|
681
|
-
const topLevelOperationInvocation = Boolean(invocation?.wasInvocation && looksLikeLowerCamelInvocation(firstSegment));
|
|
682
|
-
const keyPredicatePlaceholderKeys = topLevelOperationInvocation ? [] : rawKeyPredicatePlaceholderKeys;
|
|
683
|
-
const topLevelOperationNameCandidate = Boolean(topLevelOperationName && !hasNavigationSegments && !firstSegment.includes("("));
|
|
684
|
-
const base = { rawPath, method: normalizedMethod, pathWithoutQuery, queryString, hasQueryString: queryIndex >= 0, entitySegment, placeholderKeys, keyPredicatePlaceholderKeys, invocationArgumentPlaceholderKeys: invocation?.invocationArgumentPlaceholderKeys ?? [], navigationSuffix, mediaOrPropertySuffix, hasEntityKeyPredicate: firstOpen >= 0 && firstClose !== void 0, hasNavigationSuffix: hasNavigationSegments, hasMediaOrPropertySuffix: Boolean(mediaOrPropertySuffix && isMediaOrPropertySuffix(mediaOrPropertySuffix)), topLevelOperationName, topLevelOperationNameCandidate, topLevelOperationInvocation };
|
|
685
|
-
if (!rawPath || !rawPath.startsWith("/")) return { ...base, kind: "unknown", reason: "path_missing_or_not_absolute" };
|
|
686
|
-
const upperEntityLike = /^[A-Z][A-Za-z0-9_]*$/.test(entitySegment ?? firstSegment);
|
|
687
|
-
const mediaLike = isMediaOrPropertySuffix(segments.at(-1) ?? "");
|
|
688
|
-
if (normalizedMethod !== "GET") {
|
|
689
|
-
if (invocation?.wasInvocation && looksLikeLowerCamelInvocation(firstSegment)) return { ...base, kind: "operation_invocation", reason: "non_get_balanced_top_level_operation_invocation" };
|
|
690
|
-
if (mediaLike) return { ...base, kind: "entity_media", reason: "non_get_entity_media_stream_path" };
|
|
691
|
-
if (hasNavigationSegments || firstSegment.includes("(")) return { ...base, kind: normalizedMethod === "DELETE" ? "entity_delete" : "entity_mutation", reason: firstSegment.includes("(") ? "non_get_entity_key_or_navigation_path_shape" : "non_get_entity_navigation_path_shape" };
|
|
692
|
-
if (upperEntityLike) return { ...base, kind: normalizedMethod === "DELETE" ? "entity_delete" : "entity_mutation", reason: "non_get_entity_path_shape" };
|
|
693
|
-
return { ...base, kind: "operation_invocation", reason: "non_get_lowercase_path_may_be_operation" };
|
|
691
|
+
async function importsFor(repoPath, filePath, sf) {
|
|
692
|
+
const imports = [];
|
|
693
|
+
for (const stmt of sf.statements) {
|
|
694
|
+
if (!ts4.isImportDeclaration(stmt) || !ts4.isStringLiteralLike(stmt.moduleSpecifier)) continue;
|
|
695
|
+
const sourceFile = await resolveImport(repoPath, filePath, stmt.moduleSpecifier.text);
|
|
696
|
+
const clause = stmt.importClause;
|
|
697
|
+
if (!clause) continue;
|
|
698
|
+
if (clause.name) imports.push({ localName: clause.name.text, exportedName: "default", sourceFile });
|
|
699
|
+
const bindings = clause.namedBindings;
|
|
700
|
+
if (bindings && ts4.isNamedImports(bindings))
|
|
701
|
+
for (const el of bindings.elements) imports.push({ localName: el.name.text, exportedName: el.propertyName?.text ?? el.name.text, sourceFile });
|
|
694
702
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
703
|
+
return imports;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// src/parsers/service-binding-parser.ts
|
|
707
|
+
function collectLocalBindingFacts(fn) {
|
|
708
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
709
|
+
function visit(node) {
|
|
710
|
+
if (node !== fn && (ts5.isFunctionDeclaration(node) || ts5.isArrowFunction(node) || ts5.isFunctionExpression(node)))
|
|
711
|
+
return;
|
|
712
|
+
if (ts5.isVariableDeclaration(node) && ts5.isIdentifier(node.name) && node.initializer) {
|
|
713
|
+
const fact = findConnectInExpression(node.initializer);
|
|
714
|
+
if (fact) bindings.set(node.name.text, fact);
|
|
715
|
+
const sourceName = transactionReceiverName(node.initializer);
|
|
716
|
+
if (sourceName) {
|
|
717
|
+
const source = bindings.get(sourceName);
|
|
718
|
+
if (source) bindings.set(node.name.text, { ...source, helperChain: [...source.helperChain ?? [], { aliasOf: sourceName, callerVariable: node.name.text, aliasKind: "transaction", transactionAliasSource: sourceName }] });
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
ts5.forEachChild(node, visit);
|
|
699
722
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
723
|
+
visit(fn);
|
|
724
|
+
return bindings;
|
|
725
|
+
}
|
|
726
|
+
function collectReturnedObjectBindings(fn) {
|
|
727
|
+
const bindings = collectLocalBindingFacts(fn);
|
|
728
|
+
const returns = /* @__PURE__ */ new Map();
|
|
729
|
+
function visit(node) {
|
|
730
|
+
if (node !== fn && (ts5.isFunctionDeclaration(node) || ts5.isArrowFunction(node) || ts5.isFunctionExpression(node)))
|
|
731
|
+
return;
|
|
732
|
+
if (ts5.isReturnStatement(node) && node.expression && ts5.isObjectLiteralExpression(node.expression)) {
|
|
733
|
+
for (const prop of node.expression.properties) {
|
|
734
|
+
if (ts5.isShorthandPropertyAssignment(prop)) returns.set(prop.name.text, prop.name.text);
|
|
735
|
+
if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.initializer)) {
|
|
736
|
+
const propertyName2 = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
|
|
737
|
+
if (propertyName2) returns.set(propertyName2, prop.initializer.text);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
ts5.forEachChild(node, visit);
|
|
704
742
|
}
|
|
705
|
-
|
|
706
|
-
|
|
743
|
+
visit(fn);
|
|
744
|
+
const out = /* @__PURE__ */ new Map();
|
|
745
|
+
for (const [propertyName2, variableName] of returns) {
|
|
746
|
+
const fact = bindings.get(variableName);
|
|
747
|
+
if (fact) out.set(propertyName2, fact);
|
|
748
|
+
}
|
|
749
|
+
return out;
|
|
707
750
|
}
|
|
708
|
-
function
|
|
709
|
-
|
|
710
|
-
if (
|
|
711
|
-
|
|
712
|
-
const entity = (open >= 0 ? first.slice(0, open) : first).trim();
|
|
713
|
-
return entity || void 0;
|
|
751
|
+
function functionLikeInitializer(expr) {
|
|
752
|
+
if (!expr) return void 0;
|
|
753
|
+
if (ts5.isArrowFunction(expr) || ts5.isFunctionExpression(expr)) return expr;
|
|
754
|
+
return void 0;
|
|
714
755
|
}
|
|
715
|
-
function
|
|
716
|
-
|
|
756
|
+
function directReturnConnectFact(fn) {
|
|
757
|
+
const localBindings = collectLocalBindingFacts(fn);
|
|
758
|
+
let returned;
|
|
759
|
+
function visit(node) {
|
|
760
|
+
if (node !== fn && (ts5.isFunctionDeclaration(node) || ts5.isArrowFunction(node) || ts5.isFunctionExpression(node)))
|
|
761
|
+
return;
|
|
762
|
+
if (!returned && ts5.isReturnStatement(node) && node.expression)
|
|
763
|
+
returned = node.expression;
|
|
764
|
+
if (!returned) ts5.forEachChild(node, visit);
|
|
765
|
+
}
|
|
766
|
+
visit(fn);
|
|
767
|
+
if (!returned) return void 0;
|
|
768
|
+
if (ts5.isIdentifier(returned)) return localBindings.get(returned.text);
|
|
769
|
+
return findConnectInExpression(returned);
|
|
717
770
|
}
|
|
718
|
-
function
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
return /^[a-z][A-Za-z0-9_]*$/.test(name);
|
|
771
|
+
function directConnectFactFromFunctionLike(fn) {
|
|
772
|
+
if (ts5.isArrowFunction(fn) && fn.body && !ts5.isBlock(fn.body))
|
|
773
|
+
return findConnectInExpression(fn.body);
|
|
774
|
+
return directReturnConnectFact(fn);
|
|
723
775
|
}
|
|
724
|
-
function
|
|
725
|
-
const
|
|
726
|
-
for (
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
776
|
+
function exportedLocalNames(sf) {
|
|
777
|
+
const exports = /* @__PURE__ */ new Map();
|
|
778
|
+
for (const stmt of sf.statements) {
|
|
779
|
+
const direct = ts5.canHaveModifiers(stmt) ? ts5.getModifiers(stmt)?.some(
|
|
780
|
+
(m) => m.kind === ts5.SyntaxKind.ExportKeyword
|
|
781
|
+
) ?? false : false;
|
|
782
|
+
if (direct && ts5.isFunctionDeclaration(stmt) && stmt.name)
|
|
783
|
+
exports.set(stmt.name.text, stmt.name.text);
|
|
784
|
+
if (direct && ts5.isVariableStatement(stmt)) {
|
|
785
|
+
for (const decl of stmt.declarationList.declarations)
|
|
786
|
+
if (ts5.isIdentifier(decl.name)) exports.set(decl.name.text, decl.name.text);
|
|
787
|
+
}
|
|
788
|
+
if (!ts5.isExportDeclaration(stmt) || !stmt.exportClause) continue;
|
|
789
|
+
if (!ts5.isNamedExports(stmt.exportClause)) continue;
|
|
790
|
+
for (const el of stmt.exportClause.elements)
|
|
791
|
+
exports.set(el.name.text, el.propertyName?.text ?? el.name.text);
|
|
733
792
|
}
|
|
734
|
-
return
|
|
793
|
+
return exports;
|
|
735
794
|
}
|
|
736
|
-
function
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
}
|
|
750
|
-
if (quote === "single" && char === "'" || quote === "double" && char === '"' || quote === "template" && char === "`") quote = void 0;
|
|
751
|
-
continue;
|
|
752
|
-
}
|
|
753
|
-
if (char === "$" && text[index + 1] === "{") {
|
|
754
|
-
const close = matchingPlaceholderClose(text, index + 1);
|
|
755
|
-
if (close === void 0) return void 0;
|
|
756
|
-
index = close;
|
|
757
|
-
continue;
|
|
758
|
-
}
|
|
759
|
-
if (char === "'") {
|
|
760
|
-
quote = "single";
|
|
761
|
-
continue;
|
|
762
|
-
}
|
|
763
|
-
if (char === '"') {
|
|
764
|
-
quote = "double";
|
|
765
|
-
continue;
|
|
766
|
-
}
|
|
767
|
-
if (char === "`") {
|
|
768
|
-
quote = "template";
|
|
769
|
-
continue;
|
|
770
|
-
}
|
|
771
|
-
if (char === "(") depth += 1;
|
|
772
|
-
if (char === ")") {
|
|
773
|
-
depth -= 1;
|
|
774
|
-
if (depth === 0) return index;
|
|
775
|
-
if (depth < 0) return void 0;
|
|
795
|
+
async function helperBindings(repoPath, filePath) {
|
|
796
|
+
const sf = await readSource(path8.join(repoPath, filePath));
|
|
797
|
+
if (!sf) return [];
|
|
798
|
+
const sourceFileAst = sf;
|
|
799
|
+
const out = [];
|
|
800
|
+
const exportedLocals = exportedLocalNames(sf);
|
|
801
|
+
const factsByLocal = /* @__PURE__ */ new Map();
|
|
802
|
+
for (const stmt of sf.statements) {
|
|
803
|
+
if (ts5.isFunctionDeclaration(stmt) && stmt.name) {
|
|
804
|
+
const fact = directConnectFactFromFunctionLike(stmt);
|
|
805
|
+
if (fact) factsByLocal.set(stmt.name.text, { ...fact, sourceLine: lineOf3(sf, stmt) });
|
|
806
|
+
for (const [returnedProperty, objectFact] of collectReturnedObjectBindings(stmt))
|
|
807
|
+
factsByLocal.set(`${stmt.name.text}#${returnedProperty}`, { ...objectFact, returnedProperty, sourceLine: lineOf3(sf, stmt) });
|
|
776
808
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
809
|
+
if (ts5.isVariableStatement(stmt))
|
|
810
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
811
|
+
if (!ts5.isIdentifier(decl.name) || !decl.initializer) continue;
|
|
812
|
+
const helper = functionLikeInitializer(decl.initializer);
|
|
813
|
+
if (helper) {
|
|
814
|
+
const directReturn = directConnectFactFromFunctionLike(helper);
|
|
815
|
+
if (directReturn)
|
|
816
|
+
factsByLocal.set(decl.name.text, {
|
|
817
|
+
...directReturn,
|
|
818
|
+
sourceLine: lineOf3(sourceFileAst, decl)
|
|
819
|
+
});
|
|
820
|
+
for (const [returnedProperty, objectFact] of collectReturnedObjectBindings(helper))
|
|
821
|
+
factsByLocal.set(`${decl.name.text}#${returnedProperty}`, {
|
|
822
|
+
...objectFact,
|
|
823
|
+
returnedProperty,
|
|
824
|
+
sourceLine: lineOf3(sourceFileAst, decl)
|
|
825
|
+
});
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
const fact = findConnectInExpression(decl.initializer);
|
|
829
|
+
if (fact)
|
|
830
|
+
factsByLocal.set(decl.name.text, {
|
|
831
|
+
...fact,
|
|
832
|
+
sourceLine: lineOf3(sourceFileAst, decl)
|
|
833
|
+
});
|
|
792
834
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
if (
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
depth -= 1;
|
|
811
|
-
if (depth === 0) return index;
|
|
812
|
-
if (depth < 0) return void 0;
|
|
835
|
+
}
|
|
836
|
+
for (const [exportedName, localName] of exportedLocals) {
|
|
837
|
+
const fact = factsByLocal.get(localName);
|
|
838
|
+
if (fact)
|
|
839
|
+
out.push({
|
|
840
|
+
...fact,
|
|
841
|
+
exportedName,
|
|
842
|
+
sourceFile: normalizePath(filePath),
|
|
843
|
+
sourceLine: fact.sourceLine
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
for (const [key, fact] of factsByLocal) {
|
|
847
|
+
const [localName, returnedProperty] = key.split("#");
|
|
848
|
+
if (!returnedProperty) continue;
|
|
849
|
+
for (const [exportedName, exportedLocal] of exportedLocals) {
|
|
850
|
+
if (exportedLocal !== localName) continue;
|
|
851
|
+
out.push({ ...fact, exportedName, returnedProperty, sourceFile: normalizePath(filePath), sourceLine: fact.sourceLine });
|
|
813
852
|
}
|
|
814
853
|
}
|
|
815
|
-
return
|
|
854
|
+
return out;
|
|
816
855
|
}
|
|
817
|
-
function
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
if (
|
|
836
|
-
index = close;
|
|
837
|
-
continue;
|
|
838
|
-
}
|
|
839
|
-
if (char === "'") {
|
|
840
|
-
quote = "single";
|
|
841
|
-
continue;
|
|
842
|
-
}
|
|
843
|
-
if (char === '"') {
|
|
844
|
-
quote = "double";
|
|
845
|
-
continue;
|
|
856
|
+
async function parseServiceBindings(repoPath, filePath) {
|
|
857
|
+
const sf = await readSource(path8.join(repoPath, filePath));
|
|
858
|
+
if (!sf) return [];
|
|
859
|
+
const sourceFileAst = sf;
|
|
860
|
+
const out = [];
|
|
861
|
+
const imports = await importsFor(repoPath, filePath, sf);
|
|
862
|
+
const helperCache = /* @__PURE__ */ new Map();
|
|
863
|
+
const classHelpers = collectClassHelpers(sourceFileAst);
|
|
864
|
+
const localObjectHelpers = /* @__PURE__ */ new Map();
|
|
865
|
+
const localDirectHelpers = /* @__PURE__ */ new Map();
|
|
866
|
+
const objectHelperVariables = /* @__PURE__ */ new Map();
|
|
867
|
+
for (const stmt of sourceFileAst.statements) {
|
|
868
|
+
if (ts5.isFunctionDeclaration(stmt) && stmt.name) {
|
|
869
|
+
const directFact = directConnectFactFromFunctionLike(stmt);
|
|
870
|
+
if (directFact) localDirectHelpers.set(stmt.name.text, { ...directFact, exportedName: stmt.name.text, sourceFile: normalizePath(filePath), sourceLine: lineOf3(sourceFileAst, stmt) });
|
|
871
|
+
const rows2 = [];
|
|
872
|
+
for (const [returnedProperty, fact] of collectReturnedObjectBindings(stmt))
|
|
873
|
+
rows2.push({ ...fact, exportedName: stmt.name.text, returnedProperty, sourceFile: normalizePath(filePath), sourceLine: lineOf3(sourceFileAst, stmt) });
|
|
874
|
+
if (rows2.length > 0) localObjectHelpers.set(stmt.name.text, rows2);
|
|
846
875
|
}
|
|
847
|
-
if (
|
|
848
|
-
|
|
849
|
-
|
|
876
|
+
if (ts5.isVariableStatement(stmt)) {
|
|
877
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
878
|
+
if (!ts5.isIdentifier(decl.name)) continue;
|
|
879
|
+
const helper = functionLikeInitializer(decl.initializer);
|
|
880
|
+
if (!helper) continue;
|
|
881
|
+
const directFact = directConnectFactFromFunctionLike(helper);
|
|
882
|
+
if (directFact) localDirectHelpers.set(decl.name.text, { ...directFact, exportedName: decl.name.text, sourceFile: normalizePath(filePath), sourceLine: lineOf3(sourceFileAst, decl) });
|
|
883
|
+
const rows2 = [];
|
|
884
|
+
for (const [returnedProperty, fact] of collectReturnedObjectBindings(helper))
|
|
885
|
+
rows2.push({ ...fact, exportedName: decl.name.text, returnedProperty, sourceFile: normalizePath(filePath), sourceLine: lineOf3(sourceFileAst, decl) });
|
|
886
|
+
if (rows2.length > 0) localObjectHelpers.set(decl.name.text, rows2);
|
|
887
|
+
}
|
|
850
888
|
}
|
|
851
|
-
if (char === "?") return index;
|
|
852
889
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
if (ts4.isIdentifier(expr) || ts4.isStringLiteral(expr) || ts4.isNoSubstitutionTemplateLiteral(expr)) return expr.text;
|
|
863
|
-
if (ts4.isPropertyAccessExpression(expr) && expr.expression.kind === ts4.SyntaxKind.ThisKeyword) return expr.name.text;
|
|
864
|
-
if (ts4.isElementAccessExpression(expr) && expr.argumentExpression && (ts4.isStringLiteral(expr.argumentExpression) || ts4.isNoSubstitutionTemplateLiteral(expr.argumentExpression))) return expr.argumentExpression.text;
|
|
865
|
-
return void 0;
|
|
866
|
-
}
|
|
867
|
-
function expressionName(expr) {
|
|
868
|
-
if (ts4.isIdentifier(expr)) return expr.text;
|
|
869
|
-
if (ts4.isPropertyAccessExpression(expr)) return `${expressionName(expr.expression)}.${expr.name.text}`;
|
|
870
|
-
return expr.getText();
|
|
871
|
-
}
|
|
872
|
-
function variableInitializers(source) {
|
|
873
|
-
const initializers = /* @__PURE__ */ new Map();
|
|
874
|
-
for (const statement of source.statements) {
|
|
875
|
-
if (!ts4.isVariableStatement(statement) || (statement.declarationList.flags & ts4.NodeFlags.Const) === 0) continue;
|
|
876
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
877
|
-
if (ts4.isIdentifier(declaration.name) && declaration.initializer) initializers.set(declaration.name.text, declaration.initializer);
|
|
878
|
-
}
|
|
890
|
+
async function importedHelpers(localName) {
|
|
891
|
+
const imp = imports.find((i) => i.localName === localName && i.sourceFile);
|
|
892
|
+
if (!imp?.sourceFile) return [];
|
|
893
|
+
if (!helperCache.has(imp.sourceFile))
|
|
894
|
+
helperCache.set(
|
|
895
|
+
imp.sourceFile,
|
|
896
|
+
await helperBindings(repoPath, imp.sourceFile)
|
|
897
|
+
);
|
|
898
|
+
return (helperCache.get(imp.sourceFile) ?? []).filter((h) => h.exportedName === imp.exportedName).map((helper) => ({ imp, helper }));
|
|
879
899
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
function queryEntityFromAst(expr, initializers = /* @__PURE__ */ new Map()) {
|
|
883
|
-
if (ts4.isParenthesizedExpression(expr) || ts4.isAwaitExpression(expr)) return queryEntityFromAst(expr.expression, initializers);
|
|
884
|
-
if (ts4.isIdentifier(expr) && initializers.has(expr.text)) return queryEntityFromAst(initializers.get(expr.text), initializers);
|
|
885
|
-
if (ts4.isCallExpression(expr)) {
|
|
886
|
-
const name = expressionName(expr.expression);
|
|
887
|
-
if (name === "cds.run") return queryEntityFromAst(expr.arguments[0], initializers);
|
|
888
|
-
if (["SELECT.one.from", "SELECT.from", "SELECT.one", "INSERT.into", "UPSERT.into", "DELETE.from", "UPDATE.entity"].includes(name)) return entityFromExpression(expr.arguments[0]);
|
|
889
|
-
if (name === "UPDATE") return entityFromExpression(expr.arguments[0]);
|
|
890
|
-
const receiver = ts4.isPropertyAccessExpression(expr.expression) ? expr.expression.expression : void 0;
|
|
891
|
-
if (receiver) return queryEntityFromAst(receiver, initializers);
|
|
900
|
+
async function importedHelper(localName) {
|
|
901
|
+
return (await importedHelpers(localName)).find((row) => !row.helper.returnedProperty);
|
|
892
902
|
}
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
const source = ts4.createSourceFile("query.ts", `const __query = (${expr});`, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TS);
|
|
897
|
-
const initializers = variableInitializers(source);
|
|
898
|
-
let found;
|
|
899
|
-
const visit = (node) => {
|
|
900
|
-
if (found) return;
|
|
901
|
-
if (ts4.isParenthesizedExpression(node)) found = queryEntityFromAst(node.expression, initializers);
|
|
902
|
-
ts4.forEachChild(node, visit);
|
|
903
|
-
};
|
|
904
|
-
visit(source);
|
|
905
|
-
return found;
|
|
906
|
-
}
|
|
907
|
-
function queryWarning(expr) {
|
|
908
|
-
if (/^\s*[`'"]/.test(expr)) return "raw_sql_or_cql_expression";
|
|
909
|
-
if (/^\s*\w+\s*$/.test(expr)) return "query_variable_without_static_initializer";
|
|
910
|
-
return "dynamic_entity_expression";
|
|
911
|
-
}
|
|
912
|
-
function parserEvidence(source, node, extra) {
|
|
913
|
-
return { parser: "typescript_ast", startOffset: node.getStart(source), endOffset: node.getEnd(), ...extra };
|
|
914
|
-
}
|
|
915
|
-
function isStringLike(expr) {
|
|
916
|
-
return Boolean(expr && (ts4.isStringLiteral(expr) || ts4.isNoSubstitutionTemplateLiteral(expr)));
|
|
917
|
-
}
|
|
918
|
-
function literalText(expr) {
|
|
919
|
-
if (isStringLike(expr)) return expr.text;
|
|
920
|
-
return void 0;
|
|
921
|
-
}
|
|
922
|
-
function objectPropertyText(object, key) {
|
|
923
|
-
const prop = object.properties.find(
|
|
924
|
-
(property) => ts4.isPropertyAssignment(property) && nameOfProperty(property.name) === key || ts4.isShorthandPropertyAssignment(property) && property.name.text === key
|
|
925
|
-
);
|
|
926
|
-
if (!prop) return void 0;
|
|
927
|
-
return ts4.isShorthandPropertyAssignment(prop) ? prop.name.text : prop.initializer.getText();
|
|
928
|
-
}
|
|
929
|
-
function objectPropertyIsShorthand(object, key) {
|
|
930
|
-
return object.properties.some((property) => ts4.isShorthandPropertyAssignment(property) && property.name.text === key);
|
|
931
|
-
}
|
|
932
|
-
function nameOfProperty(name) {
|
|
933
|
-
if (ts4.isIdentifier(name) || ts4.isStringLiteral(name) || ts4.isNumericLiteral(name)) return name.text;
|
|
934
|
-
return void 0;
|
|
935
|
-
}
|
|
936
|
-
var maxAliasDepth = 5;
|
|
937
|
-
function safeRaw(expr) {
|
|
938
|
-
if (ts4.isStringLiteral(expr) || ts4.isNoSubstitutionTemplateLiteral(expr) || ts4.isIdentifier(expr) || ts4.isTemplateExpression(expr)) return expr.getText(expr.getSourceFile());
|
|
939
|
-
return void 0;
|
|
940
|
-
}
|
|
941
|
-
function placeholders(expr) {
|
|
942
|
-
return expr.templateSpans.map((span) => span.expression.getText(expr.getSourceFile()));
|
|
943
|
-
}
|
|
944
|
-
function isFunctionLikeScope(node) {
|
|
945
|
-
return ts4.isFunctionLike(node) || ts4.isSourceFile(node);
|
|
946
|
-
}
|
|
947
|
-
function nodeContains(parent, child) {
|
|
948
|
-
const source = child.getSourceFile();
|
|
949
|
-
return child.getStart(source) >= parent.getStart(source) && child.getEnd() <= parent.getEnd();
|
|
950
|
-
}
|
|
951
|
-
function declarationScope(node) {
|
|
952
|
-
if (ts4.isParameter(node)) return node.parent;
|
|
953
|
-
if (ts4.isCatchClause(node.parent) && node.parent.variableDeclaration === node) return node.parent;
|
|
954
|
-
const list = node.parent;
|
|
955
|
-
const blockScoped = (list.flags & (ts4.NodeFlags.Const | ts4.NodeFlags.Let)) !== 0;
|
|
956
|
-
let current = list.parent;
|
|
957
|
-
if (!blockScoped) {
|
|
958
|
-
while (current.parent && !isFunctionLikeScope(current)) current = current.parent;
|
|
959
|
-
return current;
|
|
903
|
+
function bindingForVariable(variableName) {
|
|
904
|
+
const sourceFile = normalizePath(filePath);
|
|
905
|
+
return [...out].reverse().find((row) => row.variableName === variableName && row.sourceFile === sourceFile);
|
|
960
906
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
return ts4.isSourceFile(scope) || nodeContains(scope, use);
|
|
980
|
-
}
|
|
981
|
-
function resolveBinding(identifier, use) {
|
|
982
|
-
const source = use.getSourceFile();
|
|
983
|
-
let best;
|
|
984
|
-
const visit = (node) => {
|
|
985
|
-
if (ts4.isVariableDeclaration(node) && ts4.isIdentifier(node.name) && node.name.text === identifier.text && isAccessibleDeclaration(node, use)) best = node;
|
|
986
|
-
if (ts4.isParameter(node) && ts4.isIdentifier(node.name) && node.name.text === identifier.text && isAccessibleDeclaration(node, use)) best = node;
|
|
987
|
-
ts4.forEachChild(node, visit);
|
|
988
|
-
};
|
|
989
|
-
visit(source);
|
|
990
|
-
if (!best) return { immutable: false, evidence: ["binding_not_found"] };
|
|
991
|
-
const immutable = ts4.isVariableDeclaration(best) && (best.parent.flags & ts4.NodeFlags.Const) !== 0;
|
|
992
|
-
return { declaration: best, initializer: ts4.isVariableDeclaration(best) ? best.initializer : void 0, immutable, evidence: [immutable ? "lexical_const_binding_before_use" : "lexical_mutable_or_parameter_binding"] };
|
|
993
|
-
}
|
|
994
|
-
function resolveExpression(expr, use, policy, depth = 0, seen = /* @__PURE__ */ new Set()) {
|
|
995
|
-
if (!expr) return { status: "unknown", sourceKind: "dynamic_expression", placeholderKeys: [], evidence: ["expression_missing"] };
|
|
996
|
-
if (ts4.isStringLiteral(expr)) return { status: "static", sourceKind: "string_literal", value: expr.text, rawExpression: safeRaw(expr), placeholderKeys: [], evidence: ["string_literal"] };
|
|
997
|
-
if (ts4.isNoSubstitutionTemplateLiteral(expr)) return { status: "static", sourceKind: "no_substitution_template", value: expr.text, rawExpression: safeRaw(expr), placeholderKeys: [], evidence: ["no_substitution_template"] };
|
|
998
|
-
if (ts4.isTemplateExpression(expr)) {
|
|
999
|
-
const keys = placeholders(expr);
|
|
1000
|
-
if (policy === "operation_path") return { status: "dynamic", sourceKind: "template_with_substitutions", value: stripQuotes(expr.getText(expr.getSourceFile())), rawExpression: safeRaw(expr), placeholderKeys: keys, evidence: ["operation_path_template_placeholders_retained"] };
|
|
1001
|
-
return { status: "dynamic", sourceKind: "template_with_substitutions", placeholderKeys: keys, evidence: ["template_substitutions_not_static_external_target"] };
|
|
907
|
+
function cloneAliasBinding(targetName, sourceName, aliasKind, node) {
|
|
908
|
+
const existing = bindingForVariable(sourceName);
|
|
909
|
+
if (!existing) return;
|
|
910
|
+
out.push({
|
|
911
|
+
...existing,
|
|
912
|
+
variableName: targetName,
|
|
913
|
+
sourceLine: lineOf3(sourceFileAst, node),
|
|
914
|
+
helperChain: [
|
|
915
|
+
...existing.helperChain ?? [],
|
|
916
|
+
{
|
|
917
|
+
callerVariable: targetName,
|
|
918
|
+
aliasOf: sourceName,
|
|
919
|
+
aliasKind,
|
|
920
|
+
scopeRule: "same-file-source-order",
|
|
921
|
+
...aliasKind === "transaction" ? { transactionAliasSource: sourceName } : {}
|
|
922
|
+
}
|
|
923
|
+
]
|
|
924
|
+
});
|
|
1002
925
|
}
|
|
1003
|
-
|
|
1004
|
-
if (
|
|
1005
|
-
const
|
|
1006
|
-
if (!
|
|
1007
|
-
|
|
1008
|
-
seen.add(binding.declaration);
|
|
1009
|
-
const resolved2 = resolveExpression(binding.initializer, binding.declaration, policy, depth + 1, seen);
|
|
1010
|
-
return { ...resolved2, sourceKind: "const_alias", rawExpression: safeRaw(expr), constName: expr.text, evidence: [...binding.evidence, ...resolved2.evidence] };
|
|
926
|
+
function recordIdentityAlias(decl) {
|
|
927
|
+
if (!ts5.isIdentifier(decl.name) || !decl.initializer) return;
|
|
928
|
+
const unwrapped = unwrapIdentityExpression(decl.initializer);
|
|
929
|
+
if (!ts5.isIdentifier(unwrapped)) return;
|
|
930
|
+
cloneAliasBinding(decl.name.text, unwrapped.text, "identity", decl);
|
|
1011
931
|
}
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
932
|
+
async function recordBindingFromExpression(targetName, expr, node, aliasKind) {
|
|
933
|
+
const call = unwrapCall(expr);
|
|
934
|
+
if (!call) return;
|
|
935
|
+
const direct = connectFactFromCall(call);
|
|
936
|
+
if (direct)
|
|
937
|
+
out.push({
|
|
938
|
+
variableName: targetName,
|
|
939
|
+
...direct,
|
|
940
|
+
sourceFile: normalizePath(filePath),
|
|
941
|
+
sourceLine: lineOf3(sourceFileAst, node),
|
|
942
|
+
helperChain: aliasKind === "assignment" ? [{ callerVariable: targetName, assignedFrom: call.expression.getText(sourceFileAst), aliasKind, scopeRule: "same-file-source-order" }] : void 0
|
|
943
|
+
});
|
|
944
|
+
else if (ts5.isIdentifier(call.expression)) {
|
|
945
|
+
const localDirect = localDirectHelpers.get(call.expression.text);
|
|
946
|
+
const resolved2 = localDirect ? { helper: localDirect, imp: void 0 } : await importedHelper(call.expression.text);
|
|
947
|
+
if (resolved2)
|
|
948
|
+
out.push({
|
|
949
|
+
variableName: targetName,
|
|
950
|
+
alias: resolved2.helper.alias,
|
|
951
|
+
aliasExpr: resolved2.helper.aliasExpr,
|
|
952
|
+
destinationExpr: resolved2.helper.destinationExpr,
|
|
953
|
+
servicePathExpr: resolved2.helper.servicePathExpr,
|
|
954
|
+
isDynamic: resolved2.helper.isDynamic,
|
|
955
|
+
placeholders: resolved2.helper.placeholders,
|
|
956
|
+
sourceFile: normalizePath(filePath),
|
|
957
|
+
sourceLine: lineOf3(sourceFileAst, node),
|
|
958
|
+
helperChain: [
|
|
959
|
+
...resolved2.helper.helperChain ?? [],
|
|
960
|
+
{
|
|
961
|
+
callerVariable: targetName,
|
|
962
|
+
...aliasKind === "assignment" ? { assignedFrom: call.expression.text, aliasKind, scopeRule: "same-file-source-order" } : {},
|
|
963
|
+
importedHelper: call.expression.text,
|
|
964
|
+
importSource: resolved2.imp?.sourceFile,
|
|
965
|
+
exportedSymbol: resolved2.imp?.exportedName ?? resolved2.helper.exportedName,
|
|
966
|
+
helperSourceFile: resolved2.helper.sourceFile,
|
|
967
|
+
helperSourceLine: resolved2.helper.sourceLine
|
|
968
|
+
}
|
|
969
|
+
]
|
|
970
|
+
});
|
|
1047
971
|
}
|
|
1048
|
-
ts4.forEachChild(node, visit);
|
|
1049
|
-
};
|
|
1050
|
-
visit(source);
|
|
1051
|
-
const candidatePaths = [...new Set(paths)];
|
|
1052
|
-
if (candidatePaths.length === 0) return void 0;
|
|
1053
|
-
const normalizedCandidateOperations = [...new Set(candidatePaths.map((candidate) => classifyODataPathIntent(candidate, method).topLevelOperationName).filter((value) => Boolean(value)))];
|
|
1054
|
-
return { candidatePaths, normalizedCandidateOperations, candidateSourceKind: "static candidates observed before call in lexical scope", candidateIdentifier: identifier.text, hasDynamicAssignments, conservativeReason: hasDynamicAssignments ? "dynamic_assignment_observed" : normalizedCandidateOperations.length > 1 ? "candidate_tie" : void 0 };
|
|
1055
|
-
}
|
|
1056
|
-
function destinationExpressionShape(expr) {
|
|
1057
|
-
if (!expr) return void 0;
|
|
1058
|
-
if (ts4.isIdentifier(expr)) return "identifier";
|
|
1059
|
-
if (ts4.isPropertyAccessExpression(expr) || ts4.isElementAccessExpression(expr)) return "property_read";
|
|
1060
|
-
if (ts4.isCallExpression(expr)) return "function_call";
|
|
1061
|
-
if (ts4.isConditionalExpression(expr)) return "conditional";
|
|
1062
|
-
if (ts4.isBinaryExpression(expr)) return "binary_expression";
|
|
1063
|
-
if (ts4.isTemplateExpression(expr)) return "template_expression";
|
|
1064
|
-
return ts4.SyntaxKind[expr.kind] ?? "expression";
|
|
1065
|
-
}
|
|
1066
|
-
function staticConditionalCandidates(expr, initializers) {
|
|
1067
|
-
const resolved2 = expr && ts4.isIdentifier(expr) && initializers.has(expr.text) ? initializers.get(expr.text) : expr;
|
|
1068
|
-
if (!resolved2 || !ts4.isConditionalExpression(resolved2)) return void 0;
|
|
1069
|
-
const left = staticExpressionText(resolved2.whenTrue, initializers);
|
|
1070
|
-
const right = staticExpressionText(resolved2.whenFalse, initializers);
|
|
1071
|
-
if (!left || !right) return void 0;
|
|
1072
|
-
return [.../* @__PURE__ */ new Set([left, right])];
|
|
1073
|
-
}
|
|
1074
|
-
function propertyInitializer(object, key) {
|
|
1075
|
-
for (const property of object.properties) {
|
|
1076
|
-
if (ts4.isPropertyAssignment(property) && nameOfProperty(property.name) === key) return property.initializer;
|
|
1077
|
-
if (ts4.isShorthandPropertyAssignment(property) && property.name.text === key) return property.name;
|
|
1078
972
|
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
const text = resolveExpression(propertyInitializer(object, "method"), use, "literal").value;
|
|
1083
|
-
return text ? stripQuotes(text).toUpperCase() : void 0;
|
|
1084
|
-
}
|
|
1085
|
-
var supportedHttpMethods = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]);
|
|
1086
|
-
function safeOperationName(value) {
|
|
1087
|
-
if (!value || !/^[A-Za-z_$][\w$]*(?:[./][A-Za-z_$][\w$]*)*$/.test(value)) return void 0;
|
|
1088
|
-
return operationPathFromStatic(value);
|
|
1089
|
-
}
|
|
1090
|
-
function hasTemplatePlaceholder(value) {
|
|
1091
|
-
return /\$\{|%7B|%7D/i.test(value);
|
|
1092
|
-
}
|
|
1093
|
-
function urlTargetFromExpression(expr, use) {
|
|
1094
|
-
const resolved2 = resolveExpression(expr, use, "external");
|
|
1095
|
-
if (resolved2.status === "static" && resolved2.value && !hasTemplatePlaceholder(resolved2.value)) return { kind: "static_url", expression: resolved2.value, dynamic: false, sourceKind: resolved2.sourceKind };
|
|
1096
|
-
if (expr) return { kind: "url_expression", dynamic: true, expression: `${resolved2.sourceKind}:${resolved2.placeholderKeys.join("|")}`, expressionShape: resolved2.sourceKind, placeholderKeys: resolved2.placeholderKeys };
|
|
1097
|
-
return { kind: "unknown", dynamic: false };
|
|
1098
|
-
}
|
|
1099
|
-
function destinationTargetFromExpression(expr, use) {
|
|
1100
|
-
const resolved2 = resolveExpression(expr, use, "external");
|
|
1101
|
-
const text = resolved2.value;
|
|
1102
|
-
if (resolved2.status === "static" && text && !hasTemplatePlaceholder(text)) return { kind: "destination", expression: text, dynamic: false, sourceKind: resolved2.sourceKind };
|
|
1103
|
-
const candidates = staticConditionalCandidates(expr, /* @__PURE__ */ new Map());
|
|
1104
|
-
if (candidates) return { kind: "destination", dynamic: true, expressionShape: "conditional", candidateLiterals: candidates };
|
|
1105
|
-
const shape = destinationExpressionShape(expr);
|
|
1106
|
-
if (shape) return { kind: "destination", dynamic: true, expressionShape: shape };
|
|
1107
|
-
return void 0;
|
|
1108
|
-
}
|
|
1109
|
-
function externalHttpEvidence(node, source) {
|
|
1110
|
-
const expr = node.expression;
|
|
1111
|
-
const exprText = expr.getText(source);
|
|
1112
|
-
if (exprText === "useOrFetchDestination") {
|
|
1113
|
-
const objectArg = node.arguments[0];
|
|
1114
|
-
if (objectArg && ts4.isObjectLiteralExpression(objectArg)) {
|
|
1115
|
-
const destination = destinationTargetFromExpression(propertyInitializer(objectArg, "destinationName"), node);
|
|
1116
|
-
return { externalTarget: destination ?? { kind: "unknown", dynamic: false }, classifier: "sap_destination_lookup", sourceCallShape: "useOrFetchDestination" };
|
|
1117
|
-
}
|
|
973
|
+
async function recordVariable(decl) {
|
|
974
|
+
if (!ts5.isIdentifier(decl.name) || !decl.initializer) return;
|
|
975
|
+
await recordBindingFromExpression(decl.name.text, decl.initializer, decl, "declaration");
|
|
1118
976
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
const
|
|
1122
|
-
const
|
|
1123
|
-
|
|
1124
|
-
return { method, externalTarget: destination ? { ...url, destination } : url, classifier: "sap_execute_http_request", sourceCallShape: "executeHttpRequest" };
|
|
977
|
+
async function helpersForCall(call) {
|
|
978
|
+
if (!ts5.isIdentifier(call.expression)) return [];
|
|
979
|
+
const local = localObjectHelpers.get(call.expression.text) ?? [];
|
|
980
|
+
const imported = await importedHelpers(call.expression.text);
|
|
981
|
+
return [...local.map((helper) => ({ helper })), ...imported];
|
|
1125
982
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
return { externalTarget: { kind: "unknown", dynamic: false }, classifier: "axios_unknown_call", sourceCallShape: "axios(...)" };
|
|
983
|
+
async function rememberObjectHelperVariable(decl) {
|
|
984
|
+
if (!ts5.isIdentifier(decl.name) || !decl.initializer) return;
|
|
985
|
+
const call = unwrapCall(decl.initializer);
|
|
986
|
+
if (!call) return;
|
|
987
|
+
const helpers = (await helpersForCall(call)).filter((row) => row.helper.returnedProperty);
|
|
988
|
+
if (helpers.length > 0) objectHelperVariables.set(decl.name.text, helpers);
|
|
1133
989
|
}
|
|
1134
|
-
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1137
|
-
|
|
990
|
+
function recordObjectPropertyBinding(targetName, expr, node) {
|
|
991
|
+
const unwrapped = unwrapIdentityExpression(expr);
|
|
992
|
+
if (!ts5.isPropertyAccessExpression(unwrapped) || !ts5.isIdentifier(unwrapped.expression)) return false;
|
|
993
|
+
const helpers = objectHelperVariables.get(unwrapped.expression.text) ?? [];
|
|
994
|
+
const matches2 = helpers.filter((row) => row.helper.returnedProperty === unwrapped.name.text);
|
|
995
|
+
if (matches2.length !== 1) return false;
|
|
996
|
+
const resolved2 = matches2[0];
|
|
997
|
+
out.push({
|
|
998
|
+
variableName: targetName,
|
|
999
|
+
alias: resolved2.helper.alias,
|
|
1000
|
+
aliasExpr: resolved2.helper.aliasExpr,
|
|
1001
|
+
destinationExpr: resolved2.helper.destinationExpr,
|
|
1002
|
+
servicePathExpr: resolved2.helper.servicePathExpr,
|
|
1003
|
+
isDynamic: resolved2.helper.isDynamic,
|
|
1004
|
+
placeholders: resolved2.helper.placeholders,
|
|
1005
|
+
sourceFile: normalizePath(filePath),
|
|
1006
|
+
sourceLine: lineOf3(sourceFileAst, node),
|
|
1007
|
+
helperChain: [
|
|
1008
|
+
...resolved2.helper.helperChain ?? [],
|
|
1009
|
+
{
|
|
1010
|
+
callerVariable: targetName,
|
|
1011
|
+
sourceVariable: unwrapped.expression.text,
|
|
1012
|
+
returnedProperty: unwrapped.name.text,
|
|
1013
|
+
assignedFromProperty: unwrapped.getText(sourceFileAst),
|
|
1014
|
+
importSource: resolved2.imp?.sourceFile,
|
|
1015
|
+
exportedSymbol: resolved2.imp?.exportedName,
|
|
1016
|
+
helperSourceFile: resolved2.helper.sourceFile,
|
|
1017
|
+
helperSourceLine: resolved2.helper.sourceLine
|
|
1018
|
+
}
|
|
1019
|
+
]
|
|
1020
|
+
});
|
|
1021
|
+
return true;
|
|
1138
1022
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1023
|
+
async function recordDestructuredHelper(decl) {
|
|
1024
|
+
if (!ts5.isObjectBindingPattern(decl.name) || !decl.initializer) return;
|
|
1025
|
+
const call = unwrapCall(decl.initializer);
|
|
1026
|
+
if (!call) return;
|
|
1027
|
+
const helpers = await helpersForCall(call);
|
|
1028
|
+
if (helpers.length === 0) return;
|
|
1029
|
+
for (const el of decl.name.elements) {
|
|
1030
|
+
if (!ts5.isIdentifier(el.name)) continue;
|
|
1031
|
+
const propertyName2 = el.propertyName && ts5.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
|
|
1032
|
+
const matches2 = helpers.filter((row) => row.helper.returnedProperty === propertyName2);
|
|
1033
|
+
if (matches2.length !== 1) continue;
|
|
1034
|
+
const resolved2 = matches2[0];
|
|
1035
|
+
out.push({
|
|
1036
|
+
variableName: el.name.text,
|
|
1037
|
+
alias: resolved2.helper.alias,
|
|
1038
|
+
aliasExpr: resolved2.helper.aliasExpr,
|
|
1039
|
+
destinationExpr: resolved2.helper.destinationExpr,
|
|
1040
|
+
servicePathExpr: resolved2.helper.servicePathExpr,
|
|
1041
|
+
isDynamic: resolved2.helper.isDynamic,
|
|
1042
|
+
placeholders: resolved2.helper.placeholders,
|
|
1043
|
+
sourceFile: normalizePath(filePath),
|
|
1044
|
+
sourceLine: lineOf3(sourceFileAst, decl),
|
|
1045
|
+
helperChain: [...resolved2.helper.helperChain ?? [], { callerVariable: el.name.text, helperFunction: call.expression.getText(sourceFileAst), returnedProperty: propertyName2, importSource: resolved2.imp?.sourceFile, exportedSymbol: resolved2.imp?.exportedName, helperSourceFile: resolved2.helper.sourceFile, helperSourceLine: resolved2.helper.sourceLine }]
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1141
1048
|
}
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
function receiverName(expr) {
|
|
1157
|
-
if (ts4.isIdentifier(expr)) return expr.text;
|
|
1158
|
-
if (ts4.isPropertyAccessExpression(expr)) return expr.getText(sourceOf(expr));
|
|
1159
|
-
return void 0;
|
|
1160
|
-
}
|
|
1161
|
-
function sourceOf(node) {
|
|
1162
|
-
return node.getSourceFile();
|
|
1163
|
-
}
|
|
1164
|
-
function rootReceiverName(expr) {
|
|
1165
|
-
if (ts4.isIdentifier(expr)) return expr.text;
|
|
1166
|
-
if (ts4.isPropertyAccessExpression(expr)) return rootReceiverName(expr.expression);
|
|
1167
|
-
if (ts4.isCallExpression(expr)) return rootReceiverName(expr.expression);
|
|
1168
|
-
return void 0;
|
|
1169
|
-
}
|
|
1170
|
-
function isSupportedEventReceiver(receiver, rootReceiver, serviceVariables) {
|
|
1171
|
-
const candidate = rootReceiver ?? receiver;
|
|
1172
|
-
if (!candidate) return false;
|
|
1173
|
-
if (candidate === "cds") return true;
|
|
1174
|
-
if (serviceVariables.has(candidate)) return true;
|
|
1175
|
-
if (receiver && serviceVariables.has(receiver)) return true;
|
|
1176
|
-
if (/^(srv|service|serviceClient|messaging|messageClient|eventClient)$/.test(candidate)) return true;
|
|
1177
|
-
return false;
|
|
1178
|
-
}
|
|
1179
|
-
function collectWrapperSpecs(source) {
|
|
1180
|
-
const specs = /* @__PURE__ */ new Map();
|
|
1181
|
-
const serviceVariables = collectServiceVariables(source);
|
|
1182
|
-
const calledNames = /* @__PURE__ */ new Set();
|
|
1183
|
-
const collectCalls = (node) => {
|
|
1184
|
-
if (ts4.isCallExpression(node)) {
|
|
1185
|
-
if (ts4.isIdentifier(node.expression)) calledNames.add(node.expression.text);
|
|
1186
|
-
if (ts4.isCallExpression(node.expression) && ts4.isIdentifier(node.expression.expression)) calledNames.add(node.expression.expression.text);
|
|
1187
|
-
}
|
|
1188
|
-
ts4.forEachChild(node, collectCalls);
|
|
1189
|
-
};
|
|
1190
|
-
collectCalls(source);
|
|
1191
|
-
const scanFunction = (name, fn) => {
|
|
1192
|
-
if (!calledNames.has(name)) return;
|
|
1193
|
-
const params = fn.parameters.map((param) => ts4.isIdentifier(param.name) ? param.name.text : void 0);
|
|
1194
|
-
const sends = [];
|
|
1195
|
-
const visit = (node) => {
|
|
1196
|
-
if (ts4.isCallExpression(node) && ts4.isPropertyAccessExpression(node.expression) && node.expression.name.text === "send" && ts4.isIdentifier(node.expression.expression)) {
|
|
1197
|
-
const objectArg = node.arguments[0];
|
|
1198
|
-
if (objectArg && ts4.isObjectLiteralExpression(objectArg)) {
|
|
1199
|
-
const pathProp = propertyInitializer(objectArg, "path");
|
|
1200
|
-
const methodProp = propertyInitializer(objectArg, "method");
|
|
1201
|
-
const pathName = pathProp && ts4.isIdentifier(pathProp) ? pathProp.text : void 0;
|
|
1202
|
-
const methodName = methodProp && ts4.isIdentifier(methodProp) ? methodProp.text : void 0;
|
|
1203
|
-
const methodLiteral = resolveExpression(methodProp, node, "literal").value;
|
|
1204
|
-
if (pathName) sends.push({ client: node.expression.expression.text, path: pathName, method: methodName, methodLiteral, start: node.getStart(source), end: node.getEnd() });
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
if (ts4.isCallExpression(node) && ts4.isIdentifier(node.expression) && specs.has(node.expression.text)) {
|
|
1208
|
-
const nested = specs.get(node.expression.text);
|
|
1209
|
-
const pathArg = nested ? node.arguments[nested.pathIndex] : void 0;
|
|
1210
|
-
const clientArg = nested?.clientIndex === void 0 ? void 0 : node.arguments[nested.clientIndex];
|
|
1211
|
-
const pathName = pathArg && ts4.isIdentifier(pathArg) ? pathArg.text : void 0;
|
|
1212
|
-
const clientName = clientArg && ts4.isIdentifier(clientArg) ? clientArg.text : nested?.clientName;
|
|
1213
|
-
if (nested && pathName && clientName) sends.push({ client: clientName, path: pathName, method: nested.methodName, methodLiteral: nested.methodLiteral, nestedWrapperFunction: node.expression.text, start: node.getStart(source), end: node.getEnd() });
|
|
1214
|
-
}
|
|
1215
|
-
ts4.forEachChild(node, visit);
|
|
1216
|
-
};
|
|
1217
|
-
visit(fn);
|
|
1218
|
-
if (sends.length !== 1) return;
|
|
1219
|
-
const found = sends[0];
|
|
1220
|
-
const clientIndex = params.indexOf(found.client);
|
|
1221
|
-
const pathIndex = params.indexOf(found.path);
|
|
1222
|
-
const methodIndex = found.method ? params.indexOf(found.method) : -1;
|
|
1223
|
-
const capturesKnownClient = serviceVariables.has(found.client) || /^(srv|service|serviceClient|client|.*Client)$/.test(found.client);
|
|
1224
|
-
if (pathIndex >= 0 && (clientIndex >= 0 || capturesKnownClient)) specs.set(name, { clientIndex: clientIndex >= 0 ? clientIndex : void 0, clientName: clientIndex >= 0 ? void 0 : found.client, pathIndex, methodIndex: methodIndex >= 0 ? methodIndex : void 0, methodName: found.method, methodLiteral: found.methodLiteral, nestedWrapperFunction: found.nestedWrapperFunction, definitionLine: lineOf3(source.text, fn.getStart(source)), internalStart: found.start, internalEnd: found.end });
|
|
1225
|
-
};
|
|
1226
|
-
const visitTop = (node) => {
|
|
1227
|
-
if (ts4.isFunctionDeclaration(node) && node.name) scanFunction(node.name.text, node);
|
|
1228
|
-
if (ts4.isVariableDeclaration(node) && ts4.isIdentifier(node.name) && node.initializer && (ts4.isArrowFunction(node.initializer) || ts4.isFunctionExpression(node.initializer))) scanFunction(node.name.text, node.initializer);
|
|
1229
|
-
ts4.forEachChild(node, visitTop);
|
|
1230
|
-
};
|
|
1231
|
-
visitTop(source);
|
|
1232
|
-
return specs;
|
|
1233
|
-
}
|
|
1234
|
-
function classifyOutboundCallsInSource(source, filePath) {
|
|
1235
|
-
const calls = [];
|
|
1236
|
-
const sourceFile = normalizePath(filePath);
|
|
1237
|
-
const initializers = variableInitializers(source);
|
|
1238
|
-
const serviceVariables = collectServiceVariables(source);
|
|
1239
|
-
const wrapperSpecs = collectWrapperSpecs(source);
|
|
1240
|
-
const wrapperInternalRanges = [...wrapperSpecs.values()].map((spec) => ({ start: spec.internalStart, end: spec.internalEnd }));
|
|
1241
|
-
const add = (node, fact, extra) => {
|
|
1242
|
-
calls.push({ node, fact: { ...fact, sourceFile, sourceLine: lineOf3(source.text, node.getStart(source)), confidence: fact.confidence ?? 0.8, evidence: parserEvidence(source, node, extra) } });
|
|
1243
|
-
};
|
|
1244
|
-
const visit = (node) => {
|
|
1245
|
-
if (ts4.isCallExpression(node)) {
|
|
1246
|
-
if (wrapperInternalRanges.some((range) => node.getStart(source) >= range.start && node.getEnd() <= range.end)) {
|
|
1247
|
-
return;
|
|
1049
|
+
async function recordDestructuredAssignment(pattern, expr, node) {
|
|
1050
|
+
const call = unwrapCall(expr);
|
|
1051
|
+
if (!call) return;
|
|
1052
|
+
const helpers = await helpersForCall(call);
|
|
1053
|
+
if (helpers.length === 0) return;
|
|
1054
|
+
for (const prop of pattern.properties) {
|
|
1055
|
+
let propertyName2;
|
|
1056
|
+
let targetName;
|
|
1057
|
+
if (ts5.isShorthandPropertyAssignment(prop)) {
|
|
1058
|
+
propertyName2 = prop.name.text;
|
|
1059
|
+
targetName = prop.name.text;
|
|
1060
|
+
} else if (ts5.isPropertyAssignment(prop)) {
|
|
1061
|
+
propertyName2 = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
|
|
1062
|
+
targetName = ts5.isIdentifier(prop.initializer) ? prop.initializer.text : void 0;
|
|
1248
1063
|
}
|
|
1249
|
-
|
|
1250
|
-
const
|
|
1251
|
-
if (
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1064
|
+
if (!propertyName2 || !targetName) continue;
|
|
1065
|
+
const matches2 = helpers.filter((row) => row.helper.returnedProperty === propertyName2);
|
|
1066
|
+
if (matches2.length !== 1) continue;
|
|
1067
|
+
const resolved2 = matches2[0];
|
|
1068
|
+
out.push({
|
|
1069
|
+
variableName: targetName,
|
|
1070
|
+
alias: resolved2.helper.alias,
|
|
1071
|
+
aliasExpr: resolved2.helper.aliasExpr,
|
|
1072
|
+
destinationExpr: resolved2.helper.destinationExpr,
|
|
1073
|
+
servicePathExpr: resolved2.helper.servicePathExpr,
|
|
1074
|
+
isDynamic: resolved2.helper.isDynamic,
|
|
1075
|
+
placeholders: resolved2.helper.placeholders,
|
|
1076
|
+
sourceFile: normalizePath(filePath),
|
|
1077
|
+
sourceLine: lineOf3(sourceFileAst, node),
|
|
1078
|
+
helperChain: [...resolved2.helper.helperChain ?? [], { callerVariable: targetName, assignedFrom: call.expression.getText(sourceFileAst), aliasKind: "assignment", scopeRule: "same-file-source-order", returnedProperty: propertyName2, importSource: resolved2.imp?.sourceFile, exportedSymbol: resolved2.imp?.exportedName, helperSourceFile: resolved2.helper.sourceFile, helperSourceLine: resolved2.helper.sourceLine }]
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
function recordDestructuredClassHelper(decl) {
|
|
1083
|
+
if (!ts5.isObjectBindingPattern(decl.name) || !decl.initializer) return;
|
|
1084
|
+
const call = unwrapCall(decl.initializer);
|
|
1085
|
+
if (!call || !ts5.isPropertyAccessExpression(call.expression)) return;
|
|
1086
|
+
const target = call.expression;
|
|
1087
|
+
if (target.expression.kind !== ts5.SyntaxKind.ThisKeyword) return;
|
|
1088
|
+
for (const el of decl.name.elements) {
|
|
1089
|
+
if (!ts5.isIdentifier(el.name)) continue;
|
|
1090
|
+
const propertyName2 = el.propertyName && ts5.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
|
|
1091
|
+
const helper = classHelpers.find(
|
|
1092
|
+
(h) => h.helperName === target.name.text && h.propertyName === propertyName2
|
|
1093
|
+
);
|
|
1094
|
+
if (!helper) continue;
|
|
1095
|
+
out.push({
|
|
1096
|
+
variableName: el.name.text,
|
|
1097
|
+
...helper.fact,
|
|
1098
|
+
sourceFile: normalizePath(filePath),
|
|
1099
|
+
sourceLine: lineOf3(sourceFileAst, decl),
|
|
1100
|
+
helperChain: [
|
|
1101
|
+
{
|
|
1102
|
+
callerVariable: el.name.text,
|
|
1103
|
+
className: helper.className,
|
|
1104
|
+
classHelper: helper.helperName,
|
|
1105
|
+
returnedProperty: helper.propertyName,
|
|
1106
|
+
helperVariable: helper.variableName,
|
|
1107
|
+
helperSourceFile: normalizePath(filePath),
|
|
1108
|
+
helperSourceLine: helper.sourceLine
|
|
1289
1109
|
}
|
|
1290
|
-
|
|
1291
|
-
}
|
|
1292
|
-
const wrapperName = ts4.isIdentifier(expr) ? expr.text : ts4.isCallExpression(expr) && ts4.isIdentifier(expr.expression) ? expr.expression.text : "";
|
|
1293
|
-
const wrapperArgs = ts4.isIdentifier(expr) ? node.arguments : ts4.isCallExpression(expr) ? expr.arguments : node.arguments;
|
|
1294
|
-
const spec = wrapperSpecs.get(wrapperName);
|
|
1295
|
-
const clientArg = spec?.clientIndex === void 0 ? void 0 : wrapperArgs[spec.clientIndex];
|
|
1296
|
-
const pathArg = spec ? wrapperArgs[spec.pathIndex] : void 0;
|
|
1297
|
-
const methodArg = spec?.methodIndex === void 0 ? void 0 : wrapperArgs[spec.methodIndex];
|
|
1298
|
-
const receiver = clientArg && ts4.isIdentifier(clientArg) ? clientArg.text : spec?.clientName;
|
|
1299
|
-
const method = stripQuotes(resolveExpression(methodArg, node, "literal").value ?? spec?.methodLiteral ?? "POST");
|
|
1300
|
-
const resolvedPath = staticPathExpression(pathArg, node);
|
|
1301
|
-
const operationPathExpr = operationPathFromStatic(resolvedPath.text);
|
|
1302
|
-
const normalizedOperationPath = operationPathExpr ? classifyODataPathIntent(operationPathExpr, method).topLevelOperationName : void 0;
|
|
1303
|
-
if (spec && receiver && operationPathExpr) {
|
|
1304
|
-
add(node, { callType: "remote_action", serviceVariableName: receiver, method, operationPathExpr, payloadSummary: summarizeExpression(node.getText(source)), confidence: 0.75 }, { receiver, classifier: resolvedPath.sourceKind === "literal" ? "higher_order_wrapper_literal_path" : "higher_order_wrapper_static_path", wrapperFunction: wrapperName, nestedWrapperFunction: spec.nestedWrapperFunction, wrapperDefinitionLine: spec.definitionLine, callerLine: lineOf3(source.text, node.getStart(source)), wrapperPathSourceKind: resolvedPath.sourceKind, rawPathExpression: resolvedPath.rawExpression, normalizedOperationPath, literalPathSource: resolvedPath.sourceKind === "const" ? "same_scope_const_initializer" : `wrapper_call_${resolvedPath.sourceKind}`, literalCallerArgumentDetected: true });
|
|
1305
|
-
} else if (spec && receiver) {
|
|
1306
|
-
add(node, { callType: "remote_action", serviceVariableName: receiver, method, payloadSummary: summarizeExpression(node.getText(source)), confidence: 0.45, unresolvedReason: "dynamic_operation_path_identifier" }, { receiver, classifier: "higher_order_wrapper_dynamic_path", wrapperFunction: wrapperName, wrapperDefinitionLine: spec.definitionLine, callerLine: lineOf3(source.text, node.getStart(source)), wrapperPathSourceKind: resolvedPath.sourceKind, rawPathExpression: resolvedPath.rawExpression, parserWarning: "dynamic_operation_path_identifier" });
|
|
1307
|
-
}
|
|
1308
|
-
} else if (ts4.isPropertyAccessExpression(expr) && ["emit", "publish", "on"].includes(expr.name.text)) {
|
|
1309
|
-
const receiver = receiverName(expr.expression);
|
|
1310
|
-
const rootReceiver = rootReceiverName(expr.expression);
|
|
1311
|
-
if (isSupportedEventReceiver(receiver, rootReceiver, serviceVariables)) {
|
|
1312
|
-
const eventName = literalText(node.arguments[0]);
|
|
1313
|
-
if (eventName) add(node, { callType: expr.name.text === "on" ? "async_subscribe" : "async_emit", serviceVariableName: rootReceiver ?? receiver, eventNameExpr: eventName }, { receiver, rootReceiver, classifier: expr.name.text === "on" ? "cap_service_event_subscription" : "cap_service_event_emit", receiverClassification: "cap_evidence" });
|
|
1314
|
-
}
|
|
1315
|
-
} else {
|
|
1316
|
-
const external = externalHttpEvidence(node, source);
|
|
1317
|
-
if (external) {
|
|
1318
|
-
const evidenceTarget = { ...external.externalTarget, method: external.method, parserClassifier: external.classifier, sourceCallShape: external.sourceCallShape };
|
|
1319
|
-
const safeTarget = externalHttpTarget({ method: external.method, evidence_json: JSON.stringify({ externalTarget: evidenceTarget }) });
|
|
1320
|
-
add(node, { callType: "external_http", method: external.method, payloadSummary: void 0, confidence: 0.7, unresolvedReason: "External HTTP destination is outside indexed CAP services", externalTarget: { kind: safeTarget.kind, stableId: safeTarget.toId, label: safeTarget.label, dynamic: safeTarget.dynamic } }, { classifier: external.classifier, externalTarget: safeTarget, sourceCallShape: external.sourceCallShape });
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1110
|
+
]
|
|
1111
|
+
});
|
|
1323
1112
|
}
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
if (origin) aliases.set(node.name.text, { ...origin, chain: [...origin.chain, node.name.text] });
|
|
1113
|
+
}
|
|
1114
|
+
function arrayElementsFromExpression(expr) {
|
|
1115
|
+
const unwrapped = unwrapIdentityExpression(expr);
|
|
1116
|
+
if (ts5.isArrayLiteralExpression(unwrapped)) return { elements: unwrapped.elements, promiseAll: false };
|
|
1117
|
+
const call = unwrapCall(expr);
|
|
1118
|
+
if (!call) return void 0;
|
|
1119
|
+
if (!ts5.isPropertyAccessExpression(call.expression) || call.expression.name.text !== "all" || call.expression.expression.getText(sourceFileAst) !== "Promise") return void 0;
|
|
1120
|
+
const first = call.arguments[0];
|
|
1121
|
+
if (!first) return void 0;
|
|
1122
|
+
const container = unwrapIdentityExpression(first);
|
|
1123
|
+
if (!ts5.isArrayLiteralExpression(container)) return void 0;
|
|
1124
|
+
return { elements: container.elements, promiseAll: true };
|
|
1125
|
+
}
|
|
1126
|
+
async function recordArrayElementBinding(targetName, expr, node, arrayIndex, promiseAll) {
|
|
1127
|
+
const before = out.length;
|
|
1128
|
+
await recordBindingFromExpression(targetName, expr, node, "declaration");
|
|
1129
|
+
if (out.length > before) {
|
|
1130
|
+
const row = out[out.length - 1];
|
|
1131
|
+
row.helperChain = [
|
|
1132
|
+
...row.helperChain ?? [],
|
|
1133
|
+
{ callerVariable: targetName, targetVariable: targetName, arrayIndex, promiseAll, arrayContainer: promiseAll ? "Promise.all" : "array_literal" }
|
|
1134
|
+
];
|
|
1135
|
+
return;
|
|
1348
1136
|
}
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
unresolvedReason: ["send", "emit", "publish", "on"].includes(parsed.operation) ? "transport_client_method" : void 0,
|
|
1362
|
-
evidence: parserEvidence(source, node, {
|
|
1363
|
-
classifier: parsed.classifier,
|
|
1364
|
-
parserCallType: parsed.operation === "send" ? "transport_client_method" : parsed.classifier,
|
|
1365
|
-
localServiceLookup: parsed.lookup,
|
|
1366
|
-
localServiceName: parsed.service,
|
|
1367
|
-
operation: parsed.operation,
|
|
1368
|
-
aliasChain: parsed.chain
|
|
1369
|
-
})
|
|
1137
|
+
const unwrapped = unwrapIdentityExpression(expr);
|
|
1138
|
+
if (ts5.isIdentifier(unwrapped)) {
|
|
1139
|
+
const existing = bindingForVariable(unwrapped.text);
|
|
1140
|
+
if (!existing) return;
|
|
1141
|
+
out.push({
|
|
1142
|
+
...existing,
|
|
1143
|
+
variableName: targetName,
|
|
1144
|
+
sourceLine: lineOf3(sourceFileAst, node),
|
|
1145
|
+
helperChain: [
|
|
1146
|
+
...existing.helperChain ?? [],
|
|
1147
|
+
{ callerVariable: targetName, targetVariable: targetName, sourceVariable: unwrapped.text, aliasKind: "array-destructuring", arrayIndex, promiseAll, arrayContainer: promiseAll ? "Promise.all" : "array_literal" }
|
|
1148
|
+
]
|
|
1370
1149
|
});
|
|
1371
1150
|
}
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1151
|
+
}
|
|
1152
|
+
async function recordArrayDestructuredVariable(decl) {
|
|
1153
|
+
if (!ts5.isArrayBindingPattern(decl.name) || !decl.initializer) return;
|
|
1154
|
+
const container = arrayElementsFromExpression(decl.initializer);
|
|
1155
|
+
if (!container) return;
|
|
1156
|
+
for (let index = 0; index < decl.name.elements.length; index += 1) {
|
|
1157
|
+
const el = decl.name.elements[index];
|
|
1158
|
+
if (!el || ts5.isOmittedExpression(el) || ts5.isBindingElement(el) && el.dotDotDotToken) continue;
|
|
1159
|
+
if (!ts5.isBindingElement(el) || !ts5.isIdentifier(el.name)) continue;
|
|
1160
|
+
const source = container.elements[index];
|
|
1161
|
+
if (!source || ts5.isOmittedExpression(source)) continue;
|
|
1162
|
+
await recordArrayElementBinding(el.name.text, source, decl, index, container.promiseAll);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
async function recordArrayDestructuredAssignment(pattern, expr, node) {
|
|
1166
|
+
const container = arrayElementsFromExpression(expr);
|
|
1167
|
+
if (!container) return;
|
|
1168
|
+
for (let index = 0; index < pattern.elements.length; index += 1) {
|
|
1169
|
+
const el = pattern.elements[index];
|
|
1170
|
+
if (!el || ts5.isOmittedExpression(el) || ts5.isSpreadElement(el) || !ts5.isIdentifier(el)) continue;
|
|
1171
|
+
const source = container.elements[index];
|
|
1172
|
+
if (!source || ts5.isOmittedExpression(source)) continue;
|
|
1173
|
+
await recordArrayElementBinding(el.text, source, node, index, container.promiseAll);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
const events = [];
|
|
1177
|
+
function collectEvents(node) {
|
|
1178
|
+
if (ts5.isVariableDeclaration(node)) events.push({ pos: node.getStart(sourceFileAst), node });
|
|
1179
|
+
if (ts5.isBinaryExpression(node) && node.operatorToken.kind === ts5.SyntaxKind.EqualsToken)
|
|
1180
|
+
events.push({ pos: node.getStart(sourceFileAst), node });
|
|
1181
|
+
ts5.forEachChild(node, collectEvents);
|
|
1182
|
+
}
|
|
1183
|
+
collectEvents(sourceFileAst);
|
|
1184
|
+
events.sort((a, b) => a.pos - b.pos);
|
|
1185
|
+
for (const event of events) {
|
|
1186
|
+
if (ts5.isVariableDeclaration(event.node)) {
|
|
1187
|
+
const decl = event.node;
|
|
1188
|
+
await recordDestructuredHelper(decl);
|
|
1189
|
+
await recordArrayDestructuredVariable(decl);
|
|
1190
|
+
recordDestructuredClassHelper(decl);
|
|
1191
|
+
await rememberObjectHelperVariable(decl);
|
|
1192
|
+
if (ts5.isIdentifier(decl.name) && decl.initializer) recordObjectPropertyBinding(decl.name.text, decl.initializer, decl);
|
|
1193
|
+
await recordVariable(decl);
|
|
1194
|
+
recordIdentityAlias(decl);
|
|
1195
|
+
if (ts5.isIdentifier(decl.name) && decl.initializer) {
|
|
1196
|
+
const sourceName = transactionReceiverName(decl.initializer);
|
|
1197
|
+
if (sourceName) cloneAliasBinding(decl.name.text, sourceName, "transaction", decl);
|
|
1198
|
+
}
|
|
1199
|
+
continue;
|
|
1200
|
+
}
|
|
1201
|
+
const assignment = event.node;
|
|
1202
|
+
if (ts5.isIdentifier(assignment.left)) {
|
|
1203
|
+
const rhs = unwrapIdentityExpression(assignment.right);
|
|
1204
|
+
if (ts5.isIdentifier(rhs)) {
|
|
1205
|
+
cloneAliasBinding(assignment.left.text, rhs.text, "identity-assignment", assignment);
|
|
1206
|
+
continue;
|
|
1207
|
+
}
|
|
1208
|
+
if (recordObjectPropertyBinding(assignment.left.text, assignment.right, assignment)) continue;
|
|
1209
|
+
await recordBindingFromExpression(assignment.left.text, assignment.right, assignment, "assignment");
|
|
1210
|
+
continue;
|
|
1211
|
+
}
|
|
1212
|
+
const left = ts5.isParenthesizedExpression(assignment.left) ? assignment.left.expression : assignment.left;
|
|
1213
|
+
if (ts5.isObjectLiteralExpression(left))
|
|
1214
|
+
await recordDestructuredAssignment(left, assignment.right, assignment);
|
|
1215
|
+
if (ts5.isArrayLiteralExpression(left))
|
|
1216
|
+
await recordArrayDestructuredAssignment(left, assignment.right, assignment);
|
|
1217
|
+
}
|
|
1218
|
+
return out;
|
|
1382
1219
|
}
|
|
1383
|
-
function
|
|
1384
|
-
const
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1220
|
+
function collectClassHelpers(sf) {
|
|
1221
|
+
const helpers = [];
|
|
1222
|
+
for (const stmt of sf.statements) {
|
|
1223
|
+
if (!ts5.isClassDeclaration(stmt) || !stmt.name) continue;
|
|
1224
|
+
for (const member of stmt.members) {
|
|
1225
|
+
let visit2 = function(node) {
|
|
1226
|
+
if (ts5.isVariableDeclaration(node) && ts5.isIdentifier(node.name) && node.initializer) {
|
|
1227
|
+
const fact = findConnectInExpression(node.initializer);
|
|
1228
|
+
if (fact) bindings.set(node.name.text, fact);
|
|
1229
|
+
}
|
|
1230
|
+
if (ts5.isReturnStatement(node) && node.expression && ts5.isObjectLiteralExpression(node.expression)) {
|
|
1231
|
+
for (const prop of node.expression.properties) {
|
|
1232
|
+
if (ts5.isShorthandPropertyAssignment(prop)) {
|
|
1233
|
+
const fact = bindings.get(prop.name.text);
|
|
1234
|
+
if (fact)
|
|
1235
|
+
helpers.push({
|
|
1236
|
+
className,
|
|
1237
|
+
helperName,
|
|
1238
|
+
propertyName: prop.name.text,
|
|
1239
|
+
variableName: prop.name.text,
|
|
1240
|
+
fact,
|
|
1241
|
+
sourceLine: lineOf3(sf, prop)
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.initializer)) {
|
|
1245
|
+
const propertyName2 = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
|
|
1246
|
+
const fact = propertyName2 ? bindings.get(prop.initializer.text) : void 0;
|
|
1247
|
+
if (propertyName2 && fact)
|
|
1248
|
+
helpers.push({
|
|
1249
|
+
className,
|
|
1250
|
+
helperName,
|
|
1251
|
+
propertyName: propertyName2,
|
|
1252
|
+
variableName: prop.initializer.text,
|
|
1253
|
+
fact,
|
|
1254
|
+
sourceLine: lineOf3(sf, prop)
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
ts5.forEachChild(node, visit2);
|
|
1260
|
+
};
|
|
1261
|
+
var visit = visit2;
|
|
1262
|
+
if (!ts5.isPropertyDeclaration(member) || !member.initializer) continue;
|
|
1263
|
+
if (!ts5.isIdentifier(member.name)) continue;
|
|
1264
|
+
const className = stmt.name.text;
|
|
1265
|
+
const helperName = member.name.text;
|
|
1266
|
+
const initializer = member.initializer;
|
|
1267
|
+
if (!ts5.isArrowFunction(initializer) && !ts5.isFunctionExpression(initializer))
|
|
1268
|
+
continue;
|
|
1269
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
1270
|
+
visit2(initializer);
|
|
1271
|
+
}
|
|
1394
1272
|
}
|
|
1395
|
-
return
|
|
1273
|
+
return helpers;
|
|
1396
1274
|
}
|
|
1397
1275
|
|
|
1398
|
-
// src/parsers/
|
|
1399
|
-
import path9 from "path";
|
|
1400
|
-
import ts6 from "typescript";
|
|
1401
|
-
|
|
1402
|
-
// src/parsers/service-binding-parser-helpers.ts
|
|
1276
|
+
// src/parsers/outbound-call-parser.ts
|
|
1403
1277
|
import fs7 from "fs/promises";
|
|
1404
|
-
import
|
|
1405
|
-
import
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
if (ts5.isTemplateExpression(node)) return node.getText().replace(/^`|`$/g, "");
|
|
1413
|
-
return node.getText();
|
|
1414
|
-
}
|
|
1415
|
-
function placeholders2(value) {
|
|
1416
|
-
return [...(value ?? "").matchAll(/\$\{([^}]*)\}/g)].map((m) => (m[1] ?? "").trim()).filter(Boolean);
|
|
1278
|
+
import path10 from "path";
|
|
1279
|
+
import ts7 from "typescript";
|
|
1280
|
+
|
|
1281
|
+
// src/linker/external-http-target.ts
|
|
1282
|
+
import { createHash } from "crypto";
|
|
1283
|
+
var sensitiveKeys = /* @__PURE__ */ new Set(["token", "access_token", "id_token", "api_key", "apikey", "key", "password", "passwd", "pwd", "secret", "client_secret", "authorization", "cookie", "signature"]);
|
|
1284
|
+
function hash(value) {
|
|
1285
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 12);
|
|
1417
1286
|
}
|
|
1418
|
-
function
|
|
1419
|
-
|
|
1420
|
-
if (!ts5.isPropertyAccessExpression(expr) || expr.name.text !== "to") return void 0;
|
|
1421
|
-
const inner = expr.expression;
|
|
1422
|
-
if (!ts5.isPropertyAccessExpression(inner) || inner.name.text !== "connect" || inner.expression.getText() !== "cds") return void 0;
|
|
1423
|
-
const first = call.arguments[0];
|
|
1424
|
-
if (!first) return void 0;
|
|
1425
|
-
const second = call.arguments[1];
|
|
1426
|
-
const objectArg = ts5.isObjectLiteralExpression(first) ? first : second && ts5.isObjectLiteralExpression(second) ? second : void 0;
|
|
1427
|
-
let alias;
|
|
1428
|
-
let aliasExpr;
|
|
1429
|
-
if (ts5.isStringLiteralLike(first) || ts5.isNoSubstitutionTemplateLiteral(first)) alias = first.text;
|
|
1430
|
-
else if (!ts5.isObjectLiteralExpression(first)) aliasExpr = stringValue(first);
|
|
1431
|
-
if ((ts5.isStringLiteralLike(first) || ts5.isNoSubstitutionTemplateLiteral(first)) && !objectArg) return { alias: first.text, isDynamic: false, placeholders: [] };
|
|
1432
|
-
if (!objectArg && aliasExpr) return { aliasExpr, isDynamic: true, placeholders: placeholders2(aliasExpr) };
|
|
1433
|
-
const expressions = objectArg ? objectExpressions(objectArg) : {};
|
|
1434
|
-
const ph = [...placeholders2(aliasExpr ?? alias), ...placeholders2(expressions.destinationExpr), ...placeholders2(expressions.servicePathExpr)];
|
|
1435
|
-
return { alias, aliasExpr, ...expressions, isDynamic: ph.length > 0 || !expressions.destinationExpr && !expressions.servicePathExpr, placeholders: ph };
|
|
1287
|
+
function methodPrefix(method) {
|
|
1288
|
+
return typeof method === "string" && method.length > 0 ? `${method.toUpperCase()} ` : "";
|
|
1436
1289
|
}
|
|
1437
|
-
function
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1290
|
+
function redactUrl(value) {
|
|
1291
|
+
try {
|
|
1292
|
+
const url = new URL(value, value.startsWith("/") ? "https://relative.invalid" : void 0);
|
|
1293
|
+
url.username = "";
|
|
1294
|
+
url.password = "";
|
|
1295
|
+
for (const key of [...url.searchParams.keys()]) url.searchParams.set(key, sensitiveKeys.has(key.toLowerCase()) ? "<redacted>" : "<redacted>");
|
|
1296
|
+
const path11 = `${url.pathname}${url.search ? url.search : ""}`;
|
|
1297
|
+
return value.startsWith("/") ? path11 : `${url.origin}${path11}`;
|
|
1298
|
+
} catch {
|
|
1299
|
+
return value.replace(/([?&][^=;&]*(?:token|key|password|secret|cookie|authorization)[^=;&]*=)[^&]*/gi, "$1<redacted>");
|
|
1447
1300
|
}
|
|
1448
|
-
visitObject(objectArg);
|
|
1449
|
-
return out;
|
|
1450
|
-
}
|
|
1451
|
-
function unwrapCall(expr) {
|
|
1452
|
-
if (ts5.isAwaitExpression(expr)) return unwrapCall(expr.expression);
|
|
1453
|
-
if (ts5.isParenthesizedExpression(expr)) return unwrapCall(expr.expression);
|
|
1454
|
-
if (ts5.isAsExpression(expr) || ts5.isSatisfiesExpression(expr)) return unwrapCall(expr.expression);
|
|
1455
|
-
if (ts5.isTypeAssertionExpression(expr)) return unwrapCall(expr.expression);
|
|
1456
|
-
if (ts5.isCallExpression(expr)) return expr;
|
|
1457
|
-
return void 0;
|
|
1458
|
-
}
|
|
1459
|
-
function unwrapIdentityExpression(expr) {
|
|
1460
|
-
if (ts5.isAwaitExpression(expr)) return unwrapIdentityExpression(expr.expression);
|
|
1461
|
-
if (ts5.isParenthesizedExpression(expr)) return unwrapIdentityExpression(expr.expression);
|
|
1462
|
-
if (ts5.isAsExpression(expr) || ts5.isSatisfiesExpression(expr)) return unwrapIdentityExpression(expr.expression);
|
|
1463
|
-
if (ts5.isTypeAssertionExpression(expr)) return unwrapIdentityExpression(expr.expression);
|
|
1464
|
-
return expr;
|
|
1465
|
-
}
|
|
1466
|
-
function transactionReceiverName(expr) {
|
|
1467
|
-
const call = unwrapCall(expr);
|
|
1468
|
-
if (call && ts5.isPropertyAccessExpression(call.expression) && ["tx", "transaction"].includes(call.expression.name.text) && ts5.isIdentifier(call.expression.expression)) return call.expression.expression.text;
|
|
1469
|
-
const unwrapped = unwrapIdentityExpression(expr);
|
|
1470
|
-
if (!ts5.isConditionalExpression(unwrapped)) return void 0;
|
|
1471
|
-
const left = transactionReceiverName(unwrapped.whenTrue);
|
|
1472
|
-
const right = transactionReceiverName(unwrapped.whenFalse);
|
|
1473
|
-
return left && left === right ? left : void 0;
|
|
1474
1301
|
}
|
|
1475
|
-
function
|
|
1476
|
-
const
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1302
|
+
function externalHttpTarget(call) {
|
|
1303
|
+
const evidence = typeof call.evidence_json === "string" ? safeParse(call.evidence_json) : {};
|
|
1304
|
+
const target = evidence.externalTarget && typeof evidence.externalTarget === "object" && !Array.isArray(evidence.externalTarget) ? evidence.externalTarget : {};
|
|
1305
|
+
const method = typeof call.method === "string" ? call.method : typeof target.method === "string" ? target.method : void 0;
|
|
1306
|
+
const kind = typeof target.kind === "string" ? target.kind : "unknown";
|
|
1307
|
+
const expression = typeof target.expression === "string" ? target.expression : void 0;
|
|
1308
|
+
if (kind === "destination" && target.dynamic === true) {
|
|
1309
|
+
const shape = typeof target.expressionShape === "string" ? target.expressionShape : "expression";
|
|
1310
|
+
const candidates = Array.isArray(target.candidateLiterals) ? target.candidateLiterals.filter((item) => typeof item === "string") : [];
|
|
1311
|
+
return { kind, toKind: "external_destination", toId: `destination:dynamic:${hash(`${shape}:${candidates.join("|")}`)}`, label: "External destination: dynamic destination", method, dynamic: true, expression: candidates.length ? `candidates:${candidates.join("|")}` : `shape:${shape}` };
|
|
1480
1312
|
}
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
if (!found) ts5.forEachChild(node, visit);
|
|
1313
|
+
if (kind === "destination" && expression) return { kind, toKind: "external_destination", toId: `destination:${expression}`, label: `External destination: ${expression}`, method, dynamic: false, expression };
|
|
1314
|
+
if (kind === "static_url" && expression) {
|
|
1315
|
+
const redacted = redactUrl(expression);
|
|
1316
|
+
return { kind, toKind: "external_endpoint", toId: `endpoint:${hash(`${method ?? ""}:${redacted}`)}`, label: `External endpoint: ${methodPrefix(method)}${redacted}`, method, dynamic: false, expression: redacted };
|
|
1486
1317
|
}
|
|
1487
|
-
|
|
1488
|
-
return
|
|
1318
|
+
if (kind === "url_expression" && expression) return { kind, toKind: "external_endpoint", toId: `dynamic:${hash(expression)}`, label: `External endpoint: ${methodPrefix(method)}dynamic URL`, method, dynamic: true, expression: `expr:${hash(expression)}` };
|
|
1319
|
+
return { kind: "unknown", toKind: "external_endpoint", toId: "unknown", label: "External endpoint: unknown", method, dynamic: false };
|
|
1489
1320
|
}
|
|
1490
|
-
|
|
1321
|
+
function safeParse(value) {
|
|
1491
1322
|
try {
|
|
1492
|
-
const
|
|
1493
|
-
return
|
|
1323
|
+
const parsed = JSON.parse(value);
|
|
1324
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
1494
1325
|
} catch {
|
|
1495
|
-
return
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1498
|
-
async function resolveImport(repoPath, fromFile, spec) {
|
|
1499
|
-
if (!spec.startsWith(".")) return void 0;
|
|
1500
|
-
const rawBase = path8.resolve(repoPath, path8.dirname(fromFile), spec);
|
|
1501
|
-
const parsed = path8.parse(rawBase);
|
|
1502
|
-
const base = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"].includes(parsed.ext) ? path8.join(parsed.dir, parsed.name) : rawBase;
|
|
1503
|
-
for (const candidate of [base, `${base}.ts`, `${base}.js`, path8.join(base, "index.ts"), path8.join(base, "index.js")]) {
|
|
1504
|
-
const stat = await fs7.stat(candidate).catch(() => void 0);
|
|
1505
|
-
if (stat?.isFile()) return normalizePath(path8.relative(repoPath, candidate));
|
|
1506
|
-
}
|
|
1507
|
-
return void 0;
|
|
1508
|
-
}
|
|
1509
|
-
async function importsFor(repoPath, filePath, sf) {
|
|
1510
|
-
const imports = [];
|
|
1511
|
-
for (const stmt of sf.statements) {
|
|
1512
|
-
if (!ts5.isImportDeclaration(stmt) || !ts5.isStringLiteralLike(stmt.moduleSpecifier)) continue;
|
|
1513
|
-
const sourceFile = await resolveImport(repoPath, filePath, stmt.moduleSpecifier.text);
|
|
1514
|
-
const clause = stmt.importClause;
|
|
1515
|
-
if (!clause) continue;
|
|
1516
|
-
if (clause.name) imports.push({ localName: clause.name.text, exportedName: "default", sourceFile });
|
|
1517
|
-
const bindings = clause.namedBindings;
|
|
1518
|
-
if (bindings && ts5.isNamedImports(bindings))
|
|
1519
|
-
for (const el of bindings.elements) imports.push({ localName: el.name.text, exportedName: el.propertyName?.text ?? el.name.text, sourceFile });
|
|
1326
|
+
return {};
|
|
1520
1327
|
}
|
|
1521
|
-
return imports;
|
|
1522
1328
|
}
|
|
1523
1329
|
|
|
1524
|
-
// src/
|
|
1525
|
-
function
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1330
|
+
// src/linker/odata-path-normalizer.ts
|
|
1331
|
+
function normalizeODataOperationInvocationPath(path11) {
|
|
1332
|
+
if (path11 === void 0) return void 0;
|
|
1333
|
+
const raw = path11.trim();
|
|
1334
|
+
if (!raw) return void 0;
|
|
1335
|
+
const rejected = (reason) => ({ rawOperationPath: raw, normalizedOperationPath: raw, wasInvocation: false, invocationArgumentPlaceholderKeys: [], normalizationRejectedReason: reason });
|
|
1336
|
+
const open = raw.indexOf("(");
|
|
1337
|
+
if (open < 0) return rejected("no_top_level_parenthesis");
|
|
1338
|
+
const query = topLevelQueryIndex(raw);
|
|
1339
|
+
if (query >= 0) return rejected("query_string_paths_are_not_operation_invocations");
|
|
1340
|
+
if (!raw.startsWith("/")) return rejected("path_is_not_absolute");
|
|
1341
|
+
if (raw.slice(1, open).includes("/")) return rejected("operation_segment_contains_navigation_separator");
|
|
1342
|
+
const close = matchingClose(raw, open);
|
|
1343
|
+
if (close === void 0) return rejected("top_level_invocation_parenthesis_is_unbalanced");
|
|
1344
|
+
if (raw.slice(close + 1).trim().length > 0) return rejected("top_level_invocation_does_not_cover_remaining_path");
|
|
1345
|
+
const operationSegment = raw.slice(0, open).trim();
|
|
1346
|
+
if (operationSegment.length <= 1) return rejected("operation_segment_is_empty");
|
|
1347
|
+
return {
|
|
1348
|
+
rawOperationPath: raw,
|
|
1349
|
+
normalizedOperationPath: operationSegment,
|
|
1350
|
+
wasInvocation: true,
|
|
1351
|
+
invocationArguments: raw.slice(open + 1, close),
|
|
1352
|
+
invocationArgumentPlaceholderKeys: [...new Set(extractTemplatePlaceholders(raw.slice(open + 1, close)))],
|
|
1353
|
+
normalizationReason: "balanced_top_level_operation_invocation"
|
|
1354
|
+
};
|
|
1543
1355
|
}
|
|
1544
|
-
function
|
|
1545
|
-
const
|
|
1546
|
-
const
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1356
|
+
function classifyODataPathIntent(path11, method) {
|
|
1357
|
+
const rawPath = (path11 ?? "").trim();
|
|
1358
|
+
const normalizedMethod = (method ?? "GET").trim().toUpperCase() || "GET";
|
|
1359
|
+
const queryIndex = rawPath.indexOf("?");
|
|
1360
|
+
const pathWithoutQuery = queryIndex >= 0 ? rawPath.slice(0, queryIndex) : rawPath;
|
|
1361
|
+
const queryString = queryIndex >= 0 ? rawPath.slice(queryIndex + 1) : void 0;
|
|
1362
|
+
const segments = pathWithoutQuery.replace(/^\//, "").split("/").filter(Boolean);
|
|
1363
|
+
const firstSegment = segments[0] ?? "";
|
|
1364
|
+
const hasNavigationSegments = segments.length > 1;
|
|
1365
|
+
const entitySegment = entitySegmentFromPath(pathWithoutQuery);
|
|
1366
|
+
const placeholderKeys2 = [...new Set(extractTemplatePlaceholders(rawPath))];
|
|
1367
|
+
const firstOpen = firstSegment.indexOf("(");
|
|
1368
|
+
const firstClose = firstOpen >= 0 ? matchingClose(firstSegment, firstOpen) : void 0;
|
|
1369
|
+
const keyPredicateText = firstOpen >= 0 && firstClose !== void 0 ? firstSegment.slice(firstOpen + 1, firstClose) : "";
|
|
1370
|
+
const rawKeyPredicatePlaceholderKeys = [...new Set(extractTemplatePlaceholders(keyPredicateText))];
|
|
1371
|
+
const navigationSuffix = hasNavigationSegments ? segments.slice(1).join("/") : void 0;
|
|
1372
|
+
const lastSegment = segments.at(-1) ?? "";
|
|
1373
|
+
const mediaOrPropertySuffix = hasNavigationSegments ? lastSegment : void 0;
|
|
1374
|
+
const invocation = normalizeODataOperationInvocationPath(pathWithoutQuery);
|
|
1375
|
+
const operationNameFromInvocation = invocation?.wasInvocation ? invocation.normalizedOperationPath.replace(/^\//, "").split(".").at(-1) : void 0;
|
|
1376
|
+
const topLevelName = firstSegment.split("(")[0]?.replace(/^\//, "");
|
|
1377
|
+
const topLevelOperationName = operationNameFromInvocation ?? (!hasNavigationSegments && !firstSegment.includes("(") ? topLevelName : void 0);
|
|
1378
|
+
const topLevelOperationInvocation = Boolean(invocation?.wasInvocation && looksLikeLowerCamelInvocation(firstSegment));
|
|
1379
|
+
const keyPredicatePlaceholderKeys = topLevelOperationInvocation ? [] : rawKeyPredicatePlaceholderKeys;
|
|
1380
|
+
const topLevelOperationNameCandidate = Boolean(topLevelOperationName && !hasNavigationSegments && !firstSegment.includes("("));
|
|
1381
|
+
const base = { rawPath, method: normalizedMethod, pathWithoutQuery, queryString, hasQueryString: queryIndex >= 0, entitySegment, placeholderKeys: placeholderKeys2, keyPredicatePlaceholderKeys, invocationArguments: invocation?.invocationArguments, invocationArgumentPlaceholderKeys: invocation?.invocationArgumentPlaceholderKeys ?? [], navigationSuffix, mediaOrPropertySuffix, hasEntityKeyPredicate: firstOpen >= 0 && firstClose !== void 0, hasNavigationSuffix: hasNavigationSegments, hasMediaOrPropertySuffix: Boolean(mediaOrPropertySuffix && isMediaOrPropertySuffix(mediaOrPropertySuffix)), topLevelOperationName, topLevelOperationNameCandidate, topLevelOperationInvocation };
|
|
1382
|
+
if (!rawPath || !rawPath.startsWith("/")) return { ...base, kind: "unknown", reason: "path_missing_or_not_absolute" };
|
|
1383
|
+
const upperEntityLike = /^[A-Z][A-Za-z0-9_]*$/.test(entitySegment ?? firstSegment);
|
|
1384
|
+
const mediaLike = isMediaOrPropertySuffix(segments.at(-1) ?? "");
|
|
1385
|
+
if (normalizedMethod !== "GET") {
|
|
1386
|
+
if (invocation?.wasInvocation && looksLikeLowerCamelInvocation(firstSegment)) return { ...base, kind: "operation_invocation", reason: "non_get_balanced_top_level_operation_invocation" };
|
|
1387
|
+
if (mediaLike) return { ...base, kind: "entity_media", reason: "non_get_entity_media_stream_path" };
|
|
1388
|
+
if (hasNavigationSegments || firstSegment.includes("(")) return { ...base, kind: normalizedMethod === "DELETE" ? "entity_delete" : "entity_mutation", reason: firstSegment.includes("(") ? "non_get_entity_key_or_navigation_path_shape" : "non_get_entity_navigation_path_shape" };
|
|
1389
|
+
if (upperEntityLike) return { ...base, kind: normalizedMethod === "DELETE" ? "entity_delete" : "entity_mutation", reason: "non_get_entity_path_shape" };
|
|
1390
|
+
return { ...base, kind: "operation_invocation", reason: "non_get_lowercase_path_may_be_operation" };
|
|
1560
1391
|
}
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1392
|
+
if (queryIndex >= 0) {
|
|
1393
|
+
if (hasNavigationSegments) return { ...base, kind: "entity_navigation_query", reason: "get_path_has_navigation_and_query_string" };
|
|
1394
|
+
if (looksLikeLowerCamelInvocation(firstSegment)) return { ...base, kind: "unknown", reason: "get_invocation_with_query_string_requires_indexed_operation_evidence" };
|
|
1395
|
+
return { ...base, kind: "entity_query", reason: "get_collection_path_has_query_string" };
|
|
1396
|
+
}
|
|
1397
|
+
if (hasNavigationSegments) return mediaLike ? { ...base, kind: "entity_media", reason: "get_entity_media_stream_path" } : { ...base, kind: "entity_navigation_query", reason: "get_path_has_navigation_segments" };
|
|
1398
|
+
if (firstSegment.includes("(")) {
|
|
1399
|
+
if (invocation?.wasInvocation && looksLikeLowerCamelInvocation(firstSegment)) return { ...base, kind: "operation_invocation", reason: "get_balanced_top_level_operation_invocation" };
|
|
1400
|
+
return looksLikeLowerCamelInvocation(firstSegment) ? { ...base, kind: "operation_invocation", reason: "get_single_lower_camel_segment_has_top_level_invocation" } : { ...base, kind: "entity_key_read", reason: "get_entity_segment_has_key_predicate" };
|
|
1566
1401
|
}
|
|
1567
|
-
return
|
|
1402
|
+
if (/^[A-Z][A-Za-z0-9_]*$/.test(firstSegment)) return { ...base, kind: "entity_candidate", reason: "uppercase_collection_segment_without_indexed_entity_evidence" };
|
|
1403
|
+
return { ...base, kind: "unknown", reason: "get_path_has_no_query_key_or_navigation_signal" };
|
|
1568
1404
|
}
|
|
1569
|
-
function
|
|
1570
|
-
|
|
1571
|
-
if (
|
|
1572
|
-
|
|
1405
|
+
function entitySegmentFromPath(path11) {
|
|
1406
|
+
const first = path11.replace(/^\//, "").split("/")[0]?.trim();
|
|
1407
|
+
if (!first) return void 0;
|
|
1408
|
+
const open = first.indexOf("(");
|
|
1409
|
+
const entity = (open >= 0 ? first.slice(0, open) : first).trim();
|
|
1410
|
+
return entity || void 0;
|
|
1573
1411
|
}
|
|
1574
|
-
function
|
|
1575
|
-
|
|
1576
|
-
let returned;
|
|
1577
|
-
function visit(node) {
|
|
1578
|
-
if (node !== fn && (ts6.isFunctionDeclaration(node) || ts6.isArrowFunction(node) || ts6.isFunctionExpression(node)))
|
|
1579
|
-
return;
|
|
1580
|
-
if (!returned && ts6.isReturnStatement(node) && node.expression)
|
|
1581
|
-
returned = node.expression;
|
|
1582
|
-
if (!returned) ts6.forEachChild(node, visit);
|
|
1583
|
-
}
|
|
1584
|
-
visit(fn);
|
|
1585
|
-
if (!returned) return void 0;
|
|
1586
|
-
if (ts6.isIdentifier(returned)) return localBindings.get(returned.text);
|
|
1587
|
-
return findConnectInExpression(returned);
|
|
1412
|
+
function isMediaOrPropertySuffix(segment) {
|
|
1413
|
+
return ["file", "content", "$value", "metadata", "items"].includes(segment.toLowerCase());
|
|
1588
1414
|
}
|
|
1589
|
-
function
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1415
|
+
function looksLikeLowerCamelInvocation(segment) {
|
|
1416
|
+
const open = segment.indexOf("(");
|
|
1417
|
+
if (open <= 0) return false;
|
|
1418
|
+
const name = segment.slice(0, open).split(".").at(-1) ?? segment.slice(0, open);
|
|
1419
|
+
return /^[a-z][A-Za-z0-9_]*$/.test(name);
|
|
1593
1420
|
}
|
|
1594
|
-
function
|
|
1595
|
-
const
|
|
1596
|
-
for (
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
for (const decl of stmt.declarationList.declarations)
|
|
1604
|
-
if (ts6.isIdentifier(decl.name)) exports.set(decl.name.text, decl.name.text);
|
|
1605
|
-
}
|
|
1606
|
-
if (!ts6.isExportDeclaration(stmt) || !stmt.exportClause) continue;
|
|
1607
|
-
if (!ts6.isNamedExports(stmt.exportClause)) continue;
|
|
1608
|
-
for (const el of stmt.exportClause.elements)
|
|
1609
|
-
exports.set(el.name.text, el.propertyName?.text ?? el.name.text);
|
|
1421
|
+
function extractTemplatePlaceholders(text) {
|
|
1422
|
+
const keys = [];
|
|
1423
|
+
for (let index = 0; index < text.length - 1; index += 1) {
|
|
1424
|
+
if (text[index] !== "$" || text[index + 1] !== "{") continue;
|
|
1425
|
+
const close = matchingPlaceholderClose(text, index + 1);
|
|
1426
|
+
if (close === void 0) continue;
|
|
1427
|
+
const key = text.slice(index + 2, close).trim();
|
|
1428
|
+
if (key) keys.push(key);
|
|
1429
|
+
index = close;
|
|
1610
1430
|
}
|
|
1611
|
-
return
|
|
1431
|
+
return keys;
|
|
1612
1432
|
}
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
}
|
|
1627
|
-
if (ts6.isVariableStatement(stmt))
|
|
1628
|
-
for (const decl of stmt.declarationList.declarations) {
|
|
1629
|
-
if (!ts6.isIdentifier(decl.name) || !decl.initializer) continue;
|
|
1630
|
-
const helper = functionLikeInitializer(decl.initializer);
|
|
1631
|
-
if (helper) {
|
|
1632
|
-
const directReturn = directConnectFactFromFunctionLike(helper);
|
|
1633
|
-
if (directReturn)
|
|
1634
|
-
factsByLocal.set(decl.name.text, {
|
|
1635
|
-
...directReturn,
|
|
1636
|
-
sourceLine: lineOf4(sourceFileAst, decl)
|
|
1637
|
-
});
|
|
1638
|
-
for (const [returnedProperty, objectFact] of collectReturnedObjectBindings(helper))
|
|
1639
|
-
factsByLocal.set(`${decl.name.text}#${returnedProperty}`, {
|
|
1640
|
-
...objectFact,
|
|
1641
|
-
returnedProperty,
|
|
1642
|
-
sourceLine: lineOf4(sourceFileAst, decl)
|
|
1643
|
-
});
|
|
1644
|
-
continue;
|
|
1645
|
-
}
|
|
1646
|
-
const fact = findConnectInExpression(decl.initializer);
|
|
1647
|
-
if (fact)
|
|
1648
|
-
factsByLocal.set(decl.name.text, {
|
|
1649
|
-
...fact,
|
|
1650
|
-
sourceLine: lineOf4(sourceFileAst, decl)
|
|
1651
|
-
});
|
|
1433
|
+
function matchingClose(text, openIndex) {
|
|
1434
|
+
let depth = 0;
|
|
1435
|
+
let quote;
|
|
1436
|
+
for (let index = openIndex; index < text.length; index += 1) {
|
|
1437
|
+
const char = text[index];
|
|
1438
|
+
const prev = text[index - 1];
|
|
1439
|
+
if (quote) {
|
|
1440
|
+
if (prev === "\\") continue;
|
|
1441
|
+
if (quote === "template" && char === "$" && text[index + 1] === "{") {
|
|
1442
|
+
const close = matchingPlaceholderClose(text, index + 1);
|
|
1443
|
+
if (close === void 0) return void 0;
|
|
1444
|
+
index = close;
|
|
1445
|
+
continue;
|
|
1652
1446
|
}
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
if (
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
if (
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1447
|
+
if (quote === "single" && char === "'" || quote === "double" && char === '"' || quote === "template" && char === "`") quote = void 0;
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1450
|
+
if (char === "$" && text[index + 1] === "{") {
|
|
1451
|
+
const close = matchingPlaceholderClose(text, index + 1);
|
|
1452
|
+
if (close === void 0) return void 0;
|
|
1453
|
+
index = close;
|
|
1454
|
+
continue;
|
|
1455
|
+
}
|
|
1456
|
+
if (char === "'") {
|
|
1457
|
+
quote = "single";
|
|
1458
|
+
continue;
|
|
1459
|
+
}
|
|
1460
|
+
if (char === '"') {
|
|
1461
|
+
quote = "double";
|
|
1462
|
+
continue;
|
|
1463
|
+
}
|
|
1464
|
+
if (char === "`") {
|
|
1465
|
+
quote = "template";
|
|
1466
|
+
continue;
|
|
1467
|
+
}
|
|
1468
|
+
if (char === "(") depth += 1;
|
|
1469
|
+
if (char === ")") {
|
|
1470
|
+
depth -= 1;
|
|
1471
|
+
if (depth === 0) return index;
|
|
1472
|
+
if (depth < 0) return void 0;
|
|
1670
1473
|
}
|
|
1671
1474
|
}
|
|
1672
|
-
return
|
|
1475
|
+
return void 0;
|
|
1673
1476
|
}
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
if (ts6.isFunctionDeclaration(stmt) && stmt.name) {
|
|
1687
|
-
const directFact = directConnectFactFromFunctionLike(stmt);
|
|
1688
|
-
if (directFact) localDirectHelpers.set(stmt.name.text, { ...directFact, exportedName: stmt.name.text, sourceFile: normalizePath(filePath), sourceLine: lineOf4(sourceFileAst, stmt) });
|
|
1689
|
-
const rows2 = [];
|
|
1690
|
-
for (const [returnedProperty, fact] of collectReturnedObjectBindings(stmt))
|
|
1691
|
-
rows2.push({ ...fact, exportedName: stmt.name.text, returnedProperty, sourceFile: normalizePath(filePath), sourceLine: lineOf4(sourceFileAst, stmt) });
|
|
1692
|
-
if (rows2.length > 0) localObjectHelpers.set(stmt.name.text, rows2);
|
|
1693
|
-
}
|
|
1694
|
-
if (ts6.isVariableStatement(stmt)) {
|
|
1695
|
-
for (const decl of stmt.declarationList.declarations) {
|
|
1696
|
-
if (!ts6.isIdentifier(decl.name)) continue;
|
|
1697
|
-
const helper = functionLikeInitializer(decl.initializer);
|
|
1698
|
-
if (!helper) continue;
|
|
1699
|
-
const directFact = directConnectFactFromFunctionLike(helper);
|
|
1700
|
-
if (directFact) localDirectHelpers.set(decl.name.text, { ...directFact, exportedName: decl.name.text, sourceFile: normalizePath(filePath), sourceLine: lineOf4(sourceFileAst, decl) });
|
|
1701
|
-
const rows2 = [];
|
|
1702
|
-
for (const [returnedProperty, fact] of collectReturnedObjectBindings(helper))
|
|
1703
|
-
rows2.push({ ...fact, exportedName: decl.name.text, returnedProperty, sourceFile: normalizePath(filePath), sourceLine: lineOf4(sourceFileAst, decl) });
|
|
1704
|
-
if (rows2.length > 0) localObjectHelpers.set(decl.name.text, rows2);
|
|
1477
|
+
function matchingPlaceholderClose(text, openBraceIndex) {
|
|
1478
|
+
let depth = 0;
|
|
1479
|
+
let quote;
|
|
1480
|
+
for (let index = openBraceIndex; index < text.length; index += 1) {
|
|
1481
|
+
const char = text[index];
|
|
1482
|
+
const prev = text[index - 1];
|
|
1483
|
+
if (quote) {
|
|
1484
|
+
if (prev === "\\") continue;
|
|
1485
|
+
if (quote === "template" && char === "$" && text[index + 1] === "{") {
|
|
1486
|
+
depth += 1;
|
|
1487
|
+
index += 1;
|
|
1488
|
+
continue;
|
|
1705
1489
|
}
|
|
1490
|
+
if (quote === "single" && char === "'" || quote === "double" && char === '"' || quote === "template" && char === "`") quote = void 0;
|
|
1491
|
+
continue;
|
|
1492
|
+
}
|
|
1493
|
+
if (char === "'") {
|
|
1494
|
+
quote = "single";
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1497
|
+
if (char === '"') {
|
|
1498
|
+
quote = "double";
|
|
1499
|
+
continue;
|
|
1500
|
+
}
|
|
1501
|
+
if (char === "`") {
|
|
1502
|
+
quote = "template";
|
|
1503
|
+
continue;
|
|
1504
|
+
}
|
|
1505
|
+
if (char === "{") depth += 1;
|
|
1506
|
+
if (char === "}") {
|
|
1507
|
+
depth -= 1;
|
|
1508
|
+
if (depth === 0) return index;
|
|
1509
|
+
if (depth < 0) return void 0;
|
|
1706
1510
|
}
|
|
1707
1511
|
}
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1512
|
+
return void 0;
|
|
1513
|
+
}
|
|
1514
|
+
function topLevelQueryIndex(text) {
|
|
1515
|
+
let quote;
|
|
1516
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
1517
|
+
const char = text[index];
|
|
1518
|
+
const prev = text[index - 1];
|
|
1519
|
+
if (quote) {
|
|
1520
|
+
if (prev === "\\") continue;
|
|
1521
|
+
if (quote === "template" && char === "$" && text[index + 1] === "{") {
|
|
1522
|
+
const close = matchingPlaceholderClose(text, index + 1);
|
|
1523
|
+
if (close === void 0) return -1;
|
|
1524
|
+
index = close;
|
|
1525
|
+
continue;
|
|
1526
|
+
}
|
|
1527
|
+
if (quote === "single" && char === "'" || quote === "double" && char === '"' || quote === "template" && char === "`") quote = void 0;
|
|
1528
|
+
continue;
|
|
1529
|
+
}
|
|
1530
|
+
if (char === "$" && text[index + 1] === "{") {
|
|
1531
|
+
const close = matchingPlaceholderClose(text, index + 1);
|
|
1532
|
+
if (close === void 0) return -1;
|
|
1533
|
+
index = close;
|
|
1534
|
+
continue;
|
|
1535
|
+
}
|
|
1536
|
+
if (char === "'") {
|
|
1537
|
+
quote = "single";
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1540
|
+
if (char === '"') {
|
|
1541
|
+
quote = "double";
|
|
1542
|
+
continue;
|
|
1543
|
+
}
|
|
1544
|
+
if (char === "`") {
|
|
1545
|
+
quote = "template";
|
|
1546
|
+
continue;
|
|
1547
|
+
}
|
|
1548
|
+
if (char === "?") return index;
|
|
1743
1549
|
}
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1550
|
+
return -1;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
// src/parsers/imported-wrapper-parser.ts
|
|
1554
|
+
import path9 from "path";
|
|
1555
|
+
import ts6 from "typescript";
|
|
1556
|
+
async function parseImportedWrapperCalls(repoPath, filePath, source, serviceBindings) {
|
|
1557
|
+
const imports = await importsFor(repoPath, filePath, source);
|
|
1558
|
+
const importedByLocal = new Map(imports.filter((item) => item.sourceFile).map((item) => [item.localName, item]));
|
|
1559
|
+
const calls = collectImportedCalls(source, importedByLocal);
|
|
1560
|
+
const out = [];
|
|
1561
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1562
|
+
for (const call of calls) {
|
|
1563
|
+
if (!ts6.isIdentifier(call.expression)) continue;
|
|
1564
|
+
const imported = importedByLocal.get(call.expression.text);
|
|
1565
|
+
if (!imported?.sourceFile) continue;
|
|
1566
|
+
const spec = await loadWrapperSpec(repoPath, imported, cache, 0);
|
|
1567
|
+
const fact = spec ? wrapperCallFact(source, filePath, call, spec, serviceBindings) : void 0;
|
|
1568
|
+
if (fact) out.push(fact);
|
|
1749
1569
|
}
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1570
|
+
return out;
|
|
1571
|
+
}
|
|
1572
|
+
function collectImportedCalls(source, imports) {
|
|
1573
|
+
const calls = [];
|
|
1574
|
+
const visit = (node) => {
|
|
1575
|
+
if (ts6.isCallExpression(node) && ts6.isIdentifier(node.expression) && imports.has(node.expression.text)) calls.push(node);
|
|
1576
|
+
ts6.forEachChild(node, visit);
|
|
1577
|
+
};
|
|
1578
|
+
visit(source);
|
|
1579
|
+
return calls;
|
|
1580
|
+
}
|
|
1581
|
+
async function loadWrapperSpec(repoPath, imported, cache, depth) {
|
|
1582
|
+
if (!imported.sourceFile || depth > 5) return void 0;
|
|
1583
|
+
const key = `${imported.sourceFile}#${imported.exportedName}`;
|
|
1584
|
+
const existing = cache.get(key);
|
|
1585
|
+
if (existing) return existing;
|
|
1586
|
+
const pending = inspectWrapper(repoPath, imported.sourceFile, imported.exportedName, cache, depth);
|
|
1587
|
+
cache.set(key, pending);
|
|
1588
|
+
return pending;
|
|
1589
|
+
}
|
|
1590
|
+
async function inspectWrapper(repoPath, sourceFile, exportedName, cache, depth) {
|
|
1591
|
+
const source = await readSource(path9.join(repoPath, sourceFile));
|
|
1592
|
+
if (!source) return void 0;
|
|
1593
|
+
const named = findFunction(source, exportedName);
|
|
1594
|
+
if (!named) return void 0;
|
|
1595
|
+
const direct = directSendSpec(source, sourceFile, named.name, named.fn);
|
|
1596
|
+
if (direct) return direct;
|
|
1597
|
+
return nestedSendSpec(repoPath, sourceFile, source, named.name, named.fn, cache, depth);
|
|
1598
|
+
}
|
|
1599
|
+
function directSendSpec(source, sourceFile, name, fn) {
|
|
1600
|
+
const params = parameterNames(fn);
|
|
1601
|
+
const sends = [];
|
|
1602
|
+
visitFunctionBody(fn, (node) => {
|
|
1603
|
+
if (ts6.isCallExpression(node) && ts6.isPropertyAccessExpression(node.expression) && node.expression.name.text === "send") sends.push(node);
|
|
1604
|
+
});
|
|
1605
|
+
if (sends.length !== 1) return void 0;
|
|
1606
|
+
const send = sends[0];
|
|
1607
|
+
const receiver = send && ts6.isPropertyAccessExpression(send.expression) && ts6.isIdentifier(send.expression.expression) ? send.expression.expression.text : void 0;
|
|
1608
|
+
const object = send?.arguments[0];
|
|
1609
|
+
if (!receiver || !object || !ts6.isObjectLiteralExpression(object)) return void 0;
|
|
1610
|
+
const pathExpr = propertyExpression(object, "path");
|
|
1611
|
+
const methodExpr = propertyExpression(object, "method");
|
|
1612
|
+
const pathName = pathExpr && ts6.isIdentifier(pathExpr) ? pathExpr.text : void 0;
|
|
1613
|
+
const clientIndex = params.indexOf(receiver);
|
|
1614
|
+
const pathIndex = pathName ? params.indexOf(pathName) : -1;
|
|
1615
|
+
if (clientIndex < 0 || pathIndex < 0) return void 0;
|
|
1616
|
+
const methodName = methodExpr && ts6.isIdentifier(methodExpr) ? methodExpr.text : void 0;
|
|
1617
|
+
const methodIndex = methodName ? params.indexOf(methodName) : -1;
|
|
1618
|
+
return { clientIndex, pathIndex, methodIndex: methodIndex >= 0 ? methodIndex : void 0, methodLiteral: literal(methodExpr), sourceFile, sourceLine: lineOf3(source, fn), chain: [name] };
|
|
1619
|
+
}
|
|
1620
|
+
async function nestedSendSpec(repoPath, sourceFile, source, name, fn, cache, depth) {
|
|
1621
|
+
const imports = await importsFor(repoPath, sourceFile, source);
|
|
1622
|
+
const byLocal = new Map(imports.filter((item) => item.sourceFile).map((item) => [item.localName, item]));
|
|
1623
|
+
const calls = [];
|
|
1624
|
+
visitFunctionBody(fn, (node) => {
|
|
1625
|
+
if (ts6.isCallExpression(node) && ts6.isIdentifier(node.expression) && byLocal.has(node.expression.text)) calls.push(node);
|
|
1626
|
+
});
|
|
1627
|
+
if (calls.length !== 1) return void 0;
|
|
1628
|
+
const call = calls[0];
|
|
1629
|
+
const imported = call && ts6.isIdentifier(call.expression) ? byLocal.get(call.expression.text) : void 0;
|
|
1630
|
+
const nested = imported ? await loadWrapperSpec(repoPath, imported, cache, depth + 1) : void 0;
|
|
1631
|
+
if (!call || !nested) return void 0;
|
|
1632
|
+
const params = parameterNames(fn);
|
|
1633
|
+
const clientIndex = mappedParameterIndex(call.arguments[nested.clientIndex], params);
|
|
1634
|
+
const pathIndex = mappedParameterIndex(call.arguments[nested.pathIndex], params);
|
|
1635
|
+
if (clientIndex < 0 || pathIndex < 0) return void 0;
|
|
1636
|
+
const methodIndex = nested.methodIndex === void 0 ? void 0 : mappedParameterIndex(call.arguments[nested.methodIndex], params);
|
|
1637
|
+
return { clientIndex, pathIndex, methodIndex: methodIndex !== void 0 && methodIndex >= 0 ? methodIndex : void 0, methodLiteral: nested.methodLiteral, sourceFile, sourceLine: lineOf3(source, fn), chain: [name, ...nested.chain] };
|
|
1638
|
+
}
|
|
1639
|
+
function wrapperCallFact(source, filePath, call, spec, serviceBindings) {
|
|
1640
|
+
const client = call.arguments[spec.clientIndex];
|
|
1641
|
+
if (!client || !ts6.isIdentifier(client) || !serviceBindings.has(client.text)) return void 0;
|
|
1642
|
+
const pathValue = resolveCallerPath(call.arguments[spec.pathIndex], call);
|
|
1643
|
+
const methodValue = spec.methodIndex === void 0 ? spec.methodLiteral : literal(call.arguments[spec.methodIndex]);
|
|
1644
|
+
const method = (methodValue ?? "POST").toUpperCase();
|
|
1645
|
+
const rawPath = pathValue.rawExpression;
|
|
1646
|
+
const operationPathExpr = pathValue.value ? normalizeOperationPath(pathValue.value) : runtimePathExpression(rawPath);
|
|
1647
|
+
return {
|
|
1648
|
+
callType: "remote_action",
|
|
1649
|
+
serviceVariableName: client.text,
|
|
1650
|
+
method,
|
|
1651
|
+
operationPathExpr,
|
|
1652
|
+
payloadSummary: call.getText(source),
|
|
1653
|
+
sourceFile: normalizePath(filePath),
|
|
1654
|
+
sourceLine: lineOf3(source, call),
|
|
1655
|
+
confidence: operationPathExpr ? 0.85 : 0.5,
|
|
1656
|
+
unresolvedReason: pathValue.value ? void 0 : "dynamic_operation_path_identifier",
|
|
1657
|
+
evidence: {
|
|
1658
|
+
parser: "typescript_ast",
|
|
1659
|
+
classifier: pathValue.value ? "imported_wrapper_literal_path" : "imported_wrapper_dynamic_path",
|
|
1660
|
+
receiver: client.text,
|
|
1661
|
+
wrapperFunction: spec.chain[0],
|
|
1662
|
+
wrapperChain: spec.chain,
|
|
1663
|
+
callerSite: { sourceFile: normalizePath(filePath), sourceLine: lineOf3(source, call) },
|
|
1664
|
+
calleeSite: { sourceFile: spec.sourceFile, sourceLine: spec.sourceLine },
|
|
1665
|
+
rawPathExpression: rawPath,
|
|
1666
|
+
missingPathIdentifier: pathValue.value ? void 0 : rawPath,
|
|
1667
|
+
odataPathIntent: pathValue.value ? classifyODataPathIntent(operationPathExpr, method) : void 0
|
|
1668
|
+
}
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
function resolveCallerPath(expr, use) {
|
|
1672
|
+
if (!expr) return { sourceKind: "missing", rawExpression: "" };
|
|
1673
|
+
if (ts6.isStringLiteralLike(expr) || ts6.isNoSubstitutionTemplateLiteral(expr)) return { value: expr.text, sourceKind: "literal", rawExpression: expr.getText() };
|
|
1674
|
+
if (ts6.isTemplateExpression(expr)) return { value: expr.getText().slice(1, -1), sourceKind: "template", rawExpression: expr.getText() };
|
|
1675
|
+
if (ts6.isIdentifier(expr)) {
|
|
1676
|
+
const initializer = constInitializer(expr.text, use);
|
|
1677
|
+
if (initializer) {
|
|
1678
|
+
const resolved2 = resolveCallerPath(initializer, initializer);
|
|
1679
|
+
return { ...resolved2, sourceKind: "const", rawExpression: expr.text };
|
|
1789
1680
|
}
|
|
1790
1681
|
}
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1682
|
+
return { sourceKind: "dynamic", rawExpression: expr.getText() };
|
|
1683
|
+
}
|
|
1684
|
+
function constInitializer(name, use) {
|
|
1685
|
+
let found;
|
|
1686
|
+
const source = use.getSourceFile();
|
|
1687
|
+
const visit = (node) => {
|
|
1688
|
+
if (node.getStart(source) >= use.getStart(source)) return;
|
|
1689
|
+
if (ts6.isVariableDeclaration(node) && ts6.isIdentifier(node.name) && node.name.text === name && node.initializer && (node.parent.flags & ts6.NodeFlags.Const) !== 0) found = node.initializer;
|
|
1690
|
+
ts6.forEachChild(node, visit);
|
|
1691
|
+
};
|
|
1692
|
+
visit(source);
|
|
1693
|
+
return found;
|
|
1694
|
+
}
|
|
1695
|
+
function findFunction(source, exportedName) {
|
|
1696
|
+
const localName = exportedLocalName(source, exportedName);
|
|
1697
|
+
for (const statement of source.statements) {
|
|
1698
|
+
if (ts6.isFunctionDeclaration(statement) && statement.name?.text === localName) return { name: exportedName, fn: statement };
|
|
1699
|
+
if (!ts6.isVariableStatement(statement)) continue;
|
|
1700
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
1701
|
+
if (ts6.isIdentifier(declaration.name) && declaration.name.text === localName && declaration.initializer && (ts6.isArrowFunction(declaration.initializer) || ts6.isFunctionExpression(declaration.initializer))) return { name: exportedName, fn: declaration.initializer };
|
|
1702
|
+
}
|
|
1800
1703
|
}
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1704
|
+
return void 0;
|
|
1705
|
+
}
|
|
1706
|
+
function exportedLocalName(source, exportedName) {
|
|
1707
|
+
for (const statement of source.statements) {
|
|
1708
|
+
if (!ts6.isExportDeclaration(statement) || !statement.exportClause || !ts6.isNamedExports(statement.exportClause)) continue;
|
|
1709
|
+
const match = statement.exportClause.elements.find((item) => item.name.text === exportedName);
|
|
1710
|
+
if (match) return match.propertyName?.text ?? match.name.text;
|
|
1807
1711
|
}
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
callerVariable: targetName,
|
|
1829
|
-
sourceVariable: unwrapped.expression.text,
|
|
1830
|
-
returnedProperty: unwrapped.name.text,
|
|
1831
|
-
assignedFromProperty: unwrapped.getText(sourceFileAst),
|
|
1832
|
-
importSource: resolved2.imp?.sourceFile,
|
|
1833
|
-
exportedSymbol: resolved2.imp?.exportedName,
|
|
1834
|
-
helperSourceFile: resolved2.helper.sourceFile,
|
|
1835
|
-
helperSourceLine: resolved2.helper.sourceLine
|
|
1836
|
-
}
|
|
1837
|
-
]
|
|
1838
|
-
});
|
|
1839
|
-
return true;
|
|
1712
|
+
return exportedName;
|
|
1713
|
+
}
|
|
1714
|
+
function visitFunctionBody(fn, visitor) {
|
|
1715
|
+
const visit = (node) => {
|
|
1716
|
+
if (node !== fn && ts6.isFunctionLike(node)) return;
|
|
1717
|
+
visitor(node);
|
|
1718
|
+
ts6.forEachChild(node, visit);
|
|
1719
|
+
};
|
|
1720
|
+
if (fn.body) visit(fn.body);
|
|
1721
|
+
}
|
|
1722
|
+
function parameterNames(fn) {
|
|
1723
|
+
return fn.parameters.map((parameter) => ts6.isIdentifier(parameter.name) ? parameter.name.text : "");
|
|
1724
|
+
}
|
|
1725
|
+
function mappedParameterIndex(expr, parameters) {
|
|
1726
|
+
return expr && ts6.isIdentifier(expr) ? parameters.indexOf(expr.text) : -1;
|
|
1727
|
+
}
|
|
1728
|
+
function propertyExpression(object, key) {
|
|
1729
|
+
for (const property of object.properties) {
|
|
1730
|
+
if (ts6.isPropertyAssignment(property) && propertyName(property.name) === key) return property.initializer;
|
|
1731
|
+
if (ts6.isShorthandPropertyAssignment(property) && property.name.text === key) return property.name;
|
|
1840
1732
|
}
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1733
|
+
return void 0;
|
|
1734
|
+
}
|
|
1735
|
+
function propertyName(name) {
|
|
1736
|
+
return ts6.isIdentifier(name) || ts6.isStringLiteralLike(name) ? name.text : void 0;
|
|
1737
|
+
}
|
|
1738
|
+
function literal(expr) {
|
|
1739
|
+
return expr && (ts6.isStringLiteralLike(expr) || ts6.isNoSubstitutionTemplateLiteral(expr)) ? expr.text : void 0;
|
|
1740
|
+
}
|
|
1741
|
+
function normalizeOperationPath(value) {
|
|
1742
|
+
return value.startsWith("/") ? value : `/${value}`;
|
|
1743
|
+
}
|
|
1744
|
+
function runtimePathExpression(value) {
|
|
1745
|
+
return value ? `\${${value}}` : void 0;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
// src/parsers/outbound-call-parser.ts
|
|
1749
|
+
function lineOf4(text, idx) {
|
|
1750
|
+
return text.slice(0, idx).split("\n").length;
|
|
1751
|
+
}
|
|
1752
|
+
function entityFromExpression(expr) {
|
|
1753
|
+
if (!expr) return void 0;
|
|
1754
|
+
if (ts7.isIdentifier(expr) || ts7.isStringLiteral(expr) || ts7.isNoSubstitutionTemplateLiteral(expr)) return expr.text;
|
|
1755
|
+
if (ts7.isPropertyAccessExpression(expr) && expr.expression.kind === ts7.SyntaxKind.ThisKeyword) return expr.name.text;
|
|
1756
|
+
if (ts7.isElementAccessExpression(expr) && expr.argumentExpression && (ts7.isStringLiteral(expr.argumentExpression) || ts7.isNoSubstitutionTemplateLiteral(expr.argumentExpression))) return expr.argumentExpression.text;
|
|
1757
|
+
return void 0;
|
|
1758
|
+
}
|
|
1759
|
+
function expressionName(expr) {
|
|
1760
|
+
if (ts7.isIdentifier(expr)) return expr.text;
|
|
1761
|
+
if (ts7.isPropertyAccessExpression(expr)) return `${expressionName(expr.expression)}.${expr.name.text}`;
|
|
1762
|
+
return expr.getText();
|
|
1763
|
+
}
|
|
1764
|
+
function variableInitializers(source) {
|
|
1765
|
+
const initializers = /* @__PURE__ */ new Map();
|
|
1766
|
+
for (const statement of source.statements) {
|
|
1767
|
+
if (!ts7.isVariableStatement(statement) || (statement.declarationList.flags & ts7.NodeFlags.Const) === 0) continue;
|
|
1768
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
1769
|
+
if (ts7.isIdentifier(declaration.name) && declaration.initializer) initializers.set(declaration.name.text, declaration.initializer);
|
|
1865
1770
|
}
|
|
1866
1771
|
}
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1772
|
+
return initializers;
|
|
1773
|
+
}
|
|
1774
|
+
function queryEntityFromAst(expr, initializers = /* @__PURE__ */ new Map()) {
|
|
1775
|
+
if (ts7.isParenthesizedExpression(expr) || ts7.isAwaitExpression(expr)) return queryEntityFromAst(expr.expression, initializers);
|
|
1776
|
+
if (ts7.isIdentifier(expr) && initializers.has(expr.text)) return queryEntityFromAst(initializers.get(expr.text), initializers);
|
|
1777
|
+
if (ts7.isCallExpression(expr)) {
|
|
1778
|
+
const name = expressionName(expr.expression);
|
|
1779
|
+
if (name === "cds.run") return queryEntityFromAst(expr.arguments[0], initializers);
|
|
1780
|
+
if (["SELECT.one.from", "SELECT.from", "SELECT.one", "INSERT.into", "UPSERT.into", "DELETE.from", "UPDATE.entity"].includes(name)) return entityFromExpression(expr.arguments[0]);
|
|
1781
|
+
if (name === "UPDATE") return entityFromExpression(expr.arguments[0]);
|
|
1782
|
+
const receiver = ts7.isPropertyAccessExpression(expr.expression) ? expr.expression.expression : void 0;
|
|
1783
|
+
if (receiver) return queryEntityFromAst(receiver, initializers);
|
|
1784
|
+
}
|
|
1785
|
+
return void 0;
|
|
1786
|
+
}
|
|
1787
|
+
function extractQueryEntity(expr) {
|
|
1788
|
+
const source = ts7.createSourceFile("query.ts", `const __query = (${expr});`, ts7.ScriptTarget.Latest, true, ts7.ScriptKind.TS);
|
|
1789
|
+
const initializers = variableInitializers(source);
|
|
1790
|
+
let found;
|
|
1791
|
+
const visit = (node) => {
|
|
1792
|
+
if (found) return;
|
|
1793
|
+
if (ts7.isParenthesizedExpression(node)) found = queryEntityFromAst(node.expression, initializers);
|
|
1794
|
+
ts7.forEachChild(node, visit);
|
|
1795
|
+
};
|
|
1796
|
+
visit(source);
|
|
1797
|
+
return found;
|
|
1798
|
+
}
|
|
1799
|
+
function queryWarning(expr) {
|
|
1800
|
+
if (/^\s*[`'"]/.test(expr)) return "raw_sql_or_cql_expression";
|
|
1801
|
+
if (/^\s*\w+\s*$/.test(expr)) return "query_variable_without_static_initializer";
|
|
1802
|
+
return "dynamic_entity_expression";
|
|
1803
|
+
}
|
|
1804
|
+
function parserEvidence(source, node, extra) {
|
|
1805
|
+
return { parser: "typescript_ast", startOffset: node.getStart(source), endOffset: node.getEnd(), ...extra };
|
|
1806
|
+
}
|
|
1807
|
+
function isStringLike(expr) {
|
|
1808
|
+
return Boolean(expr && (ts7.isStringLiteral(expr) || ts7.isNoSubstitutionTemplateLiteral(expr)));
|
|
1809
|
+
}
|
|
1810
|
+
function literalText(expr) {
|
|
1811
|
+
if (isStringLike(expr)) return expr.text;
|
|
1812
|
+
return void 0;
|
|
1813
|
+
}
|
|
1814
|
+
function objectPropertyText(object, key) {
|
|
1815
|
+
const prop = object.properties.find(
|
|
1816
|
+
(property) => ts7.isPropertyAssignment(property) && nameOfProperty(property.name) === key || ts7.isShorthandPropertyAssignment(property) && property.name.text === key
|
|
1817
|
+
);
|
|
1818
|
+
if (!prop) return void 0;
|
|
1819
|
+
return ts7.isShorthandPropertyAssignment(prop) ? prop.name.text : prop.initializer.getText();
|
|
1820
|
+
}
|
|
1821
|
+
function objectPropertyIsShorthand(object, key) {
|
|
1822
|
+
return object.properties.some((property) => ts7.isShorthandPropertyAssignment(property) && property.name.text === key);
|
|
1823
|
+
}
|
|
1824
|
+
function nameOfProperty(name) {
|
|
1825
|
+
if (ts7.isIdentifier(name) || ts7.isStringLiteral(name) || ts7.isNumericLiteral(name)) return name.text;
|
|
1826
|
+
return void 0;
|
|
1827
|
+
}
|
|
1828
|
+
var maxAliasDepth = 5;
|
|
1829
|
+
function safeRaw(expr) {
|
|
1830
|
+
if (ts7.isStringLiteral(expr) || ts7.isNoSubstitutionTemplateLiteral(expr) || ts7.isIdentifier(expr) || ts7.isTemplateExpression(expr)) return expr.getText(expr.getSourceFile());
|
|
1831
|
+
return void 0;
|
|
1832
|
+
}
|
|
1833
|
+
function placeholders2(expr) {
|
|
1834
|
+
return expr.templateSpans.map((span) => span.expression.getText(expr.getSourceFile()));
|
|
1835
|
+
}
|
|
1836
|
+
function isFunctionLikeScope(node) {
|
|
1837
|
+
return ts7.isFunctionLike(node) || ts7.isSourceFile(node);
|
|
1838
|
+
}
|
|
1839
|
+
function nodeContains(parent, child) {
|
|
1840
|
+
const source = child.getSourceFile();
|
|
1841
|
+
return child.getStart(source) >= parent.getStart(source) && child.getEnd() <= parent.getEnd();
|
|
1842
|
+
}
|
|
1843
|
+
function declarationScope(node) {
|
|
1844
|
+
if (ts7.isParameter(node)) return node.parent;
|
|
1845
|
+
if (ts7.isCatchClause(node.parent) && node.parent.variableDeclaration === node) return node.parent;
|
|
1846
|
+
const list = node.parent;
|
|
1847
|
+
const blockScoped = (list.flags & (ts7.NodeFlags.Const | ts7.NodeFlags.Let)) !== 0;
|
|
1848
|
+
let current = list.parent;
|
|
1849
|
+
if (!blockScoped) {
|
|
1850
|
+
while (current.parent && !isFunctionLikeScope(current)) current = current.parent;
|
|
1851
|
+
return current;
|
|
1899
1852
|
}
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1853
|
+
while (current.parent && !ts7.isBlock(current) && !ts7.isSourceFile(current) && !ts7.isModuleBlock(current) && !ts7.isCaseBlock(current) && !isLoopInitializerScope(node, current) && !isFunctionLikeScope(current)) current = current.parent;
|
|
1854
|
+
return current;
|
|
1855
|
+
}
|
|
1856
|
+
function isLoopInitializerScope(declaration, scope) {
|
|
1857
|
+
const list = declaration.parent;
|
|
1858
|
+
return ts7.isForStatement(scope) && scope.initializer === list || (ts7.isForInStatement(scope) || ts7.isForOfStatement(scope)) && scope.initializer === list;
|
|
1859
|
+
}
|
|
1860
|
+
function catchBindingScope(declaration) {
|
|
1861
|
+
if (ts7.isParameter(declaration)) return void 0;
|
|
1862
|
+
return ts7.isCatchClause(declaration.parent) && declaration.parent.variableDeclaration === declaration ? declaration.parent : void 0;
|
|
1863
|
+
}
|
|
1864
|
+
function isAccessibleDeclaration(declaration, use) {
|
|
1865
|
+
const source = use.getSourceFile();
|
|
1866
|
+
if (declaration.name.getStart(source) >= use.getStart(source)) return false;
|
|
1867
|
+
const catchScope = catchBindingScope(declaration);
|
|
1868
|
+
if (catchScope) return nodeContains(catchScope.block, use);
|
|
1869
|
+
const scope = declarationScope(declaration);
|
|
1870
|
+
if (ts7.isForStatement(scope) || ts7.isForInStatement(scope) || ts7.isForOfStatement(scope)) return nodeContains(scope.statement, use);
|
|
1871
|
+
return ts7.isSourceFile(scope) || nodeContains(scope, use);
|
|
1872
|
+
}
|
|
1873
|
+
function resolveBinding(identifier, use) {
|
|
1874
|
+
const source = use.getSourceFile();
|
|
1875
|
+
let best;
|
|
1876
|
+
const visit = (node) => {
|
|
1877
|
+
if (ts7.isVariableDeclaration(node) && ts7.isIdentifier(node.name) && node.name.text === identifier.text && isAccessibleDeclaration(node, use)) best = node;
|
|
1878
|
+
if (ts7.isParameter(node) && ts7.isIdentifier(node.name) && node.name.text === identifier.text && isAccessibleDeclaration(node, use)) best = node;
|
|
1879
|
+
ts7.forEachChild(node, visit);
|
|
1880
|
+
};
|
|
1881
|
+
visit(source);
|
|
1882
|
+
if (!best) return { immutable: false, evidence: ["binding_not_found"] };
|
|
1883
|
+
const immutable = ts7.isVariableDeclaration(best) && (best.parent.flags & ts7.NodeFlags.Const) !== 0;
|
|
1884
|
+
return { declaration: best, initializer: ts7.isVariableDeclaration(best) ? best.initializer : void 0, immutable, evidence: [immutable ? "lexical_const_binding_before_use" : "lexical_mutable_or_parameter_binding"] };
|
|
1885
|
+
}
|
|
1886
|
+
function resolveExpression(expr, use, policy, depth = 0, seen = /* @__PURE__ */ new Set()) {
|
|
1887
|
+
if (!expr) return { status: "unknown", sourceKind: "dynamic_expression", placeholderKeys: [], evidence: ["expression_missing"] };
|
|
1888
|
+
if (ts7.isStringLiteral(expr)) return { status: "static", sourceKind: "string_literal", value: expr.text, rawExpression: safeRaw(expr), placeholderKeys: [], evidence: ["string_literal"] };
|
|
1889
|
+
if (ts7.isNoSubstitutionTemplateLiteral(expr)) return { status: "static", sourceKind: "no_substitution_template", value: expr.text, rawExpression: safeRaw(expr), placeholderKeys: [], evidence: ["no_substitution_template"] };
|
|
1890
|
+
if (ts7.isTemplateExpression(expr)) {
|
|
1891
|
+
const keys = placeholders2(expr);
|
|
1892
|
+
if (policy === "operation_path") return { status: "dynamic", sourceKind: "template_with_substitutions", value: stripQuotes(expr.getText(expr.getSourceFile())), rawExpression: safeRaw(expr), placeholderKeys: keys, evidence: ["operation_path_template_placeholders_retained"] };
|
|
1893
|
+
return { status: "dynamic", sourceKind: "template_with_substitutions", placeholderKeys: keys, evidence: ["template_substitutions_not_static_external_target"] };
|
|
1931
1894
|
}
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
if (
|
|
1937
|
-
|
|
1938
|
-
const
|
|
1939
|
-
|
|
1940
|
-
const container = unwrapIdentityExpression(first);
|
|
1941
|
-
if (!ts6.isArrayLiteralExpression(container)) return void 0;
|
|
1942
|
-
return { elements: container.elements, promiseAll: true };
|
|
1895
|
+
if (ts7.isIdentifier(expr)) {
|
|
1896
|
+
if (depth >= maxAliasDepth) return { status: "unknown", sourceKind: "const_alias", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: ["alias_depth_exceeded"], constName: expr.text };
|
|
1897
|
+
const binding = resolveBinding(expr, use);
|
|
1898
|
+
if (!binding.declaration || !binding.initializer || !binding.immutable) return { status: "dynamic", sourceKind: "dynamic_expression", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: binding.evidence, constName: expr.text };
|
|
1899
|
+
if (seen.has(binding.declaration)) return { status: "unknown", sourceKind: "const_alias", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: ["alias_cycle_detected"], constName: expr.text };
|
|
1900
|
+
seen.add(binding.declaration);
|
|
1901
|
+
const resolved2 = resolveExpression(binding.initializer, binding.declaration, policy, depth + 1, seen);
|
|
1902
|
+
return { ...resolved2, sourceKind: "const_alias", rawExpression: safeRaw(expr), constName: expr.text, evidence: [...binding.evidence, ...resolved2.evidence] };
|
|
1943
1903
|
}
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1904
|
+
return { status: "dynamic", sourceKind: "dynamic_expression", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: [`unsupported_${ts7.SyntaxKind[expr.kind] ?? "expression"}`] };
|
|
1905
|
+
}
|
|
1906
|
+
function staticExpressionText(expr, initializers) {
|
|
1907
|
+
if (!expr) return void 0;
|
|
1908
|
+
if (isStringLike(expr)) return expr.text;
|
|
1909
|
+
if (ts7.isIdentifier(expr) && initializers.has(expr.text)) return staticExpressionText(initializers.get(expr.text), initializers);
|
|
1910
|
+
return void 0;
|
|
1911
|
+
}
|
|
1912
|
+
function staticPathExpression(expr, use) {
|
|
1913
|
+
const resolution = resolveExpression(expr, use, "operation_path");
|
|
1914
|
+
const sourceKind = resolution.sourceKind === "const_alias" ? "const" : resolution.sourceKind === "template_with_substitutions" || resolution.sourceKind === "no_substitution_template" ? "template" : resolution.status === "static" ? "literal" : "dynamic";
|
|
1915
|
+
return { text: resolution.value, sourceKind, rawExpression: resolution.rawExpression, constName: resolution.constName, resolution };
|
|
1916
|
+
}
|
|
1917
|
+
function operationPathFromStatic(text) {
|
|
1918
|
+
return text ? `/${stripQuotes(text).replace(/^\//, "")}` : void 0;
|
|
1919
|
+
}
|
|
1920
|
+
function staticPathCandidates(identifier, call, method) {
|
|
1921
|
+
const binding = resolveBinding(identifier, call);
|
|
1922
|
+
const declaration = binding.declaration;
|
|
1923
|
+
if (!declaration) return void 0;
|
|
1924
|
+
const source = call.getSourceFile();
|
|
1925
|
+
const paths = [];
|
|
1926
|
+
let hasDynamicAssignments = false;
|
|
1927
|
+
const addExpr = (expr, origin) => {
|
|
1928
|
+
const resolved2 = staticPathExpression(expr, origin);
|
|
1929
|
+
if (resolved2.text) paths.push(operationPathFromStatic(resolved2.text) ?? resolved2.text);
|
|
1930
|
+
else hasDynamicAssignments = true;
|
|
1931
|
+
};
|
|
1932
|
+
if (ts7.isVariableDeclaration(declaration)) addExpr(declaration.initializer, declaration);
|
|
1933
|
+
const visit = (node) => {
|
|
1934
|
+
if (node !== source && isFunctionLikeScope(node) && !nodeContains(node, call)) return;
|
|
1935
|
+
if (node.getStart(source) >= call.getStart(source)) return;
|
|
1936
|
+
if (ts7.isBinaryExpression(node) && node.operatorToken.kind === ts7.SyntaxKind.EqualsToken && ts7.isIdentifier(node.left)) {
|
|
1937
|
+
const target = resolveBinding(node.left, node);
|
|
1938
|
+
if (target.declaration === declaration) addExpr(node.right, node);
|
|
1968
1939
|
}
|
|
1940
|
+
ts7.forEachChild(node, visit);
|
|
1941
|
+
};
|
|
1942
|
+
visit(source);
|
|
1943
|
+
const candidatePaths = [...new Set(paths)];
|
|
1944
|
+
if (candidatePaths.length === 0) return void 0;
|
|
1945
|
+
const normalizedCandidateOperations = [...new Set(candidatePaths.map((candidate) => classifyODataPathIntent(candidate, method).topLevelOperationName).filter((value) => Boolean(value)))];
|
|
1946
|
+
return { candidatePaths, normalizedCandidateOperations, candidateSourceKind: "static candidates observed before call in lexical scope", candidateIdentifier: identifier.text, hasDynamicAssignments, conservativeReason: hasDynamicAssignments ? "dynamic_assignment_observed" : normalizedCandidateOperations.length > 1 ? "candidate_tie" : void 0 };
|
|
1947
|
+
}
|
|
1948
|
+
function destinationExpressionShape(expr) {
|
|
1949
|
+
if (!expr) return void 0;
|
|
1950
|
+
if (ts7.isIdentifier(expr)) return "identifier";
|
|
1951
|
+
if (ts7.isPropertyAccessExpression(expr) || ts7.isElementAccessExpression(expr)) return "property_read";
|
|
1952
|
+
if (ts7.isCallExpression(expr)) return "function_call";
|
|
1953
|
+
if (ts7.isConditionalExpression(expr)) return "conditional";
|
|
1954
|
+
if (ts7.isBinaryExpression(expr)) return "binary_expression";
|
|
1955
|
+
if (ts7.isTemplateExpression(expr)) return "template_expression";
|
|
1956
|
+
return ts7.SyntaxKind[expr.kind] ?? "expression";
|
|
1957
|
+
}
|
|
1958
|
+
function staticConditionalCandidates(expr, initializers) {
|
|
1959
|
+
const resolved2 = expr && ts7.isIdentifier(expr) && initializers.has(expr.text) ? initializers.get(expr.text) : expr;
|
|
1960
|
+
if (!resolved2 || !ts7.isConditionalExpression(resolved2)) return void 0;
|
|
1961
|
+
const left = staticExpressionText(resolved2.whenTrue, initializers);
|
|
1962
|
+
const right = staticExpressionText(resolved2.whenFalse, initializers);
|
|
1963
|
+
if (!left || !right) return void 0;
|
|
1964
|
+
return [.../* @__PURE__ */ new Set([left, right])];
|
|
1965
|
+
}
|
|
1966
|
+
function propertyInitializer(object, key) {
|
|
1967
|
+
for (const property of object.properties) {
|
|
1968
|
+
if (ts7.isPropertyAssignment(property) && nameOfProperty(property.name) === key) return property.initializer;
|
|
1969
|
+
if (ts7.isShorthandPropertyAssignment(property) && property.name.text === key) return property.name;
|
|
1969
1970
|
}
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1971
|
+
return void 0;
|
|
1972
|
+
}
|
|
1973
|
+
function httpMethodFromObject(object, use) {
|
|
1974
|
+
const text = resolveExpression(propertyInitializer(object, "method"), use, "literal").value;
|
|
1975
|
+
return text ? stripQuotes(text).toUpperCase() : void 0;
|
|
1976
|
+
}
|
|
1977
|
+
var supportedHttpMethods = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]);
|
|
1978
|
+
function safeOperationName(value) {
|
|
1979
|
+
if (!value || !/^[A-Za-z_$][\w$]*(?:[./][A-Za-z_$][\w$]*)*$/.test(value)) return void 0;
|
|
1980
|
+
return operationPathFromStatic(value);
|
|
1981
|
+
}
|
|
1982
|
+
function hasTemplatePlaceholder(value) {
|
|
1983
|
+
return /\$\{|%7B|%7D/i.test(value);
|
|
1984
|
+
}
|
|
1985
|
+
function urlTargetFromExpression(expr, use) {
|
|
1986
|
+
const resolved2 = resolveExpression(expr, use, "external");
|
|
1987
|
+
if (resolved2.status === "static" && resolved2.value && !hasTemplatePlaceholder(resolved2.value)) return { kind: "static_url", expression: resolved2.value, dynamic: false, sourceKind: resolved2.sourceKind };
|
|
1988
|
+
if (expr) return { kind: "url_expression", dynamic: true, expression: `${resolved2.sourceKind}:${resolved2.placeholderKeys.join("|")}`, expressionShape: resolved2.sourceKind, placeholderKeys: resolved2.placeholderKeys };
|
|
1989
|
+
return { kind: "unknown", dynamic: false };
|
|
1990
|
+
}
|
|
1991
|
+
function destinationTargetFromExpression(expr, use) {
|
|
1992
|
+
const resolved2 = resolveExpression(expr, use, "external");
|
|
1993
|
+
const text = resolved2.value;
|
|
1994
|
+
if (resolved2.status === "static" && text && !hasTemplatePlaceholder(text)) return { kind: "destination", expression: text, dynamic: false, sourceKind: resolved2.sourceKind };
|
|
1995
|
+
const candidates = staticConditionalCandidates(expr, /* @__PURE__ */ new Map());
|
|
1996
|
+
if (candidates) return { kind: "destination", dynamic: true, expressionShape: "conditional", candidateLiterals: candidates };
|
|
1997
|
+
const shape = destinationExpressionShape(expr);
|
|
1998
|
+
if (shape) return { kind: "destination", dynamic: true, expressionShape: shape };
|
|
1999
|
+
return void 0;
|
|
2000
|
+
}
|
|
2001
|
+
function externalHttpEvidence(node, source) {
|
|
2002
|
+
const expr = node.expression;
|
|
2003
|
+
const exprText = expr.getText(source);
|
|
2004
|
+
if (exprText === "useOrFetchDestination") {
|
|
2005
|
+
const objectArg = node.arguments[0];
|
|
2006
|
+
if (objectArg && ts7.isObjectLiteralExpression(objectArg)) {
|
|
2007
|
+
const destination = destinationTargetFromExpression(propertyInitializer(objectArg, "destinationName"), node);
|
|
2008
|
+
return { externalTarget: destination ?? { kind: "unknown", dynamic: false }, classifier: "sap_destination_lookup", sourceCallShape: "useOrFetchDestination" };
|
|
1981
2009
|
}
|
|
1982
2010
|
}
|
|
1983
|
-
|
|
1984
|
-
const
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
2011
|
+
if (exprText === "executeHttpRequest") {
|
|
2012
|
+
const destination = destinationTargetFromExpression(node.arguments[0], node);
|
|
2013
|
+
const config = node.arguments[1];
|
|
2014
|
+
const method = config && ts7.isObjectLiteralExpression(config) ? httpMethodFromObject(config, node) : void 0;
|
|
2015
|
+
const url = config && ts7.isObjectLiteralExpression(config) ? urlTargetFromExpression(propertyInitializer(config, "url"), node) : { kind: "unknown", dynamic: false };
|
|
2016
|
+
return { method, externalTarget: destination ? { ...url, destination } : url, classifier: "sap_execute_http_request", sourceCallShape: "executeHttpRequest" };
|
|
2017
|
+
}
|
|
2018
|
+
if (exprText === "axios") {
|
|
2019
|
+
const config = node.arguments[0];
|
|
2020
|
+
if (config && ts7.isObjectLiteralExpression(config)) {
|
|
2021
|
+
const method = httpMethodFromObject(config, node);
|
|
2022
|
+
return { method, externalTarget: urlTargetFromExpression(propertyInitializer(config, "url"), node), classifier: "axios_config_call", sourceCallShape: "axios(config)" };
|
|
1992
2023
|
}
|
|
2024
|
+
return { externalTarget: { kind: "unknown", dynamic: false }, classifier: "axios_unknown_call", sourceCallShape: "axios(...)" };
|
|
1993
2025
|
}
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
events.push({ pos: node.getStart(sourceFileAst), node });
|
|
1999
|
-
ts6.forEachChild(node, collectEvents);
|
|
2026
|
+
if (exprText === "fetch") {
|
|
2027
|
+
const init = node.arguments[1];
|
|
2028
|
+
const method = init && ts7.isObjectLiteralExpression(init) ? httpMethodFromObject(init, node) : void 0;
|
|
2029
|
+
return { method, externalTarget: urlTargetFromExpression(node.arguments[0], node), classifier: "fetch_call", sourceCallShape: "fetch" };
|
|
2000
2030
|
}
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2031
|
+
if (ts7.isPropertyAccessExpression(expr) && ["get", "post", "put", "patch", "delete", "head"].includes(expr.name.text) && expr.expression.getText(source) === "axios") {
|
|
2032
|
+
return { method: expr.name.text.toUpperCase(), externalTarget: urlTargetFromExpression(node.arguments[0], node), classifier: "axios_member_call", sourceCallShape: `axios.${expr.name.text}` };
|
|
2033
|
+
}
|
|
2034
|
+
return void 0;
|
|
2035
|
+
}
|
|
2036
|
+
function collectServiceVariables(source) {
|
|
2037
|
+
const vars = /* @__PURE__ */ new Set(["cds", "messaging", "messageClient", "eventClient"]);
|
|
2038
|
+
const visit = (node) => {
|
|
2039
|
+
if (ts7.isVariableDeclaration(node) && ts7.isIdentifier(node.name) && node.initializer) {
|
|
2040
|
+
const text = node.initializer.getText(source);
|
|
2041
|
+
if (/cds\.connect\.(to|messaging)\s*\(/.test(text)) vars.add(node.name.text);
|
|
2042
|
+
}
|
|
2043
|
+
ts7.forEachChild(node, visit);
|
|
2044
|
+
};
|
|
2045
|
+
visit(source);
|
|
2046
|
+
return vars;
|
|
2047
|
+
}
|
|
2048
|
+
function receiverName(expr) {
|
|
2049
|
+
if (ts7.isIdentifier(expr)) return expr.text;
|
|
2050
|
+
if (ts7.isPropertyAccessExpression(expr)) return expr.getText(sourceOf(expr));
|
|
2051
|
+
return void 0;
|
|
2052
|
+
}
|
|
2053
|
+
function sourceOf(node) {
|
|
2054
|
+
return node.getSourceFile();
|
|
2055
|
+
}
|
|
2056
|
+
function rootReceiverName(expr) {
|
|
2057
|
+
if (ts7.isIdentifier(expr)) return expr.text;
|
|
2058
|
+
if (ts7.isPropertyAccessExpression(expr)) return rootReceiverName(expr.expression);
|
|
2059
|
+
if (ts7.isCallExpression(expr)) return rootReceiverName(expr.expression);
|
|
2060
|
+
return void 0;
|
|
2061
|
+
}
|
|
2062
|
+
function isSupportedEventReceiver(receiver, rootReceiver, serviceVariables) {
|
|
2063
|
+
const candidate = rootReceiver ?? receiver;
|
|
2064
|
+
if (!candidate) return false;
|
|
2065
|
+
if (candidate === "cds") return true;
|
|
2066
|
+
if (serviceVariables.has(candidate)) return true;
|
|
2067
|
+
if (receiver && serviceVariables.has(receiver)) return true;
|
|
2068
|
+
if (/^(srv|service|serviceClient|messaging|messageClient|eventClient)$/.test(candidate)) return true;
|
|
2069
|
+
return false;
|
|
2070
|
+
}
|
|
2071
|
+
function collectWrapperSpecs(source) {
|
|
2072
|
+
const specs = /* @__PURE__ */ new Map();
|
|
2073
|
+
const serviceVariables = collectServiceVariables(source);
|
|
2074
|
+
const calledNames = /* @__PURE__ */ new Set();
|
|
2075
|
+
const collectCalls = (node) => {
|
|
2076
|
+
if (ts7.isCallExpression(node)) {
|
|
2077
|
+
if (ts7.isIdentifier(node.expression)) calledNames.add(node.expression.text);
|
|
2078
|
+
if (ts7.isCallExpression(node.expression) && ts7.isIdentifier(node.expression.expression)) calledNames.add(node.expression.expression.text);
|
|
2018
2079
|
}
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2080
|
+
ts7.forEachChild(node, collectCalls);
|
|
2081
|
+
};
|
|
2082
|
+
collectCalls(source);
|
|
2083
|
+
const scanFunction = (name, fn) => {
|
|
2084
|
+
if (!calledNames.has(name)) return;
|
|
2085
|
+
const params = fn.parameters.map((param) => ts7.isIdentifier(param.name) ? param.name.text : void 0);
|
|
2086
|
+
const sends = [];
|
|
2087
|
+
const visit = (node) => {
|
|
2088
|
+
if (ts7.isCallExpression(node) && ts7.isPropertyAccessExpression(node.expression) && node.expression.name.text === "send" && ts7.isIdentifier(node.expression.expression)) {
|
|
2089
|
+
const objectArg = node.arguments[0];
|
|
2090
|
+
if (objectArg && ts7.isObjectLiteralExpression(objectArg)) {
|
|
2091
|
+
const pathProp = propertyInitializer(objectArg, "path");
|
|
2092
|
+
const methodProp = propertyInitializer(objectArg, "method");
|
|
2093
|
+
const pathName = pathProp && ts7.isIdentifier(pathProp) ? pathProp.text : void 0;
|
|
2094
|
+
const methodName = methodProp && ts7.isIdentifier(methodProp) ? methodProp.text : void 0;
|
|
2095
|
+
const methodLiteral = resolveExpression(methodProp, node, "literal").value;
|
|
2096
|
+
if (pathName) sends.push({ client: node.expression.expression.text, path: pathName, method: methodName, methodLiteral, start: node.getStart(source), end: node.getEnd() });
|
|
2097
|
+
}
|
|
2025
2098
|
}
|
|
2026
|
-
if (
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2099
|
+
if (ts7.isCallExpression(node) && ts7.isIdentifier(node.expression) && specs.has(node.expression.text)) {
|
|
2100
|
+
const nested = specs.get(node.expression.text);
|
|
2101
|
+
const pathArg = nested ? node.arguments[nested.pathIndex] : void 0;
|
|
2102
|
+
const clientArg = nested?.clientIndex === void 0 ? void 0 : node.arguments[nested.clientIndex];
|
|
2103
|
+
const pathName = pathArg && ts7.isIdentifier(pathArg) ? pathArg.text : void 0;
|
|
2104
|
+
const clientName = clientArg && ts7.isIdentifier(clientArg) ? clientArg.text : nested?.clientName;
|
|
2105
|
+
if (nested && pathName && clientName) sends.push({ client: clientName, path: pathName, method: nested.methodName, methodLiteral: nested.methodLiteral, nestedWrapperFunction: node.expression.text, start: node.getStart(source), end: node.getEnd() });
|
|
2106
|
+
}
|
|
2107
|
+
ts7.forEachChild(node, visit);
|
|
2108
|
+
};
|
|
2109
|
+
visit(fn);
|
|
2110
|
+
if (sends.length !== 1) return;
|
|
2111
|
+
const found = sends[0];
|
|
2112
|
+
const clientIndex = params.indexOf(found.client);
|
|
2113
|
+
const pathIndex = params.indexOf(found.path);
|
|
2114
|
+
const methodIndex = found.method ? params.indexOf(found.method) : -1;
|
|
2115
|
+
const capturesKnownClient = serviceVariables.has(found.client) || /^(srv|service|serviceClient|client|.*Client)$/.test(found.client);
|
|
2116
|
+
if (pathIndex >= 0 && (clientIndex >= 0 || capturesKnownClient)) specs.set(name, { clientIndex: clientIndex >= 0 ? clientIndex : void 0, clientName: clientIndex >= 0 ? void 0 : found.client, pathIndex, methodIndex: methodIndex >= 0 ? methodIndex : void 0, methodName: found.method, methodLiteral: found.methodLiteral, nestedWrapperFunction: found.nestedWrapperFunction, definitionLine: lineOf4(source.text, fn.getStart(source)), internalStart: found.start, internalEnd: found.end });
|
|
2117
|
+
};
|
|
2118
|
+
const visitTop = (node) => {
|
|
2119
|
+
if (ts7.isFunctionDeclaration(node) && node.name) scanFunction(node.name.text, node);
|
|
2120
|
+
if (ts7.isVariableDeclaration(node) && ts7.isIdentifier(node.name) && node.initializer && (ts7.isArrowFunction(node.initializer) || ts7.isFunctionExpression(node.initializer))) scanFunction(node.name.text, node.initializer);
|
|
2121
|
+
ts7.forEachChild(node, visitTop);
|
|
2122
|
+
};
|
|
2123
|
+
visitTop(source);
|
|
2124
|
+
return specs;
|
|
2037
2125
|
}
|
|
2038
|
-
function
|
|
2039
|
-
const
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2126
|
+
function classifyOutboundCallsInSource(source, filePath) {
|
|
2127
|
+
const calls = [];
|
|
2128
|
+
const sourceFile = normalizePath(filePath);
|
|
2129
|
+
const initializers = variableInitializers(source);
|
|
2130
|
+
const serviceVariables = collectServiceVariables(source);
|
|
2131
|
+
const wrapperSpecs = collectWrapperSpecs(source);
|
|
2132
|
+
const wrapperInternalRanges = [...wrapperSpecs.values()].map((spec) => ({ start: spec.internalStart, end: spec.internalEnd }));
|
|
2133
|
+
const add = (node, fact, extra) => {
|
|
2134
|
+
calls.push({ node, fact: { ...fact, sourceFile, sourceLine: lineOf4(source.text, node.getStart(source)), confidence: fact.confidence ?? 0.8, evidence: parserEvidence(source, node, extra) } });
|
|
2135
|
+
};
|
|
2136
|
+
const visit = (node) => {
|
|
2137
|
+
if (ts7.isCallExpression(node)) {
|
|
2138
|
+
if (wrapperInternalRanges.some((range) => node.getStart(source) >= range.start && node.getEnd() <= range.end)) {
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2141
|
+
const expr = node.expression;
|
|
2142
|
+
const exprText = expr.getText(source);
|
|
2143
|
+
if (exprText === "cds.run") {
|
|
2144
|
+
const arg = node.arguments[0];
|
|
2145
|
+
const entity = arg ? queryEntityFromAst(arg, initializers) : void 0;
|
|
2146
|
+
const payload = arg?.getText(source) ?? "";
|
|
2147
|
+
add(node, { callType: "local_db_query", queryEntity: entity, payloadSummary: summarizeExpression(payload), confidence: entity ? 0.9 : 0.55, unresolvedReason: entity ? void 0 : queryWarning(payload) });
|
|
2148
|
+
} else if (ts7.isPropertyAccessExpression(expr) && expr.name.text === "send" && (ts7.isIdentifier(expr.expression) || ts7.isPropertyAccessExpression(expr.expression))) {
|
|
2149
|
+
const objectArg = node.arguments[0];
|
|
2150
|
+
if (objectArg && ts7.isObjectLiteralExpression(objectArg)) {
|
|
2151
|
+
const receiver = receiverName(expr.expression);
|
|
2152
|
+
const query = objectPropertyText(objectArg, "query");
|
|
2153
|
+
const method = stripQuotes(resolveExpression(propertyInitializer(objectArg, "method"), node, "literal").value ?? objectPropertyText(objectArg, "method") ?? "POST");
|
|
2154
|
+
const pathExpr = propertyInitializer(objectArg, "path") ?? propertyInitializer(objectArg, "event");
|
|
2155
|
+
const resolvedPath = staticPathExpression(pathExpr, node);
|
|
2156
|
+
const op = pathExpr ? resolvedPath.text ?? pathExpr.getText(source) : void 0;
|
|
2157
|
+
const shorthandPath = objectPropertyIsShorthand(objectArg, "path");
|
|
2158
|
+
const candidateEvidence2 = pathExpr && ts7.isIdentifier(pathExpr) && !resolvedPath.text ? staticPathCandidates(pathExpr, node, method) : void 0;
|
|
2159
|
+
const candidateOperationPath = candidateEvidence2 && !candidateEvidence2.hasDynamicAssignments && candidateEvidence2.normalizedCandidateOperations.length === 1 && candidateEvidence2.candidatePaths.length > 0 ? candidateEvidence2.candidatePaths[0] : void 0;
|
|
2160
|
+
const operationPathExpr = operationPathFromStatic(resolvedPath.text) ?? candidateOperationPath;
|
|
2161
|
+
const intent = classifyODataPathIntent(operationPathExpr, method);
|
|
2162
|
+
const entityCallTypes = { entity_mutation: "remote_entity_mutation", entity_delete: "remote_entity_delete", entity_media: "remote_entity_media", entity_candidate: "remote_entity_candidate" };
|
|
2163
|
+
const entityCallType = entityCallTypes[intent.kind];
|
|
2164
|
+
const isODataQueryRead = method.toUpperCase() === "GET" && ["entity_query", "entity_key_read", "entity_navigation_query"].includes(intent.kind);
|
|
2165
|
+
add(node, { callType: query ? "remote_query" : entityCallType ?? (isODataQueryRead ? "remote_query" : "remote_action"), serviceVariableName: receiver, method, operationPathExpr, queryEntity: query ? extractQueryEntity(query) : isODataQueryRead ? intent.entitySegment : void 0, payloadSummary: summarizeExpression(objectArg.getText(source)), confidence: op || query ? 0.8 : 0.4, unresolvedReason: !query && pathExpr && !operationPathExpr ? "dynamic_operation_path_identifier" : void 0 }, { receiver, classifier: "service_client_send_object", operationPathExpression: shorthandPath ? op : void 0, rawPathExpression: resolvedPath.rawExpression, literalPathSource: resolvedPath.text ? resolvedPath.sourceKind === "const" ? "same_scope_const_initializer" : resolvedPath.sourceKind : void 0, odataPathIntent: operationPathExpr ? intent : void 0, staticPathCandidates: candidateEvidence2, parserWarning: !query && pathExpr && !operationPathExpr ? "dynamic_operation_path_identifier" : void 0 });
|
|
2166
|
+
} else {
|
|
2167
|
+
const receiver = receiverName(expr.expression);
|
|
2168
|
+
const rootReceiver = rootReceiverName(expr.expression);
|
|
2169
|
+
const firstArg2 = resolveExpression(node.arguments[0], node, "literal");
|
|
2170
|
+
const method = firstArg2.value?.toUpperCase();
|
|
2171
|
+
const pathArg = node.arguments[1];
|
|
2172
|
+
const supported = method && supportedHttpMethods.has(method);
|
|
2173
|
+
if (receiver && supported && serviceVariables.has(rootReceiver ?? receiver)) {
|
|
2174
|
+
const resolvedPath = staticPathExpression(pathArg, node);
|
|
2175
|
+
const operationPathExpr = operationPathFromStatic(resolvedPath.text);
|
|
2176
|
+
const intent = classifyODataPathIntent(operationPathExpr, method);
|
|
2177
|
+
add(node, { callType: "remote_action", serviceVariableName: rootReceiver ?? receiver, method, operationPathExpr, payloadSummary: summarizeExpression(node.getText(source)), confidence: operationPathExpr ? 0.8 : 0.45, unresolvedReason: operationPathExpr ? void 0 : "dynamic_operation_path_identifier" }, { receiver, rootReceiver, classifier: "service_client_send_method_path", rawPathExpression: resolvedPath.rawExpression, literalPathSource: resolvedPath.text ? resolvedPath.sourceKind === "const" ? "same_scope_const_initializer" : resolvedPath.sourceKind : void 0, odataPathIntent: operationPathExpr ? intent : void 0, parserWarning: operationPathExpr ? void 0 : "dynamic_operation_path_identifier" });
|
|
2178
|
+
} else if (receiver && serviceVariables.has(rootReceiver ?? receiver)) {
|
|
2179
|
+
const operationPathExpr = safeOperationName(firstArg2.value);
|
|
2180
|
+
add(node, { callType: "remote_action", serviceVariableName: rootReceiver ?? receiver, operationPathExpr, payloadSummary: summarizeExpression(node.getText(source)), confidence: operationPathExpr ? 0.75 : 0.35, unresolvedReason: operationPathExpr ? void 0 : "unsupported_cap_send_signature" }, { receiver, rootReceiver, classifier: operationPathExpr ? "service_client_send_operation_event" : "service_client_send_unsupported_signature", rawOperationExpression: firstArg2.rawExpression, literalOperationSource: firstArg2.value ? firstArg2.sourceKind : void 0, parserWarning: operationPathExpr ? void 0 : "unsupported_cap_send_signature" });
|
|
2075
2181
|
}
|
|
2076
2182
|
}
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2183
|
+
} else if (ts7.isCallExpression(expr) && ts7.isIdentifier(expr.expression) && wrapperSpecs.has(expr.expression.text) || ts7.isIdentifier(expr) && wrapperSpecs.has(expr.text)) {
|
|
2184
|
+
const wrapperName = ts7.isIdentifier(expr) ? expr.text : ts7.isCallExpression(expr) && ts7.isIdentifier(expr.expression) ? expr.expression.text : "";
|
|
2185
|
+
const wrapperArgs = ts7.isIdentifier(expr) ? node.arguments : ts7.isCallExpression(expr) ? expr.arguments : node.arguments;
|
|
2186
|
+
const spec = wrapperSpecs.get(wrapperName);
|
|
2187
|
+
const clientArg = spec?.clientIndex === void 0 ? void 0 : wrapperArgs[spec.clientIndex];
|
|
2188
|
+
const pathArg = spec ? wrapperArgs[spec.pathIndex] : void 0;
|
|
2189
|
+
const methodArg = spec?.methodIndex === void 0 ? void 0 : wrapperArgs[spec.methodIndex];
|
|
2190
|
+
const receiver = clientArg && ts7.isIdentifier(clientArg) ? clientArg.text : spec?.clientName;
|
|
2191
|
+
const method = stripQuotes(resolveExpression(methodArg, node, "literal").value ?? spec?.methodLiteral ?? "POST");
|
|
2192
|
+
const resolvedPath = staticPathExpression(pathArg, node);
|
|
2193
|
+
const operationPathExpr = operationPathFromStatic(resolvedPath.text);
|
|
2194
|
+
const normalizedOperationPath = operationPathExpr ? classifyODataPathIntent(operationPathExpr, method).topLevelOperationName : void 0;
|
|
2195
|
+
if (spec && receiver && operationPathExpr) {
|
|
2196
|
+
add(node, { callType: "remote_action", serviceVariableName: receiver, method, operationPathExpr, payloadSummary: summarizeExpression(node.getText(source)), confidence: 0.75 }, { receiver, classifier: resolvedPath.sourceKind === "literal" ? "higher_order_wrapper_literal_path" : "higher_order_wrapper_static_path", wrapperFunction: wrapperName, nestedWrapperFunction: spec.nestedWrapperFunction, wrapperDefinitionLine: spec.definitionLine, callerLine: lineOf4(source.text, node.getStart(source)), wrapperPathSourceKind: resolvedPath.sourceKind, rawPathExpression: resolvedPath.rawExpression, normalizedOperationPath, literalPathSource: resolvedPath.sourceKind === "const" ? "same_scope_const_initializer" : `wrapper_call_${resolvedPath.sourceKind}`, literalCallerArgumentDetected: true });
|
|
2197
|
+
} else if (spec && receiver) {
|
|
2198
|
+
add(node, { callType: "remote_action", serviceVariableName: receiver, method, payloadSummary: summarizeExpression(node.getText(source)), confidence: 0.45, unresolvedReason: "dynamic_operation_path_identifier" }, { receiver, classifier: "higher_order_wrapper_dynamic_path", wrapperFunction: wrapperName, wrapperDefinitionLine: spec.definitionLine, callerLine: lineOf4(source.text, node.getStart(source)), wrapperPathSourceKind: resolvedPath.sourceKind, rawPathExpression: resolvedPath.rawExpression, parserWarning: "dynamic_operation_path_identifier" });
|
|
2199
|
+
}
|
|
2200
|
+
} else if (ts7.isPropertyAccessExpression(expr) && ["emit", "publish", "on"].includes(expr.name.text)) {
|
|
2201
|
+
const receiver = receiverName(expr.expression);
|
|
2202
|
+
const rootReceiver = rootReceiverName(expr.expression);
|
|
2203
|
+
if (isSupportedEventReceiver(receiver, rootReceiver, serviceVariables)) {
|
|
2204
|
+
const eventName = literalText(node.arguments[0]);
|
|
2205
|
+
if (eventName) add(node, { callType: expr.name.text === "on" ? "async_subscribe" : "async_emit", serviceVariableName: rootReceiver ?? receiver, eventNameExpr: eventName }, { receiver, rootReceiver, classifier: expr.name.text === "on" ? "cap_service_event_subscription" : "cap_service_event_emit", receiverClassification: "cap_evidence" });
|
|
2206
|
+
}
|
|
2207
|
+
} else {
|
|
2208
|
+
const external = externalHttpEvidence(node, source);
|
|
2209
|
+
if (external) {
|
|
2210
|
+
const evidenceTarget = { ...external.externalTarget, method: external.method, parserClassifier: external.classifier, sourceCallShape: external.sourceCallShape };
|
|
2211
|
+
const safeTarget = externalHttpTarget({ method: external.method, evidence_json: JSON.stringify({ externalTarget: evidenceTarget }) });
|
|
2212
|
+
add(node, { callType: "external_http", method: external.method, payloadSummary: void 0, confidence: 0.7, unresolvedReason: "External HTTP destination is outside indexed CAP services", externalTarget: { kind: safeTarget.kind, stableId: safeTarget.toId, label: safeTarget.label, dynamic: safeTarget.dynamic } }, { classifier: external.classifier, externalTarget: safeTarget, sourceCallShape: external.sourceCallShape });
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
ts7.forEachChild(node, visit);
|
|
2217
|
+
};
|
|
2218
|
+
visit(source);
|
|
2219
|
+
return calls;
|
|
2220
|
+
}
|
|
2221
|
+
function containsSupportedOutboundCall(node) {
|
|
2222
|
+
const source = node.getSourceFile();
|
|
2223
|
+
const start = node.getFullStart();
|
|
2224
|
+
const end = node.getEnd();
|
|
2225
|
+
return classifyOutboundCallsInSource(source, source.fileName).some((call) => call.node.getStart(source) >= start && call.node.getEnd() <= end);
|
|
2226
|
+
}
|
|
2227
|
+
async function parseOutboundCalls(repoPath, filePath) {
|
|
2228
|
+
const text = await fs7.readFile(path10.join(repoPath, filePath), "utf8");
|
|
2229
|
+
const source = ts7.createSourceFile(filePath, text, ts7.ScriptTarget.Latest, true, filePath.endsWith(".ts") ? ts7.ScriptKind.TS : ts7.ScriptKind.JS);
|
|
2230
|
+
const bindingNames = new Set((await parseServiceBindings(repoPath, filePath)).map((binding) => binding.variableName));
|
|
2231
|
+
const importedWrappers = await parseImportedWrapperCalls(repoPath, filePath, source, bindingNames);
|
|
2232
|
+
return [...classifyOutboundCallsInSource(source, filePath).map((call) => call.fact), ...importedWrappers, ...parseLocalServiceCalls(text, filePath)];
|
|
2233
|
+
}
|
|
2234
|
+
function parseLocalServiceCalls(text, filePath) {
|
|
2235
|
+
const source = ts7.createSourceFile(filePath, text, ts7.ScriptTarget.Latest, true, filePath.endsWith(".ts") ? ts7.ScriptKind.TS : ts7.ScriptKind.JS);
|
|
2236
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
2237
|
+
const calls = [];
|
|
2238
|
+
const visit = (node) => {
|
|
2239
|
+
if (ts7.isVariableDeclaration(node) && ts7.isIdentifier(node.name) && node.initializer) {
|
|
2240
|
+
const origin = serviceLookup(node.initializer, aliases);
|
|
2241
|
+
if (origin) aliases.set(node.name.text, { ...origin, chain: [...origin.chain, node.name.text] });
|
|
2242
|
+
}
|
|
2243
|
+
if (ts7.isCallExpression(node)) {
|
|
2244
|
+
const parsed = serviceOperationCall(node, aliases);
|
|
2245
|
+
if (parsed && parsed.operation !== "entities") calls.push({
|
|
2246
|
+
callType: "local_service_call",
|
|
2247
|
+
operationPathExpr: `/${parsed.operation}`,
|
|
2248
|
+
payloadSummary: parsed.service,
|
|
2249
|
+
localServiceName: parsed.service,
|
|
2250
|
+
localServiceLookup: parsed.lookup,
|
|
2251
|
+
aliasChain: parsed.chain,
|
|
2252
|
+
sourceFile: normalizePath(filePath),
|
|
2253
|
+
sourceLine: lineOf4(text, node.getStart(source)),
|
|
2254
|
+
confidence: 0.9,
|
|
2255
|
+
unresolvedReason: ["send", "emit", "publish", "on"].includes(parsed.operation) ? "transport_client_method" : void 0,
|
|
2256
|
+
evidence: parserEvidence(source, node, {
|
|
2257
|
+
classifier: parsed.classifier,
|
|
2258
|
+
parserCallType: parsed.operation === "send" ? "transport_client_method" : parsed.classifier,
|
|
2259
|
+
localServiceLookup: parsed.lookup,
|
|
2260
|
+
localServiceName: parsed.service,
|
|
2261
|
+
operation: parsed.operation,
|
|
2262
|
+
aliasChain: parsed.chain
|
|
2263
|
+
})
|
|
2264
|
+
});
|
|
2089
2265
|
}
|
|
2266
|
+
ts7.forEachChild(node, visit);
|
|
2267
|
+
};
|
|
2268
|
+
visit(source);
|
|
2269
|
+
return calls;
|
|
2270
|
+
}
|
|
2271
|
+
function serviceLookup(expr, aliases) {
|
|
2272
|
+
if (ts7.isIdentifier(expr)) return aliases.get(expr.text);
|
|
2273
|
+
if (ts7.isPropertyAccessExpression(expr) && expr.expression.getText() === "cds.services") return { service: expr.name.text, lookup: expr.getText(), chain: [expr.getText()] };
|
|
2274
|
+
if (ts7.isElementAccessExpression(expr) && expr.expression.getText() === "cds.services" && ts7.isStringLiteral(expr.argumentExpression)) return { service: expr.argumentExpression.text, lookup: expr.getText(), chain: [expr.getText()] };
|
|
2275
|
+
return void 0;
|
|
2276
|
+
}
|
|
2277
|
+
function serviceOperationCall(node, aliases) {
|
|
2278
|
+
const expr = node.expression;
|
|
2279
|
+
if (!ts7.isPropertyAccessExpression(expr)) return void 0;
|
|
2280
|
+
const origin = serviceLookup(expr.expression, aliases);
|
|
2281
|
+
if (!origin) return void 0;
|
|
2282
|
+
if (expr.name.text === "send") {
|
|
2283
|
+
const first = literalText(node.arguments[0]);
|
|
2284
|
+
const second = literalText(node.arguments[1]);
|
|
2285
|
+
const method = first?.toUpperCase();
|
|
2286
|
+
if (method && ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"].includes(method) && second) return { ...origin, operation: second.replace(/^\//, ""), classifier: "cap_service_send_method_path" };
|
|
2287
|
+
if (first) return { ...origin, operation: first.replace(/^\//, ""), classifier: "cap_service_send_local_dispatch" };
|
|
2090
2288
|
}
|
|
2091
|
-
return
|
|
2289
|
+
return { ...origin, operation: expr.name.text, classifier: "local_cap_service_call" };
|
|
2092
2290
|
}
|
|
2093
2291
|
|
|
2094
2292
|
// src/linker/dynamic-edge-resolver.ts
|
|
@@ -2364,9 +2562,9 @@ function generatedFromConstantName(value) {
|
|
|
2364
2562
|
return void 0;
|
|
2365
2563
|
}
|
|
2366
2564
|
function resolved(value, raw) {
|
|
2367
|
-
const
|
|
2368
|
-
const generated = generatedFromConstantName(
|
|
2369
|
-
return { status: "resolved", operationName: generated ?? normalizedOperationName(
|
|
2565
|
+
const literal2 = clean(value);
|
|
2566
|
+
const generated = generatedFromConstantName(literal2);
|
|
2567
|
+
return { status: "resolved", operationName: generated ?? normalizedOperationName(literal2), raw };
|
|
2370
2568
|
}
|
|
2371
2569
|
function normalizeDecoratorOperationSignal(value, raw, candidateOperation) {
|
|
2372
2570
|
if (value) return resolved(value, raw);
|
|
@@ -2512,7 +2710,25 @@ function objectJson(value) {
|
|
|
2512
2710
|
}
|
|
2513
2711
|
function callEvidence(call, resolution, servicePath, op, destination, normalized, odataPathIntent) {
|
|
2514
2712
|
const bindingHasDynamicExpression = Boolean(Number(call.isDynamic ?? 0));
|
|
2515
|
-
|
|
2713
|
+
const routingPlaceholderKeys = placeholderKeys([servicePath, destination, stringValue2(call.aliasExpr), stringValue2(call.alias)]);
|
|
2714
|
+
return { sourceFile: call.source_file, sourceLine: call.source_line, file: call.source_file, line: call.source_line, callId: call.id, repo: call.repoName, serviceAlias: call.alias, serviceAliasExpr: call.aliasExpr, destination, servicePath, operationPath: op, rawOperationPath: normalized?.wasInvocation ? normalized.rawOperationPath : odataPathIntent?.rawPath, normalizedOperationPath: normalized?.wasInvocation ? normalized.normalizedOperationPath : void 0, invocationArguments: normalized?.wasInvocation ? normalized.invocationArguments : void 0, invocationArgumentPlaceholderKeys: normalized?.invocationArgumentPlaceholderKeys.length ? normalized.invocationArgumentPlaceholderKeys : void 0, routingPlaceholderKeys: routingPlaceholderKeys.length ? routingPlaceholderKeys : void 0, odataOperationNormalizationReason: normalized?.normalizationReason, odataOperationNormalizationRejectedReason: normalized?.normalizationRejectedReason, localServiceName: call.local_service_name, localServiceLookup: call.local_service_lookup, aliasChain: parseJson(call.alias_chain_json), transport: call.call_type === "local_service_call" ? "local" : void 0, targetRepo: resolution.target?.repoName, targetServicePath: resolution.target?.servicePath, targetOperationPath: resolution.target?.operationPath, targetOperation: resolution.target?.operationName, helperChain: parseJson(call.helperChainJson), candidates: resolution.candidates, candidateScores: compactCandidateScores(resolution.candidates), candidateCount: resolution.candidates.length, resolutionStatus: resolution.status, resolutionReasons: resolution.reasons, odataPathIntent, queryStringPresent: odataPathIntent?.hasQueryString || void 0, queryPlaceholderKeys: odataPathIntent?.placeholderKeys.length ? odataPathIntent.placeholderKeys : void 0, bindingHasDynamicExpression: bindingHasDynamicExpression || void 0, outboundEvidence: objectJson(call.evidence_json), analysisCompleteness: call.unresolved_reason ? "partial" : "complete", parserWarning: call.unresolved_reason ? { code: "parser_warning", message: call.unresolved_reason } : void 0 };
|
|
2715
|
+
}
|
|
2716
|
+
function compactCandidateScores(candidates) {
|
|
2717
|
+
return candidates.flatMap((candidate) => {
|
|
2718
|
+
const row = objectValue(candidate);
|
|
2719
|
+
if (!row) return [];
|
|
2720
|
+
return [{ repo: row.repoName, servicePath: row.servicePath, operationPath: row.operationPath, score: row.score, reasons: row.reasons }];
|
|
2721
|
+
});
|
|
2722
|
+
}
|
|
2723
|
+
function placeholderKeys(values) {
|
|
2724
|
+
const keys = values.flatMap((value) => [...(value ?? "").matchAll(/\$\{([^}]*)\}/g)].map((match) => (match[1] ?? "").trim()).filter(Boolean));
|
|
2725
|
+
return [...new Set(keys)].sort();
|
|
2726
|
+
}
|
|
2727
|
+
function objectValue(value) {
|
|
2728
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2729
|
+
}
|
|
2730
|
+
function stringValue2(value) {
|
|
2731
|
+
return typeof value === "string" ? value : void 0;
|
|
2516
2732
|
}
|
|
2517
2733
|
function linkImplementations(db, workspaceId, generation) {
|
|
2518
2734
|
const operations = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,o.operation_name operationName,o.provenance provenance,o.base_operation_id baseOperationId,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);
|
|
@@ -2789,6 +3005,112 @@ function normalizedOperation(value) {
|
|
|
2789
3005
|
return value.startsWith("/") ? value.slice(1) : value;
|
|
2790
3006
|
}
|
|
2791
3007
|
|
|
3008
|
+
// src/trace/implementation-hints.ts
|
|
3009
|
+
function parseImplementationHint(value) {
|
|
3010
|
+
const hint = {};
|
|
3011
|
+
for (const part of value.split(",")) {
|
|
3012
|
+
const separator = part.indexOf("=");
|
|
3013
|
+
if (separator <= 0 || separator === part.length - 1) throw new Error(`Invalid implementation hint field: ${part}`);
|
|
3014
|
+
assignHintField(hint, part.slice(0, separator).trim(), part.slice(separator + 1).trim());
|
|
3015
|
+
}
|
|
3016
|
+
if (!hint.implementationRepo) throw new Error("Scoped implementation hint requires an implementation repo selection");
|
|
3017
|
+
return { ...hint, implementationRepo: hint.implementationRepo };
|
|
3018
|
+
}
|
|
3019
|
+
function selectImplementation(rawEvidence, hints, legacyRepo) {
|
|
3020
|
+
const evidence = asEvidence(rawEvidence);
|
|
3021
|
+
const scoped = hints ?? [];
|
|
3022
|
+
const matchingHints = scoped.filter((hint2) => hintMatchesEdge(hint2, evidence));
|
|
3023
|
+
if (matchingHints.length === 0) {
|
|
3024
|
+
if (legacyRepo) return selectCandidate(evidence, legacyHint(legacyRepo), "implementation_repo_hint");
|
|
3025
|
+
const reason = scoped.length > 0 ? "no_scoped_hint_matched_edge" : "no_implementation_hint_supplied";
|
|
3026
|
+
return { blocksAutomatic: false, evidence: { status: "not_matched", reason, strategy: "scoped_implementation_hint" } };
|
|
3027
|
+
}
|
|
3028
|
+
if (matchingHints.length > 1) {
|
|
3029
|
+
return {
|
|
3030
|
+
blocksAutomatic: true,
|
|
3031
|
+
evidence: {
|
|
3032
|
+
status: "tied",
|
|
3033
|
+
reason: "multiple_scoped_hints_matched_edge",
|
|
3034
|
+
strategy: "scoped_implementation_hint",
|
|
3035
|
+
matchedHints: matchingHints,
|
|
3036
|
+
candidateCount: matchingHints.length
|
|
3037
|
+
}
|
|
3038
|
+
};
|
|
3039
|
+
}
|
|
3040
|
+
const hint = matchingHints[0];
|
|
3041
|
+
return hint ? selectCandidate(evidence, hint, "scoped_implementation_hint") : { blocksAutomatic: false, evidence: { status: "not_matched" } };
|
|
3042
|
+
}
|
|
3043
|
+
function implementationHintDiagnostic(selection) {
|
|
3044
|
+
if (!selection.blocksAutomatic || selection.methodId) return void 0;
|
|
3045
|
+
return {
|
|
3046
|
+
severity: "warning",
|
|
3047
|
+
code: "implementation_hint_mismatch",
|
|
3048
|
+
message: "Implementation hint did not select exactly one viable candidate",
|
|
3049
|
+
hintStatus: selection.evidence.status,
|
|
3050
|
+
candidateCount: selection.evidence.candidateCount,
|
|
3051
|
+
implementationSelection: selection.evidence
|
|
3052
|
+
};
|
|
3053
|
+
}
|
|
3054
|
+
function assignHintField(hint, key, value) {
|
|
3055
|
+
if (key === "service" || key === "servicePath") hint.servicePath = value;
|
|
3056
|
+
else if (key === "operation" || key === "operationPath") hint.operationPath = value;
|
|
3057
|
+
else if (key === "package" || key === "packageName") hint.packageName = value;
|
|
3058
|
+
else if (key === "repository" || key === "repositoryName") hint.repositoryName = value;
|
|
3059
|
+
else if (key === "family" || key === "candidateFamily") hint.candidateFamily = value;
|
|
3060
|
+
else if (key === "repo" || key === "implementationRepo" || key === "select") hint.implementationRepo = value;
|
|
3061
|
+
else throw new Error(`Unknown implementation hint field: ${key}`);
|
|
3062
|
+
}
|
|
3063
|
+
function selectCandidate(evidence, hint, strategy) {
|
|
3064
|
+
const matches2 = (evidence.candidates ?? []).filter((candidate) => candidate.accepted && candidateMatchesRepo(candidate, hint.implementationRepo));
|
|
3065
|
+
const selected = matches2.length === 1 ? matches2[0] : void 0;
|
|
3066
|
+
if (!selected || selected.methodId === void 0) {
|
|
3067
|
+
return {
|
|
3068
|
+
blocksAutomatic: true,
|
|
3069
|
+
evidence: {
|
|
3070
|
+
status: matches2.length > 1 ? "tied" : "not_matched",
|
|
3071
|
+
reason: matches2.length > 1 ? "hint_matched_multiple_candidates" : "hint_matched_zero_candidates",
|
|
3072
|
+
strategy,
|
|
3073
|
+
matchedHint: hint,
|
|
3074
|
+
selectedRepo: hint.implementationRepo,
|
|
3075
|
+
candidateCount: matches2.length
|
|
3076
|
+
}
|
|
3077
|
+
};
|
|
3078
|
+
}
|
|
3079
|
+
return {
|
|
3080
|
+
methodId: String(selected.methodId),
|
|
3081
|
+
blocksAutomatic: false,
|
|
3082
|
+
evidence: {
|
|
3083
|
+
status: "selected",
|
|
3084
|
+
guided: true,
|
|
3085
|
+
strategy,
|
|
3086
|
+
matchedHint: hint,
|
|
3087
|
+
selectedRepo: hint.implementationRepo,
|
|
3088
|
+
selectedMethodId: selected.methodId,
|
|
3089
|
+
ambiguityReason: evidence.ambiguityReasons?.[0]
|
|
3090
|
+
}
|
|
3091
|
+
};
|
|
3092
|
+
}
|
|
3093
|
+
function hintMatchesEdge(hint, evidence) {
|
|
3094
|
+
const model = evidence.modelPackage ?? evidence.candidates?.[0]?.modelPackage;
|
|
3095
|
+
const familyNames = /* @__PURE__ */ new Set([
|
|
3096
|
+
...(evidence.candidateFamilies ?? []).flatMap((family) => family.packageName ? [family.packageName] : []),
|
|
3097
|
+
...(evidence.candidates ?? []).flatMap((candidate) => candidate.handlerPackage?.packageName ? [candidate.handlerPackage.packageName] : [])
|
|
3098
|
+
]);
|
|
3099
|
+
return matches(hint.servicePath, evidence.servicePath ?? evidence.candidates?.[0]?.servicePath) && matches(hint.operationPath, evidence.operationPath ?? evidence.candidates?.[0]?.operationPath) && matches(hint.packageName, model?.packageName) && matches(hint.repositoryName, model?.name) && (!hint.candidateFamily || familyNames.has(hint.candidateFamily));
|
|
3100
|
+
}
|
|
3101
|
+
function candidateMatchesRepo(candidate, value) {
|
|
3102
|
+
return candidate.handlerPackage?.name === value || candidate.handlerPackage?.packageName === value || candidate.sourceFile?.startsWith(value) === true;
|
|
3103
|
+
}
|
|
3104
|
+
function matches(expected, actual) {
|
|
3105
|
+
return expected === void 0 || expected === actual;
|
|
3106
|
+
}
|
|
3107
|
+
function legacyHint(implementationRepo) {
|
|
3108
|
+
return { implementationRepo };
|
|
3109
|
+
}
|
|
3110
|
+
function asEvidence(value) {
|
|
3111
|
+
return value;
|
|
3112
|
+
}
|
|
3113
|
+
|
|
2792
3114
|
// src/trace/evidence.ts
|
|
2793
3115
|
function baseTraceEvidence(row, call, persistedEvidence, contextualEvidence) {
|
|
2794
3116
|
const evidence = { ...persistedEvidence, ...contextualEvidence ?? {} };
|
|
@@ -2841,20 +3163,20 @@ function runtimeVariableDiagnostic(edges) {
|
|
|
2841
3163
|
}
|
|
2842
3164
|
function edgeTarget(row, evidence) {
|
|
2843
3165
|
const effective = parseObject(evidence.effectiveResolution);
|
|
2844
|
-
const targetServicePath =
|
|
2845
|
-
const targetOperationPath =
|
|
3166
|
+
const targetServicePath = stringValue3(effective.targetServicePath ?? evidence.targetServicePath);
|
|
3167
|
+
const targetOperationPath = stringValue3(effective.targetOperationPath ?? evidence.targetOperationPath);
|
|
2846
3168
|
if (targetServicePath && targetOperationPath) return `${targetServicePath}${targetOperationPath}`;
|
|
2847
3169
|
const runtimeCandidate = evidence.runtimeResolvedCandidate;
|
|
2848
3170
|
if (runtimeCandidate?.servicePath && runtimeCandidate.operationPath) return `${runtimeCandidate.servicePath}${runtimeCandidate.operationPath}`;
|
|
2849
|
-
const servicePath =
|
|
2850
|
-
const operationPath =
|
|
2851
|
-
const targetOperation =
|
|
2852
|
-
const targetRepo =
|
|
3171
|
+
const servicePath = stringValue3(evidence.servicePath);
|
|
3172
|
+
const operationPath = stringValue3(evidence.operationPath);
|
|
3173
|
+
const targetOperation = stringValue3(evidence.targetOperation);
|
|
3174
|
+
const targetRepo = stringValue3(evidence.targetRepo) ?? "";
|
|
2853
3175
|
if (row.edge_type === "HANDLER_RUNS_DB_QUERY") return `Entity: ${row.to_id || "unknown"}`;
|
|
2854
|
-
if (row.edge_type === "HANDLER_RUNS_REMOTE_QUERY") return
|
|
3176
|
+
if (row.edge_type === "HANDLER_RUNS_REMOTE_QUERY") return stringValue3(evidence.remoteQueryTarget) ?? `Remote query: ${row.to_id || "unknown"}`;
|
|
2855
3177
|
if (row.edge_type === "HANDLER_CALLS_EXTERNAL_HTTP") {
|
|
2856
3178
|
const target = parseObject(evidence.externalTarget);
|
|
2857
|
-
return
|
|
3179
|
+
return stringValue3(target.label) ?? `External endpoint: ${row.to_id || "unknown"}`;
|
|
2858
3180
|
}
|
|
2859
3181
|
if (servicePath && operationPath) return `${servicePath}${operationPath}`;
|
|
2860
3182
|
return targetOperation ? `${targetRepo}:${targetOperation}` : row.to_id;
|
|
@@ -2893,10 +3215,12 @@ function withEffectiveResolution(evidence, row, unresolvedReason, resolution) {
|
|
|
2893
3215
|
return { ...rest, effectiveResolution: current, linker: { status: current.status, confidence: current.confidence, reason: unresolvedReason, edgeType: current.edgeType } };
|
|
2894
3216
|
}
|
|
2895
3217
|
function resolveRuntimeOperation(db, evidence, workspaceId) {
|
|
2896
|
-
const servicePath =
|
|
2897
|
-
const
|
|
2898
|
-
const
|
|
2899
|
-
const
|
|
3218
|
+
const servicePath = stringValue3(evidence.servicePath);
|
|
3219
|
+
const rawOperationPath = stringValue3(evidence.operationPath);
|
|
3220
|
+
const normalized = normalizeODataOperationInvocationPath(rawOperationPath);
|
|
3221
|
+
const operationPath = normalized?.wasInvocation ? normalized.normalizedOperationPath : stringValue3(evidence.normalizedOperationPath) ?? rawOperationPath;
|
|
3222
|
+
const alias = stringValue3(evidence.serviceAliasExpr ?? evidence.serviceAlias);
|
|
3223
|
+
const destination = stringValue3(evidence.destination);
|
|
2900
3224
|
return resolveOperation(db, { servicePath, operationPath, alias, destination, hasExplicitOverride: true, isDynamic: true }, workspaceId);
|
|
2901
3225
|
}
|
|
2902
3226
|
function evidenceWithRuntimeVariables(evidence, vars) {
|
|
@@ -2911,7 +3235,7 @@ function evidenceWithRuntimeVariables(evidence, vars) {
|
|
|
2911
3235
|
function runtimeSubstitutions(evidence, vars) {
|
|
2912
3236
|
const substitutions = {};
|
|
2913
3237
|
for (const key of ["servicePath", "operationPath", "serviceAliasExpr", "serviceAlias", "destination"]) {
|
|
2914
|
-
const substitution = substituteVariables(
|
|
3238
|
+
const substitution = substituteVariables(stringValue3(evidence[key]), vars);
|
|
2915
3239
|
if (substitution.placeholders.length > 0) substitutions[key] = substitution;
|
|
2916
3240
|
}
|
|
2917
3241
|
return substitutions;
|
|
@@ -2933,7 +3257,7 @@ function runtimeUnresolvedReason(resolution) {
|
|
|
2933
3257
|
function parseObject(value) {
|
|
2934
3258
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2935
3259
|
}
|
|
2936
|
-
function
|
|
3260
|
+
function stringValue3(value) {
|
|
2937
3261
|
return typeof value === "string" ? value : void 0;
|
|
2938
3262
|
}
|
|
2939
3263
|
|
|
@@ -2945,7 +3269,7 @@ function normalizeOperation(value) {
|
|
|
2945
3269
|
function positiveDepth(value) {
|
|
2946
3270
|
return Number.isFinite(value) && value > 0 ? Math.floor(value) : 25;
|
|
2947
3271
|
}
|
|
2948
|
-
function operationStartScope(db, repoId, start,
|
|
3272
|
+
function operationStartScope(db, repoId, start, hintOptions) {
|
|
2949
3273
|
const requested = normalizeOperation(start.operationPath ?? start.operation);
|
|
2950
3274
|
if (!requested) return void 0;
|
|
2951
3275
|
const rows2 = db.prepare(`SELECT o.id operationId,o.operation_name operationName,o.operation_path operationPath,s.service_path servicePath,r.id repoId,r.name repoName
|
|
@@ -2961,14 +3285,16 @@ function operationStartScope(db, repoId, start, implementationRepo) {
|
|
|
2961
3285
|
const operationId = String(rows2[0]?.operationId);
|
|
2962
3286
|
const impl = implementationScope(db, operationId);
|
|
2963
3287
|
if (impl.edge?.status === "resolved" && impl.files.size > 0) return { files: impl.files, symbols: impl.symbolId ? /* @__PURE__ */ new Set([impl.symbolId]) : void 0, operationId, diagnostics: [] };
|
|
2964
|
-
const hinted = implementationMethodIdFromHint(impl.edge,
|
|
3288
|
+
const hinted = implementationMethodIdFromHint(impl.edge, hintOptions);
|
|
2965
3289
|
if (hinted.methodId) {
|
|
2966
3290
|
const hintedScope = handlerScope(db, hinted.methodId);
|
|
2967
3291
|
if (hintedScope?.files.size) return { files: hintedScope.files, symbols: hintedScope.symbolId ? /* @__PURE__ */ new Set([hintedScope.symbolId]) : void 0, operationId, diagnostics: [] };
|
|
2968
3292
|
}
|
|
2969
3293
|
if (impl.edge) {
|
|
2970
3294
|
const evidence = parseEvidence(impl.edge.evidence_json);
|
|
2971
|
-
|
|
3295
|
+
const hintDiagnostic = implementationHintDiagnostic(hinted);
|
|
3296
|
+
const diagnostics = [{ severity: "warning", code: impl.edge.status === "ambiguous" ? "trace_start_ambiguous" : "trace_start_implementation_unresolved", message: `Indexed operation matched but implementation edge is ${String(impl.edge.status ?? "unresolved")}`, resolutionStage: "implementation", resolutionStatus: impl.edge.status === "ambiguous" ? "ambiguous_implementation" : "rejected_implementation", implementationEdgeId: impl.edge.id, implementationStatus: impl.edge.status, implementationAmbiguityReasons: evidence.ambiguityReasons, candidates: evidence.candidates }];
|
|
3297
|
+
return { operationId, diagnostics: hintDiagnostic ? [hintDiagnostic, ...diagnostics] : diagnostics };
|
|
2972
3298
|
}
|
|
2973
3299
|
return { operationId, diagnostics: [{ severity: "warning", code: "trace_start_implementation_unresolved", message: "Indexed operation matched but no implementation candidate exists", resolutionStage: "implementation", resolutionStatus: "operation_without_implementation" }] };
|
|
2974
3300
|
}
|
|
@@ -3012,12 +3338,12 @@ function sourceFilesForStart(db, repoId, start) {
|
|
|
3012
3338
|
}
|
|
3013
3339
|
return void 0;
|
|
3014
3340
|
}
|
|
3015
|
-
function startScope(db, start,
|
|
3341
|
+
function startScope(db, start, hintOptions) {
|
|
3016
3342
|
const repo = start.repo ? db.prepare(
|
|
3017
3343
|
"SELECT id,name FROM repositories WHERE name=? OR package_name=?"
|
|
3018
3344
|
).get(start.repo, start.repo) : void 0;
|
|
3019
3345
|
if (start.repo && !repo) return { repo, selectorMatched: false };
|
|
3020
|
-
const operationScope = operationStartScope(db, repo?.id, start,
|
|
3346
|
+
const operationScope = operationStartScope(db, repo?.id, start, hintOptions);
|
|
3021
3347
|
const terminalOperationScope = operationScope && !operationScope.files && (operationScope.diagnostics ?? []).some((d) => d.resolutionStage === "operation" || d.resolutionStage === "implementation");
|
|
3022
3348
|
const sourceScope = operationScope?.files || terminalOperationScope ? operationScope : sourceFilesForStart(db, repo?.id, start);
|
|
3023
3349
|
const sourceFiles = sourceScope?.files;
|
|
@@ -3064,27 +3390,15 @@ function implementationScope(db, operationId) {
|
|
|
3064
3390
|
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);
|
|
3065
3391
|
return { repoId: row?.repoId, files: new Set(row?.sourceFile ? [row.sourceFile] : []), symbolId: row?.symbolId, edge };
|
|
3066
3392
|
}
|
|
3067
|
-
function implementationMethodIdFromHint(edge,
|
|
3068
|
-
if (!edge || edge.status !== "ambiguous"
|
|
3069
|
-
|
|
3070
|
-
const matches = (evidence.candidates ?? []).filter((item) => item.accepted && (item.handlerPackage?.name === implementationRepo || item.handlerPackage?.packageName === implementationRepo || item.sourceFile?.startsWith(implementationRepo)));
|
|
3071
|
-
if (matches.length !== 1 || matches[0]?.methodId === void 0) return { evidence: { status: matches.length > 1 ? "tied" : "not_matched", strategy: "implementation_repo_hint", selectedRepo: implementationRepo, candidateCount: matches.length } };
|
|
3072
|
-
return {
|
|
3073
|
-
methodId: String(matches[0].methodId),
|
|
3074
|
-
evidence: {
|
|
3075
|
-
status: "selected",
|
|
3076
|
-
guided: true,
|
|
3077
|
-
strategy: "implementation_repo_hint",
|
|
3078
|
-
selectedRepo: implementationRepo,
|
|
3079
|
-
selectedMethodId: matches[0].methodId,
|
|
3080
|
-
ambiguityReason: evidence.ambiguityReasons?.[0]
|
|
3081
|
-
}
|
|
3082
|
-
};
|
|
3393
|
+
function implementationMethodIdFromHint(edge, options) {
|
|
3394
|
+
if (!edge || edge.status !== "ambiguous") return { blocksAutomatic: false, evidence: { status: "not_applicable" } };
|
|
3395
|
+
return selectImplementation(parseEvidence(edge.evidence_json), options.implementationHints, options.implementationRepo);
|
|
3083
3396
|
}
|
|
3084
|
-
function contextImplementationMethodId(edge, callerRepoId, remoteEvidence
|
|
3085
|
-
const hinted = implementationMethodIdFromHint(edge,
|
|
3397
|
+
function contextImplementationMethodId(edge, callerRepoId, remoteEvidence, hintOptions) {
|
|
3398
|
+
const hinted = implementationMethodIdFromHint(edge, hintOptions);
|
|
3086
3399
|
if (hinted.methodId) return hinted;
|
|
3087
|
-
if (
|
|
3400
|
+
if (hinted.blocksAutomatic) return hinted;
|
|
3401
|
+
if (!edge || edge.status !== "ambiguous" || callerRepoId === void 0) return hinted;
|
|
3088
3402
|
const evidence = JSON.parse(String(edge.evidence_json || "{}"));
|
|
3089
3403
|
const scores = (evidence.candidates ?? []).filter((item) => item.accepted).map((item) => {
|
|
3090
3404
|
const reasons = [];
|
|
@@ -3103,10 +3417,10 @@ function contextImplementationMethodId(edge, callerRepoId, remoteEvidence = {},
|
|
|
3103
3417
|
}
|
|
3104
3418
|
return { methodId: item.methodId, score, reasons, handlerPackage: item.handlerPackage, applicationPackage: item.applicationPackage };
|
|
3105
3419
|
}).sort((a, b) => b.score - a.score);
|
|
3106
|
-
if (scores.length === 0) return { evidence: { status: "not_applicable", candidateScores: [] } };
|
|
3420
|
+
if (scores.length === 0) return { blocksAutomatic: false, evidence: { status: "not_applicable", candidateScores: [] } };
|
|
3107
3421
|
const [first, second] = scores;
|
|
3108
|
-
if (first && first.methodId !== void 0 && first.score > 0 && (!second || first.score > second.score)) return { methodId: String(first.methodId), evidence: { status: "selected", selectedMethodId: first.methodId, candidateScores: scores } };
|
|
3109
|
-
return { evidence: { status: "tied", tieReason: scores.length > 1 ? "duplicate_helper_implementation_candidates" : "no_unique_materially_stronger_candidate", candidateScores: scores } };
|
|
3422
|
+
if (first && first.methodId !== void 0 && first.score > 0 && (!second || first.score > second.score)) return { methodId: String(first.methodId), blocksAutomatic: false, evidence: { status: "selected", selectedMethodId: first.methodId, candidateScores: scores } };
|
|
3423
|
+
return { blocksAutomatic: false, evidence: hinted.evidence.reason === "no_scoped_hint_matched_edge" ? hinted.evidence : { status: "tied", tieReason: scores.length > 1 ? "duplicate_helper_implementation_candidates" : "no_unique_materially_stronger_candidate", candidateScores: scores } };
|
|
3110
3424
|
}
|
|
3111
3425
|
function handlerScope(db, methodId) {
|
|
3112
3426
|
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(methodId);
|
|
@@ -3204,18 +3518,22 @@ function contextForSymbolCall(db, symbolCall, callerBindings) {
|
|
|
3204
3518
|
const next = /* @__PURE__ */ new Map();
|
|
3205
3519
|
if (callerBindings.size === 0) return next;
|
|
3206
3520
|
const callEvidence2 = parseEvidence(symbolCall.evidence_json);
|
|
3207
|
-
const callee = db.prepare("SELECT evidence_json evidenceJson FROM symbols WHERE id=?").get(symbolCall.callee_symbol_id);
|
|
3521
|
+
const callee = db.prepare("SELECT evidence_json evidenceJson,source_file sourceFile,start_line startLine FROM symbols WHERE id=?").get(symbolCall.callee_symbol_id);
|
|
3208
3522
|
const calleeEvidence = parseEvidence(callee?.evidenceJson);
|
|
3209
3523
|
const params = Array.isArray(calleeEvidence.parameters) ? calleeEvidence.parameters.filter((item) => typeof item === "string") : [];
|
|
3210
3524
|
const parameterBindings = Array.isArray(calleeEvidence.parameterBindings) ? calleeEvidence.parameterBindings.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
3211
3525
|
const parameterPropertyAliases = Array.isArray(calleeEvidence.parameterPropertyAliases) ? calleeEvidence.parameterPropertyAliases.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
3212
3526
|
const args = Array.isArray(callEvidence2.callArguments) ? callEvidence2.callArguments : [];
|
|
3527
|
+
const provenance = {
|
|
3528
|
+
callerSite: { sourceFile: String(symbolCall.source_file ?? ""), sourceLine: Number(symbolCall.source_line ?? 0) },
|
|
3529
|
+
calleeSite: { sourceFile: callee?.sourceFile, sourceLine: callee?.startLine }
|
|
3530
|
+
};
|
|
3213
3531
|
args.forEach((arg, index) => {
|
|
3214
3532
|
const paramBinding = parameterBindings.find((binding) => binding.index === index);
|
|
3215
3533
|
const param = paramBinding?.kind === "identifier" && typeof paramBinding.name === "string" ? paramBinding.name : params[index];
|
|
3216
3534
|
if (arg.kind === "identifier" && typeof arg.name === "string") {
|
|
3217
3535
|
const binding = callerBindings.get(arg.name);
|
|
3218
|
-
if (binding && param) next.set(param, { ...binding, source: "local_symbol_argument", callerArgument: arg.name, calleeParameter: param, calleeReceiver: param });
|
|
3536
|
+
if (binding && param) next.set(param, { ...binding, ...provenance, source: "local_symbol_argument", callerArgument: arg.name, calleeParameter: param, calleeReceiver: param });
|
|
3219
3537
|
}
|
|
3220
3538
|
if (arg.kind === "object_literal" && Array.isArray(arg.properties)) {
|
|
3221
3539
|
for (const prop of arg.properties) {
|
|
@@ -3223,15 +3541,23 @@ function contextForSymbolCall(db, symbolCall, callerBindings) {
|
|
|
3223
3541
|
const binding = callerBindings.get(prop.argument);
|
|
3224
3542
|
if (!binding) continue;
|
|
3225
3543
|
const destructured = paramBinding?.kind === "object_pattern" && Array.isArray(paramBinding.properties) ? paramBinding.properties.find((item) => item.property === prop.property && typeof item.local === "string") : void 0;
|
|
3226
|
-
if (destructured && typeof destructured.local === "string") next.set(destructured.local, { ...binding, source: "local_symbol_destructured_object_argument", callerProperty: prop.property, callerArgument: prop.argument, calleeParameter: String(index), calleeReceiver: destructured.local });
|
|
3544
|
+
if (destructured && typeof destructured.local === "string") next.set(destructured.local, { ...binding, ...provenance, source: "local_symbol_destructured_object_argument", callerProperty: prop.property, callerArgument: prop.argument, calleeParameter: String(index), calleeReceiver: destructured.local });
|
|
3227
3545
|
else if (param) {
|
|
3228
|
-
next.set(`${param}.${prop.property}`, { ...binding, source: "local_symbol_object_argument", callerProperty: prop.property, callerArgument: prop.argument, calleeParameter: param, calleeReceiver: `${param}.${prop.property}` });
|
|
3546
|
+
next.set(`${param}.${prop.property}`, { ...binding, ...provenance, source: "local_symbol_object_argument", callerProperty: prop.property, callerArgument: prop.argument, calleeParameter: param, calleeReceiver: `${param}.${prop.property}` });
|
|
3229
3547
|
for (const alias of parameterPropertyAliases) {
|
|
3230
|
-
if (alias.parameter === param && alias.property === prop.property && typeof alias.local === "string") next.set(alias.local, { ...binding, source: "local_symbol_object_parameter_destructure", callerProperty: prop.property, callerArgument: prop.argument, calleeParameter: param, calleeObjectProperty: `${param}.${prop.property}`, calleeReceiver: alias.local, calleeLocalDestructuredIdentifier: alias.local, parameterPropertyAliasKind: alias.kind, parameterPropertyAliasLine: alias.line });
|
|
3548
|
+
if (alias.parameter === param && alias.property === prop.property && typeof alias.local === "string") next.set(alias.local, { ...binding, ...provenance, source: "local_symbol_object_parameter_destructure", callerProperty: prop.property, callerArgument: prop.argument, calleeParameter: param, calleeObjectProperty: `${param}.${prop.property}`, calleeReceiver: alias.local, calleeLocalDestructuredIdentifier: alias.local, parameterPropertyAliasKind: alias.kind, parameterPropertyAliasLine: alias.line });
|
|
3231
3549
|
}
|
|
3232
3550
|
}
|
|
3233
3551
|
}
|
|
3234
3552
|
}
|
|
3553
|
+
if (arg.kind === "array_literal" && Array.isArray(arg.elements) && paramBinding?.kind === "array_pattern" && Array.isArray(paramBinding.elements)) {
|
|
3554
|
+
for (const element of arg.elements) {
|
|
3555
|
+
const target = paramBinding.elements.find((item) => item.index === element.index);
|
|
3556
|
+
if (element.kind !== "identifier" || typeof element.name !== "string" || typeof target?.local !== "string") continue;
|
|
3557
|
+
const binding = callerBindings.get(element.name);
|
|
3558
|
+
if (binding) next.set(target.local, { ...binding, ...provenance, source: "local_symbol_destructured_array_argument", callerArgument: element.name, calleeParameter: String(index), calleeReceiver: target.local });
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3235
3561
|
});
|
|
3236
3562
|
return next;
|
|
3237
3563
|
}
|
|
@@ -3242,7 +3568,7 @@ function contextualRuntimeResolution(db, call, binding, workspaceId, persistedRo
|
|
|
3242
3568
|
const servicePath = binding.effectiveServicePath ?? binding.servicePathExpr ?? binding.requireServicePath;
|
|
3243
3569
|
const destination = binding.effectiveDestination ?? binding.destinationExpr ?? binding.requireDestination;
|
|
3244
3570
|
const resolution = resolveOperation(db, { servicePath, operationPath: op, alias: binding.aliasExpr ?? binding.alias, destination, hasExplicitOverride: true, isDynamic: false }, workspaceId);
|
|
3245
|
-
const evidence = { contextualServiceBindingAttempted: true, contextualBinding: { source: binding.source, callerArgument: binding.callerArgument, callerProperty: binding.callerProperty, calleeParameter: binding.calleeParameter, calleeReceiver: binding.calleeReceiver, bindingSourceFile: binding.sourceFile, bindingSourceLine: binding.sourceLine, alias: binding.alias, aliasExpr: binding.aliasExpr, requireServicePath: binding.requireServicePath, requireDestination: binding.requireDestination, effectiveServicePath: binding.effectiveServicePath, effectiveDestination: binding.effectiveDestination }, operationPath: op, rawOperationPath: normalized?.rawOperationPath, normalizedOperationPath: normalized?.wasInvocation ? normalized.normalizedOperationPath : void 0, invocationArgumentPlaceholderKeys: normalized?.invocationArgumentPlaceholderKeys.length ? normalized.invocationArgumentPlaceholderKeys : void 0, servicePath, serviceAlias: binding.alias, serviceAliasExpr: binding.aliasExpr, destination, requireServicePath: binding.requireServicePath, requireDestination: binding.requireDestination, effectiveServicePath: binding.effectiveServicePath, effectiveDestination: binding.effectiveDestination, contextualResolutionStatus: resolution.status, contextualCandidateCount: resolution.candidates.length, candidates: resolution.candidates, contextualResolutionReasons: resolution.reasons, resolutionReasons: resolution.reasons };
|
|
3571
|
+
const evidence = { contextualServiceBindingAttempted: true, contextualBinding: { source: binding.source, callerArgument: binding.callerArgument, callerProperty: binding.callerProperty, calleeParameter: binding.calleeParameter, calleeReceiver: binding.calleeReceiver, callerSite: binding.callerSite, calleeSite: binding.calleeSite, bindingSourceFile: binding.sourceFile, bindingSourceLine: binding.sourceLine, alias: binding.alias, aliasExpr: binding.aliasExpr, requireServicePath: binding.requireServicePath, requireDestination: binding.requireDestination, effectiveServicePath: binding.effectiveServicePath, effectiveDestination: binding.effectiveDestination }, operationPath: op, rawOperationPath: normalized?.rawOperationPath, normalizedOperationPath: normalized?.wasInvocation ? normalized.normalizedOperationPath : void 0, invocationArgumentPlaceholderKeys: normalized?.invocationArgumentPlaceholderKeys.length ? normalized.invocationArgumentPlaceholderKeys : void 0, servicePath, serviceAlias: binding.alias, serviceAliasExpr: binding.aliasExpr, destination, requireServicePath: binding.requireServicePath, requireDestination: binding.requireDestination, effectiveServicePath: binding.effectiveServicePath, effectiveDestination: binding.effectiveDestination, contextualResolutionStatus: resolution.status, contextualCandidateCount: resolution.candidates.length, candidates: resolution.candidates, contextualResolutionReasons: resolution.reasons, resolutionReasons: resolution.reasons };
|
|
3246
3572
|
if (!resolution.target) return { evidence, unresolvedReason: resolution.status === "ambiguous" ? "Ambiguous contextual operation candidates" : resolution.status === "dynamic" ? `Dynamic contextual target is missing runtime variables: ${resolution.reasons.join(", ")}` : "No contextual operation candidate matched" };
|
|
3247
3573
|
const resolvedEvidence = { ...evidence, contextualServiceBindingSelected: true, targetRepo: resolution.target.repoName, targetServicePath: resolution.target.servicePath, targetOperationPath: resolution.target.operationPath, targetOperation: resolution.target.operationName };
|
|
3248
3574
|
const persistedResolved = persistedRows.find((item) => item.status === "resolved");
|
|
@@ -3250,7 +3576,8 @@ function contextualRuntimeResolution(db, call, binding, workspaceId, persistedRo
|
|
|
3250
3576
|
return { row: { id: -Number(call.id), edge_type: "REMOTE_CALL_RESOLVES_TO_OPERATION", from_id: String(call.id), to_kind: "operation", to_id: String(resolution.target.operationId), confidence: resolution.target.score, evidence_json: JSON.stringify(resolvedEvidence), status: "resolved" }, evidence: resolvedEvidence, unresolvedReason: void 0 };
|
|
3251
3577
|
}
|
|
3252
3578
|
function trace(db, start, options) {
|
|
3253
|
-
const
|
|
3579
|
+
const hintOptions = { implementationRepo: options.implementationRepo, implementationHints: options.implementationHints };
|
|
3580
|
+
const scope = startScope(db, start, hintOptions);
|
|
3254
3581
|
const diagnostics = db.prepare(
|
|
3255
3582
|
"SELECT severity,code,message,source_file sourceFile,source_line sourceLine FROM diagnostics WHERE (? IS NULL OR repo_id=?)"
|
|
3256
3583
|
).all(scope.repo?.id, scope.repo?.id);
|
|
@@ -3273,7 +3600,7 @@ function trace(db, start, options) {
|
|
|
3273
3600
|
const op = operationNode(db, scope.startOperationId);
|
|
3274
3601
|
const impl = implementationScope(db, scope.startOperationId);
|
|
3275
3602
|
if (op) nodes.set(String(op.id), op);
|
|
3276
|
-
const startSelection = implementationMethodIdFromHint(impl.edge,
|
|
3603
|
+
const startSelection = implementationMethodIdFromHint(impl.edge, hintOptions);
|
|
3277
3604
|
if (impl.edge && (impl.edge.status === "resolved" || startSelection.methodId)) {
|
|
3278
3605
|
const selectedMethodId = impl.edge.status === "resolved" ? impl.edge.to_id : startSelection.methodId;
|
|
3279
3606
|
const implEvidence = { ...parseEvidence(impl.edge.evidence_json), startResolution: { strategy: "indexed_operation_graph", matchedOperationId: scope.startOperationId, implementationEdgeId: impl.edge.id, implementationStatus: impl.edge.status, selectedHandlerMethodId: selectedMethodId }, implementationSelection: startSelection.methodId ? startSelection.evidence : void 0 };
|
|
@@ -3360,10 +3687,12 @@ function trace(db, start, options) {
|
|
|
3360
3687
|
});
|
|
3361
3688
|
if (effectiveRow.to_kind === "operation") {
|
|
3362
3689
|
const implementation = implementationScope(db, effectiveRow.to_id);
|
|
3363
|
-
const contextSelection = contextImplementationMethodId(implementation.edge, current.repoId, evidence,
|
|
3690
|
+
const contextSelection = contextImplementationMethodId(implementation.edge, current.repoId, evidence, hintOptions);
|
|
3364
3691
|
const contextMethodId = contextSelection.methodId;
|
|
3365
3692
|
const contextNode = contextMethodId ? handlerMethodNode(db, contextMethodId) : void 0;
|
|
3366
3693
|
if (implementation.edge) {
|
|
3694
|
+
const hintDiagnostic = implementationHintDiagnostic(contextSelection);
|
|
3695
|
+
if (hintDiagnostic) diagnostics.unshift(hintDiagnostic);
|
|
3367
3696
|
const implEvidence = JSON.parse(String(implementation.edge.evidence_json || "{}"));
|
|
3368
3697
|
const handlerNode = implementation.edge.status === "resolved" ? handlerMethodNode(db, implementation.edge.to_id) : contextNode;
|
|
3369
3698
|
const implTo = handlerNode?.label ? String(handlerNode.label) : `${implementation.edge.to_kind}:${implementation.edge.to_id}`;
|
|
@@ -3373,7 +3702,7 @@ function trace(db, start, options) {
|
|
|
3373
3702
|
type: "operation_implemented_by_handler",
|
|
3374
3703
|
from: to,
|
|
3375
3704
|
to: implTo,
|
|
3376
|
-
evidence: contextMethodId ? { ...implEvidence, contextualImplementationSelected: contextSelection.evidence.strategy !== "implementation_repo_hint", contextualImplementation: contextSelection.evidence, implementationSelection: contextSelection.evidence } : { ...implEvidence, contextualImplementation: contextSelection.evidence },
|
|
3705
|
+
evidence: contextMethodId ? { ...implEvidence, contextualImplementationSelected: contextSelection.evidence.strategy !== "implementation_repo_hint", contextualImplementation: contextSelection.evidence, implementationSelection: contextSelection.evidence } : { ...implEvidence, contextualImplementation: contextSelection.evidence, implementationSelection: contextSelection.evidence },
|
|
3377
3706
|
confidence: Number(implementation.edge.confidence ?? 0),
|
|
3378
3707
|
unresolvedReason: implementation.edge.status === "resolved" || contextMethodId ? void 0 : String(implementation.edge.unresolved_reason ?? implementation.edge.status)
|
|
3379
3708
|
});
|
|
@@ -3426,13 +3755,14 @@ export {
|
|
|
3426
3755
|
redactValue,
|
|
3427
3756
|
normalizeODataOperationInvocationPath,
|
|
3428
3757
|
classifyODataPathIntent,
|
|
3758
|
+
parseServiceBindings,
|
|
3429
3759
|
containsSupportedOutboundCall,
|
|
3430
3760
|
parseOutboundCalls,
|
|
3431
|
-
parseServiceBindings,
|
|
3432
3761
|
applyVariables,
|
|
3433
3762
|
extractPlaceholders,
|
|
3434
3763
|
substituteVariables,
|
|
3435
3764
|
linkWorkspace,
|
|
3765
|
+
parseImplementationHint,
|
|
3436
3766
|
trace
|
|
3437
3767
|
};
|
|
3438
|
-
//# sourceMappingURL=chunk-
|
|
3768
|
+
//# sourceMappingURL=chunk-BXSCE5CR.js.map
|