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/README.md +1 -1
- package/lib/extract.js +727 -22
- package/lib/languages.d.ts +47 -0
- package/lib/languages.js +376 -0
- package/package.json +2 -2
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
|
-
:
|
|
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
|
|
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
|
|
974
|
-
// `struct_declaration
|
|
975
|
-
// `baseListHeritage` simply finds nothing to
|
|
976
|
-
|
|
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
|
-
:
|
|
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
|
|
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,
|
|
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(
|
|
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
|
-
//
|
|
1082
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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`.
|
|
1106
|
-
//
|
|
1107
|
-
|
|
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
|