@optave/codegraph 2.0.0 → 2.1.1-dev.00f091c
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 +74 -37
- package/package.json +10 -10
- package/src/builder.js +252 -38
- package/src/cli.js +44 -8
- package/src/config.js +1 -1
- package/src/db.js +4 -0
- package/src/embedder.js +3 -3
- package/src/extractors/csharp.js +248 -0
- package/src/extractors/go.js +172 -0
- package/src/extractors/hcl.js +73 -0
- package/src/extractors/helpers.js +10 -0
- package/src/extractors/index.js +9 -0
- package/src/extractors/java.js +230 -0
- package/src/extractors/javascript.js +414 -0
- package/src/extractors/php.js +243 -0
- package/src/extractors/python.js +150 -0
- package/src/extractors/ruby.js +188 -0
- package/src/extractors/rust.js +225 -0
- package/src/index.js +2 -0
- package/src/journal.js +109 -0
- package/src/mcp.js +47 -4
- package/src/parser.js +28 -1890
- package/src/queries.js +586 -4
- package/src/registry.js +24 -7
- package/src/resolve.js +4 -3
- package/src/watcher.js +25 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { findChild, nodeEndLine } from './helpers.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extract symbols from PHP files.
|
|
5
|
+
*/
|
|
6
|
+
export function extractPHPSymbols(tree, _filePath) {
|
|
7
|
+
const definitions = [];
|
|
8
|
+
const calls = [];
|
|
9
|
+
const imports = [];
|
|
10
|
+
const classes = [];
|
|
11
|
+
const exports = [];
|
|
12
|
+
|
|
13
|
+
function findPHPParentClass(node) {
|
|
14
|
+
let current = node.parent;
|
|
15
|
+
while (current) {
|
|
16
|
+
if (
|
|
17
|
+
current.type === 'class_declaration' ||
|
|
18
|
+
current.type === 'trait_declaration' ||
|
|
19
|
+
current.type === 'enum_declaration'
|
|
20
|
+
) {
|
|
21
|
+
const nameNode = current.childForFieldName('name');
|
|
22
|
+
return nameNode ? nameNode.text : null;
|
|
23
|
+
}
|
|
24
|
+
current = current.parent;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function walkPhpNode(node) {
|
|
30
|
+
switch (node.type) {
|
|
31
|
+
case 'function_definition': {
|
|
32
|
+
const nameNode = node.childForFieldName('name');
|
|
33
|
+
if (nameNode) {
|
|
34
|
+
definitions.push({
|
|
35
|
+
name: nameNode.text,
|
|
36
|
+
kind: 'function',
|
|
37
|
+
line: node.startPosition.row + 1,
|
|
38
|
+
endLine: nodeEndLine(node),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
case 'class_declaration': {
|
|
45
|
+
const nameNode = node.childForFieldName('name');
|
|
46
|
+
if (nameNode) {
|
|
47
|
+
definitions.push({
|
|
48
|
+
name: nameNode.text,
|
|
49
|
+
kind: 'class',
|
|
50
|
+
line: node.startPosition.row + 1,
|
|
51
|
+
endLine: nodeEndLine(node),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Check base clause (extends)
|
|
55
|
+
const baseClause =
|
|
56
|
+
node.childForFieldName('base_clause') || findChild(node, 'base_clause');
|
|
57
|
+
if (baseClause) {
|
|
58
|
+
for (let i = 0; i < baseClause.childCount; i++) {
|
|
59
|
+
const child = baseClause.child(i);
|
|
60
|
+
if (child && (child.type === 'name' || child.type === 'qualified_name')) {
|
|
61
|
+
classes.push({
|
|
62
|
+
name: nameNode.text,
|
|
63
|
+
extends: child.text,
|
|
64
|
+
line: node.startPosition.row + 1,
|
|
65
|
+
});
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Check class interface clause (implements)
|
|
72
|
+
const interfaceClause = findChild(node, 'class_interface_clause');
|
|
73
|
+
if (interfaceClause) {
|
|
74
|
+
for (let i = 0; i < interfaceClause.childCount; i++) {
|
|
75
|
+
const child = interfaceClause.child(i);
|
|
76
|
+
if (child && (child.type === 'name' || child.type === 'qualified_name')) {
|
|
77
|
+
classes.push({
|
|
78
|
+
name: nameNode.text,
|
|
79
|
+
implements: child.text,
|
|
80
|
+
line: node.startPosition.row + 1,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
case 'interface_declaration': {
|
|
90
|
+
const nameNode = node.childForFieldName('name');
|
|
91
|
+
if (nameNode) {
|
|
92
|
+
definitions.push({
|
|
93
|
+
name: nameNode.text,
|
|
94
|
+
kind: 'interface',
|
|
95
|
+
line: node.startPosition.row + 1,
|
|
96
|
+
endLine: nodeEndLine(node),
|
|
97
|
+
});
|
|
98
|
+
const body = node.childForFieldName('body');
|
|
99
|
+
if (body) {
|
|
100
|
+
for (let i = 0; i < body.childCount; i++) {
|
|
101
|
+
const child = body.child(i);
|
|
102
|
+
if (child && child.type === 'method_declaration') {
|
|
103
|
+
const methName = child.childForFieldName('name');
|
|
104
|
+
if (methName) {
|
|
105
|
+
definitions.push({
|
|
106
|
+
name: `${nameNode.text}.${methName.text}`,
|
|
107
|
+
kind: 'method',
|
|
108
|
+
line: child.startPosition.row + 1,
|
|
109
|
+
endLine: child.endPosition.row + 1,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
case 'trait_declaration': {
|
|
120
|
+
const nameNode = node.childForFieldName('name');
|
|
121
|
+
if (nameNode) {
|
|
122
|
+
definitions.push({
|
|
123
|
+
name: nameNode.text,
|
|
124
|
+
kind: 'trait',
|
|
125
|
+
line: node.startPosition.row + 1,
|
|
126
|
+
endLine: nodeEndLine(node),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
case 'enum_declaration': {
|
|
133
|
+
const nameNode = node.childForFieldName('name');
|
|
134
|
+
if (nameNode) {
|
|
135
|
+
definitions.push({
|
|
136
|
+
name: nameNode.text,
|
|
137
|
+
kind: 'enum',
|
|
138
|
+
line: node.startPosition.row + 1,
|
|
139
|
+
endLine: nodeEndLine(node),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
case 'method_declaration': {
|
|
146
|
+
const nameNode = node.childForFieldName('name');
|
|
147
|
+
if (nameNode) {
|
|
148
|
+
const parentClass = findPHPParentClass(node);
|
|
149
|
+
const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
|
|
150
|
+
definitions.push({
|
|
151
|
+
name: fullName,
|
|
152
|
+
kind: 'method',
|
|
153
|
+
line: node.startPosition.row + 1,
|
|
154
|
+
endLine: nodeEndLine(node),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
case 'namespace_use_declaration': {
|
|
161
|
+
// use App\Models\User;
|
|
162
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
163
|
+
const child = node.child(i);
|
|
164
|
+
if (child && child.type === 'namespace_use_clause') {
|
|
165
|
+
const nameNode = findChild(child, 'qualified_name') || findChild(child, 'name');
|
|
166
|
+
if (nameNode) {
|
|
167
|
+
const fullPath = nameNode.text;
|
|
168
|
+
const lastName = fullPath.split('\\').pop();
|
|
169
|
+
const alias = child.childForFieldName('alias');
|
|
170
|
+
imports.push({
|
|
171
|
+
source: fullPath,
|
|
172
|
+
names: [alias ? alias.text : lastName],
|
|
173
|
+
line: node.startPosition.row + 1,
|
|
174
|
+
phpUse: true,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Single use clause without wrapper
|
|
179
|
+
if (child && (child.type === 'qualified_name' || child.type === 'name')) {
|
|
180
|
+
const fullPath = child.text;
|
|
181
|
+
const lastName = fullPath.split('\\').pop();
|
|
182
|
+
imports.push({
|
|
183
|
+
source: fullPath,
|
|
184
|
+
names: [lastName],
|
|
185
|
+
line: node.startPosition.row + 1,
|
|
186
|
+
phpUse: true,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
case 'function_call_expression': {
|
|
194
|
+
const fn = node.childForFieldName('function') || node.child(0);
|
|
195
|
+
if (fn) {
|
|
196
|
+
if (fn.type === 'name' || fn.type === 'identifier') {
|
|
197
|
+
calls.push({ name: fn.text, line: node.startPosition.row + 1 });
|
|
198
|
+
} else if (fn.type === 'qualified_name') {
|
|
199
|
+
const parts = fn.text.split('\\');
|
|
200
|
+
calls.push({ name: parts[parts.length - 1], line: node.startPosition.row + 1 });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
case 'member_call_expression': {
|
|
207
|
+
const name = node.childForFieldName('name');
|
|
208
|
+
if (name) {
|
|
209
|
+
const obj = node.childForFieldName('object');
|
|
210
|
+
const call = { name: name.text, line: node.startPosition.row + 1 };
|
|
211
|
+
if (obj) call.receiver = obj.text;
|
|
212
|
+
calls.push(call);
|
|
213
|
+
}
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
case 'scoped_call_expression': {
|
|
218
|
+
const name = node.childForFieldName('name');
|
|
219
|
+
if (name) {
|
|
220
|
+
const scope = node.childForFieldName('scope');
|
|
221
|
+
const call = { name: name.text, line: node.startPosition.row + 1 };
|
|
222
|
+
if (scope) call.receiver = scope.text;
|
|
223
|
+
calls.push(call);
|
|
224
|
+
}
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
case 'object_creation_expression': {
|
|
229
|
+
const classNode = node.child(1); // skip 'new' keyword
|
|
230
|
+
if (classNode && (classNode.type === 'name' || classNode.type === 'qualified_name')) {
|
|
231
|
+
const parts = classNode.text.split('\\');
|
|
232
|
+
calls.push({ name: parts[parts.length - 1], line: node.startPosition.row + 1 });
|
|
233
|
+
}
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
for (let i = 0; i < node.childCount; i++) walkPhpNode(node.child(i));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
walkPhpNode(tree.rootNode);
|
|
242
|
+
return { definitions, calls, imports, classes, exports };
|
|
243
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { findChild, nodeEndLine } from './helpers.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extract symbols from Python files.
|
|
5
|
+
*/
|
|
6
|
+
export function extractPythonSymbols(tree, _filePath) {
|
|
7
|
+
const definitions = [];
|
|
8
|
+
const calls = [];
|
|
9
|
+
const imports = [];
|
|
10
|
+
const classes = [];
|
|
11
|
+
const exports = [];
|
|
12
|
+
|
|
13
|
+
function walkPythonNode(node) {
|
|
14
|
+
switch (node.type) {
|
|
15
|
+
case 'function_definition': {
|
|
16
|
+
const nameNode = node.childForFieldName('name');
|
|
17
|
+
if (nameNode) {
|
|
18
|
+
const decorators = [];
|
|
19
|
+
if (node.previousSibling && node.previousSibling.type === 'decorator') {
|
|
20
|
+
decorators.push(node.previousSibling.text);
|
|
21
|
+
}
|
|
22
|
+
const parentClass = findPythonParentClass(node);
|
|
23
|
+
const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
|
|
24
|
+
const kind = parentClass ? 'method' : 'function';
|
|
25
|
+
definitions.push({
|
|
26
|
+
name: fullName,
|
|
27
|
+
kind,
|
|
28
|
+
line: node.startPosition.row + 1,
|
|
29
|
+
endLine: nodeEndLine(node),
|
|
30
|
+
decorators,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
case 'class_definition': {
|
|
37
|
+
const nameNode = node.childForFieldName('name');
|
|
38
|
+
if (nameNode) {
|
|
39
|
+
definitions.push({
|
|
40
|
+
name: nameNode.text,
|
|
41
|
+
kind: 'class',
|
|
42
|
+
line: node.startPosition.row + 1,
|
|
43
|
+
endLine: nodeEndLine(node),
|
|
44
|
+
});
|
|
45
|
+
const superclasses =
|
|
46
|
+
node.childForFieldName('superclasses') || findChild(node, 'argument_list');
|
|
47
|
+
if (superclasses) {
|
|
48
|
+
for (let i = 0; i < superclasses.childCount; i++) {
|
|
49
|
+
const child = superclasses.child(i);
|
|
50
|
+
if (child && child.type === 'identifier') {
|
|
51
|
+
classes.push({
|
|
52
|
+
name: nameNode.text,
|
|
53
|
+
extends: child.text,
|
|
54
|
+
line: node.startPosition.row + 1,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
case 'decorated_definition': {
|
|
64
|
+
for (let i = 0; i < node.childCount; i++) walkPythonNode(node.child(i));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
case 'call': {
|
|
69
|
+
const fn = node.childForFieldName('function');
|
|
70
|
+
if (fn) {
|
|
71
|
+
let callName = null;
|
|
72
|
+
let receiver;
|
|
73
|
+
if (fn.type === 'identifier') callName = fn.text;
|
|
74
|
+
else if (fn.type === 'attribute') {
|
|
75
|
+
const attr = fn.childForFieldName('attribute');
|
|
76
|
+
if (attr) callName = attr.text;
|
|
77
|
+
const obj = fn.childForFieldName('object');
|
|
78
|
+
if (obj) receiver = obj.text;
|
|
79
|
+
}
|
|
80
|
+
if (callName) {
|
|
81
|
+
const call = { name: callName, line: node.startPosition.row + 1 };
|
|
82
|
+
if (receiver) call.receiver = receiver;
|
|
83
|
+
calls.push(call);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
case 'import_statement': {
|
|
90
|
+
const names = [];
|
|
91
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
92
|
+
const child = node.child(i);
|
|
93
|
+
if (child && (child.type === 'dotted_name' || child.type === 'aliased_import')) {
|
|
94
|
+
const name =
|
|
95
|
+
child.type === 'aliased_import'
|
|
96
|
+
? (child.childForFieldName('alias') || child.childForFieldName('name'))?.text
|
|
97
|
+
: child.text;
|
|
98
|
+
if (name) names.push(name);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (names.length > 0)
|
|
102
|
+
imports.push({
|
|
103
|
+
source: names[0],
|
|
104
|
+
names,
|
|
105
|
+
line: node.startPosition.row + 1,
|
|
106
|
+
pythonImport: true,
|
|
107
|
+
});
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
case 'import_from_statement': {
|
|
112
|
+
let source = '';
|
|
113
|
+
const names = [];
|
|
114
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
115
|
+
const child = node.child(i);
|
|
116
|
+
if (!child) continue;
|
|
117
|
+
if (child.type === 'dotted_name' || child.type === 'relative_import') {
|
|
118
|
+
if (!source) source = child.text;
|
|
119
|
+
else names.push(child.text);
|
|
120
|
+
}
|
|
121
|
+
if (child.type === 'aliased_import') {
|
|
122
|
+
const n = child.childForFieldName('name') || child.child(0);
|
|
123
|
+
if (n) names.push(n.text);
|
|
124
|
+
}
|
|
125
|
+
if (child.type === 'wildcard_import') names.push('*');
|
|
126
|
+
}
|
|
127
|
+
if (source)
|
|
128
|
+
imports.push({ source, names, line: node.startPosition.row + 1, pythonImport: true });
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (let i = 0; i < node.childCount; i++) walkPythonNode(node.child(i));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function findPythonParentClass(node) {
|
|
137
|
+
let current = node.parent;
|
|
138
|
+
while (current) {
|
|
139
|
+
if (current.type === 'class_definition') {
|
|
140
|
+
const nameNode = current.childForFieldName('name');
|
|
141
|
+
return nameNode ? nameNode.text : null;
|
|
142
|
+
}
|
|
143
|
+
current = current.parent;
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
walkPythonNode(tree.rootNode);
|
|
149
|
+
return { definitions, calls, imports, classes, exports };
|
|
150
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { findChild, nodeEndLine } from './helpers.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extract symbols from Ruby files.
|
|
5
|
+
*/
|
|
6
|
+
export function extractRubySymbols(tree, _filePath) {
|
|
7
|
+
const definitions = [];
|
|
8
|
+
const calls = [];
|
|
9
|
+
const imports = [];
|
|
10
|
+
const classes = [];
|
|
11
|
+
const exports = [];
|
|
12
|
+
|
|
13
|
+
function findRubyParentClass(node) {
|
|
14
|
+
let current = node.parent;
|
|
15
|
+
while (current) {
|
|
16
|
+
if (current.type === 'class') {
|
|
17
|
+
const nameNode = current.childForFieldName('name');
|
|
18
|
+
return nameNode ? nameNode.text : null;
|
|
19
|
+
}
|
|
20
|
+
if (current.type === 'module') {
|
|
21
|
+
const nameNode = current.childForFieldName('name');
|
|
22
|
+
return nameNode ? nameNode.text : null;
|
|
23
|
+
}
|
|
24
|
+
current = current.parent;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function walkRubyNode(node) {
|
|
30
|
+
switch (node.type) {
|
|
31
|
+
case 'class': {
|
|
32
|
+
const nameNode = node.childForFieldName('name');
|
|
33
|
+
if (nameNode) {
|
|
34
|
+
definitions.push({
|
|
35
|
+
name: nameNode.text,
|
|
36
|
+
kind: 'class',
|
|
37
|
+
line: node.startPosition.row + 1,
|
|
38
|
+
endLine: nodeEndLine(node),
|
|
39
|
+
});
|
|
40
|
+
const superclass = node.childForFieldName('superclass');
|
|
41
|
+
if (superclass) {
|
|
42
|
+
// superclass wraps the < token and class name
|
|
43
|
+
for (let i = 0; i < superclass.childCount; i++) {
|
|
44
|
+
const child = superclass.child(i);
|
|
45
|
+
if (child && (child.type === 'constant' || child.type === 'scope_resolution')) {
|
|
46
|
+
classes.push({
|
|
47
|
+
name: nameNode.text,
|
|
48
|
+
extends: child.text,
|
|
49
|
+
line: node.startPosition.row + 1,
|
|
50
|
+
});
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Direct superclass node may be a constant
|
|
55
|
+
if (superclass.type === 'superclass') {
|
|
56
|
+
for (let i = 0; i < superclass.childCount; i++) {
|
|
57
|
+
const child = superclass.child(i);
|
|
58
|
+
if (child && (child.type === 'constant' || child.type === 'scope_resolution')) {
|
|
59
|
+
classes.push({
|
|
60
|
+
name: nameNode.text,
|
|
61
|
+
extends: child.text,
|
|
62
|
+
line: node.startPosition.row + 1,
|
|
63
|
+
});
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
case 'module': {
|
|
74
|
+
const nameNode = node.childForFieldName('name');
|
|
75
|
+
if (nameNode) {
|
|
76
|
+
definitions.push({
|
|
77
|
+
name: nameNode.text,
|
|
78
|
+
kind: 'module',
|
|
79
|
+
line: node.startPosition.row + 1,
|
|
80
|
+
endLine: nodeEndLine(node),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
case 'method': {
|
|
87
|
+
const nameNode = node.childForFieldName('name');
|
|
88
|
+
if (nameNode) {
|
|
89
|
+
const parentClass = findRubyParentClass(node);
|
|
90
|
+
const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
|
|
91
|
+
definitions.push({
|
|
92
|
+
name: fullName,
|
|
93
|
+
kind: 'method',
|
|
94
|
+
line: node.startPosition.row + 1,
|
|
95
|
+
endLine: nodeEndLine(node),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
case 'singleton_method': {
|
|
102
|
+
const nameNode = node.childForFieldName('name');
|
|
103
|
+
if (nameNode) {
|
|
104
|
+
const parentClass = findRubyParentClass(node);
|
|
105
|
+
const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
|
|
106
|
+
definitions.push({
|
|
107
|
+
name: fullName,
|
|
108
|
+
kind: 'function',
|
|
109
|
+
line: node.startPosition.row + 1,
|
|
110
|
+
endLine: nodeEndLine(node),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
case 'call': {
|
|
117
|
+
const methodNode = node.childForFieldName('method');
|
|
118
|
+
if (methodNode) {
|
|
119
|
+
// Check for require/require_relative
|
|
120
|
+
if (methodNode.text === 'require' || methodNode.text === 'require_relative') {
|
|
121
|
+
const args = node.childForFieldName('arguments');
|
|
122
|
+
if (args) {
|
|
123
|
+
for (let i = 0; i < args.childCount; i++) {
|
|
124
|
+
const arg = args.child(i);
|
|
125
|
+
if (arg && (arg.type === 'string' || arg.type === 'string_content')) {
|
|
126
|
+
const strContent = arg.text.replace(/^['"]|['"]$/g, '');
|
|
127
|
+
imports.push({
|
|
128
|
+
source: strContent,
|
|
129
|
+
names: [strContent.split('/').pop()],
|
|
130
|
+
line: node.startPosition.row + 1,
|
|
131
|
+
rubyRequire: true,
|
|
132
|
+
});
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
// Look inside string for string_content
|
|
136
|
+
if (arg && arg.type === 'string') {
|
|
137
|
+
const content = findChild(arg, 'string_content');
|
|
138
|
+
if (content) {
|
|
139
|
+
imports.push({
|
|
140
|
+
source: content.text,
|
|
141
|
+
names: [content.text.split('/').pop()],
|
|
142
|
+
line: node.startPosition.row + 1,
|
|
143
|
+
rubyRequire: true,
|
|
144
|
+
});
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} else if (
|
|
151
|
+
methodNode.text === 'include' ||
|
|
152
|
+
methodNode.text === 'extend' ||
|
|
153
|
+
methodNode.text === 'prepend'
|
|
154
|
+
) {
|
|
155
|
+
// Module inclusion — treated like implements
|
|
156
|
+
const parentClass = findRubyParentClass(node);
|
|
157
|
+
if (parentClass) {
|
|
158
|
+
const args = node.childForFieldName('arguments');
|
|
159
|
+
if (args) {
|
|
160
|
+
for (let i = 0; i < args.childCount; i++) {
|
|
161
|
+
const arg = args.child(i);
|
|
162
|
+
if (arg && (arg.type === 'constant' || arg.type === 'scope_resolution')) {
|
|
163
|
+
classes.push({
|
|
164
|
+
name: parentClass,
|
|
165
|
+
implements: arg.text,
|
|
166
|
+
line: node.startPosition.row + 1,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
const recv = node.childForFieldName('receiver');
|
|
174
|
+
const call = { name: methodNode.text, line: node.startPosition.row + 1 };
|
|
175
|
+
if (recv) call.receiver = recv.text;
|
|
176
|
+
calls.push(call);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for (let i = 0; i < node.childCount; i++) walkRubyNode(node.child(i));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
walkRubyNode(tree.rootNode);
|
|
187
|
+
return { definitions, calls, imports, classes, exports };
|
|
188
|
+
}
|