Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 44 additions & 85 deletions src/EnforceAaaPatternRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

use function in_array;

final class EnforceAaaPatternRector extends AbstractRector
{
/**
Expand Down Expand Up @@ -65,41 +67,29 @@ public function getNodeTypes(): array
private function isTestMethod(ClassMethod $classMethod): bool
{
$name = $this->getName(node: $classMethod);
if ($name !== null && str_starts_with(haystack: $name, needle: 'test')) {
if (str_starts_with(haystack: $name, needle: 'test')) {
return true;
}

$docComment = $classMethod->getDocComment();
if ($docComment !== null && str_contains(haystack: strtolower(string: $docComment->getText()), needle: '@test')) {
return true;
}

return false;
return $docComment !== null && str_contains(haystack: strtolower(string: $docComment->getText()), needle: '@test');
}

private function removeAaaComments(Node $stmt): void
private function isAaaComment(Comment $comment): bool
{
$comments = $stmt->getComments();
$filteredComments = [];

foreach ($comments as $comment) {
$text = trim(string: $comment->getText());
$normalizedText = strtolower(string: $text);

// Only remove comments that are specifically AAA pattern comments
// Check if the comment is ONLY an AAA comment (possibly with // or /* */ wrapper)
$isAaaComment = false;
$text = trim(string: $comment->getText());
$coreText = trim(string: (string) preg_replace(pattern: '/^\/\/\s*|^\/\*\s*|\s*\*\/$/', replacement: '', subject: $text));
$coreTextLower = strtolower(string: $coreText);

// Remove comment markers and whitespace to get core content
$coreText = trim(string: (string) preg_replace(pattern: '/^\/\/\s*|^\/\*\s*|\s*\*\/$/', replacement: '', subject: $text));
$coreTextLower = strtolower(string: $coreText);

// Check if the core text is exactly one of the AAA keywords
if ($coreTextLower === 'arrange' || $coreTextLower === 'act' || $coreTextLower === 'assert') {
$isAaaComment = true;
}
return in_array(needle: $coreTextLower, haystack: ['arrange', 'act', 'assert'], strict: true);
}

if (!$isAaaComment) {
private function removeAaaComments(Node $stmt): void
{
$filteredComments = [];
foreach ($stmt->getComments() as $comment) {
if (! $this->isAaaComment(comment: $comment)) {
$filteredComments[] = $comment;
}
}
Expand All @@ -114,38 +104,37 @@ private function addAaaComment(Node $stmt, string $aaaComment): void
$stmt->setAttribute('comments', array_merge([$aaa], $existing));
}

private function isAssertCall(Node\Expr $expr): bool
{
if ($expr instanceof MethodCall
&& $expr->var instanceof Node\Expr\Variable
&& $this->isName(node: $expr->var, name: 'this')
) {
$methodName = $this->getName(node: $expr->name);

return $methodName !== null && str_starts_with(haystack: $methodName, needle: 'assert');
}

if ($expr instanceof StaticCall
&& $expr->class instanceof Node\Name
&& $this->isName(node: $expr->class, name: 'self')
) {
$methodName = $this->getName(node: $expr->name);

return $methodName !== null && str_starts_with(haystack: $methodName, needle: 'assert');
}

return false;
}

/**
* @param Stmt[] $stmts
*/
private function findFirstAssert(array $stmts): ?int
{
foreach ($stmts as $i => $stmt) {
if (! $stmt instanceof Expression) {
continue;
}

$expr = $stmt->expr;

// $this->assert*
if ($expr instanceof MethodCall
&& $expr->var instanceof Node\Expr\Variable
&& $this->isName(node: $expr->var, name: 'this')
) {
$methodName = $this->getName(node: $expr->name);
if ($methodName !== null && str_starts_with(haystack: $methodName, needle: 'assert')) {
return $i;
}
}

// self::assert*
if ($expr instanceof StaticCall
&& $expr->class instanceof Node\Name
&& $this->isName(node: $expr->class, name: 'self')
) {
$methodName = $this->getName(node: $expr->name);
if ($methodName !== null && str_starts_with(haystack: $methodName, needle: 'assert')) {
return $i;
}
if ($stmt instanceof Expression && $this->isAssertCall(expr: $stmt->expr)) {
return $i;
}
}

Expand All @@ -159,7 +148,7 @@ private function findLastNonAssert(array $stmts, int $firstAssertIndex): int
{
// Find the last statement before the first assert that is not an assert
for ($i = $firstAssertIndex - 1; $i >= 0; --$i) {
if (!$this->isAssertStatement(stmt: $stmts[$i])) {
if (! $this->isAssertStatement(stmt: $stmts[$i])) {
return $i;
}
}
Expand All @@ -169,35 +158,7 @@ private function findLastNonAssert(array $stmts, int $firstAssertIndex): int

private function isAssertStatement(Stmt $stmt): bool
{
if (!$stmt instanceof Expression) {
return false;
}

$expr = $stmt->expr;

// $this->assert*
if ($expr instanceof MethodCall
&& $expr->var instanceof Node\Expr\Variable
&& $this->isName(node: $expr->var, name: 'this')
) {
$methodName = $this->getName(node: $expr->name);
if ($methodName !== null && str_starts_with(haystack: $methodName, needle: 'assert')) {
return true;
}
}

// self::assert*
if ($expr instanceof StaticCall
&& $expr->class instanceof Node\Name
&& $this->isName(node: $expr->class, name: 'self')
) {
$methodName = $this->getName(node: $expr->name);
if ($methodName !== null && str_starts_with(haystack: $methodName, needle: 'assert')) {
return true;
}
}

return false;
return $stmt instanceof Expression && $this->isAssertCall(expr: $stmt->expr);
}

public function refactor(Node $node): ?Node
Expand Down Expand Up @@ -227,8 +188,6 @@ public function refactor(Node $node): ?Node
$this->removeAaaComments(stmt: $stmt);
}

$changed = true;

// Handle simple case: only one statement before assert (treat as Act only)
if ($firstAssertIndex === 1) {
$this->addAaaComment(stmt: $stmts[0], aaaComment: 'Act');
Expand All @@ -241,7 +200,7 @@ public function refactor(Node $node): ?Node

// Last non-assert statement before first assert gets "Act"
$lastActIndex = $this->findLastNonAssert(stmts: $stmts, firstAssertIndex: $firstAssertIndex);
if ($lastActIndex >= 0 && $lastActIndex !== 0 && isset($stmts[$lastActIndex])) {
if ($lastActIndex > 0 && isset($stmts[$lastActIndex])) {
$this->addAaaComment(stmt: $stmts[$lastActIndex], aaaComment: 'Act');
}
}
Expand All @@ -251,6 +210,6 @@ public function refactor(Node $node): ?Node
$this->addAaaComment(stmt: $stmts[$firstAssertIndex], aaaComment: 'Assert');
}

return $changed ? $node : null;
return $node;
}
}