@vizalice/codegraph 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -1
- package/dist/bin/codegraph.js +126 -24
- package/dist/bin/codegraph.js.map +1 -1
- package/dist/context/index.d.ts +12 -0
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +47 -8
- package/dist/context/index.js.map +1 -1
- package/dist/db/index.d.ts +2 -0
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +23 -0
- package/dist/db/index.js.map +1 -1
- package/dist/db/queries.d.ts +39 -2
- package/dist/db/queries.d.ts.map +1 -1
- package/dist/db/queries.js +104 -19
- package/dist/db/queries.js.map +1 -1
- package/dist/directory.d.ts +29 -0
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +52 -0
- package/dist/directory.js.map +1 -1
- package/dist/extraction/grammars.d.ts.map +1 -1
- package/dist/extraction/grammars.js +38 -2
- package/dist/extraction/grammars.js.map +1 -1
- package/dist/extraction/index.d.ts +59 -0
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +175 -53
- package/dist/extraction/index.js.map +1 -1
- package/dist/extraction/languages/erlang.d.ts.map +1 -1
- package/dist/extraction/languages/erlang.js +58 -22
- package/dist/extraction/languages/erlang.js.map +1 -1
- package/dist/extraction/languages/rust.d.ts +20 -0
- package/dist/extraction/languages/rust.d.ts.map +1 -1
- package/dist/extraction/languages/rust.js +44 -20
- package/dist/extraction/languages/rust.js.map +1 -1
- package/dist/extraction/parse-pool.d.ts +7 -0
- package/dist/extraction/parse-pool.d.ts.map +1 -1
- package/dist/extraction/parse-pool.js +20 -1
- package/dist/extraction/parse-pool.js.map +1 -1
- package/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.js +66 -35
- package/dist/extraction/tree-sitter.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +68 -1
- package/dist/index.js.map +1 -1
- package/dist/installer/index.d.ts +2 -2
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +4 -3
- package/dist/installer/index.js.map +1 -1
- package/dist/installer/targets/codex.d.ts +22 -8
- package/dist/installer/targets/codex.d.ts.map +1 -1
- package/dist/installer/targets/codex.js +65 -48
- package/dist/installer/targets/codex.js.map +1 -1
- package/dist/installer/targets/types.d.ts +4 -4
- package/dist/mcp/daemon-manager.d.ts +1 -1
- package/dist/mcp/daemon-manager.d.ts.map +1 -1
- package/dist/mcp/daemon-manager.js +1 -1
- package/dist/mcp/daemon-manager.js.map +1 -1
- package/dist/mcp/daemon-paths.d.ts +6 -0
- package/dist/mcp/daemon-paths.d.ts.map +1 -1
- package/dist/mcp/daemon-paths.js +54 -0
- package/dist/mcp/daemon-paths.js.map +1 -1
- package/dist/mcp/daemon-registry.d.ts +10 -0
- package/dist/mcp/daemon-registry.d.ts.map +1 -1
- package/dist/mcp/daemon-registry.js +51 -3
- package/dist/mcp/daemon-registry.js.map +1 -1
- package/dist/mcp/daemon.d.ts +9 -6
- package/dist/mcp/daemon.d.ts.map +1 -1
- package/dist/mcp/daemon.js +10 -8
- package/dist/mcp/daemon.js.map +1 -1
- package/dist/mcp/engine.d.ts +3 -0
- package/dist/mcp/engine.d.ts.map +1 -1
- package/dist/mcp/engine.js +45 -3
- package/dist/mcp/engine.js.map +1 -1
- package/dist/mcp/explore-diagnostics.d.ts +2 -0
- package/dist/mcp/explore-diagnostics.d.ts.map +1 -1
- package/dist/mcp/explore-diagnostics.js +3 -0
- package/dist/mcp/explore-diagnostics.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +17 -4
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +17 -12
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/tools.d.ts +17 -0
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +164 -25
- package/dist/mcp/tools.js.map +1 -1
- package/dist/project-config.d.ts +23 -0
- package/dist/project-config.d.ts.map +1 -1
- package/dist/project-config.js +39 -2
- package/dist/project-config.js.map +1 -1
- package/dist/resolution/c-fnptr-synthesizer.js +2 -2
- package/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
- package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
- package/dist/resolution/callback-synthesizer.js +38 -10
- package/dist/resolution/callback-synthesizer.js.map +1 -1
- package/dist/resolution/frameworks/swift.d.ts.map +1 -1
- package/dist/resolution/frameworks/swift.js +11 -1
- package/dist/resolution/frameworks/swift.js.map +1 -1
- package/dist/resolution/import-resolver.d.ts.map +1 -1
- package/dist/resolution/import-resolver.js +16 -1
- package/dist/resolution/import-resolver.js.map +1 -1
- package/dist/resolution/index.d.ts.map +1 -1
- package/dist/resolution/index.js +5 -1
- package/dist/resolution/index.js.map +1 -1
- package/dist/resolution/name-matcher.d.ts +30 -0
- package/dist/resolution/name-matcher.d.ts.map +1 -1
- package/dist/resolution/name-matcher.js +278 -0
- package/dist/resolution/name-matcher.js.map +1 -1
- package/dist/resolution/path-aliases.d.ts +6 -4
- package/dist/resolution/path-aliases.d.ts.map +1 -1
- package/dist/resolution/path-aliases.js +125 -17
- package/dist/resolution/path-aliases.js.map +1 -1
- package/dist/search/identifier-segments.d.ts +10 -0
- package/dist/search/identifier-segments.d.ts.map +1 -1
- package/dist/search/identifier-segments.js +29 -0
- package/dist/search/identifier-segments.js.map +1 -1
- package/dist/search/query-paths.d.ts +57 -0
- package/dist/search/query-paths.d.ts.map +1 -0
- package/dist/search/query-paths.js +298 -0
- package/dist/search/query-paths.js.map +1 -0
- package/dist/search/query-utils.d.ts +1 -1
- package/dist/search/query-utils.d.ts.map +1 -1
- package/dist/search/query-utils.js +12 -3
- package/dist/search/query-utils.js.map +1 -1
- package/dist/sync/watcher.d.ts +22 -0
- package/dist/sync/watcher.d.ts.map +1 -1
- package/dist/sync/watcher.js +51 -5
- package/dist/sync/watcher.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -13,6 +13,7 @@ exports.matchFunctionRef = matchFunctionRef;
|
|
|
13
13
|
exports.matchByExactName = matchByExactName;
|
|
14
14
|
exports.matchByQualifiedName = matchByQualifiedName;
|
|
15
15
|
exports.preferCallSiteFile = preferCallSiteFile;
|
|
16
|
+
exports.resolveObjectLiteralMember = resolveObjectLiteralMember;
|
|
16
17
|
exports.resolveMethodOnType = resolveMethodOnType;
|
|
17
18
|
exports.matchCppCallChain = matchCppCallChain;
|
|
18
19
|
exports.matchScopedCallChain = matchScopedCallChain;
|
|
@@ -21,6 +22,7 @@ exports.normalizeInferredTypeName = normalizeInferredTypeName;
|
|
|
21
22
|
exports.clearNameMatcherMemos = clearNameMatcherMemos;
|
|
22
23
|
exports.localReceiverTypePatterns = localReceiverTypePatterns;
|
|
23
24
|
exports.matchMethodCall = matchMethodCall;
|
|
25
|
+
exports.rustFieldTypeName = rustFieldTypeName;
|
|
24
26
|
exports.matchFuzzy = matchFuzzy;
|
|
25
27
|
exports.dumpNameMatcherProfile = dumpNameMatcherProfile;
|
|
26
28
|
exports.matchReference = matchReference;
|
|
@@ -467,6 +469,32 @@ function matchByQualifiedName(ref, context) {
|
|
|
467
469
|
};
|
|
468
470
|
}
|
|
469
471
|
}
|
|
472
|
+
// Erlang qualified refs (#1610): every erlang function's qualifiedName
|
|
473
|
+
// carries its arity (`mod::f/2`), and refs carry the call-site arity when it
|
|
474
|
+
// is statically known.
|
|
475
|
+
if (ref.language === 'erlang' && ref.referenceName.includes('::')) {
|
|
476
|
+
// A ref WITH arity that missed the exact lookup names an arity that isn't
|
|
477
|
+
// defined (or a module out of repo). Never fall through to the partial
|
|
478
|
+
// match — its "last segment" would be the arity digits — and never settle
|
|
479
|
+
// for a sibling arity: silent beats wrong.
|
|
480
|
+
if (/\/\d{1,3}$/.test(ref.referenceName))
|
|
481
|
+
return null;
|
|
482
|
+
// An arity-LESS qualified ref (dynamic MFA whose args list wasn't a
|
|
483
|
+
// static literal): resolve only when the module defines exactly ONE arity
|
|
484
|
+
// of that function; several arities with no signal is a guess.
|
|
485
|
+
const base = ref.referenceName.slice(ref.referenceName.lastIndexOf('::') + 2);
|
|
486
|
+
const prefix = `${ref.referenceName}/`;
|
|
487
|
+
const arityCands = keepForRef(context.getNodesByName(base)).filter((n) => n.qualifiedName.startsWith(prefix) && /^\d{1,3}$/.test(n.qualifiedName.slice(prefix.length)));
|
|
488
|
+
if (arityCands.length === 1) {
|
|
489
|
+
return {
|
|
490
|
+
original: ref,
|
|
491
|
+
targetNodeId: arityCands[0].id,
|
|
492
|
+
confidence: 0.85,
|
|
493
|
+
resolvedBy: 'qualified-name',
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
470
498
|
// Try partial qualified name match — again preferring the call site's own
|
|
471
499
|
// file when more than one symbol's qualifiedName ends with the reference.
|
|
472
500
|
const parts = ref.referenceName.split(/[:.]/);
|
|
@@ -507,6 +535,82 @@ function preferCallSiteFile(nodes, callSiteFile) {
|
|
|
507
535
|
}
|
|
508
536
|
return same.length ? [...same, ...other] : nodes;
|
|
509
537
|
}
|
|
538
|
+
/**
|
|
539
|
+
* Languages whose object literals declare callable members — `export const
|
|
540
|
+
* api = { call() {…}, get: () => {…} }` used as a namespace (#1573).
|
|
541
|
+
*/
|
|
542
|
+
const OBJECT_LITERAL_LANGUAGES = new Set(['typescript', 'tsx', 'javascript', 'jsx', 'arkts']);
|
|
543
|
+
/** True when `inner`'s source range lies within `outer`'s (lines, then columns on a shared line). */
|
|
544
|
+
function rangeWithin(inner, outer) {
|
|
545
|
+
const innerEnd = inner.endLine ?? inner.startLine;
|
|
546
|
+
const outerEnd = outer.endLine ?? outer.startLine;
|
|
547
|
+
if (inner.startLine < outer.startLine || innerEnd > outerEnd)
|
|
548
|
+
return false;
|
|
549
|
+
if (inner.startLine === outer.startLine && inner.startColumn < outer.startColumn)
|
|
550
|
+
return false;
|
|
551
|
+
if (innerEnd === outerEnd && inner.endColumn > outer.endColumn)
|
|
552
|
+
return false;
|
|
553
|
+
return true;
|
|
554
|
+
}
|
|
555
|
+
function sameRange(a, b) {
|
|
556
|
+
return (a.startLine === b.startLine &&
|
|
557
|
+
a.startColumn === b.startColumn &&
|
|
558
|
+
(a.endLine ?? a.startLine) === (b.endLine ?? b.startLine) &&
|
|
559
|
+
a.endColumn === b.endColumn);
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Resolve `container.member` where `container` is a VALUE holding an object
|
|
563
|
+
* literal — `export const api = { call() {…}, get: () => {…} }` used as the
|
|
564
|
+
* module's namespace (#1573). The members are extracted as plain functions
|
|
565
|
+
* with BARE qualified names inside the constant's source extent (there is no
|
|
566
|
+
* `api::call`), so neither the `Container::member` lookup the class-shaped
|
|
567
|
+
* kinds use (#825) nor the declared-type inference for singleton instances
|
|
568
|
+
* (#1292) can reach them, and every such call resolved to nothing — or, via
|
|
569
|
+
* an import, to the constant itself. This looks the member up by CONTAINMENT:
|
|
570
|
+
* a node named `member` whose range lies inside the container's, in the
|
|
571
|
+
* container's own file. A helper declared inside a member's body is not a
|
|
572
|
+
* member and is skipped; nothing else in the file can donate a match. Calls
|
|
573
|
+
* take callable kinds only; other references accept value members too.
|
|
574
|
+
*/
|
|
575
|
+
function resolveObjectLiteralMember(container, member, ref, context, confidence, resolvedBy) {
|
|
576
|
+
if (container.kind !== 'constant' && container.kind !== 'variable')
|
|
577
|
+
return null;
|
|
578
|
+
if (!OBJECT_LITERAL_LANGUAGES.has(container.language))
|
|
579
|
+
return null;
|
|
580
|
+
if (!sameLanguageFamily(container.language, ref.language))
|
|
581
|
+
return null;
|
|
582
|
+
const inFile = context.getNodesInFile(container.filePath);
|
|
583
|
+
const callable = (n) => n.kind === 'function' || n.kind === 'method';
|
|
584
|
+
const valueMember = (n) => callable(n) || n.kind === 'property' || n.kind === 'variable' || n.kind === 'constant';
|
|
585
|
+
const accepts = ref.referenceKind === 'calls' ? callable : valueMember;
|
|
586
|
+
const inside = inFile.filter((n) => n.id !== container.id && rangeWithin(n, container));
|
|
587
|
+
let candidates = inside.filter((n) => n.name === member && accepts(n));
|
|
588
|
+
if (candidates.length === 0)
|
|
589
|
+
return null;
|
|
590
|
+
// Drop a candidate nested inside ANOTHER callable's body within the literal
|
|
591
|
+
// (`{ run() { const call = () => {}; } }` — `call` is `run`'s local, not a
|
|
592
|
+
// member). Strict containment: an identically-ranged sibling node for the
|
|
593
|
+
// same member (a property node over an arrow function) is not a body.
|
|
594
|
+
const bodies = inside.filter(callable);
|
|
595
|
+
candidates = candidates.filter((c) => !bodies.some((b) => b.id !== c.id && !sameRange(b, c) && rangeWithin(c, b)));
|
|
596
|
+
if (candidates.length === 0)
|
|
597
|
+
return null;
|
|
598
|
+
// Several survivors (a property AND a function for one arrow member, say):
|
|
599
|
+
// a callable first, then the earliest in source order.
|
|
600
|
+
candidates.sort((a, b) => {
|
|
601
|
+
const ca = callable(a) ? 0 : 1;
|
|
602
|
+
const cb = callable(b) ? 0 : 1;
|
|
603
|
+
if (ca !== cb)
|
|
604
|
+
return ca - cb;
|
|
605
|
+
return a.startLine - b.startLine || a.startColumn - b.startColumn;
|
|
606
|
+
});
|
|
607
|
+
return {
|
|
608
|
+
original: ref,
|
|
609
|
+
targetNodeId: candidates[0].id,
|
|
610
|
+
confidence,
|
|
611
|
+
resolvedBy,
|
|
612
|
+
};
|
|
613
|
+
}
|
|
510
614
|
// Exported for the precedence unit tests (#1079): they assert the
|
|
511
615
|
// preferredFqn → same-file → matches[0] ordering directly.
|
|
512
616
|
function resolveMethodOnType(typeName, methodName, ref, context, confidence, resolvedBy,
|
|
@@ -1592,6 +1696,17 @@ function matchMethodCall(ref, context) {
|
|
|
1592
1696
|
if (ref.language === 'go' && dotMatch && objectOrClass.includes('.')) {
|
|
1593
1697
|
return matchGoFieldChainCall(objectOrClass, methodName, ref, context);
|
|
1594
1698
|
}
|
|
1699
|
+
// Rust call through a field of the enclosing type — `self.inner.run()`,
|
|
1700
|
+
// emitted as `self.inner.run` (#1585). Same discipline as the Go branch
|
|
1701
|
+
// above, and EXCLUSIVE for the same reason: validated field-type inference
|
|
1702
|
+
// or nothing. Letting this shape reach the bare-name strategies below is
|
|
1703
|
+
// how `self.inner.run()` resolved to a same-named method on an unrelated
|
|
1704
|
+
// type — or to the calling method itself, a self-edge the source doesn't
|
|
1705
|
+
// contain — whenever the field's type was external or merely shared a
|
|
1706
|
+
// method name with something nearby.
|
|
1707
|
+
if (ref.language === 'rust' && dotMatch && objectOrClass.startsWith('self.')) {
|
|
1708
|
+
return matchRustSelfFieldCall(objectOrClass.slice('self.'.length), methodName, ref, context);
|
|
1709
|
+
}
|
|
1595
1710
|
// Java/Kotlin: receiver may be a field whose name doesn't match the type by
|
|
1596
1711
|
// Java naming convention (`userbo` → class `UserBO`, abbreviated). Look up
|
|
1597
1712
|
// the field in the enclosing class to get its declared type, then resolve
|
|
@@ -1611,6 +1726,26 @@ function matchMethodCall(ref, context) {
|
|
|
1611
1726
|
}
|
|
1612
1727
|
}
|
|
1613
1728
|
}
|
|
1729
|
+
// Object-literal namespace receiver (#1573): `api.call()` where `api` is a
|
|
1730
|
+
// same-file `const api = { call() {…}, get: () => {…} }`. Its members are
|
|
1731
|
+
// plain functions with bare names inside the constant's extent — no
|
|
1732
|
+
// `Container::member` qualified name — so none of the class-shaped
|
|
1733
|
+
// strategies below can see them (Strategy 3 only considers `method`
|
|
1734
|
+
// kinds) and the call resolved to nothing at all. Same file only: a
|
|
1735
|
+
// cross-file use reaches the same helper through the import path.
|
|
1736
|
+
if (dotMatch && !objectOrClass.includes('.') && OBJECT_LITERAL_LANGUAGES.has(ref.language)) {
|
|
1737
|
+
const literalMatch = nmTimedT('mc-literal', ref, () => {
|
|
1738
|
+
const holders = preferCallSiteFile(context.getNodesByName(objectOrClass), ref.filePath).filter((n) => (n.kind === 'constant' || n.kind === 'variable') && n.filePath === ref.filePath);
|
|
1739
|
+
for (const holder of holders) {
|
|
1740
|
+
const hit = resolveObjectLiteralMember(holder, methodName, ref, context, 0.85, 'instance-method');
|
|
1741
|
+
if (hit)
|
|
1742
|
+
return hit;
|
|
1743
|
+
}
|
|
1744
|
+
return null;
|
|
1745
|
+
});
|
|
1746
|
+
if (literalMatch)
|
|
1747
|
+
return literalMatch;
|
|
1748
|
+
}
|
|
1614
1749
|
// Strategy 1: Direct class name match (existing logic). When the receiver
|
|
1615
1750
|
// names a class that exists in several files (`Logger.log()` / `Logger::log()`
|
|
1616
1751
|
// with a `Logger` in both `a/` and `b/`), try the class in the call site's
|
|
@@ -1814,6 +1949,109 @@ function matchGoFieldChainCall(receiverChain, methodName, ref, context) {
|
|
|
1814
1949
|
}
|
|
1815
1950
|
return null;
|
|
1816
1951
|
}
|
|
1952
|
+
// Rust primitives and the prelude's own types: a field of one of these never
|
|
1953
|
+
// names a project type, so a `self.<field>.<method>()` on it stays unresolved.
|
|
1954
|
+
const RUST_NON_PROJECT_FIELD_TYPES = new Set([
|
|
1955
|
+
'bool', 'char', 'str', 'String',
|
|
1956
|
+
'i8', 'i16', 'i32', 'i64', 'i128', 'isize',
|
|
1957
|
+
'u8', 'u16', 'u32', 'u64', 'u128', 'usize',
|
|
1958
|
+
'f32', 'f64',
|
|
1959
|
+
'Self', 'self',
|
|
1960
|
+
]);
|
|
1961
|
+
/**
|
|
1962
|
+
* Reduce a Rust field's declared type text to the simple name of the type a
|
|
1963
|
+
* method call on that field auto-derefs to, or null when there is none we can
|
|
1964
|
+
* name. Only the layers Rust's method-call auto-deref looks through are
|
|
1965
|
+
* unwrapped: references (`&`, `&'a mut`) and the owning smart pointers
|
|
1966
|
+
* (`Box`, `Rc`, `Arc`) — `self.inner.run()` with `inner: Box<Inner>` calls
|
|
1967
|
+
* `Inner::run`. Containers that do NOT auto-deref to their parameter
|
|
1968
|
+
* (`Option<Inner>`, `Vec<Inner>`, `Mutex<Inner>`, `RefCell<Inner>`) keep their
|
|
1969
|
+
* own name and, having no project node, resolve to nothing — `self.items.push()`
|
|
1970
|
+
* must never become `Inner::push`. A trait object (`Box<dyn Source>`) yields
|
|
1971
|
+
* the trait, whose method node the interface-impl synthesizer fans out. A
|
|
1972
|
+
* generic parameter (`T`), a primitive, a tuple / array / raw pointer / fn
|
|
1973
|
+
* type, or a non-identifier yields null.
|
|
1974
|
+
*/
|
|
1975
|
+
function rustFieldTypeName(raw) {
|
|
1976
|
+
let t = raw.trim();
|
|
1977
|
+
for (;;) {
|
|
1978
|
+
const before = t;
|
|
1979
|
+
t = t.replace(/^&\s*(?:'\w+\s+)?(?:mut\s+)?/, '');
|
|
1980
|
+
t = t.replace(/^(?:Box|Rc|Arc)\s*<\s*/, '');
|
|
1981
|
+
t = t.replace(/^(?:dyn|impl)\s+/, '');
|
|
1982
|
+
if (t === before)
|
|
1983
|
+
break;
|
|
1984
|
+
}
|
|
1985
|
+
// Drop generic args, the closing `>`s of unwrapped pointers, and trait-object
|
|
1986
|
+
// bounds (`dyn Source + Send`); keep the last path segment.
|
|
1987
|
+
t = t.replace(/[<>+].*$/, '').trim();
|
|
1988
|
+
const seg = t.split('::').filter(Boolean).pop();
|
|
1989
|
+
if (!seg || !/^[A-Za-z_]\w*$/.test(seg))
|
|
1990
|
+
return null;
|
|
1991
|
+
if (RUST_NON_PROJECT_FIELD_TYPES.has(seg))
|
|
1992
|
+
return null;
|
|
1993
|
+
if (/^[A-Z]$/.test(seg))
|
|
1994
|
+
return null; // bare single-letter generic parameter
|
|
1995
|
+
return seg;
|
|
1996
|
+
}
|
|
1997
|
+
/**
|
|
1998
|
+
* Resolve a Rust call through a field of the enclosing type —
|
|
1999
|
+
* `self.inner.run()`, emitted by the extractor as `self.inner.run` (#1585).
|
|
2000
|
+
* Mirrors the Go 2-hop precedent above (#1276): the owner type is the calling
|
|
2001
|
+
* method's qualified-name prefix (`Outer::run` → `Outer`), the field's declared
|
|
2002
|
+
* type comes from the owner struct's OWN declaration lines, and the method is
|
|
2003
|
+
* resolved AND VALIDATED on that type by resolveMethodOnType. The caller
|
|
2004
|
+
* treats this branch as exclusive for `self.<field>` receivers: a field whose
|
|
2005
|
+
* type is external (`std::vec::IntoIter`, `regex::Regex`), a generic
|
|
2006
|
+
* parameter, or not declared where we can see it yields null and the ref stays
|
|
2007
|
+
* unresolved. Rust struct fields are not graph nodes, so the declaration text
|
|
2008
|
+
* is the only place the type lives.
|
|
2009
|
+
*/
|
|
2010
|
+
function matchRustSelfFieldCall(field, methodName, ref, context) {
|
|
2011
|
+
// The extractor only ever emits a single field hop; anything else is not ours.
|
|
2012
|
+
if (!field || field.includes('.'))
|
|
2013
|
+
return null;
|
|
2014
|
+
const caller = context.getNodeById?.(ref.fromNodeId);
|
|
2015
|
+
if (!caller)
|
|
2016
|
+
return null;
|
|
2017
|
+
const sep = caller.qualifiedName.lastIndexOf('::');
|
|
2018
|
+
if (sep <= 0)
|
|
2019
|
+
return null; // a free fn has no `self`
|
|
2020
|
+
const owner = caller.qualifiedName.slice(0, sep).split('::').pop();
|
|
2021
|
+
if (!owner)
|
|
2022
|
+
return null;
|
|
2023
|
+
const owners = preferCallSiteFile(context.getNodesByName(owner), ref.filePath).filter((n) => (n.kind === 'struct' || n.kind === 'union' || n.kind === 'class') &&
|
|
2024
|
+
n.language === 'rust');
|
|
2025
|
+
const fieldEsc = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
2026
|
+
// `pub inner: Inner,` / `inner: Box<dyn Source>,` / `pub(crate) inner: T }` —
|
|
2027
|
+
// the type text runs to the field separator. A comma inside generic args
|
|
2028
|
+
// (`HashMap<K, V>`) truncates the capture, which rustFieldTypeName then
|
|
2029
|
+
// reduces to the container's own name — exactly the non-deref case it
|
|
2030
|
+
// refuses anyway.
|
|
2031
|
+
const fieldRe = new RegExp(`\\b${fieldEsc}\\s*:\\s*([^,{}]+)`);
|
|
2032
|
+
for (const s of owners) {
|
|
2033
|
+
const source = context.readFile(s.filePath);
|
|
2034
|
+
if (!source)
|
|
2035
|
+
continue;
|
|
2036
|
+
// Only the struct's own declaration lines, comment-stripped line by line —
|
|
2037
|
+
// same discipline as the Go helper: prose or a same-named identifier
|
|
2038
|
+
// elsewhere in the file can never donate a type.
|
|
2039
|
+
const declLines = source.split('\n').slice(Math.max(0, s.startLine - 1), s.endLine);
|
|
2040
|
+
for (const rawLine of declLines) {
|
|
2041
|
+
const line = rawLine.replace(/\/\/.*$/, '').replace(/\/\*.*?\*\//g, '');
|
|
2042
|
+
const m = line.match(fieldRe);
|
|
2043
|
+
if (!m || !m[1])
|
|
2044
|
+
continue;
|
|
2045
|
+
const fieldType = rustFieldTypeName(m[1]);
|
|
2046
|
+
// The field is declared here; whether or not its type names a project
|
|
2047
|
+
// symbol, this owner is the answer — no other same-named struct applies.
|
|
2048
|
+
if (!fieldType)
|
|
2049
|
+
return null;
|
|
2050
|
+
return resolveMethodOnType(fieldType, methodName, ref, context, 0.85, 'instance-method');
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
return null;
|
|
2054
|
+
}
|
|
1817
2055
|
/**
|
|
1818
2056
|
* Split a camelCase or PascalCase string into words.
|
|
1819
2057
|
*/
|
|
@@ -2070,6 +2308,46 @@ function matchReference(ref, context) {
|
|
|
2070
2308
|
resolvedBy: 'exact-match',
|
|
2071
2309
|
};
|
|
2072
2310
|
}
|
|
2311
|
+
// Erlang call/fun refs carry the call-site arity (`f/1` — #1610) because
|
|
2312
|
+
// arity is part of the function's identity and every erlang function's
|
|
2313
|
+
// qualifiedName carries it (`mod::f/1`). Resolve ONLY to a definition of
|
|
2314
|
+
// that exact arity: the call site's own file first (a local call targets its
|
|
2315
|
+
// own module by language semantics; `-import`ed functions ride the
|
|
2316
|
+
// cross-file branch), and when no definition of that arity exists anywhere,
|
|
2317
|
+
// resolve to NOTHING rather than a sibling arity — the real target may be
|
|
2318
|
+
// macro-generated or out of repo, and a wrong-arity edge is worse than none.
|
|
2319
|
+
if (ref.language === 'erlang' &&
|
|
2320
|
+
!ref.referenceName.includes('::') &&
|
|
2321
|
+
(ref.referenceKind === 'calls' || ref.referenceKind === 'references')) {
|
|
2322
|
+
const am = /^(.+)\/(\d{1,3})$/.exec(ref.referenceName);
|
|
2323
|
+
if (am) {
|
|
2324
|
+
// endsWith is length-anchored, so `/1` cannot match `…/11`.
|
|
2325
|
+
const arityTail = `/${am[2]}`;
|
|
2326
|
+
const candidates = context
|
|
2327
|
+
.getNodesByName(am[1])
|
|
2328
|
+
.filter((n) => n.language === 'erlang' && n.kind === 'function' && n.qualifiedName.endsWith(arityTail));
|
|
2329
|
+
if (candidates.length > 0) {
|
|
2330
|
+
const sameFile = candidates.find((n) => n.filePath === ref.filePath);
|
|
2331
|
+
if (sameFile) {
|
|
2332
|
+
return { original: ref, targetNodeId: sameFile.id, confidence: 0.95, resolvedBy: 'exact-match' };
|
|
2333
|
+
}
|
|
2334
|
+
if (candidates.length === 1) {
|
|
2335
|
+
return { original: ref, targetNodeId: candidates[0].id, confidence: 0.8, resolvedBy: 'exact-match' };
|
|
2336
|
+
}
|
|
2337
|
+
const best = findBestMatch(ref, candidates, context);
|
|
2338
|
+
if (best) {
|
|
2339
|
+
const proximity = computePathProximity(ref.filePath, best.filePath);
|
|
2340
|
+
return {
|
|
2341
|
+
original: ref,
|
|
2342
|
+
targetNodeId: best.id,
|
|
2343
|
+
confidence: proximity >= 30 ? 0.7 : 0.4,
|
|
2344
|
+
resolvedBy: 'exact-match',
|
|
2345
|
+
};
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
return null;
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2073
2351
|
// Try strategies in order of confidence
|
|
2074
2352
|
let result;
|
|
2075
2353
|
// 0. File path match (e.g., "snippets/drawer-menu.liquid" → file node)
|