Skip to content

Commit 284078f

Browse files
authored
De-duplicate phpstan.php errors (#4560)
1 parent b94ceff commit 284078f

File tree

5 files changed

+108
-6
lines changed

5 files changed

+108
-6
lines changed

src/Analyser/FileAnalyser.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use function count;
2727
use function error_reporting;
2828
use function get_class;
29+
use function hash;
2930
use function is_dir;
3031
use function is_file;
3132
use function restore_error_handler;
@@ -49,10 +50,10 @@
4950
final class FileAnalyser
5051
{
5152

52-
/** @var list<Error> */
53+
/** @var array<string, Error> */
5354
private array $allPhpErrors = [];
5455

55-
/** @var list<Error> */
56+
/** @var array<string, Error> */
5657
private array $filteredPhpErrors = [];
5758

5859
public function __construct(
@@ -326,8 +327,8 @@ public function analyseFile(
326327

327328
return new FileAnalyserResult(
328329
$fileErrors,
329-
$this->filteredPhpErrors,
330-
$this->allPhpErrors,
330+
array_values($this->filteredPhpErrors),
331+
array_values($this->allPhpErrors),
331332
$locallyIgnoredErrors,
332333
$fileCollectedData,
333334
array_values(array_unique($fileDependencies)),
@@ -366,8 +367,9 @@ private function collectErrors(array $analysedFiles): void
366367
}
367368

368369
$errorMessage = sprintf('%s: %s', $this->getErrorLabel($errno), $errstr);
370+
$errorSignature = hash('sha256', sprintf('%s:%s::%s', $errfile, $errline, $errorMessage));
369371

370-
$this->allPhpErrors[] = (new Error($errorMessage, $errfile, $errline, false))->withIdentifier('phpstan.php');
372+
$this->allPhpErrors[$errorSignature] = (new Error($errorMessage, $errfile, $errline, false))->withIdentifier('phpstan.php');
371373

372374
if ($errno === E_DEPRECATED) {
373375
return true;
@@ -377,7 +379,7 @@ private function collectErrors(array $analysedFiles): void
377379
return true;
378380
}
379381

380-
$this->filteredPhpErrors[] = (new Error($errorMessage, $errfile, $errline, $errno === E_USER_DEPRECATED))->withIdentifier('phpstan.php');
382+
$this->filteredPhpErrors[$errorSignature] = (new Error($errorMessage, $errfile, $errline, $errno === E_USER_DEPRECATED))->withIdentifier('phpstan.php');
381383

382384
return true;
383385
});
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser;
4+
5+
use PHPStan\Testing\PHPStanTestCase;
6+
use PHPUnit\Framework\Attributes\CoversNothing;
7+
use function array_map;
8+
use function array_merge;
9+
use function array_unique;
10+
use function error_reporting;
11+
use const E_ALL;
12+
13+
#[CoversNothing]
14+
class Bug13813IntegrationTest extends PHPStanTestCase
15+
{
16+
17+
public function testBug13813(): void
18+
{
19+
error_reporting(E_ALL);
20+
$analyzerResult = $this->runAnalyse([
21+
__DIR__ . '/data/bug-13813.php',
22+
__DIR__ . '/Bug13813Rule.php',
23+
]);
24+
$this->assertCount(2, $analyzerResult->getAllPhpErrors());
25+
$this->assertCount(2, $analyzerResult->getFilteredPhpErrors());
26+
27+
$this->assertSame(
28+
'Warning: Undefined variable $x',
29+
$analyzerResult->getAllPhpErrors()[0]->getMessage()
30+
);
31+
$this->assertSame(
32+
'Warning: Undefined variable $x',
33+
$analyzerResult->getAllPhpErrors()[1]->getMessage()
34+
);
35+
}
36+
37+
/**
38+
* @param string[] $files
39+
*/
40+
private function runAnalyse(array $files): AnalyserResult
41+
{
42+
$files = array_map(fn (string $file): string => $this->getFileHelper()->normalizePath($file), $files);
43+
/** @var Analyser $analyser */
44+
$analyser = self::getContainer()->getByType(Analyser::class);
45+
46+
return $analyser->analyse($files);
47+
}
48+
49+
public static function getAdditionalConfigFiles(): array
50+
{
51+
return array_unique(
52+
array_merge(
53+
parent::getAdditionalConfigFiles(),
54+
[
55+
__DIR__ . '/bug13813.neon',
56+
],
57+
),
58+
);
59+
}
60+
61+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr\Variable;
7+
use PHPStan\Rules\Rule;
8+
9+
/**
10+
* @implements Rule<Node\Expr\Variable>
11+
*/
12+
class Bug13813Rule implements Rule
13+
{
14+
15+
public function getNodeType(): string
16+
{
17+
return Variable::class;
18+
}
19+
20+
public function processNode(Node $node, Scope $scope): array
21+
{
22+
for ($i = 0; $i < 100; $i++) {
23+
// @phpstan-ignore variable.undefined
24+
echo $x; // force emit a PHP warning at runtime
25+
}
26+
27+
return [];
28+
}
29+
30+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
rules:
2+
- PHPStan\Analyser\Bug13813Rule
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Bug13813;
4+
5+
function doFoo($x) {
6+
echo $x;
7+
}

0 commit comments

Comments
 (0)