@optave/codegraph 3.1.2 → 3.1.4
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 +19 -21
- package/package.json +10 -7
- package/src/analysis/context.js +408 -0
- package/src/analysis/dependencies.js +341 -0
- package/src/analysis/exports.js +130 -0
- package/src/analysis/impact.js +463 -0
- package/src/analysis/module-map.js +322 -0
- package/src/analysis/roles.js +45 -0
- package/src/analysis/symbol-lookup.js +232 -0
- package/src/ast-analysis/shared.js +5 -4
- package/src/batch.js +2 -1
- package/src/builder/context.js +85 -0
- package/src/builder/helpers.js +218 -0
- package/src/builder/incremental.js +178 -0
- package/src/builder/pipeline.js +130 -0
- package/src/builder/stages/build-edges.js +297 -0
- package/src/builder/stages/build-structure.js +113 -0
- package/src/builder/stages/collect-files.js +44 -0
- package/src/builder/stages/detect-changes.js +413 -0
- package/src/builder/stages/finalize.js +139 -0
- package/src/builder/stages/insert-nodes.js +195 -0
- package/src/builder/stages/parse-files.js +28 -0
- package/src/builder/stages/resolve-imports.js +143 -0
- package/src/builder/stages/run-analyses.js +44 -0
- package/src/builder.js +10 -1472
- package/src/cfg.js +1 -2
- package/src/cli/commands/ast.js +26 -0
- package/src/cli/commands/audit.js +46 -0
- package/src/cli/commands/batch.js +68 -0
- package/src/cli/commands/branch-compare.js +21 -0
- package/src/cli/commands/build.js +26 -0
- package/src/cli/commands/cfg.js +30 -0
- package/src/cli/commands/check.js +79 -0
- package/src/cli/commands/children.js +31 -0
- package/src/cli/commands/co-change.js +65 -0
- package/src/cli/commands/communities.js +23 -0
- package/src/cli/commands/complexity.js +45 -0
- package/src/cli/commands/context.js +34 -0
- package/src/cli/commands/cycles.js +28 -0
- package/src/cli/commands/dataflow.js +32 -0
- package/src/cli/commands/deps.js +16 -0
- package/src/cli/commands/diff-impact.js +30 -0
- package/src/cli/commands/embed.js +30 -0
- package/src/cli/commands/export.js +75 -0
- package/src/cli/commands/exports.js +18 -0
- package/src/cli/commands/flow.js +36 -0
- package/src/cli/commands/fn-impact.js +30 -0
- package/src/cli/commands/impact.js +16 -0
- package/src/cli/commands/info.js +76 -0
- package/src/cli/commands/map.js +19 -0
- package/src/cli/commands/mcp.js +18 -0
- package/src/cli/commands/models.js +19 -0
- package/src/cli/commands/owners.js +25 -0
- package/src/cli/commands/path.js +36 -0
- package/src/cli/commands/plot.js +80 -0
- package/src/cli/commands/query.js +49 -0
- package/src/cli/commands/registry.js +100 -0
- package/src/cli/commands/roles.js +34 -0
- package/src/cli/commands/search.js +42 -0
- package/src/cli/commands/sequence.js +32 -0
- package/src/cli/commands/snapshot.js +61 -0
- package/src/cli/commands/stats.js +15 -0
- package/src/cli/commands/structure.js +32 -0
- package/src/cli/commands/triage.js +78 -0
- package/src/cli/commands/watch.js +12 -0
- package/src/cli/commands/where.js +24 -0
- package/src/cli/index.js +118 -0
- package/src/cli/shared/options.js +39 -0
- package/src/cli/shared/output.js +1 -0
- package/src/cli.js +11 -1514
- package/src/commands/check.js +5 -5
- package/src/commands/manifesto.js +3 -3
- package/src/commands/structure.js +1 -1
- package/src/communities.js +15 -87
- package/src/complexity.js +1 -1
- package/src/cycles.js +30 -85
- package/src/dataflow.js +1 -2
- package/src/db/connection.js +4 -4
- package/src/db/migrations.js +41 -0
- package/src/db/query-builder.js +6 -5
- package/src/db/repository/base.js +201 -0
- package/src/db/repository/cached-stmt.js +19 -0
- package/src/db/repository/cfg.js +27 -38
- package/src/db/repository/cochange.js +16 -3
- package/src/db/repository/complexity.js +11 -6
- package/src/db/repository/dataflow.js +6 -1
- package/src/db/repository/edges.js +120 -98
- package/src/db/repository/embeddings.js +14 -3
- package/src/db/repository/graph-read.js +32 -9
- package/src/db/repository/in-memory-repository.js +584 -0
- package/src/db/repository/index.js +6 -1
- package/src/db/repository/nodes.js +110 -40
- package/src/db/repository/sqlite-repository.js +219 -0
- package/src/db.js +5 -0
- package/src/embeddings/generator.js +163 -0
- package/src/embeddings/index.js +13 -0
- package/src/embeddings/models.js +218 -0
- package/src/embeddings/search/cli-formatter.js +151 -0
- package/src/embeddings/search/filters.js +46 -0
- package/src/embeddings/search/hybrid.js +121 -0
- package/src/embeddings/search/keyword.js +68 -0
- package/src/embeddings/search/prepare.js +66 -0
- package/src/embeddings/search/semantic.js +145 -0
- package/src/embeddings/stores/fts5.js +27 -0
- package/src/embeddings/stores/sqlite-blob.js +24 -0
- package/src/embeddings/strategies/source.js +14 -0
- package/src/embeddings/strategies/structured.js +43 -0
- package/src/embeddings/strategies/text-utils.js +43 -0
- package/src/errors.js +78 -0
- package/src/export.js +217 -520
- package/src/extractors/csharp.js +10 -2
- package/src/extractors/go.js +3 -1
- package/src/extractors/helpers.js +71 -0
- package/src/extractors/java.js +9 -2
- package/src/extractors/javascript.js +38 -1
- package/src/extractors/php.js +3 -1
- package/src/extractors/python.js +14 -3
- package/src/extractors/rust.js +3 -1
- package/src/graph/algorithms/bfs.js +49 -0
- package/src/graph/algorithms/centrality.js +16 -0
- package/src/graph/algorithms/index.js +5 -0
- package/src/graph/algorithms/louvain.js +26 -0
- package/src/graph/algorithms/shortest-path.js +41 -0
- package/src/graph/algorithms/tarjan.js +49 -0
- package/src/graph/builders/dependency.js +91 -0
- package/src/graph/builders/index.js +3 -0
- package/src/graph/builders/structure.js +40 -0
- package/src/graph/builders/temporal.js +33 -0
- package/src/graph/classifiers/index.js +2 -0
- package/src/graph/classifiers/risk.js +85 -0
- package/src/graph/classifiers/roles.js +64 -0
- package/src/graph/index.js +13 -0
- package/src/graph/model.js +230 -0
- package/src/index.js +33 -204
- package/src/infrastructure/result-formatter.js +2 -21
- package/src/mcp/index.js +2 -0
- package/src/mcp/middleware.js +26 -0
- package/src/mcp/server.js +128 -0
- package/src/mcp/tool-registry.js +801 -0
- package/src/mcp/tools/ast-query.js +14 -0
- package/src/mcp/tools/audit.js +21 -0
- package/src/mcp/tools/batch-query.js +11 -0
- package/src/mcp/tools/branch-compare.js +10 -0
- package/src/mcp/tools/cfg.js +21 -0
- package/src/mcp/tools/check.js +43 -0
- package/src/mcp/tools/co-changes.js +20 -0
- package/src/mcp/tools/code-owners.js +12 -0
- package/src/mcp/tools/communities.js +15 -0
- package/src/mcp/tools/complexity.js +18 -0
- package/src/mcp/tools/context.js +17 -0
- package/src/mcp/tools/dataflow.js +26 -0
- package/src/mcp/tools/diff-impact.js +24 -0
- package/src/mcp/tools/execution-flow.js +26 -0
- package/src/mcp/tools/export-graph.js +57 -0
- package/src/mcp/tools/file-deps.js +12 -0
- package/src/mcp/tools/file-exports.js +13 -0
- package/src/mcp/tools/find-cycles.js +15 -0
- package/src/mcp/tools/fn-impact.js +15 -0
- package/src/mcp/tools/impact-analysis.js +12 -0
- package/src/mcp/tools/index.js +71 -0
- package/src/mcp/tools/list-functions.js +14 -0
- package/src/mcp/tools/list-repos.js +11 -0
- package/src/mcp/tools/module-map.js +6 -0
- package/src/mcp/tools/node-roles.js +14 -0
- package/src/mcp/tools/path.js +12 -0
- package/src/mcp/tools/query.js +30 -0
- package/src/mcp/tools/semantic-search.js +65 -0
- package/src/mcp/tools/sequence.js +17 -0
- package/src/mcp/tools/structure.js +15 -0
- package/src/mcp/tools/symbol-children.js +14 -0
- package/src/mcp/tools/triage.js +35 -0
- package/src/mcp/tools/where.js +13 -0
- package/src/mcp.js +2 -1470
- package/src/native.js +34 -10
- package/src/parser.js +53 -2
- package/src/presentation/colors.js +44 -0
- package/src/presentation/export.js +444 -0
- package/src/presentation/result-formatter.js +21 -0
- package/src/presentation/sequence-renderer.js +43 -0
- package/src/presentation/table.js +47 -0
- package/src/presentation/viewer.js +634 -0
- package/src/queries.js +35 -2276
- package/src/resolve.js +1 -1
- package/src/sequence.js +2 -38
- package/src/shared/file-utils.js +153 -0
- package/src/shared/generators.js +125 -0
- package/src/shared/hierarchy.js +27 -0
- package/src/shared/normalize.js +59 -0
- package/src/snapshot.js +6 -5
- package/src/structure.js +15 -40
- package/src/triage.js +20 -72
- package/src/viewer.js +35 -656
- package/src/watcher.js +8 -148
- package/src/embedder.js +0 -1097
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stage: insertNodes
|
|
3
|
+
*
|
|
4
|
+
* Batch-inserts file nodes, definitions, exports, children, and contains/parameter_of edges.
|
|
5
|
+
* Updates file hashes for incremental builds.
|
|
6
|
+
*/
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { performance } from 'node:perf_hooks';
|
|
9
|
+
import { bulkNodeIdsByFile } from '../../db.js';
|
|
10
|
+
import {
|
|
11
|
+
batchInsertEdges,
|
|
12
|
+
batchInsertNodes,
|
|
13
|
+
fileHash,
|
|
14
|
+
fileStat,
|
|
15
|
+
readFileSafe,
|
|
16
|
+
} from '../helpers.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {import('../context.js').PipelineContext} ctx
|
|
20
|
+
*/
|
|
21
|
+
export async function insertNodes(ctx) {
|
|
22
|
+
const { db, allSymbols, filesToParse, metadataUpdates, rootDir, removed } = ctx;
|
|
23
|
+
|
|
24
|
+
// Build lookup from incremental data (pre-computed hashes + stats)
|
|
25
|
+
const precomputedData = new Map();
|
|
26
|
+
for (const item of filesToParse) {
|
|
27
|
+
if (item.relPath) {
|
|
28
|
+
precomputedData.set(item.relPath, item);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const bulkGetNodeIds = { all: (file) => bulkNodeIdsByFile(db, file) };
|
|
33
|
+
|
|
34
|
+
// Prepare hash upsert
|
|
35
|
+
let upsertHash;
|
|
36
|
+
try {
|
|
37
|
+
upsertHash = db.prepare(
|
|
38
|
+
'INSERT OR REPLACE INTO file_hashes (file, hash, mtime, size) VALUES (?, ?, ?, ?)',
|
|
39
|
+
);
|
|
40
|
+
} catch {
|
|
41
|
+
upsertHash = null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Populate fileSymbols before the transaction so it is a pure input
|
|
45
|
+
// to (rather than a side-effect of) the DB write — avoids partial
|
|
46
|
+
// population if the transaction rolls back.
|
|
47
|
+
for (const [relPath, symbols] of allSymbols) {
|
|
48
|
+
ctx.fileSymbols.set(relPath, symbols);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const insertAll = db.transaction(() => {
|
|
52
|
+
// Phase 1: Batch insert all file nodes + definitions + exports
|
|
53
|
+
// Row format: [name, kind, file, line, end_line, parent_id, qualified_name, scope, visibility]
|
|
54
|
+
const phase1Rows = [];
|
|
55
|
+
for (const [relPath, symbols] of allSymbols) {
|
|
56
|
+
phase1Rows.push([relPath, 'file', relPath, 0, null, null, null, null, null]);
|
|
57
|
+
for (const def of symbols.definitions) {
|
|
58
|
+
// Methods already have 'Class.method' as name — use as qualified_name.
|
|
59
|
+
// For methods, scope is the class portion; for top-level defs, scope is null.
|
|
60
|
+
const dotIdx = def.name.lastIndexOf('.');
|
|
61
|
+
const scope = dotIdx !== -1 ? def.name.slice(0, dotIdx) : null;
|
|
62
|
+
phase1Rows.push([
|
|
63
|
+
def.name,
|
|
64
|
+
def.kind,
|
|
65
|
+
relPath,
|
|
66
|
+
def.line,
|
|
67
|
+
def.endLine || null,
|
|
68
|
+
null,
|
|
69
|
+
def.name,
|
|
70
|
+
scope,
|
|
71
|
+
def.visibility || null,
|
|
72
|
+
]);
|
|
73
|
+
}
|
|
74
|
+
for (const exp of symbols.exports) {
|
|
75
|
+
phase1Rows.push([exp.name, exp.kind, relPath, exp.line, null, null, exp.name, null, null]);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
batchInsertNodes(db, phase1Rows);
|
|
79
|
+
|
|
80
|
+
// Phase 1b: Mark exported symbols
|
|
81
|
+
const markExported = db.prepare(
|
|
82
|
+
'UPDATE nodes SET exported = 1 WHERE name = ? AND kind = ? AND file = ? AND line = ?',
|
|
83
|
+
);
|
|
84
|
+
for (const [relPath, symbols] of allSymbols) {
|
|
85
|
+
for (const exp of symbols.exports) {
|
|
86
|
+
markExported.run(exp.name, exp.kind, relPath, exp.line);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Phase 3: Batch insert children (needs parent IDs from Phase 2)
|
|
91
|
+
const childRows = [];
|
|
92
|
+
for (const [relPath, symbols] of allSymbols) {
|
|
93
|
+
const nodeIdMap = new Map();
|
|
94
|
+
for (const row of bulkGetNodeIds.all(relPath)) {
|
|
95
|
+
nodeIdMap.set(`${row.name}|${row.kind}|${row.line}`, row.id);
|
|
96
|
+
}
|
|
97
|
+
for (const def of symbols.definitions) {
|
|
98
|
+
if (!def.children?.length) continue;
|
|
99
|
+
const defId = nodeIdMap.get(`${def.name}|${def.kind}|${def.line}`);
|
|
100
|
+
if (!defId) continue;
|
|
101
|
+
for (const child of def.children) {
|
|
102
|
+
const qualifiedName = `${def.name}.${child.name}`;
|
|
103
|
+
childRows.push([
|
|
104
|
+
child.name,
|
|
105
|
+
child.kind,
|
|
106
|
+
relPath,
|
|
107
|
+
child.line,
|
|
108
|
+
child.endLine || null,
|
|
109
|
+
defId,
|
|
110
|
+
qualifiedName,
|
|
111
|
+
def.name,
|
|
112
|
+
child.visibility || null,
|
|
113
|
+
]);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
batchInsertNodes(db, childRows);
|
|
118
|
+
|
|
119
|
+
// Phase 5: Batch insert contains/parameter_of edges
|
|
120
|
+
const edgeRows = [];
|
|
121
|
+
for (const [relPath, symbols] of allSymbols) {
|
|
122
|
+
const nodeIdMap = new Map();
|
|
123
|
+
for (const row of bulkGetNodeIds.all(relPath)) {
|
|
124
|
+
nodeIdMap.set(`${row.name}|${row.kind}|${row.line}`, row.id);
|
|
125
|
+
}
|
|
126
|
+
const fileId = nodeIdMap.get(`${relPath}|file|0`);
|
|
127
|
+
for (const def of symbols.definitions) {
|
|
128
|
+
const defId = nodeIdMap.get(`${def.name}|${def.kind}|${def.line}`);
|
|
129
|
+
if (fileId && defId) {
|
|
130
|
+
edgeRows.push([fileId, defId, 'contains', 1.0, 0]);
|
|
131
|
+
}
|
|
132
|
+
if (def.children?.length && defId) {
|
|
133
|
+
for (const child of def.children) {
|
|
134
|
+
const childId = nodeIdMap.get(`${child.name}|${child.kind}|${child.line}`);
|
|
135
|
+
if (childId) {
|
|
136
|
+
edgeRows.push([defId, childId, 'contains', 1.0, 0]);
|
|
137
|
+
if (child.kind === 'parameter') {
|
|
138
|
+
edgeRows.push([childId, defId, 'parameter_of', 1.0, 0]);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Update file hash — skip reverse-dep files (unchanged)
|
|
146
|
+
if (upsertHash) {
|
|
147
|
+
const precomputed = precomputedData.get(relPath);
|
|
148
|
+
if (precomputed?._reverseDepOnly) {
|
|
149
|
+
// no-op: file unchanged, hash already correct
|
|
150
|
+
} else if (precomputed?.hash) {
|
|
151
|
+
const stat = precomputed.stat || fileStat(path.join(rootDir, relPath));
|
|
152
|
+
const mtime = stat ? Math.floor(stat.mtimeMs) : 0;
|
|
153
|
+
const size = stat ? stat.size : 0;
|
|
154
|
+
upsertHash.run(relPath, precomputed.hash, mtime, size);
|
|
155
|
+
} else {
|
|
156
|
+
const absPath = path.join(rootDir, relPath);
|
|
157
|
+
let code;
|
|
158
|
+
try {
|
|
159
|
+
code = readFileSafe(absPath);
|
|
160
|
+
} catch {
|
|
161
|
+
code = null;
|
|
162
|
+
}
|
|
163
|
+
if (code !== null) {
|
|
164
|
+
const stat = fileStat(absPath);
|
|
165
|
+
const mtime = stat ? Math.floor(stat.mtimeMs) : 0;
|
|
166
|
+
const size = stat ? stat.size : 0;
|
|
167
|
+
upsertHash.run(relPath, fileHash(code), mtime, size);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
batchInsertEdges(db, edgeRows);
|
|
173
|
+
|
|
174
|
+
// Also update metadata-only entries (self-heal mtime/size without re-parse)
|
|
175
|
+
if (upsertHash) {
|
|
176
|
+
for (const item of metadataUpdates) {
|
|
177
|
+
const mtime = item.stat ? Math.floor(item.stat.mtimeMs) : 0;
|
|
178
|
+
const size = item.stat ? item.stat.size : 0;
|
|
179
|
+
upsertHash.run(item.relPath, item.hash, mtime, size);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const t0 = performance.now();
|
|
185
|
+
insertAll();
|
|
186
|
+
ctx.timing.insertMs = performance.now() - t0;
|
|
187
|
+
|
|
188
|
+
// Clean up removed file hashes
|
|
189
|
+
if (upsertHash && removed.length > 0) {
|
|
190
|
+
const deleteHash = db.prepare('DELETE FROM file_hashes WHERE file = ?');
|
|
191
|
+
for (const relPath of removed) {
|
|
192
|
+
deleteHash.run(relPath);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stage: parseFiles
|
|
3
|
+
*
|
|
4
|
+
* Parses source files via parseFilesAuto (native or WASM engine).
|
|
5
|
+
* Populates ctx.allSymbols, ctx.fileSymbols, ctx.filesToParse.
|
|
6
|
+
*/
|
|
7
|
+
import { performance } from 'node:perf_hooks';
|
|
8
|
+
import { info } from '../../logger.js';
|
|
9
|
+
import { parseFilesAuto } from '../../parser.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {import('../context.js').PipelineContext} ctx
|
|
13
|
+
*/
|
|
14
|
+
export async function parseFiles(ctx) {
|
|
15
|
+
const { allFiles, parseChanges, isFullBuild, engineOpts, rootDir } = ctx;
|
|
16
|
+
|
|
17
|
+
ctx.filesToParse = isFullBuild ? allFiles.map((f) => ({ file: f })) : parseChanges;
|
|
18
|
+
ctx.fileSymbols = new Map();
|
|
19
|
+
|
|
20
|
+
const filePaths = ctx.filesToParse.map((item) => item.file);
|
|
21
|
+
const t0 = performance.now();
|
|
22
|
+
ctx.allSymbols = await parseFilesAuto(filePaths, rootDir, engineOpts);
|
|
23
|
+
ctx.timing.parseMs = performance.now() - t0;
|
|
24
|
+
|
|
25
|
+
const parsed = ctx.allSymbols.size;
|
|
26
|
+
const skipped = ctx.filesToParse.length - parsed;
|
|
27
|
+
info(`Parsed ${parsed} files (${skipped} skipped)`);
|
|
28
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stage: resolveImports
|
|
3
|
+
*
|
|
4
|
+
* Batch import resolution + barrel/re-export map construction.
|
|
5
|
+
* For incremental builds, loads unchanged barrel files for resolution.
|
|
6
|
+
*/
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { performance } from 'node:perf_hooks';
|
|
9
|
+
import { parseFilesAuto } from '../../parser.js';
|
|
10
|
+
import { resolveImportPath, resolveImportsBatch } from '../../resolve.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {import('../context.js').PipelineContext} ctx
|
|
14
|
+
*/
|
|
15
|
+
export async function resolveImports(ctx) {
|
|
16
|
+
const { db, fileSymbols, rootDir, aliases, allFiles, isFullBuild, engineOpts } = ctx;
|
|
17
|
+
|
|
18
|
+
// Collect all (fromFile, importSource) pairs and resolve in one native call
|
|
19
|
+
const t0 = performance.now();
|
|
20
|
+
const batchInputs = [];
|
|
21
|
+
for (const [relPath, symbols] of fileSymbols) {
|
|
22
|
+
const absFile = path.join(rootDir, relPath);
|
|
23
|
+
for (const imp of symbols.imports) {
|
|
24
|
+
batchInputs.push({ fromFile: absFile, importSource: imp.source });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
ctx.batchResolved = resolveImportsBatch(batchInputs, rootDir, aliases, allFiles);
|
|
28
|
+
ctx.timing.resolveMs = performance.now() - t0;
|
|
29
|
+
|
|
30
|
+
// Build re-export map for barrel resolution
|
|
31
|
+
ctx.reexportMap = new Map();
|
|
32
|
+
for (const [relPath, symbols] of fileSymbols) {
|
|
33
|
+
const reexports = symbols.imports.filter((imp) => imp.reexport);
|
|
34
|
+
if (reexports.length > 0) {
|
|
35
|
+
ctx.reexportMap.set(
|
|
36
|
+
relPath,
|
|
37
|
+
reexports.map((imp) => ({
|
|
38
|
+
source: getResolved(ctx, path.join(rootDir, relPath), imp.source),
|
|
39
|
+
names: imp.names,
|
|
40
|
+
wildcardReexport: imp.wildcardReexport || false,
|
|
41
|
+
})),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// For incremental builds, load unchanged barrel files into reexportMap
|
|
47
|
+
ctx.barrelOnlyFiles = new Set();
|
|
48
|
+
if (!isFullBuild) {
|
|
49
|
+
const barrelCandidates = db
|
|
50
|
+
.prepare(
|
|
51
|
+
`SELECT DISTINCT n1.file FROM edges e
|
|
52
|
+
JOIN nodes n1 ON e.source_id = n1.id
|
|
53
|
+
WHERE e.kind = 'reexports' AND n1.kind = 'file'`,
|
|
54
|
+
)
|
|
55
|
+
.all();
|
|
56
|
+
for (const { file: relPath } of barrelCandidates) {
|
|
57
|
+
if (fileSymbols.has(relPath)) continue;
|
|
58
|
+
const absPath = path.join(rootDir, relPath);
|
|
59
|
+
try {
|
|
60
|
+
const symbols = await parseFilesAuto([absPath], rootDir, engineOpts);
|
|
61
|
+
const fileSym = symbols.get(relPath);
|
|
62
|
+
if (fileSym) {
|
|
63
|
+
fileSymbols.set(relPath, fileSym);
|
|
64
|
+
ctx.barrelOnlyFiles.add(relPath);
|
|
65
|
+
const reexports = fileSym.imports.filter((imp) => imp.reexport);
|
|
66
|
+
if (reexports.length > 0) {
|
|
67
|
+
ctx.reexportMap.set(
|
|
68
|
+
relPath,
|
|
69
|
+
reexports.map((imp) => ({
|
|
70
|
+
source: getResolved(ctx, absPath, imp.source),
|
|
71
|
+
names: imp.names,
|
|
72
|
+
wildcardReexport: imp.wildcardReexport || false,
|
|
73
|
+
})),
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
/* skip if unreadable */
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Resolve an import source, preferring batch results.
|
|
86
|
+
* Exported so other stages (build-edges) can reuse it.
|
|
87
|
+
*/
|
|
88
|
+
export function getResolved(ctx, absFile, importSource) {
|
|
89
|
+
if (ctx.batchResolved) {
|
|
90
|
+
const key = `${absFile}|${importSource}`;
|
|
91
|
+
const hit = ctx.batchResolved.get(key);
|
|
92
|
+
if (hit !== undefined) return hit;
|
|
93
|
+
}
|
|
94
|
+
return resolveImportPath(absFile, importSource, ctx.rootDir, ctx.aliases);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check if a file is a barrel (re-export hub).
|
|
99
|
+
*/
|
|
100
|
+
export function isBarrelFile(ctx, relPath) {
|
|
101
|
+
const symbols = ctx.fileSymbols.get(relPath);
|
|
102
|
+
if (!symbols) return false;
|
|
103
|
+
const reexports = symbols.imports.filter((imp) => imp.reexport);
|
|
104
|
+
if (reexports.length === 0) return false;
|
|
105
|
+
const ownDefs = symbols.definitions.length;
|
|
106
|
+
return reexports.length >= ownDefs;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Resolve a symbol through barrel re-export chains.
|
|
111
|
+
*/
|
|
112
|
+
export function resolveBarrelExport(ctx, barrelPath, symbolName, visited = new Set()) {
|
|
113
|
+
if (visited.has(barrelPath)) return null;
|
|
114
|
+
visited.add(barrelPath);
|
|
115
|
+
const reexports = ctx.reexportMap.get(barrelPath);
|
|
116
|
+
if (!reexports) return null;
|
|
117
|
+
|
|
118
|
+
for (const re of reexports) {
|
|
119
|
+
if (re.names.length > 0 && !re.wildcardReexport) {
|
|
120
|
+
if (re.names.includes(symbolName)) {
|
|
121
|
+
const targetSymbols = ctx.fileSymbols.get(re.source);
|
|
122
|
+
if (targetSymbols) {
|
|
123
|
+
const hasDef = targetSymbols.definitions.some((d) => d.name === symbolName);
|
|
124
|
+
if (hasDef) return re.source;
|
|
125
|
+
const deeper = resolveBarrelExport(ctx, re.source, symbolName, visited);
|
|
126
|
+
if (deeper) return deeper;
|
|
127
|
+
}
|
|
128
|
+
return re.source;
|
|
129
|
+
}
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (re.wildcardReexport || re.names.length === 0) {
|
|
133
|
+
const targetSymbols = ctx.fileSymbols.get(re.source);
|
|
134
|
+
if (targetSymbols) {
|
|
135
|
+
const hasDef = targetSymbols.definitions.some((d) => d.name === symbolName);
|
|
136
|
+
if (hasDef) return re.source;
|
|
137
|
+
const deeper = resolveBarrelExport(ctx, re.source, symbolName, visited);
|
|
138
|
+
if (deeper) return deeper;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stage: runAnalyses
|
|
3
|
+
*
|
|
4
|
+
* Dispatches to the unified AST analysis engine (AST nodes, complexity, CFG, dataflow).
|
|
5
|
+
* Filters out reverse-dep files for incremental builds.
|
|
6
|
+
*/
|
|
7
|
+
import { debug, warn } from '../../logger.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {import('../context.js').PipelineContext} ctx
|
|
11
|
+
*/
|
|
12
|
+
export async function runAnalyses(ctx) {
|
|
13
|
+
const { db, allSymbols, rootDir, opts, engineOpts, isFullBuild, filesToParse } = ctx;
|
|
14
|
+
|
|
15
|
+
// For incremental builds, exclude reverse-dep-only files
|
|
16
|
+
let astComplexitySymbols = allSymbols;
|
|
17
|
+
if (!isFullBuild) {
|
|
18
|
+
const reverseDepFiles = new Set(
|
|
19
|
+
filesToParse.filter((item) => item._reverseDepOnly).map((item) => item.relPath),
|
|
20
|
+
);
|
|
21
|
+
if (reverseDepFiles.size > 0) {
|
|
22
|
+
astComplexitySymbols = new Map();
|
|
23
|
+
for (const [relPath, symbols] of allSymbols) {
|
|
24
|
+
if (!reverseDepFiles.has(relPath)) {
|
|
25
|
+
astComplexitySymbols.set(relPath, symbols);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
debug(
|
|
29
|
+
`AST/complexity/CFG/dataflow: processing ${astComplexitySymbols.size} changed files (skipping ${reverseDepFiles.size} reverse-deps)`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const { runAnalyses: runAnalysesFn } = await import('../../ast-analysis/engine.js');
|
|
35
|
+
try {
|
|
36
|
+
const analysisTiming = await runAnalysesFn(db, astComplexitySymbols, rootDir, opts, engineOpts);
|
|
37
|
+
ctx.timing.astMs = analysisTiming.astMs;
|
|
38
|
+
ctx.timing.complexityMs = analysisTiming.complexityMs;
|
|
39
|
+
ctx.timing.cfgMs = analysisTiming.cfgMs;
|
|
40
|
+
ctx.timing.dataflowMs = analysisTiming.dataflowMs;
|
|
41
|
+
} catch (err) {
|
|
42
|
+
warn(`Analysis engine failed (AST/complexity/CFG/dataflow may be incomplete): ${err.message}`);
|
|
43
|
+
}
|
|
44
|
+
}
|