Skip to content
Draft
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
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"require": {
"php": "^7.4 || ^8.0",
"phar-io/version": "^3.2",
"phpstan/phpstan": "^2.2.6"
"phpstan/phpstan": "^2.2.6",
"sebastian/version-requirement": "dev-main"
},
"conflict": {
"phpunit/phpunit": "<7.0"
Expand All @@ -19,7 +20,7 @@
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/phpstan-deprecation-rules": "^2.0",
"phpstan/phpstan-strict-rules": "^2.0",
"phpunit/phpunit": "^9.6",
"phpunit/phpunit": "^12",
"shipmonk/name-collision-detector": "^2.1"
},
"config": {
Expand Down
141 changes: 52 additions & 89 deletions src/Rules/PHPUnit/AttributeVersionRequirementHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,23 @@

namespace PHPStan\Rules\PHPUnit;

use PharIo\Version\UnsupportedVersionConstraintException;
use PharIo\Version\Version;
use PharIo\Version\VersionConstraintParser;
use PHPStan\Analyser\Scope;
use PHPStan\BetterReflection\Reflection\ReflectionAttribute;
use PHPStan\Php\ConfiguredPhpVersionRangeHelper;
use PHPStan\Php\PhpMinorVersionIterator;
use PHPStan\Rules\IdentifierRuleError;
use PHPStan\Rules\RuleErrorBuilder;
use SebastianBergmann\VersionRequirement\InvalidVersionOperatorException;
use SebastianBergmann\VersionRequirement\InvalidVersionRequirementException;
use SebastianBergmann\VersionRequirement\Requirement;
use function count;
use function is_numeric;
use function preg_match;
use function sprintf;
use function strpos;
use function substr_count;
use function version_compare;

final class AttributeVersionRequirementHelper
{

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

Check failure on line 21 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, lowest, ^12.0.9)

Constant PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::VERSION_COMPARISON is unused.

Check failure on line 21 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, highest, ^10.5)

Constant PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::VERSION_COMPARISON is unused.

Check failure on line 21 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, highest, ^10.5)

Constant PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::VERSION_COMPARISON is unused.

Check failure on line 21 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, highest, ^12.0.9)

Constant PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::VERSION_COMPARISON is unused.

Check failure on line 21 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, lowest, ^10.5)

Constant PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::VERSION_COMPARISON is unused.

Check failure on line 21 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, lowest, ^9.5)

Constant PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::VERSION_COMPARISON is unused.

Check failure on line 21 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, lowest, ^11.5)

Constant PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::VERSION_COMPARISON is unused.

Check failure on line 21 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, highest, ^11.5)

Constant PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::VERSION_COMPARISON is unused.

Check failure on line 21 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, lowest, ^9.5)

Constant PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::VERSION_COMPARISON is unused.

Check failure on line 21 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, highest, ^11.5)

Constant PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::VERSION_COMPARISON is unused.

Check failure on line 21 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, lowest, ^12.0.9)

Constant PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::VERSION_COMPARISON is unused.

Check failure on line 21 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, highest, ^9.5)

Constant PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::VERSION_COMPARISON is unused.

Check failure on line 21 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, lowest, ^10.5)

Constant PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::VERSION_COMPARISON is unused.

Check failure on line 21 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, highest, ^9.5)

Constant PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::VERSION_COMPARISON is unused.

Check failure on line 21 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, highest, ^12.0.9)

Constant PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::VERSION_COMPARISON is unused.

Check failure on line 21 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, lowest, ^11.5)

Constant PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::VERSION_COMPARISON is unused.

private PHPUnitVersion $PHPUnitVersion;

Expand All @@ -36,7 +32,7 @@
*/
private bool $warnAboutIncompleteVersion;

private bool $bleedingEdge;

Check failure on line 35 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, lowest, ^12.0.9)

Property PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::$bleedingEdge is never read, only written.

Check failure on line 35 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, highest, ^10.5)

Property PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::$bleedingEdge is never read, only written.

Check failure on line 35 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, highest, ^10.5)

Property PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::$bleedingEdge is never read, only written.

Check failure on line 35 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, highest, ^12.0.9)

Property PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::$bleedingEdge is never read, only written.

Check failure on line 35 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, lowest, ^10.5)

Property PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::$bleedingEdge is never read, only written.

Check failure on line 35 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, lowest, ^9.5)

Property PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::$bleedingEdge is never read, only written.

Check failure on line 35 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, lowest, ^11.5)

Property PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::$bleedingEdge is never read, only written.

Check failure on line 35 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, highest, ^11.5)

Property PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::$bleedingEdge is never read, only written.

Check failure on line 35 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, lowest, ^9.5)

Property PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::$bleedingEdge is never read, only written.

Check failure on line 35 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, highest, ^11.5)

Property PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::$bleedingEdge is never read, only written.

Check failure on line 35 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, lowest, ^12.0.9)

Property PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::$bleedingEdge is never read, only written.

Check failure on line 35 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, highest, ^9.5)

Property PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::$bleedingEdge is never read, only written.

Check failure on line 35 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, lowest, ^10.5)

Property PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::$bleedingEdge is never read, only written.

Check failure on line 35 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, highest, ^9.5)

Property PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::$bleedingEdge is never read, only written.

Check failure on line 35 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, highest, ^12.0.9)

Property PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::$bleedingEdge is never read, only written.

Check failure on line 35 in src/Rules/PHPUnit/AttributeVersionRequirementHelper.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, lowest, ^11.5)

Property PHPStan\Rules\PHPUnit\AttributeVersionRequirementHelper::$bleedingEdge is never read, only written.

private ConfiguredPhpVersionRangeHelper $phpVersionRangeHelper;

Expand All @@ -63,103 +59,88 @@
public function checkVersionRequirement(array $attributes, Scope $scope): array
{
$errors = [];
$parser = new VersionConstraintParser();
foreach ($attributes as $attr) {
$args = $attr->getArguments();
if (count($args) !== 1) {
continue;
}

// the following block is mimicing PHPUnit version parsing
// see https://github.com/sebastianbergmann/phpunit/blob/43c2cd7b96ee1e800b35e4df23b419a88b53111d/src/Metadata/Version/Requirement.php

$versionRequirement = $args[0];

if ($this->warnAboutIncompleteVersion($versionRequirement)) {
try {
$requirement = Requirement::from($versionRequirement);
} catch (InvalidVersionOperatorException $e) {
if ($this->PHPUnitVersion->requiresPhpversionAttributeWithOperator()->yes()) {
$errors[] = RuleErrorBuilder::message(
sprintf('Version requirement is missing operator.'),
)
->identifier('phpunit.attributeRequiresPhpVersion')
->build();
} elseif (
$this->deprecationRulesInstalled
&& $this->PHPUnitVersion->deprecatesPhpversionAttributeWithoutOperator()->yes()
) {
$errors[] = RuleErrorBuilder::message(
sprintf('Version requirement without operator is deprecated.'),
)
->identifier('phpunit.attributeRequiresPhpVersion')
->build();
}

continue;
} catch (InvalidVersionRequirementException $e) {
$errors[] = RuleErrorBuilder::message(
sprintf('Version requirement is incomplete.'),
sprintf($e->getMessage()),
)
->identifier('phpunit.attributeRequiresPhpVersion')
->build();

continue;
}

if (
!is_numeric($versionRequirement)
) {
if (!$this->bleedingEdge) {
if (!$requirement->isComplete()) {
if (!$this->warnAboutIncompleteVersion) {
continue;
}

$pharIoVersions = strpos($attr->getName(), 'RequiresPhpunit') !== false
? $this->PHPUnitVersion->getPharIoVersions()
: $this->getAnalyzedPhpVersions();
if ($pharIoVersions === []) {
if (!$this->PHPUnitVersion->warnsAboutIncompleteVersion()->yes()) {
continue;
}

try {
// check composer like version constraints, e.g. ^1 or ~2
$testPhpVersionConstraint = $parser->parse($versionRequirement);

foreach ($pharIoVersions as $pharIoVersion) {
if ($testPhpVersionConstraint->complies($pharIoVersion)) {
// one of the versions within range matched, check next attribute
continue 2;
}
}
} catch (UnsupportedVersionConstraintException $e) {
// test php-src builtin operators as in version_compare()
if (preg_match(self::VERSION_COMPARISON, $versionRequirement, $matches) <= 0) {
$errors[] = RuleErrorBuilder::message(
sprintf($e->getMessage()),
)
->identifier('phpunit.attributeRequiresPhpVersion')
->build();

continue;
}

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

foreach ($pharIoVersions as $pharIoVersion) {
if (version_compare($pharIoVersion->getVersionString(), $matches['version'], $operator)) {
// one of the versions within range matched, check next attribute
continue 2;
}
}
}

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

continue;
}

if ($this->PHPUnitVersion->requiresPhpversionAttributeWithOperator()->yes()) {
$errors[] = RuleErrorBuilder::message(
sprintf('Version requirement is missing operator.'),
)
->identifier('phpunit.attributeRequiresPhpVersion')
->build();
} elseif (
$this->deprecationRulesInstalled
&& $this->PHPUnitVersion->deprecatesPhpversionAttributeWithoutOperator()->yes()
) {
$errors[] = RuleErrorBuilder::message(
sprintf('Version requirement without operator is deprecated.'),
)
->identifier('phpunit.attributeRequiresPhpVersion')
->build();
$versionStrings = strpos($attr->getName(), 'RequiresPhpunit') !== false
? $this->PHPUnitVersion->getMinMaxVersion()
: $this->getAnalyzedPhpVersions();
if ($versionStrings === []) {
continue;
}
foreach ($versionStrings as $versionString) {
if ($requirement->isSatisfiedBy($versionString)) {
// one of the versions within range matched, check next attribute
continue 2;
}
}

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

return $errors;
}

/**
* @return Version[]
* @return list<string>
*/
private function getAnalyzedPhpVersions(): array
{
Expand All @@ -172,30 +153,12 @@
$maxVersion,
);
foreach ($minorVersionIterator as $phpstanVersion) {
$versions[] = new Version($phpstanVersion->getVersionString());
$versions[] = $phpstanVersion->getVersionString();
}
return $versions;
}

return [];
}

// see https://github.com/sebastianbergmann/phpunit/issues/6451
private function warnAboutIncompleteVersion(string $versionRequirement): bool
{
if (!$this->bleedingEdge) {
return false;
}

if (!$this->warnAboutIncompleteVersion) {
return false;
}

if (!$this->PHPUnitVersion->warnsAboutIncompleteVersion()->yes()) {
return false;
}

return substr_count($versionRequirement, '.') !== 2;
}

}
9 changes: 4 additions & 5 deletions src/Rules/PHPUnit/PHPUnitVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace PHPStan\Rules\PHPUnit;

use PharIo\Version\Version;
use PHPStan\TrinaryLogic;
use function sprintf;

Expand All @@ -20,17 +19,17 @@ public function __construct(?int $majorVersion, ?int $minorVersion)
}

/**
* @return array{}|array{Version, Version}
* @return array{}|array{string, string}
*/
public function getPharIoVersions(): array
public function getMinMaxVersion(): array
{
if ($this->majorVersion === null || $this->minorVersion === null) {
return [];
}

return [
new Version(sprintf('%d.%d.0', $this->majorVersion, $this->minorVersion)),
new Version(sprintf('%d.%d.99', $this->majorVersion, $this->minorVersion)),
sprintf('%d.%d.0', $this->majorVersion, $this->minorVersion),
sprintf('%d.%d.99', $this->majorVersion, $this->minorVersion),
];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public function testInvalidPhpVersion(): void

$this->analyse([__DIR__ . '/data/requires-php-version-invalid.php'], [
[
'Version constraint abc is not supported.',
'"abc" is not a valid version requirement: expected a version constraint (such as "^8.1", "~8.1.0", or "8.1.*") or a version comparison (such as ">= 8.1.0")',
12,
],
]);
Expand Down
Loading