Skip to content

Commit 3a0c3bd

Browse files
Merge branch 'main' into phpstan-2
2 parents b1bd411 + 15d62d9 commit 3a0c3bd

19 files changed

Lines changed: 315 additions & 1 deletion

composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,14 @@
4646
"analyse": "phpstan analyse",
4747
"lint": "parallel-lint src tests",
4848
"test": "phpunit",
49+
"e2e": "phpstan analyse --configuration=e2e/phpstan-e2e.neon --error-format=json | php e2e/test-runner",
4950
"ci": [
5051
"@composer-validate",
5152
"@lint",
5253
"@cs",
5354
"@test",
54-
"@analyse"
55+
"@analyse",
56+
"@e2e"
5557
]
5658
},
5759
"extra": {

e2e/PHPStanResultsChecker.php

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
3+
/**
4+
* Script takes JSON output from PHPStan and a list of expected errors. It checks this list matches.
5+
*
6+
* This should be called from the `test-runner` script.
7+
*/
8+
final class PHPStanResultsChecker
9+
{
10+
private const IDENTIFIER_PREFIX = 'phpExtensionLibrary.';
11+
private const FILE_PATH_TO_REMOVE = 'e2e/data/';
12+
13+
/**
14+
* @param array<int,string> $expectedResults see `test-runner` script for format
15+
*/
16+
public function checkResults(string $phpstanResultsAsJsonString, array $expectedResults): void
17+
{
18+
$asJson = json_decode($phpstanResultsAsJsonString, true);
19+
$this->assertArray($asJson, 'Failed to decode PHPStan results');
20+
21+
$totals = $asJson['totals'] ?? null;
22+
$this->assertArray($totals, 'Failed to find totals in PHPStan results');
23+
24+
$errorCount = $totals['errors'] ?? null;
25+
$this->assertNotNull($errorCount, 'Failed to find error count in PHPStan results');
26+
if ((int) $errorCount > 0) {
27+
$errors = $asJson['errors'] ?? null;
28+
throw new RuntimeException('PHPStan reported errors: '.var_export($errors, true));
29+
}
30+
31+
$files = $asJson['files'] ?? null;
32+
$this->assertArray($files, 'Failed to find files in PHPStan results');
33+
34+
$additionalReportedErrors = [];
35+
36+
foreach ($files as $fullFileName => $fileIssues) {
37+
$filePath = $this->getCleanFilename($fullFileName);
38+
39+
$this->assertArray($fileIssues, 'Failed to find issues in PHPStan results');
40+
$messages = $fileIssues['messages'] ?? null;
41+
$this->assertArray($messages, 'Failed to find messages in PHPStan results');
42+
43+
foreach ($messages as $issue) {
44+
$line = $issue['line'] ?? null;
45+
$identifier = $issue['identifier'] ?? '';
46+
$cleanIdentifier = str_replace(self::IDENTIFIER_PREFIX, '', $identifier);
47+
48+
$key = sprintf('%s:%d:%s', $filePath, $line, $cleanIdentifier);
49+
50+
$expectedResultsKey = array_search($key, $expectedResults, true);
51+
if (false === $expectedResultsKey) {
52+
$additionalReportedErrors[] = $key;
53+
} else {
54+
unset($expectedResults[$expectedResultsKey]);
55+
}
56+
}
57+
}
58+
59+
if (([] === $additionalReportedErrors) && ([] === $expectedResults)) {
60+
// ALL OK
61+
return;
62+
}
63+
64+
$errorMessage = implode("\n", [
65+
'Additional reported errors:',
66+
var_export(array_values($additionalReportedErrors), true),
67+
'Expected errors not reported:',
68+
var_export(array_values($expectedResults), true),
69+
]);
70+
71+
throw new RuntimeException($errorMessage);
72+
}
73+
74+
/** @phpstan-assert !null $value */
75+
private function assertNotNull(mixed $value, string $error): void
76+
{
77+
if (null === $value) {
78+
throw new RuntimeException($error);
79+
}
80+
}
81+
82+
/** @phpstan-assert array $value */
83+
private function assertArray(mixed $value, string $error): void
84+
{
85+
if (!is_array($value)) {
86+
throw new RuntimeException($error);
87+
}
88+
}
89+
90+
private function getCleanFilename(string $fullFileName): string
91+
{
92+
$position = strpos($fullFileName, self::FILE_PATH_TO_REMOVE);
93+
if (false === $position) {
94+
throw new RuntimeException('Failed to find '.self::FILE_PATH_TO_REMOVE.' in '.$fullFileName);
95+
}
96+
$filePath = substr($fullFileName, $position + strlen(self::FILE_PATH_TO_REMOVE));
97+
98+
return str_replace('.php', '', $filePath);
99+
}
100+
}

e2e/data/BaseTraitClass.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace data;
4+
5+
class BaseTraitClass
6+
{
7+
}

e2e/data/FriendProblems.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace data;
4+
5+
class FriendProblems
6+
{
7+
public function badCode(Person $person): void
8+
{
9+
new Person();
10+
Person::aStaticMethod();
11+
$person->aMethod();
12+
}
13+
}

e2e/data/InjectableBad.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace data;
4+
5+
class InjectableBad
6+
{
7+
public function __construct(
8+
public PersonRepository $repository,
9+
) {
10+
}
11+
}

e2e/data/InjectableGood.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace data;
4+
5+
class InjectableGood
6+
{
7+
public function __construct(
8+
public Repository $repository,
9+
) {
10+
}
11+
}

e2e/data/MustUse.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace data;
4+
5+
use DaveLiddament\PhpLanguageExtensions\MustUseResult;
6+
7+
class MustUse
8+
{
9+
#[MustUseResult]
10+
public function getResult(): int
11+
{
12+
return 1;
13+
}
14+
15+
public function code(): void
16+
{
17+
echo $this->getResult();
18+
$this->getResult();
19+
}
20+
}

e2e/data/MyTrait.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace data;
4+
5+
use DaveLiddament\PhpLanguageExtensions\RestrictTraitTo;
6+
7+
#[RestrictTraitTo(BaseTraitClass::class)]
8+
trait MyTrait
9+
{
10+
}

e2e/data/Person.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace data;
4+
5+
use DaveLiddament\PhpLanguageExtensions\Friend;
6+
7+
#[Friend(PersonBuilder::class)]
8+
class Person
9+
{
10+
public function __construct()
11+
{
12+
}
13+
14+
public function aMethod(): void
15+
{
16+
}
17+
18+
public static function aStaticMethod(): void
19+
{
20+
}
21+
}

e2e/data/PersonBuilder.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace data;
4+
5+
class PersonBuilder
6+
{
7+
public function create(): Person
8+
{
9+
return new Person();
10+
}
11+
}

0 commit comments

Comments
 (0)