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.
- package/CHANGELOG.md +456 -0
- package/README.md +72 -15
- package/SECURITY.md +21 -4
- package/bin/graph-indexer.mjs +46 -5
- package/integrations/claude-code/.claude-plugin/plugin.json +3 -2
- 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 +12 -6
- package/integrations/opencode/graph-indexer.js +13 -8
- package/package.json +2 -2
- package/src/cli/helper.mjs +15 -1
- package/src/cli/hook.mjs +140 -48
- package/src/cli/init.mjs +49 -17
- package/src/cli/resident.mjs +15 -5
- package/src/index/discover.mjs +3 -5
- package/src/index/indexer.mjs +77 -41
- package/src/index/modules.mjs +82 -7
- package/src/index/parse-worker.mjs +12 -0
- package/src/index/parsepool.mjs +73 -0
- package/src/index/resolver.mjs +371 -25
- package/src/mcp/server.mjs +16 -12
- package/src/mcp/tools.mjs +419 -26
- package/src/parse/arity.mjs +41 -3
- package/src/parse/extract.mjs +277 -46
- 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 +55 -17
- 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 +87 -22
- 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/query/testcmd.mjs +25 -14
- package/src/query/textsearch.mjs +29 -11
- 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/src/util/git.mjs +30 -0
- package/src/util/paths.mjs +40 -1
- package/src/util/regex.mjs +2 -0
- 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
|
@@ -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 (
|
|
62
|
-
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; }
|
|
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
|
|
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;
|
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);
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
629
|
-
|
|
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
|
-
|
|
752
|
-
|
|
753
|
-
|
|
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
|
-
|
|
775
|
-
|
|
776
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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)))
|
|
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
|
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
|