returnAnalyzer = $returnAnalyzer; $this->reflectionProvider = $reflectionProvider; $this->valueResolver = $valueResolver; $this->betterNodeFinder = $betterNodeFinder; $this->classMethodReturnTypeOverrideGuard = $classMethodReturnTypeOverrideGuard; } public function getRuleDefinition() : RuleDefinition { return new RuleDefinition('Change return type based on strict returns type operations', [new CodeSample(<<<'CODE_SAMPLE' class SomeClass { public function resolve($first, $second) { if ($first) { return false; } return $first > $second; } } CODE_SAMPLE , <<<'CODE_SAMPLE' class SomeClass { public function resolve($first, $second): bool { if ($first) { return false; } return $first > $second; } } CODE_SAMPLE )]); } /** * @funcCall 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; } $returns = $this->betterNodeFinder->findInstancesOfInFunctionLikeScoped($node, Return_::class); if (!$this->hasOnlyBoolScalarReturnExprs($returns, $node)) { return null; } $node->returnType = new Identifier('bool'); return $node; } public function provideMinPhpVersion() : int { return PhpVersionFeature::SCALAR_TYPES; } /** * @param ClassMethod|Function_|Closure $node */ private function shouldSkip(Node $node, Scope $scope) : bool { if ($node->returnType instanceof Node) { return \true; } return $node instanceof ClassMethod && $this->classMethodReturnTypeOverrideGuard->shouldSkipClassMethod($node, $scope); } /** * @param Return_[] $returns * @param \PhpParser\Node\Stmt\ClassMethod|\PhpParser\Node\Stmt\Function_|\PhpParser\Node\Expr\Closure $functionLike */ private function hasOnlyBoolScalarReturnExprs(array $returns, $functionLike) : bool { if ($returns === []) { return \false; } if (!$this->returnAnalyzer->hasClassMethodRootReturn($functionLike)) { return \false; } foreach ($returns as $return) { if (!$return->expr instanceof Expr) { return \false; } if ($this->valueResolver->isTrueOrFalse($return->expr)) { continue; } if ($this->isBooleanBinaryOp($return->expr)) { continue; } if ($return->expr instanceof FuncCall && $this->isNativeBooleanReturnTypeFuncCall($return->expr)) { continue; } return \false; } return \true; } private function isNativeBooleanReturnTypeFuncCall(FuncCall $funcCall) : bool { $functionName = $this->getName($funcCall); if (!\is_string($functionName)) { return \false; } $functionReflection = $this->reflectionProvider->getFunction(new Name($functionName), null); if (!$functionReflection->isBuiltin()) { return \false; } foreach ($functionReflection->getVariants() as $parametersAcceptorWithPhpDoc) { return $parametersAcceptorWithPhpDoc->getNativeReturnType() instanceof BooleanType; } return \false; } private function isBooleanBinaryOp(Expr $expr) : bool { if ($expr instanceof Smaller) { return \true; } if ($expr instanceof SmallerOrEqual) { return \true; } if ($expr instanceof Greater) { return \true; } if ($expr instanceof GreaterOrEqual) { return \true; } if ($expr instanceof BooleanOr) { return \true; } if ($expr instanceof BooleanAnd) { return \true; } if ($expr instanceof Identical) { return \true; } if ($expr instanceof NotIdentical) { return \true; } if ($expr instanceof Equal) { return \true; } return $expr instanceof NotEqual; } }