Skip to content

Commit 433511a

Browse files
committed
CleaningParser - remove conditional code by PHP_VERSION_ID
1 parent 97a181e commit 433511a

9 files changed

+196
-6
lines changed

src/Parser/CleaningParser.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@
44

55
use PhpParser\Node\Stmt;
66
use PhpParser\NodeTraverser;
7+
use PHPStan\Php\PhpVersion;
78

89
class CleaningParser implements Parser
910
{
1011

1112
private NodeTraverser $traverser;
1213

13-
public function __construct(private Parser $wrappedParser)
14+
public function __construct(private Parser $wrappedParser, PhpVersion $phpVersion)
1415
{
1516
$this->traverser = new NodeTraverser();
1617
$this->traverser->addVisitor(new CleaningVisitor());
18+
$this->traverser->addVisitor(new RemoveUnusedCodeByPhpVersionIdVisitor($phpVersion->getVersionString()));
1719
}
1820

1921
public function parseFile(string $file): array
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Parser;
4+
5+
use PhpParser\Node;
6+
use PhpParser\NodeVisitorAbstract;
7+
use PHPStan\Php\PhpVersion;
8+
use function count;
9+
use function version_compare;
10+
11+
class RemoveUnusedCodeByPhpVersionIdVisitor extends NodeVisitorAbstract
12+
{
13+
14+
public function __construct(private string $phpVersionString)
15+
{
16+
}
17+
18+
public function enterNode(Node $node): Node|int|null
19+
{
20+
if ($node instanceof Node\Stmt\ClassLike) {
21+
return null;
22+
}
23+
if ($node instanceof Node\FunctionLike) {
24+
return null;
25+
}
26+
27+
if (!$node instanceof Node\Stmt\If_) {
28+
return null;
29+
}
30+
31+
if (count($node->elseifs) > 0) {
32+
return null;
33+
}
34+
35+
if ($node->else === null) {
36+
return null;
37+
}
38+
39+
$cond = $node->cond;
40+
if (
41+
!$cond instanceof Node\Expr\BinaryOp\Smaller
42+
&& !$cond instanceof Node\Expr\BinaryOp\SmallerOrEqual
43+
&& !$cond instanceof Node\Expr\BinaryOp\Greater
44+
&& !$cond instanceof Node\Expr\BinaryOp\GreaterOrEqual
45+
&& !$cond instanceof Node\Expr\BinaryOp\Equal
46+
&& !$cond instanceof Node\Expr\BinaryOp\NotEqual
47+
&& !$cond instanceof Node\Expr\BinaryOp\Identical
48+
&& !$cond instanceof Node\Expr\BinaryOp\NotIdentical
49+
) {
50+
return null;
51+
}
52+
53+
$operator = $cond->getOperatorSigil();
54+
if ($operator === '===') {
55+
$operator = '==';
56+
}
57+
if ($operator === '!==') {
58+
$operator = '!=';
59+
}
60+
61+
$operands = $this->getOperands($cond->left, $cond->right);
62+
if ($operands === null) {
63+
return null;
64+
}
65+
66+
$result = version_compare($operands[0], $operands[1], $operator);
67+
if ($result) {
68+
// remove else
69+
$node->cond = new Node\Expr\ConstFetch(new Node\Name('true'));
70+
$node->else = null;
71+
72+
return $node;
73+
}
74+
75+
// remove if
76+
$node->cond = new Node\Expr\ConstFetch(new Node\Name('false'));
77+
$node->stmts = [];
78+
79+
return $node;
80+
}
81+
82+
/**
83+
* @return array{string, string}|null
84+
*/
85+
private function getOperands(Node\Expr $left, Node\Expr $right): ?array
86+
{
87+
if (
88+
$left instanceof Node\Scalar\LNumber
89+
&& $right instanceof Node\Expr\ConstFetch
90+
&& $right->name->toString() === 'PHP_VERSION_ID'
91+
) {
92+
return [(new PhpVersion($left->value))->getVersionString(), $this->phpVersionString];
93+
}
94+
95+
if (
96+
$right instanceof Node\Scalar\LNumber
97+
&& $left instanceof Node\Expr\ConstFetch
98+
&& $left->name->toString() === 'PHP_VERSION_ID'
99+
) {
100+
return [$this->phpVersionString, (new PhpVersion($right->value))->getVersionString()];
101+
}
102+
103+
return null;
104+
}
105+
106+
}

src/Type/FileTypeMapper.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use PHPStan\Broker\AnonymousClassNameHelper;
1111
use PHPStan\Cache\Cache;
1212
use PHPStan\Parser\Parser;
13+
use PHPStan\Php\PhpVersion;
1314
use PHPStan\PhpDoc\PhpDocNodeResolver;
1415
use PHPStan\PhpDoc\PhpDocStringResolver;
1516
use PHPStan\PhpDoc\ResolvedPhpDocBlock;
@@ -72,6 +73,7 @@ public function __construct(
7273
private PhpDocNodeResolver $phpDocNodeResolver,
7374
private Cache $cache,
7475
private AnonymousClassNameHelper $anonymousClassNameHelper,
76+
private PhpVersion $phpVersion,
7577
)
7678
{
7779
}
@@ -187,8 +189,8 @@ private function resolvePhpDocStringToDocNode(string $phpDocString): PhpDocNode
187189
private function getNameScopeMap(string $fileName): array
188190
{
189191
if (!isset($this->memoryCache[$fileName])) {
190-
$cacheKey = sprintf('%s-phpdocstring-v17-require-once', $fileName);
191-
$variableCacheKey = implode(',', array_map(static fn (array $file): string => sprintf('%s-%d', $file['filename'], $file['modifiedTime']), $this->getCachedDependentFilesWithTimestamps($fileName)));
192+
$cacheKey = sprintf('%s-phpdocstring', $fileName);
193+
$variableCacheKey = sprintf('%s-%s-v17-require-once', implode(',', array_map(static fn (array $file): string => sprintf('%s-%d', $file['filename'], $file['modifiedTime']), $this->getCachedDependentFilesWithTimestamps($fileName))), $this->phpVersion->getVersionString());
192194
$map = $this->cache->load($cacheKey, $variableCacheKey);
193195

194196
if ($map === null) {
@@ -631,12 +633,12 @@ private function getNameScopeKey(
631633
*/
632634
private function getCachedDependentFilesWithTimestamps(string $fileName): array
633635
{
634-
$cacheKey = sprintf('dependentFilesTimestamps-%s-v2-enum', $fileName);
636+
$cacheKey = sprintf('dependentFilesTimestamps-%s', $fileName);
635637
$fileModifiedTime = filemtime($fileName);
636638
if ($fileModifiedTime === false) {
637639
$fileModifiedTime = time();
638640
}
639-
$variableCacheKey = sprintf('%d', $fileModifiedTime);
641+
$variableCacheKey = sprintf('%d-%s-v2-enum', $fileModifiedTime, $this->phpVersion->getVersionString());
640642
/** @var array<array{filename: string, modifiedTime: int}>|null $cachedFilesTimestamps */
641643
$cachedFilesTimestamps = $this->cache->load($cacheKey, $variableCacheKey);
642644
if ($cachedFilesTimestamps !== null) {

tests/PHPStan/Broker/BrokerTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ protected function setUp(): void
4444
$setterReflectionProviderProvider,
4545
$classReflectionExtensionRegistryProvider,
4646
$this->createMock(FunctionReflectionFactory::class),
47-
new FileTypeMapper($setterReflectionProviderProvider, $this->getParser(), $phpDocStringResolver, $phpDocNodeResolver, $this->createMock(Cache::class), $anonymousClassNameHelper),
47+
new FileTypeMapper($setterReflectionProviderProvider, $this->getParser(), $phpDocStringResolver, $phpDocNodeResolver, $this->createMock(Cache::class), $anonymousClassNameHelper, self::getContainer()->getByType(PhpVersion::class)),
4848
self::getContainer()->getByType(PhpDocInheritanceResolver::class),
4949
self::getContainer()->getByType(PhpVersion::class),
5050
self::getContainer()->getByType(NativeFunctionReflectionProvider::class),

tests/PHPStan/Parser/CleaningParserTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
use PhpParser\Parser\Php7;
88
use PhpParser\PrettyPrinter\Standard;
99
use PHPStan\File\FileReader;
10+
use PHPStan\Php\PhpVersion;
1011
use PHPStan\Testing\PHPStanTestCase;
12+
use const PHP_VERSION_ID;
1113

1214
class CleaningParserTest extends PHPStanTestCase
1315
{
@@ -18,6 +20,37 @@ public function dataParse(): iterable
1820
[
1921
__DIR__ . '/data/cleaning-1-before.php',
2022
__DIR__ . '/data/cleaning-1-after.php',
23+
PHP_VERSION_ID,
24+
],
25+
[
26+
__DIR__ . '/data/cleaning-php-version-before.php',
27+
__DIR__ . '/data/cleaning-php-version-after-81.php',
28+
80100,
29+
],
30+
[
31+
__DIR__ . '/data/cleaning-php-version-before.php',
32+
__DIR__ . '/data/cleaning-php-version-after-81.php',
33+
80200,
34+
],
35+
[
36+
__DIR__ . '/data/cleaning-php-version-before.php',
37+
__DIR__ . '/data/cleaning-php-version-after-74.php',
38+
70400,
39+
],
40+
[
41+
__DIR__ . '/data/cleaning-php-version-before2.php',
42+
__DIR__ . '/data/cleaning-php-version-after-81.php',
43+
80100,
44+
],
45+
[
46+
__DIR__ . '/data/cleaning-php-version-before2.php',
47+
__DIR__ . '/data/cleaning-php-version-after-81.php',
48+
80200,
49+
],
50+
[
51+
__DIR__ . '/data/cleaning-php-version-before2.php',
52+
__DIR__ . '/data/cleaning-php-version-after-74.php',
53+
70400,
2154
],
2255
];
2356
}
@@ -28,13 +61,15 @@ public function dataParse(): iterable
2861
public function testParse(
2962
string $beforeFile,
3063
string $afterFile,
64+
int $phpVersionId,
3165
): void
3266
{
3367
$parser = new CleaningParser(
3468
new SimpleParser(
3569
new Php7(new Emulative()),
3670
new NameResolver(),
3771
),
72+
new PhpVersion($phpVersionId),
3873
);
3974
$printer = new Standard();
4075
$ast = $parser->parseFile($beforeFile);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
declare (strict_types=1);
3+
namespace TestCleanPhpVersion;
4+
5+
use const PHP_VERSION_ID;
6+
if (false) {
7+
} else {
8+
doBar1();
9+
doBar2();
10+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
declare (strict_types=1);
3+
namespace TestCleanPhpVersion;
4+
5+
use const PHP_VERSION_ID;
6+
if (true) {
7+
doFoo1();
8+
doFoo2();
9+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace TestCleanPhpVersion;
4+
5+
use const PHP_VERSION_ID;
6+
7+
if (PHP_VERSION_ID >= 80100) {
8+
doFoo1();
9+
doFoo2();
10+
} else {
11+
doBar1();
12+
doBar2();
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace TestCleanPhpVersion;
4+
5+
use const PHP_VERSION_ID;
6+
7+
if (80100 <= PHP_VERSION_ID) {
8+
doFoo1();
9+
doFoo2();
10+
} else {
11+
doBar1();
12+
doBar2();
13+
}

0 commit comments

Comments
 (0)