scip-query 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-26DOJ63W.js +161 -0
- package/dist/{chunk-PMJKOXOT.js → chunk-4JCSOF2O.js} +2 -2
- package/dist/{chunk-AKMBBKWV.js → chunk-5OMVSV6E.js} +12 -4
- package/dist/{chunk-R56FJU3E.js → chunk-7KIMF5PV.js} +2 -2
- package/dist/chunk-AXQKUYKF.js +1442 -0
- package/dist/chunk-C7H5WBTJ.js +46 -0
- package/dist/{chunk-AMNISGYR.js → chunk-CHDJXYBG.js} +2 -27
- package/dist/{chunk-GPJVPT3U.js → chunk-CPVAQJEC.js} +12 -4
- package/dist/{chunk-75RQSBTK.js → chunk-DH7G3DDV.js} +2 -2
- package/dist/{chunk-LTJC5ZQL.js → chunk-EOROMIFO.js} +13 -5
- package/dist/{chunk-BFLULBEU.js → chunk-EPWLXXBL.js} +2 -2
- package/dist/{chunk-MVH45PYK.js → chunk-FYYOWQXK.js} +12 -12
- package/dist/chunk-GEXE2T6I.js +87 -0
- package/dist/{chunk-IXPHLF6K.js → chunk-GJDHTTR2.js} +10 -3
- package/dist/chunk-GSH2FPKV.js +87 -0
- package/dist/{chunk-4ACRRQC4.js → chunk-HJZUSUPU.js} +2 -2
- package/dist/{chunk-M3NPW3FC.js → chunk-HLKAFWWJ.js} +81 -2
- package/dist/{chunk-CU62ZDHI.js → chunk-HLUS2HEB.js} +2 -2
- package/dist/{chunk-RFMT7UAZ.js → chunk-J3JSOSUO.js} +8 -5
- package/dist/{chunk-HDSRORNV.js → chunk-KKCHYLVI.js} +17 -11
- package/dist/{chunk-Y3M323OX.js → chunk-LFJQVJYJ.js} +2 -2
- package/dist/{chunk-6WVR5K46.js → chunk-LQJUPXQY.js} +3 -3
- package/dist/{chunk-ITZ3DDOG.js → chunk-MPGIHELS.js} +18 -3
- package/dist/{chunk-N4C3H7LH.js → chunk-NFS5W3PP.js} +2 -2
- package/dist/{chunk-Y4JFVQ7C.js → chunk-O7Q7FDUJ.js} +21 -13
- package/dist/{chunk-H6WCPKCX.js → chunk-OIDHN6GD.js} +2 -2
- package/dist/{chunk-4BQFSNFI.js → chunk-P3E6L7KW.js} +2 -2
- package/dist/{chunk-ORINICIZ.js → chunk-P4WO3BBW.js} +2 -2
- package/dist/{chunk-M4QGEKKD.js → chunk-SMDCNPMK.js} +8 -3
- package/dist/{chunk-6QSHLFSL.js → chunk-UGQKAVCD.js} +2 -2
- package/dist/{chunk-HMYJJ3HY.js → chunk-UQEQ6AHX.js} +3 -3
- package/dist/{chunk-WVK7AASK.js → chunk-VIYSWZCO.js} +2 -2
- package/dist/chunk-VJJKSGIX.js +121 -0
- package/dist/{chunk-N5KEREIA.js → chunk-VT4JBH6L.js} +19 -7
- package/dist/{chunk-R2I3M5B4.js → chunk-WGAD3GNR.js} +2 -2
- package/dist/{chunk-IJKLB2JW.js → chunk-YDBXNPYU.js} +2 -2
- package/dist/{chunk-VO4QI3LS.js → chunk-YY4QGUQ5.js} +2 -2
- package/dist/{chunk-3566TKJ5.js → chunk-ZEUCXQBN.js} +2 -2
- package/dist/cli.js +2310 -1351
- package/dist/{db-BHYam4BK.d.ts → db-ShvwGDKf.d.ts} +6 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +38 -38
- package/dist/queries/affected.d.ts +1 -1
- package/dist/queries/affected.js +2 -2
- package/dist/queries/bottlenecks.d.ts +1 -1
- package/dist/queries/by-kind.d.ts +1 -1
- package/dist/queries/by-kind.js +1 -1
- package/dist/queries/call-graph.d.ts +1 -1
- package/dist/queries/call-graph.js +2 -2
- package/dist/queries/change-surface.d.ts +1 -1
- package/dist/queries/change-surface.js +2 -1
- package/dist/queries/code.d.ts +1 -1
- package/dist/queries/code.js +2 -2
- package/dist/queries/complexity-hotspots.d.ts +1 -1
- package/dist/queries/complexity-hotspots.js +2 -2
- package/dist/queries/complexity.d.ts +1 -1
- package/dist/queries/complexity.js +2 -2
- package/dist/queries/convergence.d.ts +1 -1
- package/dist/queries/convergence.js +2 -2
- package/dist/queries/coupling.d.ts +1 -1
- package/dist/queries/coupling.js +3 -1
- package/dist/queries/cycles.d.ts +1 -1
- package/dist/queries/cycles.js +2 -2
- package/dist/queries/dataflow.d.ts +1 -1
- package/dist/queries/dataflow.js +2 -2
- package/dist/queries/dead.d.ts +1 -1
- package/dist/queries/dead.js +3 -3
- package/dist/queries/deep-chains.d.ts +1 -1
- package/dist/queries/deep-chains.js +2 -2
- package/dist/queries/deps.d.ts +1 -1
- package/dist/queries/deps.js +3 -1
- package/dist/queries/diff-impact.d.ts +1 -1
- package/dist/queries/doc-coverage.d.ts +1 -1
- package/dist/queries/drift.d.ts +1 -1
- package/dist/queries/drift.js +2 -2
- package/dist/queries/extract-candidates.d.ts +1 -1
- package/dist/queries/extract-candidates.js +2 -2
- package/dist/queries/fan.d.ts +1 -1
- package/dist/queries/fan.js +2 -1
- package/dist/queries/files.d.ts +1 -1
- package/dist/queries/health.d.ts +1 -1
- package/dist/queries/health.js +13 -13
- package/dist/queries/hierarchy.d.ts +1 -1
- package/dist/queries/hierarchy.js +2 -2
- package/dist/queries/hotspots.d.ts +1 -1
- package/dist/queries/imports.d.ts +1 -1
- package/dist/queries/imports.js +2 -2
- package/dist/queries/index.d.ts +1 -1
- package/dist/queries/index.js +38 -38
- package/dist/queries/isolated.d.ts +1 -1
- package/dist/queries/isolated.js +3 -3
- package/dist/queries/members.d.ts +1 -1
- package/dist/queries/members.js +2 -2
- package/dist/queries/methods.d.ts +1 -1
- package/dist/queries/outline.d.ts +1 -1
- package/dist/queries/outline.js +2 -1
- package/dist/queries/passthrough-candidates.d.ts +1 -1
- package/dist/queries/passthrough-candidates.js +2 -2
- package/dist/queries/redundant-reexports.d.ts +1 -1
- package/dist/queries/redundant-reexports.js +3 -3
- package/dist/queries/refs.d.ts +1 -1
- package/dist/queries/refs.js +3 -1
- package/dist/queries/similar-chains.d.ts +1 -1
- package/dist/queries/similar-chains.js +2 -2
- package/dist/queries/similar-files.d.ts +1 -1
- package/dist/queries/similar-files.js +2 -2
- package/dist/queries/similar-signatures.d.ts +1 -1
- package/dist/queries/similar.d.ts +1 -1
- package/dist/queries/similar.js +2 -2
- package/dist/queries/slice.d.ts +2 -2
- package/dist/queries/slice.js +2 -2
- package/dist/queries/stale-abstractions.d.ts +1 -1
- package/dist/queries/stale-abstractions.js +2 -2
- package/dist/queries/stats.d.ts +1 -1
- package/dist/queries/surface.d.ts +1 -1
- package/dist/queries/surface.js +2 -1
- package/dist/queries/symbols.d.ts +1 -1
- package/dist/queries/symbols.js +2 -1
- package/dist/queries/system.d.ts +1 -1
- package/dist/queries/system.js +2 -1
- package/dist/queries/trace.d.ts +1 -1
- package/dist/queries/trace.js +2 -2
- package/dist/queries/wrapper-candidates.d.ts +1 -1
- package/dist/queries/wrapper-candidates.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-34JPTNRN.js +0 -601
- package/dist/chunk-7JFZSOJ7.js +0 -103
- package/dist/chunk-DY4AFG2W.js +0 -48
- package/dist/chunk-LLMPAG56.js +0 -221
- package/dist/chunk-NVIIM34O.js +0 -65
- package/dist/chunk-YAFWL3RA.js +0 -55
|
@@ -0,0 +1,1442 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isFunctionLikeSymbol,
|
|
3
|
+
isModuleLikeSymbol,
|
|
4
|
+
leafName,
|
|
5
|
+
leafSuffix,
|
|
6
|
+
parseSymbol,
|
|
7
|
+
shortenSymbol
|
|
8
|
+
} from "./chunk-QIXNAB5K.js";
|
|
9
|
+
|
|
10
|
+
// src/source-analysis.ts
|
|
11
|
+
import {
|
|
12
|
+
existsSync,
|
|
13
|
+
readFileSync
|
|
14
|
+
} from "fs";
|
|
15
|
+
import {
|
|
16
|
+
dirname,
|
|
17
|
+
extname,
|
|
18
|
+
join,
|
|
19
|
+
relative,
|
|
20
|
+
resolve
|
|
21
|
+
} from "path";
|
|
22
|
+
var SOURCE_IMPORT_CACHE = /* @__PURE__ */ new WeakMap();
|
|
23
|
+
var SOURCE_TEXT_CACHE = /* @__PURE__ */ new WeakMap();
|
|
24
|
+
var SOURCE_CALL_CACHE = /* @__PURE__ */ new WeakMap();
|
|
25
|
+
var SOURCE_BINDING_CACHE = /* @__PURE__ */ new WeakMap();
|
|
26
|
+
var INDEXED_PATH_CACHE = /* @__PURE__ */ new WeakMap();
|
|
27
|
+
var SOURCE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
28
|
+
var PYTHON_SOURCE_EXTENSIONS = [".py", ".pyi"];
|
|
29
|
+
function getSourceImports(db, relativePath) {
|
|
30
|
+
const cache = getCachedMap(SOURCE_IMPORT_CACHE, db);
|
|
31
|
+
const normalized = normalizePath(relativePath);
|
|
32
|
+
const cached = cache.get(normalized);
|
|
33
|
+
if (cached) {
|
|
34
|
+
return cached;
|
|
35
|
+
}
|
|
36
|
+
const fullPath = join(db.config.projectRoot, normalized);
|
|
37
|
+
if (!existsSync(fullPath)) {
|
|
38
|
+
cache.set(normalized, []);
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
const source = readFileSync(fullPath, "utf-8");
|
|
42
|
+
const parsed = isPythonSourcePath(normalized) ? parsePythonImports(db, normalized, source) : parseJavaScriptImports(db, normalized, source);
|
|
43
|
+
cache.set(normalized, parsed);
|
|
44
|
+
return parsed;
|
|
45
|
+
}
|
|
46
|
+
function getSourceCalls(db, relativePath, opts = {}) {
|
|
47
|
+
const normalized = normalizePath(relativePath);
|
|
48
|
+
if (!isPythonSourcePath(normalized) && !isJavaScriptSourcePath(normalized)) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
const cache = getCachedMap(SOURCE_CALL_CACHE, db);
|
|
52
|
+
const key = `${normalized}:${opts.startLine ?? 0}:${opts.endLine ?? Number.MAX_SAFE_INTEGER}`;
|
|
53
|
+
const cached = cache.get(key);
|
|
54
|
+
if (cached) {
|
|
55
|
+
return cached;
|
|
56
|
+
}
|
|
57
|
+
const source = getSourceText(db, normalized);
|
|
58
|
+
if (!source) {
|
|
59
|
+
cache.set(key, []);
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
const lines = source.split("\n");
|
|
63
|
+
const startLine = Math.max(0, opts.startLine ?? 0);
|
|
64
|
+
const endLine = Math.min(lines.length - 1, opts.endLine ?? lines.length - 1);
|
|
65
|
+
const scopedLines = lines.slice(startLine, endLine + 1);
|
|
66
|
+
const calls = isPythonSourcePath(normalized) ? parsePythonCalls(scopedLines, startLine) : parseJavaScriptCalls(scopedLines, startLine);
|
|
67
|
+
cache.set(key, calls);
|
|
68
|
+
return calls;
|
|
69
|
+
}
|
|
70
|
+
function getSourceConstructorBindings(db, relativePath, opts = {}) {
|
|
71
|
+
const normalized = normalizePath(relativePath);
|
|
72
|
+
if (!isPythonSourcePath(normalized) && !isJavaScriptSourcePath(normalized)) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
const cache = getCachedMap(SOURCE_BINDING_CACHE, db);
|
|
76
|
+
const key = `${normalized}:${opts.startLine ?? 0}:${opts.endLine ?? Number.MAX_SAFE_INTEGER}`;
|
|
77
|
+
const cached = cache.get(key);
|
|
78
|
+
if (cached) {
|
|
79
|
+
return cached;
|
|
80
|
+
}
|
|
81
|
+
const source = getSourceText(db, normalized);
|
|
82
|
+
if (!source) {
|
|
83
|
+
cache.set(key, []);
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
const lines = source.split("\n");
|
|
87
|
+
const startLine = Math.max(0, opts.startLine ?? 0);
|
|
88
|
+
const endLine = Math.min(lines.length - 1, opts.endLine ?? lines.length - 1);
|
|
89
|
+
const scopedLines = lines.slice(startLine, endLine + 1);
|
|
90
|
+
const bindings = isPythonSourcePath(normalized) ? parsePythonConstructorBindings(scopedLines) : parseJavaScriptConstructorBindings(scopedLines);
|
|
91
|
+
cache.set(key, bindings);
|
|
92
|
+
return bindings;
|
|
93
|
+
}
|
|
94
|
+
function findIdentifierLines(db, relativePath, identifier, opts = {}) {
|
|
95
|
+
if (!identifier) {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
const source = getSourceText(db, normalizePath(relativePath));
|
|
99
|
+
if (!source) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
const lines = stripCommentsAndStrings(source).split("\n");
|
|
103
|
+
const regex = new RegExp(`\\b${escapeRegex(identifier)}\\b`);
|
|
104
|
+
const results = [];
|
|
105
|
+
for (let line = 0; line < lines.length; line++) {
|
|
106
|
+
if (typeof opts.excludeStartLine === "number" && typeof opts.excludeEndLine === "number" && line >= opts.excludeStartLine && line <= opts.excludeEndLine) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (regex.test(lines[line] ?? "")) {
|
|
110
|
+
results.push(line);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return results;
|
|
114
|
+
}
|
|
115
|
+
function parseJavaScriptImports(db, importerPath, source) {
|
|
116
|
+
return parseJavaScriptImportStatements(source).flatMap((statement) => parseJavaScriptImportStatement(
|
|
117
|
+
db,
|
|
118
|
+
importerPath,
|
|
119
|
+
statement.clause,
|
|
120
|
+
statement.specifier,
|
|
121
|
+
statement.start,
|
|
122
|
+
statement.end,
|
|
123
|
+
source
|
|
124
|
+
));
|
|
125
|
+
}
|
|
126
|
+
function parseJavaScriptImportStatements(source) {
|
|
127
|
+
const statements = [];
|
|
128
|
+
const importFromRegex = /^[ \t]*import\s+([\s\S]*?)\s+from\s+['"]([^'"]+)['"]\s*;?/gm;
|
|
129
|
+
for (const match of source.matchAll(importFromRegex)) {
|
|
130
|
+
const full = match[0];
|
|
131
|
+
const clause = match[1];
|
|
132
|
+
const specifier = match[2];
|
|
133
|
+
if (!full || !specifier || typeof match.index !== "number") continue;
|
|
134
|
+
statements.push({
|
|
135
|
+
clause,
|
|
136
|
+
specifier,
|
|
137
|
+
start: match.index,
|
|
138
|
+
end: match.index + full.length
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
const sideEffectRegex = /^[ \t]*import\s+['"]([^'"]+)['"]\s*;?/gm;
|
|
142
|
+
for (const match of source.matchAll(sideEffectRegex)) {
|
|
143
|
+
const full = match[0];
|
|
144
|
+
const specifier = match[1];
|
|
145
|
+
if (!full || !specifier || typeof match.index !== "number") continue;
|
|
146
|
+
statements.push({
|
|
147
|
+
clause: null,
|
|
148
|
+
specifier,
|
|
149
|
+
start: match.index,
|
|
150
|
+
end: match.index + full.length
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return statements.sort((a, b) => a.start - b.start);
|
|
154
|
+
}
|
|
155
|
+
function parseJavaScriptImportStatement(db, importerPath, clause, specifier, start, end, source) {
|
|
156
|
+
const resolvedSource = resolveImportPath(db, importerPath, specifier);
|
|
157
|
+
const body = buildUsageBody(source, start, end);
|
|
158
|
+
if (!clause) {
|
|
159
|
+
return [{
|
|
160
|
+
importedName: "*",
|
|
161
|
+
localName: null,
|
|
162
|
+
sourcePath: resolvedSource,
|
|
163
|
+
kind: "side-effect",
|
|
164
|
+
used: true,
|
|
165
|
+
usedMembers: []
|
|
166
|
+
}];
|
|
167
|
+
}
|
|
168
|
+
const bindings = parseImportClause(clause).map((binding) => ({
|
|
169
|
+
...binding,
|
|
170
|
+
sourcePath: resolvedSource
|
|
171
|
+
}));
|
|
172
|
+
return bindings.map((binding) => {
|
|
173
|
+
if (binding.kind === "namespace") {
|
|
174
|
+
const usedMembers = collectNamespaceMembers(body, binding.localName);
|
|
175
|
+
return {
|
|
176
|
+
...binding,
|
|
177
|
+
used: usedMembers.length > 0 || hasIdentifierUsage(body, binding.localName),
|
|
178
|
+
usedMembers
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (binding.kind === "side-effect") {
|
|
182
|
+
return { ...binding, used: true, usedMembers: [] };
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
...binding,
|
|
186
|
+
used: binding.localName ? hasIdentifierUsage(body, binding.localName) : false,
|
|
187
|
+
usedMembers: []
|
|
188
|
+
};
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
function parsePythonCalls(lines, baseLine) {
|
|
192
|
+
const calls = [];
|
|
193
|
+
const controlKeywords = /* @__PURE__ */ new Set([
|
|
194
|
+
"if",
|
|
195
|
+
"for",
|
|
196
|
+
"while",
|
|
197
|
+
"with",
|
|
198
|
+
"except",
|
|
199
|
+
"elif",
|
|
200
|
+
"return",
|
|
201
|
+
"yield",
|
|
202
|
+
"assert",
|
|
203
|
+
"raise",
|
|
204
|
+
"lambda",
|
|
205
|
+
"class",
|
|
206
|
+
"def"
|
|
207
|
+
]);
|
|
208
|
+
for (let index = 0; index < lines.length; index++) {
|
|
209
|
+
const rawLine = lines[index] ?? "";
|
|
210
|
+
const stripped = stripCommentsAndStrings(rawLine);
|
|
211
|
+
if (!stripped.trim()) continue;
|
|
212
|
+
const attributeMatches = [...stripped.matchAll(/\b([A-Za-z_][\w]*)\s*\.\s*([A-Za-z_][\w]*)\s*\(/g)];
|
|
213
|
+
const attributeRanges = attributeMatches.map((match) => ({
|
|
214
|
+
start: match.index ?? -1,
|
|
215
|
+
end: (match.index ?? -1) + match[0].length
|
|
216
|
+
}));
|
|
217
|
+
for (const match of attributeMatches) {
|
|
218
|
+
const receiverName = match[1];
|
|
219
|
+
const calleeName = match[2];
|
|
220
|
+
if (!receiverName || !calleeName) continue;
|
|
221
|
+
calls.push({
|
|
222
|
+
receiverName,
|
|
223
|
+
calleeName,
|
|
224
|
+
line: baseLine + index
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
for (const match of stripped.matchAll(/\b([A-Za-z_][\w]*)\s*\(/g)) {
|
|
228
|
+
const calleeName = match[1];
|
|
229
|
+
const start = match.index ?? -1;
|
|
230
|
+
if (!calleeName || start < 0) continue;
|
|
231
|
+
if (controlKeywords.has(calleeName)) continue;
|
|
232
|
+
if (attributeRanges.some((range) => start >= range.start && start < range.end)) continue;
|
|
233
|
+
const prefix = stripped.slice(0, start).trimEnd();
|
|
234
|
+
if (prefix.endsWith("def") || prefix.endsWith("class") || prefix.endsWith("async def")) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
calls.push({
|
|
238
|
+
receiverName: null,
|
|
239
|
+
calleeName,
|
|
240
|
+
line: baseLine + index
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return calls;
|
|
245
|
+
}
|
|
246
|
+
function parseJavaScriptCalls(lines, baseLine) {
|
|
247
|
+
const calls = [];
|
|
248
|
+
const controlKeywords = /* @__PURE__ */ new Set([
|
|
249
|
+
"if",
|
|
250
|
+
"for",
|
|
251
|
+
"while",
|
|
252
|
+
"switch",
|
|
253
|
+
"catch",
|
|
254
|
+
"function",
|
|
255
|
+
"class",
|
|
256
|
+
"return",
|
|
257
|
+
"typeof",
|
|
258
|
+
"import"
|
|
259
|
+
]);
|
|
260
|
+
for (let index = 0; index < lines.length; index++) {
|
|
261
|
+
const stripped = stripCommentsAndStrings(lines[index] ?? "");
|
|
262
|
+
if (!stripped.trim()) continue;
|
|
263
|
+
const attributeMatches = [
|
|
264
|
+
...stripped.matchAll(/\b([A-Za-z_$][\w$]*)\s*(?:\?\.|\.)\s*([A-Za-z_$][\w$]*)\s*\(/g),
|
|
265
|
+
...stripped.matchAll(/\bthis\s*(?:\?\.|\.)\s*([A-Za-z_$][\w$]*)\s*\(/g)
|
|
266
|
+
];
|
|
267
|
+
const attributeRanges = attributeMatches.map((match) => ({
|
|
268
|
+
start: match.index ?? -1,
|
|
269
|
+
end: (match.index ?? -1) + match[0].length
|
|
270
|
+
}));
|
|
271
|
+
for (const match of attributeMatches) {
|
|
272
|
+
const receiverName = match[2] ? match[1] : "this";
|
|
273
|
+
const calleeName = match[2] ?? match[1];
|
|
274
|
+
if (!receiverName || !calleeName) continue;
|
|
275
|
+
calls.push({
|
|
276
|
+
receiverName,
|
|
277
|
+
calleeName,
|
|
278
|
+
line: baseLine + index
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
for (const match of stripped.matchAll(/\b([A-Za-z_$][\w$]*)\s*\(/g)) {
|
|
282
|
+
const calleeName = match[1];
|
|
283
|
+
const start = match.index ?? -1;
|
|
284
|
+
if (!calleeName || start < 0) continue;
|
|
285
|
+
if (controlKeywords.has(calleeName)) continue;
|
|
286
|
+
if (attributeRanges.some((range) => start >= range.start && start < range.end)) continue;
|
|
287
|
+
const prefix = stripped.slice(0, start).trimEnd();
|
|
288
|
+
if (prefix.endsWith("function") || prefix.endsWith("class") || prefix.endsWith("new")) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
calls.push({
|
|
292
|
+
receiverName: null,
|
|
293
|
+
calleeName,
|
|
294
|
+
line: baseLine + index
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return calls;
|
|
299
|
+
}
|
|
300
|
+
function parsePythonConstructorBindings(lines) {
|
|
301
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
302
|
+
for (const rawLine of lines) {
|
|
303
|
+
const stripped = stripCommentsAndStrings(rawLine);
|
|
304
|
+
const constructorMatch = stripped.match(/\b([A-Za-z_][\w]*)\s*=\s*([A-Z][\w]*)\s*\(/);
|
|
305
|
+
if (constructorMatch?.[1] && constructorMatch[2]) {
|
|
306
|
+
bindings.set(constructorMatch[1], constructorMatch[2]);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return [...bindings.entries()].map(([localName, typeName]) => ({ localName, typeName }));
|
|
310
|
+
}
|
|
311
|
+
function parseJavaScriptConstructorBindings(lines) {
|
|
312
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
313
|
+
for (const rawLine of lines) {
|
|
314
|
+
const stripped = stripCommentsAndStrings(rawLine);
|
|
315
|
+
for (const match of stripped.matchAll(/\b([A-Za-z_$][\w$]*)\s*:\s*([A-Z][A-Za-z0-9_$]*)\b/g)) {
|
|
316
|
+
const localName = match[1];
|
|
317
|
+
const typeName = match[2];
|
|
318
|
+
if (localName && typeName) {
|
|
319
|
+
bindings.set(localName, typeName);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const constructorMatch = stripped.match(/\b(?:const|let|var)?\s*([A-Za-z_$][\w$]*)\s*(?::\s*[A-Z][A-Za-z0-9_$<> ,]*)?\s*=\s*new\s+([A-Z][A-Za-z0-9_$]*)\s*\(/);
|
|
323
|
+
if (constructorMatch?.[1] && constructorMatch[2]) {
|
|
324
|
+
bindings.set(constructorMatch[1], constructorMatch[2]);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return [...bindings.entries()].map(([localName, typeName]) => ({ localName, typeName }));
|
|
328
|
+
}
|
|
329
|
+
function parsePythonImports(db, importerPath, source) {
|
|
330
|
+
return collectPythonImportStatements(source).flatMap(
|
|
331
|
+
(statement) => parsePythonImportStatement(db, importerPath, statement, source)
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
function collectPythonImportStatements(source) {
|
|
335
|
+
const lines = source.split("\n");
|
|
336
|
+
const statements = [];
|
|
337
|
+
let offset = 0;
|
|
338
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
339
|
+
const line = lines[lineIndex];
|
|
340
|
+
const trimmed = line.trimStart();
|
|
341
|
+
const lineStart = offset;
|
|
342
|
+
offset += line.length + 1;
|
|
343
|
+
if (!trimmed.startsWith("import ") && !trimmed.startsWith("from ")) {
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
let statement = line;
|
|
347
|
+
let statementEnd = lineStart + line.length;
|
|
348
|
+
let balance = pythonParenBalance(line);
|
|
349
|
+
while (lineIndex + 1 < lines.length && (balance > 0 || statement.trimEnd().endsWith("\\"))) {
|
|
350
|
+
lineIndex++;
|
|
351
|
+
const nextLine = lines[lineIndex];
|
|
352
|
+
statement += `
|
|
353
|
+
${nextLine}`;
|
|
354
|
+
statementEnd += 1 + nextLine.length;
|
|
355
|
+
balance += pythonParenBalance(nextLine);
|
|
356
|
+
offset += nextLine.length + 1;
|
|
357
|
+
}
|
|
358
|
+
const parsed = parsePythonStatementHeader(statement);
|
|
359
|
+
if (parsed) {
|
|
360
|
+
statements.push({
|
|
361
|
+
...parsed,
|
|
362
|
+
start: lineStart,
|
|
363
|
+
end: statementEnd
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return statements;
|
|
368
|
+
}
|
|
369
|
+
function parsePythonStatementHeader(statement) {
|
|
370
|
+
const normalized = statement.replace(/\\\s*\n/g, " ").trim();
|
|
371
|
+
if (normalized.startsWith("import ")) {
|
|
372
|
+
return {
|
|
373
|
+
kind: "import",
|
|
374
|
+
module: null,
|
|
375
|
+
clause: normalized.slice("import ".length).trim()
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
const fromMatch = normalized.match(/^from\s+([.\w]+)\s+import\s+([\s\S]+)$/);
|
|
379
|
+
if (!fromMatch) {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
let clause = fromMatch[2].trim();
|
|
383
|
+
if (clause.startsWith("(") && clause.endsWith(")")) {
|
|
384
|
+
clause = clause.slice(1, -1).trim();
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
kind: "from",
|
|
388
|
+
module: fromMatch[1],
|
|
389
|
+
clause
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function parsePythonImportStatement(db, importerPath, statement, source) {
|
|
393
|
+
const body = buildUsageBody(source, statement.start, statement.end);
|
|
394
|
+
const normalizedClause = statement.clause.replace(/\n/g, " ").trim();
|
|
395
|
+
if (statement.kind === "import") {
|
|
396
|
+
return splitTopLevel(normalizedClause).flatMap((entry) => {
|
|
397
|
+
const cleaned = entry.trim().replace(/,$/, "");
|
|
398
|
+
if (!cleaned) return [];
|
|
399
|
+
const [moduleName, alias] = cleaned.split(/\s+as\s+/);
|
|
400
|
+
const importedName = moduleName.trim();
|
|
401
|
+
const localName = (alias ?? importedName.split(".")[0] ?? importedName).trim();
|
|
402
|
+
const sourcePath2 = resolvePythonImportPath(db, importerPath, importedName);
|
|
403
|
+
const usedMembers = collectNamespaceMembers(body, localName);
|
|
404
|
+
return [{
|
|
405
|
+
importedName,
|
|
406
|
+
localName,
|
|
407
|
+
sourcePath: sourcePath2,
|
|
408
|
+
kind: "namespace",
|
|
409
|
+
used: hasIdentifierUsage(body, localName) || usedMembers.length > 0,
|
|
410
|
+
usedMembers
|
|
411
|
+
}];
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
const sourcePath = statement.module ? resolvePythonImportPath(db, importerPath, statement.module) : null;
|
|
415
|
+
const results = [];
|
|
416
|
+
for (const entry of splitTopLevel(normalizedClause)) {
|
|
417
|
+
const cleaned = entry.trim().replace(/,$/, "");
|
|
418
|
+
if (!cleaned) continue;
|
|
419
|
+
if (cleaned === "*") {
|
|
420
|
+
results.push({
|
|
421
|
+
importedName: "*",
|
|
422
|
+
localName: null,
|
|
423
|
+
sourcePath,
|
|
424
|
+
kind: "side-effect",
|
|
425
|
+
used: true,
|
|
426
|
+
usedMembers: []
|
|
427
|
+
});
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
const [importedName, alias] = cleaned.split(/\s+as\s+/);
|
|
431
|
+
const localName = (alias ?? importedName).trim();
|
|
432
|
+
results.push({
|
|
433
|
+
importedName: importedName.trim(),
|
|
434
|
+
localName,
|
|
435
|
+
sourcePath,
|
|
436
|
+
kind: "named",
|
|
437
|
+
used: hasIdentifierUsage(body, localName),
|
|
438
|
+
usedMembers: []
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
return results;
|
|
442
|
+
}
|
|
443
|
+
function parseImportClause(clause) {
|
|
444
|
+
const trimmed = clause.trim().replace(/^type\s+/, "");
|
|
445
|
+
const [first, second] = splitImportClause(trimmed);
|
|
446
|
+
const entries = [];
|
|
447
|
+
if (first) {
|
|
448
|
+
entries.push(...parseImportBinding(first));
|
|
449
|
+
}
|
|
450
|
+
if (second) {
|
|
451
|
+
entries.push(...parseImportBinding(second));
|
|
452
|
+
}
|
|
453
|
+
return entries;
|
|
454
|
+
}
|
|
455
|
+
function parseImportBinding(binding) {
|
|
456
|
+
const trimmed = binding.trim();
|
|
457
|
+
if (!trimmed) return [];
|
|
458
|
+
if (trimmed.startsWith("{")) {
|
|
459
|
+
const inner = trimmed.slice(1, -1).trim();
|
|
460
|
+
if (!inner) return [];
|
|
461
|
+
return splitTopLevel(inner).map((entry) => {
|
|
462
|
+
const cleaned = entry.trim().replace(/^type\s+/, "");
|
|
463
|
+
const [importedName, alias] = cleaned.split(/\s+as\s+/);
|
|
464
|
+
return {
|
|
465
|
+
importedName: importedName.trim(),
|
|
466
|
+
localName: (alias ?? importedName).trim(),
|
|
467
|
+
kind: "named"
|
|
468
|
+
};
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
if (trimmed.startsWith("* as ")) {
|
|
472
|
+
return [{
|
|
473
|
+
importedName: "*",
|
|
474
|
+
localName: trimmed.slice(5).trim(),
|
|
475
|
+
kind: "namespace"
|
|
476
|
+
}];
|
|
477
|
+
}
|
|
478
|
+
return [{
|
|
479
|
+
importedName: "default",
|
|
480
|
+
localName: trimmed,
|
|
481
|
+
kind: "default"
|
|
482
|
+
}];
|
|
483
|
+
}
|
|
484
|
+
function splitImportClause(clause) {
|
|
485
|
+
let depth = 0;
|
|
486
|
+
for (let i = 0; i < clause.length; i++) {
|
|
487
|
+
const char = clause[i];
|
|
488
|
+
if (char === "{") depth++;
|
|
489
|
+
if (char === "}") depth--;
|
|
490
|
+
if (char === "," && depth === 0) {
|
|
491
|
+
return [clause.slice(0, i).trim(), clause.slice(i + 1).trim()];
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return [clause.trim(), null];
|
|
495
|
+
}
|
|
496
|
+
function splitTopLevel(input) {
|
|
497
|
+
const parts = [];
|
|
498
|
+
let depth = 0;
|
|
499
|
+
let start = 0;
|
|
500
|
+
for (let i = 0; i < input.length; i++) {
|
|
501
|
+
const char = input[i];
|
|
502
|
+
if (char === "{" || char === "[" || char === "(") depth++;
|
|
503
|
+
if (char === "}" || char === "]" || char === ")") depth--;
|
|
504
|
+
if (char === "," && depth === 0) {
|
|
505
|
+
parts.push(input.slice(start, i));
|
|
506
|
+
start = i + 1;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
parts.push(input.slice(start));
|
|
510
|
+
return parts;
|
|
511
|
+
}
|
|
512
|
+
function buildUsageBody(source, start, end) {
|
|
513
|
+
const masked = `${source.slice(0, start)}${" ".repeat(end - start)}${source.slice(end)}`;
|
|
514
|
+
return stripCommentsAndStrings(masked);
|
|
515
|
+
}
|
|
516
|
+
function stripCommentsAndStrings(source) {
|
|
517
|
+
return source.replace(/'''[\s\S]*?'''/g, maskPreservingLines).replace(/"""[\s\S]*?"""/g, maskPreservingLines).replace(/#.*$/gm, maskPreservingLines).replace(/\/\/.*$/gm, maskPreservingLines).replace(/\/\*[\s\S]*?\*\//g, maskPreservingLines).replace(/`(?:\\[\s\S]|[^`])*`/g, maskPreservingLines).replace(/'(?:\\.|[^'\\\r\n])*'/g, maskPreservingLines).replace(/"(?:\\.|[^"\\\r\n])*"/g, maskPreservingLines);
|
|
518
|
+
}
|
|
519
|
+
function maskPreservingLines(segment) {
|
|
520
|
+
return segment.replace(/[^\r\n]/g, " ");
|
|
521
|
+
}
|
|
522
|
+
function hasIdentifierUsage(body, identifier) {
|
|
523
|
+
return new RegExp(`\\b${escapeRegex(identifier)}\\b`, "m").test(body);
|
|
524
|
+
}
|
|
525
|
+
function collectNamespaceMembers(body, namespaceName) {
|
|
526
|
+
const members = /* @__PURE__ */ new Set();
|
|
527
|
+
const regex = new RegExp(`\\b${escapeRegex(namespaceName)}\\s*\\.\\s*([A-Za-z_$][\\w$]*)`, "g");
|
|
528
|
+
for (const match of body.matchAll(regex)) {
|
|
529
|
+
const member = match[1];
|
|
530
|
+
if (member) {
|
|
531
|
+
members.add(member);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return [...members];
|
|
535
|
+
}
|
|
536
|
+
function resolveImportPath(db, importerPath, specifier) {
|
|
537
|
+
if (isPythonSourcePath(importerPath)) {
|
|
538
|
+
return resolvePythonImportPath(db, importerPath, specifier);
|
|
539
|
+
}
|
|
540
|
+
return resolveJavaScriptImportPath(db, importerPath, specifier);
|
|
541
|
+
}
|
|
542
|
+
function resolveJavaScriptImportPath(db, importerPath, specifier) {
|
|
543
|
+
if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
const importerDir = dirname(join(db.config.projectRoot, importerPath));
|
|
547
|
+
const absolute = resolve(importerDir, specifier);
|
|
548
|
+
const indexedPaths = getIndexedPaths(db);
|
|
549
|
+
for (const candidate of candidateImportPaths(absolute)) {
|
|
550
|
+
const relativeCandidate = normalizePath(relative(db.config.projectRoot, candidate));
|
|
551
|
+
if (indexedPaths.has(relativeCandidate) || existsSync(candidate)) {
|
|
552
|
+
return relativeCandidate;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return normalizePath(relative(db.config.projectRoot, absolute));
|
|
556
|
+
}
|
|
557
|
+
function resolvePythonImportPath(db, importerPath, specifier) {
|
|
558
|
+
const indexedPaths = getIndexedPaths(db);
|
|
559
|
+
let basePath;
|
|
560
|
+
if (specifier.startsWith(".")) {
|
|
561
|
+
const match = specifier.match(/^(\.+)(.*)$/);
|
|
562
|
+
if (!match) return null;
|
|
563
|
+
const dots = match[1].length;
|
|
564
|
+
const remainder = match[2].replace(/^\./, "");
|
|
565
|
+
let baseDir = dirname(join(db.config.projectRoot, importerPath));
|
|
566
|
+
for (let i = 1; i < dots; i++) {
|
|
567
|
+
baseDir = dirname(baseDir);
|
|
568
|
+
}
|
|
569
|
+
basePath = remainder ? resolve(baseDir, remainder.replace(/\./g, "/")) : baseDir;
|
|
570
|
+
} else {
|
|
571
|
+
basePath = resolve(db.config.projectRoot, specifier.replace(/\./g, "/"));
|
|
572
|
+
}
|
|
573
|
+
for (const candidate of pythonCandidateImportPaths(basePath)) {
|
|
574
|
+
const relativeCandidate = normalizePath(relative(db.config.projectRoot, candidate));
|
|
575
|
+
if (indexedPaths.has(relativeCandidate) || existsSync(candidate)) {
|
|
576
|
+
return relativeCandidate;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
function pythonCandidateImportPaths(basePath) {
|
|
582
|
+
const ext = extname(basePath);
|
|
583
|
+
if (PYTHON_SOURCE_EXTENSIONS.includes(ext)) {
|
|
584
|
+
return [basePath];
|
|
585
|
+
}
|
|
586
|
+
return [
|
|
587
|
+
`${basePath}.py`,
|
|
588
|
+
`${basePath}.pyi`,
|
|
589
|
+
join(basePath, "__init__.py"),
|
|
590
|
+
join(basePath, "__init__.pyi")
|
|
591
|
+
];
|
|
592
|
+
}
|
|
593
|
+
function candidateImportPaths(absolute) {
|
|
594
|
+
const ext = extname(absolute);
|
|
595
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
596
|
+
if (ext) {
|
|
597
|
+
candidates.add(absolute);
|
|
598
|
+
for (const sourceExt of SOURCE_EXTENSIONS) {
|
|
599
|
+
candidates.add(absolute.slice(0, -ext.length) + sourceExt);
|
|
600
|
+
}
|
|
601
|
+
} else {
|
|
602
|
+
for (const sourceExt of SOURCE_EXTENSIONS) {
|
|
603
|
+
candidates.add(`${absolute}${sourceExt}`);
|
|
604
|
+
candidates.add(join(absolute, `index${sourceExt}`));
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return [...candidates];
|
|
608
|
+
}
|
|
609
|
+
function getIndexedPaths(db) {
|
|
610
|
+
const cached = INDEXED_PATH_CACHE.get(db);
|
|
611
|
+
if (cached) {
|
|
612
|
+
return cached;
|
|
613
|
+
}
|
|
614
|
+
const paths = new Set(
|
|
615
|
+
db.all(
|
|
616
|
+
`SELECT relative_path
|
|
617
|
+
FROM documents
|
|
618
|
+
WHERE 1 = 1
|
|
619
|
+
${db.pathExclusionsFor("documents")}`
|
|
620
|
+
).map((row) => normalizePath(row.relative_path)).filter((relativePath) => !db.isIgnored(relativePath))
|
|
621
|
+
);
|
|
622
|
+
INDEXED_PATH_CACHE.set(db, paths);
|
|
623
|
+
return paths;
|
|
624
|
+
}
|
|
625
|
+
function getCachedMap(cache, db) {
|
|
626
|
+
let map = cache.get(db);
|
|
627
|
+
if (!map) {
|
|
628
|
+
map = /* @__PURE__ */ new Map();
|
|
629
|
+
cache.set(db, map);
|
|
630
|
+
}
|
|
631
|
+
return map;
|
|
632
|
+
}
|
|
633
|
+
function normalizePath(path) {
|
|
634
|
+
return path.replace(/\\/g, "/");
|
|
635
|
+
}
|
|
636
|
+
function isJavaScriptSourcePath(relativePath) {
|
|
637
|
+
return SOURCE_EXTENSIONS.includes(extname(relativePath).toLowerCase());
|
|
638
|
+
}
|
|
639
|
+
function isPythonSourcePath(relativePath) {
|
|
640
|
+
return PYTHON_SOURCE_EXTENSIONS.includes(extname(relativePath).toLowerCase());
|
|
641
|
+
}
|
|
642
|
+
function getSourceText(db, relativePath) {
|
|
643
|
+
const cache = getCachedMap(SOURCE_TEXT_CACHE, db);
|
|
644
|
+
const normalized = normalizePath(relativePath);
|
|
645
|
+
const cached = cache.get(normalized);
|
|
646
|
+
if (typeof cached === "string") {
|
|
647
|
+
return cached;
|
|
648
|
+
}
|
|
649
|
+
const fullPath = join(db.config.projectRoot, normalized);
|
|
650
|
+
if (!existsSync(fullPath)) {
|
|
651
|
+
cache.set(normalized, "");
|
|
652
|
+
return "";
|
|
653
|
+
}
|
|
654
|
+
const source = readFileSync(fullPath, "utf-8");
|
|
655
|
+
cache.set(normalized, source);
|
|
656
|
+
return source;
|
|
657
|
+
}
|
|
658
|
+
function pythonParenBalance(value) {
|
|
659
|
+
let balance = 0;
|
|
660
|
+
for (const char of value) {
|
|
661
|
+
if (char === "(") balance++;
|
|
662
|
+
if (char === ")") balance--;
|
|
663
|
+
}
|
|
664
|
+
return balance;
|
|
665
|
+
}
|
|
666
|
+
function escapeRegex(value) {
|
|
667
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// src/query-support.ts
|
|
671
|
+
import { basename } from "path";
|
|
672
|
+
var FILE_DEFINITION_CACHE = /* @__PURE__ */ new WeakMap();
|
|
673
|
+
var TEST_FILE_PATTERNS = [
|
|
674
|
+
"%/__tests__/%",
|
|
675
|
+
"%.test.%",
|
|
676
|
+
"%.spec.%",
|
|
677
|
+
"%/test/%",
|
|
678
|
+
"%/tests/%",
|
|
679
|
+
"%_test.%",
|
|
680
|
+
"%_spec.%",
|
|
681
|
+
"%/test_%.%",
|
|
682
|
+
"%/spec_%.%"
|
|
683
|
+
];
|
|
684
|
+
var TEST_SUPPORT_PATH_PATTERNS = [
|
|
685
|
+
"%/test-utils/%"
|
|
686
|
+
];
|
|
687
|
+
function testFileExclusionSql(alias, extraPatterns = []) {
|
|
688
|
+
const patterns = uniquePatterns([...TEST_FILE_PATTERNS, ...extraPatterns]);
|
|
689
|
+
return patterns.map((pattern) => `${alias}.relative_path NOT LIKE '${pattern}'`).join("\n AND ");
|
|
690
|
+
}
|
|
691
|
+
function buildFileDepGraph(db, scope) {
|
|
692
|
+
const scopeFilter = scope ? `AND d1.relative_path LIKE '%${scope}%'` : "";
|
|
693
|
+
const edges = db.all(
|
|
694
|
+
`SELECT DISTINCT
|
|
695
|
+
d1.relative_path AS from_file,
|
|
696
|
+
d2.relative_path AS to_file
|
|
697
|
+
FROM mentions m
|
|
698
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
699
|
+
JOIN documents d1 ON c.document_id = d1.id
|
|
700
|
+
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
701
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
702
|
+
JOIN documents d2 ON der.document_id = d2.id
|
|
703
|
+
WHERE d1.id != d2.id
|
|
704
|
+
AND m.role != 1
|
|
705
|
+
${db.pathExclusionsFor("d1", "d2")}
|
|
706
|
+
${scopeFilter}`
|
|
707
|
+
);
|
|
708
|
+
const graph = /* @__PURE__ */ new Map();
|
|
709
|
+
for (const edge of edges) {
|
|
710
|
+
if (db.isIgnored(edge.from_file) || db.isIgnored(edge.to_file)) continue;
|
|
711
|
+
if (!graph.has(edge.from_file)) graph.set(edge.from_file, /* @__PURE__ */ new Set());
|
|
712
|
+
graph.get(edge.from_file).add(edge.to_file);
|
|
713
|
+
}
|
|
714
|
+
return graph;
|
|
715
|
+
}
|
|
716
|
+
function findFirstSymbolMatch(db, symbolPattern) {
|
|
717
|
+
const fileLineMatch = symbolPattern.match(/^(.+):(\d+)-(\d+)$/);
|
|
718
|
+
if (fileLineMatch) {
|
|
719
|
+
const [, filePath, startStr, endStr] = fileLineMatch;
|
|
720
|
+
const row = db.get(
|
|
721
|
+
`SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path
|
|
722
|
+
FROM global_symbols gs
|
|
723
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
724
|
+
JOIN documents d ON der.document_id = d.id
|
|
725
|
+
WHERE d.relative_path LIKE ?
|
|
726
|
+
AND der.start_line <= ? AND der.end_line >= ?
|
|
727
|
+
${db.pathExclusionsFor("d")}
|
|
728
|
+
ORDER BY (der.end_line - der.start_line) ASC
|
|
729
|
+
LIMIT 1`,
|
|
730
|
+
`%${filePath}%`,
|
|
731
|
+
parseInt(startStr, 10),
|
|
732
|
+
parseInt(endStr, 10)
|
|
733
|
+
);
|
|
734
|
+
if (row && !db.isIgnored(row.relative_path)) {
|
|
735
|
+
return {
|
|
736
|
+
symbolId: row.id,
|
|
737
|
+
symbol: row.symbol,
|
|
738
|
+
documentId: row.document_id,
|
|
739
|
+
startLine: row.start_line,
|
|
740
|
+
endLine: row.end_line,
|
|
741
|
+
relativePath: row.relative_path
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
const cleaned = normalizeLookupPattern(symbolPattern);
|
|
746
|
+
const tokens = lookupTokens(symbolPattern);
|
|
747
|
+
const candidates = getSymbolLookupCandidates(db, tokens);
|
|
748
|
+
let best = null;
|
|
749
|
+
for (const row of candidates) {
|
|
750
|
+
if (db.isIgnored(row.relative_path)) continue;
|
|
751
|
+
const score = scoreSymbolCandidate(row, symbolPattern, cleaned, tokens);
|
|
752
|
+
if (score <= 0) continue;
|
|
753
|
+
if (!best || score > best.score) {
|
|
754
|
+
best = { row, score };
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
if (best) {
|
|
758
|
+
return {
|
|
759
|
+
symbolId: best.row.id,
|
|
760
|
+
symbol: best.row.symbol,
|
|
761
|
+
documentId: best.row.document_id,
|
|
762
|
+
startLine: best.row.start_line,
|
|
763
|
+
endLine: best.row.end_line,
|
|
764
|
+
relativePath: best.row.relative_path
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
return null;
|
|
768
|
+
}
|
|
769
|
+
function findExactSymbolMatch(db, symbol) {
|
|
770
|
+
const row = db.get(
|
|
771
|
+
`SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path
|
|
772
|
+
FROM global_symbols gs
|
|
773
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
774
|
+
JOIN documents d ON der.document_id = d.id
|
|
775
|
+
WHERE gs.symbol = ?
|
|
776
|
+
${db.pathExclusionsFor("d")}
|
|
777
|
+
ORDER BY d.relative_path, der.start_line
|
|
778
|
+
LIMIT 1`,
|
|
779
|
+
symbol
|
|
780
|
+
);
|
|
781
|
+
if (!row || db.isIgnored(row.relative_path)) {
|
|
782
|
+
return null;
|
|
783
|
+
}
|
|
784
|
+
return {
|
|
785
|
+
symbolId: row.id,
|
|
786
|
+
symbol: row.symbol,
|
|
787
|
+
documentId: row.document_id,
|
|
788
|
+
startLine: row.start_line,
|
|
789
|
+
endLine: row.end_line,
|
|
790
|
+
relativePath: row.relative_path
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
function resolveIndexedFile(db, filePattern) {
|
|
794
|
+
return resolveDocumentCandidates(db, filePattern, { allowMultiple: false })[0]?.relativePath ?? null;
|
|
795
|
+
}
|
|
796
|
+
function resolveIndexedPaths(db, filePattern) {
|
|
797
|
+
return resolveDocumentCandidates(db, filePattern, { allowMultiple: true }).map((candidate) => candidate.relativePath);
|
|
798
|
+
}
|
|
799
|
+
function normalizeLookupPattern(symbolPattern) {
|
|
800
|
+
return symbolPattern.trim().replace(/\(\)$/, "").replace(/\(.*$/, "");
|
|
801
|
+
}
|
|
802
|
+
function lookupTokens(symbolPattern) {
|
|
803
|
+
const cleaned = normalizeLookupPattern(symbolPattern);
|
|
804
|
+
const tokens = cleaned.split(/[^A-Za-z0-9_]+/).map((token) => token.trim()).filter((token) => token.length > 0);
|
|
805
|
+
return tokens.length > 0 ? [...new Set(tokens)] : [cleaned];
|
|
806
|
+
}
|
|
807
|
+
function getSymbolLookupCandidates(db, tokens) {
|
|
808
|
+
const tokenClauses = tokens.map(
|
|
809
|
+
() => `(gs.symbol LIKE ? OR d.relative_path LIKE ? OR COALESCE(gs.display_name, '') LIKE ?)`
|
|
810
|
+
);
|
|
811
|
+
const params = tokens.flatMap((token) => {
|
|
812
|
+
const like = `%${token}%`;
|
|
813
|
+
return [like, like, like];
|
|
814
|
+
});
|
|
815
|
+
return db.all(
|
|
816
|
+
`SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path, gs.display_name
|
|
817
|
+
FROM global_symbols gs
|
|
818
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
819
|
+
JOIN documents d ON der.document_id = d.id
|
|
820
|
+
WHERE ${tokenClauses.join("\n AND ")}
|
|
821
|
+
${db.pathExclusionsFor("d")}
|
|
822
|
+
LIMIT 200`,
|
|
823
|
+
...params
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
function scoreSymbolCandidate(row, originalPattern, cleanedPattern, tokens) {
|
|
827
|
+
const original = originalPattern.toLowerCase();
|
|
828
|
+
const cleaned = cleanedPattern.toLowerCase();
|
|
829
|
+
const noParens = cleaned.replace(/\(\)$/, "");
|
|
830
|
+
const raw = row.symbol.toLowerCase();
|
|
831
|
+
const short = shortenSymbol(row.symbol).toLowerCase();
|
|
832
|
+
const leaf = leafName(row.symbol).toLowerCase();
|
|
833
|
+
const display = (row.display_name ?? "").toLowerCase();
|
|
834
|
+
const path = row.relative_path.toLowerCase();
|
|
835
|
+
const looksPathLike = /[/:.]/.test(cleanedPattern);
|
|
836
|
+
let score = 0;
|
|
837
|
+
if (raw === original || raw === cleaned) score += 1e3;
|
|
838
|
+
if (short === original || short === cleaned) score += 950;
|
|
839
|
+
if (path === original || path === cleaned) score += 925;
|
|
840
|
+
if (path.endsWith(`/${cleaned}`) || path.endsWith(`/${original}`)) score += 875;
|
|
841
|
+
if (display === noParens) score += 850;
|
|
842
|
+
if (leaf === noParens) score += 825;
|
|
843
|
+
if (`${leaf}()` === original || `${leaf}()` === cleaned) score += 820;
|
|
844
|
+
if (short.endsWith(`:${cleaned}`) || short.endsWith(`:${noParens}`) || short.endsWith(`:${noParens}()`)) score += 800;
|
|
845
|
+
if (raw.includes(cleaned)) score += 120;
|
|
846
|
+
if (short.includes(cleaned)) score += 140;
|
|
847
|
+
if (path.includes(cleaned)) score += 140;
|
|
848
|
+
if (display.includes(cleaned)) score += 110;
|
|
849
|
+
if (tokens.every((token) => {
|
|
850
|
+
const lower = token.toLowerCase();
|
|
851
|
+
return raw.includes(lower) || short.includes(lower) || path.includes(lower) || display.includes(lower);
|
|
852
|
+
})) {
|
|
853
|
+
score += 100 + tokens.length * 15;
|
|
854
|
+
}
|
|
855
|
+
if (isFunctionLikeSymbol(row.symbol) && leaf === noParens) {
|
|
856
|
+
score += 60;
|
|
857
|
+
}
|
|
858
|
+
if (!looksPathLike && isModuleLikeSymbol(row.symbol)) {
|
|
859
|
+
score -= 160;
|
|
860
|
+
}
|
|
861
|
+
score -= Math.min(50, Math.max(0, row.end_line - row.start_line));
|
|
862
|
+
return score;
|
|
863
|
+
}
|
|
864
|
+
function getCalleeRowsForSymbol(db, symbol, opts = {}) {
|
|
865
|
+
const rows = db.all(
|
|
866
|
+
`SELECT DISTINCT
|
|
867
|
+
callee_gs.symbol AS symbol,
|
|
868
|
+
callee_d.relative_path AS file,
|
|
869
|
+
c.id AS chunk_id
|
|
870
|
+
FROM mentions m
|
|
871
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
872
|
+
JOIN global_symbols callee_gs ON m.symbol_id = callee_gs.id
|
|
873
|
+
JOIN defn_enclosing_ranges callee_der ON callee_gs.id = callee_der.symbol_id
|
|
874
|
+
JOIN documents callee_d ON callee_der.document_id = callee_d.id
|
|
875
|
+
WHERE c.document_id = ?
|
|
876
|
+
AND c.start_line >= ?
|
|
877
|
+
AND c.end_line <= ?
|
|
878
|
+
AND m.role != 1
|
|
879
|
+
AND callee_gs.id != ?
|
|
880
|
+
${db.symbolNoiseFor("callee_gs")}
|
|
881
|
+
${db.pathExclusionsFor("callee_d")}
|
|
882
|
+
ORDER BY callee_d.relative_path
|
|
883
|
+
${opts.limit ? "LIMIT ?" : ""}`,
|
|
884
|
+
...calleeQueryParams(symbol, opts.limit)
|
|
885
|
+
);
|
|
886
|
+
const primary = rows.filter((row) => !db.isIgnored(row.file)).map((row) => ({
|
|
887
|
+
symbol: row.symbol,
|
|
888
|
+
file: row.file,
|
|
889
|
+
chunkId: row.chunk_id
|
|
890
|
+
}));
|
|
891
|
+
const sourceFallback = getSourceBackedCalleeRows(db, symbol, opts.limit);
|
|
892
|
+
if (sourceFallback.length === 0) {
|
|
893
|
+
return primary;
|
|
894
|
+
}
|
|
895
|
+
if (primary.length === 0) {
|
|
896
|
+
return applyLimit(sourceFallback, opts.limit);
|
|
897
|
+
}
|
|
898
|
+
const seen = new Set(primary.map((row) => `${row.symbol}|${row.file}`));
|
|
899
|
+
const merged = [...primary];
|
|
900
|
+
for (const row of sourceFallback) {
|
|
901
|
+
const key = `${row.symbol}|${row.file}`;
|
|
902
|
+
if (seen.has(key)) continue;
|
|
903
|
+
seen.add(key);
|
|
904
|
+
merged.push(row);
|
|
905
|
+
}
|
|
906
|
+
return applyLimit(merged, opts.limit);
|
|
907
|
+
}
|
|
908
|
+
function getCallerRowsForSymbol(db, symbol, opts = {}) {
|
|
909
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
910
|
+
if (!match) {
|
|
911
|
+
return [];
|
|
912
|
+
}
|
|
913
|
+
const primary = db.all(
|
|
914
|
+
`SELECT DISTINCT caller_gs.symbol AS caller_symbol, caller_d.relative_path AS caller_file
|
|
915
|
+
FROM mentions m
|
|
916
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
917
|
+
JOIN documents ref_d ON c.document_id = ref_d.id
|
|
918
|
+
JOIN defn_enclosing_ranges caller_der
|
|
919
|
+
ON caller_der.document_id = ref_d.id
|
|
920
|
+
AND c.start_line >= caller_der.start_line
|
|
921
|
+
AND c.end_line <= caller_der.end_line
|
|
922
|
+
JOIN global_symbols caller_gs ON caller_der.symbol_id = caller_gs.id
|
|
923
|
+
JOIN documents caller_d ON caller_der.document_id = caller_d.id
|
|
924
|
+
WHERE m.symbol_id = ?
|
|
925
|
+
AND m.role != 1
|
|
926
|
+
AND caller_gs.id != ?
|
|
927
|
+
${db.symbolNoiseFor("caller_gs")}
|
|
928
|
+
${db.pathExclusionsFor("caller_d")}
|
|
929
|
+
ORDER BY caller_d.relative_path
|
|
930
|
+
${opts.limit ? "LIMIT ?" : ""}`,
|
|
931
|
+
...callerQueryParams(match, opts.limit)
|
|
932
|
+
).filter((row) => !db.isIgnored(row.caller_file)).map((row) => ({
|
|
933
|
+
symbol: row.caller_symbol,
|
|
934
|
+
file: row.caller_file
|
|
935
|
+
}));
|
|
936
|
+
const sourceFallback = getPythonSourceCallerRows(db, match, opts.limit);
|
|
937
|
+
if (sourceFallback.length === 0) {
|
|
938
|
+
return primary;
|
|
939
|
+
}
|
|
940
|
+
const merged = sourceFallback.length > 0 ? [...sourceFallback] : [];
|
|
941
|
+
const seen = new Set(merged.map((row) => `${row.symbol}|${row.file}`));
|
|
942
|
+
for (const row of primary) {
|
|
943
|
+
const key = `${row.symbol}|${row.file}`;
|
|
944
|
+
if (seen.has(key)) continue;
|
|
945
|
+
if (isFunctionLikeSymbol(row.symbol) || merged.length === 0) {
|
|
946
|
+
seen.add(key);
|
|
947
|
+
merged.push(row);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return applyLimit(merged, opts.limit);
|
|
951
|
+
}
|
|
952
|
+
function getSourceReferenceSites(db, symbol) {
|
|
953
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
954
|
+
if (!match) {
|
|
955
|
+
return [];
|
|
956
|
+
}
|
|
957
|
+
const identifier = leafName(match.symbol);
|
|
958
|
+
if (!identifier || !hasUniqueLeafDefinition(db, identifier, match.symbolId)) {
|
|
959
|
+
return [];
|
|
960
|
+
}
|
|
961
|
+
const documents = db.all(
|
|
962
|
+
`SELECT relative_path
|
|
963
|
+
FROM documents
|
|
964
|
+
WHERE 1 = 1
|
|
965
|
+
${db.pathExclusionsFor("documents")}
|
|
966
|
+
ORDER BY relative_path`
|
|
967
|
+
);
|
|
968
|
+
const sites = [];
|
|
969
|
+
const seen = /* @__PURE__ */ new Set();
|
|
970
|
+
for (const document of documents) {
|
|
971
|
+
if (db.isIgnored(document.relative_path)) continue;
|
|
972
|
+
const lines = findIdentifierLines(db, document.relative_path, identifier, document.relative_path === match.relativePath ? { excludeStartLine: match.startLine, excludeEndLine: match.endLine } : {});
|
|
973
|
+
for (const line of lines) {
|
|
974
|
+
const enclosing = db.get(
|
|
975
|
+
`SELECT gs.symbol
|
|
976
|
+
FROM defn_enclosing_ranges der
|
|
977
|
+
JOIN global_symbols gs ON der.symbol_id = gs.id
|
|
978
|
+
JOIN documents d ON der.document_id = d.id
|
|
979
|
+
WHERE d.relative_path = ?
|
|
980
|
+
AND der.start_line <= ?
|
|
981
|
+
AND der.end_line >= ?
|
|
982
|
+
ORDER BY (der.end_line - der.start_line) ASC
|
|
983
|
+
LIMIT 1`,
|
|
984
|
+
document.relative_path,
|
|
985
|
+
line,
|
|
986
|
+
line
|
|
987
|
+
);
|
|
988
|
+
const key = `${document.relative_path}|${line}|${enclosing?.symbol ?? ""}`;
|
|
989
|
+
if (seen.has(key)) continue;
|
|
990
|
+
seen.add(key);
|
|
991
|
+
sites.push({
|
|
992
|
+
file: document.relative_path,
|
|
993
|
+
line,
|
|
994
|
+
enclosingSymbol: enclosing?.symbol ?? null
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
return sites;
|
|
999
|
+
}
|
|
1000
|
+
function calleeQueryParams(symbol, limit) {
|
|
1001
|
+
const params = [
|
|
1002
|
+
symbol.documentId,
|
|
1003
|
+
symbol.startLine,
|
|
1004
|
+
symbol.endLine,
|
|
1005
|
+
symbol.symbolId
|
|
1006
|
+
];
|
|
1007
|
+
if (typeof limit === "number") {
|
|
1008
|
+
params.push(limit);
|
|
1009
|
+
}
|
|
1010
|
+
return params;
|
|
1011
|
+
}
|
|
1012
|
+
function callerQueryParams(symbol, limit) {
|
|
1013
|
+
const params = [symbol.symbolId, symbol.symbolId];
|
|
1014
|
+
if (typeof limit === "number") {
|
|
1015
|
+
params.push(limit);
|
|
1016
|
+
}
|
|
1017
|
+
return params;
|
|
1018
|
+
}
|
|
1019
|
+
function getPythonSourceCalleeRows(db, symbol, limit) {
|
|
1020
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
1021
|
+
if (!match || !isPythonDocument(db, match.relativePath)) {
|
|
1022
|
+
return [];
|
|
1023
|
+
}
|
|
1024
|
+
const definitions = getDefinitionsForFile(db, match.relativePath);
|
|
1025
|
+
const current = definitions.find((definition) => definition.symbolId === match.symbolId);
|
|
1026
|
+
if (!current) {
|
|
1027
|
+
return [];
|
|
1028
|
+
}
|
|
1029
|
+
const imports = getSourceImports(db, match.relativePath);
|
|
1030
|
+
const bindings = new Map(
|
|
1031
|
+
getSourceConstructorBindings(db, match.relativePath, {
|
|
1032
|
+
startLine: match.startLine,
|
|
1033
|
+
endLine: match.endLine
|
|
1034
|
+
}).map((binding) => [binding.localName, binding.typeName])
|
|
1035
|
+
);
|
|
1036
|
+
const rows = [];
|
|
1037
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1038
|
+
for (const call of getSourceCalls(db, match.relativePath, {
|
|
1039
|
+
startLine: match.startLine,
|
|
1040
|
+
endLine: match.endLine
|
|
1041
|
+
})) {
|
|
1042
|
+
const resolved = resolvePythonCallTarget(
|
|
1043
|
+
db,
|
|
1044
|
+
current,
|
|
1045
|
+
definitions,
|
|
1046
|
+
imports,
|
|
1047
|
+
bindings,
|
|
1048
|
+
call.receiverName,
|
|
1049
|
+
call.calleeName
|
|
1050
|
+
);
|
|
1051
|
+
if (!resolved || resolved.symbolId === match.symbolId || db.isIgnored(resolved.relativePath)) continue;
|
|
1052
|
+
const chunkId = 1e9 + call.line;
|
|
1053
|
+
const key = `${resolved.symbol}|${resolved.relativePath}|${chunkId}`;
|
|
1054
|
+
if (seen.has(key)) continue;
|
|
1055
|
+
seen.add(key);
|
|
1056
|
+
rows.push({
|
|
1057
|
+
symbol: resolved.symbol,
|
|
1058
|
+
file: resolved.relativePath,
|
|
1059
|
+
chunkId
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
return applyLimit(rows, limit);
|
|
1063
|
+
}
|
|
1064
|
+
function getJavaScriptSourceCalleeRows(db, symbol, limit) {
|
|
1065
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
1066
|
+
if (!match || !isJavaScriptDocument(db, match.relativePath)) {
|
|
1067
|
+
return [];
|
|
1068
|
+
}
|
|
1069
|
+
const definitions = getDefinitionsForFile(db, match.relativePath);
|
|
1070
|
+
const current = definitions.find((definition) => definition.symbolId === match.symbolId);
|
|
1071
|
+
if (!current) {
|
|
1072
|
+
return [];
|
|
1073
|
+
}
|
|
1074
|
+
const imports = getSourceImports(db, match.relativePath);
|
|
1075
|
+
const bindings = new Map(
|
|
1076
|
+
getSourceConstructorBindings(db, match.relativePath, {
|
|
1077
|
+
startLine: match.startLine,
|
|
1078
|
+
endLine: match.endLine
|
|
1079
|
+
}).map((binding) => [binding.localName, binding.typeName])
|
|
1080
|
+
);
|
|
1081
|
+
const rows = [];
|
|
1082
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1083
|
+
for (const call of getSourceCalls(db, match.relativePath, {
|
|
1084
|
+
startLine: match.startLine,
|
|
1085
|
+
endLine: match.endLine
|
|
1086
|
+
})) {
|
|
1087
|
+
const resolved = resolveJavaScriptCallTarget(
|
|
1088
|
+
db,
|
|
1089
|
+
current,
|
|
1090
|
+
definitions,
|
|
1091
|
+
imports,
|
|
1092
|
+
bindings,
|
|
1093
|
+
call.receiverName,
|
|
1094
|
+
call.calleeName
|
|
1095
|
+
);
|
|
1096
|
+
if (!resolved || resolved.symbolId === match.symbolId || db.isIgnored(resolved.relativePath)) continue;
|
|
1097
|
+
const chunkId = 1e9 + call.line;
|
|
1098
|
+
const key = `${resolved.symbol}|${resolved.relativePath}|${chunkId}`;
|
|
1099
|
+
if (seen.has(key)) continue;
|
|
1100
|
+
seen.add(key);
|
|
1101
|
+
rows.push({
|
|
1102
|
+
symbol: resolved.symbol,
|
|
1103
|
+
file: resolved.relativePath,
|
|
1104
|
+
chunkId
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
return applyLimit(rows, limit);
|
|
1108
|
+
}
|
|
1109
|
+
function getSourceBackedCalleeRows(db, symbol, limit) {
|
|
1110
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
1111
|
+
if (!match) {
|
|
1112
|
+
return [];
|
|
1113
|
+
}
|
|
1114
|
+
if (isPythonDocument(db, match.relativePath)) {
|
|
1115
|
+
return getPythonSourceCalleeRows(db, match, limit);
|
|
1116
|
+
}
|
|
1117
|
+
if (isJavaScriptDocument(db, match.relativePath)) {
|
|
1118
|
+
return getJavaScriptSourceCalleeRows(db, match, limit);
|
|
1119
|
+
}
|
|
1120
|
+
return [];
|
|
1121
|
+
}
|
|
1122
|
+
function getPythonSourceCallerRows(db, target, limit) {
|
|
1123
|
+
if (!isPythonDocument(db, target.relativePath)) {
|
|
1124
|
+
return [];
|
|
1125
|
+
}
|
|
1126
|
+
const rows = [];
|
|
1127
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1128
|
+
for (const candidate of getAllFunctionLikeDefinitions(db)) {
|
|
1129
|
+
if (candidate.symbolId === target.symbolId) continue;
|
|
1130
|
+
const callees = getPythonSourceCalleeRows(db, candidate);
|
|
1131
|
+
if (!callees.some((callee) => callee.symbol === target.symbol)) continue;
|
|
1132
|
+
const key = `${candidate.symbol}|${candidate.relativePath}`;
|
|
1133
|
+
if (seen.has(key) || db.isIgnored(candidate.relativePath)) continue;
|
|
1134
|
+
seen.add(key);
|
|
1135
|
+
rows.push({
|
|
1136
|
+
symbol: candidate.symbol,
|
|
1137
|
+
file: candidate.relativePath
|
|
1138
|
+
});
|
|
1139
|
+
if (typeof limit === "number" && rows.length >= limit) {
|
|
1140
|
+
break;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
return rows;
|
|
1144
|
+
}
|
|
1145
|
+
function getFullSymbolMatch(db, symbol) {
|
|
1146
|
+
if ("symbol" in symbol && "relativePath" in symbol) {
|
|
1147
|
+
return symbol;
|
|
1148
|
+
}
|
|
1149
|
+
const row = db.get(
|
|
1150
|
+
`SELECT gs.symbol, d.relative_path
|
|
1151
|
+
FROM global_symbols gs
|
|
1152
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
1153
|
+
JOIN documents d ON der.document_id = d.id
|
|
1154
|
+
WHERE gs.id = ?
|
|
1155
|
+
LIMIT 1`,
|
|
1156
|
+
symbol.symbolId
|
|
1157
|
+
);
|
|
1158
|
+
if (!row) {
|
|
1159
|
+
return null;
|
|
1160
|
+
}
|
|
1161
|
+
return {
|
|
1162
|
+
...symbol,
|
|
1163
|
+
symbol: row.symbol,
|
|
1164
|
+
relativePath: row.relative_path
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
function getDefinitionsForFile(db, relativePath) {
|
|
1168
|
+
let cache = FILE_DEFINITION_CACHE.get(db);
|
|
1169
|
+
if (!cache) {
|
|
1170
|
+
cache = /* @__PURE__ */ new Map();
|
|
1171
|
+
FILE_DEFINITION_CACHE.set(db, cache);
|
|
1172
|
+
}
|
|
1173
|
+
const cached = cache.get(relativePath);
|
|
1174
|
+
if (cached) {
|
|
1175
|
+
return cached;
|
|
1176
|
+
}
|
|
1177
|
+
const definitions = db.all(
|
|
1178
|
+
`SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path
|
|
1179
|
+
FROM global_symbols gs
|
|
1180
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
1181
|
+
JOIN documents d ON der.document_id = d.id
|
|
1182
|
+
WHERE d.relative_path = ?
|
|
1183
|
+
${db.symbolNoiseFor("gs")}
|
|
1184
|
+
ORDER BY der.start_line, der.end_line`,
|
|
1185
|
+
relativePath
|
|
1186
|
+
).map((row) => ({
|
|
1187
|
+
symbolId: row.id,
|
|
1188
|
+
symbol: row.symbol,
|
|
1189
|
+
documentId: row.document_id,
|
|
1190
|
+
startLine: row.start_line,
|
|
1191
|
+
endLine: row.end_line,
|
|
1192
|
+
relativePath: row.relative_path,
|
|
1193
|
+
leaf: leafName(row.symbol),
|
|
1194
|
+
parentTypeName: parentTypeName(row.symbol),
|
|
1195
|
+
isFunctionLike: isFunctionLikeSymbol(row.symbol),
|
|
1196
|
+
isTypeLike: leafSuffix(row.symbol) === "type"
|
|
1197
|
+
}));
|
|
1198
|
+
cache.set(relativePath, definitions);
|
|
1199
|
+
return definitions;
|
|
1200
|
+
}
|
|
1201
|
+
function getAllFunctionLikeDefinitions(db) {
|
|
1202
|
+
const rows = db.all(
|
|
1203
|
+
`SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path
|
|
1204
|
+
FROM global_symbols gs
|
|
1205
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
1206
|
+
JOIN documents d ON der.document_id = d.id
|
|
1207
|
+
WHERE 1 = 1
|
|
1208
|
+
${db.pathExclusionsFor("d")}
|
|
1209
|
+
${db.symbolNoiseFor("gs")}
|
|
1210
|
+
ORDER BY d.relative_path, der.start_line`
|
|
1211
|
+
);
|
|
1212
|
+
return rows.filter((row) => !db.isIgnored(row.relative_path)).filter((row) => isFunctionLikeSymbol(row.symbol)).map((row) => ({
|
|
1213
|
+
symbolId: row.id,
|
|
1214
|
+
symbol: row.symbol,
|
|
1215
|
+
documentId: row.document_id,
|
|
1216
|
+
startLine: row.start_line,
|
|
1217
|
+
endLine: row.end_line,
|
|
1218
|
+
relativePath: row.relative_path,
|
|
1219
|
+
leaf: leafName(row.symbol),
|
|
1220
|
+
parentTypeName: parentTypeName(row.symbol),
|
|
1221
|
+
isFunctionLike: true,
|
|
1222
|
+
isTypeLike: false
|
|
1223
|
+
}));
|
|
1224
|
+
}
|
|
1225
|
+
function resolvePythonCallTarget(db, current, currentFileDefinitions, imports, constructorBindings, receiverName, calleeName) {
|
|
1226
|
+
if (receiverName === "self" || receiverName === "cls") {
|
|
1227
|
+
return findDefinitionByName(currentFileDefinitions, calleeName, current.parentTypeName, ["function"]);
|
|
1228
|
+
}
|
|
1229
|
+
if (receiverName) {
|
|
1230
|
+
const inferredType = constructorBindings.get(receiverName);
|
|
1231
|
+
if (inferredType) {
|
|
1232
|
+
const boundMethod = findDefinitionByName(currentFileDefinitions, calleeName, inferredType, ["function"]);
|
|
1233
|
+
if (boundMethod) {
|
|
1234
|
+
return boundMethod;
|
|
1235
|
+
}
|
|
1236
|
+
for (const entry of imports) {
|
|
1237
|
+
if (entry.localName !== inferredType || !entry.sourcePath) continue;
|
|
1238
|
+
const importedDefinitions = getDefinitionsForFile(db, entry.sourcePath);
|
|
1239
|
+
const importedMethod = findDefinitionByName(importedDefinitions, calleeName, entry.importedName, ["function"]);
|
|
1240
|
+
if (importedMethod) {
|
|
1241
|
+
return importedMethod;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
const localClassMethod = findDefinitionByName(currentFileDefinitions, calleeName, receiverName, ["function"]);
|
|
1246
|
+
if (localClassMethod) {
|
|
1247
|
+
return localClassMethod;
|
|
1248
|
+
}
|
|
1249
|
+
const namespaceImport = imports.find((entry) => entry.localName === receiverName && entry.sourcePath);
|
|
1250
|
+
if (namespaceImport?.sourcePath) {
|
|
1251
|
+
const importedDefinitions = getDefinitionsForFile(db, namespaceImport.sourcePath);
|
|
1252
|
+
const importedTypeMethod = namespaceImport.kind === "named" ? findDefinitionByName(importedDefinitions, calleeName, namespaceImport.importedName, ["function"]) : null;
|
|
1253
|
+
if (importedTypeMethod) {
|
|
1254
|
+
return importedTypeMethod;
|
|
1255
|
+
}
|
|
1256
|
+
return findDefinitionByName(importedDefinitions, calleeName, null, ["function", "type"]);
|
|
1257
|
+
}
|
|
1258
|
+
return null;
|
|
1259
|
+
}
|
|
1260
|
+
const importedBinding = imports.find((entry) => entry.localName === calleeName && entry.sourcePath);
|
|
1261
|
+
if (importedBinding?.sourcePath) {
|
|
1262
|
+
const importedDefinitions = getDefinitionsForFile(db, importedBinding.sourcePath);
|
|
1263
|
+
const importedName = importedBinding.importedName === "*" ? calleeName : importedBinding.importedName;
|
|
1264
|
+
const importedDefinition = findDefinitionByName(importedDefinitions, importedName, null, ["function", "type"]);
|
|
1265
|
+
if (importedDefinition) {
|
|
1266
|
+
return importedDefinition;
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
return findDefinitionByName(currentFileDefinitions, calleeName, null, ["function", "type"]);
|
|
1270
|
+
}
|
|
1271
|
+
function resolveJavaScriptCallTarget(db, current, currentFileDefinitions, imports, constructorBindings, receiverName, calleeName) {
|
|
1272
|
+
if (receiverName === "this") {
|
|
1273
|
+
return findDefinitionByName(currentFileDefinitions, calleeName, current.parentTypeName, ["function"]);
|
|
1274
|
+
}
|
|
1275
|
+
if (receiverName) {
|
|
1276
|
+
const inferredType = constructorBindings.get(receiverName);
|
|
1277
|
+
if (inferredType) {
|
|
1278
|
+
const boundMethod = findDefinitionByName(currentFileDefinitions, calleeName, inferredType, ["function"]);
|
|
1279
|
+
if (boundMethod) {
|
|
1280
|
+
return boundMethod;
|
|
1281
|
+
}
|
|
1282
|
+
for (const entry of imports) {
|
|
1283
|
+
if (entry.localName !== inferredType || !entry.sourcePath) continue;
|
|
1284
|
+
const importedDefinitions = getDefinitionsForFile(db, entry.sourcePath);
|
|
1285
|
+
const importedMethod = findDefinitionByName(importedDefinitions, calleeName, entry.importedName, ["function"]);
|
|
1286
|
+
if (importedMethod) {
|
|
1287
|
+
return importedMethod;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
const localClassMethod = findDefinitionByName(currentFileDefinitions, calleeName, receiverName, ["function"]);
|
|
1292
|
+
if (localClassMethod) {
|
|
1293
|
+
return localClassMethod;
|
|
1294
|
+
}
|
|
1295
|
+
const namespaceImport = imports.find((entry) => entry.localName === receiverName && entry.sourcePath);
|
|
1296
|
+
if (namespaceImport?.sourcePath) {
|
|
1297
|
+
const importedDefinitions = getDefinitionsForFile(db, namespaceImport.sourcePath);
|
|
1298
|
+
if (namespaceImport.kind === "named" || namespaceImport.kind === "default") {
|
|
1299
|
+
const importedTypeMethod = findDefinitionByName(
|
|
1300
|
+
importedDefinitions,
|
|
1301
|
+
calleeName,
|
|
1302
|
+
namespaceImport.importedName,
|
|
1303
|
+
["function"]
|
|
1304
|
+
);
|
|
1305
|
+
if (importedTypeMethod) {
|
|
1306
|
+
return importedTypeMethod;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
return findDefinitionByName(importedDefinitions, calleeName, null, ["function", "type"]);
|
|
1310
|
+
}
|
|
1311
|
+
return null;
|
|
1312
|
+
}
|
|
1313
|
+
const importedBinding = imports.find((entry) => entry.localName === calleeName && entry.sourcePath && entry.kind !== "namespace");
|
|
1314
|
+
if (importedBinding?.sourcePath) {
|
|
1315
|
+
const importedDefinitions = getDefinitionsForFile(db, importedBinding.sourcePath);
|
|
1316
|
+
const importedName = importedBinding.importedName === "default" ? importedBinding.localName ?? calleeName : importedBinding.importedName;
|
|
1317
|
+
const importedDefinition = findDefinitionByName(importedDefinitions, importedName, null, ["function", "type"]);
|
|
1318
|
+
if (importedDefinition) {
|
|
1319
|
+
return importedDefinition;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
return findDefinitionByName(currentFileDefinitions, calleeName, null, ["function", "type"]);
|
|
1323
|
+
}
|
|
1324
|
+
function findDefinitionByName(definitions, leaf, parentType, preference) {
|
|
1325
|
+
const candidates = definitions.filter((definition) => definition.leaf === leaf && definition.parentTypeName === parentType);
|
|
1326
|
+
if (candidates.length === 0) {
|
|
1327
|
+
return null;
|
|
1328
|
+
}
|
|
1329
|
+
for (const preferred of preference) {
|
|
1330
|
+
const match = candidates.find((candidate) => preferred === "function" ? candidate.isFunctionLike : candidate.isTypeLike);
|
|
1331
|
+
if (match) {
|
|
1332
|
+
return match;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
return candidates[0] ?? null;
|
|
1336
|
+
}
|
|
1337
|
+
function hasUniqueLeafDefinition(db, leaf, symbolId) {
|
|
1338
|
+
const rows = db.all(
|
|
1339
|
+
`SELECT id, symbol
|
|
1340
|
+
FROM global_symbols
|
|
1341
|
+
WHERE symbol LIKE ?
|
|
1342
|
+
LIMIT 50`,
|
|
1343
|
+
`%${leaf}%`
|
|
1344
|
+
);
|
|
1345
|
+
let count = 0;
|
|
1346
|
+
for (const row of rows) {
|
|
1347
|
+
if (leafName(row.symbol) !== leaf) continue;
|
|
1348
|
+
count++;
|
|
1349
|
+
if (count > 1 && row.id !== symbolId) {
|
|
1350
|
+
return false;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
return count === 1;
|
|
1354
|
+
}
|
|
1355
|
+
function parentTypeName(rawSymbol) {
|
|
1356
|
+
const parsed = parseSymbol(rawSymbol);
|
|
1357
|
+
if ("kind" in parsed) {
|
|
1358
|
+
return null;
|
|
1359
|
+
}
|
|
1360
|
+
for (let index = parsed.descriptors.length - 2; index >= 0; index--) {
|
|
1361
|
+
const descriptor = parsed.descriptors[index];
|
|
1362
|
+
if (descriptor?.suffix === "type") {
|
|
1363
|
+
return descriptor.name;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
return null;
|
|
1367
|
+
}
|
|
1368
|
+
function isPythonDocument(db, relativePath) {
|
|
1369
|
+
const row = db.get(
|
|
1370
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
1371
|
+
relativePath
|
|
1372
|
+
);
|
|
1373
|
+
return row?.language === "python" || relativePath.endsWith(".py") || relativePath.endsWith(".pyi");
|
|
1374
|
+
}
|
|
1375
|
+
function isJavaScriptDocument(db, relativePath) {
|
|
1376
|
+
const row = db.get(
|
|
1377
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
1378
|
+
relativePath
|
|
1379
|
+
);
|
|
1380
|
+
return row?.language === "typescript" || row?.language === "javascript" || /\.(?:[cm]?[jt]sx?)$/.test(relativePath);
|
|
1381
|
+
}
|
|
1382
|
+
function applyLimit(values, limit) {
|
|
1383
|
+
return typeof limit === "number" ? values.slice(0, limit) : values;
|
|
1384
|
+
}
|
|
1385
|
+
function resolveDocumentCandidates(db, filePattern, opts) {
|
|
1386
|
+
const normalizedPattern = normalizeLookupPath(filePattern);
|
|
1387
|
+
if (!normalizedPattern) {
|
|
1388
|
+
return [];
|
|
1389
|
+
}
|
|
1390
|
+
const rows = db.all(
|
|
1391
|
+
`SELECT relative_path
|
|
1392
|
+
FROM documents
|
|
1393
|
+
WHERE 1 = 1
|
|
1394
|
+
${db.pathExclusionsFor("documents")}
|
|
1395
|
+
ORDER BY relative_path`
|
|
1396
|
+
);
|
|
1397
|
+
const scored = rows.filter((row) => !db.isIgnored(row.relative_path)).map((row) => ({
|
|
1398
|
+
relativePath: row.relative_path,
|
|
1399
|
+
score: scoreDocumentPath(row.relative_path, normalizedPattern)
|
|
1400
|
+
})).filter((row) => row.score > 0).sort((a, b) => b.score - a.score || a.relativePath.localeCompare(b.relativePath));
|
|
1401
|
+
if (scored.length === 0) {
|
|
1402
|
+
return [];
|
|
1403
|
+
}
|
|
1404
|
+
const exactish = scored.filter((row) => row.score >= 800);
|
|
1405
|
+
if (exactish.length > 0) {
|
|
1406
|
+
return opts.allowMultiple ? exactish : [exactish[0]];
|
|
1407
|
+
}
|
|
1408
|
+
return opts.allowMultiple ? scored : [scored[0]];
|
|
1409
|
+
}
|
|
1410
|
+
function scoreDocumentPath(relativePath, rawPattern) {
|
|
1411
|
+
const normalizedPath = normalizeLookupPath(relativePath);
|
|
1412
|
+
const pathBase = basename(normalizedPath);
|
|
1413
|
+
const patternBase = basename(rawPattern);
|
|
1414
|
+
let score = 0;
|
|
1415
|
+
if (normalizedPath === rawPattern) score += 1200;
|
|
1416
|
+
if (normalizedPath.endsWith(`/${rawPattern}`)) score += 1100;
|
|
1417
|
+
if (pathBase === patternBase) score += 900;
|
|
1418
|
+
if (normalizedPath.startsWith(`${rawPattern}/`)) score += 850;
|
|
1419
|
+
if (normalizedPath.includes(rawPattern)) score += 250;
|
|
1420
|
+
return score;
|
|
1421
|
+
}
|
|
1422
|
+
function normalizeLookupPath(filePattern) {
|
|
1423
|
+
return filePattern.trim().replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "").replace(/\/+$/, "");
|
|
1424
|
+
}
|
|
1425
|
+
function uniquePatterns(patterns) {
|
|
1426
|
+
return [...new Set(patterns)];
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
export {
|
|
1430
|
+
getSourceImports,
|
|
1431
|
+
TEST_SUPPORT_PATH_PATTERNS,
|
|
1432
|
+
testFileExclusionSql,
|
|
1433
|
+
buildFileDepGraph,
|
|
1434
|
+
findFirstSymbolMatch,
|
|
1435
|
+
findExactSymbolMatch,
|
|
1436
|
+
resolveIndexedFile,
|
|
1437
|
+
resolveIndexedPaths,
|
|
1438
|
+
getCalleeRowsForSymbol,
|
|
1439
|
+
getCallerRowsForSymbol,
|
|
1440
|
+
getSourceReferenceSites
|
|
1441
|
+
};
|
|
1442
|
+
//# sourceMappingURL=chunk-AXQKUYKF.js.map
|