classMethodReturnTypeOverrideGuard = $classMethodReturnTypeOverrideGuard; } 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 { $hasReturn = $this->betterNodeFinder->hasInstancesOfInFunctionLikeScoped($node, Return_::class); if ($hasReturn) { return \true; } $hasNotNeverNodes = $this->betterNodeFinder->hasInstancesOfInFunctionLikeScoped($node, \array_merge([Yield_::class], ControlStructure::CONDITIONAL_NODE_SCOPE_TYPES)); 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, $scope)) { return \true; } if (!$node->returnType instanceof Node) { return \false; } return $this->isName($node->returnType, 'never'); } /** * @param \PhpParser\Node\Stmt\ClassMethod|\PhpParser\Node\Expr\Closure|\PhpParser\Node\Stmt\Function_ $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->nodeTypeResolver->getNativeType($stmt); if ($stmtType instanceof NeverType) { $hasNeverType = \true; } } return $hasNeverType; } }