dsh-plugin-codegraph-tree-sitter 0.1.1 → 0.1.5

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/lib/extract.js CHANGED
@@ -7,6 +7,7 @@
7
7
  * real workspace are of exactly this shape.
8
8
  * @module dsh-plugin-codegraph-tree-sitter/extract
9
9
  */
10
+ import { DART_FIELD_NAME_FIELD, DECLARATOR_NAME_FIELD, FIRST_CHILD_NAME_FIELD, KOTLIN_NAME_FIELD, PHP_ELEMENT_NAME_FIELD, SELF_NAME_FIELD, SWIFT_FUNCTION_NAME_FIELD, SWIFT_PROPERTY_NAME_FIELD } from "./languages.js";
10
11
  /** A node's named children, with the `null` slots `web-tree-sitter` reserves for missing nodes dropped. */
11
12
  function namedChildren(node) {
12
13
  return node.namedChildren.filter(child => child !== null);
@@ -36,16 +37,707 @@ function hasKeywordChild(node, keyword) {
36
37
  }
37
38
  return false;
38
39
  }
40
+ /**
41
+ * Java's `modifiers` node for a declaration, wherever it sits. Unlike every other grammar this
42
+ * package extracts from, Java never places a modifier keyword (`public`, `static`, `final`, …) as a
43
+ * direct child of the declaration node itself — it wraps them all in one named `modifiers` child. A
44
+ * captured `variable_declarator` (the `field`/`variable` rules in `languages.ts`) carries no
45
+ * `modifiers` of its own at all; its modifiers sit one level up, on the enclosing
46
+ * `field_declaration`/`local_variable_declaration` this function falls back to. Verified against a
47
+ * real parse, not guessed.
48
+ */
49
+ function javaModifiersNode(node) {
50
+ const own = namedChildren(node).find(child => child.type === 'modifiers');
51
+ if (own !== undefined)
52
+ return own;
53
+ // Every node this is called on is a definition the walk captured below the tree's root, so it
54
+ // always has a parent; the null case only satisfies `.parent`'s general `SyntaxNode | null` type.
55
+ /* v8 ignore next */
56
+ if (node.parent === null)
57
+ return undefined;
58
+ return namedChildren(node.parent).find(child => child.type === 'modifiers');
59
+ }
60
+ /** Whether a Java declaration carries `keyword` among its modifiers — see {@link javaModifiersNode}. */
61
+ function javaHasModifier(node, keyword) {
62
+ const modifiers = javaModifiersNode(node);
63
+ return modifiers !== undefined && hasKeywordChild(modifiers, keyword);
64
+ }
65
+ /**
66
+ * Whether a C/C++ declaration carries `keyword` (`static`, `virtual`, …) among its own named children.
67
+ * Unlike every other grammar this package extracts from, C/C++ wraps a storage-class keyword like
68
+ * `static` in its own named `storage_class_specifier` node rather than leaving it as a bare anonymous
69
+ * token on the declaration itself — `hasKeywordChild` alone would never see it. `virtual`, by contrast,
70
+ * is already its own bare named node with no wrapper, so checking each named child's own text (not just
71
+ * `storage_class_specifier`'s) covers both in one pass. Verified against a real parse, not guessed.
72
+ */
73
+ function cHasStorageClassKeyword(node, keyword) {
74
+ return namedChildren(node).some(child => child.text === keyword);
75
+ }
76
+ /**
77
+ * Whether a C# declaration carries `keyword` (`public`, `static`, …) among its own named children. C#
78
+ * wraps each modifier keyword in its own flat, individually-named `modifier` node directly on the
79
+ * declaration — unlike Java's single wrapping `modifiers` collection (see {@link javaModifiersNode}) or
80
+ * C/C++'s `storage_class_specifier`, there is no group to look inside; each keyword is its own sibling.
81
+ * Verified against a real parse, not guessed.
82
+ */
83
+ function csharpHasModifier(node, keyword) {
84
+ return namedChildren(node).some(child => child.type === 'modifier' && child.text === keyword);
85
+ }
86
+ /** C/C++ node types wrapping another `declarator` field one level further down — see {@link declaratorName}. */
87
+ const DECLARATOR_WRAPPER_TYPES = new Set([
88
+ 'pointer_declarator',
89
+ 'init_declarator',
90
+ 'array_declarator',
91
+ 'reference_declarator',
92
+ ]);
93
+ /**
94
+ * `parenthesized_declarator`'s inner declarator — unlike every wrapper in {@link DECLARATOR_WRAPPER_TYPES}
95
+ * plus `function_declarator`, its sole child is purely positional, bound to no field at all
96
+ * (`int (*fp)(void);`'s `parenthesized_declarator` wraps a `pointer_declarator` with no `declarator`
97
+ * field to find it through). Verified against a real parse, not guessed.
98
+ */
99
+ function parenthesizedDeclaratorInner(node) {
100
+ // The grammar never produces an empty `parenthesized_declarator` (`()` alone does not parse as one);
101
+ // the fallback only satisfies `namedChildren`'s general array-access return type.
102
+ /* v8 ignore next */
103
+ return namedChildren(node)[0] ?? null;
104
+ }
105
+ /**
106
+ * The terminal name behind a C/C++ declaration's `declarator` field, however many wrapper layers deep —
107
+ * `int *make(int a)`'s `function_definition.declarator` is a `pointer_declarator` wrapping a
108
+ * `function_declarator` wrapping the `identifier` "make"; a plain `int g = 1;` global's `declaration.declarator`
109
+ * is an `init_declarator` wrapping the `identifier` "g" directly; `int (*fp)(void);`'s is a
110
+ * `function_declarator` wrapping a `parenthesized_declarator` wrapping a `pointer_declarator` wrapping
111
+ * the `identifier` "fp". Every wrapper in {@link DECLARATOR_WRAPPER_TYPES}, plus `function_declarator`,
112
+ * exposes the same `declarator` field down to the next layer; `parenthesized_declarator` alone is
113
+ * unwrapped through {@link parenthesizedDeclaratorInner} instead — see there. Stops at an
114
+ * `identifier`/`field_identifier` (the common case), or at a C++ `destructor_name` (`~C`) kept whole
115
+ * rather than unwrapped to its inner `identifier` — unwrapping would make a destructor's name collide
116
+ * with its constructor's plain class name. A shape this doesn't recognize (a C++ operator-overload
117
+ * declarator's `operator_name`, or a function-pointer typedef) returns `undefined` rather than guess a
118
+ * name from it, the same "don't guess" precedent this file already follows for shapes it does not fully
119
+ * resolve.
120
+ * @param node - the declaration node (`function_definition`, `declaration`, `field_declaration`, …).
121
+ * @returns the terminal name node, or `undefined`.
122
+ */
123
+ function declaratorName(node) {
124
+ // `declaration`/`field_declaration` allow a comma-separated multi-declarator list (`int a, b;`) that
125
+ // shares the same repeated `declarator` field on one node — the same ambiguity Go's `const a, b = 1, 2`
126
+ // has on one `const_spec`; `childForFieldName` below would otherwise silently return only the first.
127
+ // `function_definition`'s `declarator` is never repeated, so this is a no-op there.
128
+ if (soleNamedField(node, 'declarator') === undefined)
129
+ return undefined;
130
+ let current = node.childForFieldName('declarator');
131
+ while (current !== null) {
132
+ if (current.type === 'identifier' || current.type === 'field_identifier' || current.type === 'destructor_name')
133
+ return current;
134
+ if (current.type === 'parenthesized_declarator') {
135
+ current = parenthesizedDeclaratorInner(current);
136
+ continue;
137
+ }
138
+ if (current.type !== 'function_declarator' && !DECLARATOR_WRAPPER_TYPES.has(current.type))
139
+ return undefined;
140
+ current = current.childForFieldName('declarator');
141
+ }
142
+ // Every wrapper type reaching this point (`function_declarator` or a `DECLARATOR_WRAPPER_TYPES`
143
+ // member) is required by the grammar to carry its own nested `declarator`; the loop always returns
144
+ // from inside before `current` could become null.
145
+ /* v8 ignore next */
146
+ return undefined;
147
+ }
148
+ /**
149
+ * The matched node's first named child, when it is an `identifier` — see {@link FIRST_CHILD_NAME_FIELD}.
150
+ * Rejects anything else (e.g. a tuple-deconstructing pattern) rather than name a declaration after a
151
+ * shape this package does not attempt to resolve, the same "don't guess" precedent every other
152
+ * unresolved shape in this file already follows.
153
+ */
154
+ function firstChildName(node) {
155
+ const first = namedChildren(node)[0];
156
+ return first?.type === 'identifier' ? first : undefined;
157
+ }
158
+ /**
159
+ * PHP's bare `name` node — either directly (`const_element`'s first named child) or one level deeper
160
+ * inside a `variable_name` wrapper (`property_element`'s and a closure-binding `assignment_expression`'s
161
+ * first named child) — see {@link PHP_ELEMENT_NAME_FIELD}. Rejects anything else (a destructuring
162
+ * `list_literal` target, a member/subscript assignment target) rather than name a declaration after a
163
+ * shape this package does not attempt to resolve, the same "don't guess" precedent every other
164
+ * unresolved shape in this file already follows. Verified against a real parse, not guessed.
165
+ */
166
+ function phpElementName(node) {
167
+ const first = namedChildren(node)[0];
168
+ // Every node this is called on (`const_element`, `property_element`, a value-guarded
169
+ // `assignment_expression`) is required by the grammar to carry at least one named child; the
170
+ // undefined case only satisfies `namedChildren`'s general array-access return type.
171
+ /* v8 ignore next */
172
+ if (first === undefined)
173
+ return undefined;
174
+ if (first.type === 'name')
175
+ return first;
176
+ if (first.type !== 'variable_name')
177
+ return undefined;
178
+ const inner = namedChildren(first)[0];
179
+ // The grammar's `variable_name` rule always wraps exactly one bare `name`; the fallback only satisfies
180
+ // `namedChildren`'s general array-access return type.
181
+ /* v8 ignore next */
182
+ return inner?.type === 'name' ? inner : undefined;
183
+ }
184
+ /**
185
+ * PHP's own modifier host for `keyword`: a class/method/interface/trait/enum declaration carries its
186
+ * `visibility_modifier`/`static_modifier` directly, but a captured `const_element`/`property_element`
187
+ * carries neither — like Java's `variable_declarator` (see {@link javaModifiersNode}), its modifiers sit
188
+ * one level up, on the enclosing `const_declaration`/`property_declaration` this falls back to.
189
+ */
190
+ function phpModifierHost(node) {
191
+ // Every `const_element`/`property_element` the walk visits sits inside a `const_declaration`/
192
+ // `property_declaration`, never at the tree's root; the `?? node` fallback only satisfies `.parent`'s
193
+ // general `SyntaxNode | null` type.
194
+ /* v8 ignore next */
195
+ if (node.type === 'const_element' || node.type === 'property_element')
196
+ return node.parent ?? node;
197
+ return node;
198
+ }
199
+ /** Whether a PHP declaration carries an explicit `public` (or other) `visibility_modifier` — see
200
+ * {@link phpModifierHost}. Verified against a real parse, not guessed. */
201
+ function phpHasVisibility(node, keyword) {
202
+ return namedChildren(phpModifierHost(node)).some(child => child.type === 'visibility_modifier' && child.text === keyword);
203
+ }
204
+ /** Whether a PHP declaration carries a `static_modifier` — see {@link phpModifierHost}. Verified against
205
+ * a real parse, not guessed. */
206
+ function phpHasStaticModifier(node) {
207
+ return namedChildren(phpModifierHost(node)).some(child => child.type === 'static_modifier');
208
+ }
209
+ /**
210
+ * Whether a Rust declaration carries a bare `pub` `visibility_modifier` among its own named children.
211
+ * Every Rust item this package captures places its visibility keyword there directly, unlike Java's
212
+ * wrapping `modifiers` node or C's `storage_class_specifier` — but `pub(crate)`/`pub(super)`/`pub(self)`
213
+ * restrict visibility to inside the crate rather than truly exporting it, so only the bare, unrestricted
214
+ * keyword counts, mirroring Java's/C#'s explicit-`public`-only convention. Verified against a real parse,
215
+ * not guessed.
216
+ */
217
+ function rustIsPublic(node) {
218
+ return namedChildren(node).some(child => child.type === 'visibility_modifier' && child.text === 'pub');
219
+ }
220
+ /**
221
+ * The lone named child bound to `field`, or `undefined` when zero or more than one is bound. A field
222
+ * ordinarily binds exactly one child (a declaration's name); Go's `const a, b = 1, 2` is the exception
223
+ * — both identifiers share the `name` field on one `const_spec` node — and this package extracts a
224
+ * single declared name per definition, never a silently partial pick from an ambiguous group.
225
+ */
226
+ function soleNamedField(node, field) {
227
+ const named = node.childrenForFieldName(field).filter((child) => child !== null && child.isNamed);
228
+ return named.length === 1 ? named[0] : undefined;
229
+ }
230
+ /**
231
+ * A Zig `variable_declaration`'s initializer value — its last named child, when one is present. Neither
232
+ * the declared name nor its value binds to a field of its own (both are purely positional), but an
233
+ * explicit type annotation (`var counter: i32 = 0;`) does bind to a `type` field — when present, the
234
+ * value always follows it, so comparing the last named child against that field tells an initializer
235
+ * apart from a type-only forward declaration with no value at all (`extern var x: i32;`, which this
236
+ * package never sees paired with a captured name in practice, but which `matchDefinition`'s general
237
+ * "don't guess" precedent still guards against misreading as its own type). Verified against a real
238
+ * parse, not guessed.
239
+ */
240
+ function zigDeclarationValue(node) {
241
+ const typeNode = node.childForFieldName('type');
242
+ const last = namedChildren(node).at(-1);
243
+ // The grammar's `variable_declaration` always wraps at least its own declared name as a named child;
244
+ // the undefined case only satisfies `Array.prototype.at`'s general return type.
245
+ /* v8 ignore next */
246
+ if (last === undefined)
247
+ return undefined;
248
+ if (typeNode !== null && last.equals(typeNode))
249
+ return undefined;
250
+ return last;
251
+ }
252
+ /**
253
+ * The seam kind a Zig `variable_declaration` actually reports — `DefinitionRule.kind` is fixed per rule,
254
+ * with no way to vary by a value's shape, so this is computed here instead of in `LANGUAGE_TABLE`. A
255
+ * `struct_declaration`/`union_declaration` value reports `struct` (matching this file's existing
256
+ * union→`struct` precedent for C/C++/Rust); an `enum_declaration` value reports `enum`; anything else
257
+ * falls back to `constant`/`variable` by whether the declaration's own keyword is `const` or `var`.
258
+ * Verified against a real parse, not guessed.
259
+ */
260
+ function zigDeclarationKind(node) {
261
+ const value = zigDeclarationValue(node);
262
+ if (value?.type === 'struct_declaration' || value?.type === 'union_declaration')
263
+ return 'struct';
264
+ if (value?.type === 'enum_declaration')
265
+ return 'enum';
266
+ return hasKeywordChild(node, 'var') ? 'variable' : 'constant';
267
+ }
268
+ /**
269
+ * A Zig `const name = @import("...")` recognized as an import binding — the only way Zig brings in
270
+ * another file or the standard library, spelled as an ordinary builtin-function call bound through the
271
+ * same `variable_declaration` shape `zigDeclarationKind` already classifies as an unremarkable constant
272
+ * (both are recorded — the same "also captured as an ordinary variable" precedent CommonJS's
273
+ * `require()`-bound `const` already sets). Neither `builtin_function` nor its own `arguments` wrapper
274
+ * binds a field of its own; both are purely positional, verified against a real parse, not guessed.
275
+ * @param node - the `variable_declaration` node.
276
+ * @param localName - the declaration's own bound name, already resolved by the caller.
277
+ * @returns the import binding, or `undefined` when `node`'s value is not an `@import(...)` call.
278
+ */
279
+ function zigImportBinding(node, localName) {
280
+ const value = zigDeclarationValue(node);
281
+ if (value?.type !== 'builtin_function')
282
+ return undefined;
283
+ const [builtinIdentifier, argsNode] = namedChildren(value);
284
+ if (builtinIdentifier?.type !== 'builtin_identifier' || builtinIdentifier.text !== '@import')
285
+ return undefined;
286
+ // The grammar's builtin-call syntax always pairs a `builtin_identifier` with an `arguments` node right
287
+ // after it, even when the call has zero arguments (`@import()`); the undefined-return case only
288
+ // satisfies `childForFieldName`'s general return type, verified against a real parse, not guessed.
289
+ /* v8 ignore next */
290
+ if (argsNode?.type !== 'arguments')
291
+ return undefined;
292
+ const args = namedChildren(argsNode);
293
+ const specifierNode = args.length === 1 ? args[0] : undefined;
294
+ if (specifierNode?.type !== 'string')
295
+ return undefined;
296
+ const specifier = namedChildren(specifierNode)[0]?.text ?? specifierNode.text.slice(1, -1);
297
+ return { localName, importedName: '*', specifier };
298
+ }
299
+ /**
300
+ * A Kotlin `class_declaration`/`function_declaration`/`enum_entry`'s declared name — see
301
+ * {@link KOTLIN_NAME_FIELD}. Found by node type among direct named children rather than by position,
302
+ * since an optional leading `modifiers` node (`private fun foo()`'s own wrapping `modifiers` node,
303
+ * itself containing a `visibility_modifier`) would otherwise occupy index 0 ahead of the real name.
304
+ * Verified against a real parse, not guessed.
305
+ */
306
+ function kotlinDeclaredName(node) {
307
+ return namedChildren(node).find(child => child.type === 'simple_identifier' || child.type === 'type_identifier');
308
+ }
309
+ /**
310
+ * The Kotlin `modifiers` node wrapping a declaration's `private`/`internal`/`protected`/`public`
311
+ * keyword (and any annotations), if present — unlike a bare keyword token directly on the declaration
312
+ * itself (see `hasKeywordChild`), Kotlin always wraps its visibility keyword one level down inside a
313
+ * `visibility_modifier` child of this node. Verified against a real parse, not guessed.
314
+ */
315
+ function kotlinModifiersNode(node) {
316
+ return namedChildren(node).find(child => child.type === 'modifiers');
317
+ }
318
+ /** Whether a Kotlin declaration's `modifiers` node (see {@link kotlinModifiersNode}) carries a
319
+ * `visibility_modifier` with the given keyword text. Verified against a real parse, not guessed. */
320
+ function kotlinHasVisibility(node, keyword) {
321
+ const modifiers = kotlinModifiersNode(node);
322
+ if (modifiers === undefined)
323
+ return false;
324
+ return namedChildren(modifiers).some(child => child.type === 'visibility_modifier' && hasKeywordChild(child, keyword));
325
+ }
326
+ /**
327
+ * Whether a Scala declaration's `modifiers` node carries `keyword` (`private`/`protected`) as a bare
328
+ * anonymous child directly — unlike Kotlin's/Swift's own `modifiers` node, Scala's wraps the keyword
329
+ * with no further `visibility_modifier` layer in between. Verified against a real parse, not guessed.
330
+ */
331
+ function scalaHasModifier(node, keyword) {
332
+ const modifiers = namedChildren(node).find(child => child.type === 'modifiers');
333
+ return modifiers !== undefined && hasKeywordChild(modifiers, keyword);
334
+ }
335
+ /**
336
+ * The seam kind a Kotlin `class_declaration` actually reports — `class`, `interface`, and `enum class`
337
+ * all share this one node type, distinguished only by a bare `interface`/`enum` keyword among the
338
+ * declaration's own children (never wrapped, unlike a visibility keyword — see
339
+ * {@link kotlinModifiersNode}); `DefinitionRule.kind` has no way to vary by a keyword the way this
340
+ * needs, so this is computed here instead of in `LANGUAGE_TABLE`, the same "kind computed per node"
341
+ * precedent `zigDeclarationKind` already establishes. Verified against a real parse, not guessed.
342
+ */
343
+ function kotlinClassKind(node) {
344
+ if (hasKeywordChild(node, 'interface'))
345
+ return 'interface';
346
+ if (hasKeywordChild(node, 'enum'))
347
+ return 'enum';
348
+ return 'class';
349
+ }
350
+ /**
351
+ * One Kotlin `delegation_specifier`'s target name, from a `class Foo : Base(), Shape` list — a
352
+ * superclass with an explicit constructor call wraps a `constructor_invocation` around its `user_type`;
353
+ * a bare interface (or an abstract base with no call) is a `user_type` directly. Either way, the target
354
+ * name itself sits one level deeper still, inside the `user_type`'s own `type_identifier`. Kotlin's
355
+ * grammar draws no distinction here between extending a class and implementing an interface — the same
356
+ * ambiguity `pythonClassHeritage`/`baseListHeritage` already document for a comparable shape — so every
357
+ * entry reports `extends`. Verified against a real parse, not guessed.
358
+ * @param delegationSpecifier - the `delegation_specifier` node.
359
+ * @returns the target's simple name, or `undefined` when its shape is not one of the two above.
360
+ */
361
+ function kotlinHeritageTargetName(delegationSpecifier) {
362
+ const first = namedChildren(delegationSpecifier)[0];
363
+ const userType = first?.type === 'constructor_invocation' ? namedChildren(first)[0] : first;
364
+ const typeIdentifier = userType?.type === 'user_type' ? namedChildren(userType)[0] : undefined;
365
+ return typeIdentifier?.type === 'type_identifier' ? typeIdentifier.text : undefined;
366
+ }
367
+ /**
368
+ * Kotlin `class_declaration`/interface heritage extraction from every `delegation_specifier` child —
369
+ * verified against a real parse, not guessed.
370
+ * @param node - the declaring `class_declaration` node.
371
+ * @param sourceKey - the declaring definition's own {@link RawDefinition.key}.
372
+ * @returns every heritage reference the declaration declares.
373
+ */
374
+ function kotlinHeritage(node, sourceKey) {
375
+ return namedChildren(node)
376
+ .filter(child => child.type === 'delegation_specifier')
377
+ .map(child => kotlinHeritageTargetName(child))
378
+ .filter((name) => name !== undefined)
379
+ .map(targetName => ({ sourceKey, targetName, relation: 'extends' }));
380
+ }
381
+ /**
382
+ * A Kotlin `call_expression`'s callee, resolved down to the trailing simple name — `call_expression`
383
+ * binds no field of its own for this grammar (see {@link KOTLIN_NAME_FIELD}'s doc comment), so this is
384
+ * consulted directly by `extractFile` instead of through `LanguageSpec.callFunctionField`, which can
385
+ * never resolve anything here. A bare call (`add(1, 2)`)'s first child is the callee `simple_identifier`
386
+ * directly; a member call (`p.greet()`, `kotlin.math.abs(-1)`)'s first child is a `navigation_expression`
387
+ * instead — its own direct `navigation_suffix` child (never itself nested, even when the receiver chain
388
+ * is: `a.b.c()`'s outer `navigation_expression` wraps an inner `navigation_expression` as one child and
389
+ * this same node's own trailing `navigation_suffix` as the other, so reading only the outermost level
390
+ * always reaches the correct final segment regardless of chain depth) wraps the trailing
391
+ * `simple_identifier` this resolves to. Verified against a real parse, not guessed.
392
+ * @param node - the `call_expression` node.
393
+ * @returns the callee's simple-name node, or `undefined` when its shape is not one of the two above.
394
+ */
395
+ function kotlinCallee(node) {
396
+ const first = namedChildren(node)[0];
397
+ // The grammar's `call_expression` always wraps a callee expression as its first named child; the
398
+ // undefined case only satisfies `Array.prototype.at`-style general array-access return type.
399
+ /* v8 ignore next */
400
+ if (first === undefined)
401
+ return undefined;
402
+ if (first.type !== 'navigation_expression')
403
+ return first;
404
+ const suffix = namedChildren(first).find(child => child.type === 'navigation_suffix');
405
+ // A `navigation_expression` always wraps a trailing `navigation_suffix` naming the accessed member
406
+ // (`.foo`, `?.foo`, even off `super`) — verified against a real parse across a plain member access, a
407
+ // safe-nav access, and a `super` receiver; the undefined case only satisfies `Array.prototype.find`'s
408
+ // general return type.
409
+ /* v8 ignore next */
410
+ return suffix === undefined ? undefined : namedChildren(suffix).find(child => child.type === 'simple_identifier');
411
+ }
412
+ /**
413
+ * Kotlin `import_header` extraction — a plain import (`import kotlin.math.abs`) wraps its dotted path
414
+ * in a bare `identifier`, whose own last `simple_identifier` segment is the imported symbol; a wildcard
415
+ * import (`import kotlin.collections.*`) pairs that same `identifier` prefix with a sibling
416
+ * `wildcard_import` marker instead; an aliased import (`import java.util.List as JList`) adds a sibling
417
+ * `import_alias` wrapping the alias `type_identifier`. Neither `identifier` nor `import_alias` binds a
418
+ * field of its own. Verified against a real parse, not guessed.
419
+ * @param node - the `import_header` node.
420
+ * @returns the single import binding this statement introduces.
421
+ */
422
+ function kotlinImports(node) {
423
+ const children = namedChildren(node);
424
+ const path = children.find(child => child.type === 'identifier');
425
+ // `import_header` always wraps a dotted path per the grammar (a bare `import;` does not parse); the
426
+ // empty-array case only satisfies `Array.prototype.find`'s general return type.
427
+ /* v8 ignore next */
428
+ if (path === undefined)
429
+ return [];
430
+ const specifier = path.text;
431
+ if (children.some(child => child.type === 'wildcard_import'))
432
+ return [{ localName: '', importedName: '*', specifier }];
433
+ const segments = namedChildren(path).filter(child => child.type === 'simple_identifier');
434
+ const importedName = segments.at(-1)?.text;
435
+ // The grammar's `identifier` rule always wraps at least one `simple_identifier` segment; the
436
+ // empty-array case only satisfies `Array.prototype.at`'s general return type.
437
+ /* v8 ignore next */
438
+ if (importedName === undefined)
439
+ return [];
440
+ const alias = children.find(child => child.type === 'import_alias');
441
+ // The grammar's `import_alias` rule always wraps exactly one `type_identifier`; the fallback only
442
+ // satisfies `namedChildren`'s general array-access return type.
443
+ /* v8 ignore next */
444
+ const localName = alias === undefined ? importedName : (namedChildren(alias)[0]?.text ?? importedName);
445
+ return [{ localName, importedName, specifier }];
446
+ }
447
+ /**
448
+ * A Swift `property_declaration`'s declared name — see {@link SWIFT_PROPERTY_NAME_FIELD}. Its own
449
+ * `name` field points to a `pattern` node, not the identifier itself; the identifier is one level
450
+ * deeper, bound to that `pattern`'s own `bound_identifier` field. Verified against a real parse, not
451
+ * guessed.
452
+ */
453
+ function swiftPropertyName(node) {
454
+ const pattern = node.childForFieldName('name');
455
+ // The grammar's `property_declaration` always binds its `name` field, even for a destructuring pattern
456
+ // (`let (a, b) = ...`, whose `pattern` still binds the field, just with no `bound_identifier` of its
457
+ // own — see the fallback below); the undefined case only satisfies `childForFieldName`'s general
458
+ // return type, verified against a real parse, not guessed.
459
+ /* v8 ignore next */
460
+ if (pattern === null)
461
+ return undefined;
462
+ const bound = pattern.childForFieldName('bound_identifier');
463
+ return bound?.type === 'simple_identifier' ? bound : undefined;
464
+ }
465
+ /**
466
+ * A Swift `function_declaration`/`protocol_function_declaration`'s declared name — see
467
+ * {@link SWIFT_FUNCTION_NAME_FIELD}. Uses `childForFieldName` (first match, document order) rather than
468
+ * `soleNamedField` (all matches, requiring exactly one): a return-typed function binds both its name
469
+ * and its `return_type` node to this same `name` field in this grammar build, and the name always comes
470
+ * first syntactically (`func f() -> Int` never writes the return type before the identifier), so the
471
+ * first match is always correct. Verified against a real parse, not guessed.
472
+ */
473
+ function swiftFunctionName(node) {
474
+ const name = node.childForFieldName('name');
475
+ return name?.type === 'simple_identifier' ? name : undefined;
476
+ }
477
+ /** Whether a Swift `property_declaration` was declared with `var` (mutable) rather than `let` —
478
+ * the keyword sits as a bare anonymous token on the declaration's own `value_binding_pattern` child,
479
+ * itself one level down from `property_declaration` directly. Verified against a real parse, not
480
+ * guessed. */
481
+ function swiftIsVar(node) {
482
+ const binding = namedChildren(node).find(child => child.type === 'value_binding_pattern');
483
+ return binding !== undefined && hasKeywordChild(binding, 'var');
484
+ }
485
+ /**
486
+ * The seam kind a Swift `class_declaration`/`property_declaration` actually reports —
487
+ * `DefinitionRule.kind` has no way to vary by a field's value or a keyword the way either needs, so
488
+ * this is computed here instead of in `LANGUAGE_TABLE`, the same "kind computed per node" precedent
489
+ * Zig's `variable_declaration`/Kotlin's `class_declaration` entries already establish. A
490
+ * `class_declaration`'s own `declaration_kind` field value node's type is directly `struct`/`class`/
491
+ * `enum` — an unusual shape (most grammars this package extracts from leave a keyword as a bare
492
+ * anonymous token, but this one promotes it to its own field value) verified against a real parse, not
493
+ * guessed. A `property_declaration` reports `field` when it is a class/struct member (inspecting the
494
+ * node's actual parent, not which of the two Swift `property_declaration` rules fired, so this gives
495
+ * the right answer regardless), or `constant`/`variable` by {@link swiftIsVar} otherwise.
496
+ */
497
+ function swiftDeclarationKind(node) {
498
+ if (node.type === 'class_declaration') {
499
+ const kind = node.childForFieldName('declaration_kind')?.type;
500
+ return kind === 'struct' || kind === 'class' || kind === 'enum' ? kind : 'class';
501
+ }
502
+ if (node.parent?.type === 'class_body')
503
+ return 'field';
504
+ return swiftIsVar(node) ? 'variable' : 'constant';
505
+ }
506
+ /**
507
+ * Swift `class_declaration`/`protocol_declaration` heritage extraction from every `inheritance_specifier`
508
+ * child's `inherits_from` field — Swift draws no distinction between extending a class and conforming to
509
+ * a protocol in this same colon-separated list, the same ambiguity `pythonClassHeritage`/
510
+ * `baseListHeritage`/`kotlinHeritage` already document for a comparable shape, so every entry reports
511
+ * `extends`. Verified against a real parse, not guessed.
512
+ * @param node - the declaring `class_declaration`/`protocol_declaration` node.
513
+ * @param sourceKey - the declaring definition's own {@link RawDefinition.key}.
514
+ * @returns every heritage reference the declaration declares.
515
+ */
516
+ function swiftHeritage(node, sourceKey) {
517
+ return namedChildren(node)
518
+ .filter(child => child.type === 'inheritance_specifier')
519
+ .map((child) => {
520
+ const userType = child.childForFieldName('inherits_from');
521
+ // `inherits_from` always resolves to a `user_type` whose first child is a `type_identifier` for
522
+ // every shape this has been checked against — a plain base, a `&`-composed protocol list (each
523
+ // conjunct its own `inheritance_specifier`), and a module-qualified name (`Swift.Equatable`, whose
524
+ // `user_type` still starts with a `type_identifier`, just the qualifier segment rather than the
525
+ // one this package would ideally resolve to — a known, accepted imprecision, not a crash). Neither
526
+ // fallback is known to be reachable; both exist only to satisfy the general return type of
527
+ // `childForFieldName`/array access rather than guess a name for a shape not yet seen.
528
+ /* v8 ignore next */
529
+ const typeIdentifier = userType?.type === 'user_type' ? namedChildren(userType)[0] : undefined;
530
+ /* v8 ignore next */
531
+ return typeIdentifier?.type === 'type_identifier' ? typeIdentifier.text : undefined;
532
+ })
533
+ .filter((name) => name !== undefined)
534
+ .map(targetName => ({ sourceKey, targetName, relation: 'extends' }));
535
+ }
536
+ /**
537
+ * A Swift `call_expression`'s callee, resolved down to the trailing simple name — `call_expression`
538
+ * binds no field of its own for its callee (see `LANGUAGE_TABLE`'s `swift` entry), so this is consulted
539
+ * directly by `extractFile` instead of through `LanguageSpec.callFunctionField`. A bare call
540
+ * (`add(1, 2)`)'s first child is the callee `simple_identifier` directly; a member call (`p.length()`)'s
541
+ * first child is a `navigation_expression` instead, whose own `suffix` field gives a `navigation_suffix`
542
+ * node, itself carrying the trailing identifier through its own `suffix` field in turn — reading only
543
+ * this outermost level reaches the correct final segment regardless of a longer dotted chain, the same
544
+ * "outermost level is enough" shape `kotlinCallee` already documents. Verified against a real parse, not
545
+ * guessed.
546
+ * @param node - the `call_expression` node.
547
+ * @returns the callee's simple-name node, or `undefined` when its shape is not one of the two above.
548
+ */
549
+ function swiftCallee(node) {
550
+ const first = namedChildren(node)[0];
551
+ // The grammar's `call_expression` always wraps a callee expression as its first named child; the
552
+ // undefined case only satisfies `Array.prototype.at`-style general array-access return type.
553
+ /* v8 ignore next */
554
+ if (first === undefined)
555
+ return undefined;
556
+ if (first.type !== 'navigation_expression')
557
+ return first;
558
+ const navigationSuffix = first.childForFieldName('suffix');
559
+ const trailing = navigationSuffix?.childForFieldName('suffix');
560
+ // A `navigation_expression`'s own `suffix` field always resolves to a `navigation_suffix` carrying a
561
+ // trailing `suffix` of its own in turn (`.foo`, `?.foo`, even off `super`) — verified against a real
562
+ // parse across a plain member access, a safe-nav access, and a `super` receiver; the undefined case
563
+ // only satisfies `childForFieldName`'s general return type.
564
+ /* v8 ignore next */
565
+ return trailing === null ? undefined : trailing;
566
+ }
567
+ /**
568
+ * Swift `import_declaration` extraction — a whole-module import (`import Foundation`) wraps its dotted
569
+ * path in a bare `identifier`, whose own last `simple_identifier` segment is the module's own simple
570
+ * name; like a Go package import, it binds no individual symbol this package tracks by name, so the
571
+ * local name recorded is that same trailing segment (matching `goImports`'s own convention for a
572
+ * whole-package binding) rather than the empty-string, side-effect-only marker a `#include`/`require`
573
+ * binding uses elsewhere in this file. Verified against a real parse, not guessed.
574
+ * @param node - the `import_declaration` node.
575
+ * @returns the single import binding this statement introduces.
576
+ */
577
+ function swiftImports(node) {
578
+ const path = namedChildren(node).find(child => child.type === 'identifier');
579
+ // `import_declaration` always wraps a dotted path per the grammar; the empty-array case only
580
+ // satisfies `Array.prototype.find`'s general return type.
581
+ /* v8 ignore next */
582
+ if (path === undefined)
583
+ return [];
584
+ const specifier = path.text;
585
+ const segments = namedChildren(path).filter(child => child.type === 'simple_identifier');
586
+ const localName = segments.at(-1)?.text;
587
+ // The grammar's `identifier` rule always wraps at least one `simple_identifier` segment; the
588
+ // empty-array case only satisfies `Array.prototype.at`'s general return type.
589
+ /* v8 ignore next */
590
+ if (localName === undefined)
591
+ return [];
592
+ return [{ localName, importedName: '*', specifier }];
593
+ }
594
+ /**
595
+ * A Dart `declaration`'s field name — see {@link DART_FIELD_NAME_FIELD}. Only the single-identifier
596
+ * `initialized_identifier_list` shape is named; a constructor-wrapping `declaration` (no
597
+ * `initialized_identifier_list` child at all) and a multi-declare `int x, y;` (more than one
598
+ * `initialized_identifier`) both return `undefined` rather than guess a name for a shape this package
599
+ * does not attempt to resolve. Verified against a real parse, not guessed.
600
+ */
601
+ function dartFieldName(node) {
602
+ const list = namedChildren(node).find(child => child.type === 'initialized_identifier_list');
603
+ if (list === undefined)
604
+ return undefined;
605
+ const items = namedChildren(list).filter(child => child.type === 'initialized_identifier');
606
+ const sole = items.length === 1 ? items[0] : undefined;
607
+ if (sole === undefined)
608
+ return undefined;
609
+ const name = namedChildren(sole)[0];
610
+ // The grammar's `initialized_identifier` always wraps its own name as an `identifier` first child,
611
+ // with an optional initializer expression only ever following it (`int x = 5;` still binds `x` first)
612
+ // — verified against a real parse, not guessed; the undefined case only satisfies
613
+ // `Array.prototype.at`-style general array-access return type.
614
+ /* v8 ignore next */
615
+ return name?.type === 'identifier' ? name : undefined;
616
+ }
617
+ /**
618
+ * Dart `class_definition` heritage extraction from its `superclass` field (`extends Base`, wrapping the
619
+ * base type directly) and `interfaces` field (`implements Shape, Other` — a single wrapper node holding
620
+ * one `type_identifier` per implemented interface as its own direct children, verified against a real
621
+ * parse). Dart draws no distinction here that this package resolves beyond `extends`/`implements`
622
+ * themselves, so a `superclass` entry reports `extends` and each `interfaces` entry reports
623
+ * `implements`.
624
+ * @param node - the declaring `class_definition` node.
625
+ * @param sourceKey - the declaring definition's own {@link RawDefinition.key}.
626
+ * @returns every heritage reference the class declares.
627
+ */
628
+ function dartHeritage(node, sourceKey) {
629
+ const refs = [];
630
+ const superclass = node.childForFieldName('superclass');
631
+ const base = superclass === null ? undefined : namedChildren(superclass)[0];
632
+ // Every `superclass` shape checked against a real parse — a plain base, a module-qualified one
633
+ // (`lib.Base`), and a generic one (`Generic<int>`) — puts a heritage-name-shaped node first; the false
634
+ // case exists only as this file's usual "don't guess" guard for a shape not yet seen, matching
635
+ // `isHeritageName`'s other call sites.
636
+ /* v8 ignore next */
637
+ if (base !== undefined && isHeritageName(base))
638
+ refs.push({ sourceKey, targetName: base.text, relation: 'extends' });
639
+ const interfaces = node.childForFieldName('interfaces');
640
+ if (interfaces !== null) {
641
+ // An `implements` clause always lists at least one target per the grammar (`implements` with an
642
+ // empty list does not parse); the zero-iteration case only satisfies the general shape of iterating
643
+ // an array that could in principle be empty.
644
+ /* v8 ignore next */
645
+ for (const target of namedChildren(interfaces)) {
646
+ if (isHeritageName(target))
647
+ refs.push({ sourceKey, targetName: target.text, relation: 'implements' });
648
+ }
649
+ }
650
+ return refs;
651
+ }
652
+ /**
653
+ * Dart `import_specification` extraction — the URI sits three levels down, in a `configurable_uri` →
654
+ * `uri` → `string_literal` chain, none of them bound to a field of their own; an `as` alias adds a
655
+ * sibling `identifier`. This grammar's `string_literal` carries no separate content child (unlike
656
+ * ECMAScript's `string_fragment`/PHP's escaped content) — its text is read directly off the quoted node
657
+ * itself, stripping the surrounding quote characters. Verified against a real parse, not guessed.
658
+ * @param node - the `import_specification` node.
659
+ * @returns the single import binding this statement introduces.
660
+ */
661
+ function dartImports(node) {
662
+ // Every `import_specification` shape checked against a real parse — a plain import, an aliased one,
663
+ // and a conditional import (`if (dart.library.html) '...'`, whose primary `uri` sits alongside rather
664
+ // than inside the conditional part) — resolves this same `configurable_uri` → `uri` → `string_literal`
665
+ // chain; each undefined/type-mismatch fallback below only satisfies `Array.prototype.find`'s and
666
+ // `childForFieldName`'s general return types, not a shape actually seen.
667
+ /* v8 ignore next */
668
+ const configurableUri = namedChildren(node).find(child => child.type === 'configurable_uri');
669
+ /* v8 ignore next */
670
+ const uri = configurableUri === undefined ? undefined : namedChildren(configurableUri).find(child => child.type === 'uri');
671
+ /* v8 ignore next */
672
+ const stringLiteral = uri === undefined ? undefined : namedChildren(uri)[0];
673
+ /* v8 ignore next */
674
+ if (stringLiteral?.type !== 'string_literal')
675
+ return [];
676
+ const specifier = stringLiteral.text.slice(1, -1);
677
+ const alias = namedChildren(node).find(child => child.type === 'identifier');
678
+ return [{ localName: alias?.text ?? '', importedName: '*', specifier }];
679
+ }
680
+ /**
681
+ * Scala `import_declaration` extraction, covering both its shapes: a plain dotted path (`import
682
+ * scala.math.abs`, whose `path` field's own trailing `identifier` descendant is the imported symbol),
683
+ * and a selector list (`import scala.collection.{List, Map}`, whose sibling `import_selectors` node
684
+ * binds each imported name directly). An `import_selectors` entry renamed with `=>` or a wildcard `_`
685
+ * import is not named here — the same "don't guess" precedent this file already follows for a shape it
686
+ * does not fully resolve; every `import_selectors` child this does not recognize is simply filtered out
687
+ * rather than mis-read. Verified against a real parse, not guessed.
688
+ * @param node - the `import_declaration` node.
689
+ * @returns every import binding this statement introduces.
690
+ */
691
+ function scalaImports(node) {
692
+ const path = node.childForFieldName('path');
693
+ // `import_declaration` always binds a `path` field — verified against a real parse for a single
694
+ // bare-name import, a dotted one, and a comma-separated multi-import (each still binds its own `path`);
695
+ // the empty-array case only satisfies `childForFieldName`'s general return type.
696
+ /* v8 ignore next */
697
+ if (path === null)
698
+ return [];
699
+ const prefix = path.text;
700
+ const selectors = namedChildren(node).find(child => child.type === 'import_selectors');
701
+ if (selectors === undefined) {
702
+ const last = descendantsOfType(path, ['identifier']).at(-1);
703
+ // The grammar's `stable_identifier`/`identifier` path always wraps at least one `identifier`
704
+ // segment; the empty-array case only satisfies `Array.prototype.at`'s general return type.
705
+ /* v8 ignore next */
706
+ if (last === undefined)
707
+ return [];
708
+ return [{ localName: last.text, importedName: last.text, specifier: prefix }];
709
+ }
710
+ return namedChildren(selectors)
711
+ .filter(child => child.type === 'identifier')
712
+ .map(name => ({ localName: name.text, importedName: name.text, specifier: `${prefix}.${name.text}` }));
713
+ }
39
714
  /** The definition rule matching `node`, or `undefined` when it introduces no declaration. */
40
715
  function matchDefinition(node, definitions) {
41
716
  for (const rule of definitions) {
42
717
  if (node.type !== rule.nodeType)
43
718
  continue;
719
+ if (rule.parentType !== undefined && node.parent?.type !== rule.parentType)
720
+ continue;
721
+ if (rule.grandparentType !== undefined && node.parent?.parent?.type !== rule.grandparentType)
722
+ continue;
44
723
  if (rule.value !== undefined) {
45
724
  const value = node.childForFieldName(rule.value.field);
46
725
  if (value === null || !rule.value.types.includes(value.type))
47
726
  continue;
48
727
  }
728
+ const nameNode = rule.nameField === SELF_NAME_FIELD ? node
729
+ : rule.nameField === DECLARATOR_NAME_FIELD ? declaratorName(node)
730
+ : rule.nameField === FIRST_CHILD_NAME_FIELD ? firstChildName(node)
731
+ : rule.nameField === PHP_ELEMENT_NAME_FIELD ? phpElementName(node)
732
+ : rule.nameField === KOTLIN_NAME_FIELD ? kotlinDeclaredName(node)
733
+ : rule.nameField === SWIFT_PROPERTY_NAME_FIELD ? swiftPropertyName(node)
734
+ : rule.nameField === SWIFT_FUNCTION_NAME_FIELD ? swiftFunctionName(node)
735
+ : rule.nameField === DART_FIELD_NAME_FIELD ? dartFieldName(node)
736
+ : soleNamedField(node, rule.nameField);
737
+ if (nameNode === undefined)
738
+ continue;
739
+ if (rule.nameNodeTypes !== undefined && !rule.nameNodeTypes.includes(nameNode.type))
740
+ continue;
49
741
  return rule;
50
742
  }
51
743
  return undefined;
@@ -60,15 +752,136 @@ function matchDefinition(node, definitions) {
60
752
  * computed or parenthesized callee, for instance).
61
753
  */
62
754
  function calleeName(callee) {
755
+ // Rust's turbofish call (`collect::<Vec<_>>()`) wraps the real callee expression in its own
756
+ // `generic_function` node, pairing a `function` field with a sibling `type_arguments` field for the
757
+ // explicit generics — unwrap to the inner expression and resolve it the same way as any other call.
758
+ // Verified against a real parse, not guessed.
759
+ if (callee.type === 'generic_function') {
760
+ const inner = callee.childForFieldName('function');
761
+ // `generic_function` always binds a `function` field per the grammar; the undefined case only
762
+ // satisfies `childForFieldName`'s general return type.
763
+ /* v8 ignore next */
764
+ return inner === null ? undefined : calleeName(inner);
765
+ }
63
766
  if (callee.type === 'identifier')
64
767
  return callee.text;
768
+ // Kotlin's bare-name node type — `kotlinCallee` already resolves a call's callee (bare or member) down
769
+ // to this node type directly before `calleeName` ever sees it. Verified against a real parse.
770
+ if (callee.type === 'simple_identifier')
771
+ return callee.text;
772
+ // PHP's bare-name node type — the resolved `callFunctionFieldByType` field value for all three of its
773
+ // call shapes (`function_call_expression`'s simple case, `member_call_expression`'s and
774
+ // `scoped_call_expression`'s method name) is this node type directly, never wrapped in a further
775
+ // field. Verified against a real parse, not guessed.
776
+ if (callee.type === 'name')
777
+ return callee.text;
778
+ // PHP's namespaced free-function call (`Foo\bar()`) — `qualified_name` binds neither its namespace
779
+ // prefix nor its final segment to a field of its own (unlike C++'s `qualified_identifier`, below); the
780
+ // final segment is always its last named child. Verified against a real parse, not guessed.
781
+ if (callee.type === 'qualified_name') {
782
+ const last = namedChildren(callee).at(-1);
783
+ // The grammar's `qualified_name` rule always wraps at least one final segment of this type; the
784
+ // fallback only satisfies `Array.prototype.at`'s general return type.
785
+ /* v8 ignore next */
786
+ return last?.type === 'name' ? last.text : undefined;
787
+ }
788
+ // `name` covers C++'s `qualified_identifier` (`ns::func`), C#'s `member_access_expression`
789
+ // (`obj.Method()`), and Rust's `scoped_identifier` (`Type::method()`, `std::mem::swap()`) — no other
790
+ // call-callee shape in this file's language tables binds a field called `name` for anything else,
791
+ // and Rust's `field_expression` (`obj.method()`) binds `field` instead, already covered below.
792
+ // `member` covers Zig's own `field_expression` (`obj.method()`, `Point.init()`) instead — Zig's
793
+ // grammar names this field `member`, not `field`, the only grammar in this file's tables to do so.
794
+ // Verified against a real parse, not guessed.
65
795
  const property = callee.childForFieldName('property')
66
796
  ?? callee.childForFieldName('field')
67
- ?? callee.childForFieldName('attribute');
797
+ ?? callee.childForFieldName('member')
798
+ ?? callee.childForFieldName('attribute')
799
+ ?? callee.childForFieldName('name');
68
800
  if (property !== null && property.type !== 'computed_property_name')
69
801
  return property.text;
70
802
  return undefined;
71
803
  }
804
+ /** Seam language labels using the ECMAScript-family grammars, for CommonJS `require`/export detection. */
805
+ const ECMASCRIPT_LANGUAGES = new Set(['typescript', 'tsx', 'javascript', 'jsx']);
806
+ /** Shared empty set for a non-ECMAScript file, which never has CommonJS export assignments to find. */
807
+ const EMPTY_NAME_SET = new Set();
808
+ /**
809
+ * A CommonJS `require('./foo')` call recognized as an import binding: `const foo = require('./foo')`
810
+ * binds `foo`; a bare `require('./foo')` statement imports for its side effect only. Any other
811
+ * position (a sub-expression, immediate member access on the call result) is not a binding this
812
+ * package attempts to name — the same "don't guess a name" precedent `ecmascriptImports`/
813
+ * `pythonBinding` already follow for shapes they do not fully resolve. Without this, CommonJS code
814
+ * (still common outside pure-ESM projects) parses `require` as an ordinary, always-unresolved call
815
+ * and the workspace gets no `imports` edge for it at all.
816
+ * @param node - a `call_expression` node.
817
+ * @returns the import binding, or `undefined` when `node` is not a bare top-level `require(...)` call.
818
+ */
819
+ function commonJsRequireImport(node) {
820
+ const callee = node.childForFieldName('function');
821
+ // A call node's `function` field is required by the grammar; the null case only satisfies
822
+ // `childForFieldName`'s general return type.
823
+ /* v8 ignore next */
824
+ if (callee === null)
825
+ return undefined;
826
+ if (callee.type !== 'identifier' || callee.text !== 'require')
827
+ return undefined;
828
+ const argsNode = node.childForFieldName('arguments');
829
+ // Likewise required by the grammar, present (empty) even for a zero-argument call.
830
+ /* v8 ignore next */
831
+ if (argsNode === null)
832
+ return undefined;
833
+ const args = namedChildren(argsNode);
834
+ const specifierNode = args.length === 1 ? args[0] : undefined;
835
+ if (specifierNode?.type !== 'string')
836
+ return undefined;
837
+ const specifier = namedChildren(specifierNode)[0]?.text ?? specifierNode.text.slice(1, -1);
838
+ const parent = node.parent;
839
+ if (parent?.type === 'variable_declarator') {
840
+ const name = parent.childForFieldName('name');
841
+ if (name?.type !== 'identifier')
842
+ return undefined;
843
+ return { localName: name.text, importedName: '*', specifier };
844
+ }
845
+ if (parent?.type === 'expression_statement')
846
+ return { localName: '', importedName: '*', specifier };
847
+ return undefined;
848
+ }
849
+ /**
850
+ * A Ruby `require '...'`/`require_relative '...'` call recognized as an import binding — Ruby's
851
+ * grammar has no dedicated import-statement node type at all (see `LANGUAGE_TABLE`'s `ruby` entry),
852
+ * so this dispatches off an ordinary `call` node the same way `commonJsRequireImport` does for
853
+ * CommonJS. Binds no local name — like a C `#include` or a Go whole-package import, `require` pulls
854
+ * in a file for its side effects, not a single symbol this package tracks by name. Verified against a
855
+ * real parse, not guessed.
856
+ * @param node - a `call` node.
857
+ * @returns the import binding, or `undefined` when `node` is not a bare top-level `require`/
858
+ * `require_relative` call.
859
+ */
860
+ function rubyRequireImport(node) {
861
+ const method = node.childForFieldName('method');
862
+ // A `call` node's `method` field always resolves to a plain `identifier` regardless of receiver shape
863
+ // — verified against a real parse for a bare call, a receiver call (`obj.require(...)`), a scope-
864
+ // resolution call (`Foo::require(...)`), and a safe-nav call (`obj&.require(...)`), every one still
865
+ // binding `method` to the same `identifier` node type; neither fallback below is known to be reachable.
866
+ /* v8 ignore next */
867
+ if (method === null || method.type !== 'identifier')
868
+ return undefined;
869
+ if (method.text !== 'require' && method.text !== 'require_relative')
870
+ return undefined;
871
+ const argsNode = node.childForFieldName('arguments');
872
+ // A `call` node always binds an `arguments` field, even to an empty `argument_list` for a call with no
873
+ // arguments at all — verified against a real parse; a bare `require` with neither parentheses nor
874
+ // arguments parses as a plain `identifier`, never reaching a `call` node in the first place.
875
+ /* v8 ignore next */
876
+ if (argsNode === null)
877
+ return undefined;
878
+ const args = namedChildren(argsNode);
879
+ const specifierNode = args.length === 1 ? args[0] : undefined;
880
+ if (specifierNode?.type !== 'string')
881
+ return undefined;
882
+ const specifier = namedChildren(specifierNode)[0]?.text ?? specifierNode.text.slice(1, -1);
883
+ return { localName: '', importedName: '*', specifier };
884
+ }
72
885
  /** ECMAScript-family import extraction: `import_statement` with a `import_clause` and a `source`. */
73
886
  function ecmascriptImports(node) {
74
887
  // `source` is required by the grammar; the null case only satisfies `childForFieldName`'s general
@@ -116,6 +929,60 @@ function ecmascriptImports(node) {
116
929
  }
117
930
  return imports;
118
931
  }
932
+ /**
933
+ * One Python `decorator` node's name — a bare `@staticmethod` names its own `identifier`; `@app.route`
934
+ * names its dotted `attribute` verbatim (not just the rightmost segment, unlike `calleeName`'s call-site
935
+ * ambiguity — a decorator name is metadata, not something this package resolves, so there is no reason
936
+ * to throw away the qualifying prefix); `@app.route('/x')` first unwraps the `call` to its `function`
937
+ * field, then applies the same rule. Any other shape (e.g. a subscript, `@decorators[0]`) is not named —
938
+ * the "don't guess" precedent this file already follows elsewhere.
939
+ * @param decorator - the `decorator` node.
940
+ * @returns the decorator's name, or `undefined` when its expression is not identifier/attribute-shaped.
941
+ */
942
+ function pythonDecoratorName(decorator) {
943
+ let expr = namedChildren(decorator)[0];
944
+ // A decorator's expression is required by the grammar (`@` alone does not parse); the undefined case
945
+ // only satisfies `namedChildren`'s general array-access return type.
946
+ /* v8 ignore next */
947
+ if (expr === undefined)
948
+ return undefined;
949
+ if (expr.type === 'call') {
950
+ // A call node's `function` field is required by the grammar; the null case only satisfies
951
+ // `childForFieldName`'s general return type.
952
+ const callee = expr.childForFieldName('function');
953
+ /* v8 ignore next */
954
+ if (callee === null)
955
+ return undefined;
956
+ expr = callee;
957
+ }
958
+ if (expr.type === 'identifier' || expr.type === 'attribute')
959
+ return expr.text;
960
+ return undefined;
961
+ }
962
+ /**
963
+ * Every decorator name applied to a Python `function_definition`/`class_definition`, from its enclosing
964
+ * `decorated_definition`'s `decorator` children (a decorated declaration is wrapped one level up by the
965
+ * grammar, not marked on the declaration node itself — verified against a real parse, not guessed).
966
+ * Empty when the language is not Python or the declaration is undecorated.
967
+ * @param node - the `function_definition`/`class_definition` node.
968
+ * @param language - the seam language label the file was parsed as.
969
+ * @returns every decorator name applied, outermost first.
970
+ */
971
+ function pythonDecorators(node, language) {
972
+ if (language !== 'python')
973
+ return [];
974
+ if (node.parent?.type !== 'decorated_definition')
975
+ return [];
976
+ const names = [];
977
+ for (const child of namedChildren(node.parent)) {
978
+ if (child.type !== 'decorator')
979
+ continue;
980
+ const name = pythonDecoratorName(child);
981
+ if (name !== undefined)
982
+ names.push(name);
983
+ }
984
+ return names;
985
+ }
119
986
  /**
120
987
  * One `dotted_name` or `aliased_import` child of a Python import statement, resolved to a binding.
121
988
  * @param child - the `dotted_name` or `aliased_import` node.
@@ -151,10 +1018,14 @@ function pythonImports(node) {
151
1018
  // by type from an imported symbol written the same way (`from x import y`).
152
1019
  .filter(child => !(moduleNode !== null && child.equals(moduleNode)))
153
1020
  .filter(child => child.type === 'dotted_name' || child.type === 'aliased_import' || child.type === 'wildcard_import')
154
- .flatMap((child) => {
1021
+ .map((child) => {
1022
+ // `from x import *` binds no individual symbol this package tracks by name, but the module
1023
+ // itself is still imported — recording it (with no `localName`, `importedName: '*'`, matching
1024
+ // the namespace-import convention `pythonBinding` already uses) keeps the `imports` edge to
1025
+ // `specifier` instead of silently dropping the statement.
155
1026
  if (child.type === 'wildcard_import')
156
- return [];
157
- return [pythonBinding(child, true, specifier)];
1027
+ return { localName: '', importedName: '*', specifier };
1028
+ return pythonBinding(child, true, specifier);
158
1029
  });
159
1030
  }
160
1031
  /** Go import extraction: `import_declaration` wraps one or more `import_spec` nodes, each requiring a `path`. */
@@ -174,6 +1045,571 @@ function goImports(node) {
174
1045
  return { localName: local, importedName: '*', specifier: raw };
175
1046
  });
176
1047
  }
1048
+ /**
1049
+ * Java import extraction: `import_declaration` wraps a `scoped_identifier` (a dotted path,
1050
+ * `java.util.List`) or, for a single-segment specifier, a bare `identifier`, plus an optional
1051
+ * trailing `asterisk` child for a wildcard import (`import java.util.*;`). The `static` keyword
1052
+ * (`import static java.lang.Math.max;`) is an unnamed token the grammar tacks onto the same shape, so
1053
+ * no separate handling is needed — the specifier and local name below are extracted identically
1054
+ * either way. Verified against a real parse, not guessed.
1055
+ * @param node - the `import_declaration` node.
1056
+ * @returns the single import binding this statement introduces.
1057
+ */
1058
+ function javaImports(node) {
1059
+ const children = namedChildren(node);
1060
+ const path = children.find(child => child.type === 'scoped_identifier' || child.type === 'identifier');
1061
+ // `import_declaration` always wraps one of these two node types per the grammar; the undefined case
1062
+ // only satisfies `Array.prototype.find`'s general return type.
1063
+ /* v8 ignore next */
1064
+ if (path === undefined)
1065
+ return [];
1066
+ const specifier = path.text;
1067
+ // A package import binds no individual symbol; resolution never matches on `importedName: '*'`,
1068
+ // matching `goImports`'s same precedent for a whole-package/wildcard binding.
1069
+ if (children.some(child => child.type === 'asterisk'))
1070
+ return [{ localName: '', importedName: '*', specifier }];
1071
+ const localName = path.type === 'scoped_identifier' ? fieldText(path, 'name', specifier) : specifier;
1072
+ return [{ localName, importedName: '*', specifier }];
1073
+ }
1074
+ /**
1075
+ * C/C++ `#include` extraction. A system include (`#include <stdio.h>`) parses as a `system_lib_string`
1076
+ * token holding the whole `<...>` text; a local include (`#include "local.h"`) wraps a `string_literal`
1077
+ * around a `string_content` child holding the bare path. Neither binds an individual symbol this
1078
+ * package tracks by name — like a Go whole-package import, it is recorded for completeness with no
1079
+ * `localName`, matching that same side-effect-only convention. Verified against a real parse, not guessed.
1080
+ * @param node - the `preproc_include` node.
1081
+ * @returns the single import binding this directive introduces.
1082
+ */
1083
+ function cIncludeImports(node) {
1084
+ const path = namedChildren(node)[0];
1085
+ // `preproc_include` always wraps exactly one of `system_lib_string`/`string_literal`/an
1086
+ // expanded-macro path per the grammar; the undefined case only satisfies `namedChildren`'s general
1087
+ // array-access return type.
1088
+ /* v8 ignore next */
1089
+ if (path === undefined)
1090
+ return [];
1091
+ const specifier = path.type === 'system_lib_string' ? path.text.slice(1, -1) : (namedChildren(path)[0]?.text ?? path.text.slice(1, -1));
1092
+ return [{ localName: '', importedName: '*', specifier }];
1093
+ }
1094
+ /**
1095
+ * C# `using_directive` extraction. A plain directive (`using System;`) wraps its dotted path
1096
+ * (`identifier`/`qualified_name`) directly; an aliased one (`using Alias = System.Text;`) wraps a
1097
+ * `name_equals` (the alias) alongside the same dotted-path shape for the target. Neither form binds an
1098
+ * individual symbol the way an ECMAScript named import does — a C# `using` imports a whole namespace —
1099
+ * so the aliased form's `localName` is the alias itself, matching the namespace-import convention
1100
+ * `pythonBinding`/`goImports` already use elsewhere in this file. Verified against a real parse, not
1101
+ * guessed.
1102
+ * @param node - the `using_directive` node.
1103
+ * @returns the single import binding this directive introduces.
1104
+ */
1105
+ function csharpUsingImports(node) {
1106
+ const children = namedChildren(node);
1107
+ const alias = children.find(child => child.type === 'name_equals');
1108
+ const path = children.find(child => child.type === 'identifier' || child.type === 'qualified_name');
1109
+ // `using_directive` always wraps a dotted path per the grammar (a bare `using;` does not parse); the
1110
+ // undefined case only satisfies `Array.prototype.find`'s general return type.
1111
+ /* v8 ignore next */
1112
+ if (path === undefined)
1113
+ return [];
1114
+ const specifier = path.text;
1115
+ // The grammar's `name_equals` rule always wraps exactly one `identifier`; the fallback only satisfies
1116
+ // `namedChildren`'s general array-access return type.
1117
+ /* v8 ignore next */
1118
+ const localName = alias === undefined ? '' : (namedChildren(alias)[0]?.text ?? '');
1119
+ return [{ localName, importedName: '*', specifier }];
1120
+ }
1121
+ /**
1122
+ * One `namespace_use_clause`'s bound name and optional alias — `use App\Contracts\Cacheable;` binds a
1123
+ * `qualified_name` (its final segment is always the last named child, with no field of its own, matching
1124
+ * `calleeName`'s same `qualified_name` handling); a single-segment `use Foo;` binds a bare `name`
1125
+ * directly instead. An `as` rename wraps a `namespace_aliasing_clause` sibling. Verified against a real
1126
+ * parse, not guessed.
1127
+ * @param clause - the `namespace_use_clause` node.
1128
+ * @returns the imported name and its local binding, or `undefined` when the clause's target is a shape
1129
+ * this package does not name (never observed in practice, but `qualified_name`'s grammar rule allows no
1130
+ * other target).
1131
+ */
1132
+ function phpUseClauseBinding(clause) {
1133
+ // `namespace_use_clause` always wraps exactly one of these two node types per the grammar; the
1134
+ // fallback (`clause` itself) only satisfies `Array.prototype.find`'s general return type.
1135
+ /* v8 ignore next */
1136
+ const target = namedChildren(clause).find(child => child.type === 'qualified_name' || child.type === 'name') ?? clause;
1137
+ const specifier = target.text;
1138
+ // The grammar's `qualified_name` rule always wraps at least one final segment; the fallback only
1139
+ // satisfies `Array.prototype.at`'s general return type.
1140
+ /* v8 ignore next */
1141
+ const importedName = target.type === 'qualified_name' ? (namedChildren(target).at(-1)?.text ?? specifier) : specifier;
1142
+ const alias = namedChildren(clause).find(child => child.type === 'namespace_aliasing_clause');
1143
+ // The grammar's `namespace_aliasing_clause` rule always wraps exactly one `name`; the fallback only
1144
+ // satisfies `namedChildren`'s general array-access return type.
1145
+ /* v8 ignore next */
1146
+ const localName = alias === undefined ? importedName : (namedChildren(alias)[0]?.text ?? importedName);
1147
+ return { importedName, localName, specifier };
1148
+ }
1149
+ /**
1150
+ * PHP `namespace_use_declaration` extraction, covering both its shapes: a comma-separated list of
1151
+ * `namespace_use_clause` siblings (`use A\B, C\D as E;`), and a group `use App\{Foo, Bar as Baz};` — the
1152
+ * group form's shared prefix sits in a bare `namespace_name` sibling of the `namespace_use_group`, and
1153
+ * each `namespace_use_group_clause` inside it repeats the same name-plus-optional-alias shape as a plain
1154
+ * clause. The `function`/`const` keyword a `use function …`/`use const …` statement adds is an anonymous
1155
+ * token the grammar tacks onto the same shape either way, so no separate handling is needed. Verified
1156
+ * against a real parse, not guessed.
1157
+ * @param node - the `namespace_use_declaration` node.
1158
+ * @returns every import binding this statement introduces.
1159
+ */
1160
+ function phpImports(node) {
1161
+ const group = namedChildren(node).find(child => child.type === 'namespace_use_group');
1162
+ if (group !== undefined) {
1163
+ // The grammar always pairs a `namespace_use_group` with a preceding `namespace_name` prefix; the
1164
+ // undefined case only satisfies `Array.prototype.find`'s general return type.
1165
+ const prefix = namedChildren(node).find(child => child.type === 'namespace_name');
1166
+ /* v8 ignore next */
1167
+ const prefixText = prefix?.text ?? '';
1168
+ return namedChildren(group)
1169
+ .filter(child => child.type === 'namespace_use_group_clause')
1170
+ .map((clause) => {
1171
+ // A group clause's own name is always a bare `namespace_name` (itself wrapping a single `name`
1172
+ // token, even for a single-segment clause) — never a `qualified_name`, since the shared prefix
1173
+ // already carries every segment before it. Verified against a real parse, not guessed.
1174
+ const nameNode = namedChildren(clause).find(child => child.type === 'namespace_name');
1175
+ // The grammar always binds one per `namespace_use_group_clause`; the undefined case only
1176
+ // satisfies `Array.prototype.find`'s general return type.
1177
+ /* v8 ignore next */
1178
+ const segment = nameNode?.text ?? '';
1179
+ const alias = namedChildren(clause).find(child => child.type === 'namespace_aliasing_clause');
1180
+ // The grammar's `namespace_aliasing_clause` rule always wraps exactly one `name`; the fallback
1181
+ // only satisfies `namedChildren`'s general array-access return type.
1182
+ /* v8 ignore next */
1183
+ const localName = alias === undefined ? segment : (namedChildren(alias)[0]?.text ?? segment);
1184
+ return { localName, importedName: segment, specifier: `${prefixText}\\${segment}` };
1185
+ });
1186
+ }
1187
+ return namedChildren(node)
1188
+ .filter(child => child.type === 'namespace_use_clause')
1189
+ .map(clause => phpUseClauseBinding(clause));
1190
+ }
1191
+ /** A path's final `::`-separated segment, or the whole text when it has none. */
1192
+ function rustLastSegment(path) {
1193
+ const index = path.lastIndexOf('::');
1194
+ return index === -1 ? path : path.slice(index + 2);
1195
+ }
1196
+ /** `prefix::segment`, or just `segment` when there is no enclosing prefix yet. */
1197
+ function combineRustPrefix(prefix, segment) {
1198
+ return prefix === '' ? segment : `${prefix}::${segment}`;
1199
+ }
1200
+ /**
1201
+ * One Rust `use` clause target, resolved to zero or more import bindings — recursive because a
1202
+ * `scoped_use_list`/`use_list` can nest arbitrarily (`use std::{fmt::{self, Display}, io};`).
1203
+ * @param node - a `_use_clause` node: `identifier`, `self`, `scoped_identifier`, `use_wildcard`,
1204
+ * `use_as_clause`, `scoped_use_list`, or `use_list`. `crate`/`super` never reach this function on their
1205
+ * own — the grammar only ever produces them as a `scoped_identifier`'s `path` field, never as a
1206
+ * standalone use target.
1207
+ * @param prefix - the module path text accumulated from enclosing `scoped_use_list` wrappers, or `''`
1208
+ * at the top of a `use_declaration`.
1209
+ * @returns every import binding this clause (and any it nests) introduces.
1210
+ */
1211
+ function rustUseTarget(node, prefix) {
1212
+ if (node.type === 'identifier') {
1213
+ return [{ localName: node.text, importedName: node.text, specifier: combineRustPrefix(prefix, node.text) }];
1214
+ }
1215
+ if (node.type === 'self') {
1216
+ // `self` inside a `use_list` (`use std::fmt::{self, Display};`) imports the enclosing module path
1217
+ // itself, bound to its own last segment rather than a member of it. Verified against a real parse.
1218
+ const name = rustLastSegment(prefix);
1219
+ return [{ localName: name, importedName: name, specifier: prefix }];
1220
+ }
1221
+ if (node.type === 'scoped_identifier') {
1222
+ // `name` is required by the grammar; the fallback only satisfies `childForFieldName`'s general
1223
+ // return type.
1224
+ const name = node.childForFieldName('name');
1225
+ /* v8 ignore next */
1226
+ const localName = name?.text ?? node.text;
1227
+ return [{ localName, importedName: localName, specifier: combineRustPrefix(prefix, node.text) }];
1228
+ }
1229
+ if (node.type === 'use_wildcard') {
1230
+ // The grammar's `use_wildcard` rule always wraps exactly one path node before the `*`; the
1231
+ // undefined case only satisfies `namedChildren`'s general array-access return type.
1232
+ const path = namedChildren(node)[0];
1233
+ /* v8 ignore next */
1234
+ const specifier = path === undefined ? prefix : combineRustPrefix(prefix, path.text);
1235
+ // A glob import binds no individual symbol this package tracks by name, matching Go's
1236
+ // whole-package-import convention.
1237
+ return [{ localName: '', importedName: '*', specifier }];
1238
+ }
1239
+ if (node.type === 'use_as_clause') {
1240
+ const path = node.childForFieldName('path');
1241
+ const alias = node.childForFieldName('alias');
1242
+ // Both fields are required by the grammar's `use_as_clause` rule; the empty-array case only
1243
+ // satisfies `childForFieldName`'s general return type.
1244
+ /* v8 ignore next */
1245
+ if (path === null || alias === null)
1246
+ return [];
1247
+ const [binding] = rustUseTarget(path, prefix);
1248
+ // `rustUseTarget` always returns exactly one binding for the node types `path` can hold here
1249
+ // (`identifier`, `self`, `scoped_identifier`); the undefined case only satisfies the general
1250
+ // array-destructuring return type.
1251
+ /* v8 ignore next */
1252
+ return binding === undefined ? [] : [{ ...binding, localName: alias.text }];
1253
+ }
1254
+ if (node.type === 'scoped_use_list') {
1255
+ const path = node.childForFieldName('path');
1256
+ const list = node.childForFieldName('list');
1257
+ // `list` is required by the grammar's `scoped_use_list` rule; the empty-array case only satisfies
1258
+ // `childForFieldName`'s general return type.
1259
+ /* v8 ignore next */
1260
+ if (list === null)
1261
+ return [];
1262
+ // A `scoped_use_list` is exactly what distinguishes a path-prefixed braced group (`foo::{bar}`) from
1263
+ // a bare one (`use {foo, bar};`, which parses as a plain `use_list` instead, never reaching this
1264
+ // branch at all) — its `path` field is therefore always present whenever this node type is matched;
1265
+ // the fallback only satisfies `childForFieldName`'s general return type.
1266
+ /* v8 ignore next */
1267
+ const newPrefix = path === null ? prefix : combineRustPrefix(prefix, path.text);
1268
+ return namedChildren(list).flatMap(child => rustUseTarget(child, newPrefix));
1269
+ }
1270
+ // Every node type a `_use_clause` alternative can hold is handled by an earlier branch above; falling
1271
+ // through to here would require some other node type, which the grammar never produces in this
1272
+ // position.
1273
+ /* v8 ignore next */
1274
+ if (node.type === 'use_list') {
1275
+ return namedChildren(node).flatMap(child => rustUseTarget(child, prefix));
1276
+ }
1277
+ // No other node type is a valid `_use_clause` alternative per the grammar.
1278
+ /* v8 ignore next */
1279
+ return [];
1280
+ }
1281
+ /** Rust `use_declaration` extraction, dispatching to {@link rustUseTarget} on its `argument` field. */
1282
+ function rustImports(node) {
1283
+ const argument = node.childForFieldName('argument');
1284
+ // `argument` is required by the grammar's `use_declaration` rule; the empty-array case only satisfies
1285
+ // `childForFieldName`'s general return type.
1286
+ /* v8 ignore next */
1287
+ if (argument === null)
1288
+ return [];
1289
+ return rustUseTarget(argument, '');
1290
+ }
1291
+ /** Whether `node` is a bare name this package resolves heritage references against — a member
1292
+ * expression (`ns.Base`) or a call (a mixin, `f(Base)`) is not, matching the "don't guess" precedent
1293
+ * `calleeName`/`ecmascriptImports` already follow for shapes they do not fully resolve. `constant` is
1294
+ * Ruby's own bare-name node type (see `rubyClassHeritage`), verified against a real parse. */
1295
+ function isHeritageName(node) {
1296
+ return node.type === 'identifier' || node.type === 'type_identifier' || node.type === 'name' || node.type === 'constant';
1297
+ }
1298
+ /**
1299
+ * ECMAScript-family `extends`/`implements` extraction from a `class_declaration`'s `class_heritage`
1300
+ * child. Plain JavaScript's `class_heritage` wraps the extended expression directly (`extends Base` has
1301
+ * no further wrapper — JavaScript has no `implements`); TypeScript's wraps an `extends_clause` and an
1302
+ * optional `implements_clause` instead, verified against a real parse, not guessed.
1303
+ * @param node - the `class_declaration` node.
1304
+ * @param sourceKey - the declaring class's own {@link RawDefinition.key}.
1305
+ * @returns every heritage reference the class declares.
1306
+ */
1307
+ function ecmascriptClassHeritage(node, sourceKey) {
1308
+ const heritage = namedChildren(node).find(child => child.type === 'class_heritage');
1309
+ if (heritage === undefined)
1310
+ return [];
1311
+ const refs = [];
1312
+ for (const child of namedChildren(heritage)) {
1313
+ if (child.type === 'extends_clause') {
1314
+ const target = namedChildren(child)[0];
1315
+ if (target !== undefined && isHeritageName(target))
1316
+ refs.push({ sourceKey, targetName: target.text, relation: 'extends' });
1317
+ continue;
1318
+ }
1319
+ if (child.type === 'implements_clause') {
1320
+ for (const impl of namedChildren(child)) {
1321
+ if (isHeritageName(impl))
1322
+ refs.push({ sourceKey, targetName: impl.text, relation: 'implements' });
1323
+ }
1324
+ continue;
1325
+ }
1326
+ // Plain JavaScript: `child` is the extended expression itself.
1327
+ if (isHeritageName(child))
1328
+ refs.push({ sourceKey, targetName: child.text, relation: 'extends' });
1329
+ }
1330
+ return refs;
1331
+ }
1332
+ /**
1333
+ * TypeScript `interface_declaration` `extends` extraction from its `extends_type_clause` child — an
1334
+ * interface can extend more than one other interface (`interface C extends A, B {}`).
1335
+ * @param node - the `interface_declaration` node.
1336
+ * @param sourceKey - the declaring interface's own {@link RawDefinition.key}.
1337
+ * @returns every heritage reference the interface declares.
1338
+ */
1339
+ function tsInterfaceHeritage(node, sourceKey) {
1340
+ const clause = namedChildren(node).find(child => child.type === 'extends_type_clause');
1341
+ if (clause === undefined)
1342
+ return [];
1343
+ return namedChildren(clause)
1344
+ .filter(isHeritageName)
1345
+ .map(target => ({ sourceKey, targetName: target.text, relation: 'extends' }));
1346
+ }
1347
+ /**
1348
+ * Python `class_definition` base-class extraction from its `argument_list` child — shared with call
1349
+ * argument syntax, so a `keyword_argument` (`metaclass=Meta`) is filtered out rather than treated as a
1350
+ * base; Python draws no distinction between a base class and an interface, so every entry is `extends`.
1351
+ * @param node - the `class_definition` node.
1352
+ * @param sourceKey - the declaring class's own {@link RawDefinition.key}.
1353
+ * @returns every heritage reference the class declares.
1354
+ */
1355
+ function pythonClassHeritage(node, sourceKey) {
1356
+ const args = namedChildren(node).find(child => child.type === 'argument_list');
1357
+ if (args === undefined)
1358
+ return [];
1359
+ return namedChildren(args)
1360
+ .filter(isHeritageName)
1361
+ .map(target => ({ sourceKey, targetName: target.text, relation: 'extends' }));
1362
+ }
1363
+ /**
1364
+ * Java `class_declaration`/`record_declaration` heritage extraction from their `superclass` (a record
1365
+ * has none — a record can never extend another class, so the field is simply absent) and `interfaces`
1366
+ * fields — both hold the clause node (`superclass`/`super_interfaces`) directly, one level above the
1367
+ * `type_identifier`(s) themselves, verified against a real parse, not guessed.
1368
+ * @param node - the `class_declaration`/`record_declaration` node.
1369
+ * @param sourceKey - the declaring type's own {@link RawDefinition.key}.
1370
+ * @returns every heritage reference the type declares.
1371
+ */
1372
+ function javaClassHeritage(node, sourceKey) {
1373
+ const refs = [];
1374
+ const superclass = node.childForFieldName('superclass');
1375
+ const target = superclass === null ? undefined : namedChildren(superclass)[0];
1376
+ if (target !== undefined && isHeritageName(target))
1377
+ refs.push({ sourceKey, targetName: target.text, relation: 'extends' });
1378
+ const interfaces = node.childForFieldName('interfaces');
1379
+ const typeList = interfaces === null ? undefined : namedChildren(interfaces)[0];
1380
+ if (typeList !== undefined) {
1381
+ for (const impl of namedChildren(typeList)) {
1382
+ if (isHeritageName(impl))
1383
+ refs.push({ sourceKey, targetName: impl.text, relation: 'implements' });
1384
+ }
1385
+ }
1386
+ return refs;
1387
+ }
1388
+ /**
1389
+ * Java `interface_declaration` `extends` extraction — an interface can extend more than one other
1390
+ * interface (`interface C extends A, B {}`), from its `extends_interfaces` child; unlike
1391
+ * `class_declaration`'s `superclass`/`interfaces` fields, `interface_declaration` binds no field name
1392
+ * of its own to this clause, so it is found by node type instead, matching `ecmascriptClassHeritage`'s
1393
+ * same fallback for plain JavaScript. Verified against a real parse, not guessed.
1394
+ * @param node - the `interface_declaration` node.
1395
+ * @param sourceKey - the declaring interface's own {@link RawDefinition.key}.
1396
+ * @returns every heritage reference the interface declares.
1397
+ */
1398
+ function javaInterfaceHeritage(node, sourceKey) {
1399
+ const clause = namedChildren(node).find(child => child.type === 'extends_interfaces');
1400
+ if (clause === undefined)
1401
+ return [];
1402
+ const typeList = namedChildren(clause)[0];
1403
+ // `extends_interfaces` always wraps exactly one `type_list` per the grammar (an interface can never
1404
+ // write a bare `extends` with nothing after it); the undefined case only satisfies the general
1405
+ // array-access return type.
1406
+ /* v8 ignore next */
1407
+ if (typeList === undefined)
1408
+ return [];
1409
+ return namedChildren(typeList)
1410
+ .filter(isHeritageName)
1411
+ .map(target => ({ sourceKey, targetName: target.text, relation: 'extends' }));
1412
+ }
1413
+ /**
1414
+ * C++/C# base-list heritage extraction, shared by both: neither C++'s `class_specifier`/`struct_specifier`
1415
+ * nor C#'s `class_declaration`/`struct_declaration`/`record_declaration`/`interface_declaration` bind
1416
+ * their base list to a dedicated field name — both are found by node type instead, matching
1417
+ * `ecmascriptClassHeritage`'s same fallback for plain JavaScript. Neither grammar syntactically
1418
+ * distinguishes an extended base class from an implemented interface in this list — the same ambiguity
1419
+ * `pythonClassHeritage` already documents for Python's `argument_list` bases — so every entry reports
1420
+ * `extends`, including when the declaring node is itself an interface extending another interface (C#'s
1421
+ * `interface IBar : IFoo`), matching `javaInterfaceHeritage`/`tsInterfaceHeritage`'s existing convention
1422
+ * for that shape. Verified against a real parse, not guessed.
1423
+ * @param node - the declaring class/struct/interface/record node.
1424
+ * @param sourceKey - the declaring definition's own {@link RawDefinition.key}.
1425
+ * @param clauseType - the base-list node's own type (`base_class_clause` for C++, `base_list` for C#).
1426
+ * @returns every heritage reference the definition declares.
1427
+ */
1428
+ function baseListHeritage(node, sourceKey, clauseType) {
1429
+ const clause = namedChildren(node).find(child => child.type === clauseType);
1430
+ if (clause === undefined)
1431
+ return [];
1432
+ return namedChildren(clause)
1433
+ .filter(isHeritageName)
1434
+ .map(target => ({ sourceKey, targetName: target.text, relation: 'extends' }));
1435
+ }
1436
+ /**
1437
+ * PHP `class_declaration`/`interface_declaration`/`enum_declaration` heritage extraction. A class's
1438
+ * single `extends` base sits in its own `base_clause` (PHP classes have no multiple inheritance); an
1439
+ * interface's possibly-multiple `extends` bases reuse that same `base_clause` node type; a class's or
1440
+ * enum's `implements` list sits in a `class_interface_clause` — neither clause binds to a field of its
1441
+ * own, found by node type instead, matching `ecmascriptClassHeritage`'s same fallback for plain
1442
+ * JavaScript. One function covers all three kinds since neither clause is exclusive to one of them (an
1443
+ * interface never has a `class_interface_clause`, an enum never has a `base_clause`), so there is no
1444
+ * ambiguity in reporting the relation `base_clause` → `extends` and `class_interface_clause` →
1445
+ * `implements` regardless of which kind is declaring. A trait's `use` of another trait is not
1446
+ * `extends`/`implements` shaped and is not reported here — see `extractHeritage`'s call site. Verified
1447
+ * against a real parse, not guessed.
1448
+ * @param node - the declaring class/interface/enum node.
1449
+ * @param sourceKey - the declaring definition's own {@link RawDefinition.key}.
1450
+ * @returns every heritage reference the definition declares.
1451
+ */
1452
+ function phpHeritage(node, sourceKey) {
1453
+ const refs = [];
1454
+ const base = namedChildren(node).find(child => child.type === 'base_clause');
1455
+ if (base !== undefined) {
1456
+ for (const target of namedChildren(base)) {
1457
+ if (isHeritageName(target))
1458
+ refs.push({ sourceKey, targetName: target.text, relation: 'extends' });
1459
+ }
1460
+ }
1461
+ const iface = namedChildren(node).find(child => child.type === 'class_interface_clause');
1462
+ if (iface !== undefined) {
1463
+ for (const target of namedChildren(iface)) {
1464
+ if (isHeritageName(target))
1465
+ refs.push({ sourceKey, targetName: target.text, relation: 'implements' });
1466
+ }
1467
+ }
1468
+ return refs;
1469
+ }
1470
+ /**
1471
+ * Ruby `class` superclass extraction from its `superclass` field (`class Dog < Animal`) — the field
1472
+ * wraps the extended name directly, one level above the bare `constant`, the same shape C's
1473
+ * `type_definition`'s `declarator` field wraps its target one level down. Ruby draws no
1474
+ * `implements`-shaped distinction of its own (a mixin `include Module` is an ordinary method call,
1475
+ * structurally indistinguishable from any other, so it is not extracted — the "don't guess" precedent
1476
+ * `pythonClassHeritage` already documents for a comparable ambiguity), so every entry reports `extends`.
1477
+ * Verified against a real parse, not guessed.
1478
+ * @param node - the `class` node.
1479
+ * @param sourceKey - the declaring class's own {@link RawDefinition.key}.
1480
+ * @returns every heritage reference the class declares.
1481
+ */
1482
+ function rubyClassHeritage(node, sourceKey) {
1483
+ const superclass = node.childForFieldName('superclass');
1484
+ const target = superclass === null ? undefined : namedChildren(superclass)[0];
1485
+ if (target !== undefined && isHeritageName(target))
1486
+ return [{ sourceKey, targetName: target.text, relation: 'extends' }];
1487
+ return [];
1488
+ }
1489
+ /**
1490
+ * Rust `trait_item` supertrait extraction from its `bounds` field (`trait Shape: Debug + Display {}`).
1491
+ * Every supertrait requirement is reported as `extends`, matching `tsInterfaceHeritage`'s and
1492
+ * `javaInterfaceHeritage`'s convention for a multi-base interface declaration; a `impl Trait for Type`
1493
+ * block's own trait relationship is not extracted here or anywhere else in this package — unlike a
1494
+ * class/struct/trait declaration, an `impl` block is never itself captured as a `RawDefinition` (it
1495
+ * introduces no name of its own the seam's `container`/qualified-name scheme could attach to), so it has
1496
+ * no {@link RawDefinition.key} to source a heritage reference from. Verified against a real parse, not
1497
+ * guessed.
1498
+ * @param node - the `trait_item` node.
1499
+ * @param sourceKey - the declaring trait's own {@link RawDefinition.key}.
1500
+ * @returns every heritage reference the trait declares.
1501
+ */
1502
+ function rustTraitHeritage(node, sourceKey) {
1503
+ const bounds = node.childForFieldName('bounds');
1504
+ if (bounds === null)
1505
+ return [];
1506
+ return namedChildren(bounds)
1507
+ .filter(isHeritageName)
1508
+ .map(target => ({ sourceKey, targetName: target.text, relation: 'extends' }));
1509
+ }
1510
+ /**
1511
+ * Scala `class_definition`/`object_definition`/`trait_definition` heritage extraction from an
1512
+ * `extends_clause`'s `type` field — a single base (`extends Base`) is a bare `type_identifier`; one or
1513
+ * more `with` mixins added to it (`extends Base with Shape with Other`) wrap all of them together in one
1514
+ * `compound_type`, whose own `base` field names the first (the real superclass, reported `extends`) with
1515
+ * every other named child being an additional mixin (reported `implements`, matching this package's
1516
+ * class/interface-conflating-list precedent already documented for `pythonClassHeritage`/
1517
+ * `baseListHeritage`). Verified against a real parse, not guessed.
1518
+ * @param node - the declaring `class_definition`/`object_definition`/`trait_definition` node.
1519
+ * @param sourceKey - the declaring definition's own {@link RawDefinition.key}.
1520
+ * @returns every heritage reference the declaration declares.
1521
+ */
1522
+ function scalaHeritage(node, sourceKey) {
1523
+ const extendsClause = node.childForFieldName('extend');
1524
+ const type = extendsClause === null ? null : extendsClause.childForFieldName('type');
1525
+ if (type === null)
1526
+ return [];
1527
+ if (type.type !== 'compound_type') {
1528
+ return isHeritageName(type) ? [{ sourceKey, targetName: type.text, relation: 'extends' }] : [];
1529
+ }
1530
+ const base = type.childForFieldName('base');
1531
+ const refs = [];
1532
+ if (base !== null && isHeritageName(base))
1533
+ refs.push({ sourceKey, targetName: base.text, relation: 'extends' });
1534
+ for (const mixin of namedChildren(type)) {
1535
+ if (base !== null && mixin.equals(base))
1536
+ continue;
1537
+ if (isHeritageName(mixin))
1538
+ refs.push({ sourceKey, targetName: mixin.text, relation: 'implements' });
1539
+ }
1540
+ return refs;
1541
+ }
1542
+ /**
1543
+ * Dispatch heritage extraction to the language family that owns a captured class, struct, or interface
1544
+ * node's syntax. Go is absent: its interfaces are satisfied structurally, never declared at the
1545
+ * implementing type, so there is no static reference here to extract.
1546
+ * @param node - the captured `class`-, `struct`-, `interface`-, or `trait`-kind definition node.
1547
+ * @param kind - the captured definition's seam kind (`'class'`, `'struct'`, `'interface'`, or `'trait'`).
1548
+ * @param sourceKey - the declaring definition's own {@link RawDefinition.key}.
1549
+ * @param language - the seam language label the file was parsed as.
1550
+ * @returns every heritage reference the definition declares.
1551
+ */
1552
+ function extractHeritage(node, kind, sourceKey, language) {
1553
+ // `kind === 'trait'` only ever comes from Rust's `trait_item` rule or Scala's `trait_definition` rule
1554
+ // — no other language in LANGUAGE_TABLE produces it.
1555
+ if (kind === 'trait')
1556
+ return language === 'scala' ? scalaHeritage(node, sourceKey) : rustTraitHeritage(node, sourceKey);
1557
+ // `kind === 'interface'` only ever comes from TYPESCRIPT_DEFINITIONS's, Java's, or C#'s
1558
+ // `interface_declaration` rule — no other language in LANGUAGE_TABLE produces it.
1559
+ if (kind === 'interface') {
1560
+ if (language === 'java')
1561
+ return javaInterfaceHeritage(node, sourceKey);
1562
+ if (language === 'csharp')
1563
+ return baseListHeritage(node, sourceKey, 'base_list');
1564
+ if (language === 'php')
1565
+ return phpHeritage(node, sourceKey);
1566
+ if (language === 'kotlin')
1567
+ return kotlinHeritage(node, sourceKey);
1568
+ if (language === 'swift')
1569
+ return swiftHeritage(node, sourceKey);
1570
+ return tsInterfaceHeritage(node, sourceKey);
1571
+ }
1572
+ // `kind === 'struct'` only ever comes from C/C++'s `struct_specifier`/`union_specifier`, C#'s
1573
+ // `struct_declaration`, or Swift's own `class_declaration` (`declaration_kind` field `struct`) rule —
1574
+ // a plain C struct/union has no base-list syntax at all, so `baseListHeritage` simply finds nothing to
1575
+ // report for it.
1576
+ if (kind === 'struct') {
1577
+ if (language === 'swift')
1578
+ return swiftHeritage(node, sourceKey);
1579
+ return baseListHeritage(node, sourceKey, language === 'csharp' ? 'base_list' : 'base_class_clause');
1580
+ }
1581
+ // `kind === 'enum'` only ever comes from PHP's `enum_declaration` rule today — no other language's
1582
+ // enum rule reaches this dispatch (TypeScript's/Java's/C#'s enum kinds carry no heritage syntax of
1583
+ // their own this package extracts elsewhere), so every other language reports nothing here.
1584
+ if (kind === 'enum')
1585
+ return language === 'php' ? phpHeritage(node, sourceKey) : [];
1586
+ if (kind !== 'class')
1587
+ return [];
1588
+ if (language === 'python')
1589
+ return pythonClassHeritage(node, sourceKey);
1590
+ // `kind === 'class'` from Java's `class_declaration`/`record_declaration` rules — see `javaClassHeritage`.
1591
+ if (language === 'java')
1592
+ return javaClassHeritage(node, sourceKey);
1593
+ if (language === 'cpp')
1594
+ return baseListHeritage(node, sourceKey, 'base_class_clause');
1595
+ if (language === 'csharp')
1596
+ return baseListHeritage(node, sourceKey, 'base_list');
1597
+ if (language === 'php')
1598
+ return phpHeritage(node, sourceKey);
1599
+ if (language === 'ruby')
1600
+ return rubyClassHeritage(node, sourceKey);
1601
+ if (language === 'kotlin')
1602
+ return kotlinHeritage(node, sourceKey);
1603
+ if (language === 'swift')
1604
+ return swiftHeritage(node, sourceKey);
1605
+ if (language === 'dart')
1606
+ return dartHeritage(node, sourceKey);
1607
+ if (language === 'scala')
1608
+ return scalaHeritage(node, sourceKey);
1609
+ // Otherwise only comes from ECMASCRIPT_DEFINITIONS's `class_declaration` rule — Go has no class
1610
+ // concept, so this is never reached with `language === 'go'`.
1611
+ return ecmascriptClassHeritage(node, sourceKey);
1612
+ }
177
1613
  /**
178
1614
  * Extract every definition, call, and import from one parsed file.
179
1615
  * @param tree - the file's parsed syntax tree.
@@ -184,12 +1620,34 @@ export function extractFile(tree, spec) {
184
1620
  const definitions = [];
185
1621
  const calls = [];
186
1622
  const imports = [];
1623
+ const heritage = [];
187
1624
  const containerNames = [];
188
1625
  const containerKeys = [null];
1626
+ const commonJsExports = ECMASCRIPT_LANGUAGES.has(spec.language) ? commonJsExportedNames(tree.rootNode) : EMPTY_NAME_SET;
1627
+ // Tracks whether the node currently being visited sits at module top level, directly inside a class
1628
+ // body, or inside a function/method body — see `DefinitionRule.scopeRestricted` and
1629
+ // `LanguageSpec.bareFunctionScopeTypes`.
1630
+ const scopeKinds = ['module'];
189
1631
  function visit(node) {
190
1632
  const rule = matchDefinition(node, spec.definitions);
191
- if (rule !== undefined) {
192
- const nameNode = node.childForFieldName(rule.nameField);
1633
+ // A `scopeRestricted` rule matched inside a function/method body is treated as no match at all —
1634
+ // the node still gets visited below, just without becoming a definition or a container.
1635
+ const captured = rule !== undefined
1636
+ && (rule.scopeRestricted !== true || scopeKinds[scopeKinds.length - 1] !== 'other');
1637
+ if (captured) {
1638
+ // `matchDefinition` already confirmed `declaratorName`/`firstChildName` return non-`undefined` for
1639
+ // this same node when `rule` matched; the assertions below only satisfy the ternary's
1640
+ // `SyntaxNode | null` type, mirroring `node.childForFieldName`'s own return type — not a runtime
1641
+ // branch, so nothing here needs a test of its own.
1642
+ const nameNode = rule.nameField === SELF_NAME_FIELD ? node
1643
+ : rule.nameField === DECLARATOR_NAME_FIELD ? declaratorName(node)
1644
+ : rule.nameField === FIRST_CHILD_NAME_FIELD ? firstChildName(node)
1645
+ : rule.nameField === PHP_ELEMENT_NAME_FIELD ? phpElementName(node)
1646
+ : rule.nameField === KOTLIN_NAME_FIELD ? kotlinDeclaredName(node)
1647
+ : rule.nameField === SWIFT_PROPERTY_NAME_FIELD ? swiftPropertyName(node)
1648
+ : rule.nameField === SWIFT_FUNCTION_NAME_FIELD ? swiftFunctionName(node)
1649
+ : rule.nameField === DART_FIELD_NAME_FIELD ? dartFieldName(node)
1650
+ : node.childForFieldName(rule.nameField);
193
1651
  // A matched rule's node type is always the NAMED-declaration form the grammar mandates a name
194
1652
  // for; the anonymous form (`function_expression`, `class` as an expression, both produced by an
195
1653
  // anonymous default export) parses as a different node type this rule never matches.
@@ -197,56 +1655,140 @@ export function extractFile(tree, spec) {
197
1655
  if (nameNode !== null) {
198
1656
  const key = `${node.startPosition.row}:${node.startPosition.column}`;
199
1657
  const parentKey = containerKeys[containerKeys.length - 1] ?? null;
1658
+ // Zig's own `variable_declaration` rule, Kotlin's own `class_declaration` rule, and Swift's own
1659
+ // `class_declaration`/`property_declaration` rules each report one fixed placeholder kind in
1660
+ // `LANGUAGE_TABLE` (no rule can vary `kind` by a value's shape or a keyword the way any of them
1661
+ // needs) — the real kind is computed here instead, see
1662
+ // `zigDeclarationKind`/`kotlinClassKind`/`swiftDeclarationKind`. Every other language's rule
1663
+ // already carries the right kind.
1664
+ const kind = spec.language === 'zig' && node.type === 'variable_declaration' ? zigDeclarationKind(node)
1665
+ : spec.language === 'kotlin' && node.type === 'class_declaration' ? kotlinClassKind(node)
1666
+ : spec.language === 'swift' && (node.type === 'class_declaration' || node.type === 'property_declaration') ? swiftDeclarationKind(node)
1667
+ : rule.kind;
1668
+ if (spec.language === 'zig' && node.type === 'variable_declaration') {
1669
+ const zigImport = zigImportBinding(node, nameNode.text);
1670
+ if (zigImport !== undefined)
1671
+ imports.push(zigImport);
1672
+ }
200
1673
  definitions.push({
201
1674
  key,
202
1675
  parentKey,
203
- kind: rule.kind,
1676
+ kind,
204
1677
  name: nameNode.text,
205
1678
  container: [...containerNames],
206
1679
  startLine: node.startPosition.row + 1,
207
1680
  endLine: node.endPosition.row + 1,
208
1681
  startColumn: node.startPosition.column,
209
1682
  endColumn: node.endPosition.column,
210
- isExported: isExported(node, spec.language, nameNode.text),
1683
+ isExported: isExported(node, spec.language, nameNode.text, commonJsExports),
211
1684
  isAsync: hasKeywordChild(node, 'async'),
212
- isStatic: hasKeywordChild(node, 'static'),
1685
+ // Java nests every modifier keyword one level down inside a `modifiers` node rather than as
1686
+ // a direct child of the declaration itself — see `javaHasModifier`. C/C++ wrap `static` in a
1687
+ // `storage_class_specifier` — see `cHasStorageClassKeyword`. C# gives each modifier its own
1688
+ // flat `modifier` node — see `csharpHasModifier`.
1689
+ isStatic: spec.language === 'java' ? javaHasModifier(node, 'static')
1690
+ : spec.language === 'c' || spec.language === 'cpp' ? cHasStorageClassKeyword(node, 'static')
1691
+ : spec.language === 'csharp' ? csharpHasModifier(node, 'static')
1692
+ : spec.language === 'php' ? phpHasStaticModifier(node)
1693
+ : hasKeywordChild(node, 'static'),
1694
+ decorators: pythonDecorators(node, spec.language),
213
1695
  });
1696
+ heritage.push(...extractHeritage(node, kind, key, spec.language));
214
1697
  containerNames.push(nameNode.text);
215
1698
  containerKeys.push(key);
1699
+ // A C/C++/C# `struct` is scoped exactly like a `class` for this purpose — a `scopeRestricted`
1700
+ // field rule must fire directly inside either body, not just a `class` one. Rust's `mod`
1701
+ // (`namespace`) is scoped like the module top level it nests, not like a function body — a
1702
+ // `scopeRestricted` `const`/`static` rule must still fire directly inside one.
1703
+ scopeKinds.push(kind === 'class' || kind === 'struct' ? 'class' : kind === 'namespace' ? 'module' : 'other');
216
1704
  for (const child of namedChildren(node))
217
1705
  visit(child);
1706
+ scopeKinds.pop();
218
1707
  containerKeys.pop();
219
1708
  containerNames.pop();
220
1709
  return;
221
1710
  }
222
1711
  }
223
1712
  if (spec.callTypes.includes(node.type)) {
224
- // Every call-shaped node type in every language table entry requires its callee field; the null
225
- // case only satisfies `childForFieldName`'s general return type.
226
- const callee = node.childForFieldName(spec.callFunctionField);
1713
+ // Kotlin's and Swift's `call_expression` bind no field of their own at all — see `kotlinCallee`'s/
1714
+ // `swiftCallee`'s doc comments — so this consults one of them directly instead of
1715
+ // `node.childForFieldName`, which could never resolve anything for either grammar regardless of
1716
+ // which field name `LanguageSpec.callFunctionField` names. Every call-shaped node type in every
1717
+ // OTHER language table entry requires its callee field; the null case only satisfies
1718
+ // `childForFieldName`'s general return type.
1719
+ const calleeField = spec.callFunctionFieldByType?.[node.type] ?? spec.callFunctionField;
1720
+ // `kotlinCallee`/`swiftCallee` are not known to return `undefined` for any real `call_expression`
1721
+ // — every shape checked against a real parse (a bare call, a member call, a chained member call, a
1722
+ // `super` receiver, a safe-nav/optional-chaining receiver, a call on a parenthesized or indexing
1723
+ // expression) resolves to a node; the `?? null` fallback exists only to match
1724
+ // `childForFieldName`'s `Node | null` shape for the other languages' branch below, not a case seen.
1725
+ /* v8 ignore next 2 */
1726
+ const callee = spec.language === 'kotlin' ? kotlinCallee(node) ?? null
1727
+ : spec.language === 'swift' ? swiftCallee(node) ?? null
1728
+ : node.childForFieldName(calleeField);
227
1729
  /* v8 ignore next */
228
1730
  const name = callee === null ? undefined : calleeName(callee);
229
1731
  if (name !== undefined) {
1732
+ // `callee` is non-null whenever `name` is: the optional chaining only satisfies the type
1733
+ // system's view of the field lookup above, not a real possibility here.
1734
+ /* v8 ignore next */
1735
+ // Kotlin's `kotlinCallee`/Swift's `swiftCallee` already unwrap a member call's receiver chain
1736
+ // down to the same `simple_identifier` node type a bare call's callee is, so its own type alone
1737
+ // can no longer distinguish the two for either language — a bare call's `call_expression` has
1738
+ // that `simple_identifier` as its own first child directly, while a member call's has a
1739
+ // `navigation_expression` there instead (see `kotlinCallee`/`swiftCallee`). Verified against a
1740
+ // real parse, not guessed.
1741
+ const isBareCallee = callee?.type === 'identifier' || callee?.type === 'name'
1742
+ || ((spec.language === 'kotlin' || spec.language === 'swift') && namedChildren(node)[0]?.type === 'simple_identifier');
230
1743
  calls.push({
231
1744
  callerKey: containerKeys[containerKeys.length - 1] ?? null,
232
1745
  calleeName: name,
233
1746
  line: node.startPosition.row + 1,
234
1747
  column: node.startPosition.column,
235
- // `callee` is non-null whenever `name` is: the optional chaining only satisfies the type
236
- // system's view of the field lookup above, not a real possibility here.
237
- /* v8 ignore next */
238
- isMemberCall: callee?.type !== 'identifier',
1748
+ // The `object` check covers Java's `method_invocation` and PHP's `member_call_expression`,
1749
+ // whose `name` field is always a bare identifier — the receiver, if any, sits in a separate
1750
+ // sibling field instead of wrapping the callee the way every other grammar's member expression
1751
+ // does. The `scope` check covers PHP's `scoped_call_expression` (`Class::method()`), whose
1752
+ // receiver sits in a sibling `scope` field instead of `object`; no other call-type node in
1753
+ // LANGUAGE_TABLE binds either field, so both are a no-op for every other language. Without
1754
+ // `isBareCallee` also accepting PHP's `'name'` type alongside `'identifier'`, every PHP call —
1755
+ // including an ordinary global `function_call_expression` — would be misclassified as
1756
+ // member-like, since PHP's bare-name node type is `name`, never `identifier`. The `receiver`
1757
+ // check covers Ruby's `call` node (`obj.method_name(x)`), whose callee is always a bare
1758
+ // `identifier` in its own `method` field regardless of receiver — unlike every other grammar's
1759
+ // member expression, the receiver sits in a distinct sibling field instead of wrapping the
1760
+ // callee; no other call-type node in LANGUAGE_TABLE binds a field named `receiver`. Verified
1761
+ // against a real parse, not guessed.
1762
+ isMemberCall: !isBareCallee || node.childForFieldName('object') !== null || node.childForFieldName('scope') !== null || node.childForFieldName('receiver') !== null,
239
1763
  });
240
1764
  }
241
1765
  }
242
1766
  if (spec.importTypes.includes(node.type)) {
243
1767
  imports.push(...extractImports(node, spec.language));
244
1768
  }
1769
+ if (node.type === 'call_expression' && ECMASCRIPT_LANGUAGES.has(spec.language)) {
1770
+ const requireImport = commonJsRequireImport(node);
1771
+ if (requireImport !== undefined)
1772
+ imports.push(requireImport);
1773
+ }
1774
+ if (node.type === 'call' && spec.language === 'ruby') {
1775
+ const requireImport = rubyRequireImport(node);
1776
+ if (requireImport !== undefined)
1777
+ imports.push(requireImport);
1778
+ }
1779
+ // A callback or IIFE's function value is never itself captured as a definition (only a *named*
1780
+ // declaration, or one assigned through a captured `variable_declarator`, is) — but a `const`/`var`
1781
+ // in its body is still function-local, so its scope must flip to `'other'` here regardless.
1782
+ const entersBareFunctionScope = spec.bareFunctionScopeTypes.includes(node.type);
1783
+ if (entersBareFunctionScope)
1784
+ scopeKinds.push('other');
245
1785
  for (const child of namedChildren(node))
246
1786
  visit(child);
1787
+ if (entersBareFunctionScope)
1788
+ scopeKinds.pop();
247
1789
  }
248
1790
  visit(tree.rootNode);
249
- return { definitions, calls, imports };
1791
+ return { definitions, calls, imports, heritage };
250
1792
  }
251
1793
  /** Dispatch import extraction to the language family that owns `node`'s syntax. */
252
1794
  function extractImports(node, language) {
@@ -260,27 +1802,172 @@ function extractImports(node, language) {
260
1802
  return pythonImports(node);
261
1803
  case 'go':
262
1804
  return goImports(node);
1805
+ case 'java':
1806
+ return javaImports(node);
1807
+ case 'c':
1808
+ case 'cpp':
1809
+ return cIncludeImports(node);
1810
+ case 'csharp':
1811
+ return csharpUsingImports(node);
1812
+ case 'php':
1813
+ return phpImports(node);
1814
+ case 'rust':
1815
+ return rustImports(node);
1816
+ case 'kotlin':
1817
+ return kotlinImports(node);
1818
+ case 'swift':
1819
+ return swiftImports(node);
1820
+ case 'dart':
1821
+ return dartImports(node);
1822
+ case 'scala':
1823
+ return scalaImports(node);
263
1824
  /* v8 ignore next 2 -- exhaustive over LANGUAGE_TABLE's current language labels; unreachable. */
264
1825
  default:
265
1826
  return [];
266
1827
  }
267
1828
  }
1829
+ /** Whether `node` is the two-level `module.exports` member expression itself (not `module.exports.x`). */
1830
+ function isModuleExportsExpression(node) {
1831
+ return node?.type === 'member_expression'
1832
+ && node.childForFieldName('object')?.type === 'identifier'
1833
+ && node.childForFieldName('object')?.text === 'module'
1834
+ && node.childForFieldName('property')?.text === 'exports';
1835
+ }
1836
+ /**
1837
+ * Every name a top-level CommonJS export assignment marks exported: `module.exports.NAME = ...` /
1838
+ * `exports.NAME = ...` (named export), and `module.exports = NAME` (whole-module reassignment to a
1839
+ * single local declaration). `module.exports = { a, b }` (object-literal reassignment) is not handled
1840
+ * — distinguishing a shorthand property from a computed or renamed one adds a second layer of "don't
1841
+ * guess" cases this pass does not need yet; only the two unambiguous forms above are recognized.
1842
+ * Restricted to true top-level statements, matching this file's existing module/class-only scope
1843
+ * restriction for a `scopeRestricted` `DefinitionRule` — a conditional or function-body export
1844
+ * assignment is not a module's public surface in the same unconditional sense.
1845
+ * @param root - the file's parsed root (`program`) node.
1846
+ * @returns every name a CommonJS export assignment binds.
1847
+ */
1848
+ function commonJsExportedNames(root) {
1849
+ const names = new Set();
1850
+ for (const statement of namedChildren(root)) {
1851
+ if (statement.type !== 'expression_statement')
1852
+ continue;
1853
+ const expr = namedChildren(statement)[0];
1854
+ if (expr?.type !== 'assignment_expression')
1855
+ continue;
1856
+ const left = expr.childForFieldName('left');
1857
+ if (left?.type !== 'member_expression')
1858
+ continue;
1859
+ const object = left.childForFieldName('object');
1860
+ const property = left.childForFieldName('property');
1861
+ if (isModuleExportsExpression(object) || (object?.type === 'identifier' && object.text === 'exports')) {
1862
+ // `property` is required by the grammar's `member_expression` rule; the null case only
1863
+ // satisfies `childForFieldName`'s general return type.
1864
+ /* v8 ignore next */
1865
+ if (property !== null)
1866
+ names.add(property.text);
1867
+ continue;
1868
+ }
1869
+ if (isModuleExportsExpression(left)) {
1870
+ const right = expr.childForFieldName('right');
1871
+ if (right?.type === 'identifier')
1872
+ names.add(right.text);
1873
+ }
1874
+ }
1875
+ return names;
1876
+ }
268
1877
  /**
269
1878
  * Whether a declaration is exported from its module, by the export construct its own language
270
- * defines: ECMAScript wraps an exported statement in `export_statement`; Go's spec defines an
271
- * exported identifier as one starting with an uppercase letter, with no separate keyword; Python
272
- * defines no export construct at all, so every Python declaration reports `false` rather than guess
273
- * one from a naming convention or an `__all__` list the extractor does not read.
1879
+ * defines: ECMAScript wraps an exported statement in `export_statement`, or — for CommonJS code, still
1880
+ * common outside pure-ESM projects — is named by a top-level `module.exports`/`exports` assignment (see
1881
+ * {@link commonJsExportedNames}); Go's spec defines an exported identifier as one starting with an
1882
+ * uppercase letter, with no separate keyword; Java marks a declaration exported by an explicit
1883
+ * `public` modifier (see {@link javaHasModifier}) — an interface member's implicit `public` with no
1884
+ * keyword at all is not detected, matching this function's existing refusal to infer visibility from
1885
+ * anything but an explicit language construct; C's is external vs. internal *linkage* — a top-level
1886
+ * function or variable without `static` has external linkage (visible to other translation units), the
1887
+ * same real-rule precedent Go's capitalization check follows rather than a guess; C++ inherits that same
1888
+ * rule for its own free (non-member) functions and variables, but reports `false` for a class/struct
1889
+ * member — a method or field has no comparable linkage concept of its own, and neither does any other
1890
+ * kind (`struct`, `enum`, `type_alias`); C# marks a declaration exported by an explicit `public` modifier
1891
+ * (see {@link csharpHasModifier}), mirroring Java; Python defines no export construct at all, so every
1892
+ * Python declaration reports `false` rather than guess one from a naming convention or an `__all__` list
1893
+ * the extractor does not read; Rust marks a declaration exported by an explicit bare `pub`
1894
+ * `visibility_modifier` (see {@link rustIsPublic}) — a restricted `pub(crate)`/`pub(super)`/`pub(self)`
1895
+ * does not count, mirroring Java's/C#'s explicit-`public`-only convention.
274
1896
  * @param node - the definition node.
275
1897
  * @param language - the seam language label the file was parsed as.
276
1898
  * @param name - the declaration's simple name.
1899
+ * @param commonJsExports - every name a CommonJS export assignment in this file binds.
277
1900
  * @returns whether the language's own export rule marks this declaration exported.
278
1901
  */
279
- function isExported(node, language, name) {
1902
+ function isExported(node, language, name, commonJsExports) {
280
1903
  if (language === 'go')
281
1904
  return /^\p{Lu}/u.test(name);
1905
+ if (language === 'java')
1906
+ return javaHasModifier(node, 'public');
1907
+ if (language === 'csharp')
1908
+ return csharpHasModifier(node, 'public');
1909
+ if (language === 'c' || language === 'cpp') {
1910
+ // A C++ method is a `function_definition` directly inside a class/struct's `field_declaration_list`
1911
+ // — the same node type a free function uses, but with no linkage concept of its own to report.
1912
+ if (node.type === 'function_definition')
1913
+ return node.parent?.type !== 'field_declaration_list' && !cHasStorageClassKeyword(node, 'static');
1914
+ // A top-level `declaration` (kind `variable`) is always module-scope — `scopeRestricted` already
1915
+ // excludes the function-local case, so no further scope check is needed here.
1916
+ if (node.type === 'declaration')
1917
+ return !cHasStorageClassKeyword(node, 'static');
1918
+ return false;
1919
+ }
282
1920
  if (language === 'python')
283
1921
  return false;
1922
+ // Ruby's `private`/`protected`/`public` are ordinary method calls that toggle visibility for
1923
+ // subsequently defined methods, not a keyword on the declaration itself, and there is no separate
1924
+ // module-level export construct at all — matching Python's precedent, every Ruby declaration reports
1925
+ // `false` rather than guess one from tracking those calls' effect through the file.
1926
+ if (language === 'ruby')
1927
+ return false;
1928
+ // Zig marks a declaration exported by an explicit bare `pub` keyword — a plain, always-anonymous
1929
+ // token directly on the declaration itself, the same shape Rust's bare `pub` `visibility_modifier`
1930
+ // has (see `rustIsPublic`), except Zig's grammar gives this token no named-child wrapper of its own
1931
+ // to check `hasKeywordChild` still finds it. Verified against a real parse, not guessed.
1932
+ if (language === 'zig')
1933
+ return hasKeywordChild(node, 'pub');
1934
+ // Kotlin's default visibility (no keyword at all) is already public — the inverse of every other
1935
+ // language's "not exported unless an explicit keyword says so" convention — so this reports `true`
1936
+ // unless an explicit `private`/`internal`/`protected` keyword (wrapped in the declaration's own
1937
+ // `modifiers` node — see `kotlinHasVisibility`) narrows it. `protected` is treated as not exported:
1938
+ // visible only to subclasses, not to an arbitrary importer, the same bar `internal`'s
1939
+ // module-restricted visibility already fails to clear.
1940
+ if (language === 'kotlin') {
1941
+ return !kotlinHasVisibility(node, 'private') && !kotlinHasVisibility(node, 'internal') && !kotlinHasVisibility(node, 'protected');
1942
+ }
1943
+ // Swift's default visibility (no keyword at all) is `internal` — visible within the same module, but
1944
+ // not to another module's importer — so unlike Kotlin's default-public convention, this reports `true`
1945
+ // only for an explicit `public`/`open` keyword. Swift wraps its visibility keyword in the very same
1946
+ // `modifiers` → `visibility_modifier` shape Kotlin's grammar does (verified against a real parse), so
1947
+ // `kotlinHasVisibility` is reused directly rather than duplicated under a new name.
1948
+ if (language === 'swift')
1949
+ return kotlinHasVisibility(node, 'public') || kotlinHasVisibility(node, 'open');
1950
+ // Dart has no visibility keyword at all — the language's own convention is a leading underscore
1951
+ // marking a name library-private, matching Go's capitalization-based convention but spelled with a
1952
+ // naming convention instead of a keyword.
1953
+ if (language === 'dart')
1954
+ return !name.startsWith('_');
1955
+ // Scala's default visibility (no keyword at all) is already public, matching Kotlin's convention —
1956
+ // this reports `true` unless an explicit `private`/`protected` keyword narrows it.
1957
+ if (language === 'scala')
1958
+ return !scalaHasModifier(node, 'private') && !scalaHasModifier(node, 'protected');
1959
+ // A PHP top-level function/class/interface/trait/enum carries no visibility keyword of its own — the
1960
+ // language has no export construct for them, matching Python's precedent, and this reports `false`
1961
+ // for all of them since `phpHasVisibility` finds no `visibility_modifier` to check. A class/enum
1962
+ // member (`const`/property/method) does carry one, mirroring Java's/C#'s explicit-`public`-only
1963
+ // convention: an interface member's implicit `public` with no keyword at all is not detected either,
1964
+ // the same refusal to infer visibility from anything but an explicit language construct.
1965
+ if (language === 'php')
1966
+ return phpHasVisibility(node, 'public');
1967
+ if (language === 'rust')
1968
+ return rustIsPublic(node);
1969
+ if (commonJsExports.has(name))
1970
+ return true;
284
1971
  let current = node.parent;
285
1972
  while (current !== null) {
286
1973
  if (current.type === 'export_statement')