Skip to content
Open
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
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
],
"require": {
"php": "^7.4 || ^8.0",
"phar-io/version": "^3.2",
"phpstan/phpstan": "^2.1.32"
},
"conflict": {
Expand Down
47 changes: 45 additions & 2 deletions src/Rules/PHPUnit/AttributeRequiresPhpVersionRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,33 @@

namespace PHPStan\Rules\PHPUnit;

use PharIo\Version\UnsupportedVersionConstraintException;
use PharIo\Version\Version;
use PharIo\Version\VersionConstraintParser;
use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Node\InClassMethodNode;
use PHPStan\Php\PhpVersion;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPUnit\Framework\TestCase;
use function count;
use function is_numeric;
use function method_exists;
use function preg_match;
use function sprintf;
use function version_compare;

/**
* @implements Rule<InClassMethodNode>
*/
class AttributeRequiresPhpVersionRule implements Rule
{

private const VERSION_COMPARISON = "/(?P<operator>!=|<|<=|<>|=|==|>|>=)?\s*(?P<version>[\d\.-]+(dev|(RC|alpha|beta)[\d\.])?)[ \t]*\r?$/m";

private Version $phpstanPhpVersion;

private PHPUnitVersion $PHPUnitVersion;

private TestMethodsHelper $testMethodsHelper;
Expand All @@ -31,12 +41,15 @@ class AttributeRequiresPhpVersionRule implements Rule
public function __construct(
PHPUnitVersion $PHPUnitVersion,
TestMethodsHelper $testMethodsHelper,
bool $deprecationRulesInstalled
bool $deprecationRulesInstalled,
PhpVersion $phpVersion
)
{
$this->PHPUnitVersion = $PHPUnitVersion;
$this->testMethodsHelper = $testMethodsHelper;
$this->deprecationRulesInstalled = $deprecationRulesInstalled;

$this->phpstanPhpVersion = new Version($phpVersion->getVersionString());
}

public function getNodeType(): string
Expand All @@ -62,6 +75,7 @@ public function processNode(Node $node, Scope $scope): array
}

$errors = [];
$parser = new VersionConstraintParser();
foreach ($reflectionMethod->getAttributes('PHPUnit\Framework\Attributes\RequiresPhp') as $attr) {
$args = $attr->getArguments();
if (count($args) !== 1) {
Expand All @@ -71,6 +85,36 @@ public function processNode(Node $node, Scope $scope): array
if (
!is_numeric($args[0])
) {
try {
$testPhpVersionConstraint = $parser->parse($args[0]);

if ($testPhpVersionConstraint->complies($this->phpstanPhpVersion)) {
continue;
}
} catch (UnsupportedVersionConstraintException $e) {
if (preg_match(self::VERSION_COMPARISON, $args[0], $matches) <= 0) {
$errors[] = RuleErrorBuilder::message(
sprintf($e->getMessage()),
)
->identifier('phpunit.attributeRequiresPhpVersion')
->build();

continue;
}

$operator = $matches['operator'] !== '' ? $matches['operator'] : '>=';

if (version_compare($this->phpstanPhpVersion->getVersionString(), $matches['version'], $operator)) {
continue;
}
}

$errors[] = RuleErrorBuilder::message(
sprintf('Version requirement will always evaluate to false.'),
)
->identifier('phpunit.attributeRequiresPhpVersion')
->build();

continue;
}

Expand All @@ -90,7 +134,6 @@ public function processNode(Node $node, Scope $scope): array
->identifier('phpunit.attributeRequiresPhpVersion')
->build();
}

}

return $errors;
Expand Down
32 changes: 32 additions & 0 deletions tests/Rules/PHPUnit/AttributeRequiresPhpVersionRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PHPStan\Rules\PHPUnit;

use PHPStan\Php\PhpVersion;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPStan\Type\FileTypeMapper;
Expand All @@ -12,6 +13,8 @@
final class AttributeRequiresPhpVersionRuleTest extends RuleTestCase
{

private int $phpVersion = 80500;

private ?int $phpunitMajorVersion;

private ?int $phpunitMinorVersion;
Expand Down Expand Up @@ -78,6 +81,34 @@ public function testRuleOnPHPUnit13(): void
]);
}

public function testPhpVersionMismatch(): void
{
$this->phpunitMajorVersion = 12;
$this->phpunitMinorVersion = 4;
$this->deprecationRulesInstalled = false;

$this->analyse([__DIR__ . '/data/requires-php-version-mismatch.php'], [
[
'Version requirement will always evaluate to false.',
12,
],
]);
}

public function testInvalidPhpVersion(): void
{
$this->phpunitMajorVersion = 12;
$this->phpunitMinorVersion = 4;
$this->deprecationRulesInstalled = false;

$this->analyse([__DIR__ . '/data/requires-php-version-invalid.php'], [
[
'Version constraint abc is not supported.',
12,
],
]);
}

protected function getRule(): Rule
{
$phpunitVersion = new PHPUnitVersion($this->phpunitMajorVersion, $this->phpunitMinorVersion);
Expand All @@ -89,6 +120,7 @@ protected function getRule(): Rule
$phpunitVersion,
),
$this->deprecationRulesInstalled,
new PhpVersion($this->phpVersion),
);
}

Expand Down
17 changes: 17 additions & 0 deletions tests/Rules/PHPUnit/data/requires-php-version-invalid.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace RequiresPhpVersionMismatch;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\RequiresPhp;

class InvalidConstraint extends TestCase
{
#[RequiresPhp('abc')]
public function testFoo(): void {

}
}

24 changes: 24 additions & 0 deletions tests/Rules/PHPUnit/data/requires-php-version-mismatch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace RequiresPhpVersionMismatch;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\RequiresPhp;

class RequiresPhp5 extends TestCase
{
#[RequiresPhp('< 7.0')]
public function testFoo(): void {

}
}

class RequiresPhp8 extends TestCase
{
#[RequiresPhp('>=8.0')]
public function testFoo(): void {

}
}