betterNodeFinder = $betterNodeFinder; $this->silentVoidResolver = $silentVoidResolver; $this->phpDocInfoFactory = $phpDocInfoFactory; $this->typeFactory = $typeFactory; $this->phpDocTypeChanger = $phpDocTypeChanger; } public function getRuleDefinition() : RuleDefinition { return new RuleDefinition('Add explicit return null to method/function that returns a value, but missed main return', [new CodeSample(<<<'CODE_SAMPLE' class SomeClass { /** * @return string|void */ public function run(int $number) { if ($number > 50) { return 'yes'; } } } CODE_SAMPLE , <<<'CODE_SAMPLE' class SomeClass { /** * @return string|null */ public function run(int $number) { if ($number > 50) { return 'yes'; } return null; } } CODE_SAMPLE )]); } /** * @return array> */ public function getNodeTypes() : array { return [ClassMethod::class]; } /** * @param ClassMethod $node */ public function refactor(Node $node) : ?Node { // known return type, nothing to improve if ($node->returnType instanceof Node) { return null; } if ($this->containsYieldOrThrow($node)) { return null; } // it has at least some return value in it if (!$this->hasReturnsWithValues($node)) { return null; } if (!$this->silentVoidResolver->hasSilentVoid($node)) { return null; } $node->stmts[] = new Return_(new ConstFetch(new Name('null'))); $this->transformDocUnionVoidToUnionNull($node); return $node; } private function transformDocUnionVoidToUnionNull(ClassMethod $classMethod) : void { $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($classMethod); $returnType = $phpDocInfo->getReturnType(); if (!$returnType instanceof UnionType) { return; } $newTypes = []; foreach ($returnType->getTypes() as $type) { if ($type instanceof VoidType) { $type = new NullType(); } $newTypes[] = $type; } $type = $this->typeFactory->createMixedPassedOrUnionTypeAndKeepConstant($newTypes); if (!$type instanceof UnionType) { return; } $this->phpDocTypeChanger->changeReturnType($classMethod, $phpDocInfo, $type); } private function containsYieldOrThrow(ClassMethod $classMethod) : bool { return (bool) $this->betterNodeFinder->findInstancesOf($classMethod, [Yield_::class, Throw_::class, Node\Expr\Throw_::class, YieldFrom::class]); } private function hasReturnsWithValues(ClassMethod $classMethod) : bool { /** @var Return_[] $returns */ $returns = $this->betterNodeFinder->findInstancesOfInFunctionLikeScoped($classMethod, Return_::class); foreach ($returns as $return) { if (!$return->expr instanceof Node) { return \false; } } return $returns !== []; } }