graph-indexer 3.0.1 → 3.1.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/CHANGELOG.md +404 -0
- package/README.md +61 -8
- package/bin/graph-indexer.mjs +46 -5
- package/integrations/claude-code/.claude-plugin/plugin.json +1 -1
- package/integrations/claude-code/agents/code-structure.md +0 -3
- package/integrations/claude-code/skills/setup/SKILL.md +26 -0
- package/integrations/claude-code-local/.claude-plugin/plugin.json +9 -0
- package/integrations/{claude-code → claude-code-local}/.mcp.json +1 -1
- package/integrations/claude-code-local/README.md +10 -0
- package/integrations/claude-code-local/agents/code-structure.md +22 -0
- package/integrations/{claude-code → claude-code-local}/hooks/gi-hook.mjs +3 -1
- package/package.json +1 -1
- package/src/cli/helper.mjs +15 -1
- package/src/cli/hook.mjs +123 -41
- package/src/cli/init.mjs +16 -1
- package/src/cli/resident.mjs +1 -1
- package/src/index/indexer.mjs +77 -41
- package/src/index/modules.mjs +76 -2
- package/src/index/parse-worker.mjs +12 -0
- package/src/index/parsepool.mjs +73 -0
- package/src/index/resolver.mjs +362 -23
- package/src/mcp/server.mjs +16 -12
- package/src/mcp/tools.mjs +417 -25
- package/src/parse/arity.mjs +39 -2
- package/src/parse/extract.mjs +269 -45
- package/src/parse/lang/ecma.mjs +2 -0
- package/src/parse/lang/go.mjs +2 -0
- package/src/parse/lang/jvm.mjs +33 -0
- package/src/parse/lang/markdown.mjs +92 -0
- package/src/parse/lang/python.mjs +43 -0
- package/src/parse/lang/rust.mjs +215 -15
- package/src/parse/lang/sfc.mjs +125 -0
- package/src/parse/languages.mjs +9 -2
- package/src/query/check.mjs +49 -13
- package/src/query/facts.mjs +3 -2
- package/src/query/injection-spring.mjs +405 -0
- package/src/query/injection.mjs +173 -0
- package/src/query/intel.mjs +76 -14
- package/src/query/issue.mjs +156 -0
- package/src/query/orm.mjs +286 -0
- package/src/query/read.mjs +6 -1
- package/src/query/report.mjs +53 -0
- package/src/query/routes.mjs +142 -0
- package/src/query/showcase.mjs +59 -0
- package/src/search/search.mjs +85 -17
- package/src/search/semantic.mjs +201 -0
- package/src/search/vectors.mjs +122 -0
- package/src/store/db.mjs +22 -4
- package/vendor/grammars/README.md +47 -0
- package/vendor/grammars/kotlin.wasm.gz +0 -0
- package/vendor/grammars/manifest.json +4 -4
- /package/integrations/{claude-code → claude-code-local}/hooks/hooks.json +0 -0
package/src/index/resolver.mjs
CHANGED
|
@@ -15,10 +15,13 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import path from 'node:path';
|
|
17
17
|
import { normalizeType } from '../parse/extract.mjs';
|
|
18
|
-
import { fits, sigArity } from '../parse/arity.mjs';
|
|
18
|
+
import { fits, sigArity, sigParamTypes, simpleParamType } from '../parse/arity.mjs';
|
|
19
19
|
import { escapeRegExp } from '../util/regex.mjs';
|
|
20
20
|
|
|
21
21
|
const TYPE_KINDS = new Set(['class', 'interface', 'struct', 'enum', 'trait', 'type', 'object', 'module', 'impl']);
|
|
22
|
+
// generic wrappers whose members are those of their argument (`InstanceType<typeof Repl>`): a call on
|
|
23
|
+
// one is not on a type the index lacks, so it keeps the by-name fallback
|
|
24
|
+
const TYPE_WRAPPERS = new Set(['ReturnType', 'InstanceType', 'Awaited', 'Partial', 'Required', 'Readonly', 'NonNullable', 'Pick', 'Omit', 'Mocked', 'MockedObject', 'Type']);
|
|
22
25
|
// element accesses one descriptor may stack (`xs[i][j]`…): a field assigned an element of itself
|
|
23
26
|
// (`self.impl = self.impl[0]`) would add one on every lookup, with no end
|
|
24
27
|
const MAX_ELEM = 8;
|
|
@@ -28,6 +31,7 @@ const MEMBER_HOLDERS = new Set(['class', 'interface', 'struct', 'enum', 'trait',
|
|
|
28
31
|
const MEMBER_KINDS = new Set(['field', 'property', 'method', 'constructor']);
|
|
29
32
|
const PACKAGE_LANGS = new Set(['java', 'kotlin', 'scala', 'csharp', 'php']);
|
|
30
33
|
const DIR_PACKAGE_LANGS = new Set(['go']);
|
|
34
|
+
const SINGLE_TYPE_IMPORTS = new Set(['java', 'kotlin', 'scala']);
|
|
31
35
|
const MAX_GLOBAL_CANDIDATES = 12;
|
|
32
36
|
|
|
33
37
|
/**
|
|
@@ -220,11 +224,15 @@ export class SymbolTable {
|
|
|
220
224
|
file(id) { return this.files.get(id); }
|
|
221
225
|
}
|
|
222
226
|
|
|
227
|
+
/** A resolved type as membersOf takes it: {id, name, fileId}, and `allOf` for a value of several traits. */
|
|
228
|
+
const asType = (t) => (t.allOf ? t : { id: t.id ?? null, name: t.name, fileId: t.fileId ?? null });
|
|
229
|
+
|
|
223
230
|
export class Resolver {
|
|
224
|
-
constructor(table, specsById, { sigOf = null } = {}) {
|
|
231
|
+
constructor(table, specsById, { sigOf = null, modules = null } = {}) {
|
|
225
232
|
this.t = table;
|
|
226
233
|
this.specs = specsById; // lang id -> spec (implicitThis etc.)
|
|
227
234
|
this.sigOf = sigOf; // symbol id -> signature text (read on demand: callback parameter types)
|
|
235
|
+
this.modules = modules; // module resolver (Rust paths: `crate::a::B`, `super::f`, `other_crate::x`)
|
|
228
236
|
this.memo = new Map();
|
|
229
237
|
}
|
|
230
238
|
|
|
@@ -265,22 +273,39 @@ export class Resolver {
|
|
|
265
273
|
/** Members named `name` of a type (by id and/or name), including inherited ones. */
|
|
266
274
|
membersOf(type, name, depth = 0, seen = new Set()) {
|
|
267
275
|
if (!type || depth > 6) return [];
|
|
276
|
+
// a value bound by several traits (`W: AsyncWrite + AsyncSeek`): the member of each that has it
|
|
277
|
+
if (type.allOf) return [...new Set(type.allOf.flatMap(t => this.membersOf(t, name, depth + 1, new Set(seen))))];
|
|
268
278
|
const key = (type.id ?? '') + ':' + type.name;
|
|
269
279
|
if (seen.has(key)) return [];
|
|
270
280
|
seen.add(key);
|
|
271
281
|
const out = [];
|
|
272
282
|
if (type.id != null) for (const id of this.t.byParent.get(type.id)?.get(name) ?? []) out.push(id);
|
|
283
|
+
const ownSym = type.id != null ? this.t.sym(type.id) : null;
|
|
273
284
|
for (const id of this.t.byOwner.get(type.name) ?? []) {
|
|
274
285
|
const s = this.t.sym(id);
|
|
275
286
|
if (s.name !== name) continue;
|
|
287
|
+
// a blanket impl's method (`impl<M: Matcher> Matcher for &M`) is the trait's own, called through its declaration
|
|
288
|
+
if (ownSym?.kind === 'trait' && s.bases?.includes(type.name) && out.length) continue;
|
|
276
289
|
// owner-declared members must live near the type (same package/dir/file family)
|
|
277
290
|
if (type.fileId != null) {
|
|
278
291
|
const a = this.t.file(s.fileId), b = this.t.file(type.fileId);
|
|
279
|
-
if (a && b && a.lang ===
|
|
292
|
+
if (a && b && a.lang === 'rust' && b.lang === 'rust') { if (this.#implFor(s, type, a, b)) out.push(id); }
|
|
293
|
+
else if (a && b && a.lang === b.lang && (a.dir === b.dir || a.package === b.package || a.lang === 'cpp' || a.lang === 'c' || a.lang === 'javascript' || a.lang === 'typescript')) out.push(id);
|
|
280
294
|
} else out.push(id);
|
|
281
295
|
}
|
|
282
|
-
if (out.length) return out;
|
|
283
296
|
const typeSym = type.id != null ? this.t.sym(type.id) : null;
|
|
297
|
+
if (out.length) {
|
|
298
|
+
// Python: `self.x = …` hoisted as a field of a class whose base declares `x` as a property
|
|
299
|
+
// runs the base's setter — the attribute is the base's property, not a new field
|
|
300
|
+
if (typeSym && out.every(id => this.t.sym(id)?.kind === 'field') && this.t.file(typeSym.fileId)?.lang === 'python') {
|
|
301
|
+
for (const base of typeSym.bases ?? []) {
|
|
302
|
+
const bt = this.resolveTypeName(base, typeSym.fileId);
|
|
303
|
+
const inherited = bt ? this.membersOf({ id: bt.id, name: bt.name, fileId: bt.fileId }, name, depth + 1, new Set(seen)) : [];
|
|
304
|
+
if (inherited.some(id => this.t.sym(id)?.kind === 'method')) return inherited;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return out;
|
|
308
|
+
}
|
|
284
309
|
for (const base of typeSym?.bases ?? []) {
|
|
285
310
|
const bt = this.resolveTypeName(base, typeSym.fileId);
|
|
286
311
|
if (bt) {
|
|
@@ -294,6 +319,20 @@ export class Resolver {
|
|
|
294
319
|
return out;
|
|
295
320
|
}
|
|
296
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Is a Rust member declared in an `impl Owner` block a member of this type? Several crates of a
|
|
324
|
+
* workspace may each have a type of that name (ripgrep has four `RegexMatcher`s): the impl's
|
|
325
|
+
* owner is the type its own file sees under that name, or, when that file cannot tell, a type of
|
|
326
|
+
* the same Cargo package (an inherent impl lives in the type's crate).
|
|
327
|
+
*/
|
|
328
|
+
#implFor(s, type, a, b) {
|
|
329
|
+
if (type.id == null || s.fileId === type.fileId) return true;
|
|
330
|
+
const seen = this.resolveTypeName(type.name, s.fileId);
|
|
331
|
+
if (seen) return seen.id === type.id;
|
|
332
|
+
const pa = this.modules?.rustPackageOf?.(a.path), pb = this.modules?.rustPackageOf?.(b.path);
|
|
333
|
+
return pa == null || pb == null || pa === pb;
|
|
334
|
+
}
|
|
335
|
+
|
|
297
336
|
/**
|
|
298
337
|
* Method resolution order of a type: C3 linearization over its resolved bases (Python's MRO; for
|
|
299
338
|
* single inheritance it is the chain of supertypes). Unresolved bases are left out; an inconsistent
|
|
@@ -385,6 +424,8 @@ export class Resolver {
|
|
|
385
424
|
return desc.split('[]').length - 1 > MAX_ELEM ? null : this.resolveRecvType(desc, fileId, null);
|
|
386
425
|
}
|
|
387
426
|
let s = str;
|
|
427
|
+
// a tuple (`(A,B)`, Rust): its elements are reached by position (`#0`), see resolveRecvType
|
|
428
|
+
if (s.startsWith('(') && s.endsWith(')')) return elem ? null : { tupleOf: s.slice(1, -1).split(','), fileId };
|
|
388
429
|
for (let i = 0; i < elem; i++) {
|
|
389
430
|
if (!s.endsWith('[]') && !s.endsWith('{}')) {
|
|
390
431
|
// an element of a collection class (`class Registry extends Map<string, Module>`)
|
|
@@ -394,6 +435,13 @@ export class Resolver {
|
|
|
394
435
|
s = s.slice(0, -2);
|
|
395
436
|
}
|
|
396
437
|
if (s === '!') return EXTERNAL;
|
|
438
|
+
if (s.includes('+') && !s.endsWith('[]') && !s.endsWith('{}')) {
|
|
439
|
+
// `A+B` (Rust bounds, see normalizeType): the traits of the repository among them
|
|
440
|
+
const ts = s.split('+').map(x => this.typeFromString(x, fileId)).filter(Boolean);
|
|
441
|
+
const own = ts.filter(x => x !== EXTERNAL && x.id != null);
|
|
442
|
+
if (!own.length) return ts.length ? EXTERNAL : null;
|
|
443
|
+
return own.length === 1 ? own[0] : { id: null, name: s, fileId, allOf: own };
|
|
444
|
+
}
|
|
397
445
|
if (s.endsWith('[]')) return { arrayOf: s.slice(0, -2), fileId }; // a collection value
|
|
398
446
|
if (s.endsWith('{}')) return { mapOf: s.slice(0, -2), fileId }; // a map value
|
|
399
447
|
if (this.#qualifiedType(s, fileId) === EXTERNAL || this.#nestedType(s, fileId) === EXTERNAL) return EXTERNAL;
|
|
@@ -432,7 +480,7 @@ export class Resolver {
|
|
|
432
480
|
if (TYPE_KINDS.has(s.kind)) cur = head.elem ? null : s; // constructor call
|
|
433
481
|
else if (s.type) cur = this.typeFromString(s.type, s.fileId, head.elem);
|
|
434
482
|
}
|
|
435
|
-
if ((cur?.arrayOf != null || cur?.mapOf != null) && parts.length === 1) cur = EXTERNAL;
|
|
483
|
+
if ((cur?.arrayOf != null || cur?.mapOf != null || cur?.tupleOf != null) && parts.length === 1) cur = EXTERNAL;
|
|
436
484
|
} else if (head.name.startsWith('cb:')) {
|
|
437
485
|
cur = this.#callbackParam(head.name, fileId, srcId, head.elem);
|
|
438
486
|
} else if (!head.elem) {
|
|
@@ -456,6 +504,20 @@ export class Resolver {
|
|
|
456
504
|
for (let i = 1; i < parts.length && cur && cur !== EXTERNAL; i++) {
|
|
457
505
|
const { name, elem } = parts[i];
|
|
458
506
|
if (!name) { cur = elem ? this.#elementOf(cur, elem) : cur; continue; } // `T#[]`: an element of T itself
|
|
507
|
+
// `~m`: member m of the type if it has one, else the same value (`x.clone()`, `opt.unwrap()`)
|
|
508
|
+
if (name.startsWith('~')) {
|
|
509
|
+
const real = name.slice(1);
|
|
510
|
+
const own = cur.arrayOf == null && cur.mapOf == null && cur.tupleOf == null ? this.membersOf(asType(cur), real) : [];
|
|
511
|
+
if (!own.length) { cur = elem ? this.#elementOf(cur, elem) : cur; continue; }
|
|
512
|
+
parts[i] = { name: real, elem };
|
|
513
|
+
i--;
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
if (cur.tupleOf != null) {
|
|
517
|
+
const el = /^\d+$/.test(name) ? cur.tupleOf[Number(name)] : null;
|
|
518
|
+
cur = el && el !== '?' ? this.typeFromString(el, cur.fileId, elem) : EXTERNAL;
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
459
521
|
if (cur.arrayOf != null) {
|
|
460
522
|
// `list.get(0)` / `xs.first()` reach the element; other members are the language's
|
|
461
523
|
cur = spec?.elementMethods?.has(name) ? this.typeFromString(cur.arrayOf, cur.fileId, elem) : EXTERNAL;
|
|
@@ -467,7 +529,7 @@ export class Resolver {
|
|
|
467
529
|
: spec?.mapValueMethods?.has(name) ? this.typeFromString(cur.mapOf + '[]', cur.fileId, elem) : EXTERNAL;
|
|
468
530
|
continue;
|
|
469
531
|
}
|
|
470
|
-
const mem = this.membersOf(
|
|
532
|
+
const mem = this.membersOf(asType(cur), name);
|
|
471
533
|
if (!mem.length && cur.id != null && spec?.qualifiedTypes) {
|
|
472
534
|
// Go: an embedded field is named after its type (`m.MatchRegexp` in a struct embedding MatchRegexp)
|
|
473
535
|
const os = this.t.sym(cur.id);
|
|
@@ -484,6 +546,8 @@ export class Resolver {
|
|
|
484
546
|
let next = null;
|
|
485
547
|
for (const id of mem) {
|
|
486
548
|
const m = this.t.sym(id);
|
|
549
|
+
// a nested type reached through its outer one (`Token.TokenType.valueOf(…)`)
|
|
550
|
+
if (TYPE_KINDS.has(m.kind) && !elem) { next = { id: m.id, name: m.name, fileId: m.fileId }; break; }
|
|
487
551
|
if (m.type) { next = this.typeFromString(m.type, m.fileId, elem); if (next) break; }
|
|
488
552
|
}
|
|
489
553
|
if (!next) {
|
|
@@ -499,7 +563,7 @@ export class Resolver {
|
|
|
499
563
|
}
|
|
500
564
|
cur = next;
|
|
501
565
|
}
|
|
502
|
-
const res = cur === EXTERNAL || cur?.arrayOf != null || cur?.mapOf != null
|
|
566
|
+
const res = cur === EXTERNAL || cur?.arrayOf != null || cur?.mapOf != null || cur?.tupleOf != null ? EXTERNAL : cur ? asType(cur) : null;
|
|
503
567
|
this.memo.set(k, res);
|
|
504
568
|
return res;
|
|
505
569
|
}
|
|
@@ -562,9 +626,11 @@ export class Resolver {
|
|
|
562
626
|
if (targetFileId == null || depth > 6 || seen.has(targetFileId)) return [];
|
|
563
627
|
seen.add(targetFileId);
|
|
564
628
|
const out = [];
|
|
629
|
+
// a Rust or Go method (declared in an `impl` block or with a receiver) is no item of its module
|
|
630
|
+
const lang = this.t.file(targetFileId)?.lang, typedOwners = lang === 'rust' || lang === 'go';
|
|
565
631
|
for (const id of this.t.fileSyms.get(targetFileId) ?? []) {
|
|
566
632
|
const s = this.t.sym(id);
|
|
567
|
-
if (s.name === name && s.parentId == null) out.push(id);
|
|
633
|
+
if (s.name === name && s.parentId == null && !(typedOwners && s.owner && (MEMBER_KINDS.has(s.kind) || s.kind === 'type'))) out.push(id);
|
|
568
634
|
}
|
|
569
635
|
if (out.length) return out;
|
|
570
636
|
// in Python a later import rebinds the name (`from a import *` then `from b import *`)
|
|
@@ -574,12 +640,41 @@ export class Resolver {
|
|
|
574
640
|
const passes = imp.reexport === name || imp.reexport === '*' || imp.local === name || (imp.wildcard && imp.imported === '*');
|
|
575
641
|
if (!passes || imp.targetFileId == null) continue;
|
|
576
642
|
const inner = imp.reexport === name && imp.imported && imp.imported !== '*' ? imp.imported : (imp.local === name && imp.imported && imp.imported !== '*' ? imp.imported : name);
|
|
577
|
-
|
|
643
|
+
// `export { default as X } from './x'`: x's default export, whatever it is called there
|
|
644
|
+
const r = inner === 'default' && name !== 'default' ? this.#defaultExport(imp.targetFileId, name) : this.exportedFrom(imp.targetFileId, inner, depth + 1, seen);
|
|
578
645
|
if (r.length) return r;
|
|
579
646
|
}
|
|
580
647
|
return out;
|
|
581
648
|
}
|
|
582
649
|
|
|
650
|
+
/** The module a Python package or module file exposes as `seg`: a submodule file, or a module it imports. */
|
|
651
|
+
#submodule(fileId, seg) {
|
|
652
|
+
const f = this.t.file(fileId);
|
|
653
|
+
if (f?.path.endsWith('/__init__.py')) {
|
|
654
|
+
const dir = f.path.slice(0, -'/__init__.py'.length);
|
|
655
|
+
const sub = this.t.fileByPath.get(`${dir}/${seg}.py`) ?? this.t.fileByPath.get(`${dir}/${seg}/__init__.py`);
|
|
656
|
+
if (sub) return sub.id;
|
|
657
|
+
}
|
|
658
|
+
return this.#moduleFileOf(fileId, seg);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/** Whether a module binds `name` through an import of a module outside the repository, following re-exports. */
|
|
662
|
+
#reexportsExternal(targetFileId, name, depth = 0) {
|
|
663
|
+
if (targetFileId == null || depth > 6) return false;
|
|
664
|
+
for (const imp of this.t.imports.get(targetFileId) ?? []) {
|
|
665
|
+
// `export * from './icons'` where ./icons re-exports `name` from a package
|
|
666
|
+
if (imp.reexport === '*' && imp.targetFileId != null && imp.local == null) {
|
|
667
|
+
if (this.#reexportsExternal(imp.targetFileId, name, depth + 1)) return true;
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
if (imp.wildcard || !(imp.local === name || imp.reexport === name)) continue;
|
|
671
|
+
if (imp.targetFileId == null && !imp.targetDir) return true;
|
|
672
|
+
const inner = imp.imported && imp.imported !== '*' && imp.imported !== 'default' ? imp.imported : name;
|
|
673
|
+
if (imp.targetFileId != null && !this.exportedFrom(imp.targetFileId, inner).length) return this.#reexportsExternal(imp.targetFileId, inner, depth + 1);
|
|
674
|
+
}
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
|
|
583
678
|
#symbolsInDir(dir, name, fileId) {
|
|
584
679
|
const out = [];
|
|
585
680
|
for (const fid of this.t.dirFiles.get(dir) ?? []) {
|
|
@@ -591,6 +686,24 @@ export class Resolver {
|
|
|
591
686
|
return out;
|
|
592
687
|
}
|
|
593
688
|
|
|
689
|
+
/**
|
|
690
|
+
* `import java.io.UncheckedIOException;`: a single-type import (Java, Kotlin, Scala) from a
|
|
691
|
+
* package no file of the repository is in, nor an enclosing one — the type is the library's,
|
|
692
|
+
* not a same-name class of the repository.
|
|
693
|
+
*/
|
|
694
|
+
#importsLibraryType(file, imp) {
|
|
695
|
+
if (!SINGLE_TYPE_IMPORTS.has(file.lang) || imp.wildcard || !imp.imported || imp.imported === '*' || !imp.source) return false;
|
|
696
|
+
const segs = imp.source.split('.');
|
|
697
|
+
for (let n = segs.length; n > 0; n--) if (this.t.packageFiles.has(segs.slice(0, n).join('.'))) return false;
|
|
698
|
+
// nor a package that encloses some of the repository's (`import org.acme.X` with `org.acme.core` here)
|
|
699
|
+
if (this.pkgPrefixes?.size !== this.t.packageFiles.size) {
|
|
700
|
+
const set = new Set();
|
|
701
|
+
for (const p of this.t.packageFiles.keys()) { const s = p.split('.'); for (let n = 1; n < s.length; n++) set.add(s.slice(0, n).join('.')); }
|
|
702
|
+
this.pkgPrefixes = { size: this.t.packageFiles.size, set };
|
|
703
|
+
}
|
|
704
|
+
return !this.pkgPrefixes.set.has(imp.source);
|
|
705
|
+
}
|
|
706
|
+
|
|
594
707
|
#symbolsInPackage(pkg, name) {
|
|
595
708
|
const out = [];
|
|
596
709
|
for (const fid of this.t.packageFiles.get(pkg) ?? []) {
|
|
@@ -611,12 +724,19 @@ export class Resolver {
|
|
|
611
724
|
*/
|
|
612
725
|
resolve(ref) {
|
|
613
726
|
this.argc = ref.argc ?? null;
|
|
727
|
+
try { this.argTypes = ref.arg_types ? JSON.parse(ref.arg_types) : (ref.argTypes ?? null); } catch { this.argTypes = null; }
|
|
614
728
|
const fileId = ref.file_id;
|
|
615
729
|
const srcId = ref.src_id ?? null;
|
|
616
730
|
const recv = ref.recv || '';
|
|
617
731
|
const kind = ref.kind;
|
|
618
732
|
const name = ref.name;
|
|
619
|
-
if (recv)
|
|
733
|
+
if (recv) {
|
|
734
|
+
const r = this.#resolveMember(name, kind, recv, ref.recv_type, fileId, srcId);
|
|
735
|
+
// `f32::to_bits(self)` inside the `to_bits` of `impl Float for f32` is the primitive's own
|
|
736
|
+
// method (inherent methods come first), never a call of itself
|
|
737
|
+
if (r?.id != null && r.id === srcId && /^[a-z]\w*$/.test(recv) && this.specs[this.t.file(fileId)?.lang]?.isPrimitiveType?.(recv)) return { id: null, conf: 0, ncand: 0, external: true };
|
|
738
|
+
return r;
|
|
739
|
+
}
|
|
620
740
|
return this.#resolveName(name, kind, fileId, srcId) ?? { id: null, conf: 0, ncand: 0 };
|
|
621
741
|
}
|
|
622
742
|
|
|
@@ -642,6 +762,32 @@ export class Resolver {
|
|
|
642
762
|
return fits({ n: Math.max(0, this.argc), open: this.argc < 0 }, sigArity(this.sigOf(s.id), s.name));
|
|
643
763
|
}
|
|
644
764
|
|
|
765
|
+
/**
|
|
766
|
+
* Overloads whose parameter types the call's argument types fit, best fits first kept: an
|
|
767
|
+
* exact type beats a widening (`int` → `long`), boxing or a type parameter; a primitive or
|
|
768
|
+
* String argument never fits a different primitive or String parameter (`'-'` is no String).
|
|
769
|
+
*/
|
|
770
|
+
#byArgTypes(cands) {
|
|
771
|
+
const scored = [];
|
|
772
|
+
for (const s of cands) {
|
|
773
|
+
const params = sigParamTypes(this.sigOf(s.id), s.name);
|
|
774
|
+
if (!params) return cands;
|
|
775
|
+
let score = 0, ok = true;
|
|
776
|
+
this.argTypes.forEach((a, i) => {
|
|
777
|
+
if (!ok || a === '?') return;
|
|
778
|
+
let p = params[Math.min(i, params.length - 1)];
|
|
779
|
+
if (p == null) { ok = false; return; }
|
|
780
|
+
if (/\.\.\.$/.test(p)) p = i >= params.length - 1 && !(this.argTypes.length === params.length && a.endsWith('[]')) ? p.slice(0, -3) : p.slice(0, -3) + '[]';
|
|
781
|
+
const fit = argFits(a, simpleParamType(p));
|
|
782
|
+
if (fit < 0) ok = false; else score += fit;
|
|
783
|
+
});
|
|
784
|
+
if (ok) scored.push({ s, score });
|
|
785
|
+
}
|
|
786
|
+
if (!scored.length) return cands;
|
|
787
|
+
const best = Math.max(...scored.map(x => x.score));
|
|
788
|
+
return scored.filter(x => x.score === best).map(x => x.s);
|
|
789
|
+
}
|
|
790
|
+
|
|
645
791
|
#compatible(s, kind) {
|
|
646
792
|
if (kind === 'value' && CALLABLE_KINDS.has(s.kind) && this.specs[this.t.file(s.fileId)?.lang]?.localValues) return false; // Java: methods are no values
|
|
647
793
|
if (kind === 'type' || kind === 'inherit') return TYPE_KINDS.has(s.kind);
|
|
@@ -653,8 +799,11 @@ export class Resolver {
|
|
|
653
799
|
}
|
|
654
800
|
|
|
655
801
|
#pick(ids, kind, conf, fileId) {
|
|
656
|
-
|
|
802
|
+
let cands = ids.map(id => this.t.sym(id)).filter(s => s && this.#compatible(s, kind));
|
|
657
803
|
if (!cands.length) return null;
|
|
804
|
+
// Rust: a method of a trait implemented for the type and for references to it (`impl
|
|
805
|
+
// IntoIterator for Map` and `for &'a Map`): a call on the value reaches the value's impl
|
|
806
|
+
if (cands.length > 1 && cands.some(s => s.byRef) && cands.some(s => !s.byRef) && cands.every(s => s.qname === cands[0].qname)) cands = cands.filter(s => !s.byRef);
|
|
658
807
|
if (cands.length === 1) return { id: cands[0].id, conf, ncand: 1 };
|
|
659
808
|
// overloads / redeclarations in one type or file: prefer the definition with a body (larger span)
|
|
660
809
|
const sameQ = cands.every(s => s.qname === cands[0].qname);
|
|
@@ -666,6 +815,12 @@ export class Resolver {
|
|
|
666
815
|
const fixed = fit.filter(s => sigArity(this.sigOf(s.id), s.name)?.max !== Infinity);
|
|
667
816
|
if (fixed.length && fixed.length < fit.length) fit = fixed;
|
|
668
817
|
if (fit.length === 1) return { id: fit[0].id, conf, ncand: 1 };
|
|
818
|
+
// several of that count: the one the arguments' types fit (`append('-')` is append(char))
|
|
819
|
+
if (fit.length > 1 && this.argTypes) {
|
|
820
|
+
const typed = this.#byArgTypes(fit);
|
|
821
|
+
if (typed.length === 1) return { id: typed[0].id, conf, ncand: 1 };
|
|
822
|
+
if (typed.length && typed.length < fit.length) fit = typed;
|
|
823
|
+
}
|
|
669
824
|
if (fit.length > 1 && fit.length < cands.length) return this.#pick(fit.map(s => s.id), kind, conf, fileId);
|
|
670
825
|
}
|
|
671
826
|
const scored = cands.map(s => ({ s, score: this.#proximity(s, fileId) + (s.endLine - s.startLine) / 1e4 }));
|
|
@@ -707,7 +862,8 @@ export class Resolver {
|
|
|
707
862
|
if (s.name === name && kind === 'call' && CALLABLE_KINDS.has(s.kind) && (s.kind !== 'method' || spec?.implicitThis) && this.#reaches(s)) return { id: s.id, conf: 0.9, ncand: 1 };
|
|
708
863
|
}
|
|
709
864
|
// 2. implicit this: members of the enclosing type (Java/Kotlin/C#/Scala/C++/Ruby)
|
|
710
|
-
|
|
865
|
+
// (`new AVLNode<>(…)` inside AVLNode names the class, not one of its constructors)
|
|
866
|
+
if (spec?.implicitThis && srcId != null && kind !== 'type' && kind !== 'inherit' && kind !== 'new') {
|
|
711
867
|
const type = this.enclosingType(srcId);
|
|
712
868
|
if (type) {
|
|
713
869
|
const r = this.#pick(this.#overloadsReached(type, this.membersOf(type, name), name), kind, 0.9, fileId);
|
|
@@ -715,7 +871,9 @@ export class Resolver {
|
|
|
715
871
|
}
|
|
716
872
|
}
|
|
717
873
|
// 3. same file, top-level
|
|
718
|
-
|
|
874
|
+
// a member declared outside its type's body (a Rust `impl` block, a Go receiver, a C++
|
|
875
|
+
// `T::m`) is reached through a receiver or `self`, never by its bare name
|
|
876
|
+
const local = (this.t.fileSyms.get(fileId) ?? []).filter(id => { const s = this.t.sym(id); return s.name === name && this.#fileLevel(s); });
|
|
719
877
|
if (local.length) {
|
|
720
878
|
const r = this.#pick(local, kind, 0.9, fileId);
|
|
721
879
|
if (r) return r;
|
|
@@ -726,8 +884,14 @@ export class Resolver {
|
|
|
726
884
|
const target = imp.imported && imp.imported !== '*' && imp.imported !== 'default' ? imp.imported : name;
|
|
727
885
|
let ids = [];
|
|
728
886
|
if (imp.targetFileId != null) {
|
|
729
|
-
|
|
730
|
-
|
|
887
|
+
if (imp.imported === 'default') {
|
|
888
|
+
// a default import names the module's default export, whatever the importer calls it:
|
|
889
|
+
// a definition of that name in the module itself, else its default export; only then
|
|
890
|
+
// what the module imports under that name (it may import another `name` of its own)
|
|
891
|
+
ids = (this.t.fileSyms.get(imp.targetFileId) ?? []).filter(id => { const s = this.t.sym(id); return s.name === name && s.parentId == null; });
|
|
892
|
+
if (!ids.length) ids = this.#defaultExport(imp.targetFileId, name);
|
|
893
|
+
if (!ids.length) ids = this.exportedFrom(imp.targetFileId, target);
|
|
894
|
+
} else ids = this.exportedFrom(imp.targetFileId, target);
|
|
731
895
|
}
|
|
732
896
|
if (!ids.length && imp.targetDir) ids = this.#symbolsInDir(imp.targetDir, target, fileId);
|
|
733
897
|
if (!ids.length && imp.source && PACKAGE_LANGS.has(file.lang)) ids = this.#symbolsInPackage(imp.source, target);
|
|
@@ -735,7 +899,10 @@ export class Resolver {
|
|
|
735
899
|
const r = this.#pick(ids, kind, 0.95, fileId);
|
|
736
900
|
if (r) return r;
|
|
737
901
|
}
|
|
738
|
-
if (imp.targetFileId == null && !imp.targetDir && !PACKAGE_LANGS.has(file.lang)) return { id: null, conf: 0, ncand: 0, external: true };
|
|
902
|
+
if (imp.targetFileId == null && !imp.targetDir && (!PACKAGE_LANGS.has(file.lang) || this.#importsLibraryType(file, imp))) return { id: null, conf: 0, ncand: 0, external: true };
|
|
903
|
+
// the module re-exports the name from a package outside the repository (`fastapi/__init__.py`:
|
|
904
|
+
// `from starlette.responses import Response`): not a same-name definition found globally
|
|
905
|
+
if (!ids.length && imp.targetFileId != null && this.#reexportsExternal(imp.targetFileId, target)) return { id: null, conf: 0, ncand: 0, external: true };
|
|
739
906
|
}
|
|
740
907
|
// 5. same package / directory
|
|
741
908
|
if (PACKAGE_LANGS.has(file.lang) && file.package) {
|
|
@@ -772,22 +939,51 @@ export class Resolver {
|
|
|
772
939
|
const r = ids.length ? this.#pick(ids, kind, 0.8, fileId) : null;
|
|
773
940
|
if (r) return r;
|
|
774
941
|
}
|
|
775
|
-
// 8. global by name
|
|
942
|
+
// 8. global by name — not for a name Python code evaluates (a call, a value): one its module
|
|
943
|
+
// neither defines nor imports is a NameError, unless a star import of a module outside the
|
|
944
|
+
// index brought it. Types are still looked up (an annotation's name may be imported only
|
|
945
|
+
// under TYPE_CHECKING, and a type recorded for a receiver has lost its module: `routing.APIRouter`)
|
|
946
|
+
if (file.lang === 'python' && kind !== 'type' && kind !== 'inherit' && !(this.t.imports.get(fileId) ?? []).some(w => w.wildcard && w.targetFileId == null)) return null;
|
|
776
947
|
return this.#global(name, kind, fileId);
|
|
777
948
|
}
|
|
778
949
|
|
|
950
|
+
/**
|
|
951
|
+
* A definition its file's other code names directly: a top-level one, or a type nested in a type
|
|
952
|
+
* (Java's inner classes). Not a member declared outside its type's body (a Rust `impl` block, a Go
|
|
953
|
+
* receiver, a C++ `T::m`: reached through a receiver or `self`), not a type local to a function
|
|
954
|
+
* (seen from inside it, step 1), and not an associated type (`impl Visitor for V { type Value = …; }`).
|
|
955
|
+
*/
|
|
956
|
+
#fileLevel(s) {
|
|
957
|
+
if (s.owner && (MEMBER_KINDS.has(s.kind) || (s.kind === 'type' && s.parentId == null))) return false;
|
|
958
|
+
if (s.parentId == null) return true;
|
|
959
|
+
if (!TYPE_KINDS.has(s.kind)) return false;
|
|
960
|
+
for (let p = this.t.sym(s.parentId), g = 0; p && g < 16; p = p.parentId != null ? this.t.sym(p.parentId) : null, g++) if (CALLABLE_KINDS.has(p.kind)) return false;
|
|
961
|
+
return true;
|
|
962
|
+
}
|
|
963
|
+
|
|
779
964
|
#defaultExport(targetFileId, localName) {
|
|
780
965
|
// `export default class Foo` / `module.exports = Foo` → the file's main exported symbol
|
|
781
966
|
const ids = (this.t.fileSyms.get(targetFileId) ?? []).filter(id => { const s = this.t.sym(id); return s.parentId == null && s.exported; });
|
|
782
967
|
const same = ids.filter(id => this.t.sym(id).name.toLowerCase() === localName.toLowerCase());
|
|
783
968
|
if (same.length) return same;
|
|
969
|
+
// a Vue or Svelte file's default export is the component, named after the file whatever the importer calls it
|
|
970
|
+
const file = this.t.file(targetFileId);
|
|
971
|
+
if (file && (file.lang === 'vue' || file.lang === 'svelte')) {
|
|
972
|
+
const base = file.path.split('/').pop().replace(/\.[^.]+$/, '');
|
|
973
|
+
const stem = (base === 'index' ? file.path.split('/').slice(-2, -1)[0] ?? base : base).replace(/(^|[-_.])(\w)/g, (_, __, c) => c.toUpperCase());
|
|
974
|
+
const comp = ids.filter(id => this.t.sym(id).name === stem || this.t.sym(id).name === `${stem}Component`);
|
|
975
|
+
if (comp.length) return comp;
|
|
976
|
+
}
|
|
784
977
|
return ids.length === 1 ? ids : [];
|
|
785
978
|
}
|
|
786
979
|
|
|
787
980
|
#global(name, kind, fileId) {
|
|
788
981
|
const all = (this.t.byName.get(name) ?? []).filter(id => {
|
|
789
982
|
const s = this.t.sym(id);
|
|
790
|
-
|
|
983
|
+
if (!s || !this.#compatible(s, kind) || !(this.#fileLevel(s) || kind === 'call')) return false;
|
|
984
|
+
// a function nested in a function is visible only inside it (found in step 1 there)
|
|
985
|
+
const parent = s.parentId != null ? this.t.sym(s.parentId) : null;
|
|
986
|
+
return !(parent && CALLABLE_KINDS.has(parent.kind) && CALLABLE_KINDS.has(s.kind));
|
|
791
987
|
});
|
|
792
988
|
const file = this.t.file(fileId);
|
|
793
989
|
// never bind across unrelated languages (a TS call never resolves to a Python def)
|
|
@@ -795,10 +991,15 @@ export class Resolver {
|
|
|
795
991
|
const f = this.t.file(this.t.sym(id).fileId);
|
|
796
992
|
return f && (f.lang === file.lang || familyOf(f.lang) === familyOf(file.lang));
|
|
797
993
|
});
|
|
994
|
+
// an ES module sees another module's top-level names only through an import (resolved
|
|
995
|
+
// above): a value read that reached here is a local the extractor did not scope (a lambda
|
|
996
|
+
// parameter, a block-scoped const), not that name. Calls are left alone (CommonJS patterns).
|
|
997
|
+
const isolated = (kind === 'value' || kind === 'read') && familyOf(file.lang) === 'ecma' && this.#isModule(fileId);
|
|
798
998
|
// unqualified calls cannot target methods of unrelated classes in most languages
|
|
799
999
|
const cands = sameFamily.filter(id => {
|
|
800
1000
|
const s = this.t.sym(id);
|
|
801
1001
|
if (s.kind === 'method' || s.kind === 'field' || s.kind === 'property') return false;
|
|
1002
|
+
if (isolated && s.fileId !== fileId && s.parentId == null && this.#isModule(s.fileId)) return false;
|
|
802
1003
|
return true;
|
|
803
1004
|
});
|
|
804
1005
|
if (!cands.length) return null;
|
|
@@ -807,6 +1008,12 @@ export class Resolver {
|
|
|
807
1008
|
return this.#pick(cands, kind, base, fileId);
|
|
808
1009
|
}
|
|
809
1010
|
|
|
1011
|
+
/** A JS/TS file that imports or exports something: its top-level names are its own. */
|
|
1012
|
+
#isModule(fileId) {
|
|
1013
|
+
if ((this.t.imports.get(fileId) ?? []).length) return true;
|
|
1014
|
+
return (this.t.fileSyms.get(fileId) ?? []).some(id => { const s = this.t.sym(id); return s?.parentId == null && s?.exported; });
|
|
1015
|
+
}
|
|
1016
|
+
|
|
810
1017
|
#resolveMember(name, kind, recv, recvType, fileId, srcId) {
|
|
811
1018
|
const file = this.t.file(fileId);
|
|
812
1019
|
const root = recv.split('.')[0];
|
|
@@ -820,13 +1027,23 @@ export class Resolver {
|
|
|
820
1027
|
const ts = type.id != null ? this.t.sym(type.id) : null;
|
|
821
1028
|
ids = [];
|
|
822
1029
|
for (const b of ts?.bases ?? []) { const bt = this.resolveTypeName(b, ts.fileId); if (bt) ids.push(...this.membersOf({ id: bt.id, name: bt.name, fileId: bt.fileId }, name)); }
|
|
823
|
-
} else ids = this.membersOf(type, name);
|
|
1030
|
+
} else ids = this.#sameImpl(this.membersOf(type, name), srcId, fileId);
|
|
824
1031
|
const r = this.#pick(this.#byStatic(ids, false), kind, 0.95, fileId);
|
|
825
1032
|
if (r) return r;
|
|
826
1033
|
return { id: null, conf: 0, ncand: 0 };
|
|
827
1034
|
}
|
|
828
1035
|
}
|
|
829
1036
|
}
|
|
1037
|
+
// a Rust trait-path call: `SerializeSeq::serialize_element(self, v)`, `Debug::fmt(n, f)`
|
|
1038
|
+
if (file.lang === 'rust' && kind === 'call' && this.argTypes?.length === 1) {
|
|
1039
|
+
const r = this.#traitPathCall(name, recv, fileId);
|
|
1040
|
+
if (r) return r;
|
|
1041
|
+
}
|
|
1042
|
+
// a Rust path: `crate::a::f`, `super::B::new`, `other_crate::m::T`, `alias::sub::f`
|
|
1043
|
+
if (file.lang === 'rust' && this.modules) {
|
|
1044
|
+
const r = this.#rustPath(name, kind, recv, fileId);
|
|
1045
|
+
if (r) return r;
|
|
1046
|
+
}
|
|
830
1047
|
// inferred receiver type (locals, params, fields, factories; chains across files)
|
|
831
1048
|
if (recvType) {
|
|
832
1049
|
const type = this.resolveRecvType(recvType, fileId, srcId);
|
|
@@ -838,6 +1055,13 @@ export class Resolver {
|
|
|
838
1055
|
// interface / abstract receiver: the member may be declared only on implementations
|
|
839
1056
|
return { id: null, conf: 0, ncand: 0 };
|
|
840
1057
|
}
|
|
1058
|
+
if (this.#unknownDeclaredType(recvType)) {
|
|
1059
|
+
// the receiver is declared as a type the index does not hold (a library type, or a
|
|
1060
|
+
// declaration the parser could not read): another type's member of that name is not
|
|
1061
|
+
// a guess, so the call stays unbound and is counted with the same-name candidates
|
|
1062
|
+
const guess = kind === 'read' ? null : this.#anyMember(name, kind, fileId);
|
|
1063
|
+
return { id: null, conf: 0, ncand: guess ? Math.max(1, guess.ncand) : 0 };
|
|
1064
|
+
}
|
|
841
1065
|
}
|
|
842
1066
|
// receiver is an imported module / namespace / package alias
|
|
843
1067
|
const imp = this.#importFor(fileId, root) ?? this.#importFor(fileId, recv);
|
|
@@ -851,8 +1075,11 @@ export class Resolver {
|
|
|
851
1075
|
if (!owners.length && PACKAGE_LANGS.has(file.lang)) owners = this.#symbolsInPackage(imp.source, importedName);
|
|
852
1076
|
for (const oid of owners) ids.push(...this.#membersVia(this.t.sym(oid), name));
|
|
853
1077
|
if (!ids.length && imp.targetFileId != null && owners.length === 0) {
|
|
854
|
-
// `from pkg import submodule` then submodule.fn()
|
|
855
|
-
|
|
1078
|
+
// `from pkg import submodule` then submodule.fn(): the submodule's own name first —
|
|
1079
|
+
// pkg/__init__.py may re-export something else under it (`from fastapi import
|
|
1080
|
+
// params; params.File` is the class in params.py, not the File function __init__ exports)
|
|
1081
|
+
const mod = file.lang === 'python' ? this.#moduleFileOf(fileId, imp.local) : null;
|
|
1082
|
+
ids = this.exportedFrom(mod ?? imp.targetFileId, name);
|
|
856
1083
|
}
|
|
857
1084
|
} else if (rest.length === 0) {
|
|
858
1085
|
if (imp.targetFileId != null) ids = this.exportedFrom(imp.targetFileId, name);
|
|
@@ -867,6 +1094,12 @@ export class Resolver {
|
|
|
867
1094
|
const mod = this.#moduleFileOf(fileId, imp.local);
|
|
868
1095
|
if (mod != null && rest.length === 0) ids = this.exportedFrom(mod, name);
|
|
869
1096
|
else if (mod != null && rest.length === 1) for (const oid of this.exportedFrom(mod, rest[0])) ids.push(...this.#membersVia(this.t.sym(oid), name));
|
|
1097
|
+
// `import flask; flask.json.dumps(…)`: the rest of the receiver names submodules
|
|
1098
|
+
if (!ids.length && mod != null && rest.length) {
|
|
1099
|
+
let cur = mod;
|
|
1100
|
+
for (const seg of rest) { cur = cur != null ? this.#submodule(cur, seg) : null; }
|
|
1101
|
+
if (cur != null) ids = this.exportedFrom(cur, name);
|
|
1102
|
+
}
|
|
870
1103
|
}
|
|
871
1104
|
if (ids.length) {
|
|
872
1105
|
const r = this.#pick(ids, kind, 0.95, fileId);
|
|
@@ -902,6 +1135,82 @@ export class Resolver {
|
|
|
902
1135
|
return this.#anyMember(name, kind, fileId) ?? { id: null, conf: 0, ncand: 0 };
|
|
903
1136
|
}
|
|
904
1137
|
|
|
1138
|
+
/**
|
|
1139
|
+
* `self.m()` in a Rust impl where several impls of the type declare `m` (tokio's `impl AsyncWrite`
|
|
1140
|
+
* for `Cursor<&mut [u8]>`, `Cursor<Vec<u8>>`…): the one in the caller's own impl block, the
|
|
1141
|
+
* nearest in the file among those implementing the caller's trait.
|
|
1142
|
+
*/
|
|
1143
|
+
#sameImpl(ids, srcId, fileId) {
|
|
1144
|
+
if (ids.length < 2 || srcId == null || this.t.file(fileId)?.lang !== 'rust') return ids;
|
|
1145
|
+
let src = this.t.sym(srcId);
|
|
1146
|
+
while (src && !CALLABLE_KINDS.has(src.kind) && src.parentId != null) src = this.t.sym(src.parentId);
|
|
1147
|
+
if (!src) return ids;
|
|
1148
|
+
const key = (s) => JSON.stringify(s.bases ?? []);
|
|
1149
|
+
const same = ids.map(id => this.t.sym(id)).filter(s => s && s.fileId === fileId && key(s) === key(src));
|
|
1150
|
+
if (same.length < 2 || same.length < ids.length) return ids;
|
|
1151
|
+
same.sort((a, b) => Math.abs(a.startLine - src.startLine) - Math.abs(b.startLine - src.startLine));
|
|
1152
|
+
return [same[0].id];
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/**
|
|
1156
|
+
* `Trait::m(x)` / `<T as Trait>::m(x)` (Rust): the method `m` of the impl of `Trait` for the
|
|
1157
|
+
* type of `x` (or `T`), so a type with several traits declaring `m` (`Debug::fmt` and
|
|
1158
|
+
* `Display::fmt` on one type) is told apart. Null when that type has no such impl in the
|
|
1159
|
+
* repository: the path names a type, or the trait's own declaration is what is called.
|
|
1160
|
+
*/
|
|
1161
|
+
#traitPathCall(name, recv, fileId) {
|
|
1162
|
+
const trait = recv.split('.').pop();
|
|
1163
|
+
if (!/^[A-Z]/.test(trait ?? '')) return null;
|
|
1164
|
+
const self = this.typeFromString(this.argTypes[0], fileId);
|
|
1165
|
+
if (!self || self === EXTERNAL || (self.id == null && !self.allOf)) return null;
|
|
1166
|
+
const ids = this.membersOf(self, name).filter(id => { const m = this.t.sym(id); return m && CALLABLE_KINDS.has(m.kind) && m.bases?.includes(trait); });
|
|
1167
|
+
return ids.length ? this.#pick(ids, 'call', 0.9, fileId) : null;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
/**
|
|
1171
|
+
* A Rust path receiver, walked module by module from where it starts — `crate`, `self`,
|
|
1172
|
+
* `super`, a crate of the repository, or an imported module — to the module (and at most one
|
|
1173
|
+
* type in it) that declares `name`. Null when the path does not start at a module the index
|
|
1174
|
+
* knows; then the other rules apply.
|
|
1175
|
+
*/
|
|
1176
|
+
#rustPath(name, kind, recv, fileId) {
|
|
1177
|
+
const segs = recv.split('.');
|
|
1178
|
+
const from = this.t.file(fileId)?.path;
|
|
1179
|
+
const isModule = (p, seg) => p && (seg === 'crate' || seg === 'self' || seg === 'super' || this.modules.rustCrates?.get(seg) === p || new RegExp(`(^|/)${seg}(\\.rs|/mod\\.rs)$`).test(p));
|
|
1180
|
+
let cur = null, rest = segs;
|
|
1181
|
+
if (segs[0] === 'crate' || segs[0] === 'self' || segs[0] === 'super' || this.modules.rustCrates?.has(segs[0])) {
|
|
1182
|
+
let k = 1;
|
|
1183
|
+
while (segs[k] === 'super') k++;
|
|
1184
|
+
const t = this.modules.resolve('rust', from, segs.slice(0, k).join('::'));
|
|
1185
|
+
if (!t?.file || !isModule(t.file, segs[k - 1])) return null;
|
|
1186
|
+
cur = t.file; rest = segs.slice(k);
|
|
1187
|
+
} else {
|
|
1188
|
+
const imp = this.#importFor(fileId, segs[0]);
|
|
1189
|
+
const mod = imp?.imported === 'self' ? imp.source?.split('::').pop() : imp?.imported;
|
|
1190
|
+
let target = imp && (imp.targetPath ?? this.t.file(imp.targetFileId)?.path);
|
|
1191
|
+
if (!imp) {
|
|
1192
|
+
// a child module of this one (`mod chan;` then `chan::channel()`)
|
|
1193
|
+
target = this.modules.resolve('rust', from, `self::${segs[0]}`)?.file;
|
|
1194
|
+
if (target === from) target = null;
|
|
1195
|
+
}
|
|
1196
|
+
else if (!isModule(target, mod)) return null;
|
|
1197
|
+
if (!target || !isModule(target, imp ? mod : segs[0])) return null;
|
|
1198
|
+
cur = target; rest = segs.slice(1);
|
|
1199
|
+
}
|
|
1200
|
+
// submodules, while a segment names one
|
|
1201
|
+
while (rest.length) {
|
|
1202
|
+
const t = this.modules.resolve('rust', cur, `self::${rest[0]}`);
|
|
1203
|
+
if (!t?.file || t.file === cur || !isModule(t.file, rest[0])) break;
|
|
1204
|
+
cur = t.file; rest = rest.slice(1);
|
|
1205
|
+
}
|
|
1206
|
+
const fid = this.t.fileByPath.get(cur)?.id;
|
|
1207
|
+
if (fid == null || rest.length > 1) return null;
|
|
1208
|
+
let ids = [];
|
|
1209
|
+
if (!rest.length) ids = this.exportedFrom(fid, name);
|
|
1210
|
+
else for (const oid of this.exportedFrom(fid, rest[0])) ids.push(...this.#membersVia(this.t.sym(oid), name));
|
|
1211
|
+
return this.#pick(ids, kind, 0.95, fileId);
|
|
1212
|
+
}
|
|
1213
|
+
|
|
905
1214
|
/**
|
|
906
1215
|
* Members named `name` reached through an imported/package symbol: a type's static members, a
|
|
907
1216
|
* typed value's instance members (`export const api = new Api()`), or members declared on the
|
|
@@ -911,7 +1220,7 @@ export class Resolver {
|
|
|
911
1220
|
if (!o) return [];
|
|
912
1221
|
if (!TYPE_KINDS.has(o.kind) && o.type) {
|
|
913
1222
|
const t = this.typeFromString(o.type, o.fileId);
|
|
914
|
-
if (t && t !== EXTERNAL && t.arrayOf == null && t.mapOf == null) return this.#byStatic(this.membersOf(t, name), false);
|
|
1223
|
+
if (t && t !== EXTERNAL && t.arrayOf == null && t.mapOf == null && t.tupleOf == null) return this.#byStatic(this.membersOf(t, name), false);
|
|
915
1224
|
if (t) return [];
|
|
916
1225
|
}
|
|
917
1226
|
const ids = this.membersOf({ id: o.id, name: o.name, fileId: o.fileId }, name);
|
|
@@ -925,6 +1234,17 @@ export class Resolver {
|
|
|
925
1234
|
return same.length ? same : ids;
|
|
926
1235
|
}
|
|
927
1236
|
|
|
1237
|
+
/**
|
|
1238
|
+
* A receiver type written as a plain type name that no type in the index has. Short names and
|
|
1239
|
+
* `TName` are left out: they are usually type parameters (`T`, `K`, `TItem`), whose calls reach
|
|
1240
|
+
* the members of their bound.
|
|
1241
|
+
*/
|
|
1242
|
+
#unknownDeclaredType(desc) {
|
|
1243
|
+
if (!/^[A-Z][\w$]*$/.test(desc) || /^[A-Z][A-Z0-9]?$|^T[A-Z]\w*$/.test(desc) || TYPE_WRAPPERS.has(desc)) return false;
|
|
1244
|
+
// a descriptor may name a value rather than a type (Go `s := ByCid`, a package-level constant)
|
|
1245
|
+
return !(this.t.byName.get(desc) ?? []).some(id => { const k = this.t.sym(id)?.kind; return TYPE_KINDS.has(k) || VALUE_TYPED_KINDS.has(k); });
|
|
1246
|
+
}
|
|
1247
|
+
|
|
928
1248
|
/**
|
|
929
1249
|
* Unknown receiver: bind by name only when the repository has exactly one member with that name
|
|
930
1250
|
* and the name is not also a standard-library method of the language (`get`, `set`, `apply`…).
|
|
@@ -946,8 +1266,27 @@ export class Resolver {
|
|
|
946
1266
|
}
|
|
947
1267
|
}
|
|
948
1268
|
|
|
1269
|
+
const WIDENING = { byte: ['short', 'int', 'long', 'float', 'double'], short: ['int', 'long', 'float', 'double'], char: ['int', 'long', 'float', 'double'],
|
|
1270
|
+
int: ['long', 'float', 'double'], long: ['float', 'double'], float: ['double'] };
|
|
1271
|
+
const BOXED = { int: 'Integer', char: 'Character', long: 'Long', double: 'Double', float: 'Float', boolean: 'Boolean', byte: 'Byte', short: 'Short' };
|
|
1272
|
+
const CLOSED = new Set([...Object.keys(BOXED), 'String']);
|
|
1273
|
+
|
|
1274
|
+
/** How well an argument type fits a parameter type: 3 exact, 2 widening/boxing, 1 maybe (a class, a type parameter), -1 never. */
|
|
1275
|
+
function argFits(a, p) {
|
|
1276
|
+
if (!p) return 1;
|
|
1277
|
+
if (a === p) return 3;
|
|
1278
|
+
if (a === '*') return CLOSED.has(p) && p !== 'String' ? -1 : 1; // null: any reference type
|
|
1279
|
+
if (WIDENING[a]?.includes(p) || BOXED[a] === p || BOXED[p] === a) return 2;
|
|
1280
|
+
if (p === 'Object' || /^[A-Z]\w?$/.test(p)) return 1; // anything, or a type parameter
|
|
1281
|
+
if (CLOSED.has(a) && CLOSED.has(p)) return -1;
|
|
1282
|
+
if (BOXED[a]) return ['Number', 'Comparable', 'Serializable'].includes(p) ? 1 : -1; // a primitive
|
|
1283
|
+
if (BOXED[p]) return -1; // a primitive parameter, an object argument
|
|
1284
|
+
if (a.endsWith('[]') !== p.endsWith('[]')) return -1;
|
|
1285
|
+
return 1;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
949
1288
|
export function familyOf(lang) {
|
|
950
|
-
if (lang === 'javascript' || lang === 'typescript' || lang === 'tsx') return 'ecma';
|
|
1289
|
+
if (lang === 'javascript' || lang === 'typescript' || lang === 'tsx' || lang === 'vue' || lang === 'svelte') return 'ecma';
|
|
951
1290
|
if (lang === 'c' || lang === 'cpp') return 'c';
|
|
952
1291
|
if (lang === 'java' || lang === 'kotlin' || lang === 'scala') return 'jvm';
|
|
953
1292
|
return lang;
|