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.
Files changed (52) hide show
  1. package/CHANGELOG.md +404 -0
  2. package/README.md +61 -8
  3. package/bin/graph-indexer.mjs +46 -5
  4. package/integrations/claude-code/.claude-plugin/plugin.json +1 -1
  5. package/integrations/claude-code/agents/code-structure.md +0 -3
  6. package/integrations/claude-code/skills/setup/SKILL.md +26 -0
  7. package/integrations/claude-code-local/.claude-plugin/plugin.json +9 -0
  8. package/integrations/{claude-code → claude-code-local}/.mcp.json +1 -1
  9. package/integrations/claude-code-local/README.md +10 -0
  10. package/integrations/claude-code-local/agents/code-structure.md +22 -0
  11. package/integrations/{claude-code → claude-code-local}/hooks/gi-hook.mjs +3 -1
  12. package/package.json +1 -1
  13. package/src/cli/helper.mjs +15 -1
  14. package/src/cli/hook.mjs +123 -41
  15. package/src/cli/init.mjs +16 -1
  16. package/src/cli/resident.mjs +1 -1
  17. package/src/index/indexer.mjs +77 -41
  18. package/src/index/modules.mjs +76 -2
  19. package/src/index/parse-worker.mjs +12 -0
  20. package/src/index/parsepool.mjs +73 -0
  21. package/src/index/resolver.mjs +362 -23
  22. package/src/mcp/server.mjs +16 -12
  23. package/src/mcp/tools.mjs +417 -25
  24. package/src/parse/arity.mjs +39 -2
  25. package/src/parse/extract.mjs +269 -45
  26. package/src/parse/lang/ecma.mjs +2 -0
  27. package/src/parse/lang/go.mjs +2 -0
  28. package/src/parse/lang/jvm.mjs +33 -0
  29. package/src/parse/lang/markdown.mjs +92 -0
  30. package/src/parse/lang/python.mjs +43 -0
  31. package/src/parse/lang/rust.mjs +215 -15
  32. package/src/parse/lang/sfc.mjs +125 -0
  33. package/src/parse/languages.mjs +9 -2
  34. package/src/query/check.mjs +49 -13
  35. package/src/query/facts.mjs +3 -2
  36. package/src/query/injection-spring.mjs +405 -0
  37. package/src/query/injection.mjs +173 -0
  38. package/src/query/intel.mjs +76 -14
  39. package/src/query/issue.mjs +156 -0
  40. package/src/query/orm.mjs +286 -0
  41. package/src/query/read.mjs +6 -1
  42. package/src/query/report.mjs +53 -0
  43. package/src/query/routes.mjs +142 -0
  44. package/src/query/showcase.mjs +59 -0
  45. package/src/search/search.mjs +85 -17
  46. package/src/search/semantic.mjs +201 -0
  47. package/src/search/vectors.mjs +122 -0
  48. package/src/store/db.mjs +22 -4
  49. package/vendor/grammars/README.md +47 -0
  50. package/vendor/grammars/kotlin.wasm.gz +0 -0
  51. package/vendor/grammars/manifest.json +4 -4
  52. /package/integrations/{claude-code → claude-code-local}/hooks/hooks.json +0 -0
@@ -59,8 +59,8 @@ export function paramArity(def, { method = false, lang = '', isStatic = false, d
59
59
  if (t === 'keyword_separator') { keywordOnly = true; continue; }
60
60
  if (SKIP_PARAM.test(t)) continue;
61
61
  const text = p.text;
62
- // the receiver: Python self/cls (not for staticmethods), TS `this: T`
63
- if (first && py && method && !isStatic && !decorators.some(d => /staticmethod/.test(d)) && /^(self|cls)\b/.test(text)) { first = false; continue; }
62
+ // the receiver: Python self/cls (a classmethod's cls too, though it counts as static), TS `this: T`
63
+ if (first && py && method && !decorators.some(d => /staticmethod/.test(d)) && (!isStatic || /^cls\b/.test(text)) && /^(self|cls)\b/.test(text)) { first = false; continue; }
64
64
  first = false;
65
65
  if (/^this\s*[:?]/.test(text)) continue;
66
66
  if (VARIADIC.test(t) || /^(\.\.\.|\*\*?)/.test(text) || /^params\s/.test(text) || /\bvararg\b/.test(text) || /\.\.\.\s*\w*$/.test(text) && lang === 'go') { max = Infinity; continue; }
@@ -138,6 +138,43 @@ export function sigArity(sig, name) {
138
138
  return done ? { min, max } : null;
139
139
  }
140
140
 
141
+ /**
142
+ * Parameter types read from a stored Java-like signature (`final void append(char c, int[] xs)`):
143
+ * the text before each parameter's name, `...` kept as a trailing `...`. Null when unreadable.
144
+ */
145
+ export function sigParamTypes(sig, name) {
146
+ if (!sig) return null;
147
+ const m = new RegExp(`(?:^|[^\\w$])${escapeRegExp(name)}\\s*(?:<[^()]*>)?\\s*\\(`).exec(sig);
148
+ if (!m) return null;
149
+ const out = [];
150
+ let depth = 0, cur = '';
151
+ const param = (p) => {
152
+ p = p.replace(/@\w+(\([^)]*\))?\s*/g, '').replace(/\bfinal\s+/g, '').trim();
153
+ if (!p) return;
154
+ const k = p.search(/[\w$]+\s*$/);
155
+ out.push(k > 0 ? p.slice(0, k).trim() : p);
156
+ };
157
+ for (let i = m.index + m[0].length; i < sig.length; i++) {
158
+ const ch = sig[i];
159
+ if (ch === ')' && depth === 0) { param(cur); return out; }
160
+ if ('([{<'.includes(ch)) depth++;
161
+ else if (')]}>'.includes(ch)) depth--;
162
+ if (ch === ',' && depth === 0) { param(cur); cur = ''; } else cur += ch;
163
+ }
164
+ return null;
165
+ }
166
+
167
+ /** A Java type as overloads compare it: `final List<String>` → `List`, `java.net.URL` → `URL`, arrays kept (`int[]`). */
168
+ export function simpleParamType(t) {
169
+ if (!t) return null;
170
+ let s = String(t).replace(/@\w+(\([^)]*\))?\s*/g, '').replace(/\bfinal\s+/g, '').trim();
171
+ let dims = '';
172
+ s = s.replace(/(\s*\[\s*\])+$/, (m) => { dims = '[]'.repeat((m.match(/\[/g) ?? []).length); return ''; });
173
+ if (/\.\.\.$/.test(s)) { s = s.slice(0, -3); dims += '[]'; }
174
+ s = s.replace(/<.*>$/s, '').trim().split('.').pop();
175
+ return /^[A-Za-z_$][\w$]*$/.test(s) ? s + dims : null;
176
+ }
177
+
141
178
  /** Does a call passing `a` arguments fit a definition accepting `r`? */
142
179
  export function fits(a, r) {
143
180
  if (!a || !r) return true;
@@ -108,7 +108,7 @@ function inTypeContext(node) {
108
108
  }
109
109
  return false;
110
110
  }
111
- const CHAIN_RE = /^[A-Za-z_$@][\w$]*(?:\(\)|\[\])*(?:\.[A-Za-z_$][\w$]*(?:\(\)|\[\])*){0,7}$/;
111
+ const CHAIN_RE = /^[A-Za-z_$@][\w$]*(?:\(\)|\[\])*(?:\.(?:[A-Za-z_$][\w$]*|\d+)(?:\(\)|\[\])*){0,7}$/;
112
112
 
113
113
  /**
114
114
  * `a.b(x).c[i]` → `a.b().c[]`: argument lists and subscripts collapsed, operators normalised
@@ -165,7 +165,8 @@ function castHead(node) {
165
165
  function receiverDescriptor(node, spec) {
166
166
  if (!node) return '';
167
167
  for (let i = 0; i < 6 && node.namedChildCount >= 1; i++) {
168
- if (PASS_THROUGH.has(node.type)) node = node.namedChildren[0];
168
+ // `&mut x` is x (its first named child is the `mut`)
169
+ if (PASS_THROUGH.has(node.type)) node = node.childForFieldName('value') ?? node.namedChildren.find(c => c.type !== 'mutable_specifier') ?? node.namedChildren[0];
169
170
  else if (node.type === 'unary_expression' && node.childForFieldName('operator')?.text === '&') node = node.childForFieldName('operand') ?? node.namedChildren[0]; // Go &T{}
170
171
  else break;
171
172
  }
@@ -188,7 +189,7 @@ function receiverDescriptor(node, spec) {
188
189
  return tt ? 'as ' + tt : '?';
189
190
  }
190
191
  if (spec.receiverDescriptor) {
191
- const d = spec.receiverDescriptor(node);
192
+ const d = spec.receiverDescriptor(node, (n) => receiverDescriptor(n, spec));
192
193
  if (d !== undefined) return d;
193
194
  }
194
195
  // a chain that starts with a cast: `val.(Module).CaddyModule().ID`, `(x as Foo).bar()` → `(T).a().b`
@@ -324,8 +325,25 @@ function typeNarrowings(root, source) {
324
325
  * Extract symbols/refs/imports for one file.
325
326
  * @returns {{ symbols: object[], refs: object[], imports: object[], errors: number }}
326
327
  */
328
+ /**
329
+ * The text a language's grammar parses. A language may rewrite its source first into an equivalent
330
+ * of the same length (`spec.rewriteSource`: Rust macro arguments, syntax a grammar does not know
331
+ * yet), so every position still points at the file.
332
+ */
333
+ export function rewrittenSource(spec, parser, source) {
334
+ return spec.rewriteSource ? spec.rewriteSource(source, (s) => parser.parse(s)) : source;
335
+ }
336
+
337
+ /** Parse a file's text as extraction does (`rewrittenSource`). */
338
+ export function parseSource(spec, parser, source) {
339
+ return parser.parse(rewrittenSource(spec, parser, source));
340
+ }
341
+
327
342
  export async function extractFile(spec, source, relPath) {
343
+ if (spec.extract) return spec.extract(source, relPath);
328
344
  const { query, parser } = await prepareLanguage(spec);
345
+ // a language may rewrite its source first, keeping every position (Rust macro arguments)
346
+ source = rewrittenSource(spec, parser, source);
329
347
  const tree = parser.parse(source);
330
348
  try {
331
349
  return extractFromTree(spec, query, tree, source, relPath);
@@ -339,6 +357,8 @@ export async function extractFile(spec, source, relPath) {
339
357
  const QUERY_BUDGET_MS = 15_000;
340
358
 
341
359
  function extractFromTree(spec, query, tree, source, relPath) {
360
+ // a language may adjust its spec to the file (a Rust file whose own crate defines `Mutex`)
361
+ if (spec.specForSource) spec = spec.specForSource(source) ?? spec;
342
362
  const root = tree.rootNode;
343
363
  const deadline = Date.now() + QUERY_BUDGET_MS;
344
364
  let gaveUp = false;
@@ -357,12 +377,13 @@ function extractFromTree(spec, query, tree, source, relPath) {
357
377
  const scopeNodes = []; // anonymous functions / lambdas / comprehensions: lexical scopes
358
378
  const retNodes = []; // returned expressions (return-type inference)
359
379
  const cbSites = []; // functions passed as call arguments (callback parameter types)
380
+ const tupleFields = []; // { owner: struct node, list: its positional fields } (Rust `struct S(A, B)`)
360
381
 
361
382
  for (const m of matches) {
362
383
  let role = null, kind = null, main = null, nameNode = null, recvNode = null, ownerNode = null, typeNode = null;
363
384
  let bindName = null, bindType = null, bindNew = null, bindCall = null, bindVar = null, bindExpr = null, bindElem = null, bindEach = null, srcNode = null;
364
- let bindKey = null, bindOf = null;
365
- let cbFn = null, cbArg = null;
385
+ let bindKey = null, bindOf = null, bindLocal = false;
386
+ let cbFn = null, cbArg = null, bindTuple = null, tupleOwner = null, tupleVariant = null, bindVariant = null;
366
387
  for (const c of m.captures) {
367
388
  const n = c.name;
368
389
  if (n === 'name') nameNode = c.node;
@@ -385,15 +406,52 @@ function extractFromTree(spec, query, tree, source, relPath) {
385
406
  else if (n === 'bind.elem' || n === 'field.elem') bindElem = c.node;
386
407
  else if (n === 'bind.each') bindEach = c.node;
387
408
  else if (n === 'bind.key') bindKey = c.node.text;
409
+ else if (n === 'bind.local') bindLocal = true;
388
410
  else if (n === 'bind.of') bindOf = c.node;
389
411
  else if (n === 'scope') { role = 'scope'; main = c.node; }
412
+ else if (n === 'scope.block') { role = 'scope'; main = c.node; main.__block = true; }
390
413
  else if (n === 'ret') { role = 'ret'; main = c.node; }
391
414
  else if (n === 'cb') { role = 'cb'; main = c.node; }
392
415
  else if (n === 'cb.fn') cbFn = c.node;
393
416
  else if (n === 'cb.arg') cbArg = c.node;
417
+ else if (n === 'bind.tuple') bindTuple = c.node;
418
+ else if (n === 'tuple.owner') tupleOwner = c.node;
419
+ else if (n === 'tuple.variant') tupleVariant = c.node;
420
+ else if (n === 'bind.variant') bindVariant = c.node;
421
+ else if (n === 'tuple.fields') { role = 'tuple'; main = c.node; }
422
+ }
423
+ if (role === 'tuple') {
424
+ // a tuple struct's fields are `0`, `1`…; an enum variant's are `Variant.0`… of the enum
425
+ if (tupleOwner) tupleFields.push({ owner: tupleOwner.parent, list: main, prefix: '' });
426
+ else if (tupleVariant) tupleFields.push({ owner: tupleVariant.parent?.parent?.parent, list: main, prefix: tupleVariant.text + '.' });
427
+ continue;
428
+ }
429
+ // `Value::Object(map)` / `Self::Number(n)` in a match arm or `if let`: each name is that
430
+ // variant's field at its position
431
+ if (role === 'bind' && bindVariant) {
432
+ const path = bindVariant.childForFieldName('type');
433
+ const owner = path?.type === 'scoped_identifier' ? path.childForFieldName('path')?.text.split('::').pop() : null;
434
+ const variant = path?.childForFieldName('name')?.text;
435
+ if (owner && variant && /^[A-Z]/.test(owner)) {
436
+ bindVariant.namedChildren.filter(c => c.id !== path.id).forEach((el, i) => {
437
+ const id = el.type === 'identifier' ? el : (el.type === 'mut_pattern' || el.type === 'ref_pattern') ? el.namedChildren.find(x => x.type === 'identifier') : null;
438
+ if (id) binds.push({ node: main, name: id.text, variant: { owner, field: `${variant}.${i}` } });
439
+ });
440
+ }
441
+ continue;
442
+ }
443
+ // `let (a, mut b) = e`: each name is the element of e at its position (`e.0`, `e.1`)
444
+ if (role === 'bind' && bindTuple && bindExpr) {
445
+ const expr = receiverDescriptor(bindExpr, spec);
446
+ bindTuple.namedChildren.forEach((el, i) => {
447
+ const id = el.type === 'identifier' ? el : (el.type === 'mut_pattern' || el.type === 'ref_pattern') ? el.namedChildren.find(x => x.type === 'identifier') : null;
448
+ const d = id && memberOfDescriptor(expr, String(i));
449
+ if (d) binds.push({ node: main, name: id.text, expr: d });
450
+ });
451
+ continue;
394
452
  }
395
453
  if (role === 'cb') { if (cbFn && cbArg) cbSites.push({ call: main, fn: cbFn, arg: cbArg }); continue; }
396
- if (role === 'scope') { scopeNodes.push(main); continue; }
454
+ if (role === 'scope') { scopeNodes.push({ node: main, block: !!main.__block }); continue; }
397
455
  if (role === 'ret') { retNodes.push(main); continue; }
398
456
  if (role === 'def' && nameNode) {
399
457
  // several patterns may match one definition (with/without return type, owner); merge them
@@ -412,6 +470,8 @@ function extractFromTree(spec, query, tree, source, relPath) {
412
470
  const prev = seenRef.get(nameNode.id);
413
471
  if (prev !== undefined) {
414
472
  if ((REF_PRIORITY[kind] ?? 0) > (REF_PRIORITY[rawRefs[prev].kind] ?? 0)) rawRefs[prev] = { node: main, nameNode, kind, recvNode: recvNode ?? rawRefs[prev].recvNode };
473
+ // the same reference seen with its qualifier (`de::Deserializer` after `Deserializer`)
474
+ else if (kind === rawRefs[prev].kind && recvNode && !rawRefs[prev].recvNode) rawRefs[prev] = { ...rawRefs[prev], node: main, recvNode };
415
475
  continue;
416
476
  }
417
477
  seenRef.set(nameNode.id, rawRefs.length);
@@ -431,12 +491,12 @@ function extractFromTree(spec, query, tree, source, relPath) {
431
491
  else if (bindOf) { const t = normalizeType(bindOf.text, spec); expr = t ? `(${t}).${bindKey}` : null; }
432
492
  else expr = memberOfDescriptor(expr, bindKey);
433
493
  }
434
- const rec = { node: main, name: bindName, type: bindType, new: bindNew, call: bindCall, var: bindVar,
494
+ const rec = { node: main, name: bindName, type: bindType, new: bindNew, call: bindCall, var: bindVar, local: bindLocal,
435
495
  expr, elem: bindElem ? elementsDescriptor(bindElem, spec) : null,
436
496
  each: bindEach ? elementsDescriptor(bindEach, spec) : null };
437
497
  const prev = bindSites.get(key);
438
498
  if (prev === undefined) { bindSites.set(key, list.length); list.push(rec); }
439
- else { const p = list[prev]; for (const k of ['type', 'new', 'call', 'var', 'expr', 'elem', 'each']) p[k] ??= rec[k]; }
499
+ else { const p = list[prev]; for (const k of ['type', 'new', 'call', 'var', 'expr', 'elem', 'each']) p[k] ??= rec[k]; p.local ||= rec.local; }
440
500
  }
441
501
  }
442
502
 
@@ -549,7 +609,10 @@ function extractFromTree(spec, query, tree, source, relPath) {
549
609
  // ── lexical scopes: callables and types, plus anonymous functions (closures, lambdas) ──
550
610
  const scopes = []; // { start, end, sym (owning symbol or -1), parent }
551
611
  for (let i = 0; i < symbols.length; i++) if (!VALUE_KINDS.has(symbols[i].kind)) scopes.push({ start: symbols[i].startIndex, end: symbols[i].endIndex, sym: i });
552
- for (const n of scopeNodes) {
612
+ for (const { node: n, block } of scopeNodes) {
613
+ // a block of a language with block scoping (Rust: `{ … }`, a match arm, an `if let`) holds its
614
+ // own locals but returns for the function around it
615
+ if (block) { scopes.push({ start: n.startIndex, end: n.endIndex, sym: -1, block: true }); continue; }
553
616
  // `const f = () => …` / `m: function () {}`: the closure is the body of that symbol
554
617
  const own = n.parent ? byNodeId.get(n.parent.id) : undefined;
555
618
  scopes.push({ start: n.startIndex, end: n.endIndex, sym: own ?? -1 });
@@ -621,8 +684,31 @@ function extractFromTree(spec, query, tree, source, relPath) {
621
684
  if (pat?.type === 'identifier') binds.push({ node: arg, name: pat.text, cb: { recv, method, i, j } });
622
685
  });
623
686
  }
624
- const setLocal = (scope, name, val) => {
687
+ // the type `this`/`self` denotes inside a symbol: its class, or the receiver type of an
688
+ // out-of-body method (Rust impl blocks, Go receivers) — { idx: class symbol or -1, name }
689
+ const classByName = new Map();
690
+ const selfType = (encl) => {
691
+ const cls = classOf(encl, symbols);
692
+ if (cls >= 0) return { idx: cls, name: symbols[cls].qname };
693
+ for (let i = encl; i >= 0; i = symbols[i].parentIdx) {
694
+ const o = symbols[i].owner;
695
+ if (!o) continue;
696
+ if (!classByName.has(o)) classByName.set(o, symbols.findIndex(x => x.name === o && CLASS_KINDS.has(x.kind)));
697
+ const j = classByName.get(o);
698
+ return { idx: j, name: j >= 0 ? symbols[j].qname : o };
699
+ }
700
+ return null;
701
+ };
702
+ // where a language rebinds a name in sequence (Rust `let x = …; let x = x.y();`), every binding
703
+ // of a scope is kept with where it starts to hold: a `let` after its statement, a pattern of a
704
+ // match arm, `if let` or `for` over that construct's own scope
705
+ const sequential = new Map(); // `${scope}:${name}` -> binding records by position
706
+ const setLocalAt = (scope, name, val) => {
625
707
  const k = scope + ':' + name;
708
+ if (spec.shadowing) {
709
+ const list = sequential.get(k) ?? sequential.set(k, []).get(k);
710
+ list.push(val);
711
+ }
626
712
  const prev = localTypes.get(k);
627
713
  if (!prev) localTypes.set(k, val);
628
714
  else if (!prev.conflict && !sameBinding(prev, val)) localTypes.set(k, { conflict: true, end: Math.min(prev.end, val.end) });
@@ -631,19 +717,36 @@ function extractFromTree(spec, query, tree, source, relPath) {
631
717
  for (const b of binds) {
632
718
  const ctx = { ...ctxAt(b.node.startIndex), pos: b.node.startIndex };
633
719
  const end = b.node.endIndex; // the local is in effect from here on
720
+ const from = spec.shadowing && b.node.type !== 'let_declaration' && b.node.type !== 'parameter' ? b.node.startIndex : end;
721
+ const setLocal = (scope, name, val) => setLocalAt(scope, name, { ...val, from });
722
+ if (b.variant) {
723
+ const owner = b.variant.owner === 'Self' ? selfType(ctx.sym)?.name : b.variant.owner;
724
+ if (owner) setLocal(ctx.scope, b.name, { type: owner + '#' + b.variant.field, ctx, end });
725
+ continue;
726
+ }
634
727
  const typed = b.expr && /^(new|as) /.test(b.expr) ? b.expr.slice(3).trim() : null; // `new X(…)` / `… as X`
635
- const type = b.type ? typeText(b.type) : (b.new ? typeText(b.new) : typed ? typeText(typed) : b.expr === '!' ? '!' : null);
636
- if (type) setLocal(ctx.scope, b.name, { type, ctx, end });
728
+ let type = b.type ? typeText(b.type) : (b.new ? typeText(b.new) : typed ? typeText(typed) : b.expr === '!' ? '!' : null);
729
+ // a type parameter stands for its bound (`r: &mut R` with `R: Read`)
730
+ if (type && spec.typeParamBound && /^[A-Z][\w$]*$/.test(type)) { const bound = spec.typeParamBound(b.node, type); if (bound) type = typeText(bound) ?? type; }
731
+ if (type) setLocal(ctx.scope, b.name, { type, ctx, end, raw: b.type ?? b.new ?? typed });
637
732
  else if (b.type && /^\{[^]*\}$/.test(b.type.trim())) setLocal(ctx.scope, b.name, { inline: b.type.trim(), ctx, end }); // `x: { a: A; … }`
638
733
  else if (b.call) setLocal(ctx.scope, b.name, { call: b.call, ctx, end });
639
734
  else if (b.cb) setLocal(ctx.scope, b.name, { cb: b.cb, ctx, end });
640
735
  else if (b.expr && b.expr !== '?' && b.expr !== 'null' && b.expr !== b.name) setLocal(ctx.scope, b.name, { expr: b.expr, ctx, end });
641
736
  else if (b.elem && b.elem !== '?') setLocal(ctx.scope, b.name, { elem: b.elem, ctx, end });
642
737
  else if (b.each && b.each !== '?') setLocal(ctx.scope, b.name, { each: b.each, ctx, end });
643
- else if (b.type || (b.expr && b.expr !== 'null')) setLocal(ctx.scope, b.name, { opaque: true, ctx, end }); // shadows outer bindings
738
+ else if (b.type || (b.expr && b.expr !== 'null') || b.local) setLocal(ctx.scope, b.name, { opaque: true, ctx, end }); // shadows outer bindings
644
739
  }
645
740
  const lookupLocal = (ctx, name) => {
646
741
  for (let k = ctx.scope; ; k = scopes[k].parent) {
742
+ const seq = spec.shadowing && ctx.pos != null ? sequential.get(k + ':' + name) : null;
743
+ if (seq) {
744
+ let best = null;
745
+ for (const s of seq) if (s.from <= ctx.pos && (!best || s.from >= best.from)) best = s;
746
+ if (best) return best.opaque ? null : best;
747
+ if (k < 0) break;
748
+ continue; // bound later in this scope: an outer binding holds here
749
+ }
647
750
  const v = localTypes.get(k + ':' + name);
648
751
  if (v) return v.conflict || v.opaque ? null : v;
649
752
  if (k < 0) break;
@@ -707,23 +810,14 @@ function extractFromTree(spec, query, tree, source, relPath) {
707
810
  if (v.each) return eachOf(inferType(v.each, v.ctx, depth + 1));
708
811
  return null;
709
812
  };
710
- const SEG_RE = /^([A-Za-z_$@][\w$]*)((?:\(\)|\[\])*)$/;
711
- // the type `this`/`self` denotes inside a symbol: its class, or the receiver type of an
712
- // out-of-body method (Rust impl blocks, Go receivers) — { idx: class symbol or -1, name }
713
- const classByName = new Map();
714
- const selfType = (encl) => {
715
- const cls = classOf(encl, symbols);
716
- if (cls >= 0) return { idx: cls, name: symbols[cls].qname };
717
- for (let i = encl; i >= 0; i = symbols[i].parentIdx) {
718
- const o = symbols[i].owner;
719
- if (!o) continue;
720
- if (!classByName.has(o)) classByName.set(o, symbols.findIndex(x => x.name === o && CLASS_KINDS.has(x.kind)));
721
- const j = classByName.get(o);
722
- return { idx: j, name: j >= 0 ? symbols[j].qname : o };
723
- }
724
- return null;
725
- };
813
+ const SEG_RE = /^([A-Za-z_$@][\w$]*|\d+)((?:\(\)|\[\])*)$/;
726
814
 
815
+ /** The bound a type parameter `name` has where `pos` is (see spec.typeParamBound), as a type string. */
816
+ const boundAt = (pos, name) => {
817
+ if (!spec.typeParamBound || pos == null || !/^[A-Z][\w$]*$/.test(name)) return null;
818
+ const b = spec.typeParamBound(root.descendantForIndex(pos), name);
819
+ return b ? typeText(b) : null;
820
+ };
727
821
  /** Static type of an expression descriptor evaluated in `ctx`, as far as this file tells. */
728
822
  const inferType = (desc, ctx, depth = 0) => {
729
823
  if (!desc || depth > 5 || desc === '?' || desc === 'super' || desc === 'null') return null;
@@ -751,13 +845,25 @@ function extractFromTree(spec, query, tree, source, relPath) {
751
845
  let t = null, i = 1;
752
846
  const first = segs[0];
753
847
  if (cast) { t = cast; i = 0; }
848
+ else if (first.name === 'Self' && spec.shadowing && !first.ops.length) {
849
+ // `Self::new()` (Rust): the enclosing type
850
+ const st = selfType(ctx.sym);
851
+ if (!st) return null;
852
+ t = st.name;
853
+ }
754
854
  else if (first.name === 'this') {
755
855
  const st = selfType(ctx.sym);
756
856
  if (!st) return null;
757
857
  if (segs.length === 1) return first.ops.length ? null : st.name;
758
- const nt = ctx.pos != null && narrowed.length ? narrowedAt(ctx.pos, 'this.' + segs[1].name) : null;
759
- const ft = nt ?? (st.idx >= 0 ? fieldTypes.get(st.idx + ':' + segs[1].name) : null);
760
- t = applyOps(ft ?? st.name + '#' + segs[1].name, segs[1].ops);
858
+ if (segs[1].ops[0] === '()' && spec.identityMethods?.has(segs[1].name)) {
859
+ // `self.project()`, `self.clone()`: the same value, unless the type defines the method
860
+ t = applyOps(st.name + '#~' + segs[1].name, segs[1].ops.slice(1));
861
+ } else {
862
+ const nt = ctx.pos != null && narrowed.length ? narrowedAt(ctx.pos, 'this.' + segs[1].name) : null;
863
+ let ft = nt ?? (st.idx >= 0 ? fieldTypes.get(st.idx + ':' + segs[1].name) : null);
864
+ if (ft) ft = boundAt(ctx.pos, ft) ?? ft; // `formatter: F` in `impl<W, F: Formatter>`
865
+ t = applyOps(ft ?? st.name + '#' + segs[1].name, segs[1].ops);
866
+ }
761
867
  i = 2;
762
868
  } else {
763
869
  const isCall = first.ops[0] === '()';
@@ -772,22 +878,41 @@ function extractFromTree(spec, query, tree, source, relPath) {
772
878
  // a field the class inherits (declared in a base, maybe in another file): the resolver's to find
773
879
  if (!t && /^[a-z_]/.test(first.name) && !isDeclared(ctx, first.name)) t = applyOps(symbols[classOf(ctx.sym, symbols)].qname + '#' + first.name, first.ops);
774
880
  }
881
+ // a type parameter, in a path (`R::read(x)`) or as a value's type: its bound
882
+ if (!t && !v && !first.ops.length && /^[A-Z]/.test(first.name)) t = boundAt(ctx.pos, first.name);
883
+ // a unit struct used as a value (`Arc::new(Sender)`) is of its own type (Rust)
884
+ // (as a receiver, `Limb::as_cast(t)`, the name is a path the resolver walks)
885
+ if (!t && !v && spec.shadowing && !ctx.receiver && segs.length === 1 && !first.ops.length && /^[A-Z][a-z]/.test(first.name)) t = first.name;
775
886
  // `Type.staticField…`: a capitalised root that is not a local names a type
776
887
  if (!t && !v && segs.length > 1 && !first.ops.length && /^[A-Z]/.test(first.name)) t = first.name;
777
888
  // `b := Form`: a package-level variable, maybe of another file of the package; its type is
778
889
  // the resolver's to find (a Go name is never both a type and a value)
779
890
  else if (!t && !v && segs.length === 1 && !first.ops.length && spec.packageValues && !isDeclared(ctx, first.name)) t = first.name;
780
- // `pkg.New()` / `utils.makeFoo()`: a call through a module or import alias returns the callee's type
781
- else if (!t && !v && segs.length > 1 && !first.ops.length && segs[1].ops[0] === '()') {
782
- t = applyOps('call:' + first.name + '.' + segs[1].name, segs[1].ops.slice(1));
783
- i = 2;
891
+ // `pkg.New()` / `utils.makeFoo()`: a call through a module or import alias returns the callee's type;
892
+ // so does one through a longer path (`grep::regex::RegexMatcherBuilder::new()`, `pkg.sub.make()`)
893
+ else if (!t && !v && segs.length > 1 && !first.ops.length) {
894
+ let k = 1;
895
+ while (k < segs.length - 1 && !segs[k].ops.length) k++;
896
+ if (segs[k].ops[0] === '()' && (k === 1 || spec.pathCalls)) {
897
+ t = applyOps('call:' + segs.slice(0, k + 1).map(x => x.name).join('.'), segs[k].ops.slice(1));
898
+ i = k + 1;
899
+ }
784
900
  }
785
901
  }
786
902
  for (; i < segs.length && t; i++) {
787
903
  const seg = segs[i];
788
904
  const call = seg.ops[0] === '()';
789
- if (call && spec.identityMethods?.has(seg.name)) { t = applyOps(t, seg.ops.slice(1)); continue; }
905
+ // `Outer.this.m()` (Java, Kotlin, C#… inner classes): the enclosing instance of Outer
906
+ if (i === 1 && seg.name === 'this' && !seg.ops.length && !deferred(t)) continue;
907
+ if (call && spec.identityMethods?.has(seg.name)) {
908
+ // `x.clone()`, `opt.unwrap()`: the same value — unless x's own type defines that method
909
+ // (a repository type's `upgrade`), which only the resolver can tell: `T#~upgrade`
910
+ t = applyOps(t === '!' || /(\[\]|\{\})$/.test(t) ? t : t + '#~' + seg.name, seg.ops.slice(1));
911
+ continue;
912
+ }
790
913
  if (t === '!') return '!';
914
+ // `pair.0` of a tuple type `(A,B)` (see normalizeType)
915
+ if (t.startsWith('(') && /^\d+$/.test(seg.name)) { t = applyOps(tupleElement(t, Number(seg.name)), seg.ops); continue; }
791
916
  if (!deferred(t) && t.endsWith('[]')) {
792
917
  // `list.get(0)`, `xs.first()`: collection accessors hand back an element
793
918
  if (call && spec.elementMethods?.has(seg.name)) { t = applyOps(t.slice(0, -2), seg.ops.slice(1)); continue; }
@@ -799,6 +924,17 @@ function extractFromTree(spec, query, tree, source, relPath) {
799
924
  if (call && spec.mapValueMethods?.has(seg.name)) { t = applyOps(t.slice(0, -2) + '[]', seg.ops.slice(1)); continue; }
800
925
  return '!';
801
926
  }
927
+ // a field or a method's result typed by a type parameter of its type (`me.inner` with
928
+ // `inner: W`, `self.get_pin_mut()` returning `Pin<&mut R>`, where `W: AsyncSeek`): the bound in scope here
929
+ if (spec.typeParamBound && (!seg.ops.length || (call && seg.ops.length === 1))) {
930
+ const base = t.replace(/(#~[\w$]+)+$/, '');
931
+ const simple = /^[A-Za-z_$][\w$]*$/.test(base);
932
+ const ci = simple ? symbols.findIndex(x => x.qname === base && CLASS_KINDS.has(x.kind)) : -1;
933
+ const ft = !call ? (ci >= 0 ? fieldTypes.get(ci + ':' + seg.name) : null)
934
+ : simple ? symbols.find(x => x.qname === base + '.' + seg.name && CALLABLE_KINDS.has(x.kind))?.type ?? null : null;
935
+ const b = ft ? boundAt(ctx.pos, ft) : null;
936
+ if (b) { t = b; continue; }
937
+ }
802
938
  t = applyOps(t + '#' + seg.name, seg.ops);
803
939
  }
804
940
  return t;
@@ -831,6 +967,18 @@ function extractFromTree(spec, query, tree, source, relPath) {
831
967
  const [clsIdx, fname] = [Number(k.slice(0, k.indexOf(':'))), k.slice(k.indexOf(':') + 1)];
832
968
  inferredFields.push({ owner: symbols[clsIdx].qname, name: fname, type });
833
969
  }
970
+ // positional fields of a tuple struct (`struct Override(Gitignore)`): `self.0` is a Gitignore
971
+ for (const { owner, list, prefix } of tupleFields) {
972
+ const idx = byNodeId.get(owner?.id);
973
+ if (idx === undefined) continue;
974
+ let k = 0;
975
+ for (let i = 0; i < list.childCount; i++) {
976
+ if (list.fieldNameForChild(i) !== 'type') continue;
977
+ const type = typeText(list.child(i).text);
978
+ if (type && type !== '!') inferredFields.push({ owner: symbols[idx].qname, name: prefix + k, type });
979
+ k++;
980
+ }
981
+ }
834
982
  // module-level values typed by their initialiser (`export const api = new ApiClient()`), so
835
983
  // importers can reach the members of such singletons
836
984
  for (let i = 0; i < symbols.length; i++) {
@@ -842,7 +990,8 @@ function extractFromTree(spec, query, tree, source, relPath) {
842
990
  // undeclared return types: all `return` expressions of a callable agree on one type
843
991
  const returns = new Map(); // symIdx -> [types]
844
992
  for (const n of retNodes) {
845
- const k = scopeAt(n.startIndex);
993
+ let k = scopeAt(n.startIndex);
994
+ while (k >= 0 && scopes[k].block) k = scopes[k].parent;
846
995
  const owner = k >= 0 ? scopes[k].sym : -1;
847
996
  if (owner < 0 || symbols[owner].type || !CALLABLE_KINDS.has(symbols[owner].kind)) continue;
848
997
  const desc = receiverDescriptor(n, spec);
@@ -855,9 +1004,24 @@ function extractFromTree(spec, query, tree, source, relPath) {
855
1004
  if (uniq.size === 1 && !uniq.has(null)) symbols[owner].type = types[0];
856
1005
  }
857
1006
 
1007
+ /** Static types of a call's arguments, as far as literals, typed locals, casts and constructions tell (spec.argType). */
1008
+ const argTypesOf = (call, ctx) => {
1009
+ const list = call.childForFieldName('arguments') ?? call.namedChildren.find(c => c.type === 'argument_list');
1010
+ const args = list?.namedChildren.filter(c => !/comment/.test(c.type)) ?? [];
1011
+ // a local's declared type, else a field's of an enclosing class in this file (`replacementChar`)
1012
+ const fieldType = (name) => {
1013
+ for (let c = classOf(ctx.sym, symbols), g = 0; c >= 0 && g < 8; c = symbols[c].parentIdx >= 0 ? classOf(symbols[c].parentIdx, symbols) : -1, g++) {
1014
+ const f = symbols.find(s => s.parentIdx === c && s.name === name && VALUE_KINDS.has(s.kind));
1015
+ if (f) return f.type ?? null;
1016
+ }
1017
+ return null;
1018
+ };
1019
+ const out = args.map(n => spec.argType(n, (name) => { const v = lookupLocal(ctx, name); return v ? v.raw ?? null : isDeclared(ctx, name) ? null : fieldType(name); }) ?? '?');
1020
+ return out.some(t => t !== '?') ? out : null;
1021
+ };
858
1022
  const inferReceiverType = (recv, ctx) => {
859
1023
  if (!recv || recv === 'this') return null;
860
- const t = inferType(recv, ctx);
1024
+ const t = inferType(recv, { ...ctx, receiver: true });
861
1025
  return t && !deferred(t) && /(\[\]|\{\})$/.test(t) ? '!' : t; // a collection's own methods are the language's
862
1026
  };
863
1027
 
@@ -869,23 +1033,43 @@ function extractFromTree(spec, query, tree, source, relPath) {
869
1033
  if (spec.normalizeRefName) name = spec.normalizeRefName(name, r);
870
1034
  if (!name || name.length > 128) continue;
871
1035
  const kind = r.kind;
872
- const recv = r.recvNode ? receiverDescriptor(r.recvNode, spec) : '';
1036
+ let recv = r.recvNode ? receiverDescriptor(r.recvNode, spec) : '';
873
1037
  const ctx = ctxAt(r.nameNode.startIndex);
1038
+ // a trait-path call (Rust `Trait::m(x)`, `<T as Trait>::m(x)`): the type whose impl of the
1039
+ // trait runs is the first argument's, or the one named before `as`
1040
+ let selfArg = null;
1041
+ if (spec.traitPathCalls && kind === 'call' && r.recvNode) {
1042
+ const q = r.recvNode.type === 'bracketed_type' ? r.recvNode.namedChildren[0] : null;
1043
+ if (q?.type === 'qualified_type') {
1044
+ const alias = q.childForFieldName('alias');
1045
+ if (alias) recv = receiverDescriptor(alias, spec);
1046
+ selfArg = typeText(q.childForFieldName('type')?.text ?? '');
1047
+ } else if (/^[A-Z]/.test(recv.split('.').pop() ?? '') && !recv.endsWith('Self')) {
1048
+ const arg0 = r.node.childForFieldName('arguments')?.namedChildren.find(c => !/comment/.test(c.type));
1049
+ const d = arg0 ? receiverDescriptor(arg0, spec) : null;
1050
+ selfArg = d === 'this' ? selfType(ctx.sym)?.name ?? null : d ? inferType(d, { ...ctx, pos: r.nameNode.startIndex, receiver: true }) : null;
1051
+ }
1052
+ if (selfArg === '!') selfArg = null;
1053
+ }
874
1054
  if (kind === 'call' && !recv && spec.builtinCalls?.has(name) && !isDeclared(ctx, name)) continue;
875
1055
  const root = recv.split('.')[0];
876
1056
  // `module.x()` is the CommonJS global only when no local `module` is in scope
877
- if (recv && spec.externalReceivers?.has(root) && !isDeclared(ctx, root)) continue;
1057
+ if (recv && !selfArg && spec.externalReceivers?.has(root) && !isDeclared(ctx, root)) continue;
878
1058
  if (kind === 'value' && spec.isNoiseValue?.(name)) continue;
879
1059
  // a value or a call by the name of a local (`stack := stack(3); log(stack)`, `check := func…;
880
1060
  // check()`) is the local, in languages where no local stands for repository code (Go: no
881
1061
  // imports bind locals)
882
- if (!recv && (kind === 'value' ? spec.packageValues || spec.localValues : kind === 'call' && spec.packageValues) && isDeclaredLocally(ctx, name, r.nameNode.startIndex)) continue;
1062
+ // `localNames`: the same for any local, untyped parameters included (Python: `def send(self,
1063
+ // request): … request` is the parameter, never a function named `request` elsewhere)
1064
+ if (!recv && (kind === 'value' ? spec.packageValues || spec.localValues || spec.localNames : kind === 'call' && (spec.packageValues || spec.localNames)) && isDeclaredLocally(ctx, name, r.nameNode.startIndex)) continue;
883
1065
  const pos = r.nameNode.startPosition;
884
1066
  const encl = ctx.sym;
885
1067
  const recvType = inferReceiverType(recv, { ...ctx, pos: r.nameNode.startIndex });
886
1068
  // arguments passed: tells overloads apart where each one is a method of its own
887
1069
  const a = spec.distinctOverloads && (kind === 'call' || kind === 'new') ? callArgc(r.node) : null;
888
- refs.push({ name, kind, line: pos.row + 1, col: pos.column, recv, recvType, symIdx: encl, argc: a ? (a.open ? -1 : a.n) : null });
1070
+ // and, where overloads differ by type (`append(char)` / `append(String)`), what each argument is
1071
+ const argTypes = selfArg ? [selfArg] : a && !a.open && a.n > 0 && spec.argType ? argTypesOf(r.node, { ...ctx, pos: r.nameNode.startIndex }) : null;
1072
+ refs.push({ name, kind, line: pos.row + 1, col: pos.column, recv, recvType, symIdx: encl, argc: a ? (a.open ? -1 : a.n) : null, argTypes });
889
1073
  }
890
1074
 
891
1075
  // inheritance: attach base names to the enclosing class symbol
@@ -906,7 +1090,27 @@ function extractFromTree(spec, query, tree, source, relPath) {
906
1090
  const extra = spec.fileInfo ? spec.fileInfo(root, source) : null;
907
1091
 
908
1092
  for (const s of symbols) { delete s.startIndex; delete s.endIndex; delete s.lexParent; }
909
- return { symbols, refs, imports, fields: inferredFields, fileInfo: extra, errors: root.hasError ? 1 : 0 };
1093
+ return { symbols, refs, imports, fields: inferredFields, fileInfo: extra, ...errorRegions(root) };
1094
+ }
1095
+
1096
+ /**
1097
+ * Regions the parser could not read (ERROR nodes), and their lines (the first 100). A MISSING node is not counted: the parser
1098
+ * inserted a token and kept the structure (tsx `styled.div<T>\`…\`` gets a MISSING `!` and loses
1099
+ * nothing), while an ERROR node is source left out of the tree, where declarations can be lost.
1100
+ */
1101
+ function errorRegions(root) {
1102
+ const errorLines = [];
1103
+ let errors = 0;
1104
+ const visit = (node) => {
1105
+ if (node.type === 'ERROR') {
1106
+ errors++;
1107
+ if (errorLines.length < 100) errorLines.push([node.startPosition.row + 1, node.endPosition.row + 1]);
1108
+ return;
1109
+ }
1110
+ for (let i = 0; i < node.childCount; i++) { const c = node.child(i); if (c.hasError) visit(c); }
1111
+ };
1112
+ if (root.hasError) visit(root);
1113
+ return { errors, errorLines };
910
1114
  }
911
1115
 
912
1116
  const NULLISH_TYPES = new Set(['null', 'undefined', 'None', 'void', 'nil', 'never', 'NoneType', 'Nothing', 'Unit']);
@@ -927,6 +1131,12 @@ function splitTop(s, sep) {
927
1131
  return out;
928
1132
  }
929
1133
 
1134
+ /** Element `i` of a tuple type string `(A,B)`, or null (`?`, out of range). */
1135
+ export function tupleElement(t, i) {
1136
+ const els = t.slice(1, -1).split(',');
1137
+ return els[i] && els[i] !== '?' ? els[i] : null;
1138
+ }
1139
+
930
1140
  const arrayOf = (e) => (e && !/(\[\]|\{\})$/.test(e) ? e + '[]' : null);
931
1141
  const mapOf = (v) => (v && !/(\[\]|\{\})$/.test(v) ? v + '{}' : null);
932
1142
 
@@ -950,9 +1160,23 @@ export function normalizeType(t, spec, depth = 0) {
950
1160
  const norm = [...new Set(alts.filter(a => !NULLISH_TYPES.has(a)).map(a => normalizeType(a, spec, depth + 1)))];
951
1161
  return norm.length === 1 ? norm[0] : null;
952
1162
  }
1163
+ // Rust bounds (`AsyncWrite + AsyncSeek + Send`, `dyn Error + 'static`): the traits that give the
1164
+ // value methods, `A+B` when several do (the resolver looks a member up in each)
1165
+ if (spec?.markerTraits) {
1166
+ const parts = splitTop(s, '+').map(x => x.trim()).filter(Boolean);
1167
+ if (parts.length > 1) {
1168
+ const ts = [...new Set(parts.filter(x => !/^['?]/.test(x)).map(x => normalizeType(x, spec, depth + 1)))].filter(x => x && !x.includes('+') && !spec.markerTraits.has(x));
1169
+ return ts.length ? ts.join('+') : null;
1170
+ }
1171
+ }
953
1172
  if (splitTop(s, '&').length > 1 || splitTop(s, ',').length > 1) return null; // intersections / tuples
954
1173
  let m;
955
- if ((m = /^\((.*)\)$/s.exec(s))) return normalizeType(m[1], spec, depth + 1);
1174
+ if ((m = /^\((.*)\)$/s.exec(s))) {
1175
+ // a tuple, where the language has them (Rust `(Sender<T>, Receiver<T>)`): `(A,B)`, `?` for an element it cannot type
1176
+ const els = splitTop(m[1], ',').map(x => x.trim()).filter(Boolean);
1177
+ if (spec?.tupleTypes && els.length > 1) return '(' + els.map(x => { const n = normalizeType(x, spec, depth + 1); return n && !/[(),]/.test(n) ? n : '?'; }).join(',') + ')';
1178
+ return normalizeType(m[1], spec, depth + 1);
1179
+ }
956
1180
  if ((m = /^(.+?)\s*\[\s*\]$/s.exec(s))) return arrayOf(normalizeType(m[1], spec, depth + 1)); // T[]
957
1181
  if ((m = /^([A-Za-z_$][\w$.]*)\{\}$/.exec(s))) return mapOf(normalizeType(m[1], spec, depth + 1)); // a map of T, as normalised
958
1182
  if ((m = /^\[\s*\]\s*(.+)$/s.exec(s))) return arrayOf(normalizeType(m[1], spec, depth + 1)); // Go []T
@@ -363,6 +363,8 @@ function isModuleLevel(node) {
363
363
 
364
364
  function isExported(defNode) {
365
365
  let n = defNode;
366
+ // `export default function () {…}` / `export default {…}`: the statement is the definition
367
+ if (n.type === 'export_statement') return true;
366
368
  if (n.type === 'variable_declarator') n = n.parent;
367
369
  const p = n.parent;
368
370
  if (p?.type === 'export_statement') return true;
@@ -22,7 +22,9 @@ const QUERY = `
22
22
  (method_elem name: (field_identifier) @name) @def.method
23
23
  (field_declaration name: (field_identifier) @name type: ${TYPE_HEAD}) @def.field
24
24
  (field_declaration name: (field_identifier) @name) @def.field
25
+ (const_spec name: (identifier) @name type: ${TYPE_HEAD}) @def.constant
25
26
  (const_spec name: (identifier) @name) @def.constant
27
+ (var_spec name: (identifier) @name type: ${TYPE_HEAD}) @def.variable
26
28
  (var_spec name: (identifier) @name) @def.variable
27
29
 
28
30
  (call_expression function: (identifier) @name) @ref.call