@colbymchenry/codegraph-win32-x64 1.1.6 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +14 -1
- package/lib/dist/bin/command-supervision.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 +51 -0
- package/lib/dist/db/queries.d.ts.map +1 -1
- package/lib/dist/db/queries.js +141 -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 +254 -3
- 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 +59 -0
- package/lib/dist/extraction/languages/c-cpp.d.ts.map +1 -1
- package/lib/dist/extraction/languages/c-cpp.js +499 -3
- 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 +20 -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 +845 -16
- 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/graph/traversal.d.ts.map +1 -1
- package/lib/dist/graph/traversal.js +76 -17
- package/lib/dist/graph/traversal.js.map +1 -1
- package/lib/dist/index.d.ts +63 -1
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +310 -6
- 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 +39 -7
- 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 +21 -3
- package/lib/dist/mcp/proxy.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 +74 -9
- package/lib/dist/mcp/tools.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/callback-synthesizer.d.ts +1 -1
- package/lib/dist/resolution/callback-synthesizer.d.ts.map +1 -1
- package/lib/dist/resolution/callback-synthesizer.js +743 -11
- package/lib/dist/resolution/callback-synthesizer.js.map +1 -1
- package/lib/dist/resolution/cooperative-yield.d.ts +32 -0
- package/lib/dist/resolution/cooperative-yield.d.ts.map +1 -0
- package/lib/dist/resolution/cooperative-yield.js +42 -0
- package/lib/dist/resolution/cooperative-yield.js.map +1 -0
- 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/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 +44 -2
- package/lib/dist/resolution/index.d.ts.map +1 -1
- package/lib/dist/resolution/index.js +335 -30
- package/lib/dist/resolution/index.js.map +1 -1
- package/lib/dist/resolution/name-matcher.d.ts +22 -3
- package/lib/dist/resolution/name-matcher.d.ts.map +1 -1
- package/lib/dist/resolution/name-matcher.js +484 -39
- 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 +20 -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/dist/upgrade/index.js +1 -1
- package/lib/dist/upgrade/index.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
|
@@ -12,6 +12,8 @@ exports.crossesKnownFamily = crossesKnownFamily;
|
|
|
12
12
|
exports.matchFunctionRef = matchFunctionRef;
|
|
13
13
|
exports.matchByExactName = matchByExactName;
|
|
14
14
|
exports.matchByQualifiedName = matchByQualifiedName;
|
|
15
|
+
exports.preferCallSiteFile = preferCallSiteFile;
|
|
16
|
+
exports.resolveMethodOnType = resolveMethodOnType;
|
|
15
17
|
exports.matchCppCallChain = matchCppCallChain;
|
|
16
18
|
exports.matchScopedCallChain = matchScopedCallChain;
|
|
17
19
|
exports.matchDottedCallChain = matchDottedCallChain;
|
|
@@ -138,7 +140,9 @@ function pickClosestFileNode(candidates, ref) {
|
|
|
138
140
|
const LANGUAGE_FAMILY = {
|
|
139
141
|
java: 'jvm', kotlin: 'jvm', scala: 'jvm',
|
|
140
142
|
swift: 'apple', objc: 'apple',
|
|
141
|
-
|
|
143
|
+
// ArkTS is a TS superset — every HarmonyOS project mixes `.ets` UI with
|
|
144
|
+
// `.ts` logic modules, so refs must cross freely between them.
|
|
145
|
+
typescript: 'web', tsx: 'web', javascript: 'web', jsx: 'web', arkts: 'web',
|
|
142
146
|
c: 'c', cpp: 'c',
|
|
143
147
|
// Razor/Blazor markup names C# types — same family so `@model Foo` /
|
|
144
148
|
// `<MyComponent/>` resolve to their `.cs` class through the cross-family gate.
|
|
@@ -220,6 +224,7 @@ function matchFunctionRef(ref, context) {
|
|
|
220
224
|
// implicit-self, Java/Kotlin method references.
|
|
221
225
|
const bareFnOnly = ref.language === 'typescript' || ref.language === 'tsx' ||
|
|
222
226
|
ref.language === 'javascript' || ref.language === 'jsx' ||
|
|
227
|
+
ref.language === 'arkts' ||
|
|
223
228
|
ref.language === 'cpp' || ref.language === 'python' ||
|
|
224
229
|
ref.language === 'php';
|
|
225
230
|
// Qualified member-pointer (`&Widget::on_click` → "Widget::on_click"):
|
|
@@ -382,7 +387,17 @@ function matchByQualifiedName(ref, context) {
|
|
|
382
387
|
if (!ref.referenceName.includes('::') && !ref.referenceName.includes('.')) {
|
|
383
388
|
return null;
|
|
384
389
|
}
|
|
385
|
-
|
|
390
|
+
// A method call `receiver.method()` can share an exact qualified name with a
|
|
391
|
+
// config-file key: `service.process()` (a `calls` ref named `service.process`)
|
|
392
|
+
// vs the yaml key `service.process`. Config keys are bound to their code refs
|
|
393
|
+
// upstream by the framework resolvers (`@Value` → `references`); a `calls` ref
|
|
394
|
+
// must never resolve to a yaml/properties config node — that's a wrong edge
|
|
395
|
+
// AND it hides the real callee. Drop those from both the exact and the partial
|
|
396
|
+
// candidate sets so resolution falls through to method resolution below (#1180).
|
|
397
|
+
const keepForRef = (nodes) => ref.referenceKind === 'calls'
|
|
398
|
+
? nodes.filter((n) => !(n.kind === 'constant' && (n.language === 'yaml' || n.language === 'properties')))
|
|
399
|
+
: nodes;
|
|
400
|
+
const candidates = keepForRef(context.getNodesByQualifiedName(ref.referenceName));
|
|
386
401
|
if (candidates.length === 1) {
|
|
387
402
|
return {
|
|
388
403
|
original: ref,
|
|
@@ -391,24 +406,63 @@ function matchByQualifiedName(ref, context) {
|
|
|
391
406
|
resolvedBy: 'qualified-name',
|
|
392
407
|
};
|
|
393
408
|
}
|
|
394
|
-
//
|
|
409
|
+
// Several symbols share this exact qualified name (e.g. `Logger::log` declared
|
|
410
|
+
// in two files — an ODR clash or separate translation units): prefer the one
|
|
411
|
+
// in the call site's own file before the partial-match fallback below, else
|
|
412
|
+
// the first-indexed def wins and a call in `b/svc` targets `a/svc` (#1079).
|
|
413
|
+
if (candidates.length > 1) {
|
|
414
|
+
const ordered = preferCallSiteFile(candidates, ref.filePath);
|
|
415
|
+
if (ordered[0].filePath === ref.filePath) {
|
|
416
|
+
return {
|
|
417
|
+
original: ref,
|
|
418
|
+
targetNodeId: ordered[0].id,
|
|
419
|
+
confidence: 0.95,
|
|
420
|
+
resolvedBy: 'qualified-name',
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
// Try partial qualified name match — again preferring the call site's own
|
|
425
|
+
// file when more than one symbol's qualifiedName ends with the reference.
|
|
395
426
|
const parts = ref.referenceName.split(/[:.]/);
|
|
396
427
|
const lastName = parts[parts.length - 1];
|
|
397
428
|
if (lastName) {
|
|
398
|
-
const partialCandidates = context.getNodesByName(lastName)
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
}
|
|
429
|
+
const partialCandidates = keepForRef(context.getNodesByName(lastName))
|
|
430
|
+
.filter((candidate) => candidate.qualifiedName.endsWith(ref.referenceName));
|
|
431
|
+
const chosen = preferCallSiteFile(partialCandidates, ref.filePath)[0];
|
|
432
|
+
if (chosen) {
|
|
433
|
+
return {
|
|
434
|
+
original: ref,
|
|
435
|
+
targetNodeId: chosen.id,
|
|
436
|
+
confidence: 0.85,
|
|
437
|
+
resolvedBy: 'qualified-name',
|
|
438
|
+
};
|
|
408
439
|
}
|
|
409
440
|
}
|
|
410
441
|
return null;
|
|
411
442
|
}
|
|
443
|
+
/**
|
|
444
|
+
* When a symbol name is ambiguous across files, prefer the candidate(s) declared
|
|
445
|
+
* in the call site's own file, keeping the rest in their original order (#1079).
|
|
446
|
+
* A same-file definition is the strongest language-agnostic signal for which of
|
|
447
|
+
* several same-named symbols a call means; without it, resolution collapses onto
|
|
448
|
+
* whichever was indexed first, so a call in `b/svc` wrongly targets `a/svc`.
|
|
449
|
+
* No-op when there are <2 candidates or none share the call site's file.
|
|
450
|
+
*/
|
|
451
|
+
function preferCallSiteFile(nodes, callSiteFile) {
|
|
452
|
+
if (nodes.length < 2)
|
|
453
|
+
return nodes;
|
|
454
|
+
const same = [];
|
|
455
|
+
const other = [];
|
|
456
|
+
for (const n of nodes) {
|
|
457
|
+
if (n.filePath === callSiteFile)
|
|
458
|
+
same.push(n);
|
|
459
|
+
else
|
|
460
|
+
other.push(n);
|
|
461
|
+
}
|
|
462
|
+
return same.length ? [...same, ...other] : nodes;
|
|
463
|
+
}
|
|
464
|
+
// Exported for the precedence unit tests (#1079): they assert the
|
|
465
|
+
// preferredFqn → same-file → matches[0] ordering directly.
|
|
412
466
|
function resolveMethodOnType(typeName, methodName, ref, context, confidence, resolvedBy,
|
|
413
467
|
/**
|
|
414
468
|
* Optional FQN that identifies WHICH class declaration `typeName`
|
|
@@ -426,17 +480,28 @@ depth = 0) {
|
|
|
426
480
|
// in-class (`class Foo { int bar() { ... } }`) or out-of-line in a separate
|
|
427
481
|
// file (`int Foo::bar() { ... }` in foo.cpp while class Foo is in foo.hpp).
|
|
428
482
|
// The previous same-file approach missed the latter — the typical C++ layout.
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
483
|
+
// Prefer the context's per-(type, method) memo: the raw name lookup fetches
|
|
484
|
+
// EVERY node sharing the method name — tens of thousands of rows for a
|
|
485
|
+
// collision-heavy Java name like `execute` — and re-filtering that per ref
|
|
486
|
+
// was a dominant term in the #1122 watchdog kill on large repos. Only the
|
|
487
|
+
// ref-independent filter is memoized; per-ref disambiguation stays below.
|
|
488
|
+
let matches;
|
|
489
|
+
if (context.getMethodMatches) {
|
|
490
|
+
matches = context.getMethodMatches(typeName, methodName, ref.language);
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
const methodCandidates = context.getNodesByName(methodName);
|
|
494
|
+
const want = `${typeName}::${methodName}`;
|
|
495
|
+
matches = [];
|
|
496
|
+
for (const m of methodCandidates) {
|
|
497
|
+
if (m.kind !== 'method')
|
|
498
|
+
continue;
|
|
499
|
+
if (m.language !== ref.language)
|
|
500
|
+
continue;
|
|
501
|
+
const qn = m.qualifiedName;
|
|
502
|
+
if (qn === want || qn.endsWith(`::${want}`)) {
|
|
503
|
+
matches.push(m);
|
|
504
|
+
}
|
|
440
505
|
}
|
|
441
506
|
}
|
|
442
507
|
if (matches.length === 0) {
|
|
@@ -472,9 +537,19 @@ depth = 0) {
|
|
|
472
537
|
};
|
|
473
538
|
}
|
|
474
539
|
}
|
|
540
|
+
// Language-agnostic disambiguation: when several same-named methods survive
|
|
541
|
+
// (e.g. two files each declaring `class Logger { void log(); }` — an ODR
|
|
542
|
+
// clash, an anonymous-namespace type, or separate translation units), prefer
|
|
543
|
+
// the definition in the CALL SITE's own file. Without this, every ambiguous
|
|
544
|
+
// call collapses onto the first-indexed definition, so a call in `b/svc.cpp`
|
|
545
|
+
// wrongly points at `a/svc.cpp` (#1079). This runs AFTER the `preferredFqn`
|
|
546
|
+
// block, so Java/Kotlin import disambiguation — whose target is intentionally
|
|
547
|
+
// in ANOTHER file (#314) — is unaffected: that block returns early whenever
|
|
548
|
+
// an import FQN pins the class.
|
|
549
|
+
const ordered = preferCallSiteFile(matches, ref.filePath);
|
|
475
550
|
return {
|
|
476
551
|
original: ref,
|
|
477
|
-
targetNodeId:
|
|
552
|
+
targetNodeId: ordered[0].id,
|
|
478
553
|
confidence,
|
|
479
554
|
resolvedBy,
|
|
480
555
|
};
|
|
@@ -513,10 +588,14 @@ function buildDeclaratorRegex(escapedReceiver) {
|
|
|
513
588
|
return new RegExp(`([A-Za-z_][\\w:]*(?:\\s*<[^;=(){}]+>)?(?:\\s*[*&]+)?)\\s*\\b${escapedReceiver}\\b\\s*(?=[;=,)\\[{(]|$)`);
|
|
514
589
|
}
|
|
515
590
|
function inferCppReceiverType(receiverName, ref, context, depth = 0) {
|
|
516
|
-
|
|
517
|
-
|
|
591
|
+
// Per-file lines cache when available — this runs per `receiver->method()`
|
|
592
|
+
// ref and re-splitting the file each time is the same quadratic as the
|
|
593
|
+
// shared inferrer's (#1122).
|
|
594
|
+
const lines = context.getFileLines
|
|
595
|
+
? context.getFileLines(ref.filePath)
|
|
596
|
+
: (context.readFile(ref.filePath)?.split(/\r?\n/) ?? null);
|
|
597
|
+
if (!lines || lines.length === 0)
|
|
518
598
|
return null;
|
|
519
|
-
const lines = source.split(/\r?\n/);
|
|
520
599
|
const callLineIndex = Math.max(0, Math.min(lines.length - 1, ref.line - 1));
|
|
521
600
|
const escapedReceiver = receiverName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
522
601
|
const receiverPattern = new RegExp(`\\b${escapedReceiver}\\b`);
|
|
@@ -549,10 +628,12 @@ function inferCppReceiverType(receiverName, ref, context, depth = 0) {
|
|
|
549
628
|
for (const headerPath of headerCandidates) {
|
|
550
629
|
if (!context.fileExists(headerPath))
|
|
551
630
|
continue;
|
|
552
|
-
const
|
|
553
|
-
|
|
631
|
+
const headerLines = context.getFileLines
|
|
632
|
+
? context.getFileLines(headerPath)
|
|
633
|
+
: (context.readFile(headerPath)?.split(/\r?\n/) ?? null);
|
|
634
|
+
if (!headerLines)
|
|
554
635
|
continue;
|
|
555
|
-
for (const line of
|
|
636
|
+
for (const line of headerLines) {
|
|
556
637
|
if (!receiverPattern.test(line))
|
|
557
638
|
continue;
|
|
558
639
|
const declaratorMatch = line.match(declaratorRegex);
|
|
@@ -880,6 +961,283 @@ function inferJavaFieldReceiverType(receiverName, ref, context) {
|
|
|
880
961
|
return null; // primitives / lowercase → skip
|
|
881
962
|
return lastPart;
|
|
882
963
|
}
|
|
964
|
+
// ── Local-variable receiver-type inference (#1108) ──────────────────────────
|
|
965
|
+
//
|
|
966
|
+
// Instance calls through a local variable (`const lg = new Logger(); lg.log()`)
|
|
967
|
+
// only resolved in C++ before this — no other language could learn the
|
|
968
|
+
// receiver's type. Local variables are not indexed as nodes (node-explosion),
|
|
969
|
+
// so, like the C++ inferrer above, we read the enclosing function's source and
|
|
970
|
+
// match the receiver's declaration/initializer to recover its type. The type is
|
|
971
|
+
// then handed to resolveMethodOnType, which VALIDATES that the type actually
|
|
972
|
+
// declares the method, so a mis-inference produces NO edge — the safety net
|
|
973
|
+
// that lets the patterns below stay simple. C++ keeps its dedicated inferrer
|
|
974
|
+
// (header scan + `auto`); this covers every other language.
|
|
975
|
+
// Tokens a loose pattern might capture that are never a user-defined type.
|
|
976
|
+
const NON_TYPE_RECEIVER_TOKENS = new Set([
|
|
977
|
+
'this', 'self', 'super', 'new', 'return', 'await', 'yield', 'typeof',
|
|
978
|
+
'null', 'nil', 'None', 'true', 'false', 'True', 'False', 'undefined',
|
|
979
|
+
]);
|
|
980
|
+
/**
|
|
981
|
+
* Normalize a captured type expression to a simple type name: drop generic
|
|
982
|
+
* args and pointer/ref markers, take the last `.`/`::`-qualified segment, and
|
|
983
|
+
* reject obvious non-types.
|
|
984
|
+
*/
|
|
985
|
+
function normalizeInferredTypeName(raw) {
|
|
986
|
+
const cleaned = raw.replace(/<[^>]*>/g, '').replace(/[&*]/g, '').trim();
|
|
987
|
+
const seg = cleaned.split(/[.:]+/).filter(Boolean).pop();
|
|
988
|
+
if (!seg)
|
|
989
|
+
return null;
|
|
990
|
+
if (NON_TYPE_RECEIVER_TOKENS.has(seg))
|
|
991
|
+
return null;
|
|
992
|
+
return seg;
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Per-language patterns that recover a local variable's (or typed parameter's)
|
|
996
|
+
* type from its declaration/initializer. Each regex captures the type in group
|
|
997
|
+
* 1; `r` is the already-escaped receiver name. Ordered most-specific first.
|
|
998
|
+
* PascalCase is required in the capture where the language convention allows,
|
|
999
|
+
* as a cheap false-positive guard on top of resolveMethodOnType's validation.
|
|
1000
|
+
*/
|
|
1001
|
+
function localReceiverTypePatterns(language, r) {
|
|
1002
|
+
switch (language) {
|
|
1003
|
+
case 'typescript':
|
|
1004
|
+
case 'javascript':
|
|
1005
|
+
case 'tsx':
|
|
1006
|
+
case 'jsx':
|
|
1007
|
+
case 'arkts':
|
|
1008
|
+
return [
|
|
1009
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*new\\s+([A-Za-z_$][\\w.$]*)`), // = new Logger()
|
|
1010
|
+
// No keyword requirement, so this matches BOTH a local annotation
|
|
1011
|
+
// (`const lg: Logger`) and a typed parameter (`function use(lg: Logger)`
|
|
1012
|
+
// / `(lg: Logger) =>`) — the parameter case the old `const|let|var`
|
|
1013
|
+
// prefix excluded (#1125). Mirrors Kotlin/Swift/Scala; the capture stops
|
|
1014
|
+
// at `<` so a generic-typed param (`repo: Repository<User>`) still yields
|
|
1015
|
+
// `Repository`. resolveMethodOnType validates the type actually declares
|
|
1016
|
+
// the method, so the looser match produces no edge on a mis-inference.
|
|
1017
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.$]*)`), // lg: Logger (annotation or typed param)
|
|
1018
|
+
];
|
|
1019
|
+
case 'python':
|
|
1020
|
+
return [
|
|
1021
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w.]*)\\s*\\(`), // lg = Logger(...)
|
|
1022
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.]*)`), // lg: Logger (PEP 526)
|
|
1023
|
+
];
|
|
1024
|
+
case 'java':
|
|
1025
|
+
return [
|
|
1026
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*new\\s+([A-Za-z_][\\w.]*)`), // = new Logger()
|
|
1027
|
+
new RegExp(`\\b([A-Z][\\w.]*)\\s+${r}\\b\\s*[=;,)]`), // Logger lg; / param
|
|
1028
|
+
];
|
|
1029
|
+
case 'kotlin':
|
|
1030
|
+
return [
|
|
1031
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w.]*)\\s*\\(`), // val lg = Logger(...)
|
|
1032
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.]*)`), // val lg: Logger / param
|
|
1033
|
+
];
|
|
1034
|
+
case 'csharp':
|
|
1035
|
+
return [
|
|
1036
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*new\\s+([A-Za-z_][\\w.]*)`), // = new Logger()
|
|
1037
|
+
new RegExp(`\\b([A-Z][\\w.]*)\\s+${r}\\b\\s*[=;,)]`), // Logger lg; / param
|
|
1038
|
+
];
|
|
1039
|
+
case 'swift':
|
|
1040
|
+
return [
|
|
1041
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w.]*)\\s*\\(`), // let lg = Logger(...)
|
|
1042
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.]*)`), // let lg: Logger / param
|
|
1043
|
+
];
|
|
1044
|
+
case 'rust':
|
|
1045
|
+
return [
|
|
1046
|
+
new RegExp(`\\blet\\s+(?:mut\\s+)?${r}\\b(?:\\s*:[^=]+)?=\\s*&?(?:mut\\s+)?([A-Z][\\w]*)`), // let lg = Logger::new()/Logger{}/Logger
|
|
1047
|
+
// No `let`, so this covers a `let lg: Logger` binding AND a typed
|
|
1048
|
+
// parameter (`fn use(lg: &Logger)`, a closure `|lg: Logger|`) — the
|
|
1049
|
+
// parameter case the old `let`-anchored pattern excluded (#1125).
|
|
1050
|
+
new RegExp(`\\b${r}\\s*:\\s*&?(?:mut\\s+)?([A-Z][\\w]*)`), // lg: Logger (binding or typed param)
|
|
1051
|
+
];
|
|
1052
|
+
case 'go':
|
|
1053
|
+
return [
|
|
1054
|
+
new RegExp(`\\b${r}\\b\\s*:=\\s*&?([A-Za-z_][\\w.]*)\\s*{`), // lg := Logger{} / &Logger{}
|
|
1055
|
+
new RegExp(`\\bvar\\s+${r}\\s+\\*?([A-Za-z_][\\w.]*)`), // var lg Logger / *Logger
|
|
1056
|
+
// A typed parameter / method receiver (`func use(lg Logger)`,
|
|
1057
|
+
// `func (l Logger) M()`) — name-before-type with no `var`/`:=` (#1125).
|
|
1058
|
+
// PascalCase-guarded (unlike the anchored patterns above) to keep the
|
|
1059
|
+
// keyword-free `ident Type` shape from matching unrelated pairs; the
|
|
1060
|
+
// enclosing-scope bound already excludes package-level struct fields.
|
|
1061
|
+
new RegExp(`\\b${r}\\s+\\*?([A-Z][\\w.]*)`), // func use(lg Logger) / (l Logger)
|
|
1062
|
+
];
|
|
1063
|
+
case 'ruby':
|
|
1064
|
+
return [
|
|
1065
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w:]*)\\.new\\b`), // lg = Logger.new
|
|
1066
|
+
];
|
|
1067
|
+
case 'scala':
|
|
1068
|
+
return [
|
|
1069
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*(?:new\\s+)?([A-Z][\\w.]*)`), // val lg = new Logger / Logger(...)
|
|
1070
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.]*)`), // val lg: Logger / param
|
|
1071
|
+
];
|
|
1072
|
+
case 'dart':
|
|
1073
|
+
return [
|
|
1074
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w.]*)\\s*\\(`), // var lg = Logger(...)
|
|
1075
|
+
// Trailing `[=;,)]` (not just `[=;]`) so a typed parameter — `Logger lg)`
|
|
1076
|
+
// / `Logger lg,` — matches too, not only `Logger lg = ...` / `Logger lg;`
|
|
1077
|
+
// (#1125). Mirrors Java/C#.
|
|
1078
|
+
new RegExp(`\\b([A-Z][\\w.]*)\\s+${r}\\b\\s*[=;,)]`), // Logger lg = ... / param
|
|
1079
|
+
];
|
|
1080
|
+
case 'php':
|
|
1081
|
+
return [
|
|
1082
|
+
new RegExp(`\\$?${r}\\b\\s*=\\s*new\\s+([A-Za-z_\\\\][\\w\\\\]*)`), // $lg = new Logger()
|
|
1083
|
+
// A typed parameter (`function use(Logger $lg)`, `?Logger $lg`,
|
|
1084
|
+
// `\\App\\Logger $lg`, `&$lg` by-ref) and a typed `catch (E $e)` — the
|
|
1085
|
+
// type sits before the `$`-variable (#1125). Namespace `\\` allowed.
|
|
1086
|
+
new RegExp(`\\b([A-Za-z_\\\\][\\w\\\\]*)\\s+&?\\$${r}\\b`), // Logger $lg (typed param)
|
|
1087
|
+
];
|
|
1088
|
+
case 'lua':
|
|
1089
|
+
case 'luau':
|
|
1090
|
+
return [
|
|
1091
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w]*)\\.new\\b`), // local lg = Logger.new()
|
|
1092
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w]*)\\s*\\(`), // local lg = Logger(...) (callable table)
|
|
1093
|
+
// Luau annotation (`local lg: Logger`) / typed param — but Lua's
|
|
1094
|
+
// method-call syntax is the IDENTICAL `receiver:Name` shape, and the
|
|
1095
|
+
// backward scan starts on the call's own line, so without a gate any
|
|
1096
|
+
// PascalCase method call (`lg:Log()`, the Roblox convention)
|
|
1097
|
+
// self-matches as "type = Log" before the scan reaches the real
|
|
1098
|
+
// declaration (#1124). The lookahead rejects a capture followed by
|
|
1099
|
+
// any of Lua's three call forms — `(args)`, `"s"`/`'s'`/`[[s]]`,
|
|
1100
|
+
// `{t}` — and its leading `[\w.]` alternative stops backtracking from
|
|
1101
|
+
// shrinking the capture to dodge the gate (`lg:Log()` would otherwise
|
|
1102
|
+
// still match, as `Lo`).
|
|
1103
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.]*)(?![\\w.]|\\s*[({"'\\[])`), // local lg: Logger / typed param
|
|
1104
|
+
];
|
|
1105
|
+
case 'r':
|
|
1106
|
+
return [
|
|
1107
|
+
new RegExp(`\\b${r}\\b\\s*(?:<-|<<-|=)\\s*([A-Z][\\w.]*)\\$new\\b`), // lg <- Logger$new() (R6)
|
|
1108
|
+
];
|
|
1109
|
+
case 'pascal':
|
|
1110
|
+
return [
|
|
1111
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w]*)`), // var lg: TLogger / param lg: TLogger
|
|
1112
|
+
new RegExp(`\\b${r}\\b\\s*:=\\s*([A-Z][\\w.]*)\\.Create\\b`), // lg := TLogger.Create
|
|
1113
|
+
];
|
|
1114
|
+
case 'cfml':
|
|
1115
|
+
case 'cfscript':
|
|
1116
|
+
return [
|
|
1117
|
+
// svc = new UserService() / new path.to.UserService() — dotted component
|
|
1118
|
+
// paths reduce to their final segment via normalizeInferredTypeName.
|
|
1119
|
+
// Also matches inside tag markup (`<cfset svc = new UserService()>`)
|
|
1120
|
+
// since the scan reads raw source lines.
|
|
1121
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*new\\s+([A-Za-z_][\\w.]*)`),
|
|
1122
|
+
// The classic form: svc = createObject("component", "path.to.UserService")
|
|
1123
|
+
// (casing of createObject varies in the wild), plus the modern
|
|
1124
|
+
// single-argument form createObject("path.to.UserService").
|
|
1125
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*[Cc]reate[Oo]bject\\s*\\(\\s*["']component["']\\s*,\\s*["']([\\w.]+)["']`),
|
|
1126
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*[Cc]reate[Oo]bject\\s*\\(\\s*["']([\\w.]+)["']\\s*\\)`),
|
|
1127
|
+
// Typed cfscript parameter: `function save(UserService svc)` /
|
|
1128
|
+
// `required UserService svc` — CFML's built-in types (string, numeric,
|
|
1129
|
+
// any, struct…) are lowercase by convention, so the PascalCase guard
|
|
1130
|
+
// excludes them.
|
|
1131
|
+
new RegExp(`\\b([A-Z][\\w.]*)\\s+${r}\\b\\s*[=;,)]`),
|
|
1132
|
+
// Tag-form typed argument, either attribute order:
|
|
1133
|
+
// <cfargument name="svc" type="path.to.UserService">
|
|
1134
|
+
new RegExp(`\\bcfargument[^>\\n]*\\bname\\s*=\\s*["']${r}["'][^>\\n]*\\btype\\s*=\\s*["']([\\w.]+)["']`, 'i'),
|
|
1135
|
+
new RegExp(`\\bcfargument[^>\\n]*\\btype\\s*=\\s*["']([\\w.]+)["'][^>\\n]*\\bname\\s*=\\s*["']${r}["']`, 'i'),
|
|
1136
|
+
// Component property (incl. WireBox DI): `property name="svc"
|
|
1137
|
+
// inject="UserService";` / `<cfproperty name="svc" type="UserService">`,
|
|
1138
|
+
// either attribute order. An inject DSL value with a namespace
|
|
1139
|
+
// (`inject="svc@core"`) captures only the leading name and simply
|
|
1140
|
+
// fails type-validation — no edge, never a wrong one.
|
|
1141
|
+
new RegExp(`\\b(?:cf)?property\\b[^;\\n]*\\bname\\s*=\\s*["']${r}["'][^;\\n]*\\b(?:type|inject)\\s*=\\s*["']([\\w.]+)["']`, 'i'),
|
|
1142
|
+
new RegExp(`\\b(?:cf)?property\\b[^;\\n]*\\b(?:type|inject)\\s*=\\s*["']([\\w.]+)["'][^;\\n]*\\bname\\s*=\\s*["']${r}["']`, 'i'),
|
|
1143
|
+
];
|
|
1144
|
+
default:
|
|
1145
|
+
return [];
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
/** 1-based start line of the tightest function/method enclosing the call. */
|
|
1149
|
+
function enclosingScopeStartLine(ref, context) {
|
|
1150
|
+
let start = 1;
|
|
1151
|
+
for (const n of context.getNodesInFile(ref.filePath)) {
|
|
1152
|
+
if (n.kind !== 'function' && n.kind !== 'method')
|
|
1153
|
+
continue;
|
|
1154
|
+
if (n.language !== ref.language)
|
|
1155
|
+
continue;
|
|
1156
|
+
const end = n.endLine ?? n.startLine;
|
|
1157
|
+
if (n.startLine <= ref.line && end >= ref.line && n.startLine >= start) {
|
|
1158
|
+
start = n.startLine;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
return start;
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Infer a receiver's type from its local declaration/initializer in the
|
|
1165
|
+
* enclosing function body. Language-dispatched; returns null for languages
|
|
1166
|
+
* without patterns or when no declaration is found. Bounded to the enclosing
|
|
1167
|
+
* scope so a same-named variable in another function can't leak in.
|
|
1168
|
+
*/
|
|
1169
|
+
function inferLocalReceiverType(receiverName, ref, context) {
|
|
1170
|
+
// CFML scope prefixes: `variables.svc` / `this.svc` name a COMPONENT-scoped
|
|
1171
|
+
// field whose assignment or `property` declaration usually lives outside the
|
|
1172
|
+
// calling function (the init-pseudoconstructor / WireBox-injection pattern),
|
|
1173
|
+
// and `local.svc` is an explicit function-local. Strip the prefix so the
|
|
1174
|
+
// declaration patterns match (`variables.svc = new X()`, `property
|
|
1175
|
+
// name="svc" …`, `var svc = …` all bind the bare name), and widen the scan
|
|
1176
|
+
// to the whole file for the component-scoped forms — nearest-declaration-
|
|
1177
|
+
// backward still wins, so a function-local shadowing the field is preferred.
|
|
1178
|
+
let scanReceiver = receiverName;
|
|
1179
|
+
let componentScoped = false;
|
|
1180
|
+
if (ref.language === 'cfml' || ref.language === 'cfscript') {
|
|
1181
|
+
const scoped = receiverName.match(/^(variables|this|local|arguments)\.(.+)$/i);
|
|
1182
|
+
if (scoped) {
|
|
1183
|
+
scanReceiver = scoped[2];
|
|
1184
|
+
const scope = scoped[1].toLowerCase();
|
|
1185
|
+
componentScoped = scope === 'variables' || scope === 'this';
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
const patterns = localReceiverTypePatterns(ref.language, scanReceiver.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
1189
|
+
if (patterns.length === 0)
|
|
1190
|
+
return null;
|
|
1191
|
+
// Split through the context's per-file lines cache when available: this runs
|
|
1192
|
+
// for EVERY `receiver.method()` ref, and re-splitting the whole file per ref
|
|
1193
|
+
// was ~20% of total index CPU on Java-heavy repos (#1122).
|
|
1194
|
+
const lines = context.getFileLines
|
|
1195
|
+
? context.getFileLines(ref.filePath)
|
|
1196
|
+
: (context.readFile(ref.filePath)?.split(/\r?\n/) ?? null);
|
|
1197
|
+
if (!lines || lines.length === 0)
|
|
1198
|
+
return null;
|
|
1199
|
+
const callIdx = Math.max(0, Math.min(lines.length - 1, ref.line - 1));
|
|
1200
|
+
const startIdx = componentScoped
|
|
1201
|
+
? 0
|
|
1202
|
+
: Math.max(0, enclosingScopeStartLine(ref, context) - 1);
|
|
1203
|
+
const matchLine = (i) => {
|
|
1204
|
+
const line = lines[i];
|
|
1205
|
+
if (!line)
|
|
1206
|
+
return null;
|
|
1207
|
+
// A generated/minified line (one multi-KB statement) is not something a
|
|
1208
|
+
// human-written local declaration lives on, and regexing it per ref is
|
|
1209
|
+
// pure waste — skip it rather than scan it.
|
|
1210
|
+
if (line.length > 10_000)
|
|
1211
|
+
return null;
|
|
1212
|
+
for (const re of patterns) {
|
|
1213
|
+
const m = line.match(re);
|
|
1214
|
+
if (m && m[1]) {
|
|
1215
|
+
const type = normalizeInferredTypeName(m[1]);
|
|
1216
|
+
if (type)
|
|
1217
|
+
return type;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
return null;
|
|
1221
|
+
};
|
|
1222
|
+
// Nearest declaration wins: scan backward from the call to the scope start.
|
|
1223
|
+
for (let i = callIdx; i >= startIdx; i--) {
|
|
1224
|
+
const type = matchLine(i);
|
|
1225
|
+
if (type)
|
|
1226
|
+
return type;
|
|
1227
|
+
}
|
|
1228
|
+
// A component-scoped field's declaration is position-independent — the
|
|
1229
|
+
// `variables.svc = new X()` pseudoconstructor assignment or `property`
|
|
1230
|
+
// declaration may sit BELOW the calling function in the file — so when the
|
|
1231
|
+
// backward pass finds nothing, sweep the remainder of the file too.
|
|
1232
|
+
if (componentScoped) {
|
|
1233
|
+
for (let i = callIdx + 1; i < lines.length; i++) {
|
|
1234
|
+
const type = matchLine(i);
|
|
1235
|
+
if (type)
|
|
1236
|
+
return type;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
return null;
|
|
1240
|
+
}
|
|
883
1241
|
/**
|
|
884
1242
|
* Try to resolve by method name on a class/object
|
|
885
1243
|
*/
|
|
@@ -895,15 +1253,42 @@ function matchMethodCall(ref, context) {
|
|
|
895
1253
|
// `Guard.Against.X()`) matched no pattern and never resolved.
|
|
896
1254
|
const dotMatch = ref.referenceName.match(/^([\w.]+)\.(\w+:?(?:\w+:)*)$/);
|
|
897
1255
|
const colonMatch = ref.referenceName.match(/^(\w+)::(\w+)$/);
|
|
898
|
-
|
|
1256
|
+
// Lua/Luau method calls use a single colon (`lg:log`); R uses `$` (`lg$log`).
|
|
1257
|
+
// Recognize these receiver/method separators so local-variable receiver-type
|
|
1258
|
+
// inference (#1108) applies to them too — extraction already emits the ref in
|
|
1259
|
+
// this shape, but the resolver otherwise only understood `.` and `::`.
|
|
1260
|
+
const luaColonMatch = (ref.language === 'lua' || ref.language === 'luau')
|
|
1261
|
+
? ref.referenceName.match(/^([\w.]+):(\w+)$/)
|
|
1262
|
+
: null;
|
|
1263
|
+
const rDollarMatch = ref.language === 'r'
|
|
1264
|
+
? ref.referenceName.match(/^([\w.]+)\$(\w+)$/)
|
|
1265
|
+
: null;
|
|
1266
|
+
const match = dotMatch || colonMatch || luaColonMatch || rDollarMatch;
|
|
899
1267
|
if (!match) {
|
|
900
1268
|
return null;
|
|
901
1269
|
}
|
|
902
1270
|
const [, objectOrClass, methodName] = match;
|
|
903
|
-
|
|
904
|
-
|
|
1271
|
+
// A simple `receiver.method` / `receiver:method` / `receiver$method` shape whose
|
|
1272
|
+
// receiver type we can try to infer from its local declaration.
|
|
1273
|
+
const inferableReceiver = dotMatch || luaColonMatch || rDollarMatch;
|
|
1274
|
+
// Infer the receiver's type from its local declaration/initializer in the
|
|
1275
|
+
// enclosing scope, then resolve the method on that type (#1108). C++ keeps its
|
|
1276
|
+
// dedicated inferrer (header scan + `auto`); every other language uses the
|
|
1277
|
+
// shared source-based inferrer. resolveMethodOnType validates the method
|
|
1278
|
+
// exists on the inferred type, so a mis-inference produces no edge.
|
|
1279
|
+
if (inferableReceiver) {
|
|
1280
|
+
const inferredType = ref.language === 'cpp'
|
|
1281
|
+
? inferCppReceiverType(objectOrClass, ref, context)
|
|
1282
|
+
: inferLocalReceiverType(objectOrClass, ref, context);
|
|
905
1283
|
if (inferredType) {
|
|
906
|
-
|
|
1284
|
+
// Java/Kotlin: when two classes share the simple name, the file's import
|
|
1285
|
+
// pins WHICH one (#314). Other languages disambiguate by call-site file.
|
|
1286
|
+
const importedFqn = ref.language === 'java' || ref.language === 'kotlin'
|
|
1287
|
+
? context
|
|
1288
|
+
.getImportMappings(ref.filePath, ref.language)
|
|
1289
|
+
.find((i) => i.localName === inferredType)?.source
|
|
1290
|
+
: undefined;
|
|
1291
|
+
const typedMatch = resolveMethodOnType(inferredType, methodName, ref, context, 0.9, 'instance-method', importedFqn);
|
|
907
1292
|
if (typedMatch) {
|
|
908
1293
|
return typedMatch;
|
|
909
1294
|
}
|
|
@@ -928,8 +1313,12 @@ function matchMethodCall(ref, context) {
|
|
|
928
1313
|
}
|
|
929
1314
|
}
|
|
930
1315
|
}
|
|
931
|
-
// Strategy 1: Direct class name match (existing logic)
|
|
932
|
-
|
|
1316
|
+
// Strategy 1: Direct class name match (existing logic). When the receiver
|
|
1317
|
+
// names a class that exists in several files (`Logger.log()` / `Logger::log()`
|
|
1318
|
+
// with a `Logger` in both `a/` and `b/`), try the class in the call site's
|
|
1319
|
+
// own file first — otherwise the first-indexed class wins and a call in `b/`
|
|
1320
|
+
// resolves to `a/`'s method (#1079).
|
|
1321
|
+
const classCandidates = preferCallSiteFile(context.getNodesByName(objectOrClass), ref.filePath);
|
|
933
1322
|
for (const classNode of classCandidates) {
|
|
934
1323
|
if (classNode.kind === 'class' || classNode.kind === 'struct' || classNode.kind === 'interface') {
|
|
935
1324
|
// Skip cross-language class matches
|
|
@@ -953,7 +1342,7 @@ function matchMethodCall(ref, context) {
|
|
|
953
1342
|
// e.g., "permissionEngine" → look for classes containing "PermissionEngine"
|
|
954
1343
|
const capitalizedReceiver = objectOrClass.charAt(0).toUpperCase() + objectOrClass.slice(1);
|
|
955
1344
|
if (capitalizedReceiver !== objectOrClass) {
|
|
956
|
-
const fuzzyClassCandidates = context.getNodesByName(capitalizedReceiver);
|
|
1345
|
+
const fuzzyClassCandidates = preferCallSiteFile(context.getNodesByName(capitalizedReceiver), ref.filePath);
|
|
957
1346
|
for (const classNode of fuzzyClassCandidates) {
|
|
958
1347
|
if (classNode.kind === 'class' || classNode.kind === 'struct' || classNode.kind === 'interface') {
|
|
959
1348
|
// Skip cross-language class matches
|
|
@@ -1006,7 +1395,10 @@ function matchMethodCall(ref, context) {
|
|
|
1006
1395
|
const receiverWords = splitCamelCase(objectOrClass);
|
|
1007
1396
|
let bestMatch;
|
|
1008
1397
|
let bestScore = 0;
|
|
1009
|
-
|
|
1398
|
+
// Same-file candidates first, so a score tie (`score > bestScore` keeps
|
|
1399
|
+
// the first seen) resolves to the call site's own file rather than the
|
|
1400
|
+
// first-indexed duplicate (#1079).
|
|
1401
|
+
for (const method of preferCallSiteFile(targetMethods, ref.filePath)) {
|
|
1010
1402
|
const classWords = splitCamelCase(method.qualifiedName);
|
|
1011
1403
|
let score = receiverWords.filter(w => classWords.some(cw => cw.toLowerCase() === w.toLowerCase())).length;
|
|
1012
1404
|
// Bonus for same language
|
|
@@ -1185,6 +1577,8 @@ function matchFuzzy(ref, context) {
|
|
|
1185
1577
|
/**
|
|
1186
1578
|
* Match all strategies in order of confidence
|
|
1187
1579
|
*/
|
|
1580
|
+
/** ArkUI attribute-helper decorators a `.attr(...)` chain may resolve to. */
|
|
1581
|
+
const ARKUI_ATTRIBUTE_DECORATORS = new Set(['Extend', 'Styles', 'AnimatableExtend', 'Builder']);
|
|
1188
1582
|
function matchReference(ref, context) {
|
|
1189
1583
|
// Function-as-value refs (#756) resolve ONLY through the dedicated matcher —
|
|
1190
1584
|
// never the fuzzy/qualified fallthrough below (a wrong callback edge is
|
|
@@ -1192,6 +1586,57 @@ function matchReference(ref, context) {
|
|
|
1192
1586
|
if (ref.referenceKind === 'function_ref') {
|
|
1193
1587
|
return matchFunctionRef(ref, context);
|
|
1194
1588
|
}
|
|
1589
|
+
// ArkTS chained UI attributes — emitted with a leading dot (`.titleStyle`,
|
|
1590
|
+
// `.width`) by the extractor — resolve ONLY to decorator-marked attribute
|
|
1591
|
+
// helpers: `@Extend`/`@Styles`/`@AnimatableExtend` functions (and global
|
|
1592
|
+
// `@Builder`s used attribute-position). Framework attributes (`.width`,
|
|
1593
|
+
// `.fontSize` — on nearly every UI line) match no such helper and stay
|
|
1594
|
+
// unresolved, NEVER falling through to bare-name matching: on a samples
|
|
1595
|
+
// monorepo that fallthrough manufactured 36k wrong edges, giving single
|
|
1596
|
+
// same-named properties thousands of false callers. Ambiguity rule matches
|
|
1597
|
+
// the rest of the file: several same-named helpers → prefer the call-site
|
|
1598
|
+
// file, still ambiguous → drop the ref rather than guess.
|
|
1599
|
+
if (ref.language === 'arkts' && ref.referenceName.startsWith('.')) {
|
|
1600
|
+
const base = ref.referenceName.slice(1);
|
|
1601
|
+
const candidates = context
|
|
1602
|
+
.getNodesByName(base)
|
|
1603
|
+
.filter((n) => n.language === 'arkts' &&
|
|
1604
|
+
n.kind === 'function' &&
|
|
1605
|
+
(n.decorators ?? []).some((d) => ARKUI_ATTRIBUTE_DECORATORS.has(d)));
|
|
1606
|
+
const chosen = candidates.length > 1 ? preferCallSiteFile(candidates, ref.filePath) : candidates;
|
|
1607
|
+
if (chosen.length !== 1)
|
|
1608
|
+
return null;
|
|
1609
|
+
return {
|
|
1610
|
+
original: ref,
|
|
1611
|
+
targetNodeId: chosen[0].id,
|
|
1612
|
+
confidence: 0.85,
|
|
1613
|
+
resolvedBy: 'exact-match',
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
// Erlang `-behaviour(m)` refs target a MODULE. Letting them fall through to
|
|
1617
|
+
// bare-name matching grabs any same-named symbol — on emqx,
|
|
1618
|
+
// `-behaviour(supervisor)` resolved to a `-define(supervisor, …)` macro
|
|
1619
|
+
// constant in an unrelated app. Resolve only to the behaviour module's
|
|
1620
|
+
// namespace; an out-of-repo behaviour (OTP's gen_server/supervisor) stays
|
|
1621
|
+
// unresolved rather than guessed. The same module-only rule applies to every
|
|
1622
|
+
// ref an `.app`/`.app.src` resource file emits — its `{mod, …}` callback and
|
|
1623
|
+
// `{applications, …}` dependency names can only mean modules, and on emqx
|
|
1624
|
+
// the `ssl` OTP app otherwise resolved to a test helper FUNCTION named ssl.
|
|
1625
|
+
if (ref.language === 'erlang' &&
|
|
1626
|
+
(ref.referenceKind === 'implements' || /\.app(?:\.src)?$/i.test(ref.filePath))) {
|
|
1627
|
+
const modules = context
|
|
1628
|
+
.getNodesByName(ref.referenceName)
|
|
1629
|
+
.filter((n) => n.language === 'erlang' && n.kind === 'namespace');
|
|
1630
|
+
const chosen = preferCallSiteFile(modules, ref.filePath)[0];
|
|
1631
|
+
if (!chosen)
|
|
1632
|
+
return null;
|
|
1633
|
+
return {
|
|
1634
|
+
original: ref,
|
|
1635
|
+
targetNodeId: chosen.id,
|
|
1636
|
+
confidence: 0.9,
|
|
1637
|
+
resolvedBy: 'exact-match',
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1195
1640
|
// Try strategies in order of confidence
|
|
1196
1641
|
let result;
|
|
1197
1642
|
// 0. File path match (e.g., "snippets/drawer-menu.liquid" → file node)
|