diff --git a/bin/generate-function-metadata.php b/bin/generate-function-metadata.php index 1de02f286e..a7e4ebd35a 100755 --- a/bin/generate-function-metadata.php +++ b/bin/generate-function-metadata.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PhpParser\NodeTraverser; use PhpParser\NodeVisitor\NameResolver; +use PhpParser\NodeVisitor\NodeConnectingVisitor; use PhpParser\ParserFactory; (function () { @@ -18,17 +19,34 @@ /** @var string[] */ public $functions = []; + /** @var string[] */ + public $methods = []; + public function enterNode(Node $node) { - if (!$node instanceof Node\Stmt\Function_) { - return; + if ($node instanceof Node\Stmt\Function_) { + foreach ($node->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + if ($attr->name->toString() === \JetBrains\PhpStorm\Pure::class) { + $this->functions[] = $node->namespacedName->toLowerString(); + break; + } + } + } } - foreach ($node->attrGroups as $attrGroup) { - foreach ($attrGroup->attrs as $attr) { - if ($attr->name->toString() === \JetBrains\PhpStorm\Pure::class) { - $this->functions[] = $node->namespacedName->toLowerString(); - break; + if ($node instanceof Node\Stmt\ClassMethod) { + $class = $node->getAttribute('parent'); + if (!$class instanceof Node\Stmt\ClassLike) { + throw new \PHPStan\ShouldNotHappenException($node->name->toString()); + } + $className = $class->namespacedName->toString(); + foreach ($node->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + if ($attr->name->toString() === \JetBrains\PhpStorm\Pure::class) { + $this->methods[] = sprintf('%s::%s', $className, $node->name->toString()); + break; + } } } } @@ -41,6 +59,7 @@ public function enterNode(Node $node) $path = $stubFile->getPathname(); $traverser = new NodeTraverser(); $traverser->addVisitor(new NameResolver()); + $traverser->addVisitor(new NodeConnectingVisitor()); $traverser->addVisitor($visitor); $traverser->traverse( @@ -58,6 +77,15 @@ public function enterNode(Node $node) $metadata[$functionName] = ['hasSideEffects' => false]; } + foreach ($visitor->methods as $methodName) { + if (array_key_exists($methodName, $metadata)) { + if ($metadata[$methodName]['hasSideEffects']) { + throw new \PHPStan\ShouldNotHappenException($methodName); + } + } + $metadata[$methodName] = ['hasSideEffects' => false]; + } + ksort($metadata); $template = <<<'php' diff --git a/resources/functionMetadata.php b/resources/functionMetadata.php index af134c51a3..de5e26f36b 100644 --- a/resources/functionMetadata.php +++ b/resources/functionMetadata.php @@ -1,6 +1,30 @@ ['hasSideEffects' => false], + 'Cassandra\\Exception\\AuthenticationException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\ConfigurationException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\DivideByZeroException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\DomainException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\ExecutionException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\InvalidArgumentException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\InvalidQueryException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\InvalidSyntaxException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\IsBootstrappingException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\LogicException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\OverloadedException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\ProtocolException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\RangeException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\ReadTimeoutException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\RuntimeException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\ServerException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\TimeoutException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\TruncateException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\UnauthorizedException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\UnavailableException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\UnpreparedException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\ValidationException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\WriteTimeoutException::__construct' => ['hasSideEffects' => false], 'DateTime::add' => ['hasSideEffects' => true], 'DateTime::createFromFormat' => ['hasSideEffects' => false], 'DateTime::createFromImmutable' => ['hasSideEffects' => false], @@ -33,6 +57,18 @@ 'DateTimeImmutable::setTimestamp' => ['hasSideEffects' => false], 'DateTimeImmutable::setTimezone' => ['hasSideEffects' => false], 'DateTimeImmutable::sub' => ['hasSideEffects' => false], + 'ErrorException::__construct' => ['hasSideEffects' => false], + 'Exception::__construct' => ['hasSideEffects' => false], + 'Exception::getCode' => ['hasSideEffects' => false], + 'Exception::getFile' => ['hasSideEffects' => false], + 'Exception::getLine' => ['hasSideEffects' => false], + 'Exception::getMessage' => ['hasSideEffects' => false], + 'Exception::getPrevious' => ['hasSideEffects' => false], + 'Exception::getTrace' => ['hasSideEffects' => false], + 'Exception::getTraceAsString' => ['hasSideEffects' => false], + 'MemcachedException::__construct' => ['hasSideEffects' => false], + 'SQLiteException::__construct' => ['hasSideEffects' => false], + 'SoapFault::__construct' => ['hasSideEffects' => false], '_' => ['hasSideEffects' => false], 'abs' => ['hasSideEffects' => false], 'acos' => ['hasSideEffects' => false], diff --git a/tests/PHPStan/Rules/Methods/CallToMethodStamentWithoutSideEffectsRuleTest.php b/tests/PHPStan/Rules/Methods/CallToMethodStamentWithoutSideEffectsRuleTest.php index 2796cc5a77..d368906420 100644 --- a/tests/PHPStan/Rules/Methods/CallToMethodStamentWithoutSideEffectsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallToMethodStamentWithoutSideEffectsRuleTest.php @@ -28,6 +28,10 @@ public function testRule(): void 'Call to static method DateTimeImmutable::createFromFormat() on a separate line has no effect.', 16, ], + [ + 'Call to method Exception::getCode() on a separate line has no effect.', + 21, + ], ]); } diff --git a/tests/PHPStan/Rules/Methods/data/method-call-statement-no-side-effects.php b/tests/PHPStan/Rules/Methods/data/method-call-statement-no-side-effects.php index b677e7c4bf..84b50bac5c 100644 --- a/tests/PHPStan/Rules/Methods/data/method-call-statement-no-side-effects.php +++ b/tests/PHPStan/Rules/Methods/data/method-call-statement-no-side-effects.php @@ -16,4 +16,9 @@ public function doBar(\DateTimeImmutable $dti) $dti->createFromFormat('Y-m-d', '2019-07-24'); } + public function doBaz(\Exception $e) + { + $e->getCode(); + } + }