classMethodReturnTypeOverrideGuard = $classMethodReturnTypeOverrideGuard; $this->betterNodeFinder = $betterNodeFinder; $this->neverFuncCallAnalyzer = $neverFuncCallAnalyzer; $this->classModifierChecker = $classModifierChecker; } public function getRuleDefinition() : RuleDefinition { return new RuleDefinition('Add "never" return-type for methods that never return anything', [new CodeSample(<<<'CODE_SAMPLE' final class SomeClass { public function run() { throw new InvalidException(); } } CODE_SAMPLE , <<<'CODE_SAMPLE' final class SomeClass { public function run(): never { throw new InvalidException(); } } CODE_SAMPLE )]); } /** * @return array> */ public function getNodeTypes() : array { return [ClassMethod::class, Function_::class, Closure::class]; } /** * @param ClassMethod|Function_|Closure $node */ public function refactorWithScope(Node $node, Scope $scope) : ?Node { if ($this->shouldSkip($node, $scope)) { return null; } $node->returnType = new Identifier('never'); return $node; } public function provideMinPhpVersion() : int { return PhpVersionFeature::NEVER_TYPE; } /** * @param \PhpParser\Node\Stmt\ClassMethod|\PhpParser\Node\Stmt\Function_|\PhpParser\Node\Expr\Closure $node */ private function shouldSkip($node, Scope $scope) : bool { if ($node->returnType instanceof Node && !$this->isName($node->returnType, 'void')) { return \true; } if ($this->hasReturnOrYields($node)) { return \true; } if (!$this->hasNeverNodesOrNeverFuncCalls($node)) { return \true; } if ($node instanceof ClassMethod && $this->classMethodReturnTypeOverrideGuard->shouldSkipClassMethod($node, $scope)) { return \true; } if (!$node->returnType instanceof Node) { return \false; } // skip as most likely intentional if (!$this->classModifierChecker->isInsideFinalClass($node) && $this->isName($node->returnType, 'void')) { return \true; } return $this->isName($node->returnType, 'never'); } /** * @param \PhpParser\Node\Stmt\ClassMethod|\PhpParser\Node\Stmt\Function_|\PhpParser\Node\Expr\Closure $node */ private function hasReturnOrYields($node) : bool { if ($this->betterNodeFinder->hasInstancesOfInFunctionLikeScoped($node, Return_::class)) { return \true; } return $this->betterNodeFinder->hasInstancesOfInFunctionLikeScoped($node, \array_merge([Yield_::class, YieldFrom::class], ControlStructure::CONDITIONAL_NODE_SCOPE_TYPES)); } /** * @param \PhpParser\Node\Stmt\ClassMethod|\PhpParser\Node\Stmt\Function_|\PhpParser\Node\Expr\Closure $node */ private function hasNeverNodesOrNeverFuncCalls($node) : bool { $hasNeverNodes = $this->betterNodeFinder->hasInstancesOfInFunctionLikeScoped($node, [Throw_::class]); if ($hasNeverNodes) { return \true; } return $this->neverFuncCallAnalyzer->hasNeverFuncCall($node); } }