Files
NextEdu/scripts/arch-scan/query.ts

156 lines
4.7 KiB
TypeScript

import type Database from "better-sqlite3";
export interface SymbolRef {
caller_name: string;
caller_path: string;
caller_line: number;
is_server_action: boolean;
depth: number;
path_chain: string;
}
export function querySymbolRefs(
db: Database.Database,
symbolName: string,
maxDepth = 10
): SymbolRef[] {
const sql = `
WITH RECURSIVE upstream(caller_id, caller_name, caller_path, caller_line, is_sa, depth, path_chain) AS (
SELECT c.caller_id, s.name, f.path, c.call_line, s.is_server_action, 0, s.name
FROM calls c
JOIN symbols s ON c.caller_id = s.id
JOIN files f ON s.file_id = f.id
WHERE c.callee_id = (SELECT id FROM symbols WHERE name = ? LIMIT 1)
UNION
SELECT c.caller_id, s.name, f.path, c.call_line, s.is_server_action, u.depth + 1,
u.path_chain || ' → ' || s.name
FROM upstream u
JOIN calls c ON c.callee_id = u.caller_id
JOIN symbols s ON c.caller_id = s.id
JOIN files f ON s.file_id = f.id
WHERE u.depth < ?
)
SELECT caller_name, caller_path, caller_line, is_sa as is_server_action, depth, path_chain
FROM upstream ORDER BY depth, caller_path`;
return db.prepare(sql).all(symbolName, maxDepth) as SymbolRef[];
}
export interface SymbolCall {
callee_name: string;
callee_path: string;
callee_line: number;
depth: number;
path_chain: string;
}
export function querySymbolForwardCalls(
db: Database.Database,
symbolName: string,
maxDepth = 10
): SymbolCall[] {
const sql = `
WITH RECURSIVE downstream(callee_id, callee_name, callee_path, callee_line, depth, path_chain) AS (
SELECT c.callee_id, s.name, f.path, c.call_line, 0, ?
FROM calls c
JOIN symbols s ON c.callee_id = s.id
JOIN files f ON s.file_id = f.id
WHERE c.caller_id = (SELECT id FROM symbols WHERE name = ? LIMIT 1)
UNION
SELECT c.callee_id, s.name, f.path, c.call_line, d.depth + 1,
d.path_chain || ' → ' || s.name
FROM downstream d
JOIN calls c ON c.caller_id = d.callee_id
JOIN symbols s ON c.callee_id = s.id
JOIN files f ON s.file_id = f.id
WHERE d.depth < ?
)
SELECT callee_name, callee_path, callee_line, depth, path_chain
FROM downstream ORDER BY depth, callee_path`;
return db.prepare(sql).all(symbolName, symbolName, maxDepth) as SymbolCall[];
}
export interface ModuleDep {
target_module: string;
dep_type: string;
}
export function queryModuleDeps(
db: Database.Database,
moduleName: string,
_maxDepth = 10
): ModuleDep[] {
const sql = `
SELECT DISTINCT m2.name AS target_module, md.dep_type
FROM module_deps md
JOIN modules m ON md.source_module_id = m.id
JOIN modules m2 ON md.target_module_id = m2.id
WHERE m.name = ?`;
return db.prepare(sql).all(moduleName) as ModuleDep[];
}
export interface ModuleReverseDep {
source_module: string;
dep_type: string;
}
export function queryModuleReverseDeps(
db: Database.Database,
moduleName: string,
_maxDepth = 10
): ModuleReverseDep[] {
const sql = `
SELECT DISTINCT m.name AS source_module, md.dep_type
FROM module_deps md
JOIN modules m ON md.source_module_id = m.id
JOIN modules m2 ON md.target_module_id = m2.id
WHERE m2.name = ?`;
return db.prepare(sql).all(moduleName) as ModuleReverseDep[];
}
export interface TechUsage {
symbol_name: string;
file_path: string;
tag_name: string;
}
export function queryTechUsage(
db: Database.Database,
tagName: string
): TechUsage[] {
const sql = `
SELECT s.name AS symbol_name, f.path AS file_path, t.name AS tag_name
FROM symbols s
JOIN symbol_tech_tags stt ON s.id = stt.symbol_id
JOIN tech_tags t ON stt.tag_id = t.id
JOIN files f ON s.file_id = f.id
WHERE t.name = ?`;
return db.prepare(sql).all(tagName) as TechUsage[];
}
export interface Violations {
long_files: { path: string; lines: number }[];
server_actions_without_permission: { name: string; path: string }[];
}
export function queryViolations(db: Database.Database): Violations {
const longFiles = db
.prepare("SELECT path, lines FROM files WHERE lines > 800 ORDER BY lines DESC")
.all() as { path: string; lines: number }[];
const serverActionsWithoutPerm = db
.prepare(
`SELECT s.name, f.path FROM symbols s
JOIN files f ON s.file_id = f.id
WHERE s.is_server_action = 1
AND NOT EXISTS (
SELECT 1 FROM calls c
JOIN symbols cs ON c.callee_id = cs.id
WHERE c.caller_id = s.id AND cs.name = 'requirePermission'
)`
)
.all() as { name: string; path: string }[];
return {
long_files: longFiles,
server_actions_without_permission: serverActionsWithoutPerm,
};
}