graph-indexer 3.0.1 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +456 -0
  2. package/README.md +61 -8
  3. package/SECURITY.md +35 -20
  4. package/bin/graph-indexer.mjs +46 -5
  5. package/integrations/claude-code/.claude-plugin/plugin.json +1 -1
  6. package/integrations/claude-code/agents/code-structure.md +0 -3
  7. package/integrations/claude-code/skills/setup/SKILL.md +26 -0
  8. package/integrations/claude-code-local/.claude-plugin/plugin.json +9 -0
  9. package/integrations/{claude-code → claude-code-local}/.mcp.json +1 -1
  10. package/integrations/claude-code-local/README.md +10 -0
  11. package/integrations/claude-code-local/agents/code-structure.md +22 -0
  12. package/integrations/{claude-code → claude-code-local}/hooks/gi-hook.mjs +15 -4
  13. package/integrations/opencode/graph-indexer.js +10 -3
  14. package/package.json +1 -1
  15. package/src/cli/helper.mjs +15 -1
  16. package/src/cli/hook.mjs +124 -40
  17. package/src/cli/init.mjs +32 -5
  18. package/src/cli/resident.mjs +19 -4
  19. package/src/index/discover.mjs +52 -30
  20. package/src/index/indexer.mjs +78 -42
  21. package/src/index/modules.mjs +75 -2
  22. package/src/index/parse-worker.mjs +12 -0
  23. package/src/index/parsepool.mjs +73 -0
  24. package/src/index/resolver.mjs +362 -23
  25. package/src/mcp/server.mjs +43 -13
  26. package/src/mcp/tools.mjs +418 -26
  27. package/src/parse/arity.mjs +39 -2
  28. package/src/parse/extract.mjs +269 -45
  29. package/src/parse/lang/ecma.mjs +2 -0
  30. package/src/parse/lang/go.mjs +2 -0
  31. package/src/parse/lang/jvm.mjs +33 -0
  32. package/src/parse/lang/markdown.mjs +92 -0
  33. package/src/parse/lang/python.mjs +43 -0
  34. package/src/parse/lang/rust.mjs +215 -15
  35. package/src/parse/lang/sfc.mjs +126 -0
  36. package/src/parse/languages.mjs +9 -2
  37. package/src/query/check.mjs +49 -13
  38. package/src/query/facts.mjs +3 -2
  39. package/src/query/injection-spring.mjs +406 -0
  40. package/src/query/injection.mjs +174 -0
  41. package/src/query/intel.mjs +85 -15
  42. package/src/query/issue.mjs +156 -0
  43. package/src/query/orm.mjs +288 -0
  44. package/src/query/read.mjs +6 -1
  45. package/src/query/report.mjs +53 -0
  46. package/src/query/routes.mjs +142 -0
  47. package/src/query/showcase.mjs +59 -0
  48. package/src/query/textsearch.mjs +30 -9
  49. package/src/search/search.mjs +85 -17
  50. package/src/search/semantic.mjs +201 -0
  51. package/src/search/vectors.mjs +122 -0
  52. package/src/store/db.mjs +33 -4
  53. package/src/util/git.mjs +60 -11
  54. package/src/util/paths.mjs +10 -3
  55. package/vendor/grammars/README.md +47 -0
  56. package/vendor/grammars/kotlin.wasm.gz +0 -0
  57. package/vendor/grammars/manifest.json +4 -4
  58. /package/integrations/{claude-code → claude-code-local}/hooks/hooks.json +0 -0
@@ -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 === b.lang && (a.dir === b.dir || a.package === b.package || a.lang === 'cpp' || a.lang === 'c' || a.lang === 'rust' || a.lang === 'javascript' || a.lang === 'typescript')) out.push(id);
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({ id: cur.id ?? null, name: cur.name, fileId: cur.fileId }, name);
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 ? EXTERNAL : cur ? { id: cur.id ?? null, name: cur.name, fileId: cur.fileId ?? null } : 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
- const r = this.exportedFrom(imp.targetFileId, inner, depth + 1, seen);
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) return this.#resolveMember(name, kind, recv, ref.recv_type, fileId, srcId);
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
- const cands = ids.map(id => this.t.sym(id)).filter(s => s && this.#compatible(s, kind));
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
- if (spec?.implicitThis && srcId != null && kind !== 'type' && kind !== 'inherit') {
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
- const local = (this.t.fileSyms.get(fileId) ?? []).filter(id => { const s = this.t.sym(id); return s.name === name && (s.parentId == null || TYPE_KINDS.has(s.kind)); });
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
- ids = this.exportedFrom(imp.targetFileId, target);
730
- if (!ids.length && imp.imported === 'default') ids = this.#defaultExport(imp.targetFileId, name);
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
- return s && this.#compatible(s, kind) && (s.parentId == null || TYPE_KINDS.has(s.kind) || kind === 'call');
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
- ids = this.exportedFrom(imp.targetFileId, name);
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;