@optave/codegraph 2.5.1 → 3.0.0
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 +216 -89
- package/package.json +8 -7
- package/src/ast.js +392 -0
- package/src/audit.js +423 -0
- package/src/batch.js +180 -0
- package/src/boundaries.js +346 -0
- package/src/builder.js +375 -92
- package/src/cfg.js +1451 -0
- package/src/change-journal.js +130 -0
- package/src/check.js +432 -0
- package/src/cli.js +734 -107
- package/src/cochange.js +5 -2
- package/src/communities.js +7 -1
- package/src/complexity.js +124 -17
- package/src/config.js +10 -0
- package/src/dataflow.js +1187 -0
- package/src/db.js +96 -0
- package/src/embedder.js +359 -47
- package/src/export.js +305 -0
- package/src/extractors/csharp.js +64 -1
- package/src/extractors/go.js +66 -1
- package/src/extractors/hcl.js +22 -0
- package/src/extractors/java.js +61 -1
- package/src/extractors/javascript.js +142 -0
- package/src/extractors/php.js +79 -0
- package/src/extractors/python.js +134 -0
- package/src/extractors/ruby.js +89 -0
- package/src/extractors/rust.js +71 -1
- package/src/flow.js +4 -4
- package/src/index.js +78 -3
- package/src/manifesto.js +69 -1
- package/src/mcp.js +702 -193
- package/src/owners.js +359 -0
- package/src/paginate.js +37 -2
- package/src/parser.js +8 -0
- package/src/queries.js +590 -50
- package/src/snapshot.js +149 -0
- package/src/structure.js +9 -3
- package/src/triage.js +273 -0
- package/src/viewer.js +948 -0
- package/src/watcher.js +36 -1
package/src/export.js
CHANGED
|
@@ -4,6 +4,25 @@ import { isTestFile } from './queries.js';
|
|
|
4
4
|
|
|
5
5
|
const DEFAULT_MIN_CONFIDENCE = 0.5;
|
|
6
6
|
|
|
7
|
+
/** Escape special XML characters. */
|
|
8
|
+
function escapeXml(s) {
|
|
9
|
+
return String(s)
|
|
10
|
+
.replace(/&/g, '&')
|
|
11
|
+
.replace(/</g, '<')
|
|
12
|
+
.replace(/>/g, '>')
|
|
13
|
+
.replace(/"/g, '"')
|
|
14
|
+
.replace(/'/g, ''');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** RFC 4180 CSV field escaping — quote fields containing commas, quotes, or newlines. */
|
|
18
|
+
function escapeCsv(s) {
|
|
19
|
+
const str = String(s);
|
|
20
|
+
if (str.includes(',') || str.includes('"') || str.includes('\n') || str.includes('\r')) {
|
|
21
|
+
return `"${str.replace(/"/g, '""')}"`;
|
|
22
|
+
}
|
|
23
|
+
return str;
|
|
24
|
+
}
|
|
25
|
+
|
|
7
26
|
/**
|
|
8
27
|
* Export the dependency graph in DOT (Graphviz) format.
|
|
9
28
|
*/
|
|
@@ -374,3 +393,289 @@ export function exportJSON(db, opts = {}) {
|
|
|
374
393
|
const base = { nodes, edges };
|
|
375
394
|
return paginateResult(base, 'edges', { limit: opts.limit, offset: opts.offset });
|
|
376
395
|
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Export the dependency graph in GraphML (XML) format.
|
|
399
|
+
*/
|
|
400
|
+
export function exportGraphML(db, opts = {}) {
|
|
401
|
+
const fileLevel = opts.fileLevel !== false;
|
|
402
|
+
const noTests = opts.noTests || false;
|
|
403
|
+
const minConf = opts.minConfidence ?? DEFAULT_MIN_CONFIDENCE;
|
|
404
|
+
const edgeLimit = opts.limit;
|
|
405
|
+
|
|
406
|
+
const lines = [
|
|
407
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
408
|
+
'<graphml xmlns="http://graphml.graphstruct.net/graphml">',
|
|
409
|
+
];
|
|
410
|
+
|
|
411
|
+
if (fileLevel) {
|
|
412
|
+
lines.push(' <key id="d0" for="node" attr.name="name" attr.type="string"/>');
|
|
413
|
+
lines.push(' <key id="d1" for="node" attr.name="file" attr.type="string"/>');
|
|
414
|
+
lines.push(' <key id="d2" for="edge" attr.name="kind" attr.type="string"/>');
|
|
415
|
+
lines.push(' <graph id="codegraph" edgedefault="directed">');
|
|
416
|
+
|
|
417
|
+
let edges = db
|
|
418
|
+
.prepare(`
|
|
419
|
+
SELECT DISTINCT n1.file AS source, n2.file AS target
|
|
420
|
+
FROM edges e
|
|
421
|
+
JOIN nodes n1 ON e.source_id = n1.id
|
|
422
|
+
JOIN nodes n2 ON e.target_id = n2.id
|
|
423
|
+
WHERE n1.file != n2.file AND e.kind IN ('imports', 'imports-type', 'calls')
|
|
424
|
+
AND e.confidence >= ?
|
|
425
|
+
`)
|
|
426
|
+
.all(minConf);
|
|
427
|
+
if (noTests) edges = edges.filter((e) => !isTestFile(e.source) && !isTestFile(e.target));
|
|
428
|
+
if (edgeLimit && edges.length > edgeLimit) edges = edges.slice(0, edgeLimit);
|
|
429
|
+
|
|
430
|
+
const files = new Set();
|
|
431
|
+
for (const { source, target } of edges) {
|
|
432
|
+
files.add(source);
|
|
433
|
+
files.add(target);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const fileIds = new Map();
|
|
437
|
+
let nIdx = 0;
|
|
438
|
+
for (const f of files) {
|
|
439
|
+
const id = `n${nIdx++}`;
|
|
440
|
+
fileIds.set(f, id);
|
|
441
|
+
lines.push(` <node id="${id}">`);
|
|
442
|
+
lines.push(` <data key="d0">${escapeXml(path.basename(f))}</data>`);
|
|
443
|
+
lines.push(` <data key="d1">${escapeXml(f)}</data>`);
|
|
444
|
+
lines.push(' </node>');
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
let eIdx = 0;
|
|
448
|
+
for (const { source, target } of edges) {
|
|
449
|
+
lines.push(
|
|
450
|
+
` <edge id="e${eIdx++}" source="${fileIds.get(source)}" target="${fileIds.get(target)}">`,
|
|
451
|
+
);
|
|
452
|
+
lines.push(' <data key="d2">imports</data>');
|
|
453
|
+
lines.push(' </edge>');
|
|
454
|
+
}
|
|
455
|
+
} else {
|
|
456
|
+
lines.push(' <key id="d0" for="node" attr.name="name" attr.type="string"/>');
|
|
457
|
+
lines.push(' <key id="d1" for="node" attr.name="kind" attr.type="string"/>');
|
|
458
|
+
lines.push(' <key id="d2" for="node" attr.name="file" attr.type="string"/>');
|
|
459
|
+
lines.push(' <key id="d3" for="node" attr.name="line" attr.type="int"/>');
|
|
460
|
+
lines.push(' <key id="d4" for="node" attr.name="role" attr.type="string"/>');
|
|
461
|
+
lines.push(' <key id="d5" for="edge" attr.name="kind" attr.type="string"/>');
|
|
462
|
+
lines.push(' <key id="d6" for="edge" attr.name="confidence" attr.type="double"/>');
|
|
463
|
+
lines.push(' <graph id="codegraph" edgedefault="directed">');
|
|
464
|
+
|
|
465
|
+
let edges = db
|
|
466
|
+
.prepare(`
|
|
467
|
+
SELECT n1.id AS source_id, n1.name AS source_name, n1.kind AS source_kind,
|
|
468
|
+
n1.file AS source_file, n1.line AS source_line, n1.role AS source_role,
|
|
469
|
+
n2.id AS target_id, n2.name AS target_name, n2.kind AS target_kind,
|
|
470
|
+
n2.file AS target_file, n2.line AS target_line, n2.role AS target_role,
|
|
471
|
+
e.kind AS edge_kind, e.confidence
|
|
472
|
+
FROM edges e
|
|
473
|
+
JOIN nodes n1 ON e.source_id = n1.id
|
|
474
|
+
JOIN nodes n2 ON e.target_id = n2.id
|
|
475
|
+
WHERE n1.kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module')
|
|
476
|
+
AND n2.kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module')
|
|
477
|
+
AND e.kind = 'calls'
|
|
478
|
+
AND e.confidence >= ?
|
|
479
|
+
`)
|
|
480
|
+
.all(minConf);
|
|
481
|
+
if (noTests)
|
|
482
|
+
edges = edges.filter((e) => !isTestFile(e.source_file) && !isTestFile(e.target_file));
|
|
483
|
+
if (edgeLimit && edges.length > edgeLimit) edges = edges.slice(0, edgeLimit);
|
|
484
|
+
|
|
485
|
+
const emittedNodes = new Set();
|
|
486
|
+
function emitNode(id, name, kind, file, line, role) {
|
|
487
|
+
if (emittedNodes.has(id)) return;
|
|
488
|
+
emittedNodes.add(id);
|
|
489
|
+
lines.push(` <node id="n${id}">`);
|
|
490
|
+
lines.push(` <data key="d0">${escapeXml(name)}</data>`);
|
|
491
|
+
lines.push(` <data key="d1">${escapeXml(kind)}</data>`);
|
|
492
|
+
lines.push(` <data key="d2">${escapeXml(file)}</data>`);
|
|
493
|
+
lines.push(` <data key="d3">${line}</data>`);
|
|
494
|
+
if (role) lines.push(` <data key="d4">${escapeXml(role)}</data>`);
|
|
495
|
+
lines.push(' </node>');
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
let eIdx = 0;
|
|
499
|
+
for (const e of edges) {
|
|
500
|
+
emitNode(
|
|
501
|
+
e.source_id,
|
|
502
|
+
e.source_name,
|
|
503
|
+
e.source_kind,
|
|
504
|
+
e.source_file,
|
|
505
|
+
e.source_line,
|
|
506
|
+
e.source_role,
|
|
507
|
+
);
|
|
508
|
+
emitNode(
|
|
509
|
+
e.target_id,
|
|
510
|
+
e.target_name,
|
|
511
|
+
e.target_kind,
|
|
512
|
+
e.target_file,
|
|
513
|
+
e.target_line,
|
|
514
|
+
e.target_role,
|
|
515
|
+
);
|
|
516
|
+
lines.push(` <edge id="e${eIdx++}" source="n${e.source_id}" target="n${e.target_id}">`);
|
|
517
|
+
lines.push(` <data key="d5">${escapeXml(e.edge_kind)}</data>`);
|
|
518
|
+
lines.push(` <data key="d6">${e.confidence}</data>`);
|
|
519
|
+
lines.push(' </edge>');
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
lines.push(' </graph>');
|
|
524
|
+
lines.push('</graphml>');
|
|
525
|
+
return lines.join('\n');
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Export the dependency graph in TinkerPop GraphSON v3 format.
|
|
530
|
+
*/
|
|
531
|
+
export function exportGraphSON(db, opts = {}) {
|
|
532
|
+
const noTests = opts.noTests || false;
|
|
533
|
+
const minConf = opts.minConfidence ?? DEFAULT_MIN_CONFIDENCE;
|
|
534
|
+
|
|
535
|
+
let nodes = db
|
|
536
|
+
.prepare(`
|
|
537
|
+
SELECT id, name, kind, file, line, role FROM nodes
|
|
538
|
+
WHERE kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module', 'file')
|
|
539
|
+
`)
|
|
540
|
+
.all();
|
|
541
|
+
if (noTests) nodes = nodes.filter((n) => !isTestFile(n.file));
|
|
542
|
+
|
|
543
|
+
let edges = db
|
|
544
|
+
.prepare(`
|
|
545
|
+
SELECT e.rowid AS id, n1.id AS outV, n2.id AS inV, e.kind, e.confidence
|
|
546
|
+
FROM edges e
|
|
547
|
+
JOIN nodes n1 ON e.source_id = n1.id
|
|
548
|
+
JOIN nodes n2 ON e.target_id = n2.id
|
|
549
|
+
WHERE e.confidence >= ?
|
|
550
|
+
`)
|
|
551
|
+
.all(minConf);
|
|
552
|
+
if (noTests) {
|
|
553
|
+
const nodeIds = new Set(nodes.map((n) => n.id));
|
|
554
|
+
edges = edges.filter((e) => nodeIds.has(e.outV) && nodeIds.has(e.inV));
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const vertices = nodes.map((n) => ({
|
|
558
|
+
id: n.id,
|
|
559
|
+
label: n.kind,
|
|
560
|
+
properties: {
|
|
561
|
+
name: [{ id: 0, value: n.name }],
|
|
562
|
+
file: [{ id: 0, value: n.file }],
|
|
563
|
+
...(n.line != null ? { line: [{ id: 0, value: n.line }] } : {}),
|
|
564
|
+
...(n.role ? { role: [{ id: 0, value: n.role }] } : {}),
|
|
565
|
+
},
|
|
566
|
+
}));
|
|
567
|
+
|
|
568
|
+
const gEdges = edges.map((e) => ({
|
|
569
|
+
id: e.id,
|
|
570
|
+
label: e.kind,
|
|
571
|
+
inV: e.inV,
|
|
572
|
+
outV: e.outV,
|
|
573
|
+
properties: {
|
|
574
|
+
confidence: e.confidence,
|
|
575
|
+
},
|
|
576
|
+
}));
|
|
577
|
+
|
|
578
|
+
const base = { vertices, edges: gEdges };
|
|
579
|
+
return paginateResult(base, 'edges', { limit: opts.limit, offset: opts.offset });
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Export the dependency graph as Neo4j bulk-import CSV files.
|
|
584
|
+
* Returns { nodes: string, relationships: string }.
|
|
585
|
+
*/
|
|
586
|
+
export function exportNeo4jCSV(db, opts = {}) {
|
|
587
|
+
const fileLevel = opts.fileLevel !== false;
|
|
588
|
+
const noTests = opts.noTests || false;
|
|
589
|
+
const minConf = opts.minConfidence ?? DEFAULT_MIN_CONFIDENCE;
|
|
590
|
+
const edgeLimit = opts.limit;
|
|
591
|
+
|
|
592
|
+
if (fileLevel) {
|
|
593
|
+
let edges = db
|
|
594
|
+
.prepare(`
|
|
595
|
+
SELECT DISTINCT n1.file AS source, n2.file AS target, e.kind, e.confidence
|
|
596
|
+
FROM edges e
|
|
597
|
+
JOIN nodes n1 ON e.source_id = n1.id
|
|
598
|
+
JOIN nodes n2 ON e.target_id = n2.id
|
|
599
|
+
WHERE n1.file != n2.file AND e.kind IN ('imports', 'imports-type', 'calls')
|
|
600
|
+
AND e.confidence >= ?
|
|
601
|
+
`)
|
|
602
|
+
.all(minConf);
|
|
603
|
+
if (noTests) edges = edges.filter((e) => !isTestFile(e.source) && !isTestFile(e.target));
|
|
604
|
+
if (edgeLimit && edges.length > edgeLimit) edges = edges.slice(0, edgeLimit);
|
|
605
|
+
|
|
606
|
+
const files = new Map();
|
|
607
|
+
let idx = 0;
|
|
608
|
+
for (const { source, target } of edges) {
|
|
609
|
+
if (!files.has(source)) files.set(source, idx++);
|
|
610
|
+
if (!files.has(target)) files.set(target, idx++);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const nodeLines = ['nodeId:ID,name,file:string,:LABEL'];
|
|
614
|
+
for (const [file, id] of files) {
|
|
615
|
+
nodeLines.push(`${id},${escapeCsv(path.basename(file))},${escapeCsv(file)},File`);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const relLines = [':START_ID,:END_ID,:TYPE,confidence:float'];
|
|
619
|
+
for (const e of edges) {
|
|
620
|
+
const edgeType = e.kind.toUpperCase().replace(/-/g, '_');
|
|
621
|
+
relLines.push(`${files.get(e.source)},${files.get(e.target)},${edgeType},${e.confidence}`);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
return { nodes: nodeLines.join('\n'), relationships: relLines.join('\n') };
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
let edges = db
|
|
628
|
+
.prepare(`
|
|
629
|
+
SELECT n1.id AS source_id, n1.name AS source_name, n1.kind AS source_kind,
|
|
630
|
+
n1.file AS source_file, n1.line AS source_line, n1.role AS source_role,
|
|
631
|
+
n2.id AS target_id, n2.name AS target_name, n2.kind AS target_kind,
|
|
632
|
+
n2.file AS target_file, n2.line AS target_line, n2.role AS target_role,
|
|
633
|
+
e.kind AS edge_kind, e.confidence
|
|
634
|
+
FROM edges e
|
|
635
|
+
JOIN nodes n1 ON e.source_id = n1.id
|
|
636
|
+
JOIN nodes n2 ON e.target_id = n2.id
|
|
637
|
+
WHERE n1.kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module')
|
|
638
|
+
AND n2.kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module')
|
|
639
|
+
AND e.kind = 'calls'
|
|
640
|
+
AND e.confidence >= ?
|
|
641
|
+
`)
|
|
642
|
+
.all(minConf);
|
|
643
|
+
if (noTests)
|
|
644
|
+
edges = edges.filter((e) => !isTestFile(e.source_file) && !isTestFile(e.target_file));
|
|
645
|
+
if (edgeLimit && edges.length > edgeLimit) edges = edges.slice(0, edgeLimit);
|
|
646
|
+
|
|
647
|
+
const emitted = new Set();
|
|
648
|
+
const nodeLines = ['nodeId:ID,name,kind,file:string,line:int,role,:LABEL'];
|
|
649
|
+
function emitNode(id, name, kind, file, line, role) {
|
|
650
|
+
if (emitted.has(id)) return;
|
|
651
|
+
emitted.add(id);
|
|
652
|
+
const label = kind.charAt(0).toUpperCase() + kind.slice(1);
|
|
653
|
+
nodeLines.push(
|
|
654
|
+
`${id},${escapeCsv(name)},${escapeCsv(kind)},${escapeCsv(file)},${line},${escapeCsv(role || '')},${label}`,
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const relLines = [':START_ID,:END_ID,:TYPE,confidence:float'];
|
|
659
|
+
for (const e of edges) {
|
|
660
|
+
emitNode(
|
|
661
|
+
e.source_id,
|
|
662
|
+
e.source_name,
|
|
663
|
+
e.source_kind,
|
|
664
|
+
e.source_file,
|
|
665
|
+
e.source_line,
|
|
666
|
+
e.source_role,
|
|
667
|
+
);
|
|
668
|
+
emitNode(
|
|
669
|
+
e.target_id,
|
|
670
|
+
e.target_name,
|
|
671
|
+
e.target_kind,
|
|
672
|
+
e.target_file,
|
|
673
|
+
e.target_line,
|
|
674
|
+
e.target_role,
|
|
675
|
+
);
|
|
676
|
+
const edgeType = e.edge_kind.toUpperCase().replace(/-/g, '_');
|
|
677
|
+
relLines.push(`${e.source_id},${e.target_id},${edgeType},${e.confidence}`);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
return { nodes: nodeLines.join('\n'), relationships: relLines.join('\n') };
|
|
681
|
+
}
|
package/src/extractors/csharp.js
CHANGED
|
@@ -33,11 +33,13 @@ export function extractCSharpSymbols(tree, _filePath) {
|
|
|
33
33
|
case 'class_declaration': {
|
|
34
34
|
const nameNode = node.childForFieldName('name');
|
|
35
35
|
if (nameNode) {
|
|
36
|
+
const classChildren = extractCSharpClassFields(node);
|
|
36
37
|
definitions.push({
|
|
37
38
|
name: nameNode.text,
|
|
38
39
|
kind: 'class',
|
|
39
40
|
line: node.startPosition.row + 1,
|
|
40
41
|
endLine: nodeEndLine(node),
|
|
42
|
+
children: classChildren.length > 0 ? classChildren : undefined,
|
|
41
43
|
});
|
|
42
44
|
extractCSharpBaseTypes(node, nameNode.text, classes);
|
|
43
45
|
}
|
|
@@ -47,11 +49,13 @@ export function extractCSharpSymbols(tree, _filePath) {
|
|
|
47
49
|
case 'struct_declaration': {
|
|
48
50
|
const nameNode = node.childForFieldName('name');
|
|
49
51
|
if (nameNode) {
|
|
52
|
+
const structChildren = extractCSharpClassFields(node);
|
|
50
53
|
definitions.push({
|
|
51
54
|
name: nameNode.text,
|
|
52
55
|
kind: 'struct',
|
|
53
56
|
line: node.startPosition.row + 1,
|
|
54
57
|
endLine: nodeEndLine(node),
|
|
58
|
+
children: structChildren.length > 0 ? structChildren : undefined,
|
|
55
59
|
});
|
|
56
60
|
extractCSharpBaseTypes(node, nameNode.text, classes);
|
|
57
61
|
}
|
|
@@ -105,11 +109,13 @@ export function extractCSharpSymbols(tree, _filePath) {
|
|
|
105
109
|
case 'enum_declaration': {
|
|
106
110
|
const nameNode = node.childForFieldName('name');
|
|
107
111
|
if (nameNode) {
|
|
112
|
+
const enumChildren = extractCSharpEnumMembers(node);
|
|
108
113
|
definitions.push({
|
|
109
114
|
name: nameNode.text,
|
|
110
115
|
kind: 'enum',
|
|
111
116
|
line: node.startPosition.row + 1,
|
|
112
117
|
endLine: nodeEndLine(node),
|
|
118
|
+
children: enumChildren.length > 0 ? enumChildren : undefined,
|
|
113
119
|
});
|
|
114
120
|
}
|
|
115
121
|
break;
|
|
@@ -120,11 +126,13 @@ export function extractCSharpSymbols(tree, _filePath) {
|
|
|
120
126
|
if (nameNode) {
|
|
121
127
|
const parentType = findCSharpParentType(node);
|
|
122
128
|
const fullName = parentType ? `${parentType}.${nameNode.text}` : nameNode.text;
|
|
129
|
+
const params = extractCSharpParameters(node.childForFieldName('parameters'));
|
|
123
130
|
definitions.push({
|
|
124
131
|
name: fullName,
|
|
125
132
|
kind: 'method',
|
|
126
133
|
line: node.startPosition.row + 1,
|
|
127
134
|
endLine: nodeEndLine(node),
|
|
135
|
+
children: params.length > 0 ? params : undefined,
|
|
128
136
|
});
|
|
129
137
|
}
|
|
130
138
|
break;
|
|
@@ -135,11 +143,13 @@ export function extractCSharpSymbols(tree, _filePath) {
|
|
|
135
143
|
if (nameNode) {
|
|
136
144
|
const parentType = findCSharpParentType(node);
|
|
137
145
|
const fullName = parentType ? `${parentType}.${nameNode.text}` : nameNode.text;
|
|
146
|
+
const params = extractCSharpParameters(node.childForFieldName('parameters'));
|
|
138
147
|
definitions.push({
|
|
139
148
|
name: fullName,
|
|
140
149
|
kind: 'method',
|
|
141
150
|
line: node.startPosition.row + 1,
|
|
142
151
|
endLine: nodeEndLine(node),
|
|
152
|
+
children: params.length > 0 ? params : undefined,
|
|
143
153
|
});
|
|
144
154
|
}
|
|
145
155
|
break;
|
|
@@ -152,7 +162,7 @@ export function extractCSharpSymbols(tree, _filePath) {
|
|
|
152
162
|
const fullName = parentType ? `${parentType}.${nameNode.text}` : nameNode.text;
|
|
153
163
|
definitions.push({
|
|
154
164
|
name: fullName,
|
|
155
|
-
kind: '
|
|
165
|
+
kind: 'property',
|
|
156
166
|
line: node.startPosition.row + 1,
|
|
157
167
|
endLine: nodeEndLine(node),
|
|
158
168
|
});
|
|
@@ -220,6 +230,59 @@ export function extractCSharpSymbols(tree, _filePath) {
|
|
|
220
230
|
return { definitions, calls, imports, classes, exports };
|
|
221
231
|
}
|
|
222
232
|
|
|
233
|
+
// ── Child extraction helpers ────────────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
function extractCSharpParameters(paramListNode) {
|
|
236
|
+
const params = [];
|
|
237
|
+
if (!paramListNode) return params;
|
|
238
|
+
for (let i = 0; i < paramListNode.childCount; i++) {
|
|
239
|
+
const param = paramListNode.child(i);
|
|
240
|
+
if (!param || param.type !== 'parameter') continue;
|
|
241
|
+
const nameNode = param.childForFieldName('name');
|
|
242
|
+
if (nameNode) {
|
|
243
|
+
params.push({ name: nameNode.text, kind: 'parameter', line: param.startPosition.row + 1 });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return params;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function extractCSharpClassFields(classNode) {
|
|
250
|
+
const fields = [];
|
|
251
|
+
const body = classNode.childForFieldName('body') || findChild(classNode, 'declaration_list');
|
|
252
|
+
if (!body) return fields;
|
|
253
|
+
for (let i = 0; i < body.childCount; i++) {
|
|
254
|
+
const member = body.child(i);
|
|
255
|
+
if (!member || member.type !== 'field_declaration') continue;
|
|
256
|
+
const varDecl = findChild(member, 'variable_declaration');
|
|
257
|
+
if (!varDecl) continue;
|
|
258
|
+
for (let j = 0; j < varDecl.childCount; j++) {
|
|
259
|
+
const child = varDecl.child(j);
|
|
260
|
+
if (!child || child.type !== 'variable_declarator') continue;
|
|
261
|
+
const nameNode = child.childForFieldName('name');
|
|
262
|
+
if (nameNode) {
|
|
263
|
+
fields.push({ name: nameNode.text, kind: 'property', line: member.startPosition.row + 1 });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return fields;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function extractCSharpEnumMembers(enumNode) {
|
|
271
|
+
const constants = [];
|
|
272
|
+
const body =
|
|
273
|
+
enumNode.childForFieldName('body') || findChild(enumNode, 'enum_member_declaration_list');
|
|
274
|
+
if (!body) return constants;
|
|
275
|
+
for (let i = 0; i < body.childCount; i++) {
|
|
276
|
+
const member = body.child(i);
|
|
277
|
+
if (!member || member.type !== 'enum_member_declaration') continue;
|
|
278
|
+
const nameNode = member.childForFieldName('name');
|
|
279
|
+
if (nameNode) {
|
|
280
|
+
constants.push({ name: nameNode.text, kind: 'constant', line: member.startPosition.row + 1 });
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return constants;
|
|
284
|
+
}
|
|
285
|
+
|
|
223
286
|
function extractCSharpBaseTypes(node, className, classes) {
|
|
224
287
|
const baseList = node.childForFieldName('bases');
|
|
225
288
|
if (!baseList) return;
|
package/src/extractors/go.js
CHANGED
|
@@ -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
|
+
}
|
package/src/extractors/hcl.js
CHANGED
|
@@ -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
|
|