@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,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 Go files.
@@ -15,11 +15,13 @@ export function extractGoSymbols(tree, _filePath) {
15
15
  case 'function_declaration': {
16
16
  const nameNode = node.childForFieldName('name');
17
17
  if (nameNode) {
18
+ const params = extractGoParameters(node.childForFieldName('parameters'));
18
19
  definitions.push({
19
20
  name: nameNode.text,
20
21
  kind: 'function',
21
22
  line: node.startPosition.row + 1,
22
23
  endLine: nodeEndLine(node),
24
+ children: params.length > 0 ? params : undefined,
23
25
  });
24
26
  }
25
27
  break;
@@ -46,11 +48,13 @@ export function extractGoSymbols(tree, _filePath) {
46
48
  }
47
49
  }
48
50
  const fullName = receiverType ? `${receiverType}.${nameNode.text}` : nameNode.text;
51
+ const params = extractGoParameters(node.childForFieldName('parameters'));
49
52
  definitions.push({
50
53
  name: fullName,
51
54
  kind: 'method',
52
55
  line: node.startPosition.row + 1,
53
56
  endLine: nodeEndLine(node),
57
+ children: params.length > 0 ? params : undefined,
54
58
  });
55
59
  }
56
60
  break;
@@ -64,11 +68,13 @@ export function extractGoSymbols(tree, _filePath) {
64
68
  const typeNode = spec.childForFieldName('type');
65
69
  if (nameNode && typeNode) {
66
70
  if (typeNode.type === 'struct_type') {
71
+ const fields = extractStructFields(typeNode);
67
72
  definitions.push({
68
73
  name: nameNode.text,
69
74
  kind: 'struct',
70
75
  line: node.startPosition.row + 1,
71
76
  endLine: nodeEndLine(node),
77
+ children: fields.length > 0 ? fields : undefined,
72
78
  });
73
79
  } else if (typeNode.type === 'interface_type') {
74
80
  definitions.push({
@@ -145,6 +151,23 @@ export function extractGoSymbols(tree, _filePath) {
145
151
  break;
146
152
  }
147
153
 
154
+ case 'const_declaration': {
155
+ for (let i = 0; i < node.childCount; i++) {
156
+ const spec = node.child(i);
157
+ if (!spec || spec.type !== 'const_spec') continue;
158
+ const constName = spec.childForFieldName('name');
159
+ if (constName) {
160
+ definitions.push({
161
+ name: constName.text,
162
+ kind: 'constant',
163
+ line: spec.startPosition.row + 1,
164
+ endLine: spec.endPosition.row + 1,
165
+ });
166
+ }
167
+ }
168
+ break;
169
+ }
170
+
148
171
  case 'call_expression': {
149
172
  const fn = node.childForFieldName('function');
150
173
  if (fn) {
@@ -170,3 +193,45 @@ export function extractGoSymbols(tree, _filePath) {
170
193
  walkGoNode(tree.rootNode);
171
194
  return { definitions, calls, imports, classes, exports };
172
195
  }
196
+
197
+ // ── Child extraction helpers ────────────────────────────────────────────────
198
+
199
+ function extractGoParameters(paramListNode) {
200
+ const params = [];
201
+ if (!paramListNode) return params;
202
+ for (let i = 0; i < paramListNode.childCount; i++) {
203
+ const param = paramListNode.child(i);
204
+ if (!param || param.type !== 'parameter_declaration') continue;
205
+ // A parameter_declaration may have multiple identifiers (e.g., `a, b int`)
206
+ for (let j = 0; j < param.childCount; j++) {
207
+ const child = param.child(j);
208
+ if (child && child.type === 'identifier') {
209
+ params.push({ name: child.text, kind: 'parameter', line: child.startPosition.row + 1 });
210
+ }
211
+ }
212
+ }
213
+ return params;
214
+ }
215
+
216
+ function extractStructFields(structTypeNode) {
217
+ const fields = [];
218
+ const fieldList = findChild(structTypeNode, 'field_declaration_list');
219
+ if (!fieldList) return fields;
220
+ for (let i = 0; i < fieldList.childCount; i++) {
221
+ const field = fieldList.child(i);
222
+ if (!field || field.type !== 'field_declaration') continue;
223
+ const nameNode = field.childForFieldName('name');
224
+ if (nameNode) {
225
+ fields.push({ name: nameNode.text, kind: 'property', line: field.startPosition.row + 1 });
226
+ } else {
227
+ // Struct fields may have multiple names or use first identifier child
228
+ for (let j = 0; j < field.childCount; j++) {
229
+ const child = field.child(j);
230
+ if (child && child.type === 'field_identifier') {
231
+ fields.push({ name: child.text, kind: 'property', line: field.startPosition.row + 1 });
232
+ }
233
+ }
234
+ }
235
+ }
236
+ return fields;
237
+ }
@@ -36,11 +36,33 @@ export function extractHCLSymbols(tree, _filePath) {
36
36
  }
37
37
 
38
38
  if (name) {
39
+ // Extract attributes as property children for variable/output blocks
40
+ let blockChildren;
41
+ if (blockType === 'variable' || blockType === 'output') {
42
+ blockChildren = [];
43
+ const body = children.find((c) => c.type === 'body');
44
+ if (body) {
45
+ for (let j = 0; j < body.childCount; j++) {
46
+ const attr = body.child(j);
47
+ if (attr && attr.type === 'attribute') {
48
+ const key = attr.childForFieldName('key') || attr.child(0);
49
+ if (key) {
50
+ blockChildren.push({
51
+ name: key.text,
52
+ kind: 'property',
53
+ line: attr.startPosition.row + 1,
54
+ });
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
39
60
  definitions.push({
40
61
  name,
41
62
  kind: blockType,
42
63
  line: node.startPosition.row + 1,
43
64
  endLine: nodeEndLine(node),
65
+ children: blockChildren?.length > 0 ? blockChildren : undefined,
44
66
  });
45
67
  }
46
68
 
@@ -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 Java files.
@@ -31,11 +31,13 @@ export function extractJavaSymbols(tree, _filePath) {
31
31
  case 'class_declaration': {
32
32
  const nameNode = node.childForFieldName('name');
33
33
  if (nameNode) {
34
+ const classChildren = extractClassFields(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
 
41
43
  const superclass = node.childForFieldName('superclass');
@@ -139,11 +141,13 @@ export function extractJavaSymbols(tree, _filePath) {
139
141
  case 'enum_declaration': {
140
142
  const nameNode = node.childForFieldName('name');
141
143
  if (nameNode) {
144
+ const enumChildren = extractEnumConstants(node);
142
145
  definitions.push({
143
146
  name: nameNode.text,
144
147
  kind: 'enum',
145
148
  line: node.startPosition.row + 1,
146
149
  endLine: nodeEndLine(node),
150
+ children: enumChildren.length > 0 ? enumChildren : undefined,
147
151
  });
148
152
  }
149
153
  break;
@@ -154,11 +158,13 @@ export function extractJavaSymbols(tree, _filePath) {
154
158
  if (nameNode) {
155
159
  const parentClass = findJavaParentClass(node);
156
160
  const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
161
+ const params = extractJavaParameters(node.childForFieldName('parameters'));
157
162
  definitions.push({
158
163
  name: fullName,
159
164
  kind: 'method',
160
165
  line: node.startPosition.row + 1,
161
166
  endLine: nodeEndLine(node),
167
+ children: params.length > 0 ? params : undefined,
162
168
  });
163
169
  }
164
170
  break;
@@ -169,11 +175,13 @@ export function extractJavaSymbols(tree, _filePath) {
169
175
  if (nameNode) {
170
176
  const parentClass = findJavaParentClass(node);
171
177
  const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
178
+ const params = extractJavaParameters(node.childForFieldName('parameters'));
172
179
  definitions.push({
173
180
  name: fullName,
174
181
  kind: 'method',
175
182
  line: node.startPosition.row + 1,
176
183
  endLine: nodeEndLine(node),
184
+ children: params.length > 0 ? params : undefined,
177
185
  });
178
186
  }
179
187
  break;
@@ -228,3 +236,55 @@ export function extractJavaSymbols(tree, _filePath) {
228
236
  walkJavaNode(tree.rootNode);
229
237
  return { definitions, calls, imports, classes, exports };
230
238
  }
239
+
240
+ // ── Child extraction helpers ────────────────────────────────────────────────
241
+
242
+ function extractJavaParameters(paramListNode) {
243
+ const params = [];
244
+ if (!paramListNode) return params;
245
+ for (let i = 0; i < paramListNode.childCount; i++) {
246
+ const param = paramListNode.child(i);
247
+ if (!param) continue;
248
+ if (param.type === 'formal_parameter' || param.type === 'spread_parameter') {
249
+ const nameNode = param.childForFieldName('name');
250
+ if (nameNode) {
251
+ params.push({ name: nameNode.text, kind: 'parameter', line: param.startPosition.row + 1 });
252
+ }
253
+ }
254
+ }
255
+ return params;
256
+ }
257
+
258
+ function extractClassFields(classNode) {
259
+ const fields = [];
260
+ const body = classNode.childForFieldName('body') || findChild(classNode, 'class_body');
261
+ if (!body) return fields;
262
+ for (let i = 0; i < body.childCount; i++) {
263
+ const member = body.child(i);
264
+ if (!member || member.type !== 'field_declaration') continue;
265
+ for (let j = 0; j < member.childCount; j++) {
266
+ const child = member.child(j);
267
+ if (!child || child.type !== 'variable_declarator') continue;
268
+ const nameNode = child.childForFieldName('name');
269
+ if (nameNode) {
270
+ fields.push({ name: nameNode.text, kind: 'property', line: member.startPosition.row + 1 });
271
+ }
272
+ }
273
+ }
274
+ return fields;
275
+ }
276
+
277
+ function extractEnumConstants(enumNode) {
278
+ const constants = [];
279
+ const body = enumNode.childForFieldName('body') || findChild(enumNode, 'enum_body');
280
+ if (!body) return constants;
281
+ for (let i = 0; i < body.childCount; i++) {
282
+ const member = body.child(i);
283
+ if (!member || member.type !== 'enum_constant') continue;
284
+ const nameNode = member.childForFieldName('name');
285
+ if (nameNode) {
286
+ constants.push({ name: nameNode.text, kind: 'constant', line: member.startPosition.row + 1 });
287
+ }
288
+ }
289
+ return constants;
290
+ }
@@ -28,31 +28,37 @@ function extractSymbolsQuery(tree, query) {
28
28
 
29
29
  if (c.fn_node) {
30
30
  // function_declaration
31
+ const fnChildren = extractParameters(c.fn_node);
31
32
  definitions.push({
32
33
  name: c.fn_name.text,
33
34
  kind: 'function',
34
35
  line: c.fn_node.startPosition.row + 1,
35
36
  endLine: nodeEndLine(c.fn_node),
37
+ children: fnChildren.length > 0 ? fnChildren : undefined,
36
38
  });
37
39
  } else if (c.varfn_name) {
38
40
  // variable_declarator with arrow_function / function_expression
39
41
  const declNode = c.varfn_name.parent?.parent;
40
42
  const line = declNode ? declNode.startPosition.row + 1 : c.varfn_name.startPosition.row + 1;
43
+ const varFnChildren = extractParameters(c.varfn_value);
41
44
  definitions.push({
42
45
  name: c.varfn_name.text,
43
46
  kind: 'function',
44
47
  line,
45
48
  endLine: nodeEndLine(c.varfn_value),
49
+ children: varFnChildren.length > 0 ? varFnChildren : undefined,
46
50
  });
47
51
  } else if (c.cls_node) {
48
52
  // class_declaration
49
53
  const className = c.cls_name.text;
50
54
  const startLine = c.cls_node.startPosition.row + 1;
55
+ const clsChildren = extractClassProperties(c.cls_node);
51
56
  definitions.push({
52
57
  name: className,
53
58
  kind: 'class',
54
59
  line: startLine,
55
60
  endLine: nodeEndLine(c.cls_node),
61
+ children: clsChildren.length > 0 ? clsChildren : undefined,
56
62
  });
57
63
  const heritage =
58
64
  c.cls_node.childForFieldName('heritage') || findChild(c.cls_node, 'class_heritage');
@@ -69,11 +75,13 @@ function extractSymbolsQuery(tree, query) {
69
75
  const methName = c.meth_name.text;
70
76
  const parentClass = findParentClass(c.meth_node);
71
77
  const fullName = parentClass ? `${parentClass}.${methName}` : methName;
78
+ const methChildren = extractParameters(c.meth_node);
72
79
  definitions.push({
73
80
  name: fullName,
74
81
  kind: 'method',
75
82
  line: c.meth_node.startPosition.row + 1,
76
83
  endLine: nodeEndLine(c.meth_node),
84
+ children: methChildren.length > 0 ? methChildren : undefined,
77
85
  });
78
86
  } else if (c.iface_node) {
79
87
  // interface_declaration (TS/TSX only)
@@ -162,9 +170,60 @@ function extractSymbolsQuery(tree, query) {
162
170
  }
163
171
  }
164
172
 
173
+ // Extract top-level constants via targeted walk (query patterns don't cover these)
174
+ extractConstantsWalk(tree.rootNode, definitions);
175
+
165
176
  return { definitions, calls, imports, classes, exports: exps };
166
177
  }
167
178
 
179
+ /**
180
+ * Walk program-level children to extract `const x = <literal>` as constants.
181
+ * The query-based fast path has no pattern for lexical_declaration/variable_declaration,
182
+ * so constants are missed. This targeted walk fills that gap without a full tree traversal.
183
+ */
184
+ function extractConstantsWalk(rootNode, definitions) {
185
+ for (let i = 0; i < rootNode.childCount; i++) {
186
+ const node = rootNode.child(i);
187
+ if (!node) continue;
188
+
189
+ let declNode = node;
190
+ // Handle `export const …` — unwrap the export_statement to its declaration child
191
+ if (node.type === 'export_statement') {
192
+ const inner = node.childForFieldName('declaration');
193
+ if (!inner) continue;
194
+ declNode = inner;
195
+ }
196
+
197
+ const t = declNode.type;
198
+ if (t !== 'lexical_declaration' && t !== 'variable_declaration') continue;
199
+ if (!declNode.text.startsWith('const ')) continue;
200
+
201
+ for (let j = 0; j < declNode.childCount; j++) {
202
+ const declarator = declNode.child(j);
203
+ if (!declarator || declarator.type !== 'variable_declarator') continue;
204
+ const nameN = declarator.childForFieldName('name');
205
+ const valueN = declarator.childForFieldName('value');
206
+ if (!nameN || nameN.type !== 'identifier' || !valueN) continue;
207
+ // Skip functions — already captured by query patterns
208
+ const valType = valueN.type;
209
+ if (
210
+ valType === 'arrow_function' ||
211
+ valType === 'function_expression' ||
212
+ valType === 'function'
213
+ )
214
+ continue;
215
+ if (isConstantValue(valueN)) {
216
+ definitions.push({
217
+ name: nameN.text,
218
+ kind: 'constant',
219
+ line: declNode.startPosition.row + 1,
220
+ endLine: nodeEndLine(declNode),
221
+ });
222
+ }
223
+ }
224
+ }
225
+ }
226
+
168
227
  function handleCommonJSAssignment(left, right, node, imports) {
169
228
  if (!left || !right) return;
170
229
  const leftText = left.text;
@@ -231,11 +290,13 @@ function extractSymbolsWalk(tree) {
231
290
  case 'function_declaration': {
232
291
  const nameNode = node.childForFieldName('name');
233
292
  if (nameNode) {
293
+ const fnChildren = extractParameters(node);
234
294
  definitions.push({
235
295
  name: nameNode.text,
236
296
  kind: 'function',
237
297
  line: node.startPosition.row + 1,
238
298
  endLine: nodeEndLine(node),
299
+ children: fnChildren.length > 0 ? fnChildren : undefined,
239
300
  });
240
301
  }
241
302
  break;
@@ -246,11 +307,13 @@ function extractSymbolsWalk(tree) {
246
307
  if (nameNode) {
247
308
  const className = nameNode.text;
248
309
  const startLine = node.startPosition.row + 1;
310
+ const clsChildren = extractClassProperties(node);
249
311
  definitions.push({
250
312
  name: className,
251
313
  kind: 'class',
252
314
  line: startLine,
253
315
  endLine: nodeEndLine(node),
316
+ children: clsChildren.length > 0 ? clsChildren : undefined,
254
317
  });
255
318
  const heritage = node.childForFieldName('heritage') || findChild(node, 'class_heritage');
256
319
  if (heritage) {
@@ -272,11 +335,13 @@ function extractSymbolsWalk(tree) {
272
335
  if (nameNode) {
273
336
  const parentClass = findParentClass(node);
274
337
  const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
338
+ const methChildren = extractParameters(node);
275
339
  definitions.push({
276
340
  name: fullName,
277
341
  kind: 'method',
278
342
  line: node.startPosition.row + 1,
279
343
  endLine: nodeEndLine(node),
344
+ children: methChildren.length > 0 ? methChildren : undefined,
280
345
  });
281
346
  }
282
347
  break;
@@ -317,6 +382,7 @@ function extractSymbolsWalk(tree) {
317
382
 
318
383
  case 'lexical_declaration':
319
384
  case 'variable_declaration': {
385
+ const isConst = node.text.startsWith('const ');
320
386
  for (let i = 0; i < node.childCount; i++) {
321
387
  const declarator = node.child(i);
322
388
  if (declarator && declarator.type === 'variable_declarator') {
@@ -329,15 +395,59 @@ function extractSymbolsWalk(tree) {
329
395
  valType === 'function_expression' ||
330
396
  valType === 'function'
331
397
  ) {
398
+ const varFnChildren = extractParameters(valueN);
332
399
  definitions.push({
333
400
  name: nameN.text,
334
401
  kind: 'function',
335
402
  line: node.startPosition.row + 1,
336
403
  endLine: nodeEndLine(valueN),
404
+ children: varFnChildren.length > 0 ? varFnChildren : undefined,
337
405
  });
406
+ } else if (isConst && nameN.type === 'identifier' && isConstantValue(valueN)) {
407
+ definitions.push({
408
+ name: nameN.text,
409
+ kind: 'constant',
410
+ line: node.startPosition.row + 1,
411
+ endLine: nodeEndLine(node),
412
+ });
413
+ }
414
+ } else if (isConst && nameN && nameN.type === 'identifier' && !valueN) {
415
+ // const with no value (shouldn't happen but be safe)
416
+ }
417
+ }
418
+ }
419
+ break;
420
+ }
421
+
422
+ case 'enum_declaration': {
423
+ // TypeScript enum
424
+ const nameNode = node.childForFieldName('name');
425
+ if (nameNode) {
426
+ const enumChildren = [];
427
+ const body = node.childForFieldName('body') || findChild(node, 'enum_body');
428
+ if (body) {
429
+ for (let i = 0; i < body.childCount; i++) {
430
+ const member = body.child(i);
431
+ if (!member) continue;
432
+ if (member.type === 'enum_assignment' || member.type === 'property_identifier') {
433
+ const mName = member.childForFieldName('name') || member.child(0);
434
+ if (mName) {
435
+ enumChildren.push({
436
+ name: mName.text,
437
+ kind: 'constant',
438
+ line: member.startPosition.row + 1,
439
+ });
440
+ }
338
441
  }
339
442
  }
340
443
  }
444
+ definitions.push({
445
+ name: nameNode.text,
446
+ kind: 'enum',
447
+ line: node.startPosition.row + 1,
448
+ endLine: nodeEndLine(node),
449
+ children: enumChildren.length > 0 ? enumChildren : undefined,
450
+ });
341
451
  }
342
452
  break;
343
453
  }
@@ -471,6 +581,89 @@ function extractSymbolsWalk(tree) {
471
581
  return { definitions, calls, imports, classes, exports };
472
582
  }
473
583
 
584
+ // ── Child extraction helpers ────────────────────────────────────────────────
585
+
586
+ function extractParameters(node) {
587
+ const params = [];
588
+ const paramsNode = node.childForFieldName('parameters') || findChild(node, 'formal_parameters');
589
+ if (!paramsNode) return params;
590
+ for (let i = 0; i < paramsNode.childCount; i++) {
591
+ const child = paramsNode.child(i);
592
+ if (!child) continue;
593
+ const t = child.type;
594
+ if (t === 'identifier') {
595
+ params.push({ name: child.text, kind: 'parameter', line: child.startPosition.row + 1 });
596
+ } else if (
597
+ t === 'required_parameter' ||
598
+ t === 'optional_parameter' ||
599
+ t === 'assignment_pattern'
600
+ ) {
601
+ const nameNode =
602
+ child.childForFieldName('pattern') || child.childForFieldName('left') || child.child(0);
603
+ if (
604
+ nameNode &&
605
+ (nameNode.type === 'identifier' ||
606
+ nameNode.type === 'shorthand_property_identifier_pattern')
607
+ ) {
608
+ params.push({ name: nameNode.text, kind: 'parameter', line: child.startPosition.row + 1 });
609
+ }
610
+ } else if (t === 'rest_pattern' || t === 'rest_element') {
611
+ const nameNode = child.child(1) || child.childForFieldName('name');
612
+ if (nameNode && nameNode.type === 'identifier') {
613
+ params.push({ name: nameNode.text, kind: 'parameter', line: child.startPosition.row + 1 });
614
+ }
615
+ }
616
+ }
617
+ return params;
618
+ }
619
+
620
+ function extractClassProperties(classNode) {
621
+ const props = [];
622
+ const body = classNode.childForFieldName('body') || findChild(classNode, 'class_body');
623
+ if (!body) return props;
624
+ for (let i = 0; i < body.childCount; i++) {
625
+ const child = body.child(i);
626
+ if (!child) continue;
627
+ if (
628
+ child.type === 'field_definition' ||
629
+ child.type === 'public_field_definition' ||
630
+ child.type === 'property_definition'
631
+ ) {
632
+ const nameNode =
633
+ child.childForFieldName('name') || child.childForFieldName('property') || child.child(0);
634
+ if (
635
+ nameNode &&
636
+ (nameNode.type === 'property_identifier' ||
637
+ nameNode.type === 'identifier' ||
638
+ nameNode.type === 'private_property_identifier')
639
+ ) {
640
+ props.push({ name: nameNode.text, kind: 'property', line: child.startPosition.row + 1 });
641
+ }
642
+ }
643
+ }
644
+ return props;
645
+ }
646
+
647
+ function isConstantValue(valueNode) {
648
+ if (!valueNode) return false;
649
+ const t = valueNode.type;
650
+ return (
651
+ t === 'number' ||
652
+ t === 'string' ||
653
+ t === 'template_string' ||
654
+ t === 'true' ||
655
+ t === 'false' ||
656
+ t === 'null' ||
657
+ t === 'undefined' ||
658
+ t === 'array' ||
659
+ t === 'object' ||
660
+ t === 'regex' ||
661
+ t === 'unary_expression' ||
662
+ t === 'binary_expression' ||
663
+ t === 'new_expression'
664
+ );
665
+ }
666
+
474
667
  // ── Shared helpers ──────────────────────────────────────────────────────────
475
668
 
476
669
  function extractInterfaceMethods(bodyNode, interfaceName, definitions) {