symref
Version:
Static code checker for AI code agents (Windsurf, Cline, etc.)
254 lines • 11.9 kB
JavaScript
import * as path from 'node:path';
import * as fs from 'node:fs';
import { SymbolReferenceAnalyzer } from '../../analyzer/SymbolReferenceAnalyzer.js';
/**
* シンボルの呼び出し元を分析するコマンド
*/
export class CallersCommand {
/**
* シンボル文字列をパースする
* @param input 入力文字列
* @returns パースされたシンボルの配列
*/
static parseSymbols(input) {
// カンマとスペースの両方で分割し、空の要素を除外
// まずカンマで分割し、その後スペースで分割する
const symbols = [];
// カンマで分割
const commaSeparated = input.split(',');
for (const part of commaSeparated) {
if (part.trim()) {
// スペースで分割
const spaceSeparated = part.trim().split(/\s+/);
for (const symbol of spaceSeparated) {
if (symbol.trim()) {
const trimmedSymbol = symbol.trim();
// ドット記法の解析
if (trimmedSymbol.includes('.')) {
// 複数のドットに対応するため、最後のドットで分割
const lastDotIndex = trimmedSymbol.lastIndexOf('.');
const containerName = trimmedSymbol.substring(0, lastDotIndex);
const memberName = trimmedSymbol.substring(lastDotIndex + 1);
symbols.push({
symbol: trimmedSymbol,
containerName,
memberName
});
}
else {
symbols.push({
symbol: trimmedSymbol
});
}
}
}
}
}
return symbols;
}
/**
* コマンドを実行する
* @param symbolInput 検索するシンボル
* @param options コマンドオプション
*/
static execute(symbolInput, options) {
try {
// 分析オプションを設定
const analyzerOptions = {
basePath: options.dir,
tsConfigPath: options.project,
includePatterns: options.include ? options.include.split(',') : undefined,
excludePatterns: options.exclude ? options.exclude.split(',') : undefined
};
// アナライザーを初期化
const analyzer = new SymbolReferenceAnalyzer(analyzerOptions);
// シンボルを解析
const symbols = CallersCommand.parseSymbols(symbolInput);
let hasError = false;
let errorSymbols = [];
// 呼び出しグラフを構築(一度だけ)
const nodeCount = analyzer.buildCallGraph();
console.log(`${nodeCount} 個のシンボルを分析しました。\n`);
// 全てのシンボルを分析
for (const symbol of symbols) {
try {
const symbolName = symbol.symbol;
// シンボルの存在確認
if (symbol.containerName && symbol.memberName) {
// コンテナが存在するか確認
if (!analyzer.hasSymbol(symbol.containerName)) {
hasError = true;
errorSymbols.push({
symbol: symbolName,
error: `コンテナ '${symbol.containerName}' がコードベース内に見つかりません。`
});
continue;
}
}
else {
// 単一シンボルの存在確認
if (!analyzer.hasSymbol(symbolName)) {
hasError = true;
errorSymbols.push({
symbol: symbolName,
error: `シンボル '${symbolName}' がコードベース内に見つかりません。`
});
continue;
}
}
console.log(`\n=== '${symbolName}' の呼び出し元を分析中... ===\n`);
// 呼び出し元を分析
const result = analyzer.findCallers(symbolName);
// 結果を表示
CallersCommand.displayResult(result, symbolName);
// Mermaidファイルを生成(オプション)
if (options.mermaid) {
CallersCommand.generateMermaidFile(result, `${symbolName}_${options.mermaid}`);
}
}
catch (error) {
hasError = true;
errorSymbols.push({ symbol: symbol.symbol, error: error.message });
}
}
// エラーが発生したシンボルを表示
if (errorSymbols.length > 0) {
console.log('\nエラーが発生したシンボル:');
errorSymbols.forEach(({ symbol, error }) => {
console.error(` - ${symbol}: ${error}`);
});
}
// エラーが発生した場合は終了コードを1に設定
if (hasError) {
process.exit(1);
}
}
catch (error) {
console.error(`エラーが発生しました: ${error.message}`);
if (error.stack) {
console.error(error.stack);
}
}
}
/**
* 分析結果を表示
* @param result 分析結果
* @param symbol 対象シンボル
*/
static displayResult(result, symbol) {
console.log('===== 呼び出し元の分析結果 =====');
if (result.paths.length === 0) {
console.log(`シンボル '${symbol}' の呼び出し元が見つかりませんでした`);
return;
}
console.log(`${result.paths.length} 個の呼び出し経路が見つかりました:\n`);
// 各経路を表示
result.paths.forEach((path, index) => {
console.log(`経路 ${index + 1}:`);
// 経路上のノードを表示(逆順)
for (let i = 0; i < path.nodes.length; i++) {
const node = path.nodes[i];
const location = node.location;
const locationStr = location.filePath && location.line > 0
? `${location.filePath}:${location.line}`
: '不明な位置';
// 最初のノード(エントリーポイント)
if (i === 0) {
console.log(`${node.symbol} (${locationStr})`);
}
// 中間ノードと最後のノード
else {
// エッジ情報を表示
const edge = path.edges[i - 1];
const edgeLocation = edge === null || edge === void 0 ? void 0 : edge.location;
const callLocationStr = (edgeLocation === null || edgeLocation === void 0 ? void 0 : edgeLocation.filePath) && (edgeLocation === null || edgeLocation === void 0 ? void 0 : edgeLocation.line) > 0
? `${edgeLocation.filePath}:${edgeLocation.line}`
: locationStr;
console.log(` ↑ called by (${callLocationStr})`);
console.log(`${node.symbol} (${locationStr})`);
}
}
console.log('\n');
});
}
/**
* Mermaidファイルを生成
* @param result 分析結果
* @param outputPath 出力パス
*/
static generateMermaidFile(result, outputPath) {
if (!result.graphMermaidFormat) {
console.warn('警告: Mermaidグラフデータを生成できませんでした。');
return '';
}
try {
// 出力ディレクトリを.symbolsに変更
const outputDir = '.symbols';
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const timestamp = `${year}${month}${day}_${hours}${minutes}`;
const safeBaseName = outputPath.replace(/[^a-zA-Z0-9]/g, '_');
const fileName = `${safeBaseName}_${timestamp}.md`;
const resolvedPath = path.resolve(process.cwd(), outputDir, fileName);
// 出力ディレクトリを確保
const dir = path.dirname(resolvedPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(resolvedPath, result.graphMermaidFormat);
console.log(`Mermaidグラフファイルを生成しました: ${resolvedPath}`);
console.log('可視化するには: GitHubで表示するか、https://mermaid.live で開いてください');
return result.graphMermaidFormat;
}
catch (error) {
console.error(`Mermaidファイルの生成中にエラーが発生しました: ${error.message}`);
return '';
}
}
formatCallGraph(result) {
if (result.paths.length === 0) {
return '呼び出し元は見つかりませんでした。';
}
const lines = [];
lines.push(`${result.paths.length} 個の呼び出し経路が見つかりました:\n`);
result.paths.forEach((path, index) => {
lines.push(`経路 ${index + 1}:`);
path.nodes.forEach((node, i) => {
const location = node.location;
const locationStr = location.filePath && location.line > 0
? `${location.filePath}:${location.line}`
: '不明な位置';
if (i === 0) {
lines.push(`${node.symbol} (${locationStr})`);
}
else {
const edge = path.edges[i - 1];
const edgeLocation = edge === null || edge === void 0 ? void 0 : edge.location;
const callLocationStr = (edgeLocation === null || edgeLocation === void 0 ? void 0 : edgeLocation.filePath) && (edgeLocation === null || edgeLocation === void 0 ? void 0 : edgeLocation.line) > 0
? `${edgeLocation.filePath}:${edgeLocation.line}`
: locationStr;
// デバッグ出力を削除
lines.push(` ↑ called by (${callLocationStr})`);
lines.push(`${node.symbol} (${locationStr})`);
}
});
lines.push('\n');
});
return lines.join('\n');
}
createReferenceAnalyzer(options) {
// Implementation of createReferenceAnalyzer method
// This method should return an instance of SymbolReferenceAnalyzer
throw new Error("Method not implemented");
}
generateMermaidDiagram(result) {
// Implementation of generateMermaidDiagram method
// This method should return a string representing the Mermaid diagram
throw new Error("Method not implemented");
}
}
//# sourceMappingURL=CallersCommand.js.map