@monoes/monograph 1.5.0 → 1.5.2
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/.monodesign/hook.cache.json +1 -0
- package/__tests__/storage/stores.test.ts +66 -1
- package/dist/src/analysis/cycles.d.ts.map +1 -1
- package/dist/src/analysis/cycles.js +5 -3
- package/dist/src/analysis/cycles.js.map +1 -1
- package/dist/src/analysis/trace.d.ts.map +1 -1
- package/dist/src/analysis/trace.js +1 -19
- package/dist/src/analysis/trace.js.map +1 -1
- package/dist/src/analysis/worker-pool.d.ts.map +1 -1
- package/dist/src/analysis/worker-pool.js +9 -7
- package/dist/src/analysis/worker-pool.js.map +1 -1
- package/dist/src/cli/skill-gen.d.ts.map +1 -1
- package/dist/src/cli/skill-gen.js +16 -25
- package/dist/src/cli/skill-gen.js.map +1 -1
- package/dist/src/export/html.d.ts.map +1 -1
- package/dist/src/export/html.js +3 -2
- package/dist/src/export/html.js.map +1 -1
- package/dist/src/graph/regex-search.d.ts.map +1 -1
- package/dist/src/graph/regex-search.js +9 -3
- package/dist/src/graph/regex-search.js.map +1 -1
- package/dist/src/pipeline/phases/call-site-extractors.d.ts +17 -0
- package/dist/src/pipeline/phases/call-site-extractors.d.ts.map +1 -0
- package/dist/src/pipeline/phases/call-site-extractors.js +132 -0
- package/dist/src/pipeline/phases/call-site-extractors.js.map +1 -0
- package/dist/src/pipeline/phases/module-resolution.d.ts +10 -0
- package/dist/src/pipeline/phases/module-resolution.d.ts.map +1 -0
- package/dist/src/pipeline/phases/module-resolution.js +301 -0
- package/dist/src/pipeline/phases/module-resolution.js.map +1 -0
- package/dist/src/pipeline/phases/orm.js +1 -2
- package/dist/src/pipeline/phases/orm.js.map +1 -1
- package/dist/src/pipeline/phases/scope-resolution.d.ts +2 -25
- package/dist/src/pipeline/phases/scope-resolution.d.ts.map +1 -1
- package/dist/src/pipeline/phases/scope-resolution.js +18 -580
- package/dist/src/pipeline/phases/scope-resolution.js.map +1 -1
- package/dist/src/pipeline/phases/wildcard-phase.d.ts.map +1 -1
- package/dist/src/pipeline/phases/wildcard-phase.js +6 -1
- package/dist/src/pipeline/phases/wildcard-phase.js.map +1 -1
- package/dist/src/search/hybrid-query.js +1 -1
- package/dist/src/search/hybrid-query.js.map +1 -1
- package/dist/src/storage/fts-store.d.ts.map +1 -1
- package/dist/src/storage/fts-store.js +7 -4
- package/dist/src/storage/fts-store.js.map +1 -1
- package/dist/src/watch/watcher.d.ts +2 -0
- package/dist/src/watch/watcher.d.ts.map +1 -1
- package/dist/src/watch/watcher.js +23 -1
- package/dist/src/watch/watcher.js.map +1 -1
- package/dist/src/web/api.d.ts.map +1 -1
- package/dist/src/web/api.js +17 -14
- package/dist/src/web/api.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/__tests__/pipeline/phases/wildcard-phase.test.ts +39 -4
- package/src/analysis/cycles.ts +7 -3
- package/src/analysis/trace.ts +1 -21
- package/src/analysis/worker-pool.ts +8 -7
- package/src/cli/skill-gen.ts +6 -14
- package/src/export/html.ts +3 -2
- package/src/graph/regex-search.ts +9 -5
- package/src/pipeline/phases/call-site-extractors.ts +169 -0
- package/src/pipeline/phases/module-resolution.ts +294 -0
- package/src/pipeline/phases/orm.ts +1 -2
- package/src/pipeline/phases/scope-resolution.ts +25 -618
- package/src/pipeline/phases/wildcard-phase.ts +5 -1
- package/src/search/hybrid-query.ts +1 -1
- package/src/storage/fts-store.ts +7 -3
- package/src/watch/watcher.ts +23 -1
- package/src/web/api.ts +17 -13
|
@@ -1,551 +1,12 @@
|
|
|
1
|
-
import { readFileSync
|
|
2
|
-
import { join, extname
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { join, extname } from 'path';
|
|
3
3
|
import { makeId } from '../../types.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
]);
|
|
11
|
-
const PY_KEYWORDS = new Set([
|
|
12
|
-
'if', 'for', 'while', 'with', 'return', 'def', 'class', 'import', 'from',
|
|
13
|
-
'raise', 'yield', 'lambda', 'await', 'async', 'del', 'pass', 'assert',
|
|
14
|
-
'except', 'elif', 'else', 'not', 'and', 'or', 'in', 'is', 'print',
|
|
15
|
-
]);
|
|
16
|
-
const GO_KEYWORDS = new Set([
|
|
17
|
-
'if', 'for', 'range', 'return', 'func', 'type', 'var', 'const', 'import', 'package',
|
|
18
|
-
'go', 'defer', 'select', 'case', 'default', 'break', 'continue', 'goto', 'fallthrough',
|
|
19
|
-
'chan', 'map', 'struct', 'interface', 'make', 'new', 'len', 'cap', 'append', 'delete',
|
|
20
|
-
'panic', 'recover', 'close', 'switch', 'else',
|
|
21
|
-
]);
|
|
22
|
-
const JAVA_KEYWORDS = new Set([
|
|
23
|
-
'if', 'for', 'while', 'do', 'switch', 'case', 'return', 'class', 'interface', 'enum',
|
|
24
|
-
'new', 'extends', 'implements', 'import', 'package', 'throw', 'throws', 'catch', 'try',
|
|
25
|
-
'finally', 'static', 'final', 'abstract', 'public', 'private', 'protected', 'void',
|
|
26
|
-
'break', 'continue', 'default', 'else', 'instanceof', 'this', 'super',
|
|
27
|
-
]);
|
|
28
|
-
const RUST_KEYWORDS = new Set([
|
|
29
|
-
'if', 'let', 'for', 'while', 'loop', 'match', 'return', 'fn', 'struct', 'enum', 'trait',
|
|
30
|
-
'impl', 'use', 'mod', 'pub', 'super', 'self', 'type', 'where', 'in', 'as', 'mut',
|
|
31
|
-
'ref', 'move', 'async', 'await', 'dyn', 'extern', 'crate', 'static', 'const', 'unsafe',
|
|
32
|
-
'break', 'continue', 'else',
|
|
33
|
-
]);
|
|
34
|
-
// ── Supported extensions ──────────────────────────────────────────────────────
|
|
35
|
-
const TS_JS_EXTS = new Set(['.ts', '.tsx', '.js', '.jsx']);
|
|
36
|
-
const PY_EXTS = new Set(['.py']);
|
|
37
|
-
const GO_EXTS = new Set(['.go']);
|
|
38
|
-
const JAVA_EXTS = new Set(['.java']);
|
|
39
|
-
const RUST_EXTS = new Set(['.rs']);
|
|
40
|
-
const CJS_MJS_EXTS = new Set(['.cjs', '.mjs']);
|
|
41
|
-
function isSupportedExt(ext) {
|
|
42
|
-
return TS_JS_EXTS.has(ext) || CJS_MJS_EXTS.has(ext) || PY_EXTS.has(ext) || GO_EXTS.has(ext) || JAVA_EXTS.has(ext) || RUST_EXTS.has(ext);
|
|
43
|
-
}
|
|
44
|
-
// ── Language-specific extractors ──────────────────────────────────────────────
|
|
45
|
-
export function extractGoCallSites(source, filePath, fileNodeId) {
|
|
46
|
-
const sites = [];
|
|
47
|
-
const methodPattern = /(\w+)\.(\w+)\s*\(/g;
|
|
48
|
-
let m;
|
|
49
|
-
while ((m = methodPattern.exec(source)) !== null) {
|
|
50
|
-
sites.push({ callerFileNodeId: fileNodeId, callerFilePath: filePath, calleeRaw: `${m[1]}.${m[2]}`, form: 'method', receiverName: m[1], methodName: m[2] });
|
|
51
|
-
}
|
|
52
|
-
const directPattern = /(?<![.[\w])([A-Za-z_][\w]*)\s*\(/g;
|
|
53
|
-
while ((m = directPattern.exec(source)) !== null) {
|
|
54
|
-
const name = m[1];
|
|
55
|
-
if (GO_KEYWORDS.has(name))
|
|
56
|
-
continue;
|
|
57
|
-
sites.push({ callerFileNodeId: fileNodeId, callerFilePath: filePath, calleeRaw: name, form: 'direct' });
|
|
58
|
-
}
|
|
59
|
-
return sites;
|
|
60
|
-
}
|
|
61
|
-
export function extractJavaCallSites(source, filePath, fileNodeId) {
|
|
62
|
-
const sites = [];
|
|
63
|
-
const methodPattern = /(\w+)\.(\w+)\s*\(/g;
|
|
64
|
-
let m;
|
|
65
|
-
while ((m = methodPattern.exec(source)) !== null) {
|
|
66
|
-
sites.push({ callerFileNodeId: fileNodeId, callerFilePath: filePath, calleeRaw: `${m[1]}.${m[2]}`, form: 'method', receiverName: m[1], methodName: m[2] });
|
|
67
|
-
}
|
|
68
|
-
const directPattern = /(?<![.[\w])([A-Za-z_$][\w$]*)\s*\(/g;
|
|
69
|
-
while ((m = directPattern.exec(source)) !== null) {
|
|
70
|
-
const name = m[1];
|
|
71
|
-
if (JAVA_KEYWORDS.has(name))
|
|
72
|
-
continue;
|
|
73
|
-
sites.push({ callerFileNodeId: fileNodeId, callerFilePath: filePath, calleeRaw: name, form: 'direct' });
|
|
74
|
-
}
|
|
75
|
-
return sites;
|
|
76
|
-
}
|
|
77
|
-
export function extractRustCallSites(source, filePath, fileNodeId) {
|
|
78
|
-
const sites = [];
|
|
79
|
-
const methodPattern = /(\w+)\.(\w+)\s*\(/g;
|
|
80
|
-
let m;
|
|
81
|
-
while ((m = methodPattern.exec(source)) !== null) {
|
|
82
|
-
sites.push({ callerFileNodeId: fileNodeId, callerFilePath: filePath, calleeRaw: `${m[1]}.${m[2]}`, form: 'method', receiverName: m[1], methodName: m[2] });
|
|
83
|
-
}
|
|
84
|
-
const directPattern = /(?<![.[\w])([A-Za-z_][\w]*)\s*\(/g;
|
|
85
|
-
while ((m = directPattern.exec(source)) !== null) {
|
|
86
|
-
const name = m[1];
|
|
87
|
-
if (RUST_KEYWORDS.has(name))
|
|
88
|
-
continue;
|
|
89
|
-
sites.push({ callerFileNodeId: fileNodeId, callerFilePath: filePath, calleeRaw: name, form: 'direct' });
|
|
90
|
-
}
|
|
91
|
-
return sites;
|
|
92
|
-
}
|
|
93
|
-
// ── Stage 0.5: Track constructor assignments for type-aware method resolution ─
|
|
94
|
-
/**
|
|
95
|
-
* Scan source for `const/let/var x = new ClassName(...)` patterns.
|
|
96
|
-
* Returns a map: localVarName → ClassName (e.g. "svc" → "MyService").
|
|
97
|
-
*/
|
|
98
|
-
function extractConstructorAssignments(source) {
|
|
99
|
-
const result = new Map();
|
|
100
|
-
const pattern = /(?:const|let|var)\s+(\w+)\s*=\s*new\s+([A-Z][\w$]*)\s*(?:<[^>]*>\s*)?\(/g;
|
|
101
|
-
let m;
|
|
102
|
-
while ((m = pattern.exec(source)) !== null) {
|
|
103
|
-
result.set(m[1], m[2]);
|
|
104
|
-
}
|
|
105
|
-
return result;
|
|
106
|
-
}
|
|
107
|
-
// ── Stage 1 + 2: Extract and classify call sites ──────────────────────────────
|
|
108
|
-
function extractCallSites(source, filePath, fileNodeId, ext) {
|
|
109
|
-
const sites = [];
|
|
110
|
-
if (TS_JS_EXTS.has(ext) || CJS_MJS_EXTS.has(ext)) {
|
|
111
|
-
// Method calls: word.word( or word.word<T>(
|
|
112
|
-
const methodPattern = /(\w+)\.(\w+)\s*(?:<[^>]*>\s*)?\(/g;
|
|
113
|
-
let m;
|
|
114
|
-
while ((m = methodPattern.exec(source)) !== null) {
|
|
115
|
-
sites.push({
|
|
116
|
-
callerFileNodeId: fileNodeId,
|
|
117
|
-
callerFilePath: filePath,
|
|
118
|
-
calleeRaw: `${m[1]}.${m[2]}`,
|
|
119
|
-
form: 'method',
|
|
120
|
-
receiverName: m[1],
|
|
121
|
-
methodName: m[2],
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
// Chained method calls: ).method( or ].method( — receiver unknown, resolve by method name
|
|
125
|
-
const chainedPattern = /[)\]]\s*\.(\w+)\s*(?:<[^>]*>\s*)?\(/g;
|
|
126
|
-
while ((m = chainedPattern.exec(source)) !== null) {
|
|
127
|
-
const name = m[1];
|
|
128
|
-
if (JS_KEYWORDS.has(name))
|
|
129
|
-
continue;
|
|
130
|
-
sites.push({
|
|
131
|
-
callerFileNodeId: fileNodeId,
|
|
132
|
-
callerFilePath: filePath,
|
|
133
|
-
calleeRaw: `?.${name}`,
|
|
134
|
-
form: 'method',
|
|
135
|
-
methodName: name,
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
// Direct calls: word( or word<T>( — not preceded by . or word char
|
|
139
|
-
const directPattern = /(?<![.[\w])([A-Za-z_$][\w$]*)\s*(?:<[^>]*>\s*)?\(/g;
|
|
140
|
-
while ((m = directPattern.exec(source)) !== null) {
|
|
141
|
-
const name = m[1];
|
|
142
|
-
if (JS_KEYWORDS.has(name))
|
|
143
|
-
continue;
|
|
144
|
-
sites.push({
|
|
145
|
-
callerFileNodeId: fileNodeId,
|
|
146
|
-
callerFilePath: filePath,
|
|
147
|
-
calleeRaw: name,
|
|
148
|
-
form: 'direct',
|
|
149
|
-
methodName: name,
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
// Constructor calls: new ClassName( or new ClassName<T>(
|
|
153
|
-
const newPattern = /\bnew\s+([A-Z][\w$]*)\s*(?:<[^>]*>\s*)?\(/g;
|
|
154
|
-
while ((m = newPattern.exec(source)) !== null) {
|
|
155
|
-
sites.push({
|
|
156
|
-
callerFileNodeId: fileNodeId,
|
|
157
|
-
callerFilePath: filePath,
|
|
158
|
-
calleeRaw: `new ${m[1]}`,
|
|
159
|
-
form: 'direct',
|
|
160
|
-
methodName: m[1],
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
// Dynamic calls: obj[key]( → mark as dynamic
|
|
164
|
-
const dynamicPattern = /\w+\s*\[[\w'"` ]+\]\s*\(/g;
|
|
165
|
-
while ((m = dynamicPattern.exec(source)) !== null) {
|
|
166
|
-
sites.push({
|
|
167
|
-
callerFileNodeId: fileNodeId,
|
|
168
|
-
callerFilePath: filePath,
|
|
169
|
-
calleeRaw: m[0],
|
|
170
|
-
form: 'dynamic',
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
else if (PY_EXTS.has(ext)) {
|
|
175
|
-
// Method calls: self.method( or obj.method(
|
|
176
|
-
const methodPattern = /(\w+)\.(\w+)\s*\(/g;
|
|
177
|
-
let m;
|
|
178
|
-
while ((m = methodPattern.exec(source)) !== null) {
|
|
179
|
-
sites.push({
|
|
180
|
-
callerFileNodeId: fileNodeId,
|
|
181
|
-
callerFilePath: filePath,
|
|
182
|
-
calleeRaw: `${m[1]}.${m[2]}`,
|
|
183
|
-
form: 'method',
|
|
184
|
-
receiverName: m[1],
|
|
185
|
-
methodName: m[2],
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
// Direct calls: word( not preceded by . or word char
|
|
189
|
-
const directPattern = /(?<![.[\w])([A-Za-z_][\w]*)\s*\(/g;
|
|
190
|
-
while ((m = directPattern.exec(source)) !== null) {
|
|
191
|
-
const name = m[1];
|
|
192
|
-
if (PY_KEYWORDS.has(name))
|
|
193
|
-
continue;
|
|
194
|
-
sites.push({
|
|
195
|
-
callerFileNodeId: fileNodeId,
|
|
196
|
-
callerFilePath: filePath,
|
|
197
|
-
calleeRaw: name,
|
|
198
|
-
form: 'direct',
|
|
199
|
-
methodName: name,
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
else if (GO_EXTS.has(ext)) {
|
|
204
|
-
return extractGoCallSites(source, filePath, fileNodeId);
|
|
205
|
-
}
|
|
206
|
-
else if (JAVA_EXTS.has(ext)) {
|
|
207
|
-
return extractJavaCallSites(source, filePath, fileNodeId);
|
|
208
|
-
}
|
|
209
|
-
else if (RUST_EXTS.has(ext)) {
|
|
210
|
-
return extractRustCallSites(source, filePath, fileNodeId);
|
|
211
|
-
}
|
|
212
|
-
return sites;
|
|
213
|
-
}
|
|
214
|
-
// ── Stage 3: Build import maps by parsing source imports ─────────────────────
|
|
215
|
-
const IMPORT_RE = /import\s+(?:type\s+)?(?:\{([^}]+)\}|(\w+)|\*\s+as\s+(\w+))(?:\s*,\s*(?:\{([^}]+)\}|(\w+)|\*\s+as\s+(\w+)))?\s+from\s+['"]([^'"]+)['"]/g;
|
|
216
|
-
// CJS require: const x = require('y') or const { a, b } = require('y')
|
|
217
|
-
const REQUIRE_RE = /(?:const|let|var)\s+(?:\{([^}]+)\}|(\w+))\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
218
|
-
// Python: from x import y, z OR import x
|
|
219
|
-
const PY_FROM_IMPORT_RE = /from\s+([\w.]+)\s+import\s+(.+)/g;
|
|
220
|
-
const PY_IMPORT_RE = /^import\s+([\w.]+)(?:\s+as\s+(\w+))?/gm;
|
|
221
|
-
// Go: import "path" or import ( "path" )
|
|
222
|
-
const GO_IMPORT_RE = /import\s+(?:"([^"]+)"|(?:\w+\s+)?"([^"]+)"|\(\s*([\s\S]*?)\s*\))/g;
|
|
223
|
-
// Java: import com.foo.Bar;
|
|
224
|
-
const JAVA_IMPORT_RE = /import\s+(?:static\s+)?([\w.]+)\s*;/g;
|
|
225
|
-
// Rust: use crate::module::Item; or use super::foo;
|
|
226
|
-
const RUST_USE_RE = /use\s+((?:crate|super|self)(?:::\w+)+)(?:::\{([^}]+)\})?;/g;
|
|
227
|
-
// TS/JS re-exports: export { foo, bar } from './module'
|
|
228
|
-
const REEXPORT_RE = /export\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/g;
|
|
229
|
-
const RESOLVE_EXTS = ['.ts', '.tsx', '.js', '.jsx'];
|
|
230
|
-
const PY_RESOLVE_EXTS = ['.py'];
|
|
231
|
-
const GO_RESOLVE_EXTS = ['.go'];
|
|
232
|
-
const JAVA_RESOLVE_EXTS = ['.java'];
|
|
233
|
-
const RUST_RESOLVE_EXTS = ['.rs'];
|
|
234
|
-
const workspacePackageMapCache = new Map();
|
|
235
|
-
/** Invalidate the cached workspace package map for a repo — call at the start
|
|
236
|
-
* of each build so long-lived processes (watch mode) pick up package.json
|
|
237
|
-
* additions/removals instead of serving a stale map from a prior build. */
|
|
238
|
-
export function clearWorkspacePackageMapCache(repoPath) {
|
|
239
|
-
workspacePackageMapCache.delete(repoPath);
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* Build package-name → directory map from workspace package.json files.
|
|
243
|
-
* Scans packages/ for package.json and maps npm name to its relative src path.
|
|
244
|
-
* Cached per repoPath — multiple pipeline phases (cross-file, scope-resolution,
|
|
245
|
-
* the latter's own re-export loop) call this per-file/per-edge within the same
|
|
246
|
-
* build, and the workspace's package.json set doesn't change mid-build.
|
|
247
|
-
*/
|
|
248
|
-
export function buildWorkspacePackageMap(repoPath) {
|
|
249
|
-
const cached = workspacePackageMapCache.get(repoPath);
|
|
250
|
-
if (cached)
|
|
251
|
-
return cached;
|
|
252
|
-
const result = new Map();
|
|
253
|
-
const packagesDir = join(repoPath, 'packages');
|
|
254
|
-
try {
|
|
255
|
-
const scanDirs = (base, depth) => {
|
|
256
|
-
if (depth > 2)
|
|
257
|
-
return;
|
|
258
|
-
let entries;
|
|
259
|
-
try {
|
|
260
|
-
entries = require('fs').readdirSync(base);
|
|
261
|
-
}
|
|
262
|
-
catch {
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
for (const e of entries) {
|
|
266
|
-
const full = join(base, e);
|
|
267
|
-
const pkgJson = join(full, 'package.json');
|
|
268
|
-
try {
|
|
269
|
-
const pkg = JSON.parse(readFileSync(pkgJson, 'utf-8'));
|
|
270
|
-
if (pkg.name) {
|
|
271
|
-
const relDir = full.slice(repoPath.length + 1); // e.g. packages/@monomind/cli
|
|
272
|
-
result.set(pkg.name, relDir);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
catch {
|
|
276
|
-
// No package.json — recurse for @scoped dirs
|
|
277
|
-
if (e.startsWith('@'))
|
|
278
|
-
scanDirs(full, depth + 1);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
};
|
|
282
|
-
scanDirs(packagesDir, 0);
|
|
283
|
-
}
|
|
284
|
-
catch { /* no packages dir */ }
|
|
285
|
-
workspacePackageMapCache.set(repoPath, result);
|
|
286
|
-
return result;
|
|
287
|
-
}
|
|
288
|
-
export function resolveModuleSpecifier(importerPath, specifier, repoPath, knownFiles, workspaceMap) {
|
|
289
|
-
if (specifier.startsWith('.')) {
|
|
290
|
-
const dir = dirname(importerPath);
|
|
291
|
-
const raw = resolvePath('/', dir, specifier).slice(1);
|
|
292
|
-
// Strip .js/.jsx — TS source uses .js extensions but files are .ts
|
|
293
|
-
const base = raw.replace(/\.(js|jsx)$/, '');
|
|
294
|
-
for (const candidate of [
|
|
295
|
-
raw,
|
|
296
|
-
base,
|
|
297
|
-
...RESOLVE_EXTS.map(e => base + e),
|
|
298
|
-
...RESOLVE_EXTS.map(e => base + '/index' + e),
|
|
299
|
-
]) {
|
|
300
|
-
if (knownFiles.has(candidate))
|
|
301
|
-
return candidate;
|
|
302
|
-
}
|
|
303
|
-
for (const ext of RESOLVE_EXTS) {
|
|
304
|
-
if (existsSync(join(repoPath, base + ext)))
|
|
305
|
-
return base + ext;
|
|
306
|
-
}
|
|
307
|
-
return null;
|
|
308
|
-
}
|
|
309
|
-
// Workspace package specifier: @monoes/hooks → packages/@monomind/hooks/src/index.ts
|
|
310
|
-
// Also handles subpath: @monoes/hooks/src/foo → packages/@monomind/hooks/src/foo.ts
|
|
311
|
-
for (const [pkgName, pkgDir] of workspaceMap) {
|
|
312
|
-
if (specifier === pkgName) {
|
|
313
|
-
// Bare import: resolve to package entry point (src/index.ts)
|
|
314
|
-
for (const entry of RESOLVE_EXTS.map(e => pkgDir + '/src/index' + e)) {
|
|
315
|
-
if (knownFiles.has(entry))
|
|
316
|
-
return entry;
|
|
317
|
-
}
|
|
318
|
-
return null;
|
|
319
|
-
}
|
|
320
|
-
if (specifier.startsWith(pkgName + '/')) {
|
|
321
|
-
const subpath = specifier.slice(pkgName.length + 1);
|
|
322
|
-
const base = pkgDir + '/' + subpath;
|
|
323
|
-
for (const candidate of [
|
|
324
|
-
base,
|
|
325
|
-
...RESOLVE_EXTS.map(e => base + e),
|
|
326
|
-
...RESOLVE_EXTS.map(e => base + '/index' + e),
|
|
327
|
-
]) {
|
|
328
|
-
if (knownFiles.has(candidate))
|
|
329
|
-
return candidate;
|
|
330
|
-
}
|
|
331
|
-
return null;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
return null;
|
|
335
|
-
}
|
|
336
|
-
function resolvePythonModule(importerPath, modulePath, knownFiles) {
|
|
337
|
-
// Try relative to importer's directory, then absolute from repo root
|
|
338
|
-
const dir = dirname(importerPath);
|
|
339
|
-
for (const base of [dir + '/' + modulePath, modulePath]) {
|
|
340
|
-
for (const candidate of [base + '.py', base + '/__init__.py']) {
|
|
341
|
-
if (knownFiles.has(candidate))
|
|
342
|
-
return candidate;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
return null;
|
|
346
|
-
}
|
|
347
|
-
function resolveGoPackage(importerPath, goPath, knownFiles) {
|
|
348
|
-
for (const f of knownFiles) {
|
|
349
|
-
if (f.startsWith(goPath + '/') && f.endsWith('.go'))
|
|
350
|
-
return f;
|
|
351
|
-
}
|
|
352
|
-
return null;
|
|
353
|
-
}
|
|
354
|
-
function resolveJavaImport(qualifiedName, knownFiles) {
|
|
355
|
-
// com.foo.Bar → com/foo/Bar.java or src/main/java/com/foo/Bar.java
|
|
356
|
-
const pathPart = qualifiedName.replace(/\./g, '/');
|
|
357
|
-
for (const candidate of [pathPart + '.java', 'src/main/java/' + pathPart + '.java', 'src/' + pathPart + '.java']) {
|
|
358
|
-
if (knownFiles.has(candidate))
|
|
359
|
-
return candidate;
|
|
360
|
-
}
|
|
361
|
-
// Wildcard: com.foo.* → find any .java file in com/foo/
|
|
362
|
-
if (pathPart.endsWith('/*')) {
|
|
363
|
-
const dir = pathPart.slice(0, -2);
|
|
364
|
-
for (const f of knownFiles) {
|
|
365
|
-
if (f.endsWith('.java') && (f.startsWith(dir + '/') || f.includes('/' + dir + '/')))
|
|
366
|
-
return f;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
return null;
|
|
370
|
-
}
|
|
371
|
-
function resolveRustUse(usePath, importerPath, knownFiles) {
|
|
372
|
-
// crate::module::item → src/module.rs or src/module/mod.rs
|
|
373
|
-
const parts = usePath.split('::');
|
|
374
|
-
if (parts[0] === 'crate')
|
|
375
|
-
parts[0] = 'src';
|
|
376
|
-
else if (parts[0] === 'super') {
|
|
377
|
-
const dir = dirname(importerPath);
|
|
378
|
-
parts[0] = dirname(dir);
|
|
379
|
-
}
|
|
380
|
-
else if (parts[0] === 'self') {
|
|
381
|
-
parts[0] = dirname(importerPath);
|
|
382
|
-
}
|
|
383
|
-
// Last segment is the item name — try with and without it
|
|
384
|
-
for (let len = parts.length; len >= 2; len--) {
|
|
385
|
-
const base = parts.slice(0, len).join('/');
|
|
386
|
-
for (const candidate of [base + '.rs', base + '/mod.rs']) {
|
|
387
|
-
if (knownFiles.has(candidate))
|
|
388
|
-
return candidate;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
return null;
|
|
392
|
-
}
|
|
393
|
-
function extractImportNames(clause) {
|
|
394
|
-
return clause
|
|
395
|
-
.split(',')
|
|
396
|
-
.map(s => s.trim().split(/\s+as\s+/).pop().trim())
|
|
397
|
-
.filter(Boolean);
|
|
398
|
-
}
|
|
399
|
-
/**
|
|
400
|
-
* Parse each file's import statements to build importedName → resolvedFilePath maps.
|
|
401
|
-
* Replaces the broken DB-edge approach (IMPORTS edges target Variable nodes, not Files).
|
|
402
|
-
*/
|
|
403
|
-
function buildAllImportMapsFromSource(repoPath, fileNodesByPath, fileContents) {
|
|
404
|
-
const result = new Map();
|
|
405
|
-
const knownFiles = new Set(fileNodesByPath.keys());
|
|
406
|
-
const workspaceMap = buildWorkspacePackageMap(repoPath);
|
|
407
|
-
for (const [filePath, fileNode] of fileNodesByPath) {
|
|
408
|
-
const ext = extname(filePath).toLowerCase();
|
|
409
|
-
const importMap = new Map();
|
|
410
|
-
let source = fileContents?.get(filePath);
|
|
411
|
-
if (!source) {
|
|
412
|
-
try {
|
|
413
|
-
source = readFileSync(join(repoPath, filePath), 'utf-8');
|
|
414
|
-
}
|
|
415
|
-
catch {
|
|
416
|
-
continue;
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
if (TS_JS_EXTS.has(ext) || ext === '.cjs' || ext === '.mjs') {
|
|
420
|
-
// ESM imports
|
|
421
|
-
IMPORT_RE.lastIndex = 0;
|
|
422
|
-
let m;
|
|
423
|
-
while ((m = IMPORT_RE.exec(source)) !== null) {
|
|
424
|
-
const specifier = m[7];
|
|
425
|
-
const resolved = resolveModuleSpecifier(filePath, specifier, repoPath, knownFiles, workspaceMap);
|
|
426
|
-
if (!resolved)
|
|
427
|
-
continue;
|
|
428
|
-
if (m[1])
|
|
429
|
-
for (const n of extractImportNames(m[1]))
|
|
430
|
-
importMap.set(n, resolved);
|
|
431
|
-
if (m[4])
|
|
432
|
-
for (const n of extractImportNames(m[4]))
|
|
433
|
-
importMap.set(n, resolved);
|
|
434
|
-
if (m[2])
|
|
435
|
-
importMap.set(m[2], resolved);
|
|
436
|
-
if (m[5])
|
|
437
|
-
importMap.set(m[5], resolved);
|
|
438
|
-
if (m[3])
|
|
439
|
-
importMap.set(m[3], resolved);
|
|
440
|
-
if (m[6])
|
|
441
|
-
importMap.set(m[6], resolved);
|
|
442
|
-
const baseName = resolved.split('/').pop().replace(/\.\w+$/, '');
|
|
443
|
-
importMap.set(baseName, resolved);
|
|
444
|
-
}
|
|
445
|
-
// CJS require()
|
|
446
|
-
REQUIRE_RE.lastIndex = 0;
|
|
447
|
-
while ((m = REQUIRE_RE.exec(source)) !== null) {
|
|
448
|
-
const specifier = m[3];
|
|
449
|
-
const resolved = resolveModuleSpecifier(filePath, specifier, repoPath, knownFiles, workspaceMap);
|
|
450
|
-
if (!resolved)
|
|
451
|
-
continue;
|
|
452
|
-
if (m[1])
|
|
453
|
-
for (const n of extractImportNames(m[1]))
|
|
454
|
-
importMap.set(n, resolved);
|
|
455
|
-
if (m[2])
|
|
456
|
-
importMap.set(m[2], resolved);
|
|
457
|
-
const baseName = resolved.split('/').pop().replace(/\.\w+$/, '');
|
|
458
|
-
importMap.set(baseName, resolved);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
else if (PY_EXTS.has(ext)) {
|
|
462
|
-
// Python: from module import name
|
|
463
|
-
PY_FROM_IMPORT_RE.lastIndex = 0;
|
|
464
|
-
let m;
|
|
465
|
-
while ((m = PY_FROM_IMPORT_RE.exec(source)) !== null) {
|
|
466
|
-
const modulePath = m[1].replace(/\./g, '/');
|
|
467
|
-
const resolved = resolvePythonModule(filePath, modulePath, knownFiles);
|
|
468
|
-
if (!resolved)
|
|
469
|
-
continue;
|
|
470
|
-
for (const name of m[2].split(',').map(s => s.trim().split(/\s+as\s+/).pop().trim()).filter(Boolean)) {
|
|
471
|
-
importMap.set(name, resolved);
|
|
472
|
-
}
|
|
473
|
-
const baseName = resolved.split('/').pop().replace(/\.\w+$/, '');
|
|
474
|
-
importMap.set(baseName, resolved);
|
|
475
|
-
}
|
|
476
|
-
// Python: import module
|
|
477
|
-
PY_IMPORT_RE.lastIndex = 0;
|
|
478
|
-
while ((m = PY_IMPORT_RE.exec(source)) !== null) {
|
|
479
|
-
const modulePath = m[1].replace(/\./g, '/');
|
|
480
|
-
const resolved = resolvePythonModule(filePath, modulePath, knownFiles);
|
|
481
|
-
if (!resolved)
|
|
482
|
-
continue;
|
|
483
|
-
const alias = m[2] ?? m[1].split('.').pop();
|
|
484
|
-
importMap.set(alias, resolved);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
else if (GO_EXTS.has(ext)) {
|
|
488
|
-
GO_IMPORT_RE.lastIndex = 0;
|
|
489
|
-
let m;
|
|
490
|
-
while ((m = GO_IMPORT_RE.exec(source)) !== null) {
|
|
491
|
-
const paths = m[3]
|
|
492
|
-
? m[3].match(/"([^"]+)"/g)?.map(s => s.slice(1, -1)) ?? []
|
|
493
|
-
: [m[1] ?? m[2]].filter(Boolean);
|
|
494
|
-
for (const goPath of paths) {
|
|
495
|
-
const resolved = resolveGoPackage(filePath, goPath, knownFiles);
|
|
496
|
-
if (!resolved)
|
|
497
|
-
continue;
|
|
498
|
-
const pkgName = goPath.split('/').pop();
|
|
499
|
-
importMap.set(pkgName, resolved);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
else if (JAVA_EXTS.has(ext)) {
|
|
504
|
-
JAVA_IMPORT_RE.lastIndex = 0;
|
|
505
|
-
let m;
|
|
506
|
-
while ((m = JAVA_IMPORT_RE.exec(source)) !== null) {
|
|
507
|
-
const resolved = resolveJavaImport(m[1], knownFiles);
|
|
508
|
-
if (!resolved)
|
|
509
|
-
continue;
|
|
510
|
-
const className = m[1].split('.').pop();
|
|
511
|
-
importMap.set(className, resolved);
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
else if (RUST_EXTS.has(ext)) {
|
|
515
|
-
RUST_USE_RE.lastIndex = 0;
|
|
516
|
-
let m;
|
|
517
|
-
while ((m = RUST_USE_RE.exec(source)) !== null) {
|
|
518
|
-
if (m[2]) {
|
|
519
|
-
// use crate::module::{Item1, Item2}
|
|
520
|
-
for (const name of m[2].split(',').map(s => s.trim()).filter(Boolean)) {
|
|
521
|
-
const resolved = resolveRustUse(m[1] + '::' + name, filePath, knownFiles);
|
|
522
|
-
if (resolved)
|
|
523
|
-
importMap.set(name.split('::').pop(), resolved);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
else {
|
|
527
|
-
const resolved = resolveRustUse(m[1], filePath, knownFiles);
|
|
528
|
-
if (!resolved)
|
|
529
|
-
continue;
|
|
530
|
-
const itemName = m[1].split('::').pop();
|
|
531
|
-
importMap.set(itemName, resolved);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
if (importMap.size > 0) {
|
|
536
|
-
result.set(fileNode.id, importMap);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
return result;
|
|
540
|
-
}
|
|
541
|
-
function getImportMap(allImportMaps, fileNodeId) {
|
|
542
|
-
return allImportMaps.get(fileNodeId) ?? new Map();
|
|
543
|
-
}
|
|
544
|
-
// ── Stage 4 helpers: preloaded function/method index ──────────────────────────
|
|
545
|
-
/**
|
|
546
|
-
* Load all callable nodes once (Function, Method, Constructor, Class).
|
|
547
|
-
* Class is included so `new ClassName()` can resolve to Class nodes.
|
|
548
|
-
*/
|
|
4
|
+
import { TS_JS_EXTS, CJS_MJS_EXTS, isSupportedExt, extractCallSites, extractConstructorAssignments } from './call-site-extractors.js';
|
|
5
|
+
import { buildWorkspacePackageMap, resolveModuleSpecifier, extractImportNames, buildAllImportMapsFromSource, REEXPORT_RE } from './module-resolution.js';
|
|
6
|
+
// Re-export for external consumers
|
|
7
|
+
export { clearWorkspacePackageMapCache, buildWorkspacePackageMap, resolveModuleSpecifier } from './module-resolution.js';
|
|
8
|
+
export { extractGoCallSites, extractJavaCallSites, extractRustCallSites } from './call-site-extractors.js';
|
|
9
|
+
// ── Function index ───────────────────────────────────────────────────────────
|
|
549
10
|
function buildFunctionIndex(ctx) {
|
|
550
11
|
const byFilePath = new Map();
|
|
551
12
|
const nameCounts = new Map();
|
|
@@ -555,7 +16,6 @@ function buildFunctionIndex(ctx) {
|
|
|
555
16
|
.prepare(`SELECT id, name, file_path FROM nodes WHERE label IN ('Function', 'Method', 'Constructor', 'Class') AND file_path IS NOT NULL`)
|
|
556
17
|
.all();
|
|
557
18
|
for (const row of rows) {
|
|
558
|
-
// byFilePath index
|
|
559
19
|
let fileMap = byFilePath.get(row.file_path);
|
|
560
20
|
if (!fileMap) {
|
|
561
21
|
fileMap = new Map();
|
|
@@ -567,21 +27,18 @@ function buildFunctionIndex(ctx) {
|
|
|
567
27
|
fileMap.set(row.name, ids);
|
|
568
28
|
}
|
|
569
29
|
ids.push(row.id);
|
|
570
|
-
// global count for ambiguity detection
|
|
571
30
|
nameCounts.set(row.name, (nameCounts.get(row.name) ?? 0) + 1);
|
|
572
31
|
}
|
|
573
32
|
return { byFilePath, nameCounts };
|
|
574
33
|
}
|
|
575
|
-
// ──
|
|
34
|
+
// ── Target resolution ────────────────────────────────────────────────────────
|
|
576
35
|
function pickBestId(ids, site) {
|
|
577
36
|
if (ids.length === 1)
|
|
578
37
|
return ids[0];
|
|
579
38
|
if (ids.length === 0)
|
|
580
39
|
return null;
|
|
581
|
-
// Disambiguate: prefer Function for direct calls, Method for method calls
|
|
582
40
|
const suffix = site.form === 'method' ? '_method' : '_function';
|
|
583
41
|
const match = ids.find(id => id.endsWith(suffix));
|
|
584
|
-
// For `new ClassName()` prefer _class
|
|
585
42
|
if (!match && site.calleeRaw.startsWith('new ')) {
|
|
586
43
|
const classMatch = ids.find(id => id.endsWith('_class'));
|
|
587
44
|
if (classMatch)
|
|
@@ -670,6 +127,9 @@ function emitEdge(stmts, sourceId, targetId) {
|
|
|
670
127
|
}
|
|
671
128
|
}
|
|
672
129
|
// ── Phase definition ──────────────────────────────────────────────────────────
|
|
130
|
+
function getImportMap(allImportMaps, fileNodeId) {
|
|
131
|
+
return allImportMaps.get(fileNodeId) ?? new Map();
|
|
132
|
+
}
|
|
673
133
|
export const scopeResolutionPhase = {
|
|
674
134
|
name: 'scope-resolution',
|
|
675
135
|
deps: ['parse', 'cross-file'],
|
|
@@ -677,19 +137,16 @@ export const scopeResolutionPhase = {
|
|
|
677
137
|
if (ctx.allFilesCached) {
|
|
678
138
|
return { resolvedEdges: 0, skippedDynamic: 0, ambiguous: 0, reexportEdges: 0, orphanImportsRemoved: 0, importsReconstructed: 0 };
|
|
679
139
|
}
|
|
680
|
-
const { symbolNodes } = deps.get('parse');
|
|
140
|
+
const { symbolNodes, fileContents } = deps.get('parse');
|
|
681
141
|
let resolvedEdges = 0;
|
|
682
142
|
let skippedDynamic = 0;
|
|
683
143
|
let ambiguous = 0;
|
|
684
|
-
// ── Batch preload: one query per table instead of one per file/call-site ──
|
|
685
|
-
// Build a map from file_path → file node id using the parse output
|
|
686
144
|
const fileNodesByPath = new Map();
|
|
687
145
|
for (const node of symbolNodes) {
|
|
688
146
|
if (node.label === 'File' && node.filePath) {
|
|
689
147
|
fileNodesByPath.set(node.filePath, node);
|
|
690
148
|
}
|
|
691
149
|
}
|
|
692
|
-
// Also look up File nodes from the DB (includes nodes created by structure phase)
|
|
693
150
|
if (ctx.db) {
|
|
694
151
|
const dbFileNodes = ctx.db
|
|
695
152
|
.prepare(`SELECT id, file_path FROM nodes WHERE label = 'File'`)
|
|
@@ -707,10 +164,7 @@ export const scopeResolutionPhase = {
|
|
|
707
164
|
}
|
|
708
165
|
}
|
|
709
166
|
}
|
|
710
|
-
// Parse source imports → per-file import maps (replaces broken DB-edge approach)
|
|
711
|
-
const { fileContents } = deps.get('parse');
|
|
712
167
|
const allImportMaps = buildAllImportMapsFromSource(ctx.repoPath, fileNodesByPath, fileContents);
|
|
713
|
-
// Preload all Function/Method/Constructor nodes into indices (one DB query total)
|
|
714
168
|
const { byFilePath: fnIndex, nameCounts } = buildFunctionIndex(ctx);
|
|
715
169
|
const edgeStmts = ctx.db ? prepareEdgeStmts(ctx.db) : null;
|
|
716
170
|
const resolveAllCalls = ctx.db?.transaction(() => {
|
|
@@ -718,7 +172,6 @@ export const scopeResolutionPhase = {
|
|
|
718
172
|
const ext = extname(filePath).toLowerCase();
|
|
719
173
|
if (!isSupportedExt(ext))
|
|
720
174
|
continue;
|
|
721
|
-
// Read file source (prefer cached from parse phase)
|
|
722
175
|
let source = fileContents.get(filePath);
|
|
723
176
|
if (!source) {
|
|
724
177
|
try {
|
|
@@ -728,29 +181,21 @@ export const scopeResolutionPhase = {
|
|
|
728
181
|
continue;
|
|
729
182
|
}
|
|
730
183
|
}
|
|
731
|
-
// Stage 1+2: Extract call sites
|
|
732
184
|
const callSites = extractCallSites(source, filePath, fileNode.id, ext);
|
|
733
|
-
// Stage 3: Get pre-built import map for this file (O(1))
|
|
734
185
|
const importMap = getImportMap(allImportMaps, fileNode.id);
|
|
735
|
-
// Track constructor assignments for type-aware method resolution
|
|
736
186
|
const ctorMap = (TS_JS_EXTS.has(ext) || CJS_MJS_EXTS.has(ext)) ? extractConstructorAssignments(source) : undefined;
|
|
737
|
-
// Precompute once per file instead of per call site
|
|
738
187
|
const importedFiles = [...new Set(importMap.values())];
|
|
739
188
|
for (const site of callSites) {
|
|
740
189
|
if (site.form === 'dynamic') {
|
|
741
190
|
skippedDynamic++;
|
|
742
191
|
continue;
|
|
743
192
|
}
|
|
744
|
-
// Stage 4+5: Resolve target using preloaded indices (no DB query)
|
|
745
193
|
const resolved = resolveTarget(site, filePath, importMap, fnIndex, ctorMap, importedFiles);
|
|
746
|
-
if (!resolved)
|
|
194
|
+
if (!resolved)
|
|
747
195
|
continue;
|
|
748
|
-
}
|
|
749
|
-
// Ambiguity check using preloaded name counts (no DB query)
|
|
750
196
|
if (site.methodName && (nameCounts.get(site.methodName) ?? 0) > 1) {
|
|
751
197
|
ambiguous++;
|
|
752
198
|
}
|
|
753
|
-
// Stage 6: Emit edge (source is the file node of the caller)
|
|
754
199
|
const result = edgeStmts ? emitEdge(edgeStmts, site.callerFileNodeId, resolved.targetId) : 'skipped';
|
|
755
200
|
if (result === 'inserted' || result === 'upgraded') {
|
|
756
201
|
resolvedEdges++;
|
|
@@ -759,14 +204,14 @@ export const scopeResolutionPhase = {
|
|
|
759
204
|
}
|
|
760
205
|
});
|
|
761
206
|
resolveAllCalls?.();
|
|
762
|
-
// Scan for re-exports: export { foo, bar } from './module'
|
|
763
|
-
// Creates REFERENCES edges so re-exported symbols don't appear as dead code.
|
|
764
207
|
let reexportEdges = 0;
|
|
765
208
|
if (ctx.db) {
|
|
766
209
|
const insertRef = ctx.db.prepare(`
|
|
767
210
|
INSERT OR IGNORE INTO edges (id, source_id, target_id, relation, confidence, confidence_score)
|
|
768
211
|
VALUES (?, ?, ?, 'REFERENCES', 'EXTRACTED', 0.85)
|
|
769
212
|
`);
|
|
213
|
+
const reexportKnownFiles = new Set(fileNodesByPath.keys());
|
|
214
|
+
const reexportWorkspaceMap = buildWorkspacePackageMap(ctx.repoPath);
|
|
770
215
|
const insertReexports = ctx.db.transaction(() => {
|
|
771
216
|
for (const [filePath, fileNode] of fileNodesByPath) {
|
|
772
217
|
const ext = extname(filePath).toLowerCase();
|
|
@@ -781,20 +226,15 @@ export const scopeResolutionPhase = {
|
|
|
781
226
|
continue;
|
|
782
227
|
}
|
|
783
228
|
}
|
|
784
|
-
const importMap = getImportMap(allImportMaps, fileNode.id);
|
|
785
229
|
REEXPORT_RE.lastIndex = 0;
|
|
786
230
|
let m;
|
|
787
231
|
while ((m = REEXPORT_RE.exec(source)) !== null) {
|
|
788
232
|
const specifier = m[2];
|
|
789
233
|
const names = extractImportNames(m[1]);
|
|
790
|
-
|
|
791
|
-
const knownFiles = new Set(fileNodesByPath.keys());
|
|
792
|
-
const workspaceMap = buildWorkspacePackageMap(ctx.repoPath);
|
|
793
|
-
const resolvedFile = resolveModuleSpecifier(filePath, specifier, ctx.repoPath, knownFiles, workspaceMap);
|
|
234
|
+
const resolvedFile = resolveModuleSpecifier(filePath, specifier, ctx.repoPath, reexportKnownFiles, reexportWorkspaceMap);
|
|
794
235
|
if (!resolvedFile)
|
|
795
236
|
continue;
|
|
796
237
|
for (const name of names) {
|
|
797
|
-
// Find the function/class node in the source file
|
|
798
238
|
const ids = fnIndex.get(resolvedFile)?.get(name);
|
|
799
239
|
if (!ids || ids.length === 0)
|
|
800
240
|
continue;
|
|
@@ -811,7 +251,6 @@ export const scopeResolutionPhase = {
|
|
|
811
251
|
});
|
|
812
252
|
insertReexports();
|
|
813
253
|
}
|
|
814
|
-
// Clean up orphan IMPORTS edges that target Variable nodes instead of Files.
|
|
815
254
|
let orphanImportsRemoved = 0;
|
|
816
255
|
if (ctx.db) {
|
|
817
256
|
const result = ctx.db
|
|
@@ -825,7 +264,6 @@ export const scopeResolutionPhase = {
|
|
|
825
264
|
.run();
|
|
826
265
|
orphanImportsRemoved = result.changes;
|
|
827
266
|
}
|
|
828
|
-
// Reconstruct proper File→File IMPORTS edges from source-parsed import maps.
|
|
829
267
|
let importsReconstructed = 0;
|
|
830
268
|
if (ctx.db) {
|
|
831
269
|
const insertImport = ctx.db.prepare(`
|
|
@@ -847,7 +285,7 @@ export const scopeResolutionPhase = {
|
|
|
847
285
|
insertImport.run(edgeId, fileNodeId, targetFileId);
|
|
848
286
|
importsReconstructed++;
|
|
849
287
|
}
|
|
850
|
-
catch { /* duplicate
|
|
288
|
+
catch { /* duplicate */ }
|
|
851
289
|
}
|
|
852
290
|
}
|
|
853
291
|
});
|