diff --git a/src/TodoBySymfonyDeprecationRule.php b/src/TodoBySymfonyDeprecationRule.php new file mode 100644 index 0000000..31392be --- /dev/null +++ b/src/TodoBySymfonyDeprecationRule.php @@ -0,0 +1,120 @@ + + */ +final class TodoBySymfonyDeprecationRule implements Rule +{ + private string $workingDirectory; + + public function __construct( + string $workingDirectory + ) { + $this->workingDirectory = $workingDirectory; + + // require the top level installed versions, so we don't mix it up with the one in phpstan.phar + $installedVersions = $this->workingDirectory . '/vendor/composer/InstalledVersions.php'; + if (!class_exists(InstalledVersions::class, false) && is_readable($installedVersions)) { + require_once $installedVersions; + } + } + + public function getNodeType(): string + { + return Node\Expr\FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!($node->name instanceof Node\Name)) { + return []; + } + + $args = $node->getArgs(); + if (count($args) < 3) { + return []; + } + + $functionName = strtolower($node->name->toString()); + if ($functionName !== 'trigger_deprecation') { + return []; + } + + $packageArgType = $scope->getType($args[0]->value); + $versionArgType = $scope->getType($args[1]->value); + $messageArgType = $scope->getType($args[2]->value); + + $messages = $messageArgType->getConstantStrings(); + if (count($messages) !== 1) { + return []; + } + $message = $messages[0]->getValue(); + + $errors = []; + foreach($packageArgType->getConstantStrings() as $package) { + foreach ($versionArgType->getConstantStrings() as $version) { + + $satisfiesOrError = $this->satisfiesInstalledPackage($package->getValue(), $version->getValue()); + if ($satisfiesOrError instanceof RuleError) { + $errors[] = $satisfiesOrError; + continue; + } + if (true !== $satisfiesOrError) { + continue; + } + + $errorMessage = 'Since %s %s: %s.'; + $errors[] = sprintf($errorMessage, $package->getValue(), $version->getValue(), $message); + } + } + + return $errors; + } + + /** + * @return bool|\PHPStan\Rules\RuleError + */ + private function satisfiesInstalledPackage(string $package, string $version) + { + $versionParser = new VersionParser(); + + // see https://getcomposer.org/doc/07-runtime.md#installed-versions + if (!InstalledVersions::isInstalled($package)) { + return false; + } + + try { + return InstalledVersions::satisfies($versionParser, $package, '>='.$version); + } catch (UnexpectedValueException $e) { + return RuleErrorBuilder::message( + 'Invalid version constraint "' . $version . '" for package "' . $package . '".', + )->build(); + } + } + +} diff --git a/tests/TodoBySymfonyDeprecationTest.php b/tests/TodoBySymfonyDeprecationTest.php new file mode 100644 index 0000000..7e1f804 --- /dev/null +++ b/tests/TodoBySymfonyDeprecationTest.php @@ -0,0 +1,36 @@ + + * @internal + */ +final class TodoBySymfonyDeprecationTest extends RuleTestCase +{ + protected function getRule(): Rule + { + return new TodoBySymfonyDeprecationRule( + dirname(__DIR__), + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/sf-trigger-deprecation.php'], [ + [ + 'Since phpunit/phpunit 9.5: Using this is deprecated. Use that instead..', + 5 + ] + ]); + } + +} diff --git a/tests/data/sf-trigger-deprecation.php b/tests/data/sf-trigger-deprecation.php new file mode 100644 index 0000000..3119bb1 --- /dev/null +++ b/tests/data/sf-trigger-deprecation.php @@ -0,0 +1,5 @@ +