-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathexport.js
More file actions
150 lines (135 loc) · 4.66 KB
/
export.js
File metadata and controls
150 lines (135 loc) · 4.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import path from 'node:path';
/**
* Export the dependency graph in DOT (Graphviz) format.
*/
export function exportDOT(db, opts = {}) {
const fileLevel = opts.fileLevel !== false;
const lines = [
'digraph codegraph {',
' rankdir=LR;',
' node [shape=box, fontname="monospace", fontsize=10];',
' edge [color="#666666"];',
'',
];
if (fileLevel) {
const edges = db
.prepare(`
SELECT DISTINCT n1.file AS source, n2.file AS target
FROM edges e
JOIN nodes n1 ON e.source_id = n1.id
JOIN nodes n2 ON e.target_id = n2.id
WHERE n1.file != n2.file AND e.kind IN ('imports', 'imports-type', 'calls')
`)
.all();
const dirs = new Map();
const allFiles = new Set();
for (const { source, target } of edges) {
allFiles.add(source);
allFiles.add(target);
}
for (const file of allFiles) {
const dir = path.dirname(file) || '.';
if (!dirs.has(dir)) dirs.set(dir, []);
dirs.get(dir).push(file);
}
let clusterIdx = 0;
for (const [dir, files] of [...dirs].sort()) {
lines.push(` subgraph cluster_${clusterIdx++} {`);
lines.push(` label="${dir}";`);
lines.push(` style=dashed;`);
lines.push(` color="#999999";`);
for (const f of files) {
const label = path.basename(f);
lines.push(` "${f}" [label="${label}"];`);
}
lines.push(` }`);
lines.push('');
}
for (const { source, target } of edges) {
lines.push(` "${source}" -> "${target}";`);
}
} else {
const edges = db
.prepare(`
SELECT n1.name AS source_name, n1.kind AS source_kind, n1.file AS source_file,
n2.name AS target_name, n2.kind AS target_kind, n2.file AS target_file,
e.kind AS edge_kind
FROM edges e
JOIN nodes n1 ON e.source_id = n1.id
JOIN nodes n2 ON e.target_id = n2.id
WHERE n1.kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module') AND n2.kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module')
AND e.kind = 'calls'
`)
.all();
for (const e of edges) {
const sId = `${e.source_file}:${e.source_name}`.replace(/[^a-zA-Z0-9_]/g, '_');
const tId = `${e.target_file}:${e.target_name}`.replace(/[^a-zA-Z0-9_]/g, '_');
lines.push(` ${sId} [label="${e.source_name}\\n${path.basename(e.source_file)}"];`);
lines.push(` ${tId} [label="${e.target_name}\\n${path.basename(e.target_file)}"];`);
lines.push(` ${sId} -> ${tId};`);
}
}
lines.push('}');
return lines.join('\n');
}
/**
* Export the dependency graph in Mermaid format.
*/
export function exportMermaid(db, opts = {}) {
const fileLevel = opts.fileLevel !== false;
const lines = ['graph LR'];
if (fileLevel) {
const edges = db
.prepare(`
SELECT DISTINCT n1.file AS source, n2.file AS target
FROM edges e
JOIN nodes n1 ON e.source_id = n1.id
JOIN nodes n2 ON e.target_id = n2.id
WHERE n1.file != n2.file AND e.kind IN ('imports', 'imports-type', 'calls')
`)
.all();
for (const { source, target } of edges) {
const s = source.replace(/[^a-zA-Z0-9]/g, '_');
const t = target.replace(/[^a-zA-Z0-9]/g, '_');
lines.push(` ${s}["${source}"] --> ${t}["${target}"]`);
}
} else {
const edges = db
.prepare(`
SELECT n1.name AS source_name, n1.file AS source_file,
n2.name AS target_name, n2.file AS target_file
FROM edges e
JOIN nodes n1 ON e.source_id = n1.id
JOIN nodes n2 ON e.target_id = n2.id
WHERE n1.kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module') AND n2.kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module')
AND e.kind = 'calls'
`)
.all();
for (const e of edges) {
const sId = `${e.source_file}_${e.source_name}`.replace(/[^a-zA-Z0-9]/g, '_');
const tId = `${e.target_file}_${e.target_name}`.replace(/[^a-zA-Z0-9]/g, '_');
lines.push(` ${sId}["${e.source_name}"] --> ${tId}["${e.target_name}"]`);
}
}
return lines.join('\n');
}
/**
* Export as JSON adjacency list.
*/
export function exportJSON(db) {
const nodes = db
.prepare(`
SELECT id, name, kind, file, line FROM nodes WHERE kind = 'file'
`)
.all();
const edges = db
.prepare(`
SELECT DISTINCT n1.file AS source, n2.file AS target, e.kind
FROM edges e
JOIN nodes n1 ON e.source_id = n1.id
JOIN nodes n2 ON e.target_id = n2.id
WHERE n1.file != n2.file
`)
.all();
return { nodes, edges };
}