Files
Edu/scripts/arch-scan/scanners/go-scanner.ts
SpecialX 4629de1926 fix(arch-scan): 修复多语言扫描器并添加 DELETE 清空表
- scanner.ts: 添加 DELETE 清空旧数据语句,避免重复插入
- ts-scanner: 改用 regex 替代 ts-morph 解析(避免解析失败)
- go-scanner/py-scanner/proto-scanner: 完善实现
2026-07-08 12:49:43 +08:00

134 lines
4.2 KiB
TypeScript
Raw 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 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 {
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 };
}