Skip to content

Commit 0844657

Browse files
authored
Merge pull request #17 from smeghead/feature/add-threshold-option
feat: add check mode.
2 parents a6a01dc + 042369f commit 0844657

File tree

7 files changed

+296
-86
lines changed

7 files changed

+296
-86
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# CHANGELOG
22

3+
## v0.0.7 (2025-03-25)
4+
5+
### Features
6+
7+
* Added new `check` mode with threshold support for CI integration
8+
* Added `--threshold` option to check mode for detecting excessive variable usage
9+
* Returns exit code 2 when variable hard usage exceeds specified threshold
10+
* Restructured code with ScopesTrait to improve maintainability
11+
312
### Bug fix
413

514
* The name of the file path information item in scopes mode is `file`, but I want to match it with `filename`, which is the name of the file path item in single mode.

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,52 @@ The output for scopes mode is a combined report with results sorted by variable
122122
}
123123
```
124124

125+
### CI Integration with Check Mode
126+
127+
Use the check command with an optional threshold to analyze files and enforce variable usage standards in CI/CD pipelines:
128+
129+
```bash
130+
# Check files with default threshold (200)
131+
$ vendor/bin/php-variable-hard-usage check src/
132+
133+
# Check with custom threshold
134+
$ vendor/bin/php-variable-hard-usage check --threshold=500 src/ tests/
135+
136+
# Check specific files and directories
137+
$ vendor/bin/php-variable-hard-usage check --threshold=300 src/Command.php config/
138+
```
139+
140+
The check mode returns different exit codes based on the result:
141+
142+
* Exit code 0: Success - No analysis errors and no scopes exceeding the threshold
143+
* Exit code 1: Analysis failure - Errors occurred during file parsing or analysis
144+
* Exit code 2: Threshold exceeded - One or more scopes exceeded the specified variable hard usage threshold
145+
146+
The output includes the threshold used, result status, and a list of scopes that exceeded the threshold:
147+
148+
```json
149+
{
150+
"threshold": 500,
151+
"result": "failure",
152+
"scopes": [
153+
{
154+
"file": "src/Parse/VariableParser.php",
155+
"namespace": "Smeghead\\PhpVariableHardUsage\\Parse",
156+
"name": "VariableParser::collectParseResultPerFunctionLike",
157+
"variableHardUsage": 655
158+
},
159+
{
160+
"file": "src/Command/SingleCommand.php",
161+
"namespace": "Smeghead\\PhpVariableHardUsage\\Command",
162+
"name": "SingleCommand::execute",
163+
"variableHardUsage": 530
164+
}
165+
]
166+
}
167+
```
168+
169+
This mode is particularly useful for integrating the tool into your CI/CD pipeline to fail builds when variable usage exceeds acceptable thresholds.
170+
125171
### Help and Version Information
126172

127173
To display help information:

src/Command/AbstractCommand.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ protected function printHelp(): void
1919
echo "Commands:\n";
2020
echo " single <file> Analyze a single file\n";
2121
echo " scopes <path1> [<path2> ...] Analyze PHP files in directories or specific files\n";
22+
echo " check <path1> [<path2> ...] Check PHP files for hard-coded variables and return non-zero exit code if found\n";
2223
echo "Options:\n";
2324
echo " --help Display help information\n";
2425
echo " --version Show the version of the tool\n";
26+
echo " --threshold <number> Set the threshold value for reporting (default: 200)\n";
2527
}
2628
}

src/Command/CheckCommand.php

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Smeghead\PhpVariableHardUsage\Command;
6+
7+
use Smeghead\PhpVariableHardUsage\Analyze\AnalysisResult;
8+
9+
final class CheckCommand extends AbstractCommand
10+
{
11+
use ScopesTrait;
12+
13+
/** @var list<string> */
14+
private array $paths;
15+
private int $threshold;
16+
17+
/**
18+
* @param list<string> $paths ディレクトリまたはファイルのパスリスト
19+
* @param int|null $threshold 閾値
20+
*/
21+
public function __construct(array $paths, ?int $threshold = null)
22+
{
23+
$this->paths = $paths;
24+
$this->threshold = $threshold ?? 200; // デフォルト閾値は200
25+
}
26+
27+
public function execute(): int
28+
{
29+
$analysis = $this->analyzePaths($this->paths);
30+
$results = $analysis['results'];
31+
$hasErrors = $analysis['hasErrors'];
32+
33+
if (empty($results)) {
34+
return 1;
35+
}
36+
37+
// 閾値チェックを行い結果を表示
38+
$exceedingScopes = $this->printResults($results);
39+
40+
// 閾値を超えるスコープがあればエラーコード2を返す
41+
if (!empty($exceedingScopes['scopes'])) {
42+
return 2;
43+
}
44+
45+
// 解析エラーがあればエラーコード1を返す
46+
return $hasErrors ? 1 : 0;
47+
}
48+
49+
/**
50+
* @param list<AnalysisResult> $results
51+
* @return array{
52+
* threshold: int,
53+
* result: string,
54+
* scopes: list<array{
55+
* file: string,
56+
* filename: string,
57+
* namespace: string|null,
58+
* name: string,
59+
* variableHardUsage: int
60+
* }>
61+
* } 閾値を超えたスコープの配列
62+
*/
63+
protected function printResults(array $results): array
64+
{
65+
// 閾値を超えるスコープを検出
66+
$exceedingScopes = [];
67+
68+
foreach ($results as $result) {
69+
foreach ($result->scopes as $scope) {
70+
$hardUsage = $scope->getVariableHardUsage();
71+
// 閾値以上の変数の酷使度を持つスコープのみ追加
72+
if ($hardUsage >= $this->threshold) {
73+
$exceedingScopes[] = [
74+
'file' => $result->filename,
75+
'filename' => $result->filename,
76+
'namespace' => $scope->namespace,
77+
'name' => $scope->name,
78+
'variableHardUsage' => $hardUsage
79+
];
80+
}
81+
}
82+
}
83+
84+
// 酷使度でソート
85+
usort($exceedingScopes, function ($a, $b) {
86+
return $b['variableHardUsage'] <=> $a['variableHardUsage'];
87+
});
88+
89+
// レポート作成
90+
$report = [
91+
'threshold' => $this->threshold,
92+
'result' => empty($exceedingScopes) ? 'success' : 'failure',
93+
'scopes' => $exceedingScopes
94+
];
95+
96+
// 結果を表示
97+
echo json_encode($report, JSON_PRETTY_PRINT) . PHP_EOL;
98+
99+
return $report;
100+
}
101+
}

src/Command/ScopesCommand.php

Lines changed: 9 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@
55
namespace Smeghead\PhpVariableHardUsage\Command;
66

77
use Smeghead\PhpVariableHardUsage\Analyze\AnalysisResult;
8-
use Smeghead\PhpVariableHardUsage\Analyze\VariableAnalyzer;
9-
use Smeghead\PhpVariableHardUsage\Parse\Exception\ParseFailedException;
10-
use Smeghead\PhpVariableHardUsage\Parse\VariableParser;
118

129
final class ScopesCommand extends AbstractCommand
1310
{
11+
use ScopesTrait;
12+
1413
/** @var list<string> */
1514
private array $paths;
1615

@@ -22,67 +21,11 @@ public function __construct(array $paths)
2221
$this->paths = $paths;
2322
}
2423

25-
/**
26-
* @param list<string> $paths
27-
* @return list<string>
28-
*/
29-
private function pickupPhpFiles(array $paths): array
30-
{
31-
$phpFiles = [];
32-
33-
// 各パスを処理
34-
foreach ($paths as $path) {
35-
if (is_dir($path)) {
36-
// ディレクトリの場合は再帰的にPHPファイルを収集
37-
$dirFiles = $this->findPhpFiles($path);
38-
$phpFiles = array_merge($phpFiles, $dirFiles);
39-
} elseif (is_file($path) && pathinfo($path, PATHINFO_EXTENSION) === 'php') {
40-
// 単一のPHPファイルの場合
41-
$phpFiles[] = $path;
42-
} else {
43-
fwrite(STDERR, "Invalid path: {$path}\n");
44-
}
45-
}
46-
47-
return $phpFiles;
48-
}
49-
50-
private function analyzeFile(string $file): AnalysisResult
51-
{
52-
$parser = new VariableParser();
53-
$content = file_get_contents($file);
54-
if ($content === false) {
55-
throw new ParseFailedException("Failed to read file: {$file}");
56-
}
57-
58-
$parseResult = $parser->parse($content);
59-
$analyzer = new VariableAnalyzer($file, $parseResult->functions);
60-
return $analyzer->analyze();
61-
}
62-
6324
public function execute(): int
6425
{
65-
$phpFiles = $this->pickupPhpFiles($this->paths);
66-
67-
if (empty($phpFiles)) {
68-
fwrite(STDERR, "No PHP files found in specified paths\n");
69-
return 1;
70-
}
71-
72-
// 重複を削除
73-
$phpFiles = array_unique($phpFiles);
74-
75-
$results = [];
76-
$hasErrors = false;
77-
78-
foreach ($phpFiles as $file) {
79-
try {
80-
$results[] = $this->analyzeFile($file);
81-
} catch (\Exception $e) {
82-
fwrite(STDERR, "Error analyzing {$file}: {$e->getMessage()}\n");
83-
$hasErrors = true;
84-
}
85-
}
26+
$analysis = $this->analyzePaths($this->paths);
27+
$results = $analysis['results'];
28+
$hasErrors = $analysis['hasErrors'];
8629

8730
if (empty($results)) {
8831
return 1;
@@ -96,37 +39,17 @@ public function execute(): int
9639
}
9740

9841
/**
99-
* @return list<string>
100-
*/
101-
private function findPhpFiles(string $directory): array
102-
{
103-
$result = [];
104-
$files = new \RecursiveIteratorIterator(
105-
new \RecursiveDirectoryIterator($directory, \RecursiveDirectoryIterator::SKIP_DOTS)
106-
);
107-
108-
/** @var \SplFileInfo $file */
109-
foreach ($files as $file) {
110-
if ($file->isFile() && $file->getExtension() === 'php') {
111-
$result[] = $file->getPathname();
112-
}
113-
}
114-
115-
return $result;
116-
}
117-
118-
/**
119-
* @param list<\Smeghead\PhpVariableHardUsage\Analyze\AnalysisResult> $results
42+
* @param list<AnalysisResult> $results
12043
*/
121-
private function printResults(array $results): void
44+
protected function printResults(array $results): void
12245
{
12346
// スコープベースのレポートを生成
12447
$allScopes = [];
12548
foreach ($results as $result) {
12649
foreach ($result->scopes as $scope) {
12750
$allScopes[] = [
128-
'file' => $result->filename, // 既存の 'file' プロパティを維持
129-
'filename' => $result->filename, // 新しく 'filename' プロパティを追加
51+
'file' => $result->filename,
52+
'filename' => $result->filename,
13053
'namespace' => $scope->namespace,
13154
'name' => $scope->name,
13255
'variableHardUsage' => $scope->getVariableHardUsage()

0 commit comments

Comments
 (0)