codn_ts
Version:
智能代码分析工具 - 支持语义搜索、调用链分析和代码结构可视化,对大模型/AI agent 友好
704 lines • 28.2 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const commander_1 = require("commander");
const index_1 = require("./index");
const search_1 = require("./search");
const ls_1 = require("./ls");
const cat_1 = require("./cat");
const process = __importStar(require("process"));
const callChain_1 = require("./callChain");
const index_2 = require("./index");
const mcp_1 = require("./mcp");
const index_3 = require("./index");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const program = new commander_1.Command();
// 辅助函数:生成 Mermaid 图表
function generateMermaidDiagram(result, opts) {
let mermaid = "graph TD\n";
for (const symbolRef of result) {
const nodeId = symbolRef.symbol.replace(/[^a-zA-Z0-9]/g, "_");
// 添加主节点
mermaid += ` ${nodeId}["${symbolRef.symbol}<br/>${symbolRef.file}:${symbolRef.line}"]\n`;
// 添加调用方
for (const caller of symbolRef.callers) {
const callerId = caller.caller.replace(/[^a-zA-Z0-9]/g, "_");
mermaid += ` ${callerId}["${caller.caller}"]\n`;
mermaid += ` ${callerId} --> ${nodeId}\n`;
}
// 添加被调用方
for (const callee of symbolRef.callees) {
const calleeId = callee.callee.replace(/[^a-zA-Z0-9]/g, "_");
mermaid += ` ${calleeId}["${callee.callee}"]\n`;
mermaid += ` ${nodeId} --> ${calleeId}\n`;
}
}
return mermaid;
}
// 辅助函数:格式化文本输出
function formatTextOutput(result, opts) {
let output = "";
for (const symbolRef of result) {
output += `符号: ${symbolRef.symbol}\n`;
output += `类型: ${symbolRef.kind}\n`;
output += `位置: ${symbolRef.file}:${symbolRef.line}:${symbolRef.column}\n`;
if (opts.showDefinition && symbolRef.definition) {
output += `定义: ${symbolRef.definition.content}\n`;
}
if (opts.callers || !opts.callees) {
output += `\n调用方 (${symbolRef.callers.length} 个):\n`;
if (symbolRef.callers.length === 0) {
output += " 无\n";
}
else {
for (const caller of symbolRef.callers) {
output += ` ${caller.caller} @ ${caller.location}\n`;
}
}
}
if (opts.callees || !opts.callers) {
output += `\n被调用方 (${symbolRef.callees.length} 个):\n`;
if (symbolRef.callees.length === 0) {
output += " 无\n";
}
else {
for (const callee of symbolRef.callees) {
output += ` ${callee.callee} @ ${callee.location}\n`;
}
}
}
output += "\n" + "=".repeat(50) + "\n\n";
}
return output;
}
function summarize(references) {
const files = new Set();
const callers = new Set();
const callees = new Set();
for (const ref of references) {
files.add(ref.caller.split(":")[0]);
files.add(ref.callee.split(":")[0]);
callers.add(ref.caller);
callees.add(ref.callee);
}
return {
totalReferences: references.length,
totalFiles: files.size,
totalCallers: callers.size,
totalCallees: callees.size,
};
}
// 读取 package.json 中的版本号
const packageJsonPath = path.join(__dirname, "..", "package.json");
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
const version = packageJson.version;
program.name("cod").description("代码分析工具").version(version);
// analyze 命令
program
.command("analyze")
.description("分析项目中的函数/方法引用关系")
.option("-p, --project <path>", "项目根目录,默认为当前目录", process.cwd())
.option("--json", "以 JSON 格式输出", false)
.option("--summary", "只输出统计信息", false)
.option("--call-chain", "生成调用链可视化", false)
.option("--from <functions>", "起始函数列表,用逗号分隔", "")
.option("--to <functions>", "目标函数列表,用逗号分隔", "")
.option("--depth <number>", "调用链最大深度", "10")
.option("--format <format>", "可视化格式: text|dot|mermaid|json-graph", "text")
.option("--output <file>", "输出文件路径", "")
.option("--filter <pattern>", "文件过滤模式", "")
.option("--exclude <pattern>", "排除文件模式", "")
.option("--include-external", "包含外部依赖调用", false)
.option("--show-cycles", "显示循环调用", false)
.option("--min-calls <number>", "最小调用次数阈值", "1")
.action(async (opts) => {
try {
const result = await (0, index_1.analyzeProjectReferences)(opts.project);
const summary = summarize(result);
// 调用链可视化处理
if (opts.callChain || opts["call-chain"]) {
const callChainResult = await (0, callChain_1.generateCallChain)(result, {
from: opts.from ? opts.from.split(",").map((f) => f.trim()) : [],
to: opts.to ? opts.to.split(",").map((f) => f.trim()) : [],
maxDepth: parseInt(opts.depth),
format: opts.format,
includeExternal: opts.includeExternal,
showCycles: opts.showCycles,
minCalls: parseInt(opts.minCalls),
filter: opts.filter,
exclude: opts.exclude,
});
if (opts.output) {
// 输出到文件
await (0, callChain_1.writeCallChainToFile)(callChainResult, opts.output, opts.format);
console.log(`调用链已保存到: ${opts.output}`);
}
else if (opts.json) {
console.log(JSON.stringify(callChainResult, null, 2));
}
else {
// 控制台输出
(0, callChain_1.displayCallChain)(callChainResult, opts.format);
}
return;
}
// 原有的分析逻辑
if (opts.json) {
if (opts.summary) {
console.log(JSON.stringify({ summary }, null, 2));
}
else {
console.log(JSON.stringify({ summary, references: result }, null, 2));
}
return;
}
if (opts.summary) {
console.log(`引用总数: ${summary.totalReferences}`);
console.log(`涉及文件数: ${summary.totalFiles}`);
console.log(`调用方数: ${summary.totalCallers}`);
console.log(`被调方数: ${summary.totalCallees}`);
}
else {
for (const ref of result) {
console.log(`${ref.caller} -> ${ref.callee} @ ${ref.location}`);
}
}
}
catch (err) {
if (opts.json) {
console.log(JSON.stringify({ error: { message: err.message, stack: err.stack } }, null, 2));
}
else {
console.error("分析失败:", err);
}
process.exit(1);
}
});
// search 命令
program
.command("search")
.description("搜索项目中的文件、函数、类、文件内容等")
.argument("[query]", "搜索查询(可选,默认为空字符串)")
.option("-p, --project <path>", "项目根目录,默认为当前目录", process.cwd())
.option("-f, --file <path>", "限制搜索到指定文件")
.option("--json", "以 JSON 格式输出", false)
.option("-t, --type <type>", "搜索类型: file|function|class|interface|content|semantic|all", "all")
.option("-l, --limit <number>", "最大结果数量", "20")
.option("-i, --case-insensitive", "忽略大小写", false)
.option("-r, --regex", "使用正则表达式", false)
.option("-C, --context <lines>", "显示匹配行前后的行数", "2")
.option("-s, --semantic", "启用语义搜索", false)
.option("--min-relevance <score>", "语义搜索最小相关性分数 (0-1)", "0.1")
.action(async (query = "", opts) => {
try {
// If query is undefined (not provided), set it to empty string
const searchQuery = query || "";
// Handle multiple types (comma-separated)
const types = opts.type.split(",").map((t) => t.trim());
let allResults = [];
if (types.length === 1) {
// Single type - use original logic
const result = await (0, search_1.searchProject)(searchQuery, opts.project, {
type: opts.type,
limit: parseInt(opts.limit),
caseSensitive: !opts.caseInsensitive,
regex: opts.regex,
contextLines: parseInt(opts.context),
semantic: opts.semantic,
minRelevance: parseFloat(opts.minRelevance),
file: opts.file,
});
allResults = result;
}
else {
// Multiple types - search each type separately
const limit = Math.ceil(parseInt(opts.limit) / types.length);
for (const singleType of types) {
const result = await (0, search_1.searchProject)(searchQuery, opts.project, {
type: singleType,
limit: limit,
caseSensitive: !opts.caseInsensitive,
regex: opts.regex,
contextLines: parseInt(opts.context),
semantic: opts.semantic,
minRelevance: parseFloat(opts.minRelevance),
file: opts.file,
});
allResults.push(...result);
}
// Remove duplicates and apply overall limit
const seen = new Set();
allResults = allResults
.filter((item) => {
const key = `${item.file}:${item.line}:${item.column}:${item.name}`;
if (seen.has(key))
return false;
seen.add(key);
return true;
})
.slice(0, parseInt(opts.limit));
}
const result = allResults;
if (opts.json) {
console.log(JSON.stringify({ query: searchQuery, results: result }, null, 2));
}
else {
if (result.length === 0) {
console.log(`未找到匹配 "${searchQuery}" 的结果`);
return;
}
console.log(`找到 ${result.length} 个匹配 "${searchQuery}" 的结果:`);
console.log();
for (const item of result) {
console.log(`${item.type.toUpperCase()}: ${item.name}`);
console.log(` 文件: ${item.file}`);
console.log(` 位置: ${item.line}:${item.column}`);
if (item.description) {
console.log(` 描述: ${item.description}`);
}
if (item.relevance !== undefined) {
console.log(` 相关性: ${(item.relevance * 100).toFixed(1)}%`);
}
if (item.keywords && item.keywords.length > 0) {
console.log(` 关键词: ${item.keywords.join(", ")}`);
}
if (item.content) {
console.log(` 内容: ${item.content}`);
}
if (item.context) {
console.log(` 上下文:`);
console.log(` ${item.context.split("\n").join("\n ")}`);
}
console.log();
}
}
}
catch (err) {
if (opts.json) {
console.log(JSON.stringify({ error: { message: err.message, stack: err.stack } }, null, 2));
}
else {
console.error("搜索失败:", err);
}
process.exit(1);
}
});
// ls 命令
program
.command("ls")
.description("列出目录内容")
.argument("[path]", "目录路径,默认为当前目录", ".")
.option("--json", "以 JSON 格式输出", false)
.option("-a, --all", "显示隐藏文件", false)
.option("-l, --long", "显示详细信息", false)
.option("-d, --depth <number>", "递归深度", "1")
.option("--ignore <patterns>", "忽略的文件模式,用逗号分隔", "node_modules,dist,.git")
.action(async (dirPath, opts) => {
try {
const ignorePatterns = opts.ignore.split(",").map((p) => p.trim());
const result = await (0, ls_1.listDirectory)(dirPath, {
showHidden: opts.all,
showDetails: opts.long,
maxDepth: parseInt(opts.depth),
ignorePatterns,
});
if (opts.json) {
console.log(JSON.stringify({ path: dirPath, items: result }, null, 2));
}
else {
if (result.length === 0) {
console.log(`目录 "${dirPath}" 为空或不存在`);
return;
}
console.log(`目录: ${dirPath}`);
console.log();
// 按类型分组显示
const files = result.filter((item) => item.type === "file");
const dirs = result.filter((item) => item.type === "directory");
if (dirs.length > 0) {
console.log("📁 目录:");
for (const dir of dirs) {
if (opts.long) {
console.log(` ${dir.name.padEnd(30)} ${dir.size?.toString().padStart(10)} ${dir.modified}`);
}
else {
console.log(` ${dir.name}/`);
}
}
console.log();
}
if (files.length > 0) {
console.log("📄 文件:");
for (const file of files) {
if (opts.long) {
console.log(` ${file.name.padEnd(30)} ${file.size?.toString().padStart(10)} ${file.modified}`);
}
else {
console.log(` ${file.name}`);
}
}
}
console.log();
console.log(`总计: ${dirs.length} 个目录, ${files.length} 个文件`);
}
}
catch (err) {
if (opts.json) {
console.log(JSON.stringify({ error: { message: err.message, stack: err.stack } }, null, 2));
}
else {
console.error("列出目录失败:", err);
}
process.exit(1);
}
});
// cat 命令
program
.command("cat")
.description("读取文件内容")
.argument("<file>", "文件路径")
.option("--json", "以 JSON 格式输出", false)
.option("-n, --line-numbers", "显示行号", false)
.option("-s, --start <line>", "起始行号", "1")
.option("-e, --end <line>", "结束行号", "-1")
.option("--encoding <encoding>", "文件编码", "utf-8")
.option("--info", "只显示文件信息,不读取内容", false)
.option("-p, --file <file>", "指定文件或目录", ".")
.action(async (filePath, opts) => {
try {
if (opts.info) {
// 只显示文件信息
const info = (0, cat_1.getFileInfo)(filePath);
const fileType = (0, cat_1.detectFileType)(filePath);
if (opts.json) {
console.log(JSON.stringify({ ...info, type: fileType }, null, 2));
}
else {
if (!info.exists) {
console.log(`文件不存在: ${filePath}`);
return;
}
if (!info.isFile) {
console.log(`路径不是文件: ${filePath}`);
return;
}
console.log(`文件: ${info.file}`);
console.log(`类型: ${fileType}`);
console.log(`大小: ${info.size} 字节`);
console.log(`修改时间: ${info.modified}`);
console.log(`编码: ${info.encoding}`);
}
return;
}
// 读取文件内容
const result = await (0, cat_1.catFile)(filePath, {
showLineNumbers: opts.lineNumbers,
startLine: parseInt(opts.start),
endLine: parseInt(opts.end),
encoding: opts.encoding,
});
if (opts.json) {
console.log(JSON.stringify(result, null, 2));
}
else {
console.log(`文件: ${result.file} (${result.totalLines} 行, ${result.size} 字节)`);
console.log(`范围: ${result.startLine}-${result.endLine}`);
console.log(`编码: ${result.encoding}, 修改时间: ${result.modified}`);
console.log();
console.log(result.content);
}
}
catch (err) {
if (opts.json) {
console.log(JSON.stringify({ error: { message: err.message, stack: err.stack } }, null, 2));
}
else {
console.error("读取文件失败:", err);
}
process.exit(1);
}
});
// ref 命令
program
.command("ref")
.description("分析特定函数/符号的引用关系")
.option("-s, --symbol <name>", "要分析的符号名称", "")
.option("-p, --project <path>", "项目根目录,默认为当前目录", process.cwd())
.option("--json", "以 JSON 格式输出", false)
.option("--callers", "显示调用方", false)
.option("--callees", "显示被调用方", false)
.option("--format <format>", "输出格式: text|json|mermaid", "text")
.option("--depth <number>", "分析深度", "3")
.option("--include-external", "包含外部依赖调用", false)
.option("--case-sensitive", "区分大小写", false)
.option("--show-definition", "显示符号定义", false)
.option("--output <file>", "输出到文件", "")
.action(async (opts) => {
try {
if (!opts.symbol) {
const errorMsg = "错误: 必须指定符号名称 (-s, --symbol)";
if (opts.json) {
console.log(JSON.stringify({ error: { message: errorMsg } }, null, 2));
}
else {
console.error(errorMsg);
console.log("用法: cod ref -s <symbol_name>");
}
process.exit(1);
}
const { analyzeSymbolReferences } = await Promise.resolve().then(() => __importStar(require("./utils/reference_analyzer")));
const result = await analyzeSymbolReferences(opts.project, opts.symbol, {
includeExternal: opts.includeExternal,
maxDepth: parseInt(opts.depth),
caseSensitive: opts.caseSensitive,
});
if (result.length === 0) {
const notFoundMsg = `未找到符号 "${opts.symbol}" 的引用关系`;
if (opts.json) {
console.log(JSON.stringify({
symbol: opts.symbol,
message: notFoundMsg,
results: [],
}, null, 2));
}
else {
console.log(notFoundMsg);
}
return;
}
// 处理输出格式
if (opts.output) {
const fs = await Promise.resolve().then(() => __importStar(require("fs/promises")));
let outputContent = "";
if (opts.format === "mermaid") {
outputContent = generateMermaidDiagram(result, opts);
}
else if (opts.json) {
outputContent = JSON.stringify({
symbol: opts.symbol,
project: opts.project,
results: result,
}, null, 2);
}
else {
outputContent = formatTextOutput(result, opts);
}
await fs.writeFile(opts.output, outputContent, "utf-8");
console.log(`引用分析结果已保存到: ${opts.output}`);
return;
}
if (opts.json) {
console.log(JSON.stringify({
symbol: opts.symbol,
project: opts.project,
results: result,
}, null, 2));
}
else if (opts.format === "mermaid") {
console.log(generateMermaidDiagram(result, opts));
}
else {
console.log(formatTextOutput(result, opts));
}
}
catch (err) {
if (opts.json) {
console.log(JSON.stringify({
error: { message: err.message, stack: err.stack },
}, null, 2));
}
else {
console.error("引用分析失败:", err);
}
process.exit(1);
}
});
// show 命令
program
.command("show")
.description("展示文件区间或类定义(完整/方法列表/初始化)")
.option("-f, --file <path>", "文件路径")
.option("-p, --project <dir>", "项目根目录,自动查找符号文件")
.option("-s, --symbol <name>", "符号名称(如类名)")
.option("--start <line>", "起始行号", "1")
.option("--end <line>", "结束行号", "-1")
.option("-m, --mode <mode>", "展示模式: full|methods|init", "full")
.option("-c, --context <lines>", "上下文行数", "0")
.option("--json", "JSON 格式输出", false)
.action(async (opts) => {
try {
// 参数校验:mode 必须是 full/methods/init 之一
const validModes = ["full", "methods", "init"];
if (opts.mode && !validModes.includes(opts.mode)) {
const msg = `参数错误: --mode/-m 仅支持 full|methods|init,当前为 '${opts.mode}'`;
if (opts.json) {
console.log(JSON.stringify({ error: { message: msg } }, null, 2));
}
else {
console.error(msg);
}
process.exit(1);
}
let file = opts.file;
if (!file && opts.project && opts.symbol) {
// 自动查找 symbol 所在文件
const { searchProject } = await Promise.resolve().then(() => __importStar(require("./search")));
const results = await searchProject(opts.symbol, opts.project, {
type: "class",
limit: 1,
});
if (results && results.length > 0) {
file = results[0].file;
console.log(`[自动定位] 符号 ${opts.symbol} 位于文件: ${file}`);
}
else {
console.error(`未找到符号 ${opts.symbol} 对应的文件`);
return;
}
}
if (!file)
file = ".";
if (opts.symbol) {
// 展示类定义
const result = await (0, index_2.showClassDefinition)({
file,
symbol: opts.symbol,
mode: opts.mode,
});
if (opts.json) {
console.log(JSON.stringify(result, null, 2));
}
else {
if (opts.mode === "methods") {
// methods 模式直接输出 result.content,保留分文件分组格式
console.log(`类: ${result.symbol}`);
console.log(result.content);
}
else {
// 其他模式原样输出
console.log(`文件: ${result.file}`);
console.log(`符号: ${result.symbol}`);
console.log(`模式: ${result.mode}`);
console.log(`范围: ${result.startLine}-${result.endLine}`);
console.log("\n" + result.content);
}
}
}
else {
// 展示文件区间
const result = (0, index_2.showFileRange)({
file,
startLine: parseInt(opts.start),
endLine: parseInt(opts.end),
contextLines: parseInt(opts.context),
});
if (opts.json) {
console.log(JSON.stringify(result, null, 2));
}
else {
console.log(`文件: ${result.file}`);
console.log(`范围: ${result.startLine}-${result.endLine}`);
console.log("\n" + result.content);
}
}
}
catch (err) {
if (opts.json) {
console.log(JSON.stringify({ error: { message: err.message, stack: err.stack } }, null, 2));
}
else {
console.error("展示失败:", err);
}
process.exit(1);
}
});
// mcp 命令
program
.command("mcp")
.description("以 MCP 协议模式启动服务")
.option("-p, --port <port>", "监听端口", "7788")
.action((opts) => {
(0, mcp_1.startMcpServer)(Number(opts.port));
});
// check 命令
program
.command("check")
.description("LSP 诊断检查,输出语法/类型错误和警告")
.option("-f, --file <file>", "指定单个文件进行检查")
.option("-p, --project <dir>", "指定项目根目录,默认为当前目录", process.cwd())
.option("--json", "以 JSON 格式输出", false)
.action(async (opts) => {
try {
const diagnostics = await (0, index_3.collectDiagnostics)({
projectRoot: opts.project,
file: opts.file || "",
});
if (opts.json) {
console.log(JSON.stringify(diagnostics, null, 2));
}
else {
if (diagnostics.length === 0) {
console.log("\x1b[32m✔ 没有发现错误或警告\x1b[0m");
}
else {
for (const d of diagnostics) {
let color = "\x1b[37m";
if (d.severity === "error")
color = "\x1b[31m";
else if (d.severity === "warning")
color = "\x1b[33m";
else if (d.severity === "info")
color = "\x1b[36m";
else if (d.severity === "hint")
color = "\x1b[34m";
console.log(`${color}${d.file}:${d.line}:${d.column} ${d.severity.padEnd(7)} ${d.message}\x1b[0m`);
}
}
}
}
catch (err) {
console.error("诊断失败:", err);
process.exit(1);
}
});
// 默认命令(向后兼容)
program.action(async () => {
console.log("使用 'cod analyze' 分析引用关系,或使用 'cod search <query>' 搜索代码");
console.log("运行 'cod --help' 查看所有命令");
});
program.parse(process.argv);
//# sourceMappingURL=cli.js.map