@colbymchenry/codegraph-win32-x64 1.2.0 → 1.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/lib/dist/bin/codegraph.d.ts +1 -1
- package/lib/dist/bin/codegraph.d.ts.map +1 -1
- package/lib/dist/bin/codegraph.js +232 -61
- package/lib/dist/bin/codegraph.js.map +1 -1
- package/lib/dist/bin/command-supervision.d.ts.map +1 -1
- package/lib/dist/bin/command-supervision.js +4 -1
- package/lib/dist/bin/command-supervision.js.map +1 -1
- package/lib/dist/db/index.d.ts +17 -5
- package/lib/dist/db/index.d.ts.map +1 -1
- package/lib/dist/db/index.js +68 -12
- package/lib/dist/db/index.js.map +1 -1
- package/lib/dist/db/migrations.d.ts +1 -1
- package/lib/dist/db/migrations.d.ts.map +1 -1
- package/lib/dist/db/migrations.js +19 -1
- package/lib/dist/db/migrations.js.map +1 -1
- package/lib/dist/db/queries.d.ts +76 -1
- package/lib/dist/db/queries.d.ts.map +1 -1
- package/lib/dist/db/queries.js +180 -0
- package/lib/dist/db/queries.js.map +1 -1
- package/lib/dist/db/schema.sql +19 -0
- package/lib/dist/directory.d.ts +9 -5
- package/lib/dist/directory.d.ts.map +1 -1
- package/lib/dist/directory.js +210 -19
- package/lib/dist/directory.js.map +1 -1
- package/lib/dist/extraction/cfml-extractor.d.ts +107 -0
- package/lib/dist/extraction/cfml-extractor.d.ts.map +1 -0
- package/lib/dist/extraction/cfml-extractor.js +494 -0
- package/lib/dist/extraction/cfml-extractor.js.map +1 -0
- package/lib/dist/extraction/grammars.d.ts +9 -0
- package/lib/dist/extraction/grammars.d.ts.map +1 -1
- package/lib/dist/extraction/grammars.js +115 -3
- package/lib/dist/extraction/grammars.js.map +1 -1
- package/lib/dist/extraction/index.d.ts +46 -1
- package/lib/dist/extraction/index.d.ts.map +1 -1
- package/lib/dist/extraction/index.js +338 -34
- package/lib/dist/extraction/index.js.map +1 -1
- package/lib/dist/extraction/languages/arkts.d.ts +3 -0
- package/lib/dist/extraction/languages/arkts.d.ts.map +1 -0
- package/lib/dist/extraction/languages/arkts.js +127 -0
- package/lib/dist/extraction/languages/arkts.js.map +1 -0
- package/lib/dist/extraction/languages/c-cpp.d.ts +42 -0
- package/lib/dist/extraction/languages/c-cpp.d.ts.map +1 -1
- package/lib/dist/extraction/languages/c-cpp.js +369 -4
- package/lib/dist/extraction/languages/c-cpp.js.map +1 -1
- package/lib/dist/extraction/languages/cfquery.d.ts +12 -0
- package/lib/dist/extraction/languages/cfquery.d.ts.map +1 -0
- package/lib/dist/extraction/languages/cfquery.js +28 -0
- package/lib/dist/extraction/languages/cfquery.js.map +1 -0
- package/lib/dist/extraction/languages/cfscript.d.ts +3 -0
- package/lib/dist/extraction/languages/cfscript.d.ts.map +1 -0
- package/lib/dist/extraction/languages/cfscript.js +73 -0
- package/lib/dist/extraction/languages/cfscript.js.map +1 -0
- package/lib/dist/extraction/languages/cobol.d.ts +33 -0
- package/lib/dist/extraction/languages/cobol.d.ts.map +1 -0
- package/lib/dist/extraction/languages/cobol.js +499 -0
- package/lib/dist/extraction/languages/cobol.js.map +1 -0
- package/lib/dist/extraction/languages/erlang.d.ts +3 -0
- package/lib/dist/extraction/languages/erlang.d.ts.map +1 -0
- package/lib/dist/extraction/languages/erlang.js +350 -0
- package/lib/dist/extraction/languages/erlang.js.map +1 -0
- package/lib/dist/extraction/languages/index.d.ts.map +1 -1
- package/lib/dist/extraction/languages/index.js +18 -0
- package/lib/dist/extraction/languages/index.js.map +1 -1
- package/lib/dist/extraction/languages/nix.d.ts +3 -0
- package/lib/dist/extraction/languages/nix.d.ts.map +1 -0
- package/lib/dist/extraction/languages/nix.js +294 -0
- package/lib/dist/extraction/languages/nix.js.map +1 -0
- package/lib/dist/extraction/languages/solidity.d.ts +3 -0
- package/lib/dist/extraction/languages/solidity.d.ts.map +1 -0
- package/lib/dist/extraction/languages/solidity.js +293 -0
- package/lib/dist/extraction/languages/solidity.js.map +1 -0
- package/lib/dist/extraction/languages/terraform.d.ts +3 -0
- package/lib/dist/extraction/languages/terraform.d.ts.map +1 -0
- package/lib/dist/extraction/languages/terraform.js +641 -0
- package/lib/dist/extraction/languages/terraform.js.map +1 -0
- package/lib/dist/extraction/languages/vbnet.d.ts +11 -0
- package/lib/dist/extraction/languages/vbnet.d.ts.map +1 -0
- package/lib/dist/extraction/languages/vbnet.js +141 -0
- package/lib/dist/extraction/languages/vbnet.js.map +1 -0
- package/lib/dist/extraction/mybatis-extractor.d.ts +30 -10
- package/lib/dist/extraction/mybatis-extractor.d.ts.map +1 -1
- package/lib/dist/extraction/mybatis-extractor.js +140 -38
- package/lib/dist/extraction/mybatis-extractor.js.map +1 -1
- package/lib/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter-helpers.js +1 -0
- package/lib/dist/extraction/tree-sitter-helpers.js.map +1 -1
- package/lib/dist/extraction/tree-sitter-types.d.ts +3 -1
- package/lib/dist/extraction/tree-sitter-types.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter.d.ts +38 -0
- package/lib/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter.js +753 -10
- package/lib/dist/extraction/tree-sitter.js.map +1 -1
- package/lib/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
- package/lib/dist/index.d.ts +63 -1
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +327 -5
- package/lib/dist/index.js.map +1 -1
- package/lib/dist/installer/index.d.ts.map +1 -1
- package/lib/dist/installer/index.js +3 -1
- package/lib/dist/installer/index.js.map +1 -1
- package/lib/dist/mcp/daemon.d.ts +25 -3
- package/lib/dist/mcp/daemon.d.ts.map +1 -1
- package/lib/dist/mcp/daemon.js +62 -8
- package/lib/dist/mcp/daemon.js.map +1 -1
- package/lib/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
- package/lib/dist/mcp/dynamic-boundaries.js +2 -1
- package/lib/dist/mcp/dynamic-boundaries.js.map +1 -1
- package/lib/dist/mcp/early-ppid.d.ts +26 -0
- package/lib/dist/mcp/early-ppid.d.ts.map +1 -0
- package/lib/dist/mcp/early-ppid.js +29 -0
- package/lib/dist/mcp/early-ppid.js.map +1 -0
- package/lib/dist/mcp/index.d.ts.map +1 -1
- package/lib/dist/mcp/index.js +21 -4
- package/lib/dist/mcp/index.js.map +1 -1
- package/lib/dist/mcp/proxy.d.ts.map +1 -1
- package/lib/dist/mcp/proxy.js +29 -3
- package/lib/dist/mcp/proxy.js.map +1 -1
- package/lib/dist/mcp/query-pool.d.ts +14 -0
- package/lib/dist/mcp/query-pool.d.ts.map +1 -1
- package/lib/dist/mcp/query-pool.js +18 -0
- package/lib/dist/mcp/query-pool.js.map +1 -1
- package/lib/dist/mcp/session.d.ts.map +1 -1
- package/lib/dist/mcp/session.js +6 -0
- package/lib/dist/mcp/session.js.map +1 -1
- package/lib/dist/mcp/startup-handshake.d.ts +44 -0
- package/lib/dist/mcp/startup-handshake.d.ts.map +1 -0
- package/lib/dist/mcp/startup-handshake.js +73 -0
- package/lib/dist/mcp/startup-handshake.js.map +1 -0
- package/lib/dist/mcp/tools.d.ts +22 -0
- package/lib/dist/mcp/tools.d.ts.map +1 -1
- package/lib/dist/mcp/tools.js +88 -18
- package/lib/dist/mcp/tools.js.map +1 -1
- package/lib/dist/mcp/transport.d.ts.map +1 -1
- package/lib/dist/mcp/transport.js +18 -0
- package/lib/dist/mcp/transport.js.map +1 -1
- package/lib/dist/project-config.d.ts +38 -0
- package/lib/dist/project-config.d.ts.map +1 -1
- package/lib/dist/project-config.js +101 -2
- package/lib/dist/project-config.js.map +1 -1
- package/lib/dist/resolution/c-fnptr-synthesizer.d.ts +2 -1
- package/lib/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
- package/lib/dist/resolution/c-fnptr-synthesizer.js +170 -146
- package/lib/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
- package/lib/dist/resolution/callback-synthesizer.d.ts.map +1 -1
- package/lib/dist/resolution/callback-synthesizer.js +982 -81
- package/lib/dist/resolution/callback-synthesizer.js.map +1 -1
- package/lib/dist/resolution/frameworks/cics.d.ts +20 -0
- package/lib/dist/resolution/frameworks/cics.d.ts.map +1 -0
- package/lib/dist/resolution/frameworks/cics.js +90 -0
- package/lib/dist/resolution/frameworks/cics.js.map +1 -0
- package/lib/dist/resolution/frameworks/index.d.ts.map +1 -1
- package/lib/dist/resolution/frameworks/index.js +6 -0
- package/lib/dist/resolution/frameworks/index.js.map +1 -1
- package/lib/dist/resolution/frameworks/java.d.ts.map +1 -1
- package/lib/dist/resolution/frameworks/java.js +14 -6
- package/lib/dist/resolution/frameworks/java.js.map +1 -1
- package/lib/dist/resolution/frameworks/terraform.d.ts +38 -0
- package/lib/dist/resolution/frameworks/terraform.d.ts.map +1 -0
- package/lib/dist/resolution/frameworks/terraform.js +277 -0
- package/lib/dist/resolution/frameworks/terraform.js.map +1 -0
- package/lib/dist/resolution/goframe-synthesizer.d.ts +2 -1
- package/lib/dist/resolution/goframe-synthesizer.d.ts.map +1 -1
- package/lib/dist/resolution/goframe-synthesizer.js +8 -3
- package/lib/dist/resolution/goframe-synthesizer.js.map +1 -1
- package/lib/dist/resolution/import-resolver.d.ts +7 -0
- package/lib/dist/resolution/import-resolver.d.ts.map +1 -1
- package/lib/dist/resolution/import-resolver.js +129 -4
- package/lib/dist/resolution/import-resolver.js.map +1 -1
- package/lib/dist/resolution/index.d.ts +49 -6
- package/lib/dist/resolution/index.d.ts.map +1 -1
- package/lib/dist/resolution/index.js +342 -65
- package/lib/dist/resolution/index.js.map +1 -1
- package/lib/dist/resolution/name-matcher.d.ts +0 -3
- package/lib/dist/resolution/name-matcher.d.ts.map +1 -1
- package/lib/dist/resolution/name-matcher.js +225 -33
- package/lib/dist/resolution/name-matcher.js.map +1 -1
- package/lib/dist/resolution/strip-comments.d.ts +1 -1
- package/lib/dist/resolution/strip-comments.d.ts.map +1 -1
- package/lib/dist/resolution/strip-comments.js +49 -0
- package/lib/dist/resolution/strip-comments.js.map +1 -1
- package/lib/dist/resolution/types.d.ts +29 -0
- package/lib/dist/resolution/types.d.ts.map +1 -1
- package/lib/dist/resolution/workspace-packages.d.ts +10 -0
- package/lib/dist/resolution/workspace-packages.d.ts.map +1 -1
- package/lib/dist/resolution/workspace-packages.js +142 -4
- package/lib/dist/resolution/workspace-packages.js.map +1 -1
- package/lib/dist/search/identifier-segments.d.ts +60 -0
- package/lib/dist/search/identifier-segments.d.ts.map +1 -0
- package/lib/dist/search/identifier-segments.js +176 -0
- package/lib/dist/search/identifier-segments.js.map +1 -0
- package/lib/dist/sync/git-hooks.d.ts.map +1 -1
- package/lib/dist/sync/git-hooks.js +2 -0
- package/lib/dist/sync/git-hooks.js.map +1 -1
- package/lib/dist/sync/watcher.d.ts +10 -5
- package/lib/dist/sync/watcher.d.ts.map +1 -1
- package/lib/dist/sync/watcher.js +51 -14
- package/lib/dist/sync/watcher.js.map +1 -1
- package/lib/dist/sync/worktree.d.ts.map +1 -1
- package/lib/dist/sync/worktree.js +5 -0
- package/lib/dist/sync/worktree.js.map +1 -1
- package/lib/dist/types.d.ts +19 -1
- package/lib/dist/types.d.ts.map +1 -1
- package/lib/dist/types.js +10 -0
- package/lib/dist/types.js.map +1 -1
- package/lib/node_modules/.package-lock.json +1 -1
- package/lib/package.json +1 -1
- package/package.json +1 -1
- package/lib/dist/reasoning/config.d.ts +0 -45
- package/lib/dist/reasoning/config.d.ts.map +0 -1
- package/lib/dist/reasoning/config.js +0 -171
- package/lib/dist/reasoning/config.js.map +0 -1
- package/lib/dist/reasoning/credentials.d.ts +0 -5
- package/lib/dist/reasoning/credentials.d.ts.map +0 -1
- package/lib/dist/reasoning/credentials.js +0 -83
- package/lib/dist/reasoning/credentials.js.map +0 -1
- package/lib/dist/reasoning/login.d.ts +0 -21
- package/lib/dist/reasoning/login.d.ts.map +0 -1
- package/lib/dist/reasoning/login.js +0 -85
- package/lib/dist/reasoning/login.js.map +0 -1
- package/lib/dist/reasoning/reasoner.d.ts +0 -43
- package/lib/dist/reasoning/reasoner.d.ts.map +0 -1
- package/lib/dist/reasoning/reasoner.js +0 -308
- package/lib/dist/reasoning/reasoner.js.map +0 -1
|
@@ -240,6 +240,17 @@ class ReferenceResolver {
|
|
|
240
240
|
nameCache; // name → nodes cache
|
|
241
241
|
lowerNameCache; // lower(name) → nodes cache
|
|
242
242
|
qualifiedNameCache; // qualified_name → nodes cache
|
|
243
|
+
fileLinesCache; // file → split lines cache
|
|
244
|
+
methodMatchCache; // lang\0Type::method → matching method nodes
|
|
245
|
+
// Node kinds are a small fixed set (~24), so this is a plain Map, not an LRU.
|
|
246
|
+
// getNodesByKind returns the FULL node list for a kind; it was previously
|
|
247
|
+
// uncached — a per-ref `SELECT * FROM nodes WHERE kind=?` + row-mapping. Called
|
|
248
|
+
// for every dotted call ref by the Spring resolver (constants) and every
|
|
249
|
+
// `hook_` ref by the Drupal resolver (functions), that scan dominated
|
|
250
|
+
// resolution on large repos (#1180). The node set is stable within a
|
|
251
|
+
// resolution pass (same lifetime assumption as nameCache); clearCaches() resets
|
|
252
|
+
// it between passes. Callers must treat the returned array as read-only.
|
|
253
|
+
nodesByKindCache = new Map();
|
|
243
254
|
knownNames = null; // all known symbol names for fast pre-filtering
|
|
244
255
|
knownFiles = null;
|
|
245
256
|
cachesWarmed = false;
|
|
@@ -265,6 +276,10 @@ class ReferenceResolver {
|
|
|
265
276
|
this.nameCache = new lru_cache_1.LRUCache(limit);
|
|
266
277
|
this.lowerNameCache = new lru_cache_1.LRUCache(limit);
|
|
267
278
|
this.qualifiedNameCache = new lru_cache_1.LRUCache(limit);
|
|
279
|
+
// Split-lines arrays are heavier than content strings; refs arrive
|
|
280
|
+
// file-ordered, so a small cache still hits nearly always.
|
|
281
|
+
this.fileLinesCache = new lru_cache_1.LRUCache(contentLimit);
|
|
282
|
+
this.methodMatchCache = new lru_cache_1.LRUCache(limit);
|
|
268
283
|
this.context = this.createContext();
|
|
269
284
|
}
|
|
270
285
|
/**
|
|
@@ -320,6 +335,28 @@ class ReferenceResolver {
|
|
|
320
335
|
this.knownNames = new Set(this.queries.getAllNodeNames());
|
|
321
336
|
this.cachesWarmed = true;
|
|
322
337
|
}
|
|
338
|
+
/**
|
|
339
|
+
* warmCaches for the async resolution entry points: streams the distinct
|
|
340
|
+
* name set with periodic yields instead of one synchronous `.all()`. On a
|
|
341
|
+
* multi-million-node index the DISTINCT scan is a solid multi-second block
|
|
342
|
+
* (measured up to 28s inside `codegraph sync` on the Linux kernel index),
|
|
343
|
+
* long enough to matter to the #850 watchdog on slower hardware. Same
|
|
344
|
+
* result, same memory — only the event loop keeps turning.
|
|
345
|
+
*/
|
|
346
|
+
async warmCachesYielding(onYield) {
|
|
347
|
+
if (this.cachesWarmed)
|
|
348
|
+
return;
|
|
349
|
+
this.knownFiles = new Set(this.queries.getAllFilePaths());
|
|
350
|
+
const names = new Set();
|
|
351
|
+
let scanned = 0;
|
|
352
|
+
for (const name of this.queries.iterateNodeNames()) {
|
|
353
|
+
names.add(name);
|
|
354
|
+
if ((++scanned & 8191) === 0)
|
|
355
|
+
await onYield();
|
|
356
|
+
}
|
|
357
|
+
this.knownNames = names;
|
|
358
|
+
this.cachesWarmed = true;
|
|
359
|
+
}
|
|
323
360
|
/**
|
|
324
361
|
* Clear internal caches
|
|
325
362
|
*/
|
|
@@ -331,10 +368,30 @@ class ReferenceResolver {
|
|
|
331
368
|
this.nameCache.clear();
|
|
332
369
|
this.lowerNameCache.clear();
|
|
333
370
|
this.qualifiedNameCache.clear();
|
|
371
|
+
this.fileLinesCache.clear();
|
|
372
|
+
this.methodMatchCache.clear();
|
|
373
|
+
this.nodesByKindCache.clear();
|
|
334
374
|
this.knownNames = null;
|
|
335
375
|
this.knownFiles = null;
|
|
336
376
|
this.cachesWarmed = false;
|
|
337
377
|
}
|
|
378
|
+
/** `readFile` through the LRU content cache (null = read failed, also cached). */
|
|
379
|
+
readFileCached(filePath) {
|
|
380
|
+
if (this.fileCache.has(filePath)) {
|
|
381
|
+
return this.fileCache.get(filePath);
|
|
382
|
+
}
|
|
383
|
+
const fullPath = path.join(this.projectRoot, filePath);
|
|
384
|
+
try {
|
|
385
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
386
|
+
this.fileCache.set(filePath, content);
|
|
387
|
+
return content;
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
(0, errors_1.logDebug)('Failed to read file for resolution', { filePath, error: String(error) });
|
|
391
|
+
this.fileCache.set(filePath, null);
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
338
395
|
/**
|
|
339
396
|
* Create the resolution context
|
|
340
397
|
*/
|
|
@@ -354,6 +411,30 @@ class ReferenceResolver {
|
|
|
354
411
|
this.nameCache.set(name, result);
|
|
355
412
|
return result;
|
|
356
413
|
},
|
|
414
|
+
getMethodMatches: (typeName, methodName, language) => {
|
|
415
|
+
const key = `${language} ${typeName}::${methodName}`;
|
|
416
|
+
const cached = this.methodMatchCache.get(key);
|
|
417
|
+
if (cached !== undefined)
|
|
418
|
+
return cached;
|
|
419
|
+
let candidates = this.nameCache.get(methodName);
|
|
420
|
+
if (candidates === undefined) {
|
|
421
|
+
candidates = this.queries.getNodesByName(methodName);
|
|
422
|
+
this.nameCache.set(methodName, candidates);
|
|
423
|
+
}
|
|
424
|
+
const want = `${typeName}::${methodName}`;
|
|
425
|
+
const matches = [];
|
|
426
|
+
for (const m of candidates) {
|
|
427
|
+
if (m.kind !== 'method')
|
|
428
|
+
continue;
|
|
429
|
+
if (m.language !== language)
|
|
430
|
+
continue;
|
|
431
|
+
const qn = m.qualifiedName;
|
|
432
|
+
if (qn === want || qn.endsWith(`::${want}`))
|
|
433
|
+
matches.push(m);
|
|
434
|
+
}
|
|
435
|
+
this.methodMatchCache.set(key, matches);
|
|
436
|
+
return matches;
|
|
437
|
+
},
|
|
357
438
|
getNodesByQualifiedName: (qualifiedName) => {
|
|
358
439
|
const cached = this.qualifiedNameCache.get(qualifiedName);
|
|
359
440
|
if (cached !== undefined)
|
|
@@ -363,8 +444,18 @@ class ReferenceResolver {
|
|
|
363
444
|
return result;
|
|
364
445
|
},
|
|
365
446
|
getNodesByKind: (kind) => {
|
|
366
|
-
|
|
447
|
+
const cached = this.nodesByKindCache.get(kind);
|
|
448
|
+
if (cached !== undefined)
|
|
449
|
+
return cached;
|
|
450
|
+
const result = this.queries.getNodesByKind(kind);
|
|
451
|
+
this.nodesByKindCache.set(kind, result);
|
|
452
|
+
return result;
|
|
367
453
|
},
|
|
454
|
+
// Streamed, uncached — synthesizers scan-and-filter whole kinds, and
|
|
455
|
+
// both the materialized array AND the per-kind cache retention are
|
|
456
|
+
// O(nodes) memory (#1212). Per-ref resolvers keep the cached array
|
|
457
|
+
// variant above.
|
|
458
|
+
iterateNodesByKind: (kind) => this.queries.iterateNodesByKind(kind),
|
|
368
459
|
fileExists: (filePath) => {
|
|
369
460
|
// Check pre-built known files set first (O(1))
|
|
370
461
|
if (this.knownFiles) {
|
|
@@ -383,21 +474,15 @@ class ReferenceResolver {
|
|
|
383
474
|
return false;
|
|
384
475
|
}
|
|
385
476
|
},
|
|
386
|
-
readFile: (filePath) =>
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
}
|
|
396
|
-
catch (error) {
|
|
397
|
-
(0, errors_1.logDebug)('Failed to read file for resolution', { filePath, error: String(error) });
|
|
398
|
-
this.fileCache.set(filePath, null);
|
|
399
|
-
return null;
|
|
400
|
-
}
|
|
477
|
+
readFile: (filePath) => this.readFileCached(filePath),
|
|
478
|
+
getFileLines: (filePath) => {
|
|
479
|
+
const cached = this.fileLinesCache.get(filePath);
|
|
480
|
+
if (cached !== undefined)
|
|
481
|
+
return cached;
|
|
482
|
+
const source = this.readFileCached(filePath);
|
|
483
|
+
const lines = source === null ? null : source.split(/\r?\n/);
|
|
484
|
+
this.fileLinesCache.set(filePath, lines);
|
|
485
|
+
return lines;
|
|
401
486
|
},
|
|
402
487
|
getProjectRoot: () => this.projectRoot,
|
|
403
488
|
getAllFiles: () => {
|
|
@@ -500,7 +585,7 @@ class ReferenceResolver {
|
|
|
500
585
|
// `.ts` index barrel and silently break the chain (#629). Re-key
|
|
501
586
|
// the parse on the barrel's extension so the chase works no matter
|
|
502
587
|
// what kind of file imports through it.
|
|
503
|
-
const isJsFamily = /\.(?:d\.ts|[cm]?tsx?|[cm]?jsx
|
|
588
|
+
const isJsFamily = /\.(?:d\.ts|[cm]?tsx?|[cm]?jsx?|ets)$/i.test(filePath);
|
|
504
589
|
const reExports = (0, import_resolver_1.extractReExports)(content, isJsFamily ? 'typescript' : language);
|
|
505
590
|
this.reExportCache.set(filePath, reExports);
|
|
506
591
|
return reExports;
|
|
@@ -667,13 +752,36 @@ class ReferenceResolver {
|
|
|
667
752
|
if (this.isBuiltInOrExternal(ref)) {
|
|
668
753
|
return null;
|
|
669
754
|
}
|
|
755
|
+
// CFML component paths in inheritance (#1152): `extends="coldbox.system.web.
|
|
756
|
+
// Controller"` names the supertype by its dot-separated path (or `extends=
|
|
757
|
+
// "../base"` by relative file path) — the graph indexes the class under its
|
|
758
|
+
// final segment only, so these die at the fast pre-filter below and never
|
|
759
|
+
// resolved. Handled by a dedicated path-corroborated matcher, gated to
|
|
760
|
+
// inheritance refs only (a dotted `calls` ref is a member-access chain, not
|
|
761
|
+
// a component path). No fallthrough on miss: the full path string can only
|
|
762
|
+
// ever mis-match downstream, and an unresolvable supertype usually lives in
|
|
763
|
+
// an out-of-repo library (mxunit, testbox) — silent beats wrong.
|
|
764
|
+
if ((ref.language === 'cfml' || ref.language === 'cfscript') &&
|
|
765
|
+
(ref.referenceKind === 'extends' || ref.referenceKind === 'implements') &&
|
|
766
|
+
(ref.referenceName.includes('.') || ref.referenceName.includes('/'))) {
|
|
767
|
+
return this.resolveCfmlComponentPath(ref);
|
|
768
|
+
}
|
|
670
769
|
// Fast pre-filter: skip if no symbol with this name exists anywhere
|
|
671
770
|
// AND the name doesn't match a local import. The import escape is
|
|
672
771
|
// necessary because re-export rename chains (`import { login }
|
|
673
772
|
// from './barrel'` where the barrel has `export { signIn as login }
|
|
674
773
|
// from './auth'`) intentionally call a name that has no
|
|
675
774
|
// declaration anywhere — only the renamed upstream symbol does.
|
|
676
|
-
|
|
775
|
+
// ArkTS chained-attribute refs carry a leading dot (`.titleStyle`) that
|
|
776
|
+
// routes them to the decorator-gated matcher; the symbol itself is
|
|
777
|
+
// indexed under the bare name, so the existence check strips the dot.
|
|
778
|
+
// Nix static path imports (`import ./x.nix`) name a FILE, not a symbol —
|
|
779
|
+
// they bypass the symbol-existence check and resolve via resolveViaImport.
|
|
780
|
+
const existenceName = ref.language === 'arkts' && ref.referenceName.startsWith('.')
|
|
781
|
+
? ref.referenceName.slice(1)
|
|
782
|
+
: ref.referenceName;
|
|
783
|
+
if (!(0, import_resolver_1.isNixPathImportRef)(ref) &&
|
|
784
|
+
!this.hasAnyPossibleMatch(existenceName) &&
|
|
677
785
|
!this.matchesAnyImport(ref) &&
|
|
678
786
|
!this.frameworks.some((f) => f.claimsReference?.(ref.referenceName))) {
|
|
679
787
|
return null;
|
|
@@ -739,13 +847,40 @@ class ReferenceResolver {
|
|
|
739
847
|
// If that didn't find the file, do NOT fall back to the symbol
|
|
740
848
|
// name-matcher — it would mis-connect e.g. "inc/db.php" to an unrelated
|
|
741
849
|
// db.php elsewhere in the tree (a wrong edge is worse than none, #660).
|
|
742
|
-
|
|
850
|
+
// Terraform refs are directory-scoped by language semantics — the
|
|
851
|
+
// framework resolver IS the whole rulebook (`var.X` can never legally
|
|
852
|
+
// bind outside its module directory), so the name-matcher's
|
|
853
|
+
// qualified-name fallback would only ever add wrong cross-module edges.
|
|
854
|
+
// Nix static path imports are file references for the same reason —
|
|
855
|
+
// falling through would let "./x.nix" name-match an unrelated node.
|
|
856
|
+
if ((0, import_resolver_1.isPhpIncludePathRef)(ref) || (0, import_resolver_1.isCobolCopybookRef)(ref) || (0, import_resolver_1.isNixPathImportRef)(ref) || ref.language === 'terraform') {
|
|
743
857
|
return candidates.length > 0
|
|
744
858
|
? candidates.reduce((best, curr) => curr.confidence > best.confidence ? curr : best)
|
|
745
859
|
: null;
|
|
746
860
|
}
|
|
747
861
|
// Strategy 3: Try name matching
|
|
748
|
-
|
|
862
|
+
let nameResult = this.gateLanguage((0, name_matcher_1.matchReference)(ref, this.context), ref);
|
|
863
|
+
// Nix has no ambient cross-file namespace — a callee binds lexically
|
|
864
|
+
// (same file) or through explicit import/callPackage wiring (the import
|
|
865
|
+
// path above). A cross-file name match is wrong by construction: every
|
|
866
|
+
// module `inherit (lib) mkOption`s the same nixpkgs helpers, so the
|
|
867
|
+
// matcher would link each `mkOption` call to whichever file's inherit
|
|
868
|
+
// binding it happened to pick. Same-file matches only.
|
|
869
|
+
if (nameResult) {
|
|
870
|
+
const target = this.queries.getNodeById(nameResult.targetNodeId);
|
|
871
|
+
if (ref.language === 'nix') {
|
|
872
|
+
if (!target || target.filePath !== ref.filePath) {
|
|
873
|
+
nameResult = null;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
else if (target && target.language === 'nix') {
|
|
877
|
+
// The reverse direction is just as impossible: no other language can
|
|
878
|
+
// symbolically call into a .nix binding (interop is eval/CLI, never a
|
|
879
|
+
// linkable symbol) — without this, a Python script's `split()` lands
|
|
880
|
+
// on some module's `split = ...` binding as a low-confidence match.
|
|
881
|
+
nameResult = null;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
749
884
|
if (nameResult) {
|
|
750
885
|
candidates.push(nameResult);
|
|
751
886
|
}
|
|
@@ -832,6 +967,20 @@ class ReferenceResolver {
|
|
|
832
967
|
referenceKind: r.original.referenceKind,
|
|
833
968
|
})));
|
|
834
969
|
}
|
|
970
|
+
// Delete unresolvable refs too — parity with resolveAndPersistBatched.
|
|
971
|
+
// Keeping them bought nothing: a ref is only ever retried when its file
|
|
972
|
+
// is re-extracted, which cascade-deletes and re-inserts its rows anyway.
|
|
973
|
+
// And it broke the #1187 orphan sweep's invariant — after a COMPLETED
|
|
974
|
+
// pass the table must hold nothing that pass processed, so that any row
|
|
975
|
+
// still present belongs to an interrupted run and the sweep can key off
|
|
976
|
+
// a bare row count.
|
|
977
|
+
if (result.unresolved.length > 0) {
|
|
978
|
+
this.queries.deleteSpecificResolvedReferences(result.unresolved.map((r) => ({
|
|
979
|
+
fromNodeId: r.fromNodeId,
|
|
980
|
+
referenceName: r.referenceName,
|
|
981
|
+
referenceKind: r.referenceKind,
|
|
982
|
+
})));
|
|
983
|
+
}
|
|
835
984
|
return result;
|
|
836
985
|
}
|
|
837
986
|
/**
|
|
@@ -882,37 +1031,53 @@ class ReferenceResolver {
|
|
|
882
1031
|
return edges.length;
|
|
883
1032
|
}
|
|
884
1033
|
/**
|
|
885
|
-
* Resolve one batch
|
|
886
|
-
*
|
|
887
|
-
*
|
|
888
|
-
*
|
|
889
|
-
*
|
|
890
|
-
*
|
|
1034
|
+
* Resolve one batch with a yield checkpoint between EVERY ref so the #850
|
|
1035
|
+
* liveness heartbeat can fire on a slow/dense batch (#1091). The checkpoint
|
|
1036
|
+
* granularity is per-ref — not per-N-refs — because per-ref cost is unbounded
|
|
1037
|
+
* in the worst case (a collision-heavy method name whose candidate set misses
|
|
1038
|
+
* the LRU re-fetches tens of thousands of rows): any fixed N multiplies that
|
|
1039
|
+
* worst case into the watchdog window, which is how v1.2.0 still got killed
|
|
1040
|
+
* at "Resolving refs" on large Java monorepos (#1122). `maybeYield()` is a
|
|
1041
|
+
* ~ns time check when under budget, so per-ref checkpoints cost nothing.
|
|
1042
|
+
* Behaviourally identical to `resolveAll(batch)`: `warmCaches()` is
|
|
1043
|
+
* idempotent (guarded) and `resolveOne` is independent per ref, so yielding
|
|
1044
|
+
* between refs changes only timing, never which edges get created.
|
|
891
1045
|
*/
|
|
892
|
-
async resolveBatchYielding(batch, maybeYield
|
|
893
|
-
|
|
894
|
-
return this.resolveAll(batch);
|
|
1046
|
+
async resolveBatchYielding(batch, maybeYield) {
|
|
1047
|
+
this.warmCaches();
|
|
895
1048
|
const resolved = [];
|
|
896
1049
|
const unresolved = [];
|
|
897
1050
|
const byMethod = {};
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
byMethod[
|
|
1051
|
+
for (const raw of batch) {
|
|
1052
|
+
const ref = {
|
|
1053
|
+
fromNodeId: raw.fromNodeId,
|
|
1054
|
+
referenceName: raw.referenceName,
|
|
1055
|
+
referenceKind: raw.referenceKind,
|
|
1056
|
+
line: raw.line,
|
|
1057
|
+
column: raw.column,
|
|
1058
|
+
filePath: raw.filePath || this.getFilePathFromNodeId(raw.fromNodeId),
|
|
1059
|
+
language: raw.language || this.getLanguageFromNodeId(raw.fromNodeId),
|
|
1060
|
+
};
|
|
1061
|
+
const result = this.resolveOne(ref);
|
|
1062
|
+
if (result) {
|
|
1063
|
+
resolved.push(result);
|
|
1064
|
+
byMethod[result.resolvedBy] = (byMethod[result.resolvedBy] || 0) + 1;
|
|
1065
|
+
}
|
|
1066
|
+
else {
|
|
1067
|
+
unresolved.push(ref);
|
|
912
1068
|
}
|
|
913
1069
|
await maybeYield();
|
|
914
1070
|
}
|
|
915
|
-
return {
|
|
1071
|
+
return {
|
|
1072
|
+
resolved,
|
|
1073
|
+
unresolved,
|
|
1074
|
+
stats: {
|
|
1075
|
+
total: batch.length,
|
|
1076
|
+
resolved: resolved.length,
|
|
1077
|
+
unresolved: unresolved.length,
|
|
1078
|
+
byMethod,
|
|
1079
|
+
},
|
|
1080
|
+
};
|
|
916
1081
|
}
|
|
917
1082
|
/**
|
|
918
1083
|
* Resolve and persist in batches to keep memory bounded.
|
|
@@ -920,7 +1085,6 @@ class ReferenceResolver {
|
|
|
920
1085
|
* up resolved refs after each batch to avoid accumulating large arrays.
|
|
921
1086
|
*/
|
|
922
1087
|
async resolveAndPersistBatched(onProgress, batchSize = 5000) {
|
|
923
|
-
this.warmCaches();
|
|
924
1088
|
// Resolution runs on the indexer's MAIN thread, and the #850 liveness
|
|
925
1089
|
// watchdog SIGKILLs a process whose event loop stalls past its window (60s
|
|
926
1090
|
// by default). A single dense batch's resolveAll — or the synthesis pass
|
|
@@ -928,6 +1092,7 @@ class ReferenceResolver {
|
|
|
928
1092
|
// (#1091). A shared yielder lets both give the watchdog heartbeat a regular
|
|
929
1093
|
// window to fire; see ./cooperative-yield.
|
|
930
1094
|
const maybeYield = (0, cooperative_yield_1.createYielder)();
|
|
1095
|
+
await this.warmCachesYielding(maybeYield);
|
|
931
1096
|
const total = this.queries.getUnresolvedReferencesCount();
|
|
932
1097
|
let processed = 0;
|
|
933
1098
|
const aggregateStats = {
|
|
@@ -944,26 +1109,39 @@ class ReferenceResolver {
|
|
|
944
1109
|
if (batch.length === 0)
|
|
945
1110
|
break;
|
|
946
1111
|
const result = await this.resolveBatchYielding(batch, maybeYield);
|
|
1112
|
+
// Persist in bounded sub-transactions with yields between: a whole
|
|
1113
|
+
// batch's edge insert / keyed deletes are otherwise one solid
|
|
1114
|
+
// synchronous span each on a multi-GB index, sitting BETWEEN the
|
|
1115
|
+
// per-ref yields — the last unyielded stretch of the resolution loop.
|
|
1116
|
+
// Crash semantics are unchanged (already several transactions): edges
|
|
1117
|
+
// land before their refs are deleted, so a kill mid-way re-resolves
|
|
1118
|
+
// the remainder idempotently on the next run/sweep (#1187).
|
|
1119
|
+
const PERSIST_CHUNK = 1000;
|
|
947
1120
|
// Persist edges immediately
|
|
948
1121
|
const edges = this.createEdges(result.resolved);
|
|
949
|
-
|
|
950
|
-
this.queries.insertEdges(edges);
|
|
1122
|
+
for (let i = 0; i < edges.length; i += PERSIST_CHUNK) {
|
|
1123
|
+
this.queries.insertEdges(edges.slice(i, i + PERSIST_CHUNK));
|
|
1124
|
+
await maybeYield();
|
|
951
1125
|
}
|
|
952
1126
|
// Clean up resolved refs so they don't appear in the next batch
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1127
|
+
const resolvedKeys = result.resolved.map((r) => ({
|
|
1128
|
+
fromNodeId: r.original.fromNodeId,
|
|
1129
|
+
referenceName: r.original.referenceName,
|
|
1130
|
+
referenceKind: r.original.referenceKind,
|
|
1131
|
+
}));
|
|
1132
|
+
for (let i = 0; i < resolvedKeys.length; i += PERSIST_CHUNK) {
|
|
1133
|
+
this.queries.deleteSpecificResolvedReferences(resolvedKeys.slice(i, i + PERSIST_CHUNK));
|
|
1134
|
+
await maybeYield();
|
|
959
1135
|
}
|
|
960
1136
|
// Delete unresolvable refs from this batch to avoid re-processing them
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1137
|
+
const unresolvedKeys = result.unresolved.map((r) => ({
|
|
1138
|
+
fromNodeId: r.fromNodeId,
|
|
1139
|
+
referenceName: r.referenceName,
|
|
1140
|
+
referenceKind: r.referenceKind,
|
|
1141
|
+
}));
|
|
1142
|
+
for (let i = 0; i < unresolvedKeys.length; i += PERSIST_CHUNK) {
|
|
1143
|
+
this.queries.deleteSpecificResolvedReferences(unresolvedKeys.slice(i, i + PERSIST_CHUNK));
|
|
1144
|
+
await maybeYield();
|
|
967
1145
|
}
|
|
968
1146
|
// Aggregate stats
|
|
969
1147
|
aggregateStats.total += result.stats.total;
|
|
@@ -976,11 +1154,13 @@ class ReferenceResolver {
|
|
|
976
1154
|
onProgress?.(processed, total);
|
|
977
1155
|
// Yield so progress UI can render between batches
|
|
978
1156
|
await new Promise(resolve => setImmediate(resolve));
|
|
979
|
-
//
|
|
980
|
-
//
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1157
|
+
// NOTE: there used to be an extra early break here when a batch resolved
|
|
1158
|
+
// nothing (`result.unresolved.length === batch.length`). That was wrong:
|
|
1159
|
+
// an all-unresolvable batch still DELETES its rows (progress), yet the
|
|
1160
|
+
// break abandoned every batch after it in the same run — on a repo whose
|
|
1161
|
+
// first 5000 refs are all external/stdlib calls, resolution stopped at
|
|
1162
|
+
// batch one and left the rest of the table as permanent orphans (#1187).
|
|
1163
|
+
// The count-based guard below catches the true no-progress case.
|
|
984
1164
|
// Non-progress guard (defense-in-depth). Because we re-read from offset 0
|
|
985
1165
|
// each pass, the unresolved_refs table MUST shrink every iteration — both
|
|
986
1166
|
// resolved and unresolved refs are deleted above. If it didn't shrink, a
|
|
@@ -1022,11 +1202,18 @@ class ReferenceResolver {
|
|
|
1022
1202
|
isBuiltInOrExternal(ref) {
|
|
1023
1203
|
const name = ref.referenceName;
|
|
1024
1204
|
const isJsTs = ref.language === 'typescript' || ref.language === 'javascript'
|
|
1025
|
-
|| ref.language === 'tsx' || ref.language === 'jsx';
|
|
1205
|
+
|| ref.language === 'tsx' || ref.language === 'jsx' || ref.language === 'arkts';
|
|
1026
1206
|
// JavaScript/TypeScript built-ins
|
|
1027
1207
|
if (isJsTs && JS_BUILT_INS.has(name)) {
|
|
1028
1208
|
return true;
|
|
1029
1209
|
}
|
|
1210
|
+
// ArkTS resource-reference intrinsics — `$r('app.string.x')` /
|
|
1211
|
+
// `$rawfile('x.png')` are framework-provided and appear dozens of times
|
|
1212
|
+
// per UI file; without this they can resolve to a stray same-named
|
|
1213
|
+
// symbol (e.g. a checked-in hvigor wrapper's `$r`).
|
|
1214
|
+
if (ref.language === 'arkts' && (name === '$r' || name === '$rawfile')) {
|
|
1215
|
+
return true;
|
|
1216
|
+
}
|
|
1030
1217
|
// Common JS/TS library calls (console.log, Math.floor, JSON.parse)
|
|
1031
1218
|
if (isJsTs && (name.startsWith('console.') || name.startsWith('Math.') || name.startsWith('JSON.'))) {
|
|
1032
1219
|
return true;
|
|
@@ -1191,6 +1378,96 @@ class ReferenceResolver {
|
|
|
1191
1378
|
const target = found.values().next().value;
|
|
1192
1379
|
return { original: ref, targetNodeId: target.id, confidence: 0.9, resolvedBy: 'import' };
|
|
1193
1380
|
}
|
|
1381
|
+
/**
|
|
1382
|
+
* Resolve a CFML inheritance reference written as a component path (#1152).
|
|
1383
|
+
* Two forms exist in real code:
|
|
1384
|
+
*
|
|
1385
|
+
* - Dotted: `extends="coldbox.system.web.Controller"` — dots are directory
|
|
1386
|
+
* separators from the webroot or a CFML mapping. Mappings live in server
|
|
1387
|
+
* config / Application.cfc, so the leading segments may not exist in the
|
|
1388
|
+
* repo at all (in the coldbox repo itself the path is `system/web/
|
|
1389
|
+
* Controller.cfc` — the `coldbox.` root IS the repo). Matched by final
|
|
1390
|
+
* segment (the class), corroborated right-to-left against the candidate's
|
|
1391
|
+
* parent directories.
|
|
1392
|
+
* - Relative: `extends="../base"` / `extends="./base"` (the FW/1 style) —
|
|
1393
|
+
* resolved against the referencing file's own directory.
|
|
1394
|
+
*
|
|
1395
|
+
* Conservative by design: a candidate needs at least one corroborating
|
|
1396
|
+
* directory segment (a dotted path whose only same-named class sits in an
|
|
1397
|
+
* unrelated directory is almost always an out-of-repo library supertype —
|
|
1398
|
+
* mxunit/testbox/coldbox-as-dependency), and a corroboration tie yields no
|
|
1399
|
+
* edge. Directory comparison is case-insensitive (CFML path resolution is);
|
|
1400
|
+
* the class segment itself is matched exactly, which real code satisfies —
|
|
1401
|
+
* dotted paths are written to match the on-disk file name.
|
|
1402
|
+
*/
|
|
1403
|
+
resolveCfmlComponentPath(ref) {
|
|
1404
|
+
const cfmlCandidates = (name) => this.context
|
|
1405
|
+
.getNodesByName(name)
|
|
1406
|
+
.filter((n) => (n.kind === 'class' || n.kind === 'interface') &&
|
|
1407
|
+
(n.language === 'cfml' || n.language === 'cfscript'));
|
|
1408
|
+
const norm = (p) => p.replace(/\\/g, '/').toLowerCase();
|
|
1409
|
+
// Relative-path form: `../base`, `./base`, `sub/thing` — resolve against
|
|
1410
|
+
// the referencing file's directory and require an exact (case-insensitive)
|
|
1411
|
+
// file match.
|
|
1412
|
+
if (ref.referenceName.includes('/')) {
|
|
1413
|
+
const rel = ref.referenceName.replace(/\.cfc$/i, '');
|
|
1414
|
+
const fromDir = ref.filePath.replace(/\\/g, '/').split('/').slice(0, -1);
|
|
1415
|
+
const parts = [...fromDir];
|
|
1416
|
+
for (const seg of rel.split('/')) {
|
|
1417
|
+
if (seg === '' || seg === '.')
|
|
1418
|
+
continue;
|
|
1419
|
+
if (seg === '..') {
|
|
1420
|
+
if (parts.length === 0)
|
|
1421
|
+
return null; // escapes the project root
|
|
1422
|
+
parts.pop();
|
|
1423
|
+
}
|
|
1424
|
+
else {
|
|
1425
|
+
parts.push(seg);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
const wantPath = norm(parts.join('/') + '.cfc');
|
|
1429
|
+
const className = parts[parts.length - 1];
|
|
1430
|
+
if (!className)
|
|
1431
|
+
return null;
|
|
1432
|
+
const target = cfmlCandidates(className).find((c) => norm(c.filePath) === wantPath);
|
|
1433
|
+
return target
|
|
1434
|
+
? { original: ref, targetNodeId: target.id, confidence: 0.95, resolvedBy: 'file-path' }
|
|
1435
|
+
: null;
|
|
1436
|
+
}
|
|
1437
|
+
// Dotted form.
|
|
1438
|
+
const segments = ref.referenceName.split('.').map((s) => s.trim()).filter(Boolean);
|
|
1439
|
+
if (segments.length < 2)
|
|
1440
|
+
return null;
|
|
1441
|
+
const className = segments[segments.length - 1];
|
|
1442
|
+
const dirSegments = segments.slice(0, -1);
|
|
1443
|
+
let best = null;
|
|
1444
|
+
let bestScore = 0;
|
|
1445
|
+
let tie = false;
|
|
1446
|
+
for (const cand of cfmlCandidates(className)) {
|
|
1447
|
+
const dirs = cand.filePath.replace(/\\/g, '/').split('/').slice(0, -1);
|
|
1448
|
+
// Count matching directory segments right-to-left: for
|
|
1449
|
+
// `coldbox.system.web.Controller` vs `system/web/Controller.cfc`,
|
|
1450
|
+
// `web` and `system` match, then the repo root ends the run → score 2.
|
|
1451
|
+
let score = 0;
|
|
1452
|
+
while (score < dirSegments.length &&
|
|
1453
|
+
score < dirs.length &&
|
|
1454
|
+
dirSegments[dirSegments.length - 1 - score].toLowerCase() ===
|
|
1455
|
+
dirs[dirs.length - 1 - score].toLowerCase()) {
|
|
1456
|
+
score++;
|
|
1457
|
+
}
|
|
1458
|
+
if (score > bestScore) {
|
|
1459
|
+
best = cand;
|
|
1460
|
+
bestScore = score;
|
|
1461
|
+
tie = false;
|
|
1462
|
+
}
|
|
1463
|
+
else if (score === bestScore && score > 0) {
|
|
1464
|
+
tie = true;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
if (!best || bestScore === 0 || tie)
|
|
1468
|
+
return null;
|
|
1469
|
+
return { original: ref, targetNodeId: best.id, confidence: 0.9, resolvedBy: 'qualified-name' };
|
|
1470
|
+
}
|
|
1194
1471
|
/**
|
|
1195
1472
|
* Resolve a `this.<member>` function-as-value reference (#756/#808) to the
|
|
1196
1473
|
* ENCLOSING CLASS's own member — never a same-named symbol elsewhere. The
|