diff --git a/conf/config.level4.neon b/conf/config.level4.neon index b33f2b5f49..f5a57d273c 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -8,6 +8,7 @@ rules: - PHPStan\Rules\DeadCode\UnreachableStatementRule - PHPStan\Rules\Exceptions\DeadCatchRule - PHPStan\Rules\Functions\CallToFunctionStamentWithoutSideEffectsRule + - PHPStan\Rules\Methods\CallToConstructorStatementWithoutSideEffectsRule - PHPStan\Rules\Methods\CallToMethodStamentWithoutSideEffectsRule - PHPStan\Rules\Methods\CallToStaticMethodStamentWithoutSideEffectsRule - PHPStan\Rules\TooWideTypehints\TooWideArrowFunctionReturnTypehintRule diff --git a/src/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRule.php b/src/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRule.php new file mode 100644 index 0000000000..c68ba92b5a --- /dev/null +++ b/src/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRule.php @@ -0,0 +1,64 @@ + + */ +class CallToConstructorStatementWithoutSideEffectsRule implements Rule +{ + + private ReflectionProvider $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + + public function getNodeType(): string + { + return Node\Stmt\Expression::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->expr instanceof Node\Expr\New_) { + return []; + } + + $instantiation = $node->expr; + if (!$instantiation->class instanceof Node\Name) { + return []; + } + + $className = $scope->resolveName($instantiation->class); + if (!$this->reflectionProvider->hasClass($className)) { + return []; + } + + $classReflection = $this->reflectionProvider->getClass($className); + if (!$classReflection->hasConstructor()) { + return []; + } + + $constructor = $classReflection->getConstructor(); + if ($constructor->hasSideEffects()->no()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Call to %s::%s() on a separate line has no effect.', + $classReflection->getDisplayName(), + $constructor->getName() + ))->build(), + ]; + } + + return []; + } + +} diff --git a/tests/PHPStan/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRuleTest.php b/tests/PHPStan/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRuleTest.php new file mode 100644 index 0000000000..ac3c5a4443 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRuleTest.php @@ -0,0 +1,29 @@ + + */ +class CallToConstructorStatementWithoutSideEffectsRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new CallToConstructorStatementWithoutSideEffectsRule($this->createReflectionProvider()); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/constructor-statement-no-side-effects.php'], [ + [ + 'Call to Exception::__construct() on a separate line has no effect.', + 6, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Methods/data/constructor-statement-no-side-effects.php b/tests/PHPStan/Rules/Methods/data/constructor-statement-no-side-effects.php new file mode 100644 index 0000000000..15467310ae --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/constructor-statement-no-side-effects.php @@ -0,0 +1,13 @@ +