ifManipulator = $ifManipulator; $this->invertedIfFactory = $invertedIfFactory; $this->contextAnalyzer = $contextAnalyzer; $this->binaryOpConditionsCollector = $binaryOpConditionsCollector; $this->simpleScalarAnalyzer = $simpleScalarAnalyzer; $this->ifAndAnalyzer = $ifAndAnalyzer; } public function getRuleDefinition() : RuleDefinition { return new RuleDefinition('Changes if && to early return', [new CodeSample(<<<'CODE_SAMPLE' class SomeClass { public function canDrive(Car $car) { if ($car->hasWheels && $car->hasFuel) { return true; } return false; } } CODE_SAMPLE , <<<'CODE_SAMPLE' class SomeClass { public function canDrive(Car $car) { if (! $car->hasWheels) { return false; } if (! $car->hasFuel) { return false; } return true; } } CODE_SAMPLE )]); } /** * @return array> */ public function getNodeTypes() : array { return [ClassMethod::class, Function_::class, Foreach_::class, Closure::class, FileWithoutNamespace::class, Namespace_::class]; } /** * @param Stmt\ClassMethod|Stmt\Function_|Stmt\Foreach_|Expr\Closure|FileWithoutNamespace|Stmt\Namespace_ $node */ public function refactor(Node $node) : ?Node { $stmts = (array) $node->stmts; if ($stmts === []) { return null; } $newStmts = []; foreach ($stmts as $key => $stmt) { if (!$stmt instanceof If_) { // keep natural original order $newStmts[] = $stmt; continue; } $nextStmt = $stmts[$key + 1] ?? null; if ($this->isComplexReturn($nextStmt)) { return null; } if ($this->shouldSkip($stmt, $nextStmt)) { $newStmts[] = $stmt; continue; } if ($nextStmt instanceof Return_) { if ($this->ifAndAnalyzer->isIfStmtExprUsedInNextReturn($stmt, $nextStmt)) { continue; } if ($nextStmt->expr instanceof BooleanAnd) { continue; } } /** @var BooleanAnd $expr */ $expr = $stmt->cond; $booleanAndConditions = $this->binaryOpConditionsCollector->findConditions($expr, BooleanAnd::class); $afterStmts = []; if (!$nextStmt instanceof Return_) { $afterStmts[] = $stmt->stmts[0]; $node->stmts = \array_merge($newStmts, $this->processReplaceIfs($stmt, $booleanAndConditions, new Return_(), $afterStmts, $nextStmt)); return $node; } // remove next node unset($newStmts[$key + 1]); $afterStmts[] = $stmt->stmts[0]; $ifNextReturnClone = $stmt->stmts[0] instanceof Return_ ? clone $stmt->stmts[0] : new Return_(); if ($this->isInLoopWithoutContinueOrBreak($stmt)) { $afterStmts[] = new Return_(); } $changedStmts = $this->processReplaceIfs($stmt, $booleanAndConditions, $ifNextReturnClone, $afterStmts, $nextStmt); // update stmts $node->stmts = \array_merge($newStmts, $changedStmts); return $node; } return null; } private function isInLoopWithoutContinueOrBreak(If_ $if) : bool { if (!$this->contextAnalyzer->isInLoop($if)) { return \false; } if ($if->stmts[0] instanceof Continue_) { return \false; } return !$if->stmts[0] instanceof Break_; } /** * @param Expr[] $conditions * @param Stmt[] $afters * @return Stmt[] */ private function processReplaceIfs(If_ $if, array $conditions, Return_ $ifNextReturn, array $afters, ?Stmt $nextStmt) : array { $ifs = $this->invertedIfFactory->createFromConditions($if, $conditions, $ifNextReturn, $nextStmt); $this->mirrorComments($ifs[0], $if); $result = \array_merge($ifs, $afters); if ($if->stmts[0] instanceof Return_) { return $result; } if (!$ifNextReturn->expr instanceof Expr) { return $result; } if ($this->contextAnalyzer->isInLoop($if)) { return $result; } return \array_merge($result, [$ifNextReturn]); } private function shouldSkip(If_ $if, ?Stmt $nexStmt) : bool { if (!$this->ifManipulator->isIfWithOnlyOneStmt($if)) { return \true; } if (!$if->cond instanceof BooleanAnd) { return \true; } if (!$this->ifManipulator->isIfWithoutElseAndElseIfs($if)) { return \true; } // is simple return? skip it $onlyStmt = $if->stmts[0]; if ($onlyStmt instanceof Return_ && $onlyStmt->expr instanceof Expr && $this->simpleScalarAnalyzer->isSimpleScalar($onlyStmt->expr)) { return \true; } if ($this->ifAndAnalyzer->isIfAndWithInstanceof($if->cond)) { return \true; } return !$this->isLastIfOrBeforeLastReturn($nexStmt); } private function isLastIfOrBeforeLastReturn(?Stmt $nextStmt) : bool { if (!$nextStmt instanceof Stmt) { return \true; } return $nextStmt instanceof Return_; } private function isComplexReturn(?Stmt $stmt) : bool { if (!$stmt instanceof Return_) { return \false; } if (!$stmt->expr instanceof Expr) { return \false; } if ($stmt->expr instanceof ConstFetch) { return \false; } return !$stmt->expr instanceof Scalar; } }