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

165 lines
5.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 }[];
// 使用递归 CTE 识别直接或间接调用 requirePermission 的符号
// 这样可以正确识别通过辅助函数(如 requireAiPermission间接校验权限的 Action
const serverActionsWithoutPerm = db
.prepare(
`WITH RECURSIVE permission_callers(symbol_id) AS (
SELECT c.caller_id
FROM calls c
JOIN symbols cs ON c.callee_id = cs.id
WHERE cs.name = 'requirePermission'
UNION
SELECT c.caller_id
FROM calls c
JOIN permission_callers pc ON c.callee_id = pc.symbol_id
)
SELECT s.name, f.path FROM symbols s
JOIN files f ON s.file_id = f.id
WHERE s.is_server_action = 1
AND s.is_public = 0
AND s.id NOT IN (SELECT symbol_id FROM permission_callers)`
)
.all() as { name: string; path: string }[];
return {
long_files: longFiles,
server_actions_without_permission: serverActionsWithoutPerm,
};
}