fix(arch-scan): 修复多语言扫描器并添加 DELETE 清空表
- scanner.ts: 添加 DELETE 清空旧数据语句,避免重复插入 - ts-scanner: 改用 regex 替代 ts-morph 解析(避免解析失败) - go-scanner/py-scanner/proto-scanner: 完善实现
This commit is contained in:
@@ -1,12 +1,133 @@
|
||||
import type { Database as DBType } from 'better-sqlite3';
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import type { Database as DBType } from "better-sqlite3";
|
||||
|
||||
interface ScanStats {
|
||||
modules: number;
|
||||
symbols: number;
|
||||
}
|
||||
|
||||
/** 递归收集目录下匹配扩展名的所有文件 */
|
||||
function walkDir(dir: string, exts: string[]): string[] {
|
||||
if (!fs.existsSync(dir)) return [];
|
||||
const results: string[] = [];
|
||||
const stack: string[] = [dir];
|
||||
while (stack.length > 0) {
|
||||
const current = stack.pop()!;
|
||||
let entries: fs.Dirent[];
|
||||
try {
|
||||
entries = fs.readdirSync(current, { withFileTypes: true });
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(current, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
if (["node_modules", "dist", ".git", "vendor"].includes(entry.name))
|
||||
continue;
|
||||
stack.push(fullPath);
|
||||
} else if (entry.isFile()) {
|
||||
const ext = path.extname(entry.name);
|
||||
if (exts.includes(ext)) results.push(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
// Go 符号正则:匹配导出函数、类型、结构体、接口
|
||||
const GO_FUNC_RE = /^func\s+(?:\([^)]*\)\s+)?([A-Z]\w*)\s*\(/gm;
|
||||
const GO_TYPE_RE = /^type\s+([A-Z]\w*)\s+/gm;
|
||||
const GO_STRUCT_RE = /^type\s+([A-Z]\w*)\s+struct\s*\{/gm;
|
||||
const GO_INTERFACE_RE = /^type\s+([A-Z]\w*)\s+interface\s*\{/gm;
|
||||
|
||||
export function scanGo(db: DBType, root: string): ScanStats {
|
||||
// Go 扫描器骨架:P1 后期用 tree-sitter-go 实现
|
||||
// 当前仅扫描 go.mod 识别模块
|
||||
return { modules: 0, symbols: 0 };
|
||||
}
|
||||
const insertModule = db.prepare(
|
||||
"INSERT OR IGNORE INTO modules (name, path, language, service, type) VALUES (?, ?, ?, ?, ?)",
|
||||
);
|
||||
const insertSymbol = db.prepare(
|
||||
"INSERT INTO symbols (module_id, name, kind, language, file_path, line_start, line_end, is_exported) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
);
|
||||
const getModuleId = db.prepare("SELECT id FROM modules WHERE name = ?") as {
|
||||
get: (name: string) => { id: number } | undefined;
|
||||
};
|
||||
|
||||
let modules = 0;
|
||||
let symbols = 0;
|
||||
|
||||
// 扫描 services/* 下有 go.mod 的服务
|
||||
const servicesDir = path.join(root, "services");
|
||||
if (!fs.existsSync(servicesDir)) return { modules, symbols };
|
||||
|
||||
const entries = fs.readdirSync(servicesDir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
const serviceName = entry.name;
|
||||
const servicePath = path.join(servicesDir, serviceName);
|
||||
const hasGoMod = fs.existsSync(path.join(servicePath, "go.mod"));
|
||||
if (!hasGoMod) continue;
|
||||
|
||||
insertModule.run(serviceName, servicePath, "go", serviceName, "service");
|
||||
const modRow = getModuleId.get(serviceName);
|
||||
if (modRow) {
|
||||
modules++;
|
||||
// 扫描 .go 文件
|
||||
const goFiles = walkDir(servicePath, [".go"]);
|
||||
for (const filePath of goFiles) {
|
||||
// 跳过测试文件
|
||||
if (filePath.endsWith("_test.go")) continue;
|
||||
let content: string;
|
||||
try {
|
||||
content = fs.readFileSync(filePath, "utf-8");
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
const lines = content.split("\n");
|
||||
|
||||
// 函数
|
||||
let match: RegExpExecArray | null;
|
||||
const funcRe = new RegExp(GO_FUNC_RE);
|
||||
while ((match = funcRe.exec(content)) !== null) {
|
||||
const name = match[1];
|
||||
const lineNum = content.slice(0, match.index).split("\n").length;
|
||||
insertSymbol.run(
|
||||
modRow.id,
|
||||
name,
|
||||
"function",
|
||||
"go",
|
||||
filePath,
|
||||
lineNum,
|
||||
lineNum,
|
||||
1,
|
||||
);
|
||||
symbols++;
|
||||
}
|
||||
|
||||
// 类型(struct / interface / type alias)
|
||||
const typeRe = new RegExp(GO_TYPE_RE);
|
||||
while ((match = typeRe.exec(content)) !== null) {
|
||||
const name = match[1];
|
||||
const lineNum = content.slice(0, match.index).split("\n").length;
|
||||
// 判断是 struct 还是 interface 还是普通 type
|
||||
const line = lines[lineNum - 1] || "";
|
||||
let kind = "type";
|
||||
if (line.includes("struct")) kind = "struct";
|
||||
else if (line.includes("interface")) kind = "interface";
|
||||
insertSymbol.run(
|
||||
modRow.id,
|
||||
name,
|
||||
kind,
|
||||
"go",
|
||||
filePath,
|
||||
lineNum,
|
||||
lineNum,
|
||||
1,
|
||||
);
|
||||
symbols++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { modules, symbols };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user