classMethodReturnTypeOverrideGuard = $classMethodReturnTypeOverrideGuard; $this->phpDocTypeChanger = $phpDocTypeChanger; $this->phpVersionProvider = $phpVersionProvider; } 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 { /** * @return never */ public function run() { 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 refactor(Node $node) : ?Node { if ($this->shouldSkip($node)) { return null; } if ($this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::NEVER_TYPE)) { // never-type supported natively $node->returnType = new Name('never'); } else { // static anlysis based never type $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); $hasChanged = $this->phpDocTypeChanger->changeReturnType($phpDocInfo, new NeverType()); if (!$hasChanged) { return null; } } return $node; } /** * @param \PhpParser\Node\Stmt\ClassMethod|\PhpParser\Node\Stmt\Function_|\PhpParser\Node\Expr\Closure $node */ private function shouldSkip($node) : bool { $hasReturn = $this->betterNodeFinder->hasInstancesOfInFunctionLikeScoped($node, Return_::class); if ($node instanceof ClassMethod && $node->isMagic()) { return \true; } if ($hasReturn) { return \true; } $yieldAndConditionalNodes = \array_merge([Yield_::class], ControlStructure::CONDITIONAL_NODE_SCOPE_TYPES); $hasNotNeverNodes = $this->betterNodeFinder->hasInstancesOfInFunctionLikeScoped($node, $yieldAndConditionalNodes); if ($hasNotNeverNodes) { return \true; } $hasNeverNodes = $this->betterNodeFinder->hasInstancesOfInFunctionLikeScoped($node, [Throw_::class]); $hasNeverFuncCall = $this->hasNeverFuncCall($node); if (!$hasNeverNodes && !$hasNeverFuncCall) { return \true; } if ($node instanceof ClassMethod && $this->classMethodReturnTypeOverrideGuard->shouldSkipClassMethod($node)) { return \true; } if (!$node->returnType instanceof Node) { return \false; } return $this->isName($node->returnType, 'never'); } /** * @param \PhpParser\Node\Stmt\ClassMethod|\PhpParser\Node\Stmt\Function_|\PhpParser\Node\Expr\Closure $functionLike */ private function hasNeverFuncCall($functionLike) : bool { $hasNeverType = \false; foreach ((array) $functionLike->stmts as $stmt) { if ($stmt instanceof Expression) { $stmt = $stmt->expr; } if ($stmt instanceof Stmt) { continue; } $stmtType = $this->getType($stmt); if ($stmtType instanceof NeverType) { $hasNeverType = \true; } } return $hasNeverType; } }