@optave/codegraph 2.6.0 → 3.0.1

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.
@@ -1,5 +1,76 @@
1
1
  import { findChild, nodeEndLine } from './helpers.js';
2
2
 
3
+ function extractPhpParameters(fnNode) {
4
+ const params = [];
5
+ const paramsNode =
6
+ fnNode.childForFieldName('parameters') || findChild(fnNode, 'formal_parameters');
7
+ if (!paramsNode) return params;
8
+ for (let i = 0; i < paramsNode.childCount; i++) {
9
+ const param = paramsNode.child(i);
10
+ if (!param) continue;
11
+ if (param.type === 'simple_parameter' || param.type === 'variadic_parameter') {
12
+ const nameNode = param.childForFieldName('name') || findChild(param, 'variable_name');
13
+ if (nameNode) {
14
+ params.push({ name: nameNode.text, kind: 'parameter', line: param.startPosition.row + 1 });
15
+ }
16
+ }
17
+ }
18
+ return params;
19
+ }
20
+
21
+ function extractPhpClassChildren(classNode) {
22
+ const children = [];
23
+ const body = classNode.childForFieldName('body') || findChild(classNode, 'declaration_list');
24
+ if (!body) return children;
25
+ for (let i = 0; i < body.childCount; i++) {
26
+ const member = body.child(i);
27
+ if (!member) continue;
28
+ if (member.type === 'property_declaration') {
29
+ for (let j = 0; j < member.childCount; j++) {
30
+ const el = member.child(j);
31
+ if (!el || el.type !== 'property_element') continue;
32
+ const varNode = findChild(el, 'variable_name');
33
+ if (varNode) {
34
+ children.push({
35
+ name: varNode.text,
36
+ kind: 'property',
37
+ line: member.startPosition.row + 1,
38
+ });
39
+ }
40
+ }
41
+ } else if (member.type === 'const_declaration') {
42
+ for (let j = 0; j < member.childCount; j++) {
43
+ const el = member.child(j);
44
+ if (!el || el.type !== 'const_element') continue;
45
+ const nameNode = el.childForFieldName('name') || findChild(el, 'name');
46
+ if (nameNode) {
47
+ children.push({
48
+ name: nameNode.text,
49
+ kind: 'constant',
50
+ line: member.startPosition.row + 1,
51
+ });
52
+ }
53
+ }
54
+ }
55
+ }
56
+ return children;
57
+ }
58
+
59
+ function extractPhpEnumCases(enumNode) {
60
+ const children = [];
61
+ const body = enumNode.childForFieldName('body') || findChild(enumNode, 'enum_declaration_list');
62
+ if (!body) return children;
63
+ for (let i = 0; i < body.childCount; i++) {
64
+ const member = body.child(i);
65
+ if (!member || member.type !== 'enum_case') continue;
66
+ const nameNode = member.childForFieldName('name');
67
+ if (nameNode) {
68
+ children.push({ name: nameNode.text, kind: 'constant', line: member.startPosition.row + 1 });
69
+ }
70
+ }
71
+ return children;
72
+ }
73
+
3
74
  /**
4
75
  * Extract symbols from PHP files.
5
76
  */
@@ -31,11 +102,13 @@ export function extractPHPSymbols(tree, _filePath) {
31
102
  case 'function_definition': {
32
103
  const nameNode = node.childForFieldName('name');
33
104
  if (nameNode) {
105
+ const params = extractPhpParameters(node);
34
106
  definitions.push({
35
107
  name: nameNode.text,
36
108
  kind: 'function',
37
109
  line: node.startPosition.row + 1,
38
110
  endLine: nodeEndLine(node),
111
+ children: params.length > 0 ? params : undefined,
39
112
  });
40
113
  }
41
114
  break;
@@ -44,11 +117,13 @@ export function extractPHPSymbols(tree, _filePath) {
44
117
  case 'class_declaration': {
45
118
  const nameNode = node.childForFieldName('name');
46
119
  if (nameNode) {
120
+ const classChildren = extractPhpClassChildren(node);
47
121
  definitions.push({
48
122
  name: nameNode.text,
49
123
  kind: 'class',
50
124
  line: node.startPosition.row + 1,
51
125
  endLine: nodeEndLine(node),
126
+ children: classChildren.length > 0 ? classChildren : undefined,
52
127
  });
53
128
 
54
129
  // Check base clause (extends)
@@ -132,11 +207,13 @@ export function extractPHPSymbols(tree, _filePath) {
132
207
  case 'enum_declaration': {
133
208
  const nameNode = node.childForFieldName('name');
134
209
  if (nameNode) {
210
+ const enumChildren = extractPhpEnumCases(node);
135
211
  definitions.push({
136
212
  name: nameNode.text,
137
213
  kind: 'enum',
138
214
  line: node.startPosition.row + 1,
139
215
  endLine: nodeEndLine(node),
216
+ children: enumChildren.length > 0 ? enumChildren : undefined,
140
217
  });
141
218
  }
142
219
  break;
@@ -147,11 +224,13 @@ export function extractPHPSymbols(tree, _filePath) {
147
224
  if (nameNode) {
148
225
  const parentClass = findPHPParentClass(node);
149
226
  const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
227
+ const params = extractPhpParameters(node);
150
228
  definitions.push({
151
229
  name: fullName,
152
230
  kind: 'method',
153
231
  line: node.startPosition.row + 1,
154
232
  endLine: nodeEndLine(node),
233
+ children: params.length > 0 ? params : undefined,
155
234
  });
156
235
  }
157
236
  break;
@@ -22,12 +22,14 @@ export function extractPythonSymbols(tree, _filePath) {
22
22
  const parentClass = findPythonParentClass(node);
23
23
  const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
24
24
  const kind = parentClass ? 'method' : 'function';
25
+ const fnChildren = extractPythonParameters(node);
25
26
  definitions.push({
26
27
  name: fullName,
27
28
  kind,
28
29
  line: node.startPosition.row + 1,
29
30
  endLine: nodeEndLine(node),
30
31
  decorators,
32
+ children: fnChildren.length > 0 ? fnChildren : undefined,
31
33
  });
32
34
  }
33
35
  break;
@@ -36,11 +38,13 @@ export function extractPythonSymbols(tree, _filePath) {
36
38
  case 'class_definition': {
37
39
  const nameNode = node.childForFieldName('name');
38
40
  if (nameNode) {
41
+ const clsChildren = extractPythonClassProperties(node);
39
42
  definitions.push({
40
43
  name: nameNode.text,
41
44
  kind: 'class',
42
45
  line: node.startPosition.row + 1,
43
46
  endLine: nodeEndLine(node),
47
+ children: clsChildren.length > 0 ? clsChildren : undefined,
44
48
  });
45
49
  const superclasses =
46
50
  node.childForFieldName('superclasses') || findChild(node, 'argument_list');
@@ -108,6 +112,24 @@ export function extractPythonSymbols(tree, _filePath) {
108
112
  break;
109
113
  }
110
114
 
115
+ case 'expression_statement': {
116
+ // Module-level UPPER_CASE assignments → constants
117
+ if (node.parent && node.parent.type === 'module') {
118
+ const assignment = findChild(node, 'assignment');
119
+ if (assignment) {
120
+ const left = assignment.childForFieldName('left');
121
+ if (left && left.type === 'identifier' && /^[A-Z_][A-Z0-9_]*$/.test(left.text)) {
122
+ definitions.push({
123
+ name: left.text,
124
+ kind: 'constant',
125
+ line: node.startPosition.row + 1,
126
+ });
127
+ }
128
+ }
129
+ }
130
+ break;
131
+ }
132
+
111
133
  case 'import_from_statement': {
112
134
  let source = '';
113
135
  const names = [];
@@ -133,6 +155,118 @@ export function extractPythonSymbols(tree, _filePath) {
133
155
  for (let i = 0; i < node.childCount; i++) walkPythonNode(node.child(i));
134
156
  }
135
157
 
158
+ function extractPythonParameters(fnNode) {
159
+ const params = [];
160
+ const paramsNode = fnNode.childForFieldName('parameters') || findChild(fnNode, 'parameters');
161
+ if (!paramsNode) return params;
162
+ for (let i = 0; i < paramsNode.childCount; i++) {
163
+ const child = paramsNode.child(i);
164
+ if (!child) continue;
165
+ const t = child.type;
166
+ if (t === 'identifier') {
167
+ params.push({ name: child.text, kind: 'parameter', line: child.startPosition.row + 1 });
168
+ } else if (
169
+ t === 'typed_parameter' ||
170
+ t === 'default_parameter' ||
171
+ t === 'typed_default_parameter'
172
+ ) {
173
+ const nameNode = child.childForFieldName('name') || child.child(0);
174
+ if (nameNode && nameNode.type === 'identifier') {
175
+ params.push({
176
+ name: nameNode.text,
177
+ kind: 'parameter',
178
+ line: child.startPosition.row + 1,
179
+ });
180
+ }
181
+ } else if (t === 'list_splat_pattern' || t === 'dictionary_splat_pattern') {
182
+ // *args, **kwargs
183
+ for (let j = 0; j < child.childCount; j++) {
184
+ const inner = child.child(j);
185
+ if (inner && inner.type === 'identifier') {
186
+ params.push({ name: inner.text, kind: 'parameter', line: child.startPosition.row + 1 });
187
+ break;
188
+ }
189
+ }
190
+ }
191
+ }
192
+ return params;
193
+ }
194
+
195
+ function extractPythonClassProperties(classNode) {
196
+ const props = [];
197
+ const seen = new Set();
198
+ const body = classNode.childForFieldName('body') || findChild(classNode, 'block');
199
+ if (!body) return props;
200
+
201
+ for (let i = 0; i < body.childCount; i++) {
202
+ const child = body.child(i);
203
+ if (!child) continue;
204
+
205
+ // Direct class attribute assignments: x = 5
206
+ if (child.type === 'expression_statement') {
207
+ const assignment = findChild(child, 'assignment');
208
+ if (assignment) {
209
+ const left = assignment.childForFieldName('left');
210
+ if (left && left.type === 'identifier' && !seen.has(left.text)) {
211
+ seen.add(left.text);
212
+ props.push({ name: left.text, kind: 'property', line: child.startPosition.row + 1 });
213
+ }
214
+ }
215
+ }
216
+
217
+ // __init__ method: self.x = ... assignments
218
+ if (child.type === 'function_definition') {
219
+ const fnName = child.childForFieldName('name');
220
+ if (fnName && fnName.text === '__init__') {
221
+ const initBody = child.childForFieldName('body') || findChild(child, 'block');
222
+ if (initBody) {
223
+ walkInitBody(initBody, seen, props);
224
+ }
225
+ }
226
+ }
227
+
228
+ // decorated __init__
229
+ if (child.type === 'decorated_definition') {
230
+ for (let j = 0; j < child.childCount; j++) {
231
+ const inner = child.child(j);
232
+ if (inner && inner.type === 'function_definition') {
233
+ const fnName = inner.childForFieldName('name');
234
+ if (fnName && fnName.text === '__init__') {
235
+ const initBody = inner.childForFieldName('body') || findChild(inner, 'block');
236
+ if (initBody) {
237
+ walkInitBody(initBody, seen, props);
238
+ }
239
+ }
240
+ }
241
+ }
242
+ }
243
+ }
244
+ return props;
245
+ }
246
+
247
+ function walkInitBody(bodyNode, seen, props) {
248
+ for (let i = 0; i < bodyNode.childCount; i++) {
249
+ const stmt = bodyNode.child(i);
250
+ if (!stmt || stmt.type !== 'expression_statement') continue;
251
+ const assignment = findChild(stmt, 'assignment');
252
+ if (!assignment) continue;
253
+ const left = assignment.childForFieldName('left');
254
+ if (!left || left.type !== 'attribute') continue;
255
+ const obj = left.childForFieldName('object');
256
+ const attr = left.childForFieldName('attribute');
257
+ if (
258
+ obj &&
259
+ obj.text === 'self' &&
260
+ attr &&
261
+ attr.type === 'identifier' &&
262
+ !seen.has(attr.text)
263
+ ) {
264
+ seen.add(attr.text);
265
+ props.push({ name: attr.text, kind: 'property', line: stmt.startPosition.row + 1 });
266
+ }
267
+ }
268
+ }
269
+
136
270
  function findPythonParentClass(node) {
137
271
  let current = node.parent;
138
272
  while (current) {
@@ -31,11 +31,13 @@ export function extractRubySymbols(tree, _filePath) {
31
31
  case 'class': {
32
32
  const nameNode = node.childForFieldName('name');
33
33
  if (nameNode) {
34
+ const classChildren = extractRubyClassChildren(node);
34
35
  definitions.push({
35
36
  name: nameNode.text,
36
37
  kind: 'class',
37
38
  line: node.startPosition.row + 1,
38
39
  endLine: nodeEndLine(node),
40
+ children: classChildren.length > 0 ? classChildren : undefined,
39
41
  });
40
42
  const superclass = node.childForFieldName('superclass');
41
43
  if (superclass) {
@@ -73,11 +75,13 @@ export function extractRubySymbols(tree, _filePath) {
73
75
  case 'module': {
74
76
  const nameNode = node.childForFieldName('name');
75
77
  if (nameNode) {
78
+ const moduleChildren = extractRubyBodyConstants(node);
76
79
  definitions.push({
77
80
  name: nameNode.text,
78
81
  kind: 'module',
79
82
  line: node.startPosition.row + 1,
80
83
  endLine: nodeEndLine(node),
84
+ children: moduleChildren.length > 0 ? moduleChildren : undefined,
81
85
  });
82
86
  }
83
87
  break;
@@ -88,11 +92,13 @@ export function extractRubySymbols(tree, _filePath) {
88
92
  if (nameNode) {
89
93
  const parentClass = findRubyParentClass(node);
90
94
  const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
95
+ const params = extractRubyParameters(node);
91
96
  definitions.push({
92
97
  name: fullName,
93
98
  kind: 'method',
94
99
  line: node.startPosition.row + 1,
95
100
  endLine: nodeEndLine(node),
101
+ children: params.length > 0 ? params : undefined,
96
102
  });
97
103
  }
98
104
  break;
@@ -103,16 +109,34 @@ export function extractRubySymbols(tree, _filePath) {
103
109
  if (nameNode) {
104
110
  const parentClass = findRubyParentClass(node);
105
111
  const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
112
+ const params = extractRubyParameters(node);
106
113
  definitions.push({
107
114
  name: fullName,
108
115
  kind: 'function',
109
116
  line: node.startPosition.row + 1,
110
117
  endLine: nodeEndLine(node),
118
+ children: params.length > 0 ? params : undefined,
111
119
  });
112
120
  }
113
121
  break;
114
122
  }
115
123
 
124
+ case 'assignment': {
125
+ // Top-level constant assignments (parent is program)
126
+ if (node.parent && node.parent.type === 'program') {
127
+ const left = node.childForFieldName('left');
128
+ if (left && left.type === 'constant') {
129
+ definitions.push({
130
+ name: left.text,
131
+ kind: 'constant',
132
+ line: node.startPosition.row + 1,
133
+ endLine: nodeEndLine(node),
134
+ });
135
+ }
136
+ }
137
+ break;
138
+ }
139
+
116
140
  case 'call': {
117
141
  const methodNode = node.childForFieldName('method');
118
142
  if (methodNode) {
@@ -186,3 +210,68 @@ export function extractRubySymbols(tree, _filePath) {
186
210
  walkRubyNode(tree.rootNode);
187
211
  return { definitions, calls, imports, classes, exports };
188
212
  }
213
+
214
+ // ── Child extraction helpers ────────────────────────────────────────────────
215
+
216
+ const RUBY_PARAM_TYPES = new Set([
217
+ 'identifier',
218
+ 'optional_parameter',
219
+ 'splat_parameter',
220
+ 'hash_splat_parameter',
221
+ 'block_parameter',
222
+ 'keyword_parameter',
223
+ ]);
224
+
225
+ function extractRubyParameters(methodNode) {
226
+ const params = [];
227
+ const paramList =
228
+ methodNode.childForFieldName('parameters') || findChild(methodNode, 'method_parameters');
229
+ if (!paramList) return params;
230
+ for (let i = 0; i < paramList.childCount; i++) {
231
+ const param = paramList.child(i);
232
+ if (!param || !RUBY_PARAM_TYPES.has(param.type)) continue;
233
+ let name;
234
+ if (param.type === 'identifier') {
235
+ name = param.text;
236
+ } else {
237
+ // Compound parameter types have an identifier child for the name
238
+ const id = findChild(param, 'identifier');
239
+ name = id ? id.text : param.text;
240
+ }
241
+ params.push({ name, kind: 'parameter', line: param.startPosition.row + 1 });
242
+ }
243
+ return params;
244
+ }
245
+
246
+ function extractRubyBodyConstants(containerNode) {
247
+ const children = [];
248
+ const body = containerNode.childForFieldName('body') || findChild(containerNode, 'body');
249
+ if (!body) return children;
250
+ for (let i = 0; i < body.childCount; i++) {
251
+ const child = body.child(i);
252
+ if (!child || child.type !== 'assignment') continue;
253
+ const left = child.childForFieldName('left');
254
+ if (left && left.type === 'constant') {
255
+ children.push({ name: left.text, kind: 'constant', line: child.startPosition.row + 1 });
256
+ }
257
+ }
258
+ return children;
259
+ }
260
+
261
+ function extractRubyClassChildren(classNode) {
262
+ const children = [];
263
+ const body = classNode.childForFieldName('body') || findChild(classNode, 'body');
264
+ if (!body) return children;
265
+ for (let i = 0; i < body.childCount; i++) {
266
+ const child = body.child(i);
267
+ if (!child || child.type !== 'assignment') continue;
268
+ const left = child.childForFieldName('left');
269
+ if (!left) continue;
270
+ if (left.type === 'instance_variable') {
271
+ children.push({ name: left.text, kind: 'property', line: child.startPosition.row + 1 });
272
+ } else if (left.type === 'constant') {
273
+ children.push({ name: left.text, kind: 'constant', line: child.startPosition.row + 1 });
274
+ }
275
+ }
276
+ return children;
277
+ }
@@ -1,4 +1,4 @@
1
- import { nodeEndLine } from './helpers.js';
1
+ import { findChild, nodeEndLine } from './helpers.js';
2
2
 
3
3
  /**
4
4
  * Extract symbols from Rust files.
@@ -30,11 +30,13 @@ export function extractRustSymbols(tree, _filePath) {
30
30
  const implType = findCurrentImpl(node);
31
31
  const fullName = implType ? `${implType}.${nameNode.text}` : nameNode.text;
32
32
  const kind = implType ? 'method' : 'function';
33
+ const params = extractRustParameters(node.childForFieldName('parameters'));
33
34
  definitions.push({
34
35
  name: fullName,
35
36
  kind,
36
37
  line: node.startPosition.row + 1,
37
38
  endLine: nodeEndLine(node),
39
+ children: params.length > 0 ? params : undefined,
38
40
  });
39
41
  }
40
42
  break;
@@ -43,11 +45,13 @@ export function extractRustSymbols(tree, _filePath) {
43
45
  case 'struct_item': {
44
46
  const nameNode = node.childForFieldName('name');
45
47
  if (nameNode) {
48
+ const fields = extractStructFields(node);
46
49
  definitions.push({
47
50
  name: nameNode.text,
48
51
  kind: 'struct',
49
52
  line: node.startPosition.row + 1,
50
53
  endLine: nodeEndLine(node),
54
+ children: fields.length > 0 ? fields : undefined,
51
55
  });
52
56
  }
53
57
  break;
@@ -56,11 +60,26 @@ export function extractRustSymbols(tree, _filePath) {
56
60
  case 'enum_item': {
57
61
  const nameNode = node.childForFieldName('name');
58
62
  if (nameNode) {
63
+ const variants = extractEnumVariants(node);
59
64
  definitions.push({
60
65
  name: nameNode.text,
61
66
  kind: 'enum',
62
67
  line: node.startPosition.row + 1,
63
68
  endLine: nodeEndLine(node),
69
+ children: variants.length > 0 ? variants : undefined,
70
+ });
71
+ }
72
+ break;
73
+ }
74
+
75
+ case 'const_item': {
76
+ const nameNode = node.childForFieldName('name');
77
+ if (nameNode) {
78
+ definitions.push({
79
+ name: nameNode.text,
80
+ kind: 'constant',
81
+ line: node.startPosition.row + 1,
82
+ endLine: nodeEndLine(node),
64
83
  });
65
84
  }
66
85
  break;
@@ -170,6 +189,57 @@ export function extractRustSymbols(tree, _filePath) {
170
189
  return { definitions, calls, imports, classes, exports };
171
190
  }
172
191
 
192
+ // ── Child extraction helpers ────────────────────────────────────────────────
193
+
194
+ function extractRustParameters(paramListNode) {
195
+ const params = [];
196
+ if (!paramListNode) return params;
197
+ for (let i = 0; i < paramListNode.childCount; i++) {
198
+ const param = paramListNode.child(i);
199
+ if (!param) continue;
200
+ if (param.type === 'self_parameter') {
201
+ params.push({ name: 'self', kind: 'parameter', line: param.startPosition.row + 1 });
202
+ } else if (param.type === 'parameter') {
203
+ const pattern = param.childForFieldName('pattern');
204
+ if (pattern) {
205
+ params.push({ name: pattern.text, kind: 'parameter', line: param.startPosition.row + 1 });
206
+ }
207
+ }
208
+ }
209
+ return params;
210
+ }
211
+
212
+ function extractStructFields(structNode) {
213
+ const fields = [];
214
+ const fieldList =
215
+ structNode.childForFieldName('body') || findChild(structNode, 'field_declaration_list');
216
+ if (!fieldList) return fields;
217
+ for (let i = 0; i < fieldList.childCount; i++) {
218
+ const field = fieldList.child(i);
219
+ if (!field || field.type !== 'field_declaration') continue;
220
+ const nameNode = field.childForFieldName('name');
221
+ if (nameNode) {
222
+ fields.push({ name: nameNode.text, kind: 'property', line: field.startPosition.row + 1 });
223
+ }
224
+ }
225
+ return fields;
226
+ }
227
+
228
+ function extractEnumVariants(enumNode) {
229
+ const variants = [];
230
+ const body = enumNode.childForFieldName('body') || findChild(enumNode, 'enum_variant_list');
231
+ if (!body) return variants;
232
+ for (let i = 0; i < body.childCount; i++) {
233
+ const variant = body.child(i);
234
+ if (!variant || variant.type !== 'enum_variant') continue;
235
+ const nameNode = variant.childForFieldName('name');
236
+ if (nameNode) {
237
+ variants.push({ name: nameNode.text, kind: 'constant', line: variant.startPosition.row + 1 });
238
+ }
239
+ }
240
+ return variants;
241
+ }
242
+
173
243
  function extractRustUsePath(node) {
174
244
  if (!node) return [];
175
245
 
package/src/flow.js CHANGED
@@ -45,7 +45,10 @@ export function listEntryPointsData(dbPath, opts = {}) {
45
45
  .prepare(
46
46
  `SELECT n.name, n.kind, n.file, n.line, n.role
47
47
  FROM nodes n
48
- WHERE (${prefixConditions})
48
+ WHERE (
49
+ (${prefixConditions})
50
+ OR n.role = 'entry'
51
+ )
49
52
  AND n.kind NOT IN ('file', 'directory')
50
53
  ORDER BY n.name`,
51
54
  )
@@ -59,7 +62,7 @@ export function listEntryPointsData(dbPath, opts = {}) {
59
62
  file: r.file,
60
63
  line: r.line,
61
64
  role: r.role,
62
- type: entryPointType(r.name),
65
+ type: entryPointType(r.name) || (r.role === 'entry' ? 'exported' : null),
63
66
  }));
64
67
 
65
68
  const byType = {};