Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -342,8 +342,12 @@ export function astQueryData(pattern, customDbPath, opts = {}) {
ORDER BY a.file, a.line
`;

const rows = db.prepare(sql).all(...params);
db.close();
let rows;
try {
rows = db.prepare(sql).all(...params);
} finally {
db.close();
}

const results = rows.map((r) => ({
kind: r.kind,
Expand Down
153 changes: 79 additions & 74 deletions src/branch-compare.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,55 +83,57 @@ function makeSymbolKey(kind, file, name) {

function loadSymbolsFromDb(dbPath, changedFiles, noTests) {
const db = new Database(dbPath, { readonly: true });
const symbols = new Map();
try {
const symbols = new Map();

if (changedFiles.length === 0) {
db.close();
return symbols;
}
if (changedFiles.length === 0) {
return symbols;
}

// Query nodes in changed files
const placeholders = changedFiles.map(() => '?').join(', ');
const rows = db
.prepare(
`SELECT n.id, n.name, n.kind, n.file, n.line, n.end_line
FROM nodes n
WHERE n.file IN (${placeholders})
AND n.kind NOT IN ('file', 'directory')
ORDER BY n.file, n.line`,
)
.all(...changedFiles);

// Compute fan_in and fan_out for each node
const fanInStmt = db.prepare(
`SELECT COUNT(*) AS cnt FROM edges WHERE target_id = ? AND kind = 'calls'`,
);
const fanOutStmt = db.prepare(
`SELECT COUNT(*) AS cnt FROM edges WHERE source_id = ? AND kind = 'calls'`,
);
// Query nodes in changed files
const placeholders = changedFiles.map(() => '?').join(', ');
const rows = db
.prepare(
`SELECT n.id, n.name, n.kind, n.file, n.line, n.end_line
FROM nodes n
WHERE n.file IN (${placeholders})
AND n.kind NOT IN ('file', 'directory')
ORDER BY n.file, n.line`,
)
.all(...changedFiles);

// Compute fan_in and fan_out for each node
const fanInStmt = db.prepare(
`SELECT COUNT(*) AS cnt FROM edges WHERE target_id = ? AND kind = 'calls'`,
);
const fanOutStmt = db.prepare(
`SELECT COUNT(*) AS cnt FROM edges WHERE source_id = ? AND kind = 'calls'`,
);

for (const row of rows) {
if (noTests && isTestFile(row.file)) continue;

const lineCount = row.end_line ? row.end_line - row.line + 1 : 0;
const fanIn = fanInStmt.get(row.id).cnt;
const fanOut = fanOutStmt.get(row.id).cnt;
const key = makeSymbolKey(row.kind, row.file, row.name);

symbols.set(key, {
id: row.id,
name: row.name,
kind: row.kind,
file: row.file,
line: row.line,
lineCount,
fanIn,
fanOut,
});
}
for (const row of rows) {
if (noTests && isTestFile(row.file)) continue;

const lineCount = row.end_line ? row.end_line - row.line + 1 : 0;
const fanIn = fanInStmt.get(row.id).cnt;
const fanOut = fanOutStmt.get(row.id).cnt;
const key = makeSymbolKey(row.kind, row.file, row.name);

symbols.set(key, {
id: row.id,
name: row.name,
kind: row.kind,
file: row.file,
line: row.line,
lineCount,
fanIn,
fanOut,
});
}

db.close();
return symbols;
return symbols;
} finally {
db.close();
}
}

// ─── Caller BFS ─────────────────────────────────────────────────────────
Expand All @@ -140,40 +142,43 @@ function loadCallersFromDb(dbPath, nodeIds, maxDepth, noTests) {
if (nodeIds.length === 0) return [];

const db = new Database(dbPath, { readonly: true });
const allCallers = new Set();

for (const startId of nodeIds) {
const visited = new Set([startId]);
let frontier = [startId];

for (let d = 1; d <= maxDepth; d++) {
const nextFrontier = [];
for (const fid of frontier) {
const callers = db
.prepare(
`SELECT DISTINCT n.id, n.name, n.kind, n.file, n.line
FROM edges e JOIN nodes n ON e.source_id = n.id
WHERE e.target_id = ? AND e.kind = 'calls'`,
)
.all(fid);

for (const c of callers) {
if (!visited.has(c.id) && (!noTests || !isTestFile(c.file))) {
visited.add(c.id);
nextFrontier.push(c.id);
allCallers.add(
JSON.stringify({ name: c.name, kind: c.kind, file: c.file, line: c.line }),
);
try {
const allCallers = new Set();

for (const startId of nodeIds) {
const visited = new Set([startId]);
let frontier = [startId];

for (let d = 1; d <= maxDepth; d++) {
const nextFrontier = [];
for (const fid of frontier) {
const callers = db
.prepare(
`SELECT DISTINCT n.id, n.name, n.kind, n.file, n.line
FROM edges e JOIN nodes n ON e.source_id = n.id
WHERE e.target_id = ? AND e.kind = 'calls'`,
)
.all(fid);

for (const c of callers) {
if (!visited.has(c.id) && (!noTests || !isTestFile(c.file))) {
visited.add(c.id);
nextFrontier.push(c.id);
allCallers.add(
JSON.stringify({ name: c.name, kind: c.kind, file: c.file, line: c.line }),
);
}
}
}
frontier = nextFrontier;
if (frontier.length === 0) break;
}
frontier = nextFrontier;
if (frontier.length === 0) break;
}
}

db.close();
return [...allCallers].map((s) => JSON.parse(s));
return [...allCallers].map((s) => JSON.parse(s));
} finally {
db.close();
}
}

// ─── Symbol Comparison ──────────────────────────────────────────────────
Expand Down
125 changes: 63 additions & 62 deletions src/cfg.js
Original file line number Diff line number Diff line change
Expand Up @@ -1029,72 +1029,73 @@ function findNodes(db, name, opts = {}) {
*/
export function cfgData(name, customDbPath, opts = {}) {
const db = openReadonlyOrFail(customDbPath);
const noTests = opts.noTests || false;

if (!hasCfgTables(db)) {
db.close();
return {
name,
results: [],
warning:
'No CFG data found. Rebuild with `codegraph build` (CFG is now included by default).',
};
}
try {
const noTests = opts.noTests || false;

if (!hasCfgTables(db)) {
return {
name,
results: [],
warning:
'No CFG data found. Rebuild with `codegraph build` (CFG is now included by default).',
};
}

const nodes = findNodes(db, name, { noTests, file: opts.file, kind: opts.kind });
if (nodes.length === 0) {
db.close();
return { name, results: [] };
}
const nodes = findNodes(db, name, { noTests, file: opts.file, kind: opts.kind });
if (nodes.length === 0) {
return { name, results: [] };
}

const blockStmt = db.prepare(
`SELECT id, block_index, block_type, start_line, end_line, label
FROM cfg_blocks WHERE function_node_id = ?
ORDER BY block_index`,
);
const edgeStmt = db.prepare(
`SELECT e.kind,
sb.block_index AS source_index, sb.block_type AS source_type,
tb.block_index AS target_index, tb.block_type AS target_type
FROM cfg_edges e
JOIN cfg_blocks sb ON e.source_block_id = sb.id
JOIN cfg_blocks tb ON e.target_block_id = tb.id
WHERE e.function_node_id = ?
ORDER BY sb.block_index, tb.block_index`,
);
const blockStmt = db.prepare(
`SELECT id, block_index, block_type, start_line, end_line, label
FROM cfg_blocks WHERE function_node_id = ?
ORDER BY block_index`,
);
const edgeStmt = db.prepare(
`SELECT e.kind,
sb.block_index AS source_index, sb.block_type AS source_type,
tb.block_index AS target_index, tb.block_type AS target_type
FROM cfg_edges e
JOIN cfg_blocks sb ON e.source_block_id = sb.id
JOIN cfg_blocks tb ON e.target_block_id = tb.id
WHERE e.function_node_id = ?
ORDER BY sb.block_index, tb.block_index`,
);

const results = nodes.map((node) => {
const cfgBlocks = blockStmt.all(node.id);
const cfgEdges = edgeStmt.all(node.id);

return {
name: node.name,
kind: node.kind,
file: node.file,
line: node.line,
blocks: cfgBlocks.map((b) => ({
index: b.block_index,
type: b.block_type,
startLine: b.start_line,
endLine: b.end_line,
label: b.label,
})),
edges: cfgEdges.map((e) => ({
source: e.source_index,
sourceType: e.source_type,
target: e.target_index,
targetType: e.target_type,
kind: e.kind,
})),
summary: {
blockCount: cfgBlocks.length,
edgeCount: cfgEdges.length,
},
};
});
const results = nodes.map((node) => {
const cfgBlocks = blockStmt.all(node.id);
const cfgEdges = edgeStmt.all(node.id);

return {
name: node.name,
kind: node.kind,
file: node.file,
line: node.line,
blocks: cfgBlocks.map((b) => ({
index: b.block_index,
type: b.block_type,
startLine: b.start_line,
endLine: b.end_line,
label: b.label,
})),
edges: cfgEdges.map((e) => ({
source: e.source_index,
sourceType: e.source_type,
target: e.target_index,
targetType: e.target_type,
kind: e.kind,
})),
summary: {
blockCount: cfgBlocks.length,
edgeCount: cfgEdges.length,
},
};
});

db.close();
return paginateResult({ name, results }, 'results', opts);
return paginateResult({ name, results }, 'results', opts);
} finally {
db.close();
}
}

// ─── Export Formats ─────────────────────────────────────────────────────
Expand Down
15 changes: 9 additions & 6 deletions src/communities.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,15 @@ function getDirectory(filePath) {
export function communitiesData(customDbPath, opts = {}) {
const db = openReadonlyOrFail(customDbPath);
const resolution = opts.resolution ?? 1.0;

const graph = buildGraphologyGraph(db, {
functions: opts.functions,
noTests: opts.noTests,
});
db.close();
let graph;
try {
graph = buildGraphologyGraph(db, {
functions: opts.functions,
noTests: opts.noTests,
});
} finally {
db.close();
}

// Handle empty or trivial graphs
if (graph.order === 0 || graph.size === 0) {
Expand Down
Loading