2021-08-30 08:27:47 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare (strict_types=1);
|
2022-06-06 17:12:56 +00:00
|
|
|
namespace Rector\CodingStyle\Rector\Stmt;
|
2021-08-30 08:27:47 +00:00
|
|
|
|
2022-06-06 17:12:56 +00:00
|
|
|
use PhpParser\Comment;
|
|
|
|
use PhpParser\Node;
|
|
|
|
use PhpParser\Node\Stmt;
|
|
|
|
use PhpParser\Node\Stmt\Class_;
|
|
|
|
use PhpParser\Node\Stmt\ClassConst;
|
2023-03-27 07:32:51 +00:00
|
|
|
use PhpParser\Node\Stmt\ClassLike;
|
2022-06-06 17:12:56 +00:00
|
|
|
use PhpParser\Node\Stmt\ClassMethod;
|
|
|
|
use PhpParser\Node\Stmt\Do_;
|
|
|
|
use PhpParser\Node\Stmt\For_;
|
|
|
|
use PhpParser\Node\Stmt\Foreach_;
|
|
|
|
use PhpParser\Node\Stmt\Function_;
|
|
|
|
use PhpParser\Node\Stmt\If_;
|
|
|
|
use PhpParser\Node\Stmt\Interface_;
|
|
|
|
use PhpParser\Node\Stmt\Nop;
|
|
|
|
use PhpParser\Node\Stmt\Property;
|
|
|
|
use PhpParser\Node\Stmt\Switch_;
|
|
|
|
use PhpParser\Node\Stmt\Trait_;
|
|
|
|
use PhpParser\Node\Stmt\TryCatch;
|
|
|
|
use PhpParser\Node\Stmt\While_;
|
2024-01-02 02:40:38 +00:00
|
|
|
use Rector\Contract\PhpParser\Node\StmtsAwareInterface;
|
2022-06-06 17:12:56 +00:00
|
|
|
use Rector\NodeTypeResolver\Node\AttributeKey;
|
2024-01-02 02:40:38 +00:00
|
|
|
use Rector\Rector\AbstractRector;
|
2022-06-07 09:18:30 +00:00
|
|
|
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
|
|
|
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
2021-08-30 08:27:47 +00:00
|
|
|
/**
|
|
|
|
* @see \Rector\Tests\CodingStyle\Rector\Stmt\NewlineAfterStatementRector\NewlineAfterStatementRectorTest
|
|
|
|
*/
|
2022-06-07 08:22:29 +00:00
|
|
|
final class NewlineAfterStatementRector extends AbstractRector
|
2021-08-30 08:27:47 +00:00
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @var array<class-string<Node>>
|
|
|
|
*/
|
2023-03-27 07:32:51 +00:00
|
|
|
private const STMTS_TO_HAVE_NEXT_NEWLINE = [ClassMethod::class, Function_::class, Property::class, If_::class, Foreach_::class, Do_::class, While_::class, For_::class, ClassConst::class, TryCatch::class, Class_::class, Trait_::class, Interface_::class, Switch_::class];
|
2022-06-07 08:22:29 +00:00
|
|
|
public function getRuleDefinition() : RuleDefinition
|
2021-08-30 08:27:47 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
return new RuleDefinition('Add new line after statements to tidify code', [new CodeSample(<<<'CODE_SAMPLE'
|
2021-08-30 08:27:47 +00:00
|
|
|
class SomeClass
|
|
|
|
{
|
2022-07-03 20:44:15 +00:00
|
|
|
public function first()
|
2021-08-30 08:27:47 +00:00
|
|
|
{
|
|
|
|
}
|
2022-07-03 20:44:15 +00:00
|
|
|
public function second()
|
2021-08-30 08:27:47 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
}
|
|
|
|
CODE_SAMPLE
|
|
|
|
, <<<'CODE_SAMPLE'
|
|
|
|
class SomeClass
|
|
|
|
{
|
2022-07-03 20:44:15 +00:00
|
|
|
public function first()
|
2021-08-30 08:27:47 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2022-07-03 20:44:15 +00:00
|
|
|
public function second()
|
2021-08-30 08:27:47 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
}
|
|
|
|
CODE_SAMPLE
|
|
|
|
)]);
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* @return array<class-string<Node>>
|
|
|
|
*/
|
|
|
|
public function getNodeTypes() : array
|
|
|
|
{
|
2023-03-27 07:32:51 +00:00
|
|
|
return [StmtsAwareInterface::class, ClassLike::class];
|
2021-08-30 08:27:47 +00:00
|
|
|
}
|
|
|
|
/**
|
2023-03-27 07:32:51 +00:00
|
|
|
* @param StmtsAwareInterface|ClassLike $node
|
2024-01-02 02:40:38 +00:00
|
|
|
* @return null|\Rector\Contract\PhpParser\Node\StmtsAwareInterface|\PhpParser\Node\Stmt\ClassLike
|
2021-08-30 08:27:47 +00:00
|
|
|
*/
|
2023-03-27 07:32:51 +00:00
|
|
|
public function refactor(Node $node)
|
2021-08-30 08:27:47 +00:00
|
|
|
{
|
2023-03-27 07:32:51 +00:00
|
|
|
return $this->processAddNewLine($node, \false);
|
|
|
|
}
|
|
|
|
/**
|
2024-01-02 02:40:38 +00:00
|
|
|
* @param \Rector\Contract\PhpParser\Node\StmtsAwareInterface|\PhpParser\Node\Stmt\ClassLike $node
|
|
|
|
* @return null|\Rector\Contract\PhpParser\Node\StmtsAwareInterface|\PhpParser\Node\Stmt\ClassLike
|
2023-03-27 07:32:51 +00:00
|
|
|
*/
|
|
|
|
private function processAddNewLine($node, bool $hasChanged, int $jumpToKey = 0)
|
|
|
|
{
|
|
|
|
if ($node->stmts === null) {
|
2021-08-31 02:36:59 +00:00
|
|
|
return null;
|
|
|
|
}
|
2023-03-27 07:32:51 +00:00
|
|
|
\end($node->stmts);
|
|
|
|
$totalKeys = \key($node->stmts);
|
2024-03-08 06:34:02 +00:00
|
|
|
\reset($node->stmts);
|
2023-03-27 07:32:51 +00:00
|
|
|
for ($key = $jumpToKey; $key < $totalKeys; ++$key) {
|
|
|
|
if (!isset($node->stmts[$key], $node->stmts[$key + 1])) {
|
|
|
|
break;
|
2021-09-02 06:22:11 +00:00
|
|
|
}
|
2023-03-27 07:32:51 +00:00
|
|
|
$stmt = $node->stmts[$key];
|
|
|
|
$nextStmt = $node->stmts[$key + 1];
|
2023-05-25 15:31:05 +00:00
|
|
|
if ($this->shouldSkip($stmt)) {
|
2023-03-27 07:32:51 +00:00
|
|
|
continue;
|
2021-09-02 06:22:11 +00:00
|
|
|
}
|
2023-03-27 07:32:51 +00:00
|
|
|
$endLine = $stmt->getEndLine();
|
|
|
|
$line = $nextStmt->getStartLine();
|
2021-09-02 06:22:11 +00:00
|
|
|
$rangeLine = $line - $endLine;
|
|
|
|
if ($rangeLine > 1) {
|
2023-03-27 07:32:51 +00:00
|
|
|
$rangeLine = $this->resolveRangeLineFromComment($rangeLine, $line, $endLine, $nextStmt);
|
|
|
|
}
|
|
|
|
// skip same line or < 0 that cause infinite loop or crash
|
|
|
|
if ($rangeLine <= 0) {
|
|
|
|
continue;
|
2021-09-02 06:22:11 +00:00
|
|
|
}
|
2023-03-27 07:32:51 +00:00
|
|
|
if ($rangeLine > 1) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
\array_splice($node->stmts, $key + 1, 0, [new Nop()]);
|
|
|
|
$hasChanged = \true;
|
|
|
|
return $this->processAddNewLine($node, $hasChanged, $key + 2);
|
2021-08-30 08:27:47 +00:00
|
|
|
}
|
2023-03-27 07:32:51 +00:00
|
|
|
if ($hasChanged) {
|
|
|
|
return $node;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* @param int|float $rangeLine
|
2024-04-03 06:21:54 +00:00
|
|
|
* @return float|int
|
2023-03-27 07:32:51 +00:00
|
|
|
*/
|
|
|
|
private function resolveRangeLineFromComment($rangeLine, int $line, int $endLine, Stmt $nextStmt)
|
|
|
|
{
|
|
|
|
/** @var Comment[]|null $comments */
|
|
|
|
$comments = $nextStmt->getAttribute(AttributeKey::COMMENTS);
|
|
|
|
if ($this->hasNoComment($comments)) {
|
|
|
|
return $rangeLine;
|
2022-10-17 09:13:38 +00:00
|
|
|
}
|
2023-03-27 07:32:51 +00:00
|
|
|
/** @var Comment[] $comments */
|
2023-09-11 21:30:42 +00:00
|
|
|
$firstComment = $comments[0];
|
|
|
|
$line = $firstComment->getStartLine();
|
2023-03-27 07:32:51 +00:00
|
|
|
return $line - $endLine;
|
2021-08-30 08:27:47 +00:00
|
|
|
}
|
2021-09-12 14:45:32 +00:00
|
|
|
/**
|
2022-02-05 11:57:25 +00:00
|
|
|
* @param Comment[]|null $comments
|
2021-09-12 14:45:32 +00:00
|
|
|
*/
|
|
|
|
private function hasNoComment(?array $comments) : bool
|
|
|
|
{
|
2023-09-11 21:30:42 +00:00
|
|
|
return $comments === null || $comments === [];
|
2021-09-12 14:45:32 +00:00
|
|
|
}
|
2023-05-25 15:31:05 +00:00
|
|
|
private function shouldSkip(Stmt $stmt) : bool
|
2021-08-31 02:36:59 +00:00
|
|
|
{
|
2023-05-25 15:31:05 +00:00
|
|
|
return !\in_array(\get_class($stmt), self::STMTS_TO_HAVE_NEXT_NEWLINE, \true);
|
2021-08-31 02:36:59 +00:00
|
|
|
}
|
2021-08-30 08:27:47 +00:00
|
|
|
}
|