UNPKG

symref

Version:

Static code checker for AI code agents (Windsurf, Cline, etc.)

153 lines 6.44 kB
import * as path from 'node:path'; import * as fs from 'fs'; import { SymbolReferenceAnalyzer } from '../../analyzer/SymbolReferenceAnalyzer.js'; import { OutputFormatter } from '../formatters/OutputFormatter.js'; /** * 未使用シンボル検出のコマンドクラス * ファイル内で定義されているが、他のファイルから参照されていないシンボルを検出します。 */ export class DeadCommand { /** * コマンドを実行する * @param fileInput ファイルパス(カンマまたはスペース区切りで複数指定可能) * @param options オプション * @returns Promiseオブジェクト * * 使用例: * - 単一ファイル: `symref dead src/file.ts` * - カンマ区切り: `symref dead src/file1.ts,src/file2.ts` * - スペース区切り: `symref dead "src/file1.ts" "src/file2.ts"` * - 混合形式: `symref dead src/file1.ts,src/file2.ts "src/space file.ts"` */ static async execute(fileInput, options) { try { const filePaths = this.parseFilePaths(fileInput); if (filePaths.length === 0) { process.stderr.write(`エラー: ファイルが指定されていません\n`); process.exit(1); return; } let hasError = false; const analyzer = new SymbolReferenceAnalyzer({ basePath: options.dir, tsConfigPath: options.project, includePatterns: options.include.split(','), excludePatterns: options.exclude.split(',') }); // 複数ファイルを順番に処理 for (const file of filePaths) { const absolutePath = path.resolve(options.dir, file); if (!fs.existsSync(absolutePath)) { process.stderr.write(`エラー: ファイルが見つかりません: ${file}\n`); process.stderr.write('\n以下を確認してください:\n' + '1. ファイルパスが正しいこと\n' + '2. 指定したディレクトリにファイルが存在すること\n' + '3. ファイルの読み取り権限があること\n'); hasError = true; continue; // エラーがあっても他のファイルの処理を続行 } try { const unreferenced = analyzer.checkFile(file); OutputFormatter.displayUnreferencedSymbols(file, unreferenced); } catch (error) { process.stderr.write(`エラー: ファイル "${file}" の分析に失敗しました\n`); if (error instanceof Error) { process.stderr.write(`${error.message}\n`); } hasError = true; } } // いずれかのファイルでエラーが発生した場合は終了コード1を設定 if (hasError) { process.exit(1); } } catch (error) { process.stderr.write('エラー: ファイル分析に失敗しました\n'); if (error instanceof Error) { process.stderr.write(`${error.message}\n`); } process.exit(1); } } /** * ファイルパスを解析する * 以下の形式に対応: * - 単一ファイル: `file.ts` * - カンマ区切り: `file1.ts,file2.ts` * - スペース区切り: `"file1.ts" "file2.ts"` * - クォート内のスペース: `"file with space.ts"` * - 混合形式: `file1.ts,file2.ts "file3.ts"` * * @param fileInput ファイルパス文字列(カンマまたはスペース区切りで複数指定可能) * @returns 解析されたファイルパスの配列 */ static parseFilePaths(fileInput) { if (!fileInput || fileInput.trim() === '') { return []; } // シェルのコマンドライン引数解析のように、引用符の内側をエスケープする const result = []; let currentPath = ''; let inQuotes = false; let quoteChar = ''; for (let i = 0; i < fileInput.length; i++) { const char = fileInput[i]; // 引用符の処理 if ((char === '"' || char === "'") && (quoteChar === '' || char === quoteChar)) { if (!inQuotes) { inQuotes = true; quoteChar = char; } else { inQuotes = false; quoteChar = ''; } continue; // 引用符自体はパスに含めない } // 引用符外のスペースは区切り文字として扱う if (char === ' ' && !inQuotes) { if (currentPath.trim()) { if (!inQuotes && currentPath.includes(',')) { // カンマを含む場合は複数パスとして処理 this.processCommaDelimitedPaths(currentPath, result); } else { result.push(currentPath.trim()); } currentPath = ''; } } else { currentPath += char; } } // 最後のパスを処理 if (currentPath.trim()) { if (!inQuotes && currentPath.includes(',')) { this.processCommaDelimitedPaths(currentPath, result); } else { result.push(currentPath.trim()); } } return result; } /** * カンマ区切りのパスを処理する * カンマで区切られた文字列を分割し、個々のパスを結果配列に追加します * * @param pathStr カンマ区切りのパス文字列 * @param result 結果配列 */ static processCommaDelimitedPaths(pathStr, result) { const paths = pathStr.split(','); for (const path of paths) { if (path.trim() !== '') { result.push(path.trim()); } } } } //# sourceMappingURL=DeadCommand.js.map