graph-indexer 3.0.1 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +404 -0
- package/README.md +61 -8
- package/bin/graph-indexer.mjs +46 -5
- package/integrations/claude-code/.claude-plugin/plugin.json +1 -1
- package/integrations/claude-code/agents/code-structure.md +0 -3
- package/integrations/claude-code/skills/setup/SKILL.md +26 -0
- package/integrations/claude-code-local/.claude-plugin/plugin.json +9 -0
- package/integrations/{claude-code → claude-code-local}/.mcp.json +1 -1
- package/integrations/claude-code-local/README.md +10 -0
- package/integrations/claude-code-local/agents/code-structure.md +22 -0
- package/integrations/{claude-code → claude-code-local}/hooks/gi-hook.mjs +3 -1
- package/package.json +1 -1
- package/src/cli/helper.mjs +15 -1
- package/src/cli/hook.mjs +123 -41
- package/src/cli/init.mjs +16 -1
- package/src/cli/resident.mjs +1 -1
- package/src/index/indexer.mjs +77 -41
- package/src/index/modules.mjs +76 -2
- package/src/index/parse-worker.mjs +12 -0
- package/src/index/parsepool.mjs +73 -0
- package/src/index/resolver.mjs +362 -23
- package/src/mcp/server.mjs +16 -12
- package/src/mcp/tools.mjs +417 -25
- package/src/parse/arity.mjs +39 -2
- package/src/parse/extract.mjs +269 -45
- package/src/parse/lang/ecma.mjs +2 -0
- package/src/parse/lang/go.mjs +2 -0
- package/src/parse/lang/jvm.mjs +33 -0
- package/src/parse/lang/markdown.mjs +92 -0
- package/src/parse/lang/python.mjs +43 -0
- package/src/parse/lang/rust.mjs +215 -15
- package/src/parse/lang/sfc.mjs +125 -0
- package/src/parse/languages.mjs +9 -2
- package/src/query/check.mjs +49 -13
- package/src/query/facts.mjs +3 -2
- package/src/query/injection-spring.mjs +405 -0
- package/src/query/injection.mjs +173 -0
- package/src/query/intel.mjs +76 -14
- package/src/query/issue.mjs +156 -0
- package/src/query/orm.mjs +286 -0
- package/src/query/read.mjs +6 -1
- package/src/query/report.mjs +53 -0
- package/src/query/routes.mjs +142 -0
- package/src/query/showcase.mjs +59 -0
- package/src/search/search.mjs +85 -17
- package/src/search/semantic.mjs +201 -0
- package/src/search/vectors.mjs +122 -0
- package/src/store/db.mjs +22 -4
- package/vendor/grammars/README.md +47 -0
- package/vendor/grammars/kotlin.wasm.gz +0 -0
- package/vendor/grammars/manifest.json +4 -4
- /package/integrations/{claude-code → claude-code-local}/hooks/hooks.json +0 -0
package/src/parse/arity.mjs
CHANGED
|
@@ -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 (
|
|
63
|
-
if (first && py && method && !
|
|
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;
|
package/src/parse/extract.mjs
CHANGED
|
@@ -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$]
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
636
|
-
|
|
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
|
-
|
|
759
|
-
|
|
760
|
-
|
|
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
|
-
|
|
782
|
-
|
|
783
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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)))
|
|
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
|
package/src/parse/lang/ecma.mjs
CHANGED
|
@@ -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;
|
package/src/parse/lang/go.mjs
CHANGED
|
@@ -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
|