dsh-plugin-codegraph-tree-sitter 0.1.4 → 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,7 +7,7 @@
7
7
  * real workspace are of exactly this shape.
8
8
  * @module dsh-plugin-codegraph-tree-sitter/extract
9
9
  */
10
- import { DECLARATOR_NAME_FIELD, FIRST_CHILD_NAME_FIELD, PHP_ELEMENT_NAME_FIELD, SELF_NAME_FIELD } from "./languages.js";
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";
11
11
  /** A node's named children, with the `null` slots `web-tree-sitter` reserves for missing nodes dropped. */
12
12
  function namedChildren(node) {
13
13
  return node.namedChildren.filter(child => child !== null);
@@ -227,6 +227,490 @@ function soleNamedField(node, field) {
227
227
  const named = node.childrenForFieldName(field).filter((child) => child !== null && child.isNamed);
228
228
  return named.length === 1 ? named[0] : undefined;
229
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
+ }
230
714
  /** The definition rule matching `node`, or `undefined` when it introduces no declaration. */
231
715
  function matchDefinition(node, definitions) {
232
716
  for (const rule of definitions) {
@@ -245,7 +729,11 @@ function matchDefinition(node, definitions) {
245
729
  : rule.nameField === DECLARATOR_NAME_FIELD ? declaratorName(node)
246
730
  : rule.nameField === FIRST_CHILD_NAME_FIELD ? firstChildName(node)
247
731
  : rule.nameField === PHP_ELEMENT_NAME_FIELD ? phpElementName(node)
248
- : soleNamedField(node, rule.nameField);
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);
249
737
  if (nameNode === undefined)
250
738
  continue;
251
739
  if (rule.nameNodeTypes !== undefined && !rule.nameNodeTypes.includes(nameNode.type))
@@ -270,10 +758,17 @@ function calleeName(callee) {
270
758
  // Verified against a real parse, not guessed.
271
759
  if (callee.type === 'generic_function') {
272
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 */
273
764
  return inner === null ? undefined : calleeName(inner);
274
765
  }
275
766
  if (callee.type === 'identifier')
276
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;
277
772
  // PHP's bare-name node type — the resolved `callFunctionFieldByType` field value for all three of its
278
773
  // call shapes (`function_call_expression`'s simple case, `member_call_expression`'s and
279
774
  // `scoped_call_expression`'s method name) is this node type directly, never wrapped in a further
@@ -294,9 +789,12 @@ function calleeName(callee) {
294
789
  // (`obj.Method()`), and Rust's `scoped_identifier` (`Type::method()`, `std::mem::swap()`) — no other
295
790
  // call-callee shape in this file's language tables binds a field called `name` for anything else,
296
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.
297
794
  // Verified against a real parse, not guessed.
298
795
  const property = callee.childForFieldName('property')
299
796
  ?? callee.childForFieldName('field')
797
+ ?? callee.childForFieldName('member')
300
798
  ?? callee.childForFieldName('attribute')
301
799
  ?? callee.childForFieldName('name');
302
800
  if (property !== null && property.type !== 'computed_property_name')
@@ -348,6 +846,42 @@ function commonJsRequireImport(node) {
348
846
  return { localName: '', importedName: '*', specifier };
349
847
  return undefined;
350
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
+ }
351
885
  /** ECMAScript-family import extraction: `import_statement` with a `import_clause` and a `source`. */
352
886
  function ecmascriptImports(node) {
353
887
  // `source` is required by the grammar; the null case only satisfies `childForFieldName`'s general
@@ -725,9 +1259,18 @@ function rustUseTarget(node, prefix) {
725
1259
  /* v8 ignore next */
726
1260
  if (list === null)
727
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 */
728
1267
  const newPrefix = path === null ? prefix : combineRustPrefix(prefix, path.text);
729
1268
  return namedChildren(list).flatMap(child => rustUseTarget(child, newPrefix));
730
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 */
731
1274
  if (node.type === 'use_list') {
732
1275
  return namedChildren(node).flatMap(child => rustUseTarget(child, prefix));
733
1276
  }
@@ -747,9 +1290,10 @@ function rustImports(node) {
747
1290
  }
748
1291
  /** Whether `node` is a bare name this package resolves heritage references against — a member
749
1292
  * expression (`ns.Base`) or a call (a mixin, `f(Base)`) is not, matching the "don't guess" precedent
750
- * `calleeName`/`ecmascriptImports` already follow for shapes they do not fully resolve. */
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. */
751
1295
  function isHeritageName(node) {
752
- return node.type === 'identifier' || node.type === 'type_identifier' || node.type === 'name';
1296
+ return node.type === 'identifier' || node.type === 'type_identifier' || node.type === 'name' || node.type === 'constant';
753
1297
  }
754
1298
  /**
755
1299
  * ECMAScript-family `extends`/`implements` extraction from a `class_declaration`'s `class_heritage`
@@ -923,6 +1467,25 @@ function phpHeritage(node, sourceKey) {
923
1467
  }
924
1468
  return refs;
925
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
+ }
926
1489
  /**
927
1490
  * Rust `trait_item` supertrait extraction from its `bounds` field (`trait Shape: Debug + Display {}`).
928
1491
  * Every supertrait requirement is reported as `extends`, matching `tsInterfaceHeritage`'s and
@@ -944,6 +1507,38 @@ function rustTraitHeritage(node, sourceKey) {
944
1507
  .filter(isHeritageName)
945
1508
  .map(target => ({ sourceKey, targetName: target.text, relation: 'extends' }));
946
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
+ }
947
1542
  /**
948
1543
  * Dispatch heritage extraction to the language family that owns a captured class, struct, or interface
949
1544
  * node's syntax. Go is absent: its interfaces are satisfied structurally, never declared at the
@@ -955,10 +1550,10 @@ function rustTraitHeritage(node, sourceKey) {
955
1550
  * @returns every heritage reference the definition declares.
956
1551
  */
957
1552
  function extractHeritage(node, kind, sourceKey, language) {
958
- // `kind === 'trait'` only ever comes from Rust's `trait_item` rule — no other language in
959
- // LANGUAGE_TABLE produces it.
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.
960
1555
  if (kind === 'trait')
961
- return rustTraitHeritage(node, sourceKey);
1556
+ return language === 'scala' ? scalaHeritage(node, sourceKey) : rustTraitHeritage(node, sourceKey);
962
1557
  // `kind === 'interface'` only ever comes from TYPESCRIPT_DEFINITIONS's, Java's, or C#'s
963
1558
  // `interface_declaration` rule — no other language in LANGUAGE_TABLE produces it.
964
1559
  if (kind === 'interface') {
@@ -968,13 +1563,21 @@ function extractHeritage(node, kind, sourceKey, language) {
968
1563
  return baseListHeritage(node, sourceKey, 'base_list');
969
1564
  if (language === 'php')
970
1565
  return phpHeritage(node, sourceKey);
1566
+ if (language === 'kotlin')
1567
+ return kotlinHeritage(node, sourceKey);
1568
+ if (language === 'swift')
1569
+ return swiftHeritage(node, sourceKey);
971
1570
  return tsInterfaceHeritage(node, sourceKey);
972
1571
  }
973
- // `kind === 'struct'` only ever comes from C/C++'s `struct_specifier`/`union_specifier` or C#'s
974
- // `struct_declaration` rule — a plain C struct/union has no base-list syntax at all, so
975
- // `baseListHeritage` simply finds nothing to report for it.
976
- if (kind === 'struct')
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);
977
1579
  return baseListHeritage(node, sourceKey, language === 'csharp' ? 'base_list' : 'base_class_clause');
1580
+ }
978
1581
  // `kind === 'enum'` only ever comes from PHP's `enum_declaration` rule today — no other language's
979
1582
  // enum rule reaches this dispatch (TypeScript's/Java's/C#'s enum kinds carry no heritage syntax of
980
1583
  // their own this package extracts elsewhere), so every other language reports nothing here.
@@ -993,6 +1596,16 @@ function extractHeritage(node, kind, sourceKey, language) {
993
1596
  return baseListHeritage(node, sourceKey, 'base_list');
994
1597
  if (language === 'php')
995
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);
996
1609
  // Otherwise only comes from ECMASCRIPT_DEFINITIONS's `class_declaration` rule — Go has no class
997
1610
  // concept, so this is never reached with `language === 'go'`.
998
1611
  return ecmascriptClassHeritage(node, sourceKey);
@@ -1030,7 +1643,11 @@ export function extractFile(tree, spec) {
1030
1643
  : rule.nameField === DECLARATOR_NAME_FIELD ? declaratorName(node)
1031
1644
  : rule.nameField === FIRST_CHILD_NAME_FIELD ? firstChildName(node)
1032
1645
  : rule.nameField === PHP_ELEMENT_NAME_FIELD ? phpElementName(node)
1033
- : node.childForFieldName(rule.nameField);
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);
1034
1651
  // A matched rule's node type is always the NAMED-declaration form the grammar mandates a name
1035
1652
  // for; the anonymous form (`function_expression`, `class` as an expression, both produced by an
1036
1653
  // anonymous default export) parses as a different node type this rule never matches.
@@ -1038,10 +1655,25 @@ export function extractFile(tree, spec) {
1038
1655
  if (nameNode !== null) {
1039
1656
  const key = `${node.startPosition.row}:${node.startPosition.column}`;
1040
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
+ }
1041
1673
  definitions.push({
1042
1674
  key,
1043
1675
  parentKey,
1044
- kind: rule.kind,
1676
+ kind,
1045
1677
  name: nameNode.text,
1046
1678
  container: [...containerNames],
1047
1679
  startLine: node.startPosition.row + 1,
@@ -1061,14 +1693,14 @@ export function extractFile(tree, spec) {
1061
1693
  : hasKeywordChild(node, 'static'),
1062
1694
  decorators: pythonDecorators(node, spec.language),
1063
1695
  });
1064
- heritage.push(...extractHeritage(node, rule.kind, key, spec.language));
1696
+ heritage.push(...extractHeritage(node, kind, key, spec.language));
1065
1697
  containerNames.push(nameNode.text);
1066
1698
  containerKeys.push(key);
1067
1699
  // A C/C++/C# `struct` is scoped exactly like a `class` for this purpose — a `scopeRestricted`
1068
1700
  // field rule must fire directly inside either body, not just a `class` one. Rust's `mod`
1069
1701
  // (`namespace`) is scoped like the module top level it nests, not like a function body — a
1070
1702
  // `scopeRestricted` `const`/`static` rule must still fire directly inside one.
1071
- scopeKinds.push(rule.kind === 'class' || rule.kind === 'struct' ? 'class' : rule.kind === 'namespace' ? 'module' : 'other');
1703
+ scopeKinds.push(kind === 'class' || kind === 'struct' ? 'class' : kind === 'namespace' ? 'module' : 'other');
1072
1704
  for (const child of namedChildren(node))
1073
1705
  visit(child);
1074
1706
  scopeKinds.pop();
@@ -1078,17 +1710,36 @@ export function extractFile(tree, spec) {
1078
1710
  }
1079
1711
  }
1080
1712
  if (spec.callTypes.includes(node.type)) {
1081
- // Every call-shaped node type in every language table entry requires its callee field; the null
1082
- // case only satisfies `childForFieldName`'s general return type.
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.
1083
1719
  const calleeField = spec.callFunctionFieldByType?.[node.type] ?? spec.callFunctionField;
1084
- const callee = node.childForFieldName(calleeField);
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);
1085
1729
  /* v8 ignore next */
1086
1730
  const name = callee === null ? undefined : calleeName(callee);
1087
1731
  if (name !== undefined) {
1088
1732
  // `callee` is non-null whenever `name` is: the optional chaining only satisfies the type
1089
1733
  // system's view of the field lookup above, not a real possibility here.
1090
1734
  /* v8 ignore next */
1091
- const isBareCallee = callee?.type === 'identifier' || callee?.type === 'name';
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');
1092
1743
  calls.push({
1093
1744
  callerKey: containerKeys[containerKeys.length - 1] ?? null,
1094
1745
  calleeName: name,
@@ -1102,9 +1753,13 @@ export function extractFile(tree, spec) {
1102
1753
  // LANGUAGE_TABLE binds either field, so both are a no-op for every other language. Without
1103
1754
  // `isBareCallee` also accepting PHP's `'name'` type alongside `'identifier'`, every PHP call —
1104
1755
  // including an ordinary global `function_call_expression` — would be misclassified as
1105
- // member-like, since PHP's bare-name node type is `name`, never `identifier`. Verified against
1106
- // a real parse, not guessed.
1107
- isMemberCall: !isBareCallee || node.childForFieldName('object') !== null || node.childForFieldName('scope') !== null,
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,
1108
1763
  });
1109
1764
  }
1110
1765
  }
@@ -1116,6 +1771,11 @@ export function extractFile(tree, spec) {
1116
1771
  if (requireImport !== undefined)
1117
1772
  imports.push(requireImport);
1118
1773
  }
1774
+ if (node.type === 'call' && spec.language === 'ruby') {
1775
+ const requireImport = rubyRequireImport(node);
1776
+ if (requireImport !== undefined)
1777
+ imports.push(requireImport);
1778
+ }
1119
1779
  // A callback or IIFE's function value is never itself captured as a definition (only a *named*
1120
1780
  // declaration, or one assigned through a captured `variable_declarator`, is) — but a `const`/`var`
1121
1781
  // in its body is still function-local, so its scope must flip to `'other'` here regardless.
@@ -1153,6 +1813,14 @@ function extractImports(node, language) {
1153
1813
  return phpImports(node);
1154
1814
  case 'rust':
1155
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);
1156
1824
  /* v8 ignore next 2 -- exhaustive over LANGUAGE_TABLE's current language labels; unreachable. */
1157
1825
  default:
1158
1826
  return [];
@@ -1251,6 +1919,43 @@ function isExported(node, language, name, commonJsExports) {
1251
1919
  }
1252
1920
  if (language === 'python')
1253
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');
1254
1959
  // A PHP top-level function/class/interface/trait/enum carries no visibility keyword of its own — the
1255
1960
  // language has no export construct for them, matching Python's precedent, and this reports `false`
1256
1961
  // for all of them since `phpHasVisibility` finds no `visibility_modifier` to check. A class/enum