@@ -26,7 +26,7 @@ final class NoUselessParenthesisFixer extends AbstractFixer
2626 public function getDefinition (): FixerDefinitionInterface
2727 {
2828 return new FixerDefinition (
29- 'There can be no useless parentheses. ' ,
29+ 'There must be no useless parentheses. ' ,
3030 [
3131 new CodeSample ('<?php
3232foo(($bar));
@@ -79,47 +79,127 @@ public function fix(\SplFileInfo $file, Tokens $tokens): void
7979 /** @var Token $prevToken */
8080 $ prevToken = $ tokens [$ prevIndex ];
8181
82- if ($ prevToken ->isGivenKind (\T_RETURN )) {
82+ if ($ prevToken ->isGivenKind ([ \T_RETURN , \ T_THROW ] )) {
8383 $ tokens ->ensureWhitespaceAtIndex ($ prevIndex + 1 , 0 , ' ' );
8484 }
8585 }
8686 }
8787
8888 private function isBlockToRemove (Tokens $ tokens , int $ startIndex , int $ endIndex ): bool
8989 {
90- $ blocksAnalyzer = new BlocksAnalyzer ();
91-
92- // is there a block of parenthesis inside?
93- /** @var int $nextStartIndex */
94- $ nextStartIndex = $ tokens ->getNextMeaningfulToken ($ startIndex );
95- /** @var Token $nextStartToken */
96- $ nextStartToken = $ tokens [$ nextStartIndex ];
97- if ($ nextStartToken ->equalsAny (['( ' , [CT ::T_BRACE_CLASS_INSTANTIATION_OPEN ]])) {
98- /** @var int $prevEndIndex */
99- $ prevEndIndex = $ tokens ->getPrevMeaningfulToken ($ endIndex );
100- if ($ blocksAnalyzer ->isBlock ($ tokens , $ nextStartIndex , $ prevEndIndex )) {
101- return true ;
102- }
90+ if ($ this ->isParenthesisBlockInside ($ tokens , $ startIndex , $ endIndex )) {
91+ return true ;
10392 }
10493
105- // is there a block of parenthesis outside?
10694 /** @var int $prevStartIndex */
10795 $ prevStartIndex = $ tokens ->getPrevMeaningfulToken ($ startIndex );
10896 /** @var int $nextEndIndex */
10997 $ nextEndIndex = $ tokens ->getNextMeaningfulToken ($ endIndex );
110- if ($ blocksAnalyzer ->isBlock ($ tokens , $ prevStartIndex , $ nextEndIndex )) {
98+
99+ if ((new BlocksAnalyzer ())->isBlock ($ tokens , $ prevStartIndex , $ nextEndIndex )) {
111100 return true ;
112101 }
113102
114- // is there assignment, return or throw before?
115103 /** @var Token $prevStartToken */
116104 $ prevStartToken = $ tokens [$ prevStartIndex ];
117105 /** @var Token $nextEndToken */
118106 $ nextEndToken = $ tokens [$ nextEndIndex ];
119107
108+ if ($ this ->isForbiddenBeforeOpenParenthesis ($ tokens , $ prevStartIndex )) {
109+ return false ;
110+ }
111+
112+ if ($ this ->isExpressionInside ($ tokens , $ startIndex , $ endIndex )) {
113+ return true ;
114+ }
115+
120116 return $ prevStartToken ->equalsAny (['= ' , [\T_RETURN ], [\T_THROW ]]) && $ nextEndToken ->equals ('; ' );
121117 }
122118
119+ private function isForbiddenBeforeOpenParenthesis (Tokens $ tokens , int $ index ): bool
120+ {
121+ /** @var Token $token */
122+ $ token = $ tokens [$ index ];
123+
124+ if (
125+ $ token ->isGivenKind ([
126+ \T_ARRAY ,
127+ \T_CATCH ,
128+ \T_CLASS ,
129+ \T_ELSEIF ,
130+ \T_EMPTY ,
131+ \T_EVAL ,
132+ \T_EXIT ,
133+ \T_FUNCTION ,
134+ \T_IF ,
135+ \T_ISSET ,
136+ \T_LIST ,
137+ \T_STATIC ,
138+ \T_STRING ,
139+ \T_SWITCH ,
140+ \T_UNSET ,
141+ \T_VARIABLE ,
142+ \T_WHILE ,
143+ CT ::T_CLASS_CONSTANT ,
144+ CT ::T_USE_LAMBDA ,
145+ ])
146+ || \defined ('T_FN ' ) && $ token ->isGivenKind (\T_FN )
147+ || \defined ('T_MATCH ' ) && $ token ->isGivenKind (\T_MATCH )
148+ ) {
149+ return true ;
150+ }
151+
152+ /** @var null|array{isStart: bool, type: int} $blockType */
153+ $ blockType = Tokens::detectBlockType ($ token );
154+
155+ return $ blockType !== null && !$ blockType ['isStart ' ];
156+ }
157+
158+ private function isParenthesisBlockInside (Tokens $ tokens , int $ startIndex , int $ endIndex ): bool
159+ {
160+ /** @var int $nextStartIndex */
161+ $ nextStartIndex = $ tokens ->getNextMeaningfulToken ($ startIndex );
162+ /** @var Token $nextStartToken */
163+ $ nextStartToken = $ tokens [$ nextStartIndex ];
164+
165+ return $ nextStartToken ->equalsAny (['( ' , [CT ::T_BRACE_CLASS_INSTANTIATION_OPEN ]])
166+ && (new BlocksAnalyzer ())->isBlock ($ tokens , $ nextStartIndex , $ tokens ->getPrevMeaningfulToken ($ endIndex ));
167+ }
168+
169+ private function isExpressionInside (Tokens $ tokens , int $ startIndex , int $ endIndex ): bool
170+ {
171+ $ expression = false ;
172+
173+ /** @var int $index */
174+ $ index = $ tokens ->getNextMeaningfulToken ($ startIndex );
175+
176+ while ($ index < $ endIndex ) {
177+ $ expression = true ;
178+
179+ /** @var Token $token */
180+ $ token = $ tokens [$ index ];
181+
182+ if (
183+ !$ token ->isGivenKind ([
184+ \T_CONSTANT_ENCAPSED_STRING ,
185+ \T_DNUMBER ,
186+ \T_DOUBLE_COLON ,
187+ \T_LNUMBER ,
188+ \T_OBJECT_OPERATOR ,
189+ \T_STRING ,
190+ \T_VARIABLE ,
191+ ]) && !$ token ->isMagicConstant ()
192+ ) {
193+ return false ;
194+ }
195+
196+ /** @var int $index */
197+ $ index = $ tokens ->getNextMeaningfulToken ($ index );
198+ }
199+
200+ return $ expression ;
201+ }
202+
123203 private function clearWhitespace (Tokens $ tokens , int $ index ): void
124204 {
125205 /** @var Token $token */
0 commit comments