typeFactory = $typeFactory; $this->reflectionProvider = $reflectionProvider; $this->reflectionResolver = $reflectionResolver; $this->strictReturnNewAnalyzer = $strictReturnNewAnalyzer; $this->classMethodReturnTypeOverrideGuard = $classMethodReturnTypeOverrideGuard; $this->returnTypeInferer = $returnTypeInferer; } public function getRuleDefinition() : RuleDefinition { return new RuleDefinition('Add return type to function like with return new', [new CodeSample(<<<'CODE_SAMPLE' final class SomeClass { public function action() { return new Response(); } } CODE_SAMPLE , <<<'CODE_SAMPLE' final class SomeClass { public function action(): Response { return new Response(); } } CODE_SAMPLE )]); } /** * @return array> */ public function getNodeTypes() : array { return [ClassMethod::class, Function_::class, Closure::class, ArrowFunction::class]; } /** * @param ClassMethod|Function_|ArrowFunction $node */ public function refactor(Node $node) : ?Node { if ($node->returnType !== null) { return null; } if ($node instanceof ClassMethod && $this->classMethodReturnTypeOverrideGuard->shouldSkipClassMethod($node)) { return null; } if (!$node instanceof ArrowFunction) { $returnedNewClassName = $this->strictReturnNewAnalyzer->matchAlwaysReturnVariableNew($node); if (\is_string($returnedNewClassName)) { $node->returnType = new FullyQualified($returnedNewClassName); return $node; } } return $this->refactorDirectReturnNew($node); } public function provideMinPhpVersion() : int { return PhpVersionFeature::SCALAR_TYPES; } /** * @return \PHPStan\Type\ObjectType|\PHPStan\Type\StaticType */ private function createObjectTypeFromNew(New_ $new) { $className = $this->getName($new->class); if ($className === null) { throw new ShouldNotHappenException(); } if ($className === ObjectReference::STATIC || $className === ObjectReference::SELF) { $classReflection = $this->reflectionResolver->resolveClassReflection($new); if (!$classReflection instanceof ClassReflection) { throw new ShouldNotHappenException(); } if ($className === ObjectReference::SELF) { return new SelfStaticType($classReflection); } return new StaticType($classReflection); } $classReflection = $this->reflectionProvider->getClass($className); return new ObjectType($className, null, $classReflection); } /** * @param \PhpParser\Node\Stmt\ClassMethod|\PhpParser\Node\Stmt\Function_|\PhpParser\Node\Expr\ArrowFunction|\PhpParser\Node\Expr\Closure $node * @return null|\PhpParser\Node\Expr\ArrowFunction|\PhpParser\Node\Stmt\Function_|\PhpParser\Node\Stmt\ClassMethod|\PhpParser\Node\Expr\Closure */ private function refactorDirectReturnNew($node) { if ($node instanceof ArrowFunction) { $returns = [new Return_($node->expr)]; } else { /** @var Return_[] $returns */ $returns = $this->betterNodeFinder->findInstancesOfInFunctionLikeScoped($node, Return_::class); } if ($returns === []) { return null; } $newTypes = $this->resolveReturnNewType($returns); if ($newTypes === null) { return null; } $returnType = $this->typeFactory->createMixedPassedOrUnionType($newTypes); $returnTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($returnType, TypeKind::RETURN); if (!$returnTypeNode instanceof Node) { return null; } $returnType = $this->returnTypeInferer->inferFunctionLike($node); if ($returnType instanceof UnionType) { return null; } $node->returnType = $returnTypeNode; return $node; } /** * @param Return_[] $returns * @return Type[]|null */ private function resolveReturnNewType(array $returns) : ?array { $newTypes = []; foreach ($returns as $return) { if (!$return->expr instanceof New_) { return null; } $new = $return->expr; if (!$new->class instanceof Name) { return null; } $newTypes[] = $this->createObjectTypeFromNew($new); } return $newTypes; } }