@suss/adapter-python 0.21.0 → 0.22.0
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/README.md +71 -0
- package/dist/index.cjs +2287 -733
- package/dist/index.d.cts +148 -3
- package/dist/index.d.ts +148 -3
- package/dist/index.js +1938 -356
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -754,7 +754,7 @@ function passesParametersThrough(def, inner) {
|
|
|
754
754
|
}
|
|
755
755
|
|
|
756
756
|
// src/discovery.ts
|
|
757
|
-
import { dispatchByType, restBinding } from "@suss/behavioral-ir";
|
|
757
|
+
import { dispatchByType as dispatchByType2, restBinding } from "@suss/behavioral-ir";
|
|
758
758
|
import {
|
|
759
759
|
absentReading,
|
|
760
760
|
ambiguousReading,
|
|
@@ -765,19 +765,399 @@ import {
|
|
|
765
765
|
mapReading,
|
|
766
766
|
unreadableReading,
|
|
767
767
|
valueToReadFurtherFrom,
|
|
768
|
-
walkDescendants,
|
|
768
|
+
walkDescendants as walkDescendants2,
|
|
769
769
|
writtenReading
|
|
770
770
|
} from "@suss/extractor";
|
|
771
771
|
|
|
772
|
+
// src/envReads.ts
|
|
773
|
+
import { runtimeConfigBinding } from "@suss/behavioral-ir";
|
|
774
|
+
import { SKIP_CHILDREN, walkDescendants } from "@suss/extractor";
|
|
775
|
+
var PYTHON_ENV_RECOGNITION = "python-env";
|
|
776
|
+
var DEFERRED_BODY_TYPES = /* @__PURE__ */ new Set(["function_definition", "lambda"]);
|
|
777
|
+
function envReadEffects(root, module) {
|
|
778
|
+
const effects = [];
|
|
779
|
+
const startScope = module.scopeFor.get(root.id) ?? module.moduleScope;
|
|
780
|
+
walkDescendants(root, startScope, {
|
|
781
|
+
at: (node, scope) => {
|
|
782
|
+
const read = envReadAt(node, scope);
|
|
783
|
+
if (read !== null) {
|
|
784
|
+
effects.push(configReadEffect(read));
|
|
785
|
+
}
|
|
786
|
+
},
|
|
787
|
+
into: (node, scope) => {
|
|
788
|
+
if (DEFERRED_BODY_TYPES.has(node.type)) {
|
|
789
|
+
return SKIP_CHILDREN;
|
|
790
|
+
}
|
|
791
|
+
return module.scopeFor.get(node.id) ?? scope;
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
return effects;
|
|
795
|
+
}
|
|
796
|
+
function configReadEffect(read) {
|
|
797
|
+
return {
|
|
798
|
+
type: "interaction",
|
|
799
|
+
binding: runtimeConfigBinding({
|
|
800
|
+
recognition: PYTHON_ENV_RECOGNITION,
|
|
801
|
+
deploymentTarget: "lambda",
|
|
802
|
+
instanceName: "<unknown>"
|
|
803
|
+
}),
|
|
804
|
+
callee: `os.environ["${read.name}"]`,
|
|
805
|
+
interaction: {
|
|
806
|
+
class: "config-read",
|
|
807
|
+
name: read.name,
|
|
808
|
+
defaulted: read.defaulted
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
function envReadAt(node, scope) {
|
|
813
|
+
if (node.type === "subscript") {
|
|
814
|
+
return subscriptRead(node, scope);
|
|
815
|
+
}
|
|
816
|
+
if (node.type === "call") {
|
|
817
|
+
return callRead(node, scope);
|
|
818
|
+
}
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
function subscriptRead(node, scope) {
|
|
822
|
+
const value = field(node, "value");
|
|
823
|
+
const index = field(node, "subscript");
|
|
824
|
+
if (value === null || index === null || !isEnviron(value, scope)) {
|
|
825
|
+
return null;
|
|
826
|
+
}
|
|
827
|
+
const name = stringLiteralValue(index);
|
|
828
|
+
if (name === null || isAssignedTo(node)) {
|
|
829
|
+
return null;
|
|
830
|
+
}
|
|
831
|
+
return { name, defaulted: isDefaultedAt(node) };
|
|
832
|
+
}
|
|
833
|
+
function isAssignedTo(node) {
|
|
834
|
+
const parent = node.parent;
|
|
835
|
+
if (parent === null) {
|
|
836
|
+
return false;
|
|
837
|
+
}
|
|
838
|
+
if (parent.type === "delete_statement") {
|
|
839
|
+
return true;
|
|
840
|
+
}
|
|
841
|
+
return (parent.type === "assignment" || parent.type === "augmented_assignment") && field(parent, "left")?.id === node.id;
|
|
842
|
+
}
|
|
843
|
+
function callRead(node, scope) {
|
|
844
|
+
const callee = field(node, "function");
|
|
845
|
+
const args = field(node, "arguments");
|
|
846
|
+
if (callee === null || args === null || !isEnvGetter(callee, scope)) {
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
const positional = args.namedChildren.filter(
|
|
850
|
+
(arg) => arg !== null && arg.type !== "keyword_argument"
|
|
851
|
+
);
|
|
852
|
+
const name = positional[0] === void 0 ? null : stringLiteralValue(positional[0]);
|
|
853
|
+
if (name === null) {
|
|
854
|
+
return null;
|
|
855
|
+
}
|
|
856
|
+
const keywords = args.namedChildren.filter(
|
|
857
|
+
(arg) => arg !== null && arg.type === "keyword_argument"
|
|
858
|
+
);
|
|
859
|
+
const hasDefault = positional.length > 1 || keywords.some((arg) => field(arg, "name")?.text === "default");
|
|
860
|
+
return { name, defaulted: hasDefault || isDefaultedAt(node) };
|
|
861
|
+
}
|
|
862
|
+
function isEnvGetter(callee, scope) {
|
|
863
|
+
if (callee.type === "attribute") {
|
|
864
|
+
const object2 = field(callee, "object");
|
|
865
|
+
const attribute = field(callee, "attribute")?.text;
|
|
866
|
+
if (object2 === null) {
|
|
867
|
+
return false;
|
|
868
|
+
}
|
|
869
|
+
if (attribute === "get") {
|
|
870
|
+
return isEnviron(object2, scope);
|
|
871
|
+
}
|
|
872
|
+
return attribute === "getenv" && isOsModule(object2, scope);
|
|
873
|
+
}
|
|
874
|
+
return isImportedFromOs(callee, scope, "getenv");
|
|
875
|
+
}
|
|
876
|
+
function isEnviron(node, scope) {
|
|
877
|
+
if (node.type === "attribute") {
|
|
878
|
+
const object2 = field(node, "object");
|
|
879
|
+
return object2 !== null && field(node, "attribute")?.text === "environ" && isOsModule(object2, scope);
|
|
880
|
+
}
|
|
881
|
+
return isImportedFromOs(node, scope, "environ");
|
|
882
|
+
}
|
|
883
|
+
function isOsModule(node, scope) {
|
|
884
|
+
if (node.type !== "identifier") {
|
|
885
|
+
return false;
|
|
886
|
+
}
|
|
887
|
+
const binding = resolveName(scope, node.text);
|
|
888
|
+
return binding?.kind === "import" && binding.module === "os" && binding.relativeLevel === 0;
|
|
889
|
+
}
|
|
890
|
+
function isImportedFromOs(node, scope, name) {
|
|
891
|
+
if (node.type !== "identifier") {
|
|
892
|
+
return false;
|
|
893
|
+
}
|
|
894
|
+
const binding = resolveName(scope, node.text);
|
|
895
|
+
return binding?.kind === "importFrom" && binding.module === "os" && binding.relativeLevel === 0 && binding.importedName === name;
|
|
896
|
+
}
|
|
897
|
+
function isDefaultedAt(node) {
|
|
898
|
+
let child = node;
|
|
899
|
+
let parent = node.parent;
|
|
900
|
+
while (parent !== null) {
|
|
901
|
+
if (parent.type === "parenthesized_expression") {
|
|
902
|
+
child = parent;
|
|
903
|
+
parent = parent.parent;
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
if (parent.type !== "boolean_operator" || field(parent, "operator")?.text !== "or") {
|
|
907
|
+
return false;
|
|
908
|
+
}
|
|
909
|
+
if (field(parent, "left")?.id === child.id) {
|
|
910
|
+
return true;
|
|
911
|
+
}
|
|
912
|
+
child = parent;
|
|
913
|
+
parent = parent.parent;
|
|
914
|
+
}
|
|
915
|
+
return false;
|
|
916
|
+
}
|
|
917
|
+
|
|
772
918
|
// src/facts/resolve.ts
|
|
773
|
-
import { deriveOnDemand, evaluate } from "@suss/datalog";
|
|
919
|
+
import { deriveOnDemand, evaluate as evaluate2 } from "@suss/datalog";
|
|
774
920
|
|
|
775
921
|
// ../../resolution/dist/index.js
|
|
776
922
|
import { ruleLabel } from "@suss/datalog";
|
|
923
|
+
import { Database, evaluate, proofOf, witnesses } from "@suss/datalog";
|
|
777
924
|
import { constant, lit, rule, variable as v } from "@suss/datalog";
|
|
778
|
-
function
|
|
925
|
+
function exportTrail(proof, describe3) {
|
|
926
|
+
if (proof.kind !== "derived") {
|
|
927
|
+
return [];
|
|
928
|
+
}
|
|
929
|
+
const name = ruleLabel(proof.rule);
|
|
930
|
+
if (name === "re-export") {
|
|
931
|
+
const [m, n, m2, n2] = proof.premises[0].tuple;
|
|
932
|
+
return [
|
|
933
|
+
`${describe3(m)} takes ${String(n)} from ${describe3(m2)}, where it is called ${String(n2)}`,
|
|
934
|
+
...exportTrail(proof.premises[1], describe3)
|
|
935
|
+
];
|
|
936
|
+
}
|
|
937
|
+
if (name === "re-export all") {
|
|
938
|
+
const [m, m2] = proof.premises[0].tuple;
|
|
939
|
+
return [
|
|
940
|
+
`${describe3(m)} forwards everything ${describe3(m2)} exports`,
|
|
941
|
+
...exportTrail(proof.premises[1], describe3)
|
|
942
|
+
];
|
|
943
|
+
}
|
|
944
|
+
return [];
|
|
945
|
+
}
|
|
946
|
+
function argumentReason(proof, describe3) {
|
|
947
|
+
const [, p, a] = proof.tuple;
|
|
948
|
+
if (proof.kind !== "derived") {
|
|
949
|
+
return `${describe3(p)} is a parameter, and a call passes it ${describe3(a)}`;
|
|
950
|
+
}
|
|
951
|
+
const f = proof.premises[0].tuple[0];
|
|
952
|
+
return `${describe3(p)} is a parameter of ${describe3(f)}, and a call passes it ${describe3(a)}`;
|
|
953
|
+
}
|
|
954
|
+
var DEFAULT_PHRASES = {
|
|
955
|
+
alias: ({ tuple: tuple2, describe: describe3 }) => ({
|
|
956
|
+
reason: `${describe3(tuple2[0])} is declared as ${describe3(tuple2[1])}`
|
|
957
|
+
}),
|
|
958
|
+
"last write": ({ tuple: tuple2, describe: describe3 }) => ({
|
|
959
|
+
reason: `${describe3(tuple2[0])} is written more than once, and the last write leaves it as ${describe3(tuple2[1])}`
|
|
960
|
+
}),
|
|
961
|
+
fallback: ({ tuple: tuple2, describe: describe3 }) => ({
|
|
962
|
+
reason: `${describe3(tuple2[0])} is a fallback expression, and ${describe3(tuple2[1])} is the branch that resolves`
|
|
963
|
+
}),
|
|
964
|
+
import: ({ tuple: tuple2, premises, describe: describe3 }) => {
|
|
965
|
+
const [, module, name] = premises[0].tuple;
|
|
966
|
+
return {
|
|
967
|
+
reason: `${describe3(tuple2[0])} is imported from ${describe3(module)} under the name ${String(name)}`,
|
|
968
|
+
notes: exportTrail(premises[1], describe3)
|
|
969
|
+
};
|
|
970
|
+
},
|
|
971
|
+
argument: ({ premises, describe: describe3 }) => ({
|
|
972
|
+
reason: argumentReason(premises[0], describe3)
|
|
973
|
+
}),
|
|
974
|
+
"property read": ({ tuple: tuple2, premises, describe: describe3 }) => {
|
|
975
|
+
const [, object2, name] = premises[0].tuple;
|
|
976
|
+
const contains = premises[2];
|
|
977
|
+
const inherited = contains.kind === "derived" && contains.premises.length > 1;
|
|
978
|
+
return {
|
|
979
|
+
reason: `${describe3(tuple2[0])} reads ${String(name)} off ${describe3(object2)}, which contains ${describe3(tuple2[1])}`,
|
|
980
|
+
notes: inherited ? [`${String(name)} comes from a base class`] : []
|
|
981
|
+
};
|
|
982
|
+
},
|
|
983
|
+
"class instance": ({ tuple: tuple2, premises, describe: describe3, inline }) => ({
|
|
984
|
+
reason: `${describe3(tuple2[0])} makes an instance of ${describe3(tuple2[1])}`,
|
|
985
|
+
notes: inline(premises[1])
|
|
986
|
+
}),
|
|
987
|
+
"factory unwrap": ({ tuple: tuple2, premises, describe: describe3, inline }) => {
|
|
988
|
+
const factory = premises[1].tuple[1];
|
|
989
|
+
return {
|
|
990
|
+
reason: `${describe3(tuple2[0])} calls ${describe3(factory)}, a factory that passes its argument through, so it comes down to ${describe3(tuple2[1])}`,
|
|
991
|
+
notes: inline(premises[1])
|
|
992
|
+
};
|
|
993
|
+
},
|
|
994
|
+
"declared wrapper": ({ tuple: tuple2, premises, describe: describe3 }) => {
|
|
995
|
+
const callee = String(premises[0].tuple[1]);
|
|
996
|
+
const module = String(premises[2].tuple[1]);
|
|
997
|
+
const argument = String(premises[1].tuple[1]);
|
|
998
|
+
return {
|
|
999
|
+
reason: `${describe3(tuple2[0])} calls ${callee} from ${module}, which passes argument ${argument} through, so it comes down to ${describe3(tuple2[1])}`,
|
|
1000
|
+
assumptions: [
|
|
1001
|
+
`a pack declares that ${callee} from ${module} passes argument ${argument} through to its result`
|
|
1002
|
+
]
|
|
1003
|
+
};
|
|
1004
|
+
},
|
|
1005
|
+
"call result": ({ tuple: tuple2, premises, describe: describe3, inline }) => {
|
|
1006
|
+
const invokes = premises[0];
|
|
1007
|
+
const invoked = invokes.tuple[1];
|
|
1008
|
+
const calleeWalks = invokes.kind === "derived" ? invokes.premises.filter(
|
|
1009
|
+
(premise) => premise.kind === "derived" && (premise.relation === "comesTo" || premise.relation === "givesBack")
|
|
1010
|
+
) : [];
|
|
1011
|
+
return {
|
|
1012
|
+
reason: `${describe3(tuple2[0])} runs ${describe3(invoked)}, which returns ${describe3(tuple2[1])}`,
|
|
1013
|
+
notes: calleeWalks.flatMap((premise) => inline(premise))
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
var CHAIN_RELATIONS = /* @__PURE__ */ new Set([
|
|
1018
|
+
"resolves",
|
|
1019
|
+
"comesTo",
|
|
1020
|
+
"givesBack",
|
|
1021
|
+
"isWrittenAs",
|
|
1022
|
+
"comesFrom",
|
|
1023
|
+
"reaches",
|
|
1024
|
+
"stepsTo"
|
|
1025
|
+
]);
|
|
1026
|
+
function inlineChain(proof, state) {
|
|
1027
|
+
const explained = explainResolutionProof(proof, {
|
|
1028
|
+
describe: state.describe,
|
|
1029
|
+
phrases: state.phrases
|
|
1030
|
+
});
|
|
1031
|
+
if (explained === null || explained.steps.length === 0) {
|
|
1032
|
+
return [];
|
|
1033
|
+
}
|
|
1034
|
+
if (explained.truncated) {
|
|
1035
|
+
state.truncated = true;
|
|
1036
|
+
}
|
|
1037
|
+
return explained.steps.map((step) => step.reason);
|
|
1038
|
+
}
|
|
1039
|
+
function stepFrom(proof, state) {
|
|
1040
|
+
const name = ruleLabel(proof.rule);
|
|
1041
|
+
const phrase = state.phrases[name];
|
|
1042
|
+
const from = proof.tuple[0];
|
|
1043
|
+
const to = proof.tuple[1];
|
|
1044
|
+
if (phrase === void 0) {
|
|
1045
|
+
return {
|
|
1046
|
+
from,
|
|
1047
|
+
to,
|
|
1048
|
+
rule: name,
|
|
1049
|
+
reason: `${state.describe(from)} steps to ${state.describe(to)} (${name})`,
|
|
1050
|
+
notes: [],
|
|
1051
|
+
assumptions: []
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
const said = phrase({
|
|
1055
|
+
tuple: proof.tuple,
|
|
1056
|
+
premises: proof.premises,
|
|
1057
|
+
describe: state.describe,
|
|
1058
|
+
inline: (nested) => inlineChain(nested, state)
|
|
1059
|
+
});
|
|
1060
|
+
return {
|
|
1061
|
+
from,
|
|
1062
|
+
to,
|
|
1063
|
+
rule: name,
|
|
1064
|
+
reason: said.reason,
|
|
1065
|
+
notes: said.notes ?? [],
|
|
1066
|
+
assumptions: said.assumptions ?? []
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
function importStep(proof, state) {
|
|
1070
|
+
const [x, module, name] = proof.tuple;
|
|
1071
|
+
return {
|
|
1072
|
+
from: x,
|
|
1073
|
+
to: module,
|
|
1074
|
+
rule: "import",
|
|
1075
|
+
reason: `${state.describe(x)} is imported from ${state.describe(module)} under the name ${String(name)}`,
|
|
1076
|
+
notes: [],
|
|
1077
|
+
assumptions: []
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
var flattenInto = (proof, state) => {
|
|
1081
|
+
if (proof.kind === "truncated") {
|
|
1082
|
+
state.truncated = true;
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
if (proof.kind !== "derived") {
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
const relation = proof.relation;
|
|
1089
|
+
if (relation === "stepsTo") {
|
|
1090
|
+
state.steps.push(stepFrom(proof, state));
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
if (relation === "reaches") {
|
|
1094
|
+
for (const p of proof.premises) {
|
|
1095
|
+
flattenInto(p, state);
|
|
1096
|
+
}
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
if (relation === "comesFrom") {
|
|
1100
|
+
if (proof.premises.length === 2) {
|
|
1101
|
+
flattenInto(proof.premises[0], state);
|
|
1102
|
+
state.steps.push(importStep(proof.premises[1], state));
|
|
1103
|
+
} else {
|
|
1104
|
+
state.steps.push(importStep(proof.premises[0], state));
|
|
1105
|
+
}
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
for (const p of proof.premises) {
|
|
1109
|
+
if (p.kind === "derived" && (p.relation === "reaches" || p.relation === "comesTo")) {
|
|
1110
|
+
flattenInto(p, state);
|
|
1111
|
+
}
|
|
1112
|
+
if (p.kind === "truncated") {
|
|
1113
|
+
state.truncated = true;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
};
|
|
1117
|
+
function explainResolutionProof(proof, options) {
|
|
1118
|
+
if (proof.kind === "absence") {
|
|
1119
|
+
return null;
|
|
1120
|
+
}
|
|
1121
|
+
if (proof.kind === "derived" && !CHAIN_RELATIONS.has(proof.relation)) {
|
|
1122
|
+
return null;
|
|
1123
|
+
}
|
|
1124
|
+
const state = {
|
|
1125
|
+
steps: [],
|
|
1126
|
+
truncated: proof.kind === "truncated",
|
|
1127
|
+
describe: options.describe,
|
|
1128
|
+
phrases: { ...DEFAULT_PHRASES, ...options.phrases }
|
|
1129
|
+
};
|
|
1130
|
+
flattenInto(proof, state);
|
|
1131
|
+
const atoms = proof.kind === "fact" || proof.kind === "truncated" ? [proof.tuple[0]] : [proof.tuple[0], ...state.steps.map((step) => step.to)];
|
|
1132
|
+
return {
|
|
1133
|
+
atoms,
|
|
1134
|
+
steps: state.steps,
|
|
1135
|
+
assumptions: state.steps.flatMap((step) => step.assumptions),
|
|
1136
|
+
truncated: state.truncated
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
function renderExplanation(explanation, describe3) {
|
|
1140
|
+
const lines = [];
|
|
1141
|
+
if (explanation.steps.length > 0) {
|
|
1142
|
+
lines.push(explanation.atoms.map((atom) => describe3(atom)).join(" -> "));
|
|
1143
|
+
}
|
|
1144
|
+
for (const step of explanation.steps) {
|
|
1145
|
+
lines.push(` ${step.reason}`);
|
|
1146
|
+
for (const note of step.notes) {
|
|
1147
|
+
lines.push(` ${note}`);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
for (const assumption of explanation.assumptions) {
|
|
1151
|
+
lines.push(` assuming ${assumption}`);
|
|
1152
|
+
}
|
|
1153
|
+
if (explanation.truncated) {
|
|
1154
|
+
lines.push(" the chain goes on past the proof depth cap");
|
|
1155
|
+
}
|
|
1156
|
+
return lines;
|
|
1157
|
+
}
|
|
1158
|
+
function joinMountedPath(prefix, path6) {
|
|
779
1159
|
const trimmed = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
780
|
-
return trimmed === "" ?
|
|
1160
|
+
return trimmed === "" ? path6 : trimmed + path6;
|
|
781
1161
|
}
|
|
782
1162
|
function mountPathsOf(edges, childId) {
|
|
783
1163
|
return pathsOf(edges, /* @__PURE__ */ new Map(), childId, /* @__PURE__ */ new Set());
|
|
@@ -814,6 +1194,88 @@ function pathsOf(edges, memo, childId, visiting) {
|
|
|
814
1194
|
memo.set(childId, result);
|
|
815
1195
|
return result;
|
|
816
1196
|
}
|
|
1197
|
+
function explainResolvedKey(options) {
|
|
1198
|
+
const { db, rules, key, locate, displayPath } = options;
|
|
1199
|
+
const proofDb = new Database();
|
|
1200
|
+
let baseFacts = 0;
|
|
1201
|
+
for (const relation of db.relationNames()) {
|
|
1202
|
+
for (const tuple2 of db.facts(relation)) {
|
|
1203
|
+
proofDb.add(relation, tuple2);
|
|
1204
|
+
baseFacts++;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
const started = performance.now();
|
|
1208
|
+
evaluate(proofDb, rules, witnesses);
|
|
1209
|
+
const evaluateMs = performance.now() - started;
|
|
1210
|
+
const derivedFacts = proofDb.relationNames().reduce((count, relation) => count + proofDb.size(relation), 0) - baseFacts;
|
|
1211
|
+
const targets = new Set(
|
|
1212
|
+
proofDb.lookup("resolves", 0, key).map((tuple2) => String(tuple2[1]))
|
|
1213
|
+
);
|
|
1214
|
+
if (targets.size !== 1) {
|
|
1215
|
+
return null;
|
|
1216
|
+
}
|
|
1217
|
+
const target = [...targets][0];
|
|
1218
|
+
const proof = proofOf(
|
|
1219
|
+
proofDb,
|
|
1220
|
+
"resolves",
|
|
1221
|
+
[key, target],
|
|
1222
|
+
options.maxDepth === void 0 ? {} : { maxDepth: options.maxDepth }
|
|
1223
|
+
);
|
|
1224
|
+
const describe3 = (atom) => {
|
|
1225
|
+
const location = locate(String(atom));
|
|
1226
|
+
return location === null ? displayPath(String(atom)) : `${location.name} (${location.file}:${location.line})`;
|
|
1227
|
+
};
|
|
1228
|
+
const explanation = explainResolutionProof(proof, {
|
|
1229
|
+
describe: describe3,
|
|
1230
|
+
...options.phrases === void 0 ? {} : { phrases: options.phrases }
|
|
1231
|
+
});
|
|
1232
|
+
if (explanation === null) {
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
const targetLocation = locate(target);
|
|
1236
|
+
if (targetLocation === null) {
|
|
1237
|
+
return null;
|
|
1238
|
+
}
|
|
1239
|
+
return {
|
|
1240
|
+
explanation,
|
|
1241
|
+
chain: explanation.atoms.map(describe3),
|
|
1242
|
+
lines: renderExplanation(explanation, describe3),
|
|
1243
|
+
target: targetLocation,
|
|
1244
|
+
stats: { baseFacts, derivedFacts, evaluateMs }
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
var NO_PLACEHOLDERS = /* @__PURE__ */ new Set();
|
|
1248
|
+
function singleAnswers(rows, placeholders = NO_PLACEHOLDERS) {
|
|
1249
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
1250
|
+
for (const row of rows) {
|
|
1251
|
+
const key = String(row[0]);
|
|
1252
|
+
const answer = String(row[1]);
|
|
1253
|
+
if (answer === key) {
|
|
1254
|
+
continue;
|
|
1255
|
+
}
|
|
1256
|
+
const set2 = candidates.get(key) ?? /* @__PURE__ */ new Set();
|
|
1257
|
+
set2.add(answer);
|
|
1258
|
+
candidates.set(key, set2);
|
|
1259
|
+
}
|
|
1260
|
+
const settled = /* @__PURE__ */ new Map();
|
|
1261
|
+
for (const [key, set2] of candidates) {
|
|
1262
|
+
const answers = withoutPlaceholders(set2, placeholders);
|
|
1263
|
+
if (answers.length === 1) {
|
|
1264
|
+
settled.set(key, answers[0]);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
return settled;
|
|
1268
|
+
}
|
|
1269
|
+
function placeholderValues(db) {
|
|
1270
|
+
return new Set(db.facts("placeholderValue").map((row) => String(row[0])));
|
|
1271
|
+
}
|
|
1272
|
+
function withoutPlaceholders(answers, placeholders) {
|
|
1273
|
+
const kept = [...answers].filter((answer) => !placeholders.has(answer));
|
|
1274
|
+
if (kept.length === 0) {
|
|
1275
|
+
return [...answers];
|
|
1276
|
+
}
|
|
1277
|
+
return kept;
|
|
1278
|
+
}
|
|
817
1279
|
var VALUE_STEP = constant("value");
|
|
818
1280
|
var NAMESPACE_IMPORT_NAME = "*";
|
|
819
1281
|
var NAMESPACE_IMPORT = constant(NAMESPACE_IMPORT_NAME);
|
|
@@ -927,9 +1389,9 @@ var RESOLUTION_RULES = [
|
|
|
927
1389
|
[lit("invokes", v("r"), v("f")), lit("returnsValue", v("f"), v("ret"))],
|
|
928
1390
|
"call result"
|
|
929
1391
|
),
|
|
930
|
-
// Where a walk gets to, and whether it ran a call on the way.
|
|
931
|
-
//
|
|
932
|
-
//
|
|
1392
|
+
// Where a walk gets to, and whether it ran a call on the way. The walk
|
|
1393
|
+
// so far comes first, so demand stays on the value asked about; the
|
|
1394
|
+
// README says what the other order cost.
|
|
933
1395
|
rule(
|
|
934
1396
|
"reaches",
|
|
935
1397
|
[v("x"), v("z"), v("kind")],
|
|
@@ -939,27 +1401,27 @@ var RESOLUTION_RULES = [
|
|
|
939
1401
|
"reaches",
|
|
940
1402
|
[v("x"), v("z"), v("kind")],
|
|
941
1403
|
[
|
|
942
|
-
lit("
|
|
943
|
-
lit("
|
|
1404
|
+
lit("reaches", v("x"), v("y"), VALUE_STEP),
|
|
1405
|
+
lit("stepsTo", v("y"), v("z"), v("kind"))
|
|
944
1406
|
]
|
|
945
1407
|
),
|
|
946
|
-
// Two rules rather than one leaving the
|
|
947
|
-
//
|
|
1408
|
+
// Two rules rather than one leaving the next step's kind free. A free
|
|
1409
|
+
// kind is a second question about the same relation, and a
|
|
948
1410
|
// demand-driven run then derives both to answer either.
|
|
949
1411
|
rule(
|
|
950
1412
|
"reaches",
|
|
951
1413
|
[v("x"), v("z"), RESULT_STEP],
|
|
952
1414
|
[
|
|
953
|
-
lit("
|
|
954
|
-
lit("
|
|
1415
|
+
lit("reaches", v("x"), v("y"), RESULT_STEP),
|
|
1416
|
+
lit("stepsTo", v("y"), v("z"), VALUE_STEP)
|
|
955
1417
|
]
|
|
956
1418
|
),
|
|
957
1419
|
rule(
|
|
958
1420
|
"reaches",
|
|
959
1421
|
[v("x"), v("z"), RESULT_STEP],
|
|
960
1422
|
[
|
|
961
|
-
lit("
|
|
962
|
-
lit("
|
|
1423
|
+
lit("reaches", v("x"), v("y"), RESULT_STEP),
|
|
1424
|
+
lit("stepsTo", v("y"), v("z"), RESULT_STEP)
|
|
963
1425
|
]
|
|
964
1426
|
),
|
|
965
1427
|
// What a value comes down to: the walk that never ran a call, stopped
|
|
@@ -1005,6 +1467,18 @@ var RESOLUTION_RULES = [
|
|
|
1005
1467
|
[v("x"), v("z")],
|
|
1006
1468
|
[lit("reaches", v("x"), v("z"), VALUE_STEP), lit("objectValue", v("z"))]
|
|
1007
1469
|
),
|
|
1470
|
+
// A call is written as whatever its callee's return value is written
|
|
1471
|
+
// as. Unlike `comesTo`, this has nothing to protect: which expression
|
|
1472
|
+
// a value was written as does not compete with unwrapping it.
|
|
1473
|
+
rule(
|
|
1474
|
+
"isWrittenAs",
|
|
1475
|
+
[v("x"), v("z")],
|
|
1476
|
+
[
|
|
1477
|
+
lit("invokes", v("x"), v("f")),
|
|
1478
|
+
lit("returnsValue", v("f"), v("ret")),
|
|
1479
|
+
lit("isWrittenAs", v("ret"), v("z"))
|
|
1480
|
+
]
|
|
1481
|
+
),
|
|
1008
1482
|
// What one call site put in a parameter, told apart from what the
|
|
1009
1483
|
// other callers passed.
|
|
1010
1484
|
rule(
|
|
@@ -1305,29 +1779,14 @@ var RESOLUTION_QUESTIONS = [
|
|
|
1305
1779
|
// here demands reaches or callsInto. The chains are closures with
|
|
1306
1780
|
// no depth bound: an alias run of any length resolves.
|
|
1307
1781
|
rule("callOriginChain", [v("x"), v("x")], [lit("wantedCallOrigin", v("x"))]),
|
|
1782
|
+
// The chain with at least one hop taken, which is what tells a
|
|
1783
|
+
// destructured binding apart from asking about a node directly. The
|
|
1784
|
+
// whole chain is the seed plus those hops.
|
|
1308
1785
|
rule(
|
|
1309
1786
|
"callOriginChain",
|
|
1310
1787
|
[v("x"), v("z")],
|
|
1311
|
-
[lit("
|
|
1312
|
-
),
|
|
1313
|
-
rule(
|
|
1314
|
-
"callOriginChain",
|
|
1315
|
-
[v("x"), v("z")],
|
|
1316
|
-
[
|
|
1317
|
-
lit("callOriginChain", v("x"), v("y")),
|
|
1318
|
-
lit("endsHolding", v("y"), v("z"))
|
|
1319
|
-
]
|
|
1320
|
-
),
|
|
1321
|
-
rule(
|
|
1322
|
-
"callOriginChain",
|
|
1323
|
-
[v("x"), v("z")],
|
|
1324
|
-
[
|
|
1325
|
-
lit("callOriginChain", v("x"), v("y")),
|
|
1326
|
-
lit("fallbackBranch", v("y"), v("z"))
|
|
1327
|
-
]
|
|
1788
|
+
[lit("callOriginChainStepped", v("x"), v("z"))]
|
|
1328
1789
|
),
|
|
1329
|
-
// The chain with at least one hop taken, which is what tells a
|
|
1330
|
-
// destructured binding apart from asking about a node directly.
|
|
1331
1790
|
rule(
|
|
1332
1791
|
"callOriginChainStepped",
|
|
1333
1792
|
[v("x"), v("z")],
|
|
@@ -1567,23 +2026,33 @@ function resolveCalls(db, callKeys) {
|
|
|
1567
2026
|
for (const callKey of callKeys) {
|
|
1568
2027
|
db.add("wanted", [callKey]);
|
|
1569
2028
|
}
|
|
1570
|
-
|
|
2029
|
+
evaluate2(db, RESOLUTION_PROGRAM.rules);
|
|
1571
2030
|
}
|
|
1572
2031
|
function subjectConstructions(db, valueKeys) {
|
|
1573
2032
|
const found = /* @__PURE__ */ new Map();
|
|
1574
2033
|
if (valueKeys.length === 0) {
|
|
1575
2034
|
return found;
|
|
1576
2035
|
}
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
2036
|
+
const askSubjects = (keys) => {
|
|
2037
|
+
for (const key of keys) {
|
|
2038
|
+
db.add("wantedSubject", [key]);
|
|
2039
|
+
}
|
|
2040
|
+
evaluate2(db, RESOLUTION_PROGRAM.rules);
|
|
2041
|
+
};
|
|
2042
|
+
askSubjects(valueKeys);
|
|
2043
|
+
const placeholders = placeholderValues(db);
|
|
2044
|
+
const written = singleAnswers(db.facts("wantedSubjectWritten"), placeholders);
|
|
2045
|
+
const calls = new Set(db.facts("call").map((row) => String(row[0])));
|
|
2046
|
+
const throughCalls = new Set(
|
|
2047
|
+
[...written.values()].filter((candidate) => calls.has(candidate))
|
|
2048
|
+
);
|
|
2049
|
+
if (throughCalls.size > 0) {
|
|
2050
|
+
askSubjects(throughCalls);
|
|
1586
2051
|
}
|
|
2052
|
+
const writtenThroughCall = singleAnswers(
|
|
2053
|
+
db.facts("wantedSubjectWritten"),
|
|
2054
|
+
placeholders
|
|
2055
|
+
);
|
|
1587
2056
|
const origins = /* @__PURE__ */ new Map();
|
|
1588
2057
|
for (const row of db.facts("wantedSubjectConstruction")) {
|
|
1589
2058
|
const key = `${String(row[0])}|${String(row[1])}`;
|
|
@@ -1592,12 +2061,14 @@ function subjectConstructions(db, valueKeys) {
|
|
|
1592
2061
|
origins.set(key, listed);
|
|
1593
2062
|
}
|
|
1594
2063
|
for (const valueKey2 of valueKeys) {
|
|
1595
|
-
const
|
|
1596
|
-
if (
|
|
2064
|
+
const direct = written.get(valueKey2);
|
|
2065
|
+
if (direct === void 0) {
|
|
1597
2066
|
continue;
|
|
1598
2067
|
}
|
|
1599
|
-
const
|
|
1600
|
-
const
|
|
2068
|
+
const deeper = calls.has(direct) ? writtenThroughCall.get(direct) : void 0;
|
|
2069
|
+
const constructionKey = deeper ?? direct;
|
|
2070
|
+
const originOf = deeper === void 0 ? valueKey2 : direct;
|
|
2071
|
+
const from = origins.get(`${originOf}|${constructionKey}`);
|
|
1601
2072
|
if (from !== void 0) {
|
|
1602
2073
|
found.set(valueKey2, { constructionKey, origins: from });
|
|
1603
2074
|
}
|
|
@@ -1793,6 +2264,9 @@ function emitExpressionFact(emitter, child) {
|
|
|
1793
2264
|
if (WRITTEN_VALUE_TYPES.has(child.type)) {
|
|
1794
2265
|
add(emitter, "writtenValue", nodeId(emitter.filePath, child));
|
|
1795
2266
|
}
|
|
2267
|
+
if (child.type === "none") {
|
|
2268
|
+
add(emitter, "placeholderValue", nodeId(emitter.filePath, child));
|
|
2269
|
+
}
|
|
1796
2270
|
}
|
|
1797
2271
|
function emitExpressionFacts(emitter, node) {
|
|
1798
2272
|
walkExpressions(emitter, node, (child) => {
|
|
@@ -2251,7 +2725,7 @@ function predicateOf(node) {
|
|
|
2251
2725
|
}
|
|
2252
2726
|
|
|
2253
2727
|
// src/paths/effects.ts
|
|
2254
|
-
var
|
|
2728
|
+
var NESTED_DEFINITION_TYPES = /* @__PURE__ */ new Set(["function_definition"]);
|
|
2255
2729
|
var LITERAL_ARGS = {
|
|
2256
2730
|
string: (node) => ({ kind: "string", value: node.text.slice(1, -1) }),
|
|
2257
2731
|
integer: (node) => {
|
|
@@ -2267,7 +2741,7 @@ var LITERAL_ARGS = {
|
|
|
2267
2741
|
};
|
|
2268
2742
|
function bodyCalls(node, found = []) {
|
|
2269
2743
|
for (const child of node.namedChildren) {
|
|
2270
|
-
if (child === null ||
|
|
2744
|
+
if (child === null || NESTED_DEFINITION_TYPES.has(child.type)) {
|
|
2271
2745
|
continue;
|
|
2272
2746
|
}
|
|
2273
2747
|
if (child.type === "call") {
|
|
@@ -2309,26 +2783,12 @@ function argsOf(call) {
|
|
|
2309
2783
|
function calleeText(call) {
|
|
2310
2784
|
return field(call, "function")?.text ?? call.text;
|
|
2311
2785
|
}
|
|
2312
|
-
function withoutChainLinks(calls) {
|
|
2313
|
-
const isLink = /* @__PURE__ */ new Set();
|
|
2314
|
-
for (const call of calls) {
|
|
2315
|
-
const callee = field(call, "function");
|
|
2316
|
-
if (callee === null || callee.type !== "attribute") {
|
|
2317
|
-
continue;
|
|
2318
|
-
}
|
|
2319
|
-
const object2 = field(callee, "object");
|
|
2320
|
-
if (object2 !== null && object2.type === "call") {
|
|
2321
|
-
isLink.add(object2.id);
|
|
2322
|
-
}
|
|
2323
|
-
}
|
|
2324
|
-
return calls.filter((call) => !isLink.has(call.id));
|
|
2325
|
-
}
|
|
2326
2786
|
function invocationEffects(definitionNode) {
|
|
2327
2787
|
const body = field(definitionNode, "body");
|
|
2328
2788
|
if (body === null) {
|
|
2329
2789
|
return [];
|
|
2330
2790
|
}
|
|
2331
|
-
const calls =
|
|
2791
|
+
const calls = bodyCalls(body).sort((a, b) => a.endIndex - b.endIndex);
|
|
2332
2792
|
if (calls.length === 0) {
|
|
2333
2793
|
return [];
|
|
2334
2794
|
}
|
|
@@ -3206,10 +3666,10 @@ function mergeDefs(...defs) {
|
|
|
3206
3666
|
function cloneDef(schema) {
|
|
3207
3667
|
return mergeDefs(schema._zod.def);
|
|
3208
3668
|
}
|
|
3209
|
-
function getElementAtPath(obj,
|
|
3210
|
-
if (!
|
|
3669
|
+
function getElementAtPath(obj, path6) {
|
|
3670
|
+
if (!path6)
|
|
3211
3671
|
return obj;
|
|
3212
|
-
return
|
|
3672
|
+
return path6.reduce((acc, key) => acc?.[key], obj);
|
|
3213
3673
|
}
|
|
3214
3674
|
function promiseAllObject(promisesObj) {
|
|
3215
3675
|
const keys = Object.keys(promisesObj);
|
|
@@ -3618,11 +4078,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
3618
4078
|
}
|
|
3619
4079
|
return false;
|
|
3620
4080
|
}
|
|
3621
|
-
function prefixIssues(
|
|
4081
|
+
function prefixIssues(path6, issues) {
|
|
3622
4082
|
return issues.map((iss) => {
|
|
3623
4083
|
var _a3;
|
|
3624
4084
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
3625
|
-
iss.path.unshift(
|
|
4085
|
+
iss.path.unshift(path6);
|
|
3626
4086
|
return iss;
|
|
3627
4087
|
});
|
|
3628
4088
|
}
|
|
@@ -3769,16 +4229,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
|
|
|
3769
4229
|
}
|
|
3770
4230
|
function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
3771
4231
|
const fieldErrors = { _errors: [] };
|
|
3772
|
-
const processError = (error52,
|
|
4232
|
+
const processError = (error52, path6 = []) => {
|
|
3773
4233
|
for (const issue2 of error52.issues) {
|
|
3774
4234
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
3775
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
4235
|
+
issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
|
|
3776
4236
|
} else if (issue2.code === "invalid_key") {
|
|
3777
|
-
processError({ issues: issue2.issues }, [...
|
|
4237
|
+
processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
|
|
3778
4238
|
} else if (issue2.code === "invalid_element") {
|
|
3779
|
-
processError({ issues: issue2.issues }, [...
|
|
4239
|
+
processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
|
|
3780
4240
|
} else {
|
|
3781
|
-
const fullpath = [...
|
|
4241
|
+
const fullpath = [...path6, ...issue2.path];
|
|
3782
4242
|
if (fullpath.length === 0) {
|
|
3783
4243
|
fieldErrors._errors.push(mapper(issue2));
|
|
3784
4244
|
} else {
|
|
@@ -3805,17 +4265,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
|
3805
4265
|
}
|
|
3806
4266
|
function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
3807
4267
|
const result = { errors: [] };
|
|
3808
|
-
const processError = (error52,
|
|
4268
|
+
const processError = (error52, path6 = []) => {
|
|
3809
4269
|
var _a3, _b;
|
|
3810
4270
|
for (const issue2 of error52.issues) {
|
|
3811
4271
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
3812
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
4272
|
+
issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
|
|
3813
4273
|
} else if (issue2.code === "invalid_key") {
|
|
3814
|
-
processError({ issues: issue2.issues }, [...
|
|
4274
|
+
processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
|
|
3815
4275
|
} else if (issue2.code === "invalid_element") {
|
|
3816
|
-
processError({ issues: issue2.issues }, [...
|
|
4276
|
+
processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
|
|
3817
4277
|
} else {
|
|
3818
|
-
const fullpath = [...
|
|
4278
|
+
const fullpath = [...path6, ...issue2.path];
|
|
3819
4279
|
if (fullpath.length === 0) {
|
|
3820
4280
|
result.errors.push(mapper(issue2));
|
|
3821
4281
|
continue;
|
|
@@ -3847,8 +4307,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
|
3847
4307
|
}
|
|
3848
4308
|
function toDotPath(_path) {
|
|
3849
4309
|
const segs = [];
|
|
3850
|
-
const
|
|
3851
|
-
for (const seg of
|
|
4310
|
+
const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
4311
|
+
for (const seg of path6) {
|
|
3852
4312
|
if (typeof seg === "number")
|
|
3853
4313
|
segs.push(`[${seg}]`);
|
|
3854
4314
|
else if (typeof seg === "symbol")
|
|
@@ -16540,13 +17000,13 @@ function resolveRef(ref, ctx) {
|
|
|
16540
17000
|
if (!ref.startsWith("#")) {
|
|
16541
17001
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
16542
17002
|
}
|
|
16543
|
-
const
|
|
16544
|
-
if (
|
|
17003
|
+
const path6 = ref.slice(1).split("/").filter(Boolean);
|
|
17004
|
+
if (path6.length === 0) {
|
|
16545
17005
|
return ctx.rootSchema;
|
|
16546
17006
|
}
|
|
16547
17007
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
16548
|
-
if (
|
|
16549
|
-
const key =
|
|
17008
|
+
if (path6[0] === defsKey) {
|
|
17009
|
+
const key = path6[1];
|
|
16550
17010
|
if (!key || !ctx.defs[key]) {
|
|
16551
17011
|
throw new Error(`Reference not found: ${ref}`);
|
|
16552
17012
|
}
|
|
@@ -16954,7 +17414,7 @@ function date4(params) {
|
|
|
16954
17414
|
// ../../../node_modules/zod/v4/classic/external.js
|
|
16955
17415
|
config(en_default());
|
|
16956
17416
|
|
|
16957
|
-
// ../../ir-core/dist/chunk-
|
|
17417
|
+
// ../../ir-core/dist/chunk-PPQWK62O.js
|
|
16958
17418
|
function gqlIdentityKey(typeName, fieldName) {
|
|
16959
17419
|
return `gql:${typeName}.${fieldName}`;
|
|
16960
17420
|
}
|
|
@@ -17240,6 +17700,42 @@ var metricSemantics = defineBoundarySemantics({
|
|
|
17240
17700
|
}
|
|
17241
17701
|
}
|
|
17242
17702
|
});
|
|
17703
|
+
var HOLE_SPLIT = /(\{[^}]*\})/;
|
|
17704
|
+
function isHole(piece) {
|
|
17705
|
+
return piece.startsWith("{") && piece.endsWith("}");
|
|
17706
|
+
}
|
|
17707
|
+
function parseBoundaryName(name) {
|
|
17708
|
+
const parts = name.split(HOLE_SPLIT).filter((piece) => piece !== "").map(
|
|
17709
|
+
(piece) => isHole(piece) ? { type: "hole", label: piece.slice(1, -1) } : { type: "text", text: piece }
|
|
17710
|
+
);
|
|
17711
|
+
const only = parts.length === 1 ? parts[0] : void 0;
|
|
17712
|
+
if (only !== void 0 && only.type === "hole") {
|
|
17713
|
+
return { type: "reference", path: only.label.split(".") };
|
|
17714
|
+
}
|
|
17715
|
+
if (parts.some((part) => part.type === "hole")) {
|
|
17716
|
+
return { type: "pattern", parts };
|
|
17717
|
+
}
|
|
17718
|
+
return { type: "literal", value: name };
|
|
17719
|
+
}
|
|
17720
|
+
function patternHole(label) {
|
|
17721
|
+
return `{${label}}`;
|
|
17722
|
+
}
|
|
17723
|
+
function namesNothing(name) {
|
|
17724
|
+
return parseBoundaryName(name).type === "reference";
|
|
17725
|
+
}
|
|
17726
|
+
function referenceOf(name) {
|
|
17727
|
+
if (name.type !== "reference") {
|
|
17728
|
+
return null;
|
|
17729
|
+
}
|
|
17730
|
+
const root = name.path[0];
|
|
17731
|
+
if (root === void 0 || name.path.some((part) => part === "")) {
|
|
17732
|
+
return null;
|
|
17733
|
+
}
|
|
17734
|
+
return { root, fields: name.path.slice(1) };
|
|
17735
|
+
}
|
|
17736
|
+
function referenceFromName(name) {
|
|
17737
|
+
return referenceOf(parseBoundaryName(name));
|
|
17738
|
+
}
|
|
17243
17739
|
var SCHEME_ORIGIN = /^[a-zA-Z][a-zA-Z\d+.-]*:\/\/[^/]*/;
|
|
17244
17740
|
var PROTOCOL_RELATIVE_ORIGIN = /^\/\/[^/]*/;
|
|
17245
17741
|
function pathAfterOrigin(text) {
|
|
@@ -17266,12 +17762,12 @@ var RestSemanticsSchema = external_exports.object({
|
|
|
17266
17762
|
declaredResponses: external_exports.array(external_exports.number()).optional()
|
|
17267
17763
|
});
|
|
17268
17764
|
var OPENING_HOLE = /^\{([^{}]+)\}(.*)$/;
|
|
17269
|
-
function
|
|
17270
|
-
const
|
|
17271
|
-
return
|
|
17765
|
+
function baseUrlReference(semantics) {
|
|
17766
|
+
const label = OPENING_HOLE.exec(semantics.path ?? "")?.[1];
|
|
17767
|
+
return label === void 0 ? null : referenceFromName(patternHole(label));
|
|
17272
17768
|
}
|
|
17273
|
-
function normalizePath(
|
|
17274
|
-
let normalized =
|
|
17769
|
+
function normalizePath(path6) {
|
|
17770
|
+
let normalized = path6.replace(/:([a-zA-Z_]\w*)/g, "{$1}");
|
|
17275
17771
|
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
17276
17772
|
normalized = normalized.slice(0, -1);
|
|
17277
17773
|
}
|
|
@@ -17281,8 +17777,8 @@ function normalizePath(path4) {
|
|
|
17281
17777
|
);
|
|
17282
17778
|
return normalized;
|
|
17283
17779
|
}
|
|
17284
|
-
function pathShape(
|
|
17285
|
-
return normalizePath(
|
|
17780
|
+
function pathShape(path6) {
|
|
17781
|
+
return normalizePath(path6).replace(/\{[^}]*\}/g, "{}");
|
|
17286
17782
|
}
|
|
17287
17783
|
function methodsAgree(a, b) {
|
|
17288
17784
|
if (a === null || b === null) {
|
|
@@ -17360,8 +17856,8 @@ var restSemantics = defineBoundarySemantics({
|
|
|
17360
17856
|
return null;
|
|
17361
17857
|
}
|
|
17362
17858
|
const method = semantics.method === null ? "ANY" : semantics.method.toUpperCase();
|
|
17363
|
-
const
|
|
17364
|
-
return `${method} ${
|
|
17859
|
+
const path6 = semantics.path === null ? "?" : normalizePath(semantics.path);
|
|
17860
|
+
return `${method} ${path6}`;
|
|
17365
17861
|
},
|
|
17366
17862
|
/**
|
|
17367
17863
|
* A route with both halves stated decides for itself: the method
|
|
@@ -17370,14 +17866,14 @@ var restSemantics = defineBoundarySemantics({
|
|
|
17370
17866
|
* half might still be the one that handles the request, and nothing
|
|
17371
17867
|
* here can settle that, so it abstains instead of refusing.
|
|
17372
17868
|
*/
|
|
17373
|
-
servesRequest(semantics, method,
|
|
17869
|
+
servesRequest(semantics, method, path6) {
|
|
17374
17870
|
if (semantics.method === null || semantics.path === null) {
|
|
17375
17871
|
return "unknown";
|
|
17376
17872
|
}
|
|
17377
17873
|
if (!methodsAgree(semantics.method, method.toUpperCase())) {
|
|
17378
17874
|
return "nomatch";
|
|
17379
17875
|
}
|
|
17380
|
-
return routePathAdmits(semantics.path,
|
|
17876
|
+
return routePathAdmits(semantics.path, path6) ? "match" : "nomatch";
|
|
17381
17877
|
},
|
|
17382
17878
|
/**
|
|
17383
17879
|
* A route the registration pattern admits is inside it. A route
|
|
@@ -17402,22 +17898,20 @@ var restSemantics = defineBoundarySemantics({
|
|
|
17402
17898
|
* route parameter, and `/orders/{id}` means every id rather than a
|
|
17403
17899
|
* variable somebody set.
|
|
17404
17900
|
*/
|
|
17405
|
-
groundName(semantics,
|
|
17406
|
-
|
|
17407
|
-
|
|
17408
|
-
}
|
|
17409
|
-
const opening = OPENING_HOLE.exec(semantics.path);
|
|
17410
|
-
if (opening === null) {
|
|
17901
|
+
groundName(semantics, deployment) {
|
|
17902
|
+
const reference = baseUrlReference(semantics);
|
|
17903
|
+
if (reference === null) {
|
|
17411
17904
|
return null;
|
|
17412
17905
|
}
|
|
17413
|
-
const
|
|
17414
|
-
const base = deployedAs(variableOf(label));
|
|
17906
|
+
const base = deployment.setTo(reference);
|
|
17415
17907
|
if (base === null) {
|
|
17416
17908
|
return null;
|
|
17417
17909
|
}
|
|
17910
|
+
const rest = OPENING_HOLE.exec(semantics.path ?? "")?.[2] ?? "";
|
|
17418
17911
|
const grounded = pathAfterOrigin(`${base}${rest}`);
|
|
17419
17912
|
return { ...semantics, path: grounded === "" ? "/" : grounded };
|
|
17420
17913
|
},
|
|
17914
|
+
nameReference: baseUrlReference,
|
|
17421
17915
|
ruleBoundary: {
|
|
17422
17916
|
// "METHOD /path": one leading token, then an absolute path.
|
|
17423
17917
|
claims(raw) {
|
|
@@ -17493,6 +17987,9 @@ var StorageSemanticsSchema = external_exports.object({
|
|
|
17493
17987
|
*/
|
|
17494
17988
|
accessPath: external_exports.string().nullable()
|
|
17495
17989
|
});
|
|
17990
|
+
function containerReference(semantics) {
|
|
17991
|
+
return semantics.container === null ? null : referenceFromName(semantics.container);
|
|
17992
|
+
}
|
|
17496
17993
|
var storageSemantics = defineBoundarySemantics({
|
|
17497
17994
|
name: "storage",
|
|
17498
17995
|
schema: StorageSemanticsSchema,
|
|
@@ -17509,7 +18006,21 @@ var storageSemantics = defineBoundarySemantics({
|
|
|
17509
18006
|
exchangesHttpResponses: false,
|
|
17510
18007
|
reportsUnpairedItself: false,
|
|
17511
18008
|
identityKey: () => null,
|
|
17512
|
-
displayLabel: storageLabel
|
|
18009
|
+
displayLabel: storageLabel,
|
|
18010
|
+
/**
|
|
18011
|
+
* A container the source states as a variable is whatever the
|
|
18012
|
+
* deployment sets that variable to. `{SUBSCRIBERS_TABLE}` in the
|
|
18013
|
+
* code and `prod-subscribers-v1` in the manifest are one table.
|
|
18014
|
+
*/
|
|
18015
|
+
groundName(semantics, deployment) {
|
|
18016
|
+
const reference = containerReference(semantics);
|
|
18017
|
+
if (reference === null) {
|
|
18018
|
+
return null;
|
|
18019
|
+
}
|
|
18020
|
+
const name = deployment.setTo(reference);
|
|
18021
|
+
return name === null ? null : { ...semantics, container: name };
|
|
18022
|
+
},
|
|
18023
|
+
nameReference: containerReference
|
|
17513
18024
|
}
|
|
17514
18025
|
});
|
|
17515
18026
|
function storageLabel(semantics) {
|
|
@@ -17524,6 +18035,9 @@ var UnitInvocationSemanticsSchema = DeployableUnitSchema.extend({
|
|
|
17524
18035
|
name: external_exports.literal("unit-invocation"),
|
|
17525
18036
|
instanceName: DeployableUnitSchema.shape.instanceName.nullable()
|
|
17526
18037
|
});
|
|
18038
|
+
function calleeReference(semantics) {
|
|
18039
|
+
return semantics.instanceName === null ? null : referenceFromName(semantics.instanceName);
|
|
18040
|
+
}
|
|
17527
18041
|
var unitInvocationSemantics = defineBoundarySemantics({
|
|
17528
18042
|
name: "unit-invocation",
|
|
17529
18043
|
schema: UnitInvocationSemanticsSchema,
|
|
@@ -17546,8 +18060,13 @@ var unitInvocationSemantics = defineBoundarySemantics({
|
|
|
17546
18060
|
* being reported as a problem here.
|
|
17547
18061
|
*/
|
|
17548
18062
|
reportsUnpairedItself: false,
|
|
18063
|
+
/**
|
|
18064
|
+
* A callee the source states only as a variable is not a name for
|
|
18065
|
+
* anything until the deployment fills it in, so it keys as nothing
|
|
18066
|
+
* rather than as a bucket that would agree with itself alone.
|
|
18067
|
+
*/
|
|
17549
18068
|
identityKey(semantics) {
|
|
17550
|
-
if (semantics.instanceName === null) {
|
|
18069
|
+
if (semantics.instanceName === null || namesNothing(semantics.instanceName)) {
|
|
17551
18070
|
return null;
|
|
17552
18071
|
}
|
|
17553
18072
|
return unitIdentityKey(
|
|
@@ -17563,7 +18082,23 @@ var unitInvocationSemantics = defineBoundarySemantics({
|
|
|
17563
18082
|
semantics.deploymentTarget,
|
|
17564
18083
|
semantics.instanceName
|
|
17565
18084
|
);
|
|
17566
|
-
}
|
|
18085
|
+
},
|
|
18086
|
+
/**
|
|
18087
|
+
* A call that reads its callee's name out of the environment
|
|
18088
|
+
* reaches whichever resource the template wires that variable to.
|
|
18089
|
+
* The logical id is the answer rather than the deployed name,
|
|
18090
|
+
* because the invoked unit's own summary is keyed by the logical
|
|
18091
|
+
* id and neither side ever writes the other's string.
|
|
18092
|
+
*/
|
|
18093
|
+
groundName(semantics, deployment) {
|
|
18094
|
+
const reference = calleeReference(semantics);
|
|
18095
|
+
if (reference === null) {
|
|
18096
|
+
return null;
|
|
18097
|
+
}
|
|
18098
|
+
const logicalId = deployment.pointsAt(reference);
|
|
18099
|
+
return logicalId === null ? null : { ...semantics, instanceName: logicalId };
|
|
18100
|
+
},
|
|
18101
|
+
nameReference: calleeReference
|
|
17567
18102
|
}
|
|
17568
18103
|
});
|
|
17569
18104
|
var SemanticsSchema = external_exports.discriminatedUnion("name", [
|
|
@@ -17975,71 +18510,10 @@ function returnTypeName(node) {
|
|
|
17975
18510
|
}
|
|
17976
18511
|
return annotation.type === "subscript" ? field(annotation, "value")?.text ?? null : annotation.text;
|
|
17977
18512
|
}
|
|
17978
|
-
function
|
|
17979
|
-
|
|
17980
|
-
if (child === null) {
|
|
17981
|
-
continue;
|
|
17982
|
-
}
|
|
17983
|
-
if (child.type === "call") {
|
|
17984
|
-
found.push(child);
|
|
17985
|
-
}
|
|
17986
|
-
callsIn(child, found);
|
|
17987
|
-
}
|
|
17988
|
-
return found;
|
|
18513
|
+
function storageCallees(calls, filePath, couldMatch) {
|
|
18514
|
+
return chainsIn(calls).filter((chain) => couldMatch.has(methodName(chain.root))).map((chain) => field(chain.root, "function")).filter((callee) => callee !== null).map((callee) => nodeId(filePath, callee));
|
|
17989
18515
|
}
|
|
17990
18516
|
function storageEffects(calls, options) {
|
|
17991
|
-
const seen = /* @__PURE__ */ new Set();
|
|
17992
|
-
const asked = new Set(
|
|
17993
|
-
options.facts.facts("wanted").map((row) => String(row[0]))
|
|
17994
|
-
);
|
|
17995
|
-
const gather = (inCalls, filePath) => {
|
|
17996
|
-
const found = [...directStorageEffects(inCalls, { ...options, filePath })];
|
|
17997
|
-
resolveCalls(
|
|
17998
|
-
options.facts,
|
|
17999
|
-
inCalls.filter((call) => options.leadsToStorage.has(methodName(call))).map((call) => field(call, "function")).filter((callee) => callee !== null).map((callee) => calleeKeyOf(filePath, callee)).filter((key) => {
|
|
18000
|
-
const fresh = !asked.has(key);
|
|
18001
|
-
asked.add(key);
|
|
18002
|
-
return fresh;
|
|
18003
|
-
})
|
|
18004
|
-
);
|
|
18005
|
-
for (const call of inCalls) {
|
|
18006
|
-
const callee = field(call, "function");
|
|
18007
|
-
if (callee === null || !options.leadsToStorage.has(methodName(call))) {
|
|
18008
|
-
continue;
|
|
18009
|
-
}
|
|
18010
|
-
const key = resolvedOnce(options, calleeKeyOf(filePath, callee));
|
|
18011
|
-
const body = key === void 0 ? void 0 : options.definitionAt(key);
|
|
18012
|
-
if (key === void 0 || body === void 0 || seen.has(key)) {
|
|
18013
|
-
continue;
|
|
18014
|
-
}
|
|
18015
|
-
seen.add(key);
|
|
18016
|
-
const name = field(body, "name")?.text;
|
|
18017
|
-
const origin = {
|
|
18018
|
-
file: fileOf(key),
|
|
18019
|
-
line: body.startPosition.row + 1,
|
|
18020
|
-
...name === void 0 ? {} : { function: name }
|
|
18021
|
-
};
|
|
18022
|
-
found.push(
|
|
18023
|
-
...gather(callsIn(body), fileOf(key)).map(
|
|
18024
|
-
(effect) => effect.type === "interaction" && effect.origin === void 0 ? { ...effect, origin } : effect
|
|
18025
|
-
)
|
|
18026
|
-
);
|
|
18027
|
-
}
|
|
18028
|
-
return found;
|
|
18029
|
-
};
|
|
18030
|
-
return gather(calls, options.filePath);
|
|
18031
|
-
}
|
|
18032
|
-
function calleeKeyOf(filePath, callee) {
|
|
18033
|
-
return callee.type === "identifier" ? `${filePath}#${callee.text}` : nodeId(filePath, callee);
|
|
18034
|
-
}
|
|
18035
|
-
function resolvedOnce(options, calleeKey) {
|
|
18036
|
-
const resolved = options.facts.facts("wantedResolves").filter((row) => String(row[0]) === calleeKey).map((row) => String(row[1]));
|
|
18037
|
-
return resolved.length === 1 ? resolved[0] : void 0;
|
|
18038
|
-
}
|
|
18039
|
-
function storageCallees(calls, filePath, couldMatch) {
|
|
18040
|
-
return chainsIn(calls).filter((chain) => couldMatch.has(methodName(chain.root))).map((chain) => field(chain.root, "function")).filter((callee) => callee !== null).map((callee) => nodeId(filePath, callee));
|
|
18041
|
-
}
|
|
18042
|
-
function directStorageEffects(calls, options) {
|
|
18043
18517
|
if (options.patterns.length === 0) {
|
|
18044
18518
|
return [];
|
|
18045
18519
|
}
|
|
@@ -18097,7 +18571,7 @@ function directStorageEffects(calls, options) {
|
|
|
18097
18571
|
// src/discovery.ts
|
|
18098
18572
|
function decoratedStatements(root, module) {
|
|
18099
18573
|
const found = [];
|
|
18100
|
-
|
|
18574
|
+
walkDescendants2(
|
|
18101
18575
|
root,
|
|
18102
18576
|
{ scope: module.moduleScope, enclosingFunction: null },
|
|
18103
18577
|
{
|
|
@@ -18136,21 +18610,21 @@ function builtSubjects(decorated, options) {
|
|
|
18136
18610
|
}
|
|
18137
18611
|
const factsPath = options.absoluteFile ?? options.filePath;
|
|
18138
18612
|
const asked = /* @__PURE__ */ new Set();
|
|
18139
|
-
for (const { enclosingFunction, decorators } of decorated) {
|
|
18613
|
+
for (const { enclosingFunction: enclosingFunction2, decorators } of decorated) {
|
|
18140
18614
|
for (const { node, direct } of decorators) {
|
|
18141
18615
|
if (direct.module !== null && acceptedByAnyPattern(direct.module, options)) {
|
|
18142
18616
|
continue;
|
|
18143
18617
|
}
|
|
18144
18618
|
const written = decoratorReceiver(node);
|
|
18145
18619
|
if (written !== null) {
|
|
18146
|
-
asked.add(readKey(factsPath, written.object,
|
|
18620
|
+
asked.add(readKey(factsPath, written.object, enclosingFunction2));
|
|
18147
18621
|
}
|
|
18148
18622
|
}
|
|
18149
18623
|
}
|
|
18150
18624
|
return subjectConstructions(facts, [...asked]);
|
|
18151
18625
|
}
|
|
18152
18626
|
function decoratedUnits(decoratedStatement, module, options, subjects) {
|
|
18153
|
-
const { stmt, scope, enclosingFunction, decorators } = decoratedStatement;
|
|
18627
|
+
const { stmt, scope, enclosingFunction: enclosingFunction2, decorators } = decoratedStatement;
|
|
18154
18628
|
const units = [];
|
|
18155
18629
|
const { definition } = stripDecorators(stmt);
|
|
18156
18630
|
for (const { node: decoratorNode, direct } of decorators) {
|
|
@@ -18166,7 +18640,7 @@ function decoratedUnits(decoratedStatement, module, options, subjects) {
|
|
|
18166
18640
|
) ?? null
|
|
18167
18641
|
) ?? builtSubjectClassification(
|
|
18168
18642
|
decoratorNode,
|
|
18169
|
-
|
|
18643
|
+
enclosingFunction2,
|
|
18170
18644
|
subjects,
|
|
18171
18645
|
options
|
|
18172
18646
|
)
|
|
@@ -18202,14 +18676,14 @@ function acceptedByAnyPattern(module, options) {
|
|
|
18202
18676
|
(pack) => pack.discovery.some((pattern) => pattern.importModule.includes(module))
|
|
18203
18677
|
);
|
|
18204
18678
|
}
|
|
18205
|
-
function builtSubjectClassification(decoratorNode,
|
|
18679
|
+
function builtSubjectClassification(decoratorNode, enclosingFunction2, subjects, options) {
|
|
18206
18680
|
const written = decoratorReceiver(decoratorNode);
|
|
18207
18681
|
if (written === null) {
|
|
18208
18682
|
return null;
|
|
18209
18683
|
}
|
|
18210
18684
|
const factsPath = options.absoluteFile ?? options.filePath;
|
|
18211
18685
|
const built = subjects.get(
|
|
18212
|
-
readKey(factsPath, written.object,
|
|
18686
|
+
readKey(factsPath, written.object, enclosingFunction2)
|
|
18213
18687
|
);
|
|
18214
18688
|
const origin = built?.origins.find(
|
|
18215
18689
|
(candidate) => acceptedByAnyPattern(candidate.module, options)
|
|
@@ -18268,7 +18742,7 @@ function unitsFor(pattern, pack, decoratorModule, classification, definition, mo
|
|
|
18268
18742
|
);
|
|
18269
18743
|
}
|
|
18270
18744
|
};
|
|
18271
|
-
return
|
|
18745
|
+
return dispatchByType2(table, pattern);
|
|
18272
18746
|
}
|
|
18273
18747
|
function readPathArgument(classification) {
|
|
18274
18748
|
const first = classification.args[0];
|
|
@@ -18282,9 +18756,9 @@ function readPathArgument(classification) {
|
|
|
18282
18756
|
}
|
|
18283
18757
|
var NO_PATH_PARAMS = /* @__PURE__ */ new Set();
|
|
18284
18758
|
function templateReader(template) {
|
|
18285
|
-
return (
|
|
18759
|
+
return (path6) => {
|
|
18286
18760
|
const names = [];
|
|
18287
|
-
const canonical =
|
|
18761
|
+
const canonical = path6.replace(template, (_match, name) => {
|
|
18288
18762
|
names.push(name);
|
|
18289
18763
|
return `{${name}}`;
|
|
18290
18764
|
});
|
|
@@ -18297,10 +18771,10 @@ var PATH_TEMPLATE_READERS = {
|
|
|
18297
18771
|
/<(?:[A-Za-z_]\w*(?:\(.*?\))?:)?([A-Za-z_]\w*)>/g
|
|
18298
18772
|
)
|
|
18299
18773
|
};
|
|
18300
|
-
function readPathTemplate(pattern,
|
|
18774
|
+
function readPathTemplate(pattern, path6, range) {
|
|
18301
18775
|
const syntax = pattern.pathParamSyntax;
|
|
18302
18776
|
if (syntax === void 0) {
|
|
18303
|
-
return writtenReading({ path:
|
|
18777
|
+
return writtenReading({ path: path6, paramNames: NO_PATH_PARAMS }, range);
|
|
18304
18778
|
}
|
|
18305
18779
|
const reader = PATH_TEMPLATE_READERS[syntax];
|
|
18306
18780
|
if (reader === void 0) {
|
|
@@ -18309,7 +18783,7 @@ function readPathTemplate(pattern, path4, range) {
|
|
|
18309
18783
|
range
|
|
18310
18784
|
);
|
|
18311
18785
|
}
|
|
18312
|
-
return writtenReading(reader(
|
|
18786
|
+
return writtenReading(reader(path6), range);
|
|
18313
18787
|
}
|
|
18314
18788
|
function classRouteUnits(pattern, pack, classification, classNode, module, options) {
|
|
18315
18789
|
const pathArgument = readPathArgument(classification);
|
|
@@ -18379,7 +18853,7 @@ function readRoutePath(pattern, pathArgument, classification, module, options) {
|
|
|
18379
18853
|
);
|
|
18380
18854
|
return andThenReading(
|
|
18381
18855
|
composed,
|
|
18382
|
-
(
|
|
18856
|
+
(path6, range) => readPathTemplate(pattern, path6, range)
|
|
18383
18857
|
);
|
|
18384
18858
|
}
|
|
18385
18859
|
function readRoutePaths(pattern, pathArgument, classification, module, options) {
|
|
@@ -18408,7 +18882,7 @@ function readRoutePaths(pattern, pathArgument, classification, module, options)
|
|
|
18408
18882
|
pathArgument,
|
|
18409
18883
|
(literal2, range) => writtenReading(servedSpelling(pattern, prefix + literal2), range)
|
|
18410
18884
|
),
|
|
18411
|
-
(
|
|
18885
|
+
(path6, range) => readPathTemplate(pattern, path6, range)
|
|
18412
18886
|
),
|
|
18413
18887
|
mount: { siblings: resolution.values.length, prefix }
|
|
18414
18888
|
}));
|
|
@@ -18450,11 +18924,11 @@ function composeRoutePath(pattern, classification, module, options, literal2, ra
|
|
|
18450
18924
|
);
|
|
18451
18925
|
}
|
|
18452
18926
|
var REPEATED_SLASH_READERS = {
|
|
18453
|
-
kept: (
|
|
18454
|
-
merged: (
|
|
18927
|
+
kept: (path6) => path6,
|
|
18928
|
+
merged: (path6) => path6.replace(/\/{2,}/g, "/")
|
|
18455
18929
|
};
|
|
18456
|
-
function servedSpelling(pattern,
|
|
18457
|
-
return REPEATED_SLASH_READERS[pattern.pathRepeatedSlashes ?? "kept"](
|
|
18930
|
+
function servedSpelling(pattern, path6) {
|
|
18931
|
+
return REPEATED_SLASH_READERS[pattern.pathRepeatedSlashes ?? "kept"](path6);
|
|
18458
18932
|
}
|
|
18459
18933
|
function readRouterPrefix(pattern, classification, module, options, range) {
|
|
18460
18934
|
const resolution = routerResolutionOf(
|
|
@@ -18566,8 +19040,8 @@ function branchesFromReturns(readsReturnedStatus, definitionNode, effects, respo
|
|
|
18566
19040
|
"This return gives a status this reading cannot resolve to a number, so this outcome claims none",
|
|
18567
19041
|
range
|
|
18568
19042
|
);
|
|
18569
|
-
for (const
|
|
18570
|
-
const conditions =
|
|
19043
|
+
for (const path6 of paths) {
|
|
19044
|
+
const conditions = path6.map((condition) => ({
|
|
18571
19045
|
sourceText: condition.sourceText,
|
|
18572
19046
|
structured: condition.expression === null ? null : predicateOf(condition.expression),
|
|
18573
19047
|
polarity: condition.polarity,
|
|
@@ -18596,7 +19070,7 @@ function branchesFromReturns(readsReturnedStatus, definitionNode, effects, respo
|
|
|
18596
19070
|
},
|
|
18597
19071
|
effects: effectsReaching(effects, conditions),
|
|
18598
19072
|
location: range,
|
|
18599
|
-
isDefault:
|
|
19073
|
+
isDefault: path6.length === 0
|
|
18600
19074
|
});
|
|
18601
19075
|
}
|
|
18602
19076
|
}
|
|
@@ -18807,17 +19281,7 @@ function buildRouteUnit(options) {
|
|
|
18807
19281
|
() => readReturnAnnotation(definitionNode, enclosingScope, ctx)
|
|
18808
19282
|
);
|
|
18809
19283
|
const effects = invocationEffects(definitionNode);
|
|
18810
|
-
const
|
|
18811
|
-
...storageEffects(bodyCalls(definitionNode), {
|
|
18812
|
-
...storageLookup,
|
|
18813
|
-
filePath: storageLookup.factsPath
|
|
18814
|
-
}),
|
|
18815
|
-
...rawSqlEffects(bodyCalls(definitionNode), {
|
|
18816
|
-
facts: storageLookup.facts,
|
|
18817
|
-
filePath: storageLookup.factsPath,
|
|
18818
|
-
patterns: storageLookup.rawSql ?? []
|
|
18819
|
-
})
|
|
18820
|
-
];
|
|
19284
|
+
const extra = recognizedBodyEffects(definitionNode, module, storageLookup);
|
|
18821
19285
|
const perReturn = branchesFromReturns(
|
|
18822
19286
|
readsReturnedStatus,
|
|
18823
19287
|
definitionNode,
|
|
@@ -18826,9 +19290,9 @@ function buildRouteUnit(options) {
|
|
|
18826
19290
|
statusCode
|
|
18827
19291
|
);
|
|
18828
19292
|
const branches = (perReturn ?? []).map(
|
|
18829
|
-
(branch) =>
|
|
19293
|
+
(branch) => extra.length === 0 ? branch : { ...branch, extraEffects: extra }
|
|
18830
19294
|
);
|
|
18831
|
-
if (perReturn === null && (responseShape.reading.kind !== "absent" || statusCode.reading.kind !== "absent" ||
|
|
19295
|
+
if (perReturn === null && (responseShape.reading.kind !== "absent" || statusCode.reading.kind !== "absent" || extra.length > 0 || effects.length > 0)) {
|
|
18832
19296
|
branches.push({
|
|
18833
19297
|
conditions: [],
|
|
18834
19298
|
terminal: {
|
|
@@ -18848,7 +19312,7 @@ function buildRouteUnit(options) {
|
|
|
18848
19312
|
statusCodeReading: statusCode,
|
|
18849
19313
|
bodyShapeReading: { reading: responseShape.reading },
|
|
18850
19314
|
effects,
|
|
18851
|
-
...
|
|
19315
|
+
...extra.length === 0 ? {} : { extraEffects: extra },
|
|
18852
19316
|
location: rangeOf(definitionNode),
|
|
18853
19317
|
isDefault: true
|
|
18854
19318
|
});
|
|
@@ -18896,6 +19360,20 @@ function unreadRoleReadings(parameters, range) {
|
|
|
18896
19360
|
)
|
|
18897
19361
|
];
|
|
18898
19362
|
}
|
|
19363
|
+
function recognizedBodyEffects(definitionNode, module, storageLookup) {
|
|
19364
|
+
const storage = storageLookup === void 0 ? [] : [
|
|
19365
|
+
...storageEffects(bodyCalls(definitionNode), {
|
|
19366
|
+
...storageLookup,
|
|
19367
|
+
filePath: storageLookup.factsPath
|
|
19368
|
+
}),
|
|
19369
|
+
...rawSqlEffects(bodyCalls(definitionNode), {
|
|
19370
|
+
facts: storageLookup.facts,
|
|
19371
|
+
filePath: storageLookup.factsPath,
|
|
19372
|
+
patterns: storageLookup.rawSql ?? []
|
|
19373
|
+
})
|
|
19374
|
+
];
|
|
19375
|
+
return [...envReadEffects(definitionNode, module), ...storage];
|
|
19376
|
+
}
|
|
18899
19377
|
function bodyContentOf(bodyNode) {
|
|
18900
19378
|
for (const stmt of bodyStatements(bodyNode)) {
|
|
18901
19379
|
if (stmt.type === "pass_statement") {
|
|
@@ -19031,6 +19509,11 @@ function parameterNameAndType(param) {
|
|
|
19031
19509
|
return null;
|
|
19032
19510
|
}
|
|
19033
19511
|
|
|
19512
|
+
// src/facts/why.ts
|
|
19513
|
+
import fs4 from "fs";
|
|
19514
|
+
import path4 from "path";
|
|
19515
|
+
import { Database as Database4 } from "@suss/datalog";
|
|
19516
|
+
|
|
19034
19517
|
// src/moduleResolver.ts
|
|
19035
19518
|
import fs from "fs";
|
|
19036
19519
|
import path from "path";
|
|
@@ -19123,43 +19606,38 @@ function importedModuleText(module, relativeLevel) {
|
|
|
19123
19606
|
return relativeLevel > 0 ? `${".".repeat(relativeLevel)}${module}` : module;
|
|
19124
19607
|
}
|
|
19125
19608
|
function emitModuleImportFacts(db, filePath, module, resolverOptions) {
|
|
19126
|
-
const
|
|
19127
|
-
|
|
19128
|
-
|
|
19129
|
-
|
|
19130
|
-
|
|
19131
|
-
|
|
19132
|
-
|
|
19133
|
-
|
|
19134
|
-
|
|
19135
|
-
|
|
19136
|
-
|
|
19137
|
-
|
|
19138
|
-
|
|
19139
|
-
|
|
19140
|
-
|
|
19141
|
-
|
|
19142
|
-
|
|
19143
|
-
|
|
19144
|
-
|
|
19145
|
-
resolution.status === "resolved" ? "resolved" : resolution.reason
|
|
19146
|
-
]);
|
|
19147
|
-
const importedName = binding.kind === "import" ? binding.localName : binding.importedName;
|
|
19148
|
-
db.add("pyImportedName", [
|
|
19149
|
-
`${filePath}#${localName}`,
|
|
19150
|
-
moduleText,
|
|
19151
|
-
importedName
|
|
19152
|
-
]);
|
|
19153
|
-
if (resolution.status === "resolved") {
|
|
19154
|
-
db.add("pyImportResolved", [filePath, moduleText, resolution.file]);
|
|
19155
|
-
db.add("imports", [
|
|
19156
|
-
`${filePath}#${localName}`,
|
|
19157
|
-
resolution.file,
|
|
19158
|
-
importedName
|
|
19609
|
+
for (const scope of module.scopeFor.values()) {
|
|
19610
|
+
for (const [localName, binding] of scope.bindings) {
|
|
19611
|
+
if (binding.kind !== "import" && binding.kind !== "importFrom") {
|
|
19612
|
+
continue;
|
|
19613
|
+
}
|
|
19614
|
+
const nameKey = `${filePath}#${localName}`;
|
|
19615
|
+
const moduleText = importedModuleText(
|
|
19616
|
+
binding.module,
|
|
19617
|
+
binding.relativeLevel
|
|
19618
|
+
);
|
|
19619
|
+
const resolution = resolveModule(
|
|
19620
|
+
filePath,
|
|
19621
|
+
{ module: binding.module, relativeLevel: binding.relativeLevel },
|
|
19622
|
+
resolverOptions
|
|
19623
|
+
);
|
|
19624
|
+
db.add("pyImport", [
|
|
19625
|
+
filePath,
|
|
19626
|
+
moduleText,
|
|
19627
|
+
resolution.status === "resolved" ? "resolved" : resolution.reason
|
|
19159
19628
|
]);
|
|
19160
|
-
|
|
19629
|
+
const importedName = binding.kind === "import" ? binding.localName : binding.importedName;
|
|
19630
|
+
db.add("pyImportedName", [nameKey, moduleText, importedName]);
|
|
19631
|
+
if (scope.kind === "module") {
|
|
19632
|
+
db.add("exportsAs", [filePath, localName, nameKey]);
|
|
19633
|
+
}
|
|
19634
|
+
if (resolution.status === "resolved") {
|
|
19635
|
+
db.add("pyImportResolved", [filePath, moduleText, resolution.file]);
|
|
19636
|
+
db.add("imports", [nameKey, resolution.file, importedName]);
|
|
19637
|
+
continue;
|
|
19638
|
+
}
|
|
19639
|
+
db.add("imports", [nameKey, moduleText, importedName]);
|
|
19161
19640
|
}
|
|
19162
|
-
db.add("imports", [`${filePath}#${localName}`, moduleText, importedName]);
|
|
19163
19641
|
}
|
|
19164
19642
|
for (const openModule of module.openImports) {
|
|
19165
19643
|
db.add("pyOpenImport", [filePath, openModule]);
|
|
@@ -19178,6 +19656,7 @@ var GRAMMAR_PATH = path2.join(
|
|
|
19178
19656
|
"tree-sitter-python.wasm"
|
|
19179
19657
|
);
|
|
19180
19658
|
var languagePromise = null;
|
|
19659
|
+
var loadedLanguage = null;
|
|
19181
19660
|
function loadLanguage() {
|
|
19182
19661
|
languagePromise ??= (async () => {
|
|
19183
19662
|
if (!fs2.existsSync(GRAMMAR_PATH)) {
|
|
@@ -19186,12 +19665,28 @@ function loadLanguage() {
|
|
|
19186
19665
|
);
|
|
19187
19666
|
}
|
|
19188
19667
|
await Parser.init();
|
|
19189
|
-
|
|
19668
|
+
const language = await Language.load(GRAMMAR_PATH);
|
|
19669
|
+
loadedLanguage = language;
|
|
19670
|
+
return language;
|
|
19190
19671
|
})();
|
|
19191
19672
|
return languagePromise;
|
|
19192
19673
|
}
|
|
19193
19674
|
async function parsePython(source) {
|
|
19194
19675
|
const language = await loadLanguage();
|
|
19676
|
+
return parseWith(language, source);
|
|
19677
|
+
}
|
|
19678
|
+
async function preloadPythonGrammar() {
|
|
19679
|
+
await loadLanguage();
|
|
19680
|
+
}
|
|
19681
|
+
function parsePythonSync(source) {
|
|
19682
|
+
if (loadedLanguage === null) {
|
|
19683
|
+
throw new Error(
|
|
19684
|
+
"the Python grammar has not been loaded yet; call preloadPythonGrammar first"
|
|
19685
|
+
);
|
|
19686
|
+
}
|
|
19687
|
+
return parseWith(loadedLanguage, source);
|
|
19688
|
+
}
|
|
19689
|
+
function parseWith(language, source) {
|
|
19195
19690
|
const parser = new Parser();
|
|
19196
19691
|
parser.setLanguage(language);
|
|
19197
19692
|
try {
|
|
@@ -19208,8 +19703,705 @@ async function parsePython(source) {
|
|
|
19208
19703
|
// src/project.ts
|
|
19209
19704
|
import fs3 from "fs";
|
|
19210
19705
|
import path3 from "path";
|
|
19211
|
-
import {
|
|
19212
|
-
|
|
19706
|
+
import {
|
|
19707
|
+
disambiguateSummaryIds,
|
|
19708
|
+
linkCallsToSummaries,
|
|
19709
|
+
placeArgTargets as placeArgTargets2,
|
|
19710
|
+
placeCalleeParameters as placeCalleeParameters2,
|
|
19711
|
+
placeCalls as placeCalls2,
|
|
19712
|
+
recordParameterGaps as recordParameterGaps2,
|
|
19713
|
+
summaryIdFromParts,
|
|
19714
|
+
unfollowedCallGap as unfollowedCallGap2
|
|
19715
|
+
} from "@suss/behavioral-ir";
|
|
19716
|
+
import { Database as Database3 } from "@suss/datalog";
|
|
19717
|
+
import {
|
|
19718
|
+
assembleSummary as assembleSummary2,
|
|
19719
|
+
createCacheLayer,
|
|
19720
|
+
createTimer,
|
|
19721
|
+
moduleInitStructure,
|
|
19722
|
+
noopTimer,
|
|
19723
|
+
runDigest,
|
|
19724
|
+
stampModuleImports
|
|
19725
|
+
} from "@suss/extractor";
|
|
19726
|
+
|
|
19727
|
+
// src/diagnostics.ts
|
|
19728
|
+
import {
|
|
19729
|
+
buildUngatedExtractionReport,
|
|
19730
|
+
createPackTallies,
|
|
19731
|
+
tallyUnit
|
|
19732
|
+
} from "@suss/extractor";
|
|
19733
|
+
function buildPythonExtractionReport(args) {
|
|
19734
|
+
return buildUngatedExtractionReport({
|
|
19735
|
+
packs: args.packs.map((pack) => ({
|
|
19736
|
+
name: pack.name,
|
|
19737
|
+
version: pack.version ?? null,
|
|
19738
|
+
discovers: pack.discovery.length > 0
|
|
19739
|
+
})),
|
|
19740
|
+
tallies: args.tallies,
|
|
19741
|
+
filesWalked: args.filesWalked,
|
|
19742
|
+
summaries: args.summaries
|
|
19743
|
+
});
|
|
19744
|
+
}
|
|
19745
|
+
|
|
19746
|
+
// src/reach/closure.ts
|
|
19747
|
+
import {
|
|
19748
|
+
functionCallBinding,
|
|
19749
|
+
placeArgTargets,
|
|
19750
|
+
placeCalleeParameters,
|
|
19751
|
+
placeCalls,
|
|
19752
|
+
recordParameterGaps,
|
|
19753
|
+
TargetPlacements,
|
|
19754
|
+
unfollowedCallGap,
|
|
19755
|
+
worthRecording
|
|
19756
|
+
} from "@suss/behavioral-ir";
|
|
19757
|
+
import { Database as Database2, evaluate as evaluate3, lit as lit2, rule as rule2, variable as v2 } from "@suss/datalog";
|
|
19758
|
+
import {
|
|
19759
|
+
assembleSummary,
|
|
19760
|
+
SKIP_CHILDREN as SKIP_CHILDREN2,
|
|
19761
|
+
walkDescendants as walkDescendants3
|
|
19762
|
+
} from "@suss/extractor";
|
|
19763
|
+
|
|
19764
|
+
// src/reach/resolveCallee.ts
|
|
19765
|
+
var stop = (reason) => ({ kind: "stop", reason });
|
|
19766
|
+
var MODULE_STOP = {
|
|
19767
|
+
ambiguous: "multipleSources",
|
|
19768
|
+
outsideRoots: "outsideRun"
|
|
19769
|
+
};
|
|
19770
|
+
function resolveCallee2(call, site, ctx) {
|
|
19771
|
+
const callee = field(call, "function");
|
|
19772
|
+
if (callee === null) {
|
|
19773
|
+
return { kind: "stopped", reason: "noDeclaration" };
|
|
19774
|
+
}
|
|
19775
|
+
return asCallee(resolveExpression(callee, site, ctx), ctx);
|
|
19776
|
+
}
|
|
19777
|
+
function resolveNamedFunctionArgument(name, site, ctx) {
|
|
19778
|
+
if (site.rebound.has(name)) {
|
|
19779
|
+
return null;
|
|
19780
|
+
}
|
|
19781
|
+
const binding = resolveName(site.scope, name);
|
|
19782
|
+
if (binding !== null && binding.kind === "assignment") {
|
|
19783
|
+
return null;
|
|
19784
|
+
}
|
|
19785
|
+
const resolved = binding === null ? throughOpenImports(site.file, name, ctx, /* @__PURE__ */ new Set()) : resolveBinding(binding, site.file, site.scope, ctx, /* @__PURE__ */ new Set());
|
|
19786
|
+
return resolved.kind === "function" ? resolved.target : null;
|
|
19787
|
+
}
|
|
19788
|
+
function asCallee(resolved, ctx) {
|
|
19789
|
+
if (resolved.kind === "function") {
|
|
19790
|
+
return { kind: "followed", target: resolved.target };
|
|
19791
|
+
}
|
|
19792
|
+
if (resolved.kind === "class") {
|
|
19793
|
+
return asCallee(
|
|
19794
|
+
memberOfClass(resolved.file, resolved.node, "__init__", ctx),
|
|
19795
|
+
ctx
|
|
19796
|
+
);
|
|
19797
|
+
}
|
|
19798
|
+
if (resolved.kind === "stop") {
|
|
19799
|
+
return { kind: "stopped", reason: resolved.reason };
|
|
19800
|
+
}
|
|
19801
|
+
if (resolved.kind === "package") {
|
|
19802
|
+
return { kind: "stopped", reason: "outsideRun" };
|
|
19803
|
+
}
|
|
19804
|
+
return { kind: "stopped", reason: "noDeclaration" };
|
|
19805
|
+
}
|
|
19806
|
+
var EXPRESSION_RESOLVERS = {
|
|
19807
|
+
identifier: (node, site, ctx) => resolveIdentifier(node.text, site, ctx),
|
|
19808
|
+
attribute: (node, site, ctx) => {
|
|
19809
|
+
const object2 = field(node, "object");
|
|
19810
|
+
const attribute = field(node, "attribute");
|
|
19811
|
+
if (object2 === null || attribute === null) {
|
|
19812
|
+
return stop("noDeclaration");
|
|
19813
|
+
}
|
|
19814
|
+
return memberOf(resolveExpression(object2, site, ctx), attribute.text, ctx);
|
|
19815
|
+
},
|
|
19816
|
+
// `Service().run()` runs a method on whatever the constructor built.
|
|
19817
|
+
call: (node, site, ctx) => {
|
|
19818
|
+
const callee = field(node, "function");
|
|
19819
|
+
if (callee === null) {
|
|
19820
|
+
return stop("noDeclaration");
|
|
19821
|
+
}
|
|
19822
|
+
const built = resolveExpression(callee, site, ctx);
|
|
19823
|
+
return built.kind === "class" ? built : stop("noDeclaration");
|
|
19824
|
+
},
|
|
19825
|
+
parenthesized_expression: (node, site, ctx) => {
|
|
19826
|
+
const inner = node.namedChildren[0];
|
|
19827
|
+
return inner === null || inner === void 0 ? stop("noDeclaration") : resolveExpression(inner, site, ctx);
|
|
19828
|
+
}
|
|
19829
|
+
};
|
|
19830
|
+
function resolveExpression(node, site, ctx) {
|
|
19831
|
+
const resolver = EXPRESSION_RESOLVERS[node.type];
|
|
19832
|
+
return resolver === void 0 ? stop("noDeclaration") : resolver(node, site, ctx);
|
|
19833
|
+
}
|
|
19834
|
+
function resolveIdentifier(name, site, ctx) {
|
|
19835
|
+
if (site.rebound.has(name)) {
|
|
19836
|
+
return stop("unsettledValue");
|
|
19837
|
+
}
|
|
19838
|
+
if (isReceiver(name, site.scope)) {
|
|
19839
|
+
return receiverClass(site);
|
|
19840
|
+
}
|
|
19841
|
+
const binding = resolveName(site.scope, name);
|
|
19842
|
+
if (binding === null) {
|
|
19843
|
+
return throughOpenImports(site.file, name, ctx, /* @__PURE__ */ new Set());
|
|
19844
|
+
}
|
|
19845
|
+
return resolveBinding(binding, site.file, site.scope, ctx, /* @__PURE__ */ new Set());
|
|
19846
|
+
}
|
|
19847
|
+
function isReceiver(name, scope) {
|
|
19848
|
+
return (name === "self" || name === "cls") && scope.kind === "function" && scope.parent?.kind === "class";
|
|
19849
|
+
}
|
|
19850
|
+
function receiverClass(site) {
|
|
19851
|
+
const classScope = site.scope.parent;
|
|
19852
|
+
if (classScope === null || classScope === void 0) {
|
|
19853
|
+
return stop("noDeclaration");
|
|
19854
|
+
}
|
|
19855
|
+
return { kind: "class", file: site.file, node: classScope.node };
|
|
19856
|
+
}
|
|
19857
|
+
var BINDING_RESOLVERS = {
|
|
19858
|
+
functionDef: (binding, file2) => ({
|
|
19859
|
+
kind: "function",
|
|
19860
|
+
target: reachedFunction(file2, binding.node)
|
|
19861
|
+
}),
|
|
19862
|
+
classDef: (binding, file2) => ({ kind: "class", file: file2, node: binding.node }),
|
|
19863
|
+
parameter: () => stop("callerSupplied"),
|
|
19864
|
+
// `import a.b.c` binds `a`, and `import a.b.c as m` binds `m` to `a.b.c`.
|
|
19865
|
+
import: (binding, file2, _scope, ctx) => moduleNamed(
|
|
19866
|
+
file2,
|
|
19867
|
+
binding.localName === binding.module.split(".")[0] ? binding.localName : binding.module,
|
|
19868
|
+
0,
|
|
19869
|
+
ctx
|
|
19870
|
+
),
|
|
19871
|
+
importFrom: (binding, file2, _scope, ctx, visited) => memberOf(
|
|
19872
|
+
moduleNamed(file2, binding.module, binding.relativeLevel, ctx),
|
|
19873
|
+
binding.importedName,
|
|
19874
|
+
ctx,
|
|
19875
|
+
visited
|
|
19876
|
+
),
|
|
19877
|
+
// `run = load` is an alias worth one hop; anything else is a value.
|
|
19878
|
+
assignment: (binding, file2, scope, ctx, visited) => aliasedValue(binding.value, file2, scope, ctx, visited),
|
|
19879
|
+
global: () => stop("unsettledValue"),
|
|
19880
|
+
nonlocal: () => stop("unsettledValue")
|
|
19881
|
+
};
|
|
19882
|
+
function resolveBinding(binding, file2, scope, ctx, visited) {
|
|
19883
|
+
const resolver = BINDING_RESOLVERS[binding.kind];
|
|
19884
|
+
return resolver(binding, file2, scope, ctx, visited);
|
|
19885
|
+
}
|
|
19886
|
+
function aliasedValue(value, file2, scope, ctx, visited) {
|
|
19887
|
+
if (value === null) {
|
|
19888
|
+
return stop("unsettledValue");
|
|
19889
|
+
}
|
|
19890
|
+
if (value.type === "identifier") {
|
|
19891
|
+
const binding = resolveName(scope, value.text);
|
|
19892
|
+
if (binding === null || binding.kind === "assignment") {
|
|
19893
|
+
return stop("unsettledValue");
|
|
19894
|
+
}
|
|
19895
|
+
return resolveBinding(binding, file2, scope, ctx, visited);
|
|
19896
|
+
}
|
|
19897
|
+
if (value.type === "call") {
|
|
19898
|
+
const callee = field(value, "function");
|
|
19899
|
+
const built = callee === null ? stop("unsettledValue") : aliasedValue(callee, file2, scope, ctx, visited);
|
|
19900
|
+
return built.kind === "class" ? built : stop("unsettledValue");
|
|
19901
|
+
}
|
|
19902
|
+
if (value.type === "attribute") {
|
|
19903
|
+
const object2 = field(value, "object");
|
|
19904
|
+
const attribute = field(value, "attribute");
|
|
19905
|
+
if (object2 === null || attribute === null) {
|
|
19906
|
+
return stop("unsettledValue");
|
|
19907
|
+
}
|
|
19908
|
+
return memberOf(
|
|
19909
|
+
aliasedValue(object2, file2, scope, ctx, visited),
|
|
19910
|
+
attribute.text,
|
|
19911
|
+
ctx
|
|
19912
|
+
);
|
|
19913
|
+
}
|
|
19914
|
+
return stop("unsettledValue");
|
|
19915
|
+
}
|
|
19916
|
+
function reachedFunction(file2, node, owner) {
|
|
19917
|
+
const name = field(node, "name")?.text ?? "<anon>";
|
|
19918
|
+
const ownerName = owner === void 0 ? null : field(owner, "name")?.text;
|
|
19919
|
+
return {
|
|
19920
|
+
file: file2,
|
|
19921
|
+
node,
|
|
19922
|
+
name,
|
|
19923
|
+
exportPath: ownerName === null || ownerName === void 0 ? [name] : [ownerName, name]
|
|
19924
|
+
};
|
|
19925
|
+
}
|
|
19926
|
+
function moduleNamed(from, module, relativeLevel, ctx) {
|
|
19927
|
+
const resolution = resolveModule(
|
|
19928
|
+
from.file,
|
|
19929
|
+
{ module, relativeLevel },
|
|
19930
|
+
{ roots: ctx.roots }
|
|
19931
|
+
);
|
|
19932
|
+
if (resolution.status !== "resolved") {
|
|
19933
|
+
return resolution.reason === "external" ? { kind: "package", dotted: module, relativeLevel, from } : stop(MODULE_STOP[resolution.reason]);
|
|
19934
|
+
}
|
|
19935
|
+
const file2 = ctx.filesByPath.get(resolution.file);
|
|
19936
|
+
return file2 === void 0 ? stop("outsideRun") : { kind: "module", file: file2 };
|
|
19937
|
+
}
|
|
19938
|
+
function memberOf(base, name, ctx, visited = /* @__PURE__ */ new Set()) {
|
|
19939
|
+
if (base.kind === "module") {
|
|
19940
|
+
return memberOfModule(base.file, name, ctx, visited);
|
|
19941
|
+
}
|
|
19942
|
+
if (base.kind === "package") {
|
|
19943
|
+
return moduleNamed(
|
|
19944
|
+
base.from,
|
|
19945
|
+
base.dotted === "" ? name : `${base.dotted}.${name}`,
|
|
19946
|
+
base.relativeLevel,
|
|
19947
|
+
ctx
|
|
19948
|
+
);
|
|
19949
|
+
}
|
|
19950
|
+
if (base.kind === "class") {
|
|
19951
|
+
return memberOfClass(base.file, base.node, name, ctx);
|
|
19952
|
+
}
|
|
19953
|
+
if (base.kind === "stop") {
|
|
19954
|
+
return base;
|
|
19955
|
+
}
|
|
19956
|
+
return stop("noDeclaration");
|
|
19957
|
+
}
|
|
19958
|
+
function memberOfClass(file2, classNode, name, ctx) {
|
|
19959
|
+
const classScope = file2.module.scopeFor.get(classNode.id);
|
|
19960
|
+
const binding = classScope?.bindings.get(name);
|
|
19961
|
+
if (binding === void 0) {
|
|
19962
|
+
return stop("noDeclaration");
|
|
19963
|
+
}
|
|
19964
|
+
if (binding.kind === "functionDef") {
|
|
19965
|
+
return {
|
|
19966
|
+
kind: "function",
|
|
19967
|
+
target: reachedFunction(file2, binding.node, classNode)
|
|
19968
|
+
};
|
|
19969
|
+
}
|
|
19970
|
+
return resolveBinding(binding, file2, classScope, ctx, /* @__PURE__ */ new Set());
|
|
19971
|
+
}
|
|
19972
|
+
function memberOfModule(file2, name, ctx, visited) {
|
|
19973
|
+
const key = `${file2.file}#${name}`;
|
|
19974
|
+
if (visited.has(key)) {
|
|
19975
|
+
return stop("unsettledValue");
|
|
19976
|
+
}
|
|
19977
|
+
visited.add(key);
|
|
19978
|
+
const binding = file2.module.moduleScope.bindings.get(name);
|
|
19979
|
+
if (binding !== void 0) {
|
|
19980
|
+
return resolveBinding(binding, file2, file2.module.moduleScope, ctx, visited);
|
|
19981
|
+
}
|
|
19982
|
+
if (file2.file.endsWith("__init__.py")) {
|
|
19983
|
+
const submodule = moduleNamed(file2, name, 1, ctx);
|
|
19984
|
+
if (submodule.kind === "module") {
|
|
19985
|
+
return submodule;
|
|
19986
|
+
}
|
|
19987
|
+
}
|
|
19988
|
+
return throughOpenImports(file2, name, ctx, visited);
|
|
19989
|
+
}
|
|
19990
|
+
function throughOpenImports(file2, name, ctx, visited) {
|
|
19991
|
+
const found = [];
|
|
19992
|
+
for (const spec of file2.module.openImports) {
|
|
19993
|
+
const dots = spec.length - spec.replace(/^\.+/, "").length;
|
|
19994
|
+
const opened = moduleNamed(file2, spec.slice(dots), dots, ctx);
|
|
19995
|
+
if (opened.kind !== "module") {
|
|
19996
|
+
continue;
|
|
19997
|
+
}
|
|
19998
|
+
const member = memberOfModule(opened.file, name, ctx, visited);
|
|
19999
|
+
if (member.kind !== "stop") {
|
|
20000
|
+
found.push(member);
|
|
20001
|
+
}
|
|
20002
|
+
}
|
|
20003
|
+
if (found.length > 1) {
|
|
20004
|
+
return stop("multipleSources");
|
|
20005
|
+
}
|
|
20006
|
+
return found[0] ?? stop("noDeclaration");
|
|
20007
|
+
}
|
|
20008
|
+
|
|
20009
|
+
// src/reach/closure.ts
|
|
20010
|
+
var REACHABLE_RULES = [
|
|
20011
|
+
rule2("reachable", [v2("f")], [lit2("entry", v2("f"))]),
|
|
20012
|
+
rule2(
|
|
20013
|
+
"reachable",
|
|
20014
|
+
[v2("g")],
|
|
20015
|
+
[lit2("reachable", v2("f")), lit2("calls", v2("f"), v2("g"))]
|
|
20016
|
+
)
|
|
20017
|
+
];
|
|
20018
|
+
function reachedFunctions(seeds, options) {
|
|
20019
|
+
const ctx = {
|
|
20020
|
+
filesByPath: new Map(options.files.map((file2) => [file2.file, file2])),
|
|
20021
|
+
roots: options.roots
|
|
20022
|
+
};
|
|
20023
|
+
const db = new Database2();
|
|
20024
|
+
const functionByKey = /* @__PURE__ */ new Map();
|
|
20025
|
+
const seedKeys = /* @__PURE__ */ new Set();
|
|
20026
|
+
const scanned = /* @__PURE__ */ new Set();
|
|
20027
|
+
const targetsByKey = /* @__PURE__ */ new Map();
|
|
20028
|
+
const stopsByKey = /* @__PURE__ */ new Map();
|
|
20029
|
+
const argTargetsByKey = /* @__PURE__ */ new Map();
|
|
20030
|
+
const parameterCallsByKey = /* @__PURE__ */ new Map();
|
|
20031
|
+
const passedPositions = /* @__PURE__ */ new Set();
|
|
20032
|
+
for (const seed of seeds) {
|
|
20033
|
+
seedKeys.add(seed.key);
|
|
20034
|
+
functionByKey.set(seed.key, {
|
|
20035
|
+
file: seed.file,
|
|
20036
|
+
node: seed.node,
|
|
20037
|
+
name: field(seed.node, "name")?.text ?? "<anon>",
|
|
20038
|
+
exportPath: []
|
|
20039
|
+
});
|
|
20040
|
+
db.add("entry", [seed.key]);
|
|
20041
|
+
}
|
|
20042
|
+
for (; ; ) {
|
|
20043
|
+
evaluate3(db, REACHABLE_RULES);
|
|
20044
|
+
const frontier = db.facts("reachable").map(([key]) => String(key)).filter((key) => !scanned.has(key));
|
|
20045
|
+
if (frontier.length === 0) {
|
|
20046
|
+
break;
|
|
20047
|
+
}
|
|
20048
|
+
for (const key of frontier) {
|
|
20049
|
+
scanned.add(key);
|
|
20050
|
+
const source = functionByKey.get(key);
|
|
20051
|
+
if (source === void 0) {
|
|
20052
|
+
continue;
|
|
20053
|
+
}
|
|
20054
|
+
const scan = scanBody(source, ctx);
|
|
20055
|
+
if (scan.stops.length > 0) {
|
|
20056
|
+
stopsByKey.set(key, scan.stops);
|
|
20057
|
+
}
|
|
20058
|
+
targetsByKey.set(key, scan.targets);
|
|
20059
|
+
argTargetsByKey.set(key, scan.argTargets);
|
|
20060
|
+
if (scan.parameterCalls.length > 0) {
|
|
20061
|
+
parameterCallsByKey.set(key, scan.parameterCalls);
|
|
20062
|
+
}
|
|
20063
|
+
for (const position of scan.passedPositions) {
|
|
20064
|
+
passedPositions.add(position);
|
|
20065
|
+
}
|
|
20066
|
+
for (const target of scan.followed) {
|
|
20067
|
+
const calleeKey = keyOf(target);
|
|
20068
|
+
if (!functionByKey.has(calleeKey)) {
|
|
20069
|
+
functionByKey.set(calleeKey, target);
|
|
20070
|
+
}
|
|
20071
|
+
db.add("calls", [key, calleeKey]);
|
|
20072
|
+
}
|
|
20073
|
+
}
|
|
20074
|
+
}
|
|
20075
|
+
const summaries = [];
|
|
20076
|
+
const summariesByKey = /* @__PURE__ */ new Map();
|
|
20077
|
+
for (const [keyAtom] of db.facts("reachable")) {
|
|
20078
|
+
const key = String(keyAtom);
|
|
20079
|
+
const target = functionByKey.get(key);
|
|
20080
|
+
if (seedKeys.has(key) || target === void 0) {
|
|
20081
|
+
continue;
|
|
20082
|
+
}
|
|
20083
|
+
const summary = assembleSummary(libraryUnit(target, options), {
|
|
20084
|
+
gapHandling: options.gapHandling
|
|
20085
|
+
});
|
|
20086
|
+
summary.confidence = { source: "inferred_static", level: "low" };
|
|
20087
|
+
if (options.gapHandling !== "silent") {
|
|
20088
|
+
summary.gaps.push(...(stopsByKey.get(key) ?? []).map(unfollowedCallGap));
|
|
20089
|
+
}
|
|
20090
|
+
placeCalls(summary, targetsByKey.get(key));
|
|
20091
|
+
placeArgTargets(summary, argTargetsByKey.get(key));
|
|
20092
|
+
placeCalleeParameters(summary, parameterCallsByKey.get(key));
|
|
20093
|
+
summariesByKey.set(key, [summary]);
|
|
20094
|
+
summaries.push(summary);
|
|
20095
|
+
}
|
|
20096
|
+
if (options.gapHandling !== "silent") {
|
|
20097
|
+
recordParameterGaps(parameterCallsByKey, summariesByKey, passedPositions);
|
|
20098
|
+
}
|
|
20099
|
+
return {
|
|
20100
|
+
summaries,
|
|
20101
|
+
targetsByKey,
|
|
20102
|
+
stopsByKey,
|
|
20103
|
+
argTargetsByKey,
|
|
20104
|
+
parameterCallsByKey,
|
|
20105
|
+
passedPositions
|
|
20106
|
+
};
|
|
20107
|
+
}
|
|
20108
|
+
function keyOf(target) {
|
|
20109
|
+
return nodeId(target.file.file, target.node);
|
|
20110
|
+
}
|
|
20111
|
+
var EMPTY_SCAN = {
|
|
20112
|
+
followed: [],
|
|
20113
|
+
stops: [],
|
|
20114
|
+
targets: /* @__PURE__ */ new Map(),
|
|
20115
|
+
argTargets: /* @__PURE__ */ new Map(),
|
|
20116
|
+
parameterCalls: [],
|
|
20117
|
+
passedPositions: /* @__PURE__ */ new Set()
|
|
20118
|
+
};
|
|
20119
|
+
function scanBody(source, ctx) {
|
|
20120
|
+
const followed = [];
|
|
20121
|
+
const stops = [];
|
|
20122
|
+
const placements = new TargetPlacements();
|
|
20123
|
+
const parameterCalls = [];
|
|
20124
|
+
const passedPositions = /* @__PURE__ */ new Set();
|
|
20125
|
+
const seen = /* @__PURE__ */ new Set();
|
|
20126
|
+
const parameterCallsSeen = /* @__PURE__ */ new Set();
|
|
20127
|
+
const { file: file2, node } = source;
|
|
20128
|
+
const body = field(node, "body");
|
|
20129
|
+
if (body === null) {
|
|
20130
|
+
return EMPTY_SCAN;
|
|
20131
|
+
}
|
|
20132
|
+
const ownParameters = callParameterNames(node, source.exportPath.length > 1);
|
|
20133
|
+
const recordPassedArgs = (call, callee, calleeKey, where) => {
|
|
20134
|
+
const args = field(call, "arguments");
|
|
20135
|
+
if (args === null) {
|
|
20136
|
+
return;
|
|
20137
|
+
}
|
|
20138
|
+
args.namedChildren.forEach((arg, position) => {
|
|
20139
|
+
if (arg === null || arg.type !== "identifier") {
|
|
20140
|
+
return;
|
|
20141
|
+
}
|
|
20142
|
+
const resolved = resolveNamedFunctionArgument(
|
|
20143
|
+
arg.text,
|
|
20144
|
+
{ file: file2, scope: where.scope, rebound: where.rebound },
|
|
20145
|
+
ctx
|
|
20146
|
+
);
|
|
20147
|
+
if (resolved === null) {
|
|
20148
|
+
return;
|
|
20149
|
+
}
|
|
20150
|
+
const key = keyOf(resolved);
|
|
20151
|
+
if (!seen.has(key)) {
|
|
20152
|
+
seen.add(key);
|
|
20153
|
+
followed.push(resolved);
|
|
20154
|
+
}
|
|
20155
|
+
placements.placeArg(callee, position, {
|
|
20156
|
+
file: resolved.file.displayPath,
|
|
20157
|
+
span: spanOf(resolved.node)
|
|
20158
|
+
});
|
|
20159
|
+
if (calleeKey !== null) {
|
|
20160
|
+
passedPositions.add(`${calleeKey}#${position}`);
|
|
20161
|
+
}
|
|
20162
|
+
});
|
|
20163
|
+
};
|
|
20164
|
+
const record2 = (call, where) => {
|
|
20165
|
+
const callee = calleeText(call);
|
|
20166
|
+
const outcome = resolveCallee2(
|
|
20167
|
+
call,
|
|
20168
|
+
{ file: file2, scope: where.scope, rebound: where.rebound },
|
|
20169
|
+
ctx
|
|
20170
|
+
);
|
|
20171
|
+
const placed = outcome.kind === "followed" ? {
|
|
20172
|
+
file: outcome.target.file.displayPath,
|
|
20173
|
+
span: spanOf(outcome.target.node)
|
|
20174
|
+
} : outcome.reason === "noDeclaration" ? null : { file: file2.displayPath, span: spanOf(call) };
|
|
20175
|
+
placements.place(callee, placed);
|
|
20176
|
+
recordPassedArgs(
|
|
20177
|
+
call,
|
|
20178
|
+
callee,
|
|
20179
|
+
outcome.kind === "followed" ? keyOf(outcome.target) : null,
|
|
20180
|
+
where
|
|
20181
|
+
);
|
|
20182
|
+
if (outcome.kind === "stopped") {
|
|
20183
|
+
const stopKey = `${outcome.reason}:${callee}`;
|
|
20184
|
+
if (!seen.has(stopKey) && worthRecording(outcome.reason)) {
|
|
20185
|
+
seen.add(stopKey);
|
|
20186
|
+
stops.push({ callee, reason: outcome.reason });
|
|
20187
|
+
}
|
|
20188
|
+
if (outcome.reason === "callerSupplied" && !parameterCallsSeen.has(callee)) {
|
|
20189
|
+
const parameterIndex = ownParameters.indexOf(callee);
|
|
20190
|
+
if (parameterIndex !== -1) {
|
|
20191
|
+
parameterCallsSeen.add(callee);
|
|
20192
|
+
parameterCalls.push({ callee, parameterIndex });
|
|
20193
|
+
}
|
|
20194
|
+
}
|
|
20195
|
+
return;
|
|
20196
|
+
}
|
|
20197
|
+
const key = keyOf(outcome.target);
|
|
20198
|
+
if (!seen.has(key)) {
|
|
20199
|
+
seen.add(key);
|
|
20200
|
+
followed.push(outcome.target);
|
|
20201
|
+
}
|
|
20202
|
+
};
|
|
20203
|
+
walkDescendants3(
|
|
20204
|
+
body,
|
|
20205
|
+
{ scope: scopeAt(file2, node), rebound: reboundNames(body) },
|
|
20206
|
+
{
|
|
20207
|
+
at: (child, where) => {
|
|
20208
|
+
if (child.type === "call") {
|
|
20209
|
+
record2(child, where);
|
|
20210
|
+
}
|
|
20211
|
+
},
|
|
20212
|
+
into: (child, where) => {
|
|
20213
|
+
if (child.type === "function_definition") {
|
|
20214
|
+
return SKIP_CHILDREN2;
|
|
20215
|
+
}
|
|
20216
|
+
if (child.type === "lambda") {
|
|
20217
|
+
return {
|
|
20218
|
+
scope: where.scope,
|
|
20219
|
+
rebound: /* @__PURE__ */ new Set([...where.rebound, ...lambdaParameters(child)])
|
|
20220
|
+
};
|
|
20221
|
+
}
|
|
20222
|
+
return {
|
|
20223
|
+
scope: file2.module.scopeFor.get(child.id) ?? where.scope,
|
|
20224
|
+
rebound: where.rebound
|
|
20225
|
+
};
|
|
20226
|
+
}
|
|
20227
|
+
}
|
|
20228
|
+
);
|
|
20229
|
+
return {
|
|
20230
|
+
followed,
|
|
20231
|
+
stops,
|
|
20232
|
+
targets: placements.targets,
|
|
20233
|
+
argTargets: placements.argTargets,
|
|
20234
|
+
parameterCalls,
|
|
20235
|
+
passedPositions
|
|
20236
|
+
};
|
|
20237
|
+
}
|
|
20238
|
+
function scopeAt(file2, node) {
|
|
20239
|
+
let current = node;
|
|
20240
|
+
while (current !== null) {
|
|
20241
|
+
const scope = file2.module.scopeFor.get(current.id);
|
|
20242
|
+
if (scope !== void 0) {
|
|
20243
|
+
return scope;
|
|
20244
|
+
}
|
|
20245
|
+
current = current.parent;
|
|
20246
|
+
}
|
|
20247
|
+
return file2.module.moduleScope;
|
|
20248
|
+
}
|
|
20249
|
+
var REBINDING_FIELDS = {
|
|
20250
|
+
assignment: "left",
|
|
20251
|
+
augmented_assignment: "left",
|
|
20252
|
+
named_expression: "name",
|
|
20253
|
+
for_statement: "left",
|
|
20254
|
+
for_in_clause: "left",
|
|
20255
|
+
as_pattern: "alias"
|
|
20256
|
+
};
|
|
20257
|
+
function reboundNames(body) {
|
|
20258
|
+
const names = /* @__PURE__ */ new Set();
|
|
20259
|
+
walkDescendants3(body, null, {
|
|
20260
|
+
at: (node) => {
|
|
20261
|
+
const target = REBINDING_FIELDS[node.type];
|
|
20262
|
+
if (target === void 0 || boundByBinder(node, body)) {
|
|
20263
|
+
return;
|
|
20264
|
+
}
|
|
20265
|
+
const written = field(node, target);
|
|
20266
|
+
if (written !== null) {
|
|
20267
|
+
for (const name of identifiersUnder(written)) {
|
|
20268
|
+
names.add(name);
|
|
20269
|
+
}
|
|
20270
|
+
}
|
|
20271
|
+
},
|
|
20272
|
+
into: (node) => node.type === "function_definition" ? SKIP_CHILDREN2 : null
|
|
20273
|
+
});
|
|
20274
|
+
return names;
|
|
20275
|
+
}
|
|
20276
|
+
function boundByBinder(node, body) {
|
|
20277
|
+
if (node.type !== "assignment") {
|
|
20278
|
+
return false;
|
|
20279
|
+
}
|
|
20280
|
+
const statement = node.parent;
|
|
20281
|
+
return field(node, "left")?.type === "identifier" && statement?.type === "expression_statement" && statement.parent?.id === body.id;
|
|
20282
|
+
}
|
|
20283
|
+
function identifiersUnder(node, found = []) {
|
|
20284
|
+
if (node.type === "identifier") {
|
|
20285
|
+
found.push(node.text);
|
|
20286
|
+
return found;
|
|
20287
|
+
}
|
|
20288
|
+
for (const child of node.namedChildren) {
|
|
20289
|
+
if (child !== null && child.type !== "attribute" && child.type !== "subscript") {
|
|
20290
|
+
identifiersUnder(child, found);
|
|
20291
|
+
}
|
|
20292
|
+
}
|
|
20293
|
+
return found;
|
|
20294
|
+
}
|
|
20295
|
+
function lambdaParameters(lambda) {
|
|
20296
|
+
const parameters = field(lambda, "parameters");
|
|
20297
|
+
return parameters === null ? [] : identifiersUnder(parameters);
|
|
20298
|
+
}
|
|
20299
|
+
function libraryUnit(target, options) {
|
|
20300
|
+
const { file: file2, node, name, exportPath } = target;
|
|
20301
|
+
const body = field(node, "body");
|
|
20302
|
+
const extra = recognizedBodyEffects(
|
|
20303
|
+
node,
|
|
20304
|
+
file2.module,
|
|
20305
|
+
options.storageFor(file2)
|
|
20306
|
+
);
|
|
20307
|
+
const range = rangeOf(node);
|
|
20308
|
+
return {
|
|
20309
|
+
identity: {
|
|
20310
|
+
name,
|
|
20311
|
+
nameKind: "binding",
|
|
20312
|
+
kind: "library",
|
|
20313
|
+
file: file2.displayPath,
|
|
20314
|
+
range,
|
|
20315
|
+
span: spanOf(node),
|
|
20316
|
+
exportName: exportPath[0] ?? name,
|
|
20317
|
+
exportPath
|
|
20318
|
+
},
|
|
20319
|
+
boundaryBinding: functionCallBinding({
|
|
20320
|
+
transport: "in-process",
|
|
20321
|
+
recognition: "reachable"
|
|
20322
|
+
}),
|
|
20323
|
+
parameters: positionalParameters(node, exportPath.length > 1),
|
|
20324
|
+
branches: [
|
|
20325
|
+
{
|
|
20326
|
+
conditions: [],
|
|
20327
|
+
terminal: {
|
|
20328
|
+
kind: "return",
|
|
20329
|
+
statusCode: null,
|
|
20330
|
+
body: null,
|
|
20331
|
+
exceptionType: null,
|
|
20332
|
+
message: null,
|
|
20333
|
+
component: null,
|
|
20334
|
+
renderTree: null,
|
|
20335
|
+
delegateTarget: null,
|
|
20336
|
+
emitEvent: null,
|
|
20337
|
+
location: range
|
|
20338
|
+
},
|
|
20339
|
+
effects: invocationEffects(node),
|
|
20340
|
+
...extra.length === 0 ? {} : { extraEffects: extra },
|
|
20341
|
+
location: range,
|
|
20342
|
+
isDefault: true
|
|
20343
|
+
}
|
|
20344
|
+
],
|
|
20345
|
+
bodyContent: body === null ? "absent" : bodyContentOf(body),
|
|
20346
|
+
dependencyCalls: [],
|
|
20347
|
+
declaredContract: null
|
|
20348
|
+
};
|
|
20349
|
+
}
|
|
20350
|
+
function positionalParameters(node, isMethod) {
|
|
20351
|
+
const parameters = field(node, "parameters");
|
|
20352
|
+
if (parameters === null) {
|
|
20353
|
+
return [];
|
|
20354
|
+
}
|
|
20355
|
+
const out = [];
|
|
20356
|
+
let position = 0;
|
|
20357
|
+
for (const param of parameters.namedChildren) {
|
|
20358
|
+
if (param === null) {
|
|
20359
|
+
continue;
|
|
20360
|
+
}
|
|
20361
|
+
const name = parameterName3(param);
|
|
20362
|
+
if (name === null) {
|
|
20363
|
+
continue;
|
|
20364
|
+
}
|
|
20365
|
+
if (isMethod && position === 0 && (name === "self" || name === "cls")) {
|
|
20366
|
+
position += 1;
|
|
20367
|
+
continue;
|
|
20368
|
+
}
|
|
20369
|
+
out.push({ name, position, role: name, typeText: null });
|
|
20370
|
+
position += 1;
|
|
20371
|
+
}
|
|
20372
|
+
return out;
|
|
20373
|
+
}
|
|
20374
|
+
function callParameterNames(node, isMethod) {
|
|
20375
|
+
const parameters = field(node, "parameters");
|
|
20376
|
+
if (parameters === null) {
|
|
20377
|
+
return [];
|
|
20378
|
+
}
|
|
20379
|
+
const names = [];
|
|
20380
|
+
for (const param of parameters.namedChildren) {
|
|
20381
|
+
if (param === null) {
|
|
20382
|
+
continue;
|
|
20383
|
+
}
|
|
20384
|
+
const name = parameterName3(param);
|
|
20385
|
+
if (name === null) {
|
|
20386
|
+
continue;
|
|
20387
|
+
}
|
|
20388
|
+
if (isMethod && names.length === 0 && (name === "self" || name === "cls")) {
|
|
20389
|
+
continue;
|
|
20390
|
+
}
|
|
20391
|
+
names.push(name);
|
|
20392
|
+
}
|
|
20393
|
+
return names;
|
|
20394
|
+
}
|
|
20395
|
+
function parameterName3(param) {
|
|
20396
|
+
if (param.type === "identifier") {
|
|
20397
|
+
return param.text;
|
|
20398
|
+
}
|
|
20399
|
+
const named = field(param, "name");
|
|
20400
|
+
if (named !== null && named.type === "identifier") {
|
|
20401
|
+
return named.text;
|
|
20402
|
+
}
|
|
20403
|
+
return identifiersUnder(param)[0] ?? null;
|
|
20404
|
+
}
|
|
19213
20405
|
|
|
19214
20406
|
// src/routers.ts
|
|
19215
20407
|
var UNSTATED_PREFIX = { kind: "unstated" };
|
|
@@ -19484,6 +20676,9 @@ function buildPatternIndex(files, importModule, composition, resolverOptions) {
|
|
|
19484
20676
|
for (const bound of files) {
|
|
19485
20677
|
collectConstructions(bound, importModule, composition, index);
|
|
19486
20678
|
}
|
|
20679
|
+
for (const bound of files) {
|
|
20680
|
+
collectReturnedConstructions(bound, importModule, composition, index);
|
|
20681
|
+
}
|
|
19487
20682
|
if (objectPrefix?.carrier !== void 0) {
|
|
19488
20683
|
const carrier = objectPrefix.carrier;
|
|
19489
20684
|
for (const bound of files) {
|
|
@@ -19659,6 +20854,37 @@ function collectConstructions(bound, importModule, composition, index) {
|
|
|
19659
20854
|
);
|
|
19660
20855
|
}
|
|
19661
20856
|
}
|
|
20857
|
+
function collectReturnedConstructions(bound, importModule, composition, index) {
|
|
20858
|
+
for (const stmt of bodyStatements(bound.root)) {
|
|
20859
|
+
const definition = stripDecorators(stmt).definition;
|
|
20860
|
+
if (definition.type !== "function_definition") {
|
|
20861
|
+
continue;
|
|
20862
|
+
}
|
|
20863
|
+
const scope = bound.module.scopeFor.get(definition.id);
|
|
20864
|
+
for (const inner of nestedStatements(definition)) {
|
|
20865
|
+
if (inner.type !== "return_statement" || scope === void 0) {
|
|
20866
|
+
continue;
|
|
20867
|
+
}
|
|
20868
|
+
const returned = inner.namedChildren[0];
|
|
20869
|
+
if (returned?.type !== "call") {
|
|
20870
|
+
continue;
|
|
20871
|
+
}
|
|
20872
|
+
const callee = field(returned, "function");
|
|
20873
|
+
const constructorName = callee === null ? null : importedConstructorName(callee, scope, importModule);
|
|
20874
|
+
if (constructorName !== composition.routerConstructorName) {
|
|
20875
|
+
continue;
|
|
20876
|
+
}
|
|
20877
|
+
const { keywordArgs } = readCallArguments(field(returned, "arguments"));
|
|
20878
|
+
const valueKey2 = nodeId(bound.file, returned);
|
|
20879
|
+
index.byValueKey.set(valueKey2, {
|
|
20880
|
+
constructorName,
|
|
20881
|
+
valueKey: valueKey2,
|
|
20882
|
+
prefix: constructorPrefix(keywordArgs, composition, scope),
|
|
20883
|
+
reassigned: false
|
|
20884
|
+
});
|
|
20885
|
+
}
|
|
20886
|
+
}
|
|
20887
|
+
}
|
|
19662
20888
|
function collectCarrierConstructions(carrier, composition, scan) {
|
|
19663
20889
|
walkStatements(
|
|
19664
20890
|
bodyStatements(scan.bound.root),
|
|
@@ -19717,6 +20943,21 @@ function constructionNamed(name, scope, scan, constructions = scan.index.constru
|
|
|
19717
20943
|
};
|
|
19718
20944
|
return resolvers[binding.kind]?.() ?? null;
|
|
19719
20945
|
}
|
|
20946
|
+
function constructionThroughFacts(name, scope, scan) {
|
|
20947
|
+
const facts = scan.index.facts;
|
|
20948
|
+
const binding = resolveName(scope, name);
|
|
20949
|
+
if (facts === void 0 || binding === null) {
|
|
20950
|
+
return null;
|
|
20951
|
+
}
|
|
20952
|
+
if (binding.kind !== "assignment" && binding.kind !== "importFrom") {
|
|
20953
|
+
return null;
|
|
20954
|
+
}
|
|
20955
|
+
const nameKey = `${scan.bound.file}#${name}`;
|
|
20956
|
+
const constructionKey = subjectConstructions(facts, [nameKey]).get(
|
|
20957
|
+
nameKey
|
|
20958
|
+
)?.constructionKey;
|
|
20959
|
+
return constructionKey === void 0 ? null : scan.index.byValueKey.get(constructionKey) ?? null;
|
|
20960
|
+
}
|
|
19720
20961
|
var MOUNT_STATE_BY_EFFECT = {
|
|
19721
20962
|
prefixes: (statedPrefix, site) => ({
|
|
19722
20963
|
kind: "mounted",
|
|
@@ -19886,10 +21127,10 @@ function constructionsReturnedBy(target, index) {
|
|
|
19886
21127
|
return { kind: "unread" };
|
|
19887
21128
|
}
|
|
19888
21129
|
const contained = containedValues(facts, returned);
|
|
19889
|
-
|
|
21130
|
+
const settled = subjectConstructions(facts, contained);
|
|
19890
21131
|
const found = [];
|
|
19891
21132
|
for (const value of contained) {
|
|
19892
|
-
const resolved =
|
|
21133
|
+
const resolved = settled.get(value)?.constructionKey ?? value;
|
|
19893
21134
|
const construction = index.byValueKey.get(resolved);
|
|
19894
21135
|
if (construction === void 0) {
|
|
19895
21136
|
return {
|
|
@@ -19904,10 +21145,6 @@ function constructionsReturnedBy(target, index) {
|
|
|
19904
21145
|
}
|
|
19905
21146
|
return { kind: "settled", found };
|
|
19906
21147
|
}
|
|
19907
|
-
function resolvedValueKey(facts, value) {
|
|
19908
|
-
const row = facts.facts("wantedIsWrittenAs").find((entry) => String(entry[0]) === value);
|
|
19909
|
-
return row === void 0 ? value : String(row[1]);
|
|
19910
|
-
}
|
|
19911
21148
|
function mountedConstructions(arg, position, scan) {
|
|
19912
21149
|
if (arg?.kind === "attribute") {
|
|
19913
21150
|
const construction = constructionThroughModule(arg, position.scope, scan);
|
|
@@ -19917,7 +21154,7 @@ function mountedConstructions(arg, position, scan) {
|
|
|
19917
21154
|
return [];
|
|
19918
21155
|
}
|
|
19919
21156
|
const named = (name) => {
|
|
19920
|
-
const construction = constructionNamed(name, position.scope, scan);
|
|
21157
|
+
const construction = constructionNamed(name, position.scope, scan) ?? constructionThroughFacts(name, position.scope, scan);
|
|
19921
21158
|
return construction === null ? [] : [construction];
|
|
19922
21159
|
};
|
|
19923
21160
|
const target = position.loopBindings.get(arg.name);
|
|
@@ -20304,36 +21541,69 @@ function carrierPrefix(carrier, constructorArg, objectName, scope, scan) {
|
|
|
20304
21541
|
return readings[construction.prefix.kind]();
|
|
20305
21542
|
}
|
|
20306
21543
|
|
|
21544
|
+
// src/version.ts
|
|
21545
|
+
import { createAdapterStamp } from "@suss/extractor";
|
|
21546
|
+
var ADAPTER_VERSION = "0.1.0";
|
|
21547
|
+
var adapterStamp = createAdapterStamp({
|
|
21548
|
+
moduleUrl: import.meta.url,
|
|
21549
|
+
version: ADAPTER_VERSION
|
|
21550
|
+
});
|
|
21551
|
+
|
|
20307
21552
|
// src/project.ts
|
|
20308
|
-
function reportUnresolvedProjectModules(options) {
|
|
21553
|
+
function reportUnresolvedProjectModules(options, db) {
|
|
21554
|
+
const imported = new Set(db.facts("pyImport").map((row) => String(row[1])));
|
|
20309
21555
|
for (const pack of options.packs) {
|
|
20310
21556
|
for (const module of pack.projectModules ?? []) {
|
|
20311
|
-
|
|
20312
|
-
if (resolved.status === "resolved") {
|
|
21557
|
+
if (imported.has(module)) {
|
|
20313
21558
|
continue;
|
|
20314
21559
|
}
|
|
20315
21560
|
process.stderr.write(
|
|
20316
|
-
`[suss] ${pack.name}:
|
|
21561
|
+
`[suss] ${pack.name}: no file under ${options.roots.join(", ")} imports ${module}, so the stub for it changes nothing.
|
|
20317
21562
|
`
|
|
20318
21563
|
);
|
|
20319
21564
|
}
|
|
20320
21565
|
}
|
|
20321
21566
|
}
|
|
20322
21567
|
async function extractPythonProject(options) {
|
|
20323
|
-
const
|
|
21568
|
+
const timer = options.onTiming !== void 0 ? createTimer() : noopTimer();
|
|
21569
|
+
const cacheDir = adapterStamp.declineWhenRunFromSource(
|
|
21570
|
+
options.cacheDir === null ? null : options.cacheDir ?? (options.projectRoot !== void 0 ? path3.join(options.projectRoot, ".suss", "cache") : null)
|
|
21571
|
+
);
|
|
21572
|
+
const cache = createCacheLayer(cacheDir);
|
|
21573
|
+
const packsDigest = adapterStamp.packsDigest(
|
|
21574
|
+
options.packs.map(
|
|
21575
|
+
(pack) => pack.version !== void 0 ? { name: pack.name, version: pack.version } : { name: pack.name }
|
|
21576
|
+
)
|
|
21577
|
+
);
|
|
21578
|
+
const cacheInput = {
|
|
21579
|
+
files: cacheDir === null ? [] : options.files,
|
|
21580
|
+
adapterPacksDigest: cacheDir === null ? packsDigest : runDigest(packsDigest, options.packs, options.files)
|
|
21581
|
+
};
|
|
21582
|
+
const lookup = await timer.timeAsync(
|
|
21583
|
+
"cache.lookup",
|
|
21584
|
+
() => cache.lookup(cacheInput)
|
|
21585
|
+
);
|
|
21586
|
+
options.onCacheDiagnostic?.(lookup.diagnostic);
|
|
21587
|
+
if (lookup.kind === "hit") {
|
|
21588
|
+
options.onTiming?.(timer.report());
|
|
21589
|
+
return { summaries: lookup.summaries, facts: new Database3() };
|
|
21590
|
+
}
|
|
21591
|
+
const db = new Database3();
|
|
20324
21592
|
const summaries = [];
|
|
20325
21593
|
const gapHandling = options.gapHandling ?? "permissive";
|
|
21594
|
+
const tallies = createPackTallies(options.packs);
|
|
20326
21595
|
const displayPathOf = (file2) => options.workspaceRoot !== void 0 ? path3.relative(options.workspaceRoot, file2) : file2;
|
|
20327
|
-
reportUnresolvedProjectModules(options);
|
|
20328
21596
|
const bound = [];
|
|
20329
21597
|
for (const file2 of options.files) {
|
|
20330
|
-
|
|
20331
|
-
|
|
20332
|
-
|
|
20333
|
-
|
|
20334
|
-
|
|
20335
|
-
|
|
20336
|
-
|
|
21598
|
+
await timer.timeAsync("parse", async () => {
|
|
21599
|
+
const source = fs3.readFileSync(file2, "utf8");
|
|
21600
|
+
const tree = await parsePython(source);
|
|
21601
|
+
bound.push({
|
|
21602
|
+
file: file2,
|
|
21603
|
+
displayPath: displayPathOf(file2),
|
|
21604
|
+
root: tree.rootNode,
|
|
21605
|
+
module: bindModule(tree.rootNode)
|
|
21606
|
+
});
|
|
20337
21607
|
});
|
|
20338
21608
|
}
|
|
20339
21609
|
const mountsRouters = options.packs.some(
|
|
@@ -20344,81 +21614,172 @@ async function extractPythonProject(options) {
|
|
|
20344
21614
|
const discovers = options.packs.some((pack) => pack.discovery.length > 0);
|
|
20345
21615
|
const needsValues = discovers || mountsRouters || storagePatterns.length > 0;
|
|
20346
21616
|
const definitions = /* @__PURE__ */ new Map();
|
|
20347
|
-
|
|
20348
|
-
|
|
20349
|
-
|
|
20350
|
-
|
|
20351
|
-
|
|
20352
|
-
|
|
21617
|
+
timer.time("discover", () => {
|
|
21618
|
+
for (const { file: file2, root, module: moduleBinding } of bound) {
|
|
21619
|
+
emitModuleImportFacts(db, file2, moduleBinding, { roots: options.roots });
|
|
21620
|
+
if (needsValues) {
|
|
21621
|
+
emitValueFacts(db, file2, root);
|
|
21622
|
+
}
|
|
20353
21623
|
indexDefinitions(definitions, file2, root);
|
|
20354
21624
|
}
|
|
20355
|
-
}
|
|
20356
|
-
const couldMatch = methodsDeclaredNear(db, storagePatterns, definitions);
|
|
20357
|
-
const startsAQuery = /* @__PURE__ */ new Set([
|
|
20358
|
-
...couldMatch,
|
|
20359
|
-
...storagePatterns.flatMap((pattern) => pattern.queryFunctions ?? [])
|
|
20360
|
-
]);
|
|
20361
|
-
const leadsToStorage = functionsReachingStorage(
|
|
20362
|
-
db,
|
|
20363
|
-
definitions,
|
|
20364
|
-
startsAQuery
|
|
20365
|
-
);
|
|
20366
|
-
const routerIndex = buildRouterIndex(bound, options.packs, {
|
|
20367
|
-
roots: options.roots,
|
|
20368
|
-
...mountsRouters ? { facts: db } : {}
|
|
20369
21625
|
});
|
|
20370
|
-
|
|
21626
|
+
reportUnresolvedProjectModules(options, db);
|
|
21627
|
+
const couldMatch = timer.time(
|
|
21628
|
+
"discover",
|
|
21629
|
+
() => methodsDeclaredNear(db, storagePatterns, definitions)
|
|
21630
|
+
);
|
|
21631
|
+
const routerIndex = timer.time(
|
|
21632
|
+
"discover",
|
|
21633
|
+
() => buildRouterIndex(bound, options.packs, {
|
|
21634
|
+
roots: options.roots,
|
|
21635
|
+
...mountsRouters ? { facts: db } : {}
|
|
21636
|
+
})
|
|
21637
|
+
);
|
|
21638
|
+
const storageFor = (file2) => storagePatterns.length > 0 || rawSqlPatterns.length > 0 ? {
|
|
21639
|
+
facts: db,
|
|
21640
|
+
factsPath: file2.file,
|
|
21641
|
+
patterns: storagePatterns,
|
|
21642
|
+
definitionAt: (key) => definitions.get(key),
|
|
21643
|
+
couldMatch,
|
|
21644
|
+
rawSql: rawSqlPatterns
|
|
21645
|
+
} : void 0;
|
|
21646
|
+
const seeds = [];
|
|
21647
|
+
const summariesBySeed = /* @__PURE__ */ new Map();
|
|
21648
|
+
for (const boundFile of bound) {
|
|
21649
|
+
const { file: file2, root, module: moduleBinding } = boundFile;
|
|
20371
21650
|
const displayPath = displayPathOf(file2);
|
|
20372
|
-
const
|
|
20373
|
-
|
|
20374
|
-
|
|
20375
|
-
|
|
20376
|
-
|
|
20377
|
-
|
|
20378
|
-
|
|
20379
|
-
|
|
20380
|
-
|
|
20381
|
-
|
|
20382
|
-
|
|
20383
|
-
|
|
20384
|
-
|
|
20385
|
-
couldMatch,
|
|
20386
|
-
leadsToStorage,
|
|
20387
|
-
rawSql: rawSqlPatterns
|
|
20388
|
-
}
|
|
20389
|
-
} : {}
|
|
20390
|
-
});
|
|
21651
|
+
const storage = storageFor(boundFile);
|
|
21652
|
+
const rawUnits = timer.time(
|
|
21653
|
+
"discover",
|
|
21654
|
+
() => discoverUnits(root, moduleBinding, {
|
|
21655
|
+
packs: options.packs,
|
|
21656
|
+
filePath: displayPath,
|
|
21657
|
+
absoluteFile: file2,
|
|
21658
|
+
routerIndex,
|
|
21659
|
+
gapHandling,
|
|
21660
|
+
...needsValues ? { facts: db } : {},
|
|
21661
|
+
...storage === void 0 ? {} : { storage }
|
|
21662
|
+
})
|
|
21663
|
+
);
|
|
20391
21664
|
for (const raw of rawUnits) {
|
|
20392
|
-
const summary =
|
|
21665
|
+
const summary = timer.time(
|
|
21666
|
+
"summarize",
|
|
21667
|
+
() => assembleSummary2(raw, { gapHandling })
|
|
21668
|
+
);
|
|
20393
21669
|
summary.confidence = { source: "inferred_static", level: "low" };
|
|
20394
21670
|
summaries.push(summary);
|
|
20395
21671
|
emitEntryFact(db, file2, raw.identity.range, raw.identity.name);
|
|
21672
|
+
tallyUnit(tallies, raw.boundaryBinding?.recognition);
|
|
21673
|
+
const span = raw.identity.span;
|
|
21674
|
+
const key = span === void 0 ? null : `${file2}:${span.start}-${span.end}`;
|
|
21675
|
+
const node = key === null ? void 0 : definitions.get(key);
|
|
21676
|
+
if (key === null || node === void 0) {
|
|
21677
|
+
continue;
|
|
21678
|
+
}
|
|
21679
|
+
const sharing = summariesBySeed.get(key);
|
|
21680
|
+
if (sharing === void 0) {
|
|
21681
|
+
seeds.push({ key, file: boundFile, node });
|
|
21682
|
+
summariesBySeed.set(key, [summary]);
|
|
21683
|
+
} else {
|
|
21684
|
+
sharing.push(summary);
|
|
21685
|
+
}
|
|
20396
21686
|
}
|
|
21687
|
+
const loadTimeReads = timer.time(
|
|
21688
|
+
"discover",
|
|
21689
|
+
() => envReadEffects(root, moduleBinding)
|
|
21690
|
+
);
|
|
21691
|
+
if (loadTimeReads.length > 0) {
|
|
21692
|
+
const summary = timer.time(
|
|
21693
|
+
"summarize",
|
|
21694
|
+
() => assembleSummary2(
|
|
21695
|
+
moduleInitStructure({
|
|
21696
|
+
name: path3.basename(displayPath),
|
|
21697
|
+
file: displayPath,
|
|
21698
|
+
range: rangeOf(root),
|
|
21699
|
+
effects: loadTimeReads
|
|
21700
|
+
}),
|
|
21701
|
+
{ gapHandling }
|
|
21702
|
+
)
|
|
21703
|
+
);
|
|
21704
|
+
summary.confidence = { source: "inferred_static", level: "low" };
|
|
21705
|
+
summaries.push(summary);
|
|
21706
|
+
}
|
|
21707
|
+
}
|
|
21708
|
+
const reached = timer.time(
|
|
21709
|
+
"summarize",
|
|
21710
|
+
() => reachedFunctions(seeds, {
|
|
21711
|
+
files: bound,
|
|
21712
|
+
roots: options.roots,
|
|
21713
|
+
gapHandling,
|
|
21714
|
+
storageFor
|
|
21715
|
+
})
|
|
21716
|
+
);
|
|
21717
|
+
for (const [key, owners] of summariesBySeed) {
|
|
21718
|
+
for (const summary of owners) {
|
|
21719
|
+
if (gapHandling !== "silent") {
|
|
21720
|
+
summary.gaps.push(
|
|
21721
|
+
...(reached.stopsByKey.get(key) ?? []).map(unfollowedCallGap2)
|
|
21722
|
+
);
|
|
21723
|
+
}
|
|
21724
|
+
placeCalls2(summary, reached.targetsByKey.get(key));
|
|
21725
|
+
placeArgTargets2(summary, reached.argTargetsByKey.get(key));
|
|
21726
|
+
placeCalleeParameters2(summary, reached.parameterCallsByKey.get(key));
|
|
21727
|
+
}
|
|
21728
|
+
}
|
|
21729
|
+
if (gapHandling !== "silent") {
|
|
21730
|
+
recordParameterGaps2(
|
|
21731
|
+
reached.parameterCallsByKey,
|
|
21732
|
+
summariesBySeed,
|
|
21733
|
+
reached.passedPositions
|
|
21734
|
+
);
|
|
21735
|
+
}
|
|
21736
|
+
summaries.push(...reached.summaries);
|
|
21737
|
+
const resolvedImports = resolvedImportsOf(db, displayPathOf);
|
|
21738
|
+
stampModuleImports(summaries, (file2) => resolvedImports.get(file2) ?? []);
|
|
21739
|
+
const idRoot = options.projectRoot ?? options.workspaceRoot;
|
|
21740
|
+
for (const summary of summaries) {
|
|
21741
|
+
const absoluteFile = options.workspaceRoot === void 0 ? summary.location.file : path3.resolve(options.workspaceRoot, summary.location.file);
|
|
21742
|
+
summary.identity.id = summaryIdFromParts({
|
|
21743
|
+
workspace: void 0,
|
|
21744
|
+
file: idRoot === void 0 ? absoluteFile : path3.relative(idRoot, absoluteFile),
|
|
21745
|
+
name: summary.identity.name,
|
|
21746
|
+
exportPath: summary.identity.exportPath
|
|
21747
|
+
});
|
|
20397
21748
|
}
|
|
20398
|
-
|
|
21749
|
+
disambiguateSummaryIds(summaries);
|
|
21750
|
+
linkCallsToSummaries(summaries);
|
|
21751
|
+
await timer.timeAsync("cache.write", async () => {
|
|
21752
|
+
if (cacheDir === null || summaries.length === 0) {
|
|
21753
|
+
return;
|
|
21754
|
+
}
|
|
21755
|
+
try {
|
|
21756
|
+
await cache.write(cacheInput, summaries);
|
|
21757
|
+
} catch {
|
|
21758
|
+
}
|
|
21759
|
+
});
|
|
21760
|
+
options.onExtractionReport?.(
|
|
21761
|
+
buildPythonExtractionReport({
|
|
21762
|
+
packs: options.packs,
|
|
21763
|
+
tallies,
|
|
21764
|
+
filesWalked: options.files.length,
|
|
21765
|
+
summaries
|
|
21766
|
+
})
|
|
21767
|
+
);
|
|
21768
|
+
options.onTiming?.(timer.report());
|
|
20399
21769
|
return { summaries, facts: db };
|
|
20400
21770
|
}
|
|
20401
|
-
function
|
|
21771
|
+
function resolvedImportsOf(db, displayPathOf) {
|
|
20402
21772
|
const importsByFile = /* @__PURE__ */ new Map();
|
|
20403
21773
|
for (const [from, , to] of db.facts("pyImportResolved")) {
|
|
20404
21774
|
if (typeof from !== "string" || typeof to !== "string") {
|
|
20405
21775
|
continue;
|
|
20406
21776
|
}
|
|
20407
21777
|
const key = displayPathOf(from);
|
|
20408
|
-
const seen = importsByFile.get(key) ??
|
|
20409
|
-
seen.
|
|
21778
|
+
const seen = importsByFile.get(key) ?? [];
|
|
21779
|
+
seen.push(displayPathOf(to));
|
|
20410
21780
|
importsByFile.set(key, seen);
|
|
20411
21781
|
}
|
|
20412
|
-
|
|
20413
|
-
const imports = importsByFile.get(summary.location.file);
|
|
20414
|
-
if (imports === void 0 || imports.size === 0) {
|
|
20415
|
-
continue;
|
|
20416
|
-
}
|
|
20417
|
-
summary.metadata = {
|
|
20418
|
-
...summary.metadata ?? {},
|
|
20419
|
-
moduleImports: [...imports].sort()
|
|
20420
|
-
};
|
|
20421
|
-
}
|
|
21782
|
+
return importsByFile;
|
|
20422
21783
|
}
|
|
20423
21784
|
var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
20424
21785
|
"__pycache__",
|
|
@@ -20473,54 +21834,271 @@ function methodsDeclaredNear(db, patterns, definitions) {
|
|
|
20473
21834
|
}
|
|
20474
21835
|
return found;
|
|
20475
21836
|
}
|
|
20476
|
-
|
|
20477
|
-
|
|
20478
|
-
|
|
20479
|
-
|
|
21837
|
+
|
|
21838
|
+
// src/facts/why.ts
|
|
21839
|
+
var FUNCTION_TYPES2 = /* @__PURE__ */ new Set(["function_definition", "lambda"]);
|
|
21840
|
+
var IMPORT_TYPES = /* @__PURE__ */ new Set(["import_statement", "import_from_statement"]);
|
|
21841
|
+
function namedChildrenOf(node) {
|
|
21842
|
+
return node.namedChildren.filter((child) => child !== null);
|
|
21843
|
+
}
|
|
21844
|
+
function indexImportNames(file2, stmt, locations) {
|
|
21845
|
+
for (const nameNode of fields(stmt, "name")) {
|
|
21846
|
+
if (nameNode.type === "aliased_import") {
|
|
21847
|
+
const alias = field(nameNode, "alias");
|
|
21848
|
+
if (alias !== null) {
|
|
21849
|
+
locations.set(`${file2}#${alias.text}`, { file: file2, node: alias });
|
|
21850
|
+
}
|
|
21851
|
+
continue;
|
|
21852
|
+
}
|
|
21853
|
+
const local = nameNode.type === "dotted_name" ? nameNode.namedChild(0) : nameNode;
|
|
21854
|
+
if (local !== null) {
|
|
21855
|
+
locations.set(`${file2}#${local.text}`, { file: file2, node: local });
|
|
21856
|
+
}
|
|
20480
21857
|
}
|
|
20481
|
-
return callee.type === "attribute" ? field(callee, "attribute")?.text ?? "" : callee.text;
|
|
20482
21858
|
}
|
|
20483
|
-
function
|
|
20484
|
-
|
|
20485
|
-
|
|
21859
|
+
function enclosingFunction(node) {
|
|
21860
|
+
let current = node.parent;
|
|
21861
|
+
while (current !== null) {
|
|
21862
|
+
if (FUNCTION_TYPES2.has(current.type)) {
|
|
21863
|
+
return current;
|
|
21864
|
+
}
|
|
21865
|
+
current = current.parent;
|
|
21866
|
+
}
|
|
21867
|
+
return null;
|
|
21868
|
+
}
|
|
21869
|
+
function indexFile(file2, root, locations) {
|
|
21870
|
+
const walk = (node) => {
|
|
21871
|
+
locations.set(nodeId(file2, node), { file: file2, node });
|
|
21872
|
+
if (IMPORT_TYPES.has(node.type)) {
|
|
21873
|
+
indexImportNames(file2, node, locations);
|
|
21874
|
+
}
|
|
21875
|
+
if (FUNCTION_TYPES2.has(node.type) || node.type === "class_definition") {
|
|
21876
|
+
const name = field(node, "name");
|
|
21877
|
+
if (name !== null) {
|
|
21878
|
+
locations.set(`${file2}#${name.text}`, { file: file2, node: name });
|
|
21879
|
+
}
|
|
21880
|
+
}
|
|
21881
|
+
if (node.type === "assignment") {
|
|
21882
|
+
const left = field(node, "left");
|
|
21883
|
+
if (left !== null && left.type === "identifier") {
|
|
21884
|
+
locations.set(`${file2}#${left.text}`, { file: file2, node: left });
|
|
21885
|
+
}
|
|
21886
|
+
}
|
|
21887
|
+
for (const child of namedChildrenOf(node)) {
|
|
21888
|
+
walk(child);
|
|
21889
|
+
}
|
|
21890
|
+
};
|
|
21891
|
+
walk(root);
|
|
21892
|
+
}
|
|
21893
|
+
function displayNameOf(node) {
|
|
21894
|
+
if (node.type === "identifier") {
|
|
21895
|
+
return node.text;
|
|
21896
|
+
}
|
|
21897
|
+
if (node.type === "call") {
|
|
21898
|
+
const callee = field(node, "function");
|
|
21899
|
+
return `${callee?.text ?? "call"}(...)`;
|
|
21900
|
+
}
|
|
21901
|
+
if (node.type === "function_definition" || node.type === "class_definition") {
|
|
21902
|
+
return field(node, "name")?.text ?? node.text;
|
|
21903
|
+
}
|
|
21904
|
+
const text = node.text.split("\n")[0].trim();
|
|
21905
|
+
return text.length > 40 ? `${text.slice(0, 40)}...` : text;
|
|
21906
|
+
}
|
|
21907
|
+
function width(node) {
|
|
21908
|
+
return node.endIndex - node.startIndex;
|
|
21909
|
+
}
|
|
21910
|
+
var PythonWhySession = class {
|
|
21911
|
+
root;
|
|
21912
|
+
db = new Database4();
|
|
21913
|
+
locations = /* @__PURE__ */ new Map();
|
|
21914
|
+
trees = /* @__PURE__ */ new Map();
|
|
21915
|
+
constructor(options) {
|
|
21916
|
+
this.root = path4.resolve(options.dir);
|
|
21917
|
+
const roots = [this.root];
|
|
21918
|
+
for (const file2 of findPythonFiles(this.root)) {
|
|
21919
|
+
const source = fs4.readFileSync(file2, "utf8");
|
|
21920
|
+
const root = parsePythonSync(source).rootNode;
|
|
21921
|
+
this.trees.set(file2, root);
|
|
21922
|
+
emitModuleImportFacts(this.db, file2, bindModule(root), { roots });
|
|
21923
|
+
emitValueFacts(this.db, file2, root);
|
|
21924
|
+
indexFile(file2, root, this.locations);
|
|
21925
|
+
}
|
|
21926
|
+
}
|
|
21927
|
+
/**
|
|
21928
|
+
* The smallest expression on `line` of `file` whose text is exactly
|
|
21929
|
+
* `text`, or null.
|
|
21930
|
+
*/
|
|
21931
|
+
findExpression(file2, line, text) {
|
|
21932
|
+
const root = this.rootOf(file2);
|
|
21933
|
+
if (root === null) {
|
|
21934
|
+
return null;
|
|
21935
|
+
}
|
|
21936
|
+
let found = null;
|
|
21937
|
+
const visit = (node) => {
|
|
21938
|
+
if (node.startPosition.row + 1 === line && node.text === text) {
|
|
21939
|
+
if (found === null || width(node) <= width(found)) {
|
|
21940
|
+
found = node;
|
|
21941
|
+
}
|
|
21942
|
+
}
|
|
21943
|
+
for (const child of namedChildrenOf(node)) {
|
|
21944
|
+
visit(child);
|
|
21945
|
+
}
|
|
21946
|
+
};
|
|
21947
|
+
visit(root);
|
|
21948
|
+
return found === null ? null : { file: this.pathOf(file2), node: found };
|
|
21949
|
+
}
|
|
21950
|
+
/**
|
|
21951
|
+
* The callee of the call written as `calleeText` between `startLine`
|
|
21952
|
+
* and `endLine` of `file`, or null.
|
|
21953
|
+
*/
|
|
21954
|
+
findCallee(file2, startLine, endLine, calleeText2) {
|
|
21955
|
+
const root = this.rootOf(file2);
|
|
21956
|
+
if (root === null) {
|
|
21957
|
+
return null;
|
|
21958
|
+
}
|
|
21959
|
+
let found = null;
|
|
21960
|
+
const visit = (node) => {
|
|
21961
|
+
if (node.type === "call") {
|
|
21962
|
+
const line = node.startPosition.row + 1;
|
|
21963
|
+
const callee = field(node, "function");
|
|
21964
|
+
if (line >= startLine && line <= endLine && callee !== null && callee.text === calleeText2) {
|
|
21965
|
+
found = callee;
|
|
21966
|
+
}
|
|
21967
|
+
}
|
|
21968
|
+
for (const child of namedChildrenOf(node)) {
|
|
21969
|
+
visit(child);
|
|
21970
|
+
}
|
|
21971
|
+
};
|
|
21972
|
+
visit(root);
|
|
21973
|
+
return found === null ? null : { file: this.pathOf(file2), node: found };
|
|
21974
|
+
}
|
|
21975
|
+
/**
|
|
21976
|
+
* Why `value` resolves to what it does: the witness proof, flattened
|
|
21977
|
+
* to the chain and rendered. Null when the value does not resolve,
|
|
21978
|
+
* which the caller says in its own words.
|
|
21979
|
+
*/
|
|
21980
|
+
explain(value, options = {}) {
|
|
21981
|
+
return explainResolvedKey({
|
|
21982
|
+
db: this.db,
|
|
21983
|
+
rules: RESOLUTION_RULES,
|
|
21984
|
+
key: readKey(value.file, value.node, enclosingFunction(value.node)),
|
|
21985
|
+
locate: (key) => this.locate(key),
|
|
21986
|
+
displayPath: (key) => this.displayPath(key),
|
|
21987
|
+
...options
|
|
21988
|
+
});
|
|
21989
|
+
}
|
|
21990
|
+
locate(key) {
|
|
21991
|
+
const located = this.locations.get(key);
|
|
21992
|
+
if (located === void 0) {
|
|
21993
|
+
return null;
|
|
21994
|
+
}
|
|
21995
|
+
return {
|
|
21996
|
+
name: displayNameOf(located.node),
|
|
21997
|
+
file: this.displayPath(located.file),
|
|
21998
|
+
line: located.node.startPosition.row + 1
|
|
21999
|
+
};
|
|
22000
|
+
}
|
|
22001
|
+
rootOf(file2) {
|
|
22002
|
+
return this.trees.get(this.pathOf(file2)) ?? null;
|
|
22003
|
+
}
|
|
22004
|
+
pathOf(file2) {
|
|
22005
|
+
return path4.isAbsolute(file2) ? file2 : path4.resolve(this.root, file2);
|
|
22006
|
+
}
|
|
22007
|
+
/** A file path said relative to the root, or as given when it is outside the root. */
|
|
22008
|
+
displayPath(key) {
|
|
22009
|
+
if (key.startsWith(this.root)) {
|
|
22010
|
+
return path4.relative(this.root, key);
|
|
22011
|
+
}
|
|
22012
|
+
return key;
|
|
22013
|
+
}
|
|
22014
|
+
};
|
|
22015
|
+
|
|
22016
|
+
// src/stubEvidence.ts
|
|
22017
|
+
import fs5 from "fs";
|
|
22018
|
+
import path5 from "path";
|
|
22019
|
+
function importsSubmoduleOf(module, packageName) {
|
|
22020
|
+
return module === packageName || module.startsWith(`${packageName}.`);
|
|
22021
|
+
}
|
|
22022
|
+
function importStatementModules(stmt) {
|
|
22023
|
+
const modules = [];
|
|
22024
|
+
for (const nameNode of fields(stmt, "name")) {
|
|
22025
|
+
if (nameNode.type === "aliased_import") {
|
|
22026
|
+
const dotted = field(nameNode, "name");
|
|
22027
|
+
if (dotted !== null) {
|
|
22028
|
+
modules.push(dotted.text);
|
|
22029
|
+
}
|
|
20486
22030
|
continue;
|
|
20487
22031
|
}
|
|
20488
|
-
if (
|
|
20489
|
-
|
|
22032
|
+
if (nameNode.type === "dotted_name") {
|
|
22033
|
+
modules.push(nameNode.text);
|
|
20490
22034
|
}
|
|
20491
|
-
callsUnder(child, found);
|
|
20492
22035
|
}
|
|
20493
|
-
return
|
|
22036
|
+
return modules;
|
|
20494
22037
|
}
|
|
20495
|
-
|
|
20496
|
-
|
|
20497
|
-
|
|
20498
|
-
[
|
|
20499
|
-
|
|
20500
|
-
|
|
20501
|
-
|
|
20502
|
-
|
|
20503
|
-
|
|
20504
|
-
|
|
20505
|
-
|
|
20506
|
-
|
|
20507
|
-
|
|
20508
|
-
for (const node of definitions.values()) {
|
|
20509
|
-
const name = field(node, "name");
|
|
20510
|
-
if (name === null) {
|
|
22038
|
+
function importFromStatementEntries(stmt) {
|
|
22039
|
+
const moduleNode = field(stmt, "module_name");
|
|
22040
|
+
if (moduleNode === null || moduleNode.type === "relative_import") {
|
|
22041
|
+
return [];
|
|
22042
|
+
}
|
|
22043
|
+
const module = moduleNode.text;
|
|
22044
|
+
const entries = [];
|
|
22045
|
+
for (const nameNode of fields(stmt, "name")) {
|
|
22046
|
+
if (nameNode.type === "aliased_import") {
|
|
22047
|
+
const original = field(nameNode, "name");
|
|
22048
|
+
if (original !== null) {
|
|
22049
|
+
entries.push({ module, name: original.text });
|
|
22050
|
+
}
|
|
20511
22051
|
continue;
|
|
20512
22052
|
}
|
|
20513
|
-
|
|
20514
|
-
|
|
22053
|
+
if (nameNode.type === "dotted_name") {
|
|
22054
|
+
entries.push({ module, name: nameNode.text });
|
|
20515
22055
|
}
|
|
20516
22056
|
}
|
|
20517
|
-
|
|
20518
|
-
|
|
22057
|
+
return entries;
|
|
22058
|
+
}
|
|
22059
|
+
function importSitesIn(root) {
|
|
22060
|
+
const found = [];
|
|
22061
|
+
const visit = (node) => {
|
|
22062
|
+
if (node.type === "import_statement") {
|
|
22063
|
+
const line = rangeOf(node).start;
|
|
22064
|
+
for (const module of importStatementModules(node)) {
|
|
22065
|
+
found.push({ module, name: null, line });
|
|
22066
|
+
}
|
|
22067
|
+
} else if (node.type === "import_from_statement") {
|
|
22068
|
+
const line = rangeOf(node).start;
|
|
22069
|
+
for (const entry of importFromStatementEntries(node)) {
|
|
22070
|
+
found.push({ module: entry.module, name: entry.name, line });
|
|
22071
|
+
}
|
|
22072
|
+
}
|
|
22073
|
+
for (const child of node.namedChildren) {
|
|
22074
|
+
if (child !== null) {
|
|
22075
|
+
visit(child);
|
|
22076
|
+
}
|
|
22077
|
+
}
|
|
22078
|
+
};
|
|
22079
|
+
visit(root);
|
|
22080
|
+
return found;
|
|
22081
|
+
}
|
|
22082
|
+
async function pythonImportEvidence(options) {
|
|
22083
|
+
const byModule = /* @__PURE__ */ new Map();
|
|
22084
|
+
for (const file2 of findPythonFiles(options.directory)) {
|
|
22085
|
+
const source = fs5.readFileSync(file2, "utf8");
|
|
22086
|
+
const tree = await parsePython(source);
|
|
22087
|
+
const displayPath = path5.relative(options.directory, file2);
|
|
22088
|
+
for (const site of importSitesIn(tree.rootNode)) {
|
|
22089
|
+
if (!importsSubmoduleOf(site.module, options.packageName)) {
|
|
22090
|
+
continue;
|
|
22091
|
+
}
|
|
22092
|
+
const sites = byModule.get(site.module) ?? [];
|
|
22093
|
+
sites.push({ name: site.name, file: displayPath, line: site.line });
|
|
22094
|
+
byModule.set(site.module, sites);
|
|
22095
|
+
}
|
|
20519
22096
|
}
|
|
20520
|
-
|
|
20521
|
-
return new Set(db.facts("reachesStorage").map((row) => String(row[0])));
|
|
22097
|
+
return [...byModule.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([module, sites]) => ({ module, sites }));
|
|
20522
22098
|
}
|
|
20523
22099
|
export {
|
|
22100
|
+
ADAPTER_VERSION,
|
|
22101
|
+
PythonWhySession,
|
|
20524
22102
|
annotationToShape,
|
|
20525
22103
|
bindModule,
|
|
20526
22104
|
buildRouterIndex,
|
|
@@ -20535,6 +22113,10 @@ export {
|
|
|
20535
22113
|
nodeId,
|
|
20536
22114
|
objectReturnedBy,
|
|
20537
22115
|
parsePython,
|
|
22116
|
+
parsePythonSync,
|
|
22117
|
+
preloadPythonGrammar,
|
|
22118
|
+
pythonImportEvidence,
|
|
22119
|
+
readKey,
|
|
20538
22120
|
resolveAbsoluteModule,
|
|
20539
22121
|
resolveCalls,
|
|
20540
22122
|
resolveModule,
|