Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/Analyze/VariableAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
35 changes: 3 additions & 32 deletions src/Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,15 @@

namespace Smeghead\PhpVariableHardUsage;

use Smeghead\PhpVariableHardUsage\Analyze\VariableAnalyzer;
use Smeghead\PhpVariableHardUsage\Parse\VariableParser;

final class Command
{
/**
* @param list<string> $argv
*/
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();
}
}
26 changes: 26 additions & 0 deletions src/Command/AbstractCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Smeghead\PhpVariableHardUsage\Command;

abstract class AbstractCommand implements CommandInterface
{
private const VERSION = '0.0.3';

protected function printVersion(): void
{
echo "PHP Variable Hard Usage Analyzer, version " . self::VERSION . "\n";
}

protected function printHelp(): void
{
echo "Usage: php bin/php-variable-hard-usage [command] [options]\n";
echo "Commands:\n";
echo " single <file> Analyze a single file\n";
echo " scopes <path1> [<path2> ...] 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";
}
}
10 changes: 10 additions & 0 deletions src/Command/CommandInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Smeghead\PhpVariableHardUsage\Command;

interface CommandInterface
{
public function execute(): void;
}
13 changes: 13 additions & 0 deletions src/Command/HelpCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Smeghead\PhpVariableHardUsage\Command;

final class HelpCommand extends AbstractCommand
{
public function execute(): void
{
$this->printHelp();
}
}
117 changes: 117 additions & 0 deletions src/Command/ScopesCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

declare(strict_types=1);

namespace Smeghead\PhpVariableHardUsage\Command;

use Smeghead\PhpVariableHardUsage\Analyze\VariableAnalyzer;
use Smeghead\PhpVariableHardUsage\Parse\VariableParser;

final class ScopesCommand extends AbstractCommand
{
/** @var list<string> */
private array $paths;

/**
* @param list<string> $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<string>
*/
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;
}
}
38 changes: 38 additions & 0 deletions src/Command/SingleCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Smeghead\PhpVariableHardUsage\Command;

use Smeghead\PhpVariableHardUsage\Analyze\VariableAnalyzer;
use Smeghead\PhpVariableHardUsage\Parse\VariableParser;

final class SingleCommand extends AbstractCommand
{
private string $filePath;

public function __construct(string $filePath)
{
$this->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();
}
}
13 changes: 13 additions & 0 deletions src/Command/VersionCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Smeghead\PhpVariableHardUsage\Command;

final class VersionCommand extends AbstractCommand
{
public function execute(): void
{
$this->printVersion();
}
}
54 changes: 54 additions & 0 deletions src/CommandFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace Smeghead\PhpVariableHardUsage;

use Smeghead\PhpVariableHardUsage\Command\CommandInterface;
use Smeghead\PhpVariableHardUsage\Command\HelpCommand;
use Smeghead\PhpVariableHardUsage\Command\SingleCommand;
use Smeghead\PhpVariableHardUsage\Command\ScopesCommand;
use Smeghead\PhpVariableHardUsage\Command\VersionCommand;

final class CommandFactory
{
/**
* @param list<string> $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 <file>\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 <path1> [<path2> ...]\n");
return new HelpCommand();
}
// 複数のパスを渡す
return new ScopesCommand(array_slice($argv, 2));
}

// 後方互換性のため、コマンドが指定されていない場合は単一ファイルモードとして扱う
return new SingleCommand($argv[1]);
}
}