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