diff --git a/CHANGELOG.md b/CHANGELOG.md index 35b8dcd..b0c1615 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # CHANGELOG +### Features + + * Added subcommand support with `single` and `scopes` modes + * The `single` mode analyzes a single PHP file + * The `scopes` mode supports analyzing multiple files and directories + * Enhanced command line interface with help and version options + ## v0.0.3 (2025-03-20) ### Features diff --git a/src/Analyze/VariableAnalyzer.php b/src/Analyze/VariableAnalyzer.php index fbcd641..c7df2b7 100644 --- a/src/Analyze/VariableAnalyzer.php +++ b/src/Analyze/VariableAnalyzer.php @@ -34,7 +34,8 @@ private function analyzeFunction(Func $function): Scope $analyzedVars = []; foreach ($variableNames as $variableName) { - $vars = array_filter($variables, fn($variable) => $variable->name === $variableName); + // array_values でインデックスを振り直す + $vars = array_values(array_filter($variables, fn($variable) => $variable->name === $variableName)); $variableHardUsage = $this->calcVariableHardUsage($vars); $analyzedVars[] = new AnalyzedVariable($variableName, $variableHardUsage); } diff --git a/src/Command.php b/src/Command.php index 82c0a4d..3d37016 100644 --- a/src/Command.php +++ b/src/Command.php @@ -4,9 +4,6 @@ namespace Smeghead\PhpVariableHardUsage; -use Smeghead\PhpVariableHardUsage\Analyze\VariableAnalyzer; -use Smeghead\PhpVariableHardUsage\Parse\VariableParser; - final class Command { /** @@ -14,34 +11,8 @@ final class Command */ public function run(array $argv): void { - if (count($argv) < 2) { - $this->printHelp(); - return; - } - - $filePath = $argv[1]; - if (!file_exists($filePath)) { - echo "File not found: $filePath\n"; - return; - } - - $parser = new VariableParser(); - $content =file_get_contents($filePath); - if ($content === false) { - echo "Failed to read file: $filePath\n"; - return; - } - $parseResult = $parser->parse($content); - $analyzer = new VariableAnalyzer($filePath, $parseResult->functions); - $result = $analyzer->analyze(); - echo $result->format(); - } - - private function printHelp(): void - { - echo "Usage: php bin/php-variable-hard-usage [source_file]\n"; - echo "Options:\n"; - echo " --help Display help information\n"; - echo " --version Show the version of the tool\n"; + $factory = new CommandFactory(); + $command = $factory->createCommand($argv); + $command->execute(); } } \ No newline at end of file diff --git a/src/Command/AbstractCommand.php b/src/Command/AbstractCommand.php new file mode 100644 index 0000000..feb3d47 --- /dev/null +++ b/src/Command/AbstractCommand.php @@ -0,0 +1,26 @@ + Analyze a single file\n"; + echo " scopes [ ...] Analyze PHP files in directories or specific files\n"; + echo "Options:\n"; + echo " --help Display help information\n"; + echo " --version Show the version of the tool\n"; + } +} \ No newline at end of file diff --git a/src/Command/CommandInterface.php b/src/Command/CommandInterface.php new file mode 100644 index 0000000..3c908d8 --- /dev/null +++ b/src/Command/CommandInterface.php @@ -0,0 +1,10 @@ +printHelp(); + } +} \ No newline at end of file diff --git a/src/Command/ScopesCommand.php b/src/Command/ScopesCommand.php new file mode 100644 index 0000000..503acb4 --- /dev/null +++ b/src/Command/ScopesCommand.php @@ -0,0 +1,117 @@ + */ + private array $paths; + + /** + * @param list $paths ディレクトリまたはファイルのパスリスト + */ + public function __construct(array $paths) + { + $this->paths = $paths; + } + + public function execute(): void + { + $phpFiles = []; + + // 各パスを処理 + foreach ($this->paths as $path) { + if (is_dir($path)) { + // ディレクトリの場合は再帰的にPHPファイルを収集 + $dirFiles = $this->findPhpFiles($path); + $phpFiles = array_merge($phpFiles, $dirFiles); + } elseif (is_file($path) && pathinfo($path, PATHINFO_EXTENSION) === 'php') { + // 単一のPHPファイルの場合 + $phpFiles[] = $path; + } else { + fwrite(STDERR, "Invalid path: {$path}\n"); + } + } + + if (empty($phpFiles)) { + fwrite(STDERR, "No PHP files found in specified paths\n"); + return; + } + + // 重複を削除 + $phpFiles = array_unique($phpFiles); + + $results = []; + foreach ($phpFiles as $file) { + try { + $content = file_get_contents($file); + if ($content === false) { + fwrite(STDERR, "Failed to read file: {$file}\n"); + continue; + } + + $parser = new VariableParser(); + $parseResult = $parser->parse($content); + $analyzer = new VariableAnalyzer($file, $parseResult->functions); + $results[] = $analyzer->analyze(); + } catch (\Exception $e) { + fwrite(STDERR, "Error analyzing {$file}: {$e->getMessage()}\n"); + } + } + + // 複数ファイルの結果をまとめて表示 + $this->printResults($results); + } + + /** + * @return list + */ + private function findPhpFiles(string $directory): array + { + $result = []; + $files = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($directory, \RecursiveDirectoryIterator::SKIP_DOTS) + ); + + /** @var \SplFileInfo $file */ + foreach ($files as $file) { + if ($file->isFile() && $file->getExtension() === 'php') { + $result[] = $file->getPathname(); + } + } + + return $result; + } + + /** + * @param list<\Smeghead\PhpVariableHardUsage\Analyze\AnalysisResult> $results + */ + private function printResults(array $results): void + { + // スコープベースのレポートを生成 + $allScopes = []; + foreach ($results as $result) { + foreach ($result->scopes as $scope) { + $allScopes[] = [ + 'file' => $result->filename, + 'namespace' => $scope->namespace, + 'name' => $scope->name, + 'variableHardUsage' => $scope->getVariableHardUsage() + ]; + } + } + + // 酷使度でソート + usort($allScopes, function ($a, $b) { + return $b['variableHardUsage'] <=> $a['variableHardUsage']; + }); + + // 結果を表示 + echo json_encode(['scopes' => $allScopes], JSON_PRETTY_PRINT) . PHP_EOL; + } +} \ No newline at end of file diff --git a/src/Command/SingleCommand.php b/src/Command/SingleCommand.php new file mode 100644 index 0000000..0219743 --- /dev/null +++ b/src/Command/SingleCommand.php @@ -0,0 +1,38 @@ +filePath = $filePath; + } + + public function execute(): void + { + if (!file_exists($this->filePath)) { + fwrite(STDERR, "File not found: {$this->filePath}\n"); + return; + } + + $parser = new VariableParser(); + $content = file_get_contents($this->filePath); + if ($content === false) { + fwrite(STDERR, "Failed to read file: {$this->filePath}\n"); + return; + } + + $parseResult = $parser->parse($content); + $analyzer = new VariableAnalyzer($this->filePath, $parseResult->functions); + $result = $analyzer->analyze(); + echo $result->format(); + } +} \ No newline at end of file diff --git a/src/Command/VersionCommand.php b/src/Command/VersionCommand.php new file mode 100644 index 0000000..6a0643d --- /dev/null +++ b/src/Command/VersionCommand.php @@ -0,0 +1,13 @@ +printVersion(); + } +} \ No newline at end of file diff --git a/src/CommandFactory.php b/src/CommandFactory.php new file mode 100644 index 0000000..a8c09a7 --- /dev/null +++ b/src/CommandFactory.php @@ -0,0 +1,54 @@ + $argv + */ + public function createCommand(array $argv): CommandInterface + { + if (count($argv) < 2) { + return new HelpCommand(); + } + + $arg = $argv[1]; + + if ($arg === '--help') { + return new HelpCommand(); + } + + if ($arg === '--version') { + return new VersionCommand(); + } + + if ($arg === 'single') { + if (count($argv) < 3) { + fwrite(STDERR, "Usage: php bin/php-variable-hard-usage single \n"); + return new HelpCommand(); + } + return new SingleCommand($argv[2]); + } + + if ($arg === 'scopes') { + if (count($argv) < 3) { + fwrite(STDERR, "Usage: php bin/php-variable-hard-usage scopes [ ...]\n"); + return new HelpCommand(); + } + // 複数のパスを渡す + return new ScopesCommand(array_slice($argv, 2)); + } + + // 後方互換性のため、コマンドが指定されていない場合は単一ファイルモードとして扱う + return new SingleCommand($argv[1]); + } +} \ No newline at end of file