graph-indexer 3.0.0 → 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 (60) hide show
  1. package/CHANGELOG.md +456 -0
  2. package/README.md +72 -15
  3. package/SECURITY.md +21 -4
  4. package/bin/graph-indexer.mjs +46 -5
  5. package/integrations/claude-code/.claude-plugin/plugin.json +3 -2
  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 +12 -6
  13. package/integrations/opencode/graph-indexer.js +13 -8
  14. package/package.json +2 -2
  15. package/src/cli/helper.mjs +15 -1
  16. package/src/cli/hook.mjs +140 -48
  17. package/src/cli/init.mjs +49 -17
  18. package/src/cli/resident.mjs +15 -5
  19. package/src/index/discover.mjs +3 -5
  20. package/src/index/indexer.mjs +77 -41
  21. package/src/index/modules.mjs +82 -7
  22. package/src/index/parse-worker.mjs +12 -0
  23. package/src/index/parsepool.mjs +73 -0
  24. package/src/index/resolver.mjs +371 -25
  25. package/src/mcp/server.mjs +16 -12
  26. package/src/mcp/tools.mjs +419 -26
  27. package/src/parse/arity.mjs +41 -3
  28. package/src/parse/extract.mjs +277 -46
  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 +125 -0
  36. package/src/parse/languages.mjs +9 -2
  37. package/src/query/check.mjs +55 -17
  38. package/src/query/facts.mjs +3 -2
  39. package/src/query/injection-spring.mjs +405 -0
  40. package/src/query/injection.mjs +173 -0
  41. package/src/query/intel.mjs +87 -22
  42. package/src/query/issue.mjs +156 -0
  43. package/src/query/orm.mjs +286 -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/testcmd.mjs +25 -14
  49. package/src/query/textsearch.mjs +29 -11
  50. package/src/search/search.mjs +85 -17
  51. package/src/search/semantic.mjs +201 -0
  52. package/src/search/vectors.mjs +122 -0
  53. package/src/store/db.mjs +22 -4
  54. package/src/util/git.mjs +30 -0
  55. package/src/util/paths.mjs +40 -1
  56. package/src/util/regex.mjs +2 -0
  57. package/vendor/grammars/README.md +47 -0
  58. package/vendor/grammars/kotlin.wasm.gz +0 -0
  59. package/vendor/grammars/manifest.json +4 -4
  60. /package/integrations/{claude-code → claude-code-local}/hooks/hooks.json +0 -0
@@ -4,6 +4,7 @@
4
4
  * ({ n, open }, open = a spread/splat makes n a lower bound). Used by the edit checker to spot
5
5
  * call sites that no longer match a changed signature, without compiling anything.
6
6
  */
7
+ import { escapeRegExp } from '../util/regex.mjs';
7
8
 
8
9
  const PARAM_LIST_TYPES = new Set(['formal_parameters', 'parameters', 'parameter_list', 'function_value_parameters', 'method_parameters',
9
10
  'lambda_parameters', 'block_parameters', 'parameter_clause']);
@@ -58,8 +59,8 @@ export function paramArity(def, { method = false, lang = '', isStatic = false, d
58
59
  if (t === 'keyword_separator') { keywordOnly = true; continue; }
59
60
  if (SKIP_PARAM.test(t)) continue;
60
61
  const text = p.text;
61
- // the receiver: Python self/cls (not for staticmethods), TS `this: T`
62
- 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; }
63
64
  first = false;
64
65
  if (/^this\s*[:?]/.test(text)) continue;
65
66
  if (VARIADIC.test(t) || /^(\.\.\.|\*\*?)/.test(text) || /^params\s/.test(text) || /\bvararg\b/.test(text) || /\.\.\.\s*\w*$/.test(text) && lang === 'go') { max = Infinity; continue; }
@@ -117,7 +118,7 @@ export function callArgc(call) {
117
118
  */
118
119
  export function sigArity(sig, name) {
119
120
  if (!sig) return null;
120
- const m = new RegExp(`(?:^|[^\\w$])${name.replace(/[$]/g, '\\$')}\\s*(?:<[^()]*>)?\\s*\\(`).exec(sig);
121
+ const m = new RegExp(`(?:^|[^\\w$])${escapeRegExp(name)}\\s*(?:<[^()]*>)?\\s*\\(`).exec(sig);
121
122
  if (!m) return null;
122
123
  let depth = 0, cur = '', min = 0, max = 0, done = false;
123
124
  const param = (p) => {
@@ -137,6 +138,43 @@ export function sigArity(sig, name) {
137
138
  return done ? { min, max } : null;
138
139
  }
139
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
+
140
178
  /** Does a call passing `a` arguments fit a definition accepting `r`? */
141
179
  export function fits(a, r) {
142
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);
@@ -334,9 +352,18 @@ export async function extractFile(spec, source, relPath) {
334
352
  }
335
353
  }
336
354
 
355
+ // query time can grow super-linearly with the syntax (60,000 chained calls in a 360 KB file take
356
+ // minutes): such a file is given up after this long instead of stalling the whole index
357
+ const QUERY_BUDGET_MS = 15_000;
358
+
337
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;
338
362
  const root = tree.rootNode;
339
- const matches = query.matches(root);
363
+ const deadline = Date.now() + QUERY_BUDGET_MS;
364
+ let gaveUp = false;
365
+ const matches = query.matches(root, { progressCallback: () => (gaveUp = Date.now() > deadline) });
366
+ if (gaveUp) throw new Error(`gave up after ${QUERY_BUDGET_MS / 1000} s of queries (pathological syntax)`);
340
367
 
341
368
  const defs = []; // { node, nameNode, kind, owner }
342
369
  const rawRefs = []; // { node, nameNode, kind, recvNode }
@@ -350,12 +377,13 @@ function extractFromTree(spec, query, tree, source, relPath) {
350
377
  const scopeNodes = []; // anonymous functions / lambdas / comprehensions: lexical scopes
351
378
  const retNodes = []; // returned expressions (return-type inference)
352
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)`)
353
381
 
354
382
  for (const m of matches) {
355
383
  let role = null, kind = null, main = null, nameNode = null, recvNode = null, ownerNode = null, typeNode = null;
356
384
  let bindName = null, bindType = null, bindNew = null, bindCall = null, bindVar = null, bindExpr = null, bindElem = null, bindEach = null, srcNode = null;
357
- let bindKey = null, bindOf = null;
358
- 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;
359
387
  for (const c of m.captures) {
360
388
  const n = c.name;
361
389
  if (n === 'name') nameNode = c.node;
@@ -378,15 +406,52 @@ function extractFromTree(spec, query, tree, source, relPath) {
378
406
  else if (n === 'bind.elem' || n === 'field.elem') bindElem = c.node;
379
407
  else if (n === 'bind.each') bindEach = c.node;
380
408
  else if (n === 'bind.key') bindKey = c.node.text;
409
+ else if (n === 'bind.local') bindLocal = true;
381
410
  else if (n === 'bind.of') bindOf = c.node;
382
411
  else if (n === 'scope') { role = 'scope'; main = c.node; }
412
+ else if (n === 'scope.block') { role = 'scope'; main = c.node; main.__block = true; }
383
413
  else if (n === 'ret') { role = 'ret'; main = c.node; }
384
414
  else if (n === 'cb') { role = 'cb'; main = c.node; }
385
415
  else if (n === 'cb.fn') cbFn = c.node;
386
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;
387
452
  }
388
453
  if (role === 'cb') { if (cbFn && cbArg) cbSites.push({ call: main, fn: cbFn, arg: cbArg }); continue; }
389
- if (role === 'scope') { scopeNodes.push(main); continue; }
454
+ if (role === 'scope') { scopeNodes.push({ node: main, block: !!main.__block }); continue; }
390
455
  if (role === 'ret') { retNodes.push(main); continue; }
391
456
  if (role === 'def' && nameNode) {
392
457
  // several patterns may match one definition (with/without return type, owner); merge them
@@ -405,6 +470,8 @@ function extractFromTree(spec, query, tree, source, relPath) {
405
470
  const prev = seenRef.get(nameNode.id);
406
471
  if (prev !== undefined) {
407
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 };
408
475
  continue;
409
476
  }
410
477
  seenRef.set(nameNode.id, rawRefs.length);
@@ -424,12 +491,12 @@ function extractFromTree(spec, query, tree, source, relPath) {
424
491
  else if (bindOf) { const t = normalizeType(bindOf.text, spec); expr = t ? `(${t}).${bindKey}` : null; }
425
492
  else expr = memberOfDescriptor(expr, bindKey);
426
493
  }
427
- 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,
428
495
  expr, elem: bindElem ? elementsDescriptor(bindElem, spec) : null,
429
496
  each: bindEach ? elementsDescriptor(bindEach, spec) : null };
430
497
  const prev = bindSites.get(key);
431
498
  if (prev === undefined) { bindSites.set(key, list.length); list.push(rec); }
432
- 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; }
433
500
  }
434
501
  }
435
502
 
@@ -542,7 +609,10 @@ function extractFromTree(spec, query, tree, source, relPath) {
542
609
  // ── lexical scopes: callables and types, plus anonymous functions (closures, lambdas) ──
543
610
  const scopes = []; // { start, end, sym (owning symbol or -1), parent }
544
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 });
545
- 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; }
546
616
  // `const f = () => …` / `m: function () {}`: the closure is the body of that symbol
547
617
  const own = n.parent ? byNodeId.get(n.parent.id) : undefined;
548
618
  scopes.push({ start: n.startIndex, end: n.endIndex, sym: own ?? -1 });
@@ -614,8 +684,31 @@ function extractFromTree(spec, query, tree, source, relPath) {
614
684
  if (pat?.type === 'identifier') binds.push({ node: arg, name: pat.text, cb: { recv, method, i, j } });
615
685
  });
616
686
  }
617
- 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) => {
618
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
+ }
619
712
  const prev = localTypes.get(k);
620
713
  if (!prev) localTypes.set(k, val);
621
714
  else if (!prev.conflict && !sameBinding(prev, val)) localTypes.set(k, { conflict: true, end: Math.min(prev.end, val.end) });
@@ -624,19 +717,36 @@ function extractFromTree(spec, query, tree, source, relPath) {
624
717
  for (const b of binds) {
625
718
  const ctx = { ...ctxAt(b.node.startIndex), pos: b.node.startIndex };
626
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
+ }
627
727
  const typed = b.expr && /^(new|as) /.test(b.expr) ? b.expr.slice(3).trim() : null; // `new X(…)` / `… as X`
628
- const type = b.type ? typeText(b.type) : (b.new ? typeText(b.new) : typed ? typeText(typed) : b.expr === '!' ? '!' : null);
629
- 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 });
630
732
  else if (b.type && /^\{[^]*\}$/.test(b.type.trim())) setLocal(ctx.scope, b.name, { inline: b.type.trim(), ctx, end }); // `x: { a: A; … }`
631
733
  else if (b.call) setLocal(ctx.scope, b.name, { call: b.call, ctx, end });
632
734
  else if (b.cb) setLocal(ctx.scope, b.name, { cb: b.cb, ctx, end });
633
735
  else if (b.expr && b.expr !== '?' && b.expr !== 'null' && b.expr !== b.name) setLocal(ctx.scope, b.name, { expr: b.expr, ctx, end });
634
736
  else if (b.elem && b.elem !== '?') setLocal(ctx.scope, b.name, { elem: b.elem, ctx, end });
635
737
  else if (b.each && b.each !== '?') setLocal(ctx.scope, b.name, { each: b.each, ctx, end });
636
- 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
637
739
  }
638
740
  const lookupLocal = (ctx, name) => {
639
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
+ }
640
750
  const v = localTypes.get(k + ':' + name);
641
751
  if (v) return v.conflict || v.opaque ? null : v;
642
752
  if (k < 0) break;
@@ -700,23 +810,14 @@ function extractFromTree(spec, query, tree, source, relPath) {
700
810
  if (v.each) return eachOf(inferType(v.each, v.ctx, depth + 1));
701
811
  return null;
702
812
  };
703
- const SEG_RE = /^([A-Za-z_$@][\w$]*)((?:\(\)|\[\])*)$/;
704
- // the type `this`/`self` denotes inside a symbol: its class, or the receiver type of an
705
- // out-of-body method (Rust impl blocks, Go receivers) — { idx: class symbol or -1, name }
706
- const classByName = new Map();
707
- const selfType = (encl) => {
708
- const cls = classOf(encl, symbols);
709
- if (cls >= 0) return { idx: cls, name: symbols[cls].qname };
710
- for (let i = encl; i >= 0; i = symbols[i].parentIdx) {
711
- const o = symbols[i].owner;
712
- if (!o) continue;
713
- if (!classByName.has(o)) classByName.set(o, symbols.findIndex(x => x.name === o && CLASS_KINDS.has(x.kind)));
714
- const j = classByName.get(o);
715
- return { idx: j, name: j >= 0 ? symbols[j].qname : o };
716
- }
717
- return null;
718
- };
813
+ const SEG_RE = /^([A-Za-z_$@][\w$]*|\d+)((?:\(\)|\[\])*)$/;
719
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
+ };
720
821
  /** Static type of an expression descriptor evaluated in `ctx`, as far as this file tells. */
721
822
  const inferType = (desc, ctx, depth = 0) => {
722
823
  if (!desc || depth > 5 || desc === '?' || desc === 'super' || desc === 'null') return null;
@@ -744,13 +845,25 @@ function extractFromTree(spec, query, tree, source, relPath) {
744
845
  let t = null, i = 1;
745
846
  const first = segs[0];
746
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
+ }
747
854
  else if (first.name === 'this') {
748
855
  const st = selfType(ctx.sym);
749
856
  if (!st) return null;
750
857
  if (segs.length === 1) return first.ops.length ? null : st.name;
751
- const nt = ctx.pos != null && narrowed.length ? narrowedAt(ctx.pos, 'this.' + segs[1].name) : null;
752
- const ft = nt ?? (st.idx >= 0 ? fieldTypes.get(st.idx + ':' + segs[1].name) : null);
753
- 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
+ }
754
867
  i = 2;
755
868
  } else {
756
869
  const isCall = first.ops[0] === '()';
@@ -765,22 +878,41 @@ function extractFromTree(spec, query, tree, source, relPath) {
765
878
  // a field the class inherits (declared in a base, maybe in another file): the resolver's to find
766
879
  if (!t && /^[a-z_]/.test(first.name) && !isDeclared(ctx, first.name)) t = applyOps(symbols[classOf(ctx.sym, symbols)].qname + '#' + first.name, first.ops);
767
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;
768
886
  // `Type.staticField…`: a capitalised root that is not a local names a type
769
887
  if (!t && !v && segs.length > 1 && !first.ops.length && /^[A-Z]/.test(first.name)) t = first.name;
770
888
  // `b := Form`: a package-level variable, maybe of another file of the package; its type is
771
889
  // the resolver's to find (a Go name is never both a type and a value)
772
890
  else if (!t && !v && segs.length === 1 && !first.ops.length && spec.packageValues && !isDeclared(ctx, first.name)) t = first.name;
773
- // `pkg.New()` / `utils.makeFoo()`: a call through a module or import alias returns the callee's type
774
- else if (!t && !v && segs.length > 1 && !first.ops.length && segs[1].ops[0] === '()') {
775
- t = applyOps('call:' + first.name + '.' + segs[1].name, segs[1].ops.slice(1));
776
- 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
+ }
777
900
  }
778
901
  }
779
902
  for (; i < segs.length && t; i++) {
780
903
  const seg = segs[i];
781
904
  const call = seg.ops[0] === '()';
782
- 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
+ }
783
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; }
784
916
  if (!deferred(t) && t.endsWith('[]')) {
785
917
  // `list.get(0)`, `xs.first()`: collection accessors hand back an element
786
918
  if (call && spec.elementMethods?.has(seg.name)) { t = applyOps(t.slice(0, -2), seg.ops.slice(1)); continue; }
@@ -792,6 +924,17 @@ function extractFromTree(spec, query, tree, source, relPath) {
792
924
  if (call && spec.mapValueMethods?.has(seg.name)) { t = applyOps(t.slice(0, -2) + '[]', seg.ops.slice(1)); continue; }
793
925
  return '!';
794
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
+ }
795
938
  t = applyOps(t + '#' + seg.name, seg.ops);
796
939
  }
797
940
  return t;
@@ -824,6 +967,18 @@ function extractFromTree(spec, query, tree, source, relPath) {
824
967
  const [clsIdx, fname] = [Number(k.slice(0, k.indexOf(':'))), k.slice(k.indexOf(':') + 1)];
825
968
  inferredFields.push({ owner: symbols[clsIdx].qname, name: fname, type });
826
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
+ }
827
982
  // module-level values typed by their initialiser (`export const api = new ApiClient()`), so
828
983
  // importers can reach the members of such singletons
829
984
  for (let i = 0; i < symbols.length; i++) {
@@ -835,7 +990,8 @@ function extractFromTree(spec, query, tree, source, relPath) {
835
990
  // undeclared return types: all `return` expressions of a callable agree on one type
836
991
  const returns = new Map(); // symIdx -> [types]
837
992
  for (const n of retNodes) {
838
- const k = scopeAt(n.startIndex);
993
+ let k = scopeAt(n.startIndex);
994
+ while (k >= 0 && scopes[k].block) k = scopes[k].parent;
839
995
  const owner = k >= 0 ? scopes[k].sym : -1;
840
996
  if (owner < 0 || symbols[owner].type || !CALLABLE_KINDS.has(symbols[owner].kind)) continue;
841
997
  const desc = receiverDescriptor(n, spec);
@@ -848,9 +1004,24 @@ function extractFromTree(spec, query, tree, source, relPath) {
848
1004
  if (uniq.size === 1 && !uniq.has(null)) symbols[owner].type = types[0];
849
1005
  }
850
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
+ };
851
1022
  const inferReceiverType = (recv, ctx) => {
852
1023
  if (!recv || recv === 'this') return null;
853
- const t = inferType(recv, ctx);
1024
+ const t = inferType(recv, { ...ctx, receiver: true });
854
1025
  return t && !deferred(t) && /(\[\]|\{\})$/.test(t) ? '!' : t; // a collection's own methods are the language's
855
1026
  };
856
1027
 
@@ -862,23 +1033,43 @@ function extractFromTree(spec, query, tree, source, relPath) {
862
1033
  if (spec.normalizeRefName) name = spec.normalizeRefName(name, r);
863
1034
  if (!name || name.length > 128) continue;
864
1035
  const kind = r.kind;
865
- const recv = r.recvNode ? receiverDescriptor(r.recvNode, spec) : '';
1036
+ let recv = r.recvNode ? receiverDescriptor(r.recvNode, spec) : '';
866
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
+ }
867
1054
  if (kind === 'call' && !recv && spec.builtinCalls?.has(name) && !isDeclared(ctx, name)) continue;
868
1055
  const root = recv.split('.')[0];
869
1056
  // `module.x()` is the CommonJS global only when no local `module` is in scope
870
- if (recv && spec.externalReceivers?.has(root) && !isDeclared(ctx, root)) continue;
1057
+ if (recv && !selfArg && spec.externalReceivers?.has(root) && !isDeclared(ctx, root)) continue;
871
1058
  if (kind === 'value' && spec.isNoiseValue?.(name)) continue;
872
1059
  // a value or a call by the name of a local (`stack := stack(3); log(stack)`, `check := func…;
873
1060
  // check()`) is the local, in languages where no local stands for repository code (Go: no
874
1061
  // imports bind locals)
875
- 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;
876
1065
  const pos = r.nameNode.startPosition;
877
1066
  const encl = ctx.sym;
878
1067
  const recvType = inferReceiverType(recv, { ...ctx, pos: r.nameNode.startIndex });
879
1068
  // arguments passed: tells overloads apart where each one is a method of its own
880
1069
  const a = spec.distinctOverloads && (kind === 'call' || kind === 'new') ? callArgc(r.node) : null;
881
- 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 });
882
1073
  }
883
1074
 
884
1075
  // inheritance: attach base names to the enclosing class symbol
@@ -899,7 +1090,27 @@ function extractFromTree(spec, query, tree, source, relPath) {
899
1090
  const extra = spec.fileInfo ? spec.fileInfo(root, source) : null;
900
1091
 
901
1092
  for (const s of symbols) { delete s.startIndex; delete s.endIndex; delete s.lexParent; }
902
- 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 };
903
1114
  }
904
1115
 
905
1116
  const NULLISH_TYPES = new Set(['null', 'undefined', 'None', 'void', 'nil', 'never', 'NoneType', 'Nothing', 'Unit']);
@@ -920,6 +1131,12 @@ function splitTop(s, sep) {
920
1131
  return out;
921
1132
  }
922
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
+
923
1140
  const arrayOf = (e) => (e && !/(\[\]|\{\})$/.test(e) ? e + '[]' : null);
924
1141
  const mapOf = (v) => (v && !/(\[\]|\{\})$/.test(v) ? v + '{}' : null);
925
1142
 
@@ -943,9 +1160,23 @@ export function normalizeType(t, spec, depth = 0) {
943
1160
  const norm = [...new Set(alts.filter(a => !NULLISH_TYPES.has(a)).map(a => normalizeType(a, spec, depth + 1)))];
944
1161
  return norm.length === 1 ? norm[0] : null;
945
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
+ }
946
1172
  if (splitTop(s, '&').length > 1 || splitTop(s, ',').length > 1) return null; // intersections / tuples
947
1173
  let m;
948
- 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
+ }
949
1180
  if ((m = /^(.+?)\s*\[\s*\]$/s.exec(s))) return arrayOf(normalizeType(m[1], spec, depth + 1)); // T[]
950
1181
  if ((m = /^([A-Za-z_$][\w$.]*)\{\}$/.exec(s))) return mapOf(normalizeType(m[1], spec, depth + 1)); // a map of T, as normalised
951
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