@maxgfr/codeindex 2.17.0 → 2.18.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/README.md +26 -3
- package/docs/MIGRATION.md +107 -0
- package/package.json +1 -1
- package/scripts/engine.d.mts +243 -19
- package/scripts/engine.mjs +1768 -723
- package/src/ast/extract.ts +180 -142
- package/src/coupling.ts +0 -0
- package/src/delta.ts +417 -0
- package/src/derived.ts +17 -0
- package/src/engine-cli.ts +201 -26
- package/src/engine.ts +31 -4
- package/src/extract/code.ts +4 -1
- package/src/graph.ts +0 -0
- package/src/mcp.ts +469 -212
- package/src/pool.ts +271 -0
- package/src/preload.ts +159 -0
- package/src/render/scip.ts +80 -20
- package/src/render/symbols-json.ts +3 -2
- package/src/scan.ts +142 -17
- package/src/traverse.ts +164 -0
- package/src/types.ts +1 -1
- package/src/viz.ts +91 -1
package/src/ast/extract.ts
CHANGED
|
@@ -7,12 +7,18 @@ import { grammarKeyForExt, grammarReady, parserFor } from "./loader.js";
|
|
|
7
7
|
interface TSNode {
|
|
8
8
|
type: string;
|
|
9
9
|
text: string;
|
|
10
|
+
startIndex: number;
|
|
11
|
+
endIndex: number;
|
|
10
12
|
startPosition: { row: number; column: number };
|
|
11
13
|
endPosition: { row: number; column: number };
|
|
12
14
|
namedChildCount: number;
|
|
13
15
|
namedChild(i: number): TSNode | null;
|
|
14
16
|
childForFieldName(name: string): TSNode | null;
|
|
15
17
|
children: TSNode[];
|
|
18
|
+
// ONE marshal + ONE wasm call for the whole child list, memoized on the node —
|
|
19
|
+
// versus `namedChildCount` plus a `namedChild(i)` round-trip per index. Every
|
|
20
|
+
// traversal below reads children through this, never by index.
|
|
21
|
+
namedChildren: TSNode[];
|
|
16
22
|
}
|
|
17
23
|
|
|
18
24
|
export interface AstResult {
|
|
@@ -45,24 +51,8 @@ const ANON_DEFAULT_FN = new Set([
|
|
|
45
51
|
]);
|
|
46
52
|
const ANON_DEFAULT_CLASS = new Set(["class", "class_declaration", "abstract_class_declaration"]);
|
|
47
53
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
function collectRefIdents(root: TSNode, defNames: Set<string>): string[] {
|
|
51
|
-
const found = new Set<string>();
|
|
52
|
-
const visit = (node: TSNode): void => {
|
|
53
|
-
if (
|
|
54
|
-
node.namedChildCount === 0 &&
|
|
55
|
-
/identifier|constant|(^|_)name$/.test(node.type) &&
|
|
56
|
-
/^[A-Za-z_]\w{4,}$/.test(node.text) &&
|
|
57
|
-
!defNames.has(node.text)
|
|
58
|
-
) {
|
|
59
|
-
found.add(node.text);
|
|
60
|
-
}
|
|
61
|
-
for (let i = 0; i < node.namedChildCount; i++) visit(node.namedChild(i)!);
|
|
62
|
-
};
|
|
63
|
-
visit(root);
|
|
64
|
-
return [...found].sort().slice(0, MAX_REF_IDENTS);
|
|
65
|
-
}
|
|
54
|
+
const REF_IDENT_TYPE = /identifier|constant|(^|_)name$/;
|
|
55
|
+
const REF_IDENT_TEXT = /^[A-Za-z_]\w{4,}$/;
|
|
66
56
|
|
|
67
57
|
// How one grammar's declarations map to symbols. `defs` maps a node type to a
|
|
68
58
|
// symbol kind; `containers` are nodes whose body we recurse into for nested
|
|
@@ -274,9 +264,17 @@ const SPECS: Record<string, LangSpec> = {
|
|
|
274
264
|
},
|
|
275
265
|
};
|
|
276
266
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
267
|
+
// The node's first line, read straight out of the source instead of through
|
|
268
|
+
// `node.text` — that getter materialises the node's ENTIRE text across the wasm
|
|
269
|
+
// boundary, so a 2000-line class allocated the whole class twice per symbol.
|
|
270
|
+
// Slicing [startIndex, first newline) from `src` is the same string by
|
|
271
|
+
// construction (node.text === src.slice(startIndex, endIndex)).
|
|
272
|
+
function firstLine(node: TSNode, src: string): string {
|
|
273
|
+
const start = node.startIndex;
|
|
274
|
+
const end = node.endIndex;
|
|
275
|
+
const nl = src.indexOf("\n", start);
|
|
276
|
+
const stop = nl === -1 || nl > end ? end : nl;
|
|
277
|
+
return src.slice(start, stop).trim().slice(0, 200);
|
|
280
278
|
}
|
|
281
279
|
|
|
282
280
|
function nameOf(node: TSNode): string | undefined {
|
|
@@ -287,52 +285,21 @@ function nameOf(node: TSNode): string | undefined {
|
|
|
287
285
|
// `declarator` field down to the identifier leaf.
|
|
288
286
|
let decl = node.childForFieldName("declarator");
|
|
289
287
|
while (decl) {
|
|
290
|
-
if (decl.
|
|
288
|
+
if (decl.namedChildren.length === 0 && /(^|_)identifier$/.test(decl.type)) return decl.text;
|
|
291
289
|
const next = decl.childForFieldName("declarator");
|
|
292
290
|
if (!next || next === decl) break;
|
|
293
291
|
decl = next;
|
|
294
292
|
}
|
|
295
293
|
// Fall back to the first identifier-like named child (covers grammars that do
|
|
296
294
|
// not expose a `name` field on a given node).
|
|
297
|
-
for (
|
|
298
|
-
const c = node.namedChild(i)!;
|
|
295
|
+
for (const c of node.namedChildren) {
|
|
299
296
|
if (/(^|_)(identifier|name|constant)$/.test(c.type)) return c.text;
|
|
300
297
|
}
|
|
301
298
|
return undefined;
|
|
302
299
|
}
|
|
303
300
|
|
|
304
|
-
// Read import specifiers from the whole tree by scanning for the grammar's import
|
|
305
|
-
// node types. "string" pulls the first string literal's inner text; "path" takes
|
|
306
|
-
// the dotted/namespaced module text verbatim (resolution happens later).
|
|
307
|
-
function collectImports(root: TSNode, spec: LangSpec): RawRef[] {
|
|
308
|
-
if (!spec.imports) return [];
|
|
309
|
-
const out: RawRef[] = [];
|
|
310
|
-
const seen = new Set<string>();
|
|
311
|
-
const add = (s: string): void => {
|
|
312
|
-
const v = s.trim();
|
|
313
|
-
if (v && !seen.has(v)) {
|
|
314
|
-
seen.add(v);
|
|
315
|
-
out.push({ kind: "import", spec: v });
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
const visit = (node: TSNode): void => {
|
|
319
|
-
const how = spec.imports![node.type];
|
|
320
|
-
if (how === "string") {
|
|
321
|
-
const str = findFirst(node, (n) => /string/.test(n.type));
|
|
322
|
-
if (str) add(str.text.replace(/^['"]|['"]$/g, ""));
|
|
323
|
-
} else if (how === "path") {
|
|
324
|
-
const name = node.childForFieldName("name") ?? node.childForFieldName("module_name");
|
|
325
|
-
add((name ?? node).text.replace(/^(import|from)\s+/, "").split(/\s+/)[0]!);
|
|
326
|
-
}
|
|
327
|
-
for (let i = 0; i < node.namedChildCount; i++) visit(node.namedChild(i)!);
|
|
328
|
-
};
|
|
329
|
-
visit(root);
|
|
330
|
-
return out;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
301
|
function findFirst(node: TSNode, pred: (n: TSNode) => boolean): TSNode | undefined {
|
|
334
|
-
for (
|
|
335
|
-
const c = node.namedChild(i)!;
|
|
302
|
+
for (const c of node.namedChildren) {
|
|
336
303
|
if (pred(c)) return c;
|
|
337
304
|
const deep = findFirst(c, pred);
|
|
338
305
|
if (deep) return deep;
|
|
@@ -353,7 +320,8 @@ const IDENT_LEAF = /(^|_)(identifier|name|constant|word)$/;
|
|
|
353
320
|
// named child). Returns undefined for a computed/complex callee we can't name.
|
|
354
321
|
function readName(node: TSNode | null): string | undefined {
|
|
355
322
|
if (!node) return undefined;
|
|
356
|
-
|
|
323
|
+
const kids = node.namedChildren;
|
|
324
|
+
if (kids.length === 0) return IDENT_LEAF.test(node.type) ? node.text : undefined;
|
|
357
325
|
const seg =
|
|
358
326
|
node.childForFieldName("name") ??
|
|
359
327
|
node.childForFieldName("property") ??
|
|
@@ -365,7 +333,7 @@ function readName(node: TSNode | null): string | undefined {
|
|
|
365
333
|
// instead of tripping over type_arguments/arguments as the last child.
|
|
366
334
|
node.childForFieldName("function");
|
|
367
335
|
if (seg) return readName(seg);
|
|
368
|
-
const last =
|
|
336
|
+
const last = kids[kids.length - 1];
|
|
369
337
|
return last && last !== node ? readName(last) : undefined;
|
|
370
338
|
}
|
|
371
339
|
|
|
@@ -379,7 +347,7 @@ function readName(node: TSNode | null): string | undefined {
|
|
|
379
347
|
// expression's `table` (scala's field_expression reuses `value`). Undefined for a
|
|
380
348
|
// bare callee or a computed/complex receiver (`fetch().then(...)`, `arr[0].map(...)`).
|
|
381
349
|
function readReceiver(node: TSNode | null): string | undefined {
|
|
382
|
-
if (!node || node.
|
|
350
|
+
if (!node || node.namedChildren.length === 0) return undefined;
|
|
383
351
|
const obj =
|
|
384
352
|
node.childForFieldName("object") ??
|
|
385
353
|
node.childForFieldName("operand") ??
|
|
@@ -401,84 +369,153 @@ function readReceiver(node: TSNode | null): string | undefined {
|
|
|
401
369
|
// Names are filtered to plausible identifiers (≥ 2 chars), deduped by name+line,
|
|
402
370
|
// sorted, and capped (default MAX_CALLS; overridable via maxCalls), so the set
|
|
403
371
|
// stays small and deterministic.
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
372
|
+
// Everything the post-declaration passes need, gathered in ONE pre-order walk.
|
|
373
|
+
//
|
|
374
|
+
// These four collections used to be four independent full-tree traversals, each
|
|
375
|
+
// re-crossing the wasm boundary for every node. They are order-independent by
|
|
376
|
+
// construction — idents and importedNames are Sets that get sorted, calls dedups
|
|
377
|
+
// on (name, line) then sorts — so folding them into a single pre-order walk in
|
|
378
|
+
// the original per-node order produces byte-identical results.
|
|
379
|
+
//
|
|
380
|
+
// `refs`/`pkg` are computed only when `wantImports` is set: the production path
|
|
381
|
+
// (extractCode) recomputes both with regex and discards the AST's versions, so
|
|
382
|
+
// paying for them by default was pure waste. The public `extractAst` still asks
|
|
383
|
+
// for them, keeping its contract intact.
|
|
384
|
+
interface Collected {
|
|
385
|
+
refs: RawRef[];
|
|
386
|
+
idents: string[];
|
|
387
|
+
calls: { name: string; line: number; receiver?: string }[];
|
|
388
|
+
importedNames: string[];
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function collectAll(
|
|
392
|
+
root: TSNode,
|
|
393
|
+
spec: LangSpec,
|
|
394
|
+
defNames: Set<string>,
|
|
395
|
+
maxCalls: number,
|
|
396
|
+
wantImports: boolean,
|
|
397
|
+
): Collected {
|
|
398
|
+
const identsFound = new Set<string>();
|
|
399
|
+
|
|
400
|
+
const wantCalls = spec.calls !== undefined;
|
|
401
|
+
const calls: { name: string; line: number; receiver?: string }[] = [];
|
|
402
|
+
const callSeen = new Set<string>();
|
|
403
|
+
const addCall = (name: string | undefined, node: TSNode, receiver?: string): void => {
|
|
409
404
|
if (!name || name.length < 2 || !/^[A-Za-z_]\w*$/.test(name)) return;
|
|
410
405
|
const line = node.startPosition.row + 1;
|
|
411
406
|
const key = `${name} ${line}`;
|
|
412
|
-
if (
|
|
413
|
-
|
|
414
|
-
|
|
407
|
+
if (callSeen.has(key)) return;
|
|
408
|
+
callSeen.add(key);
|
|
409
|
+
calls.push(receiver ? { name, line, receiver } : { name, line });
|
|
415
410
|
};
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
} else if (how === "constructor") {
|
|
429
|
-
// TS/Java/C# expose the type under a `constructor`/`type` field; PHP's
|
|
430
|
-
// object_creation_expression carries it as a bare `name` child, so fall
|
|
431
|
-
// back to the first identifier-ish child when no field matches.
|
|
432
|
-
let t = node.childForFieldName("constructor") ?? node.childForFieldName("type") ?? node.childForFieldName("name");
|
|
433
|
-
for (let i = 0; !t && i < node.namedChildCount; i++) {
|
|
434
|
-
const c = node.namedChild(i)!;
|
|
435
|
-
if (IDENT_LEAF.test(c.type)) t = c;
|
|
436
|
-
}
|
|
437
|
-
add(readName(t), node, readReceiver(t ?? null));
|
|
411
|
+
|
|
412
|
+
const wantNames = spec.imports?.import_statement !== undefined;
|
|
413
|
+
const namesFound = new Set<string>();
|
|
414
|
+
|
|
415
|
+
const wantRefs = wantImports && spec.imports !== undefined;
|
|
416
|
+
const refs: RawRef[] = [];
|
|
417
|
+
const refSeen = new Set<string>();
|
|
418
|
+
const addRef = (s: string): void => {
|
|
419
|
+
const v = s.trim();
|
|
420
|
+
if (v && !refSeen.has(v)) {
|
|
421
|
+
refSeen.add(v);
|
|
422
|
+
refs.push({ kind: "import", spec: v });
|
|
438
423
|
}
|
|
439
|
-
for (let i = 0; i < node.namedChildCount; i++) visit(node.namedChild(i)!);
|
|
440
424
|
};
|
|
441
|
-
visit(root);
|
|
442
|
-
out.sort((a, b) => byStr(a.name, b.name) || a.line - b.line);
|
|
443
|
-
return out.slice(0, maxCalls);
|
|
444
|
-
}
|
|
445
425
|
|
|
446
|
-
// Collect JS/TS named-import bindings: `import { a, b as c } from "x"` →
|
|
447
|
-
// `import_clause → named_imports → import_specifier`, reading each specifier's
|
|
448
|
-
// `name` field (the pre-alias name). Default/namespace bindings are intentionally
|
|
449
|
-
// NOT collected — the call-resolution gate only corroborates named imports, and a
|
|
450
|
-
// default/namespace binding names a module, not a specific exported symbol.
|
|
451
|
-
function collectImportedNames(root: TSNode, spec: LangSpec): string[] {
|
|
452
|
-
if (!spec.imports?.import_statement) return [];
|
|
453
|
-
const found = new Set<string>();
|
|
454
426
|
const visit = (node: TSNode): void => {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
427
|
+
const type = node.type;
|
|
428
|
+
const kids = node.namedChildren;
|
|
429
|
+
|
|
430
|
+
// --- distinctive referenced identifiers (leaves only) ---
|
|
431
|
+
if (kids.length === 0 && REF_IDENT_TYPE.test(type)) {
|
|
432
|
+
const text = node.text;
|
|
433
|
+
if (REF_IDENT_TEXT.test(text) && !defNames.has(text)) identsFound.add(text);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// --- call sites ---
|
|
437
|
+
if (wantCalls) {
|
|
438
|
+
const how = spec.calls![type];
|
|
439
|
+
if (how === "function") {
|
|
440
|
+
// Grammars name the callee field differently: `function` (TS/py/go/rust/
|
|
441
|
+
// c#/php), `name` (Java's method_invocation), `method` (Ruby's call). The
|
|
442
|
+
// receiver lives on the qualified callee node, or (java/ruby) on the call
|
|
443
|
+
// node itself.
|
|
444
|
+
const callee =
|
|
445
|
+
node.childForFieldName("function") ?? node.childForFieldName("callee") ?? node.childForFieldName("method") ?? node.childForFieldName("name");
|
|
446
|
+
addCall(readName(callee), node, readReceiver(callee) ?? readReceiver(node));
|
|
447
|
+
} else if (how === "member") {
|
|
448
|
+
addCall(readName(node.childForFieldName("name")), node, readReceiver(node));
|
|
449
|
+
} else if (how === "constructor") {
|
|
450
|
+
// TS/Java/C# expose the type under a `constructor`/`type` field; PHP's
|
|
451
|
+
// object_creation_expression carries it as a bare `name` child, so fall
|
|
452
|
+
// back to the first identifier-ish child when no field matches.
|
|
453
|
+
let t = node.childForFieldName("constructor") ?? node.childForFieldName("type") ?? node.childForFieldName("name");
|
|
454
|
+
for (let i = 0; !t && i < kids.length; i++) {
|
|
455
|
+
const c = kids[i]!;
|
|
456
|
+
if (IDENT_LEAF.test(c.type)) t = c;
|
|
457
|
+
}
|
|
458
|
+
addCall(readName(t), node, readReceiver(t ?? null));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// --- JS/TS named-import bindings: import_clause → named_imports →
|
|
463
|
+
// import_specifier, reading each specifier's pre-alias `name`. Default and
|
|
464
|
+
// namespace bindings are intentionally NOT collected — the call-resolution
|
|
465
|
+
// gate only corroborates named imports.
|
|
466
|
+
if (wantNames && type === "import_statement") {
|
|
467
|
+
for (const clause of kids) {
|
|
458
468
|
if (clause.type !== "import_clause") continue;
|
|
459
|
-
for (
|
|
460
|
-
const named = clause.namedChild(j)!;
|
|
469
|
+
for (const named of clause.namedChildren) {
|
|
461
470
|
if (named.type !== "named_imports") continue;
|
|
462
|
-
for (
|
|
463
|
-
const specifier = named.namedChild(k)!;
|
|
471
|
+
for (const specifier of named.namedChildren) {
|
|
464
472
|
if (specifier.type !== "import_specifier") continue;
|
|
465
|
-
const nm = specifier.childForFieldName("name") ?? specifier.
|
|
466
|
-
if (nm?.text)
|
|
473
|
+
const nm = specifier.childForFieldName("name") ?? specifier.namedChildren[0];
|
|
474
|
+
if (nm?.text) namesFound.add(nm.text);
|
|
467
475
|
}
|
|
468
476
|
}
|
|
469
477
|
}
|
|
470
478
|
}
|
|
471
|
-
|
|
479
|
+
|
|
480
|
+
// --- raw import specifiers. "string" pulls the first string literal's inner
|
|
481
|
+
// text; "path" takes the dotted/namespaced module text verbatim (resolution
|
|
482
|
+
// happens later).
|
|
483
|
+
if (wantRefs) {
|
|
484
|
+
const how = spec.imports![type];
|
|
485
|
+
if (how === "string") {
|
|
486
|
+
const str = findFirst(node, (n) => /string/.test(n.type));
|
|
487
|
+
if (str) addRef(str.text.replace(/^['"]|['"]$/g, ""));
|
|
488
|
+
} else if (how === "path") {
|
|
489
|
+
const name = node.childForFieldName("name") ?? node.childForFieldName("module_name");
|
|
490
|
+
addRef((name ?? node).text.replace(/^(import|from)\s+/, "").split(/\s+/)[0]!);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
for (const c of kids) visit(c);
|
|
472
495
|
};
|
|
473
496
|
visit(root);
|
|
474
|
-
|
|
497
|
+
|
|
498
|
+
calls.sort((a, b) => byStr(a.name, b.name) || a.line - b.line);
|
|
499
|
+
return {
|
|
500
|
+
refs,
|
|
501
|
+
idents: [...identsFound].sort().slice(0, MAX_REF_IDENTS),
|
|
502
|
+
calls: calls.slice(0, maxCalls),
|
|
503
|
+
importedNames: [...namesFound].sort(byStr).slice(0, MAX_IMPORTED_NAMES),
|
|
504
|
+
};
|
|
475
505
|
}
|
|
476
506
|
|
|
477
507
|
// Extract declared symbols from one file via its committed grammar. Returns
|
|
478
508
|
// undefined when no grammar is loaded for the extension (caller falls back to the
|
|
479
509
|
// regex extractor). Walks top-level declarations plus one level of nested members.
|
|
480
510
|
// `opts.maxCalls` overrides the per-file call-site cap (default MAX_CALLS).
|
|
481
|
-
|
|
511
|
+
// `opts.imports` (default true) computes `refs`/`pkg`; extractCode passes false
|
|
512
|
+
// because it recomputes both with regex and discards these — see collectAll.
|
|
513
|
+
export function extractAst(
|
|
514
|
+
rel: string,
|
|
515
|
+
ext: string,
|
|
516
|
+
content: string,
|
|
517
|
+
opts: { maxCalls?: number; imports?: boolean } = {},
|
|
518
|
+
): AstResult | undefined {
|
|
482
519
|
const key = grammarKeyForExt(ext);
|
|
483
520
|
if (!key || !grammarReady(key)) return undefined;
|
|
484
521
|
const spec = SPECS[key];
|
|
@@ -503,13 +540,11 @@ export function extractAst(rel: string, ext: string, content: string, opts: { ma
|
|
|
503
540
|
// `export …` / `export default …` (JS/TS) marks the wrapped declaration.
|
|
504
541
|
const nowExported = exported || node.type === "export_statement";
|
|
505
542
|
if (node.type === "export_statement") {
|
|
506
|
-
for (
|
|
507
|
-
const c = node.namedChild(i)!;
|
|
543
|
+
for (const c of node.namedChildren) {
|
|
508
544
|
if (c.type === "identifier") exportedNames.add(c.text);
|
|
509
545
|
else if (c.type === "export_clause") {
|
|
510
|
-
for (
|
|
511
|
-
const
|
|
512
|
-
const nm = spec.childForFieldName("name") ?? spec.namedChild(0);
|
|
546
|
+
for (const spec of c.namedChildren) {
|
|
547
|
+
const nm = spec.childForFieldName("name") ?? spec.namedChildren[0];
|
|
513
548
|
if (nm?.text) exportedNames.add(nm.text);
|
|
514
549
|
}
|
|
515
550
|
}
|
|
@@ -518,8 +553,7 @@ export function extractAst(rel: string, ext: string, content: string, opts: { ma
|
|
|
518
553
|
// declaration walk could pick up — name it after the file stem (ultradoc
|
|
519
554
|
// parity), so the module's default export is a real, referencable symbol.
|
|
520
555
|
if (stem && node.children.some((c) => c.type === "default")) {
|
|
521
|
-
for (
|
|
522
|
-
const c = node.namedChild(i)!;
|
|
556
|
+
for (const c of node.namedChildren) {
|
|
523
557
|
const fnLike = ANON_DEFAULT_FN.has(c.type);
|
|
524
558
|
const classLike = ANON_DEFAULT_CLASS.has(c.type);
|
|
525
559
|
if ((fnLike || classLike) && !c.childForFieldName("name")) {
|
|
@@ -529,7 +563,7 @@ export function extractAst(rel: string, ext: string, content: string, opts: { ma
|
|
|
529
563
|
file: rel,
|
|
530
564
|
line: node.startPosition.row + 1,
|
|
531
565
|
endLine: node.endPosition.row + 1,
|
|
532
|
-
signature: firstLine(node),
|
|
566
|
+
signature: firstLine(node, content),
|
|
533
567
|
exported: true,
|
|
534
568
|
lang: spec.lang,
|
|
535
569
|
});
|
|
@@ -543,7 +577,7 @@ export function extractAst(rel: string, ext: string, content: string, opts: { ma
|
|
|
543
577
|
// `exports.*` / `module.exports.*` targets count as exported — augmenting
|
|
544
578
|
// a local object (res.*, Foo.prototype.*) is not a module export.
|
|
545
579
|
if (spec.assignments && node.type === "expression_statement") {
|
|
546
|
-
const expr = node.
|
|
580
|
+
const expr = node.namedChildren[0];
|
|
547
581
|
if (expr?.type === "assignment_expression") {
|
|
548
582
|
const left = expr.childForFieldName("left");
|
|
549
583
|
const right = expr.childForFieldName("right");
|
|
@@ -552,8 +586,7 @@ export function extractAst(rel: string, ext: string, content: string, opts: { ma
|
|
|
552
586
|
// shorthand names, keys and identifier values as exported (key = the
|
|
553
587
|
// exported surface, identifier value = the local declaration).
|
|
554
588
|
if (right.type === "object") {
|
|
555
|
-
for (
|
|
556
|
-
const p = right.namedChild(i)!;
|
|
589
|
+
for (const p of right.namedChildren) {
|
|
557
590
|
if (p.type === "shorthand_property_identifier") exportedNames.add(p.text);
|
|
558
591
|
else if (p.type === "pair") {
|
|
559
592
|
const k = p.childForFieldName("key");
|
|
@@ -592,7 +625,7 @@ export function extractAst(rel: string, ext: string, content: string, opts: { ma
|
|
|
592
625
|
line: expr.startPosition.row + 1,
|
|
593
626
|
endLine: expr.endPosition.row + 1,
|
|
594
627
|
...(parent ? { parent } : {}),
|
|
595
|
-
signature: firstLine(expr),
|
|
628
|
+
signature: firstLine(expr, content),
|
|
596
629
|
exported: nowExported || exportedAssign,
|
|
597
630
|
lang: spec.lang,
|
|
598
631
|
});
|
|
@@ -616,7 +649,7 @@ export function extractAst(rel: string, ext: string, content: string, opts: { ma
|
|
|
616
649
|
line: expr.startPosition.row + 1,
|
|
617
650
|
endLine: expr.endPosition.row + 1,
|
|
618
651
|
...(parent ? { parent } : {}),
|
|
619
|
-
signature: firstLine(expr),
|
|
652
|
+
signature: firstLine(expr, content),
|
|
620
653
|
exported: true,
|
|
621
654
|
lang: spec.lang,
|
|
622
655
|
});
|
|
@@ -636,11 +669,14 @@ export function extractAst(rel: string, ext: string, content: string, opts: { ma
|
|
|
636
669
|
if (spec.assignments && node.type === "assignment_statement") {
|
|
637
670
|
const vars = node.children.find((c) => c.type === "variable_list");
|
|
638
671
|
const vals = node.children.find((c) => c.type === "expression_list");
|
|
639
|
-
const
|
|
672
|
+
const targets = vars?.namedChildren ?? [];
|
|
673
|
+
const values = vals?.namedChildren ?? [];
|
|
674
|
+
const pairs = Math.min(targets.length, values.length);
|
|
640
675
|
for (let i = 0; i < pairs; i++) {
|
|
641
|
-
const target =
|
|
642
|
-
const value =
|
|
676
|
+
const target = targets[i]!;
|
|
677
|
+
const value = values[i]!;
|
|
643
678
|
if (value.type !== "function_definition" || !/^[\w.:]+$/.test(target.text)) continue;
|
|
679
|
+
const line = firstLine(node, content);
|
|
644
680
|
symbols.push({
|
|
645
681
|
name: target.text,
|
|
646
682
|
kind: "function",
|
|
@@ -648,8 +684,8 @@ export function extractAst(rel: string, ext: string, content: string, opts: { ma
|
|
|
648
684
|
line: node.startPosition.row + 1,
|
|
649
685
|
endLine: node.endPosition.row + 1,
|
|
650
686
|
...(parent ? { parent } : {}),
|
|
651
|
-
signature:
|
|
652
|
-
exported: nowExported || spec.exported(
|
|
687
|
+
signature: line,
|
|
688
|
+
exported: nowExported || spec.exported(line, target.text),
|
|
653
689
|
lang: spec.lang,
|
|
654
690
|
});
|
|
655
691
|
}
|
|
@@ -659,7 +695,7 @@ export function extractAst(rel: string, ext: string, content: string, opts: { ma
|
|
|
659
695
|
if (kind) {
|
|
660
696
|
const name = nameOf(node);
|
|
661
697
|
if (name) {
|
|
662
|
-
const line = firstLine(node);
|
|
698
|
+
const line = firstLine(node, content);
|
|
663
699
|
symbols.push({
|
|
664
700
|
name,
|
|
665
701
|
kind,
|
|
@@ -673,14 +709,12 @@ export function extractAst(rel: string, ext: string, content: string, opts: { ma
|
|
|
673
709
|
});
|
|
674
710
|
// Recurse into this declaration's body for nested members (methods),
|
|
675
711
|
// scoping their parent to this symbol.
|
|
676
|
-
for (
|
|
677
|
-
walkBody(node.namedChild(i)!, name, nowExported);
|
|
678
|
-
}
|
|
712
|
+
for (const c of node.namedChildren) walkBody(c, name, nowExported);
|
|
679
713
|
return;
|
|
680
714
|
}
|
|
681
715
|
}
|
|
682
716
|
if (spec.containers.has(node.type)) {
|
|
683
|
-
for (
|
|
717
|
+
for (const c of node.namedChildren) walk(c, parent, nowExported);
|
|
684
718
|
}
|
|
685
719
|
};
|
|
686
720
|
// Recurse a declaration body one level: only container-ish children yield more
|
|
@@ -688,7 +722,7 @@ export function extractAst(rel: string, ext: string, content: string, opts: { ma
|
|
|
688
722
|
// the "top-level + one level" contract).
|
|
689
723
|
const walkBody = (node: TSNode, parent: string, exported: boolean): void => {
|
|
690
724
|
if (spec.containers.has(node.type)) {
|
|
691
|
-
for (
|
|
725
|
+
for (const c of node.namedChildren) walk(c, parent, exported);
|
|
692
726
|
}
|
|
693
727
|
};
|
|
694
728
|
|
|
@@ -697,12 +731,16 @@ export function extractAst(rel: string, ext: string, content: string, opts: { ma
|
|
|
697
731
|
for (const s of symbols) if (!s.exported && exportedNames.has(s.name)) s.exported = true;
|
|
698
732
|
}
|
|
699
733
|
|
|
700
|
-
const
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
734
|
+
const wantImports = opts.imports !== false;
|
|
735
|
+
const { refs, idents, calls, importedNames } = collectAll(
|
|
736
|
+
root,
|
|
737
|
+
spec,
|
|
738
|
+
new Set(symbols.map((s) => s.name)),
|
|
739
|
+
opts.maxCalls ?? MAX_CALLS,
|
|
740
|
+
wantImports,
|
|
741
|
+
);
|
|
704
742
|
let pkg: string | undefined;
|
|
705
|
-
if (spec.lang === "java") {
|
|
743
|
+
if (wantImports && spec.lang === "java") {
|
|
706
744
|
const p = findFirst(root, (n) => n.type === "package_declaration");
|
|
707
745
|
if (p) pkg = p.text.replace(/^package\s+/, "").replace(/;.*$/, "").trim();
|
|
708
746
|
}
|
package/src/coupling.ts
CHANGED
|
Binary file
|