1616use Symplify \RuleDocGenerator \ValueObject \CodeSample \CodeSample ;
1717use Symplify \RuleDocGenerator \ValueObject \RuleDefinition ;
1818
19+ use function in_array ;
20+
1921final class EnforceAaaPatternRector extends AbstractRector
2022{
2123 /**
@@ -65,41 +67,29 @@ public function getNodeTypes(): array
6567 private function isTestMethod (ClassMethod $ classMethod ): bool
6668 {
6769 $ name = $ this ->getName (node: $ classMethod );
68- if ($ name !== null && str_starts_with (haystack: $ name , needle: 'test ' )) {
70+ if (str_starts_with (haystack: $ name , needle: 'test ' )) {
6971 return true ;
7072 }
7173
7274 $ docComment = $ classMethod ->getDocComment ();
73- if ($ docComment !== null && str_contains (haystack: strtolower (string: $ docComment ->getText ()), needle: '@test ' )) {
74- return true ;
75- }
7675
77- return false ;
76+ return $ docComment !== null && str_contains (haystack: strtolower (string: $ docComment -> getText ()), needle: ' @test ' ) ;
7877 }
7978
80- private function removeAaaComments ( Node $ stmt ): void
79+ private function isAaaComment ( Comment $ comment ): bool
8180 {
82- $ comments = $ stmt ->getComments ();
83- $ filteredComments = [];
84-
85- foreach ($ comments as $ comment ) {
86- $ text = trim (string: $ comment ->getText ());
87- $ normalizedText = strtolower (string: $ text );
88-
89- // Only remove comments that are specifically AAA pattern comments
90- // Check if the comment is ONLY an AAA comment (possibly with // or /* */ wrapper)
91- $ isAaaComment = false ;
81+ $ text = trim (string: $ comment ->getText ());
82+ $ coreText = trim (string: (string ) preg_replace (pattern: '/^\/\/\s*|^\/\*\s*|\s*\*\/$/ ' , replacement: '' , subject: $ text ));
83+ $ coreTextLower = strtolower (string: $ coreText );
9284
93- // Remove comment markers and whitespace to get core content
94- $ coreText = trim (string: (string ) preg_replace (pattern: '/^\/\/\s*|^\/\*\s*|\s*\*\/$/ ' , replacement: '' , subject: $ text ));
95- $ coreTextLower = strtolower (string: $ coreText );
96-
97- // Check if the core text is exactly one of the AAA keywords
98- if ($ coreTextLower === 'arrange ' || $ coreTextLower === 'act ' || $ coreTextLower === 'assert ' ) {
99- $ isAaaComment = true ;
100- }
85+ return in_array ($ coreTextLower , ['arrange ' , 'act ' , 'assert ' ], true );
86+ }
10187
102- if (!$ isAaaComment ) {
88+ private function removeAaaComments (Node $ stmt ): void
89+ {
90+ $ filteredComments = [];
91+ foreach ($ stmt ->getComments () as $ comment ) {
92+ if (! $ this ->isAaaComment (comment: $ comment )) {
10393 $ filteredComments [] = $ comment ;
10494 }
10595 }
@@ -114,38 +104,37 @@ private function addAaaComment(Node $stmt, string $aaaComment): void
114104 $ stmt ->setAttribute ('comments ' , array_merge ([$ aaa ], $ existing ));
115105 }
116106
107+ private function isAssertCall (Node \Expr $ expr ): bool
108+ {
109+ if ($ expr instanceof MethodCall
110+ && $ expr ->var instanceof Node \Expr \Variable
111+ && $ this ->isName (node: $ expr ->var , name: 'this ' )
112+ ) {
113+ $ methodName = $ this ->getName (node: $ expr ->name );
114+
115+ return $ methodName !== null && str_starts_with (haystack: $ methodName , needle: 'assert ' );
116+ }
117+
118+ if ($ expr instanceof StaticCall
119+ && $ expr ->class instanceof Node \Name
120+ && $ this ->isName (node: $ expr ->class , name: 'self ' )
121+ ) {
122+ $ methodName = $ this ->getName (node: $ expr ->name );
123+
124+ return $ methodName !== null && str_starts_with (haystack: $ methodName , needle: 'assert ' );
125+ }
126+
127+ return false ;
128+ }
129+
117130 /**
118131 * @param Stmt[] $stmts
119132 */
120133 private function findFirstAssert (array $ stmts ): ?int
121134 {
122135 foreach ($ stmts as $ i => $ stmt ) {
123- if (! $ stmt instanceof Expression) {
124- continue ;
125- }
126-
127- $ expr = $ stmt ->expr ;
128-
129- // $this->assert*
130- if ($ expr instanceof MethodCall
131- && $ expr ->var instanceof Node \Expr \Variable
132- && $ this ->isName (node: $ expr ->var , name: 'this ' )
133- ) {
134- $ methodName = $ this ->getName (node: $ expr ->name );
135- if ($ methodName !== null && str_starts_with (haystack: $ methodName , needle: 'assert ' )) {
136- return $ i ;
137- }
138- }
139-
140- // self::assert*
141- if ($ expr instanceof StaticCall
142- && $ expr ->class instanceof Node \Name
143- && $ this ->isName (node: $ expr ->class , name: 'self ' )
144- ) {
145- $ methodName = $ this ->getName (node: $ expr ->name );
146- if ($ methodName !== null && str_starts_with (haystack: $ methodName , needle: 'assert ' )) {
147- return $ i ;
148- }
136+ if ($ stmt instanceof Expression && $ this ->isAssertCall (expr: $ stmt ->expr )) {
137+ return $ i ;
149138 }
150139 }
151140
@@ -159,7 +148,7 @@ private function findLastNonAssert(array $stmts, int $firstAssertIndex): int
159148 {
160149 // Find the last statement before the first assert that is not an assert
161150 for ($ i = $ firstAssertIndex - 1 ; $ i >= 0 ; --$ i ) {
162- if (!$ this ->isAssertStatement (stmt: $ stmts [$ i ])) {
151+ if (! $ this ->isAssertStatement (stmt: $ stmts [$ i ])) {
163152 return $ i ;
164153 }
165154 }
@@ -169,35 +158,7 @@ private function findLastNonAssert(array $stmts, int $firstAssertIndex): int
169158
170159 private function isAssertStatement (Stmt $ stmt ): bool
171160 {
172- if (!$ stmt instanceof Expression) {
173- return false ;
174- }
175-
176- $ expr = $ stmt ->expr ;
177-
178- // $this->assert*
179- if ($ expr instanceof MethodCall
180- && $ expr ->var instanceof Node \Expr \Variable
181- && $ this ->isName (node: $ expr ->var , name: 'this ' )
182- ) {
183- $ methodName = $ this ->getName (node: $ expr ->name );
184- if ($ methodName !== null && str_starts_with (haystack: $ methodName , needle: 'assert ' )) {
185- return true ;
186- }
187- }
188-
189- // self::assert*
190- if ($ expr instanceof StaticCall
191- && $ expr ->class instanceof Node \Name
192- && $ this ->isName (node: $ expr ->class , name: 'self ' )
193- ) {
194- $ methodName = $ this ->getName (node: $ expr ->name );
195- if ($ methodName !== null && str_starts_with (haystack: $ methodName , needle: 'assert ' )) {
196- return true ;
197- }
198- }
199-
200- return false ;
161+ return $ stmt instanceof Expression && $ this ->isAssertCall (expr: $ stmt ->expr );
201162 }
202163
203164 public function refactor (Node $ node ): ?Node
@@ -227,8 +188,6 @@ public function refactor(Node $node): ?Node
227188 $ this ->removeAaaComments (stmt: $ stmt );
228189 }
229190
230- $ changed = true ;
231-
232191 // Handle simple case: only one statement before assert (treat as Act only)
233192 if ($ firstAssertIndex === 1 ) {
234193 $ this ->addAaaComment (stmt: $ stmts [0 ], aaaComment: 'Act ' );
@@ -241,7 +200,7 @@ public function refactor(Node $node): ?Node
241200
242201 // Last non-assert statement before first assert gets "Act"
243202 $ lastActIndex = $ this ->findLastNonAssert (stmts: $ stmts , firstAssertIndex: $ firstAssertIndex );
244- if ($ lastActIndex >= 0 && $ lastActIndex !== 0 && isset ($ stmts [$ lastActIndex ])) {
203+ if ($ lastActIndex > 0 && isset ($ stmts [$ lastActIndex ])) {
245204 $ this ->addAaaComment (stmt: $ stmts [$ lastActIndex ], aaaComment: 'Act ' );
246205 }
247206 }
@@ -251,6 +210,6 @@ public function refactor(Node $node): ?Node
251210 $ this ->addAaaComment (stmt: $ stmts [$ firstAssertIndex ], aaaComment: 'Assert ' );
252211 }
253212
254- return $ changed ? $ node : null ;
213+ return $ node ;
255214 }
256215}
0 commit comments