* Dariusz RumiƄski * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ namespace PhpCsFixer\Tests\Fixer\ControlStructure; use PhpCsFixer\Tests\Test\AbstractFixerTestCase; /** * @author Sullivan Senechal * @author Gregor Harlan * * @internal * * @covers \PhpCsFixer\Fixer\ControlStructure\NoUnneededControlParenthesesFixer * * @extends AbstractFixerTestCase<\PhpCsFixer\Fixer\ControlStructure\NoUnneededControlParenthesesFixer> * * @phpstan-import-type _AutogeneratedInputConfiguration from \PhpCsFixer\Fixer\ControlStructure\NoUnneededControlParenthesesFixer */ final class NoUnneededControlParenthesesFixerTest extends AbstractFixerTestCase { /** * @dataProvider provideFixCases */ public function testFix(string $expected, ?string $input = null): void { $this->fixer->configure( [ 'statements' => [ 'break', 'clone', 'continue', 'echo_print', 'return', 'switch_case', 'yield', 'yield_from', 'others', ], ], ); $this->doTest($expected, $input); } /** * @return iterable */ public static function provideFixCases(): iterable { yield [ 'getSubject() : $obj2); ', ]; yield [ '', ]; yield [ '', '', ]; yield [ '', '', ]; yield [ 'getOutput(1); ', 'getOutput(1); ', ]; yield [ '', '', ]; yield [ 'getSubject() ?? $obj2); ', ]; } /** * @dataProvider provideFixAllCases */ public function testFixAll(string $expected, ?string $input = null): void { $this->fixer->configure( [ 'statements' => [ 'break', 'clone', 'continue', 'echo_print', 'return', 'switch_case', 'yield', 'yield_from', 'others', 'negative_instanceof', ], ], ); $this->doTest($expected, $input); } /** * @return iterable */ public static function provideFixAllCases(): iterable { yield '===' => [ ' [ ' [ ' clone $z; for ( clone new Bar(1+2) ; $i < 100; ++$i) { $i = foo(); } clone $object2[0]->foo()[2] /* 1 */ ?>', ' clone ($z); for ( clone(new Bar(1+2) ); $i < 100; ++$i) { $i = foo(); } clone ($object2[0]->foo()[2]) /* 1 */ ?>', ]; yield 'print unary wrapped sequence' => [ ' [ ' [ ' [ ' print $b."something"; $b52 = fn() => print $b."else"; $b6 = foo(print $a); $b7 =[ print $a ,]; $b8 =[ print ($a+1) . "1" ,]; ', ' (print $b."something"); $b52 = fn() => print ($b."else"); $b6 = foo(print ($a)); $b7 =[ (print ($a)) ,]; $b8 =[ (print ($a+1) . "1") ,]; ', ]; yield 'simple' => [ ' [ ' [ ' [ ' [ ' $x + $y;', ' ($x + $y);', ]; yield 'wrapped FN 2 with pre and reference' => [ ' !$x;', ' !($x);', ]; yield 'wrapped FN with `,`' => [ ' 1, $array); $fn2 = array_map($array, fn() => 2); $fn3 = array_map($array, fn() => 3, $array); ', ' (1), $array); $fn2 = array_map($array, fn() => (2)); $fn3 = array_map($array, fn() => (3), $array); ', ]; yield 'wrapped FN with return type' => [ ' 123456; ', ' (123456); ', ]; yield 'wrapped `for` elements' => [ ' [ ' [ ' [ ' [ \sprintf(' [ ' [ ' [ ' [ ' [ ' [ '', '', ]; yield 'bin after open echo' => [ ' [ ' [ ' [ ' [ '', '', ]; yield 'bin after throw/return' => [ ' [ ' [ 'A::$foo; $a1 = $b->$c[1]; $a2 = $b->c[1][2]; $a3 = $b->$c[1]->$a[2]->${"abc"}; $a4 = $b->$c[1][2]->${"abc"}(22); $a5 = $o->$foo(); $o->$c[] = 6; $a7 = $o->$c[8] (7); $a9 = $o->abc($a); $a10 = $o->abc($a)[1]; $a11 = $o->{$bar}; $a12 = $o->{$c->d}($e)[1](2)[$f]->$c[1]()?> ', 'A::$foo); $a1 = (($b)->$c[1]); $a2 = (($b)->c[1][2]); $a3 = (($b)->$c[1]->$a[2]->${"abc"}); $a4 = (($b)->$c[1][2]->${"abc"}(22)); $a5 = (($o)->$foo()); ($o)->$c[] = 6; $a7 = (($o)->$c[8] (7)); $a9 = (($o)->abc($a)); $a10 = (($o)->abc($a)[1]); $a11 = (($o)->{$bar}); $a12 = (($o)->{$c->d}($e)[1](2)[$f]->$c[1]())?> ', ]; yield 'simple unary `!`' => [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ '', '', ]; yield 'bin before and after' => [ 'value + 3; echo 1 + Obj::VALUE + 3; ', 'value) + 3; echo 1 + (Obj::VALUE) + 3; ', ]; yield 'new class as sequence element' => [ ' [ ' [ ' [ ' [ '{$bar};', '{($bar)};', ]; yield 'anonymous class wrapped init' => [ ' [ ' [ ' [ '', '', ]; yield 'double comma and `)`' => [ ' [ ' [ '', '', ]; yield 'wrapped function call, short open, semicolon' => [ '', '', ]; yield 'wrapped function call, short open, close tag' => [ ' [ ' [ ' [ ' [ '', '', ]; yield 'dynamic class name op' => [ 'test();', 'test();', ]; yield 'token type changing edge case' => [ 'bar;', 'bar;', ]; yield 'brace class instantiation open, double wrapped, no assign' => [ 'bar();', 'bar();', ]; yield 'brace class instantiation open, multiple wrapped' => [ 'bar;', 'bar;', ]; yield 'wrapped instance check' => [ 'a[1]]; $l4 = [1, $foo instanceof $a[1]->$f]; $l5 = [$foo instanceof Foo, 1]; $l6 = [1, $foo instanceof Foo, 1]; $fn1 = fn($x) => $fx instanceof Foo; for ($foo instanceof Foo ; $i < 1; ++$i) { echo $i; } class foo { public function bar() { self instanceof static; self instanceof self; $a instanceof static; self instanceof $a; $a instanceof self; } } ', 'a[1])]; $l4 = [1, ($foo instanceof $a[1]->$f)]; $l5 = [($foo instanceof Foo), 1]; $l6 = [1, ($foo instanceof Foo), 1]; $fn1 = fn($x) => ($fx instanceof Foo); for (($foo instanceof Foo) ; $i < 1; ++$i) { echo $i; } class foo { public function bar() { (self instanceof static); (self instanceof self); ($a instanceof static); (self instanceof $a); ($a instanceof self); } } ', ]; yield 'wrapped negative instanceof' => [ 'b(1)[2] instanceof A\Foo; $z3 = [!$z instanceof Foo\Bar::$a]; $z4 = [1, !$z instanceof Foo\Bar::$a]; $z5 = [!$z instanceof Foo\Bar::$a, 2]; $z6 = [8, !$z instanceof Foo\Bar::$a, 2]; for( !$z instanceof Foo\Bar::$a ; $a < 100; ++$a) { foo(); } $c1 = fn() => !$z instanceof $z[1]; if (!$x instanceof $v) { echo 123; } ', 'b(1)[2] instanceof A\Foo); $z3 = [!($z instanceof Foo\Bar::$a)]; $z4 = [1, !($z instanceof Foo\Bar::$a)]; $z5 = [!($z instanceof Foo\Bar::$a), 2]; $z6 = [8, !($z instanceof Foo\Bar::$a), 2]; for( !($z instanceof Foo\Bar::$a) ; $a < 100; ++$a) { foo(); } $c1 = fn() => !($z instanceof $z[1]); if (!($x instanceof $v)) { echo 123; } ', ]; yield 'wrapped negative instanceof 2' => [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' '&=', 'coalesce_equal' => '??=', 'concat_equal' => '.=', 'div equal' => '/=', 'minus equal' => '!=', 'mod equal' => '%=', 'mul equal' => '*=', 'or equal' => '|=', 'plus equal' => '+=', 'pow equal' => '**=', 'sl equal' => '<<=', 'sr equal' => '>>=', 'xor equal' => '^=', ]; foreach ($assignOperators as $assignOperator) { yield [ ' [ ' [ '$b; ${foo}(); foo(+ $a, -$b); $a[1](2); $b = $c(); $z = foo(1) + 2; $a = ${\'foo\'}(); $b["foo"]($bar); namespace\func(); Y::class(); $z = (print \'a\') + 2; $fn1 = fn($x) => $x + $y; $d = function() use ($d) {}; function provideFixCases(): iterable{} $z = function &($x) {++$x;}; final class Foo { public static function bar() { return new static(); } } $a = (print 1) + 1; $b = $c(print $e); $c = foo(print $e); $c2 =[ print !($a + 1) ,]; $a = ($a && !$c) && $b; $a = (1 + foo()) * 2; $a = -($f+$a); $a = !($f ? $a : $b); $a = @( ($f.$b)() . foo() ); $a = $b ? ($c)($d + 2) : $d; $d = ($c && $a ? true : false) ? 1 : 2; // PHP Deprecated: Unparenthesized `a ? b : c ? d : e` is deprecated. $z = $b ? ($c)($d + 2) : $d; $x = $b ? foo($c)[$a](1) : $d; $a = [1,($a.$foo[1])[2]]; $b = [ $d[4]($a[1].$b) , ]; $c = [$d[4]($a[1].$b)]; $a = ($a ? 1 : 2) ? 1 : 2; $a = $a ? ($a ? 1 : 2): 2; $a = $a ? 1 : ($a ? 1 : 2); $a = 1 + ($a + 1) ? 1 : 2; $a = $a ? ($a + 1) + 3 : 2; $a = $a ? 3 + ($a + 1) : 2; $a = $a ? 3 : 1 + ($a + 1); $a = $a ? 3 : ($a + 1) + 2; $b += ($a+=$c); $a = isset( ( (array) $b) [1] ); $a = ( (array) $b) [1] ; exit(4); exit (foo()) + 1; $a = array(); $b = new class(){}; declare(ticks=1); $d = empty($e); $e = eval($b); list($f) = $f; try {foo();} catch (E $e){} if ($a){ echo 1; } elseif($b) { echo 2; } switch($a) {case 1: echo 1;} foreach ($a1 as $b){} foreach ($a2 as $b => $c){} for ($a =0; $a < 1; --$a){} while(bar()) {} while(!!!(bool) $a1() ){echo 1;} while( ($a2)(1) ){echo 1;} $d = isset($c); $d = isset($c,$a,$z); $y = isset($foo) && isset($foo2); unset($d); unset($d,$e); // from https://www.php.net/manual/en/language.operators.precedence.php $a = 1; echo $a + ($a++); // echo $a + $a++; // may print either 2 or 3 $i = 1; $array[$i] = ($i++); // $array[$i] = $i++; // may set either index 1 or 2 $t = 1+($i++)+1; echo (("x minus one equals " . $x) - 1) . ", or so I hope\n"; echo "x minus one equals " . ($x-1) . ", or so I hope\n"; $bool = (true and false); $a = ($a instanceof MyClass) && true; $a = foo($a instanceof MyClass); $a = $a || ($a instanceof MyClass); $a = clone ($a)[0]; // handled by the `include` rule require (__DIR__."/foo2.php"); require_once (__DIR__."/foo.php"); include ($a); include_once ($b); // halt compiler __halt_compiler(); ', ]; yield 'do not fix ' => [ ' [ ' [ ' [ 'fixer->configure($config); $this->doTest($expected, $input); $this->fixer->configure(['statements' => []]); $this->doTest($input); } public static function provideWithConfigCases(): iterable { yield 'config: break' => [ ['statements' => ['break']], ' [ ['statements' => ['clone']], ' [ ['statements' => ['continue']], ' [ ['statements' => ['echo_print']], ' [ ['statements' => ['return']], '', '', ]; yield 'config: switch_case' => [ ['statements' => ['switch_case']], ' [ ['statements' => ['yield']], ' [ ['statements' => ['yield_from']], ' [ ['statements' => ['negative_instanceof']], ' [ ['statements' => ['others']], 'fixer->configure( [ 'statements' => [ 'break', 'clone', 'continue', 'echo_print', 'return', 'switch_case', 'yield', 'yield_from', 'others', ], ], ); $this->doTest($expected, $input); } /** * @return iterable */ public static function providePrePhp8Cases(): iterable { yield 'block type array index curly brace' => [ ' [ 'fixer->configure( [ 'statements' => [ 'break', 'clone', 'continue', 'echo_print', 'return', 'switch_case', 'yield', 'yield_from', 'others', ], ], ); $this->doTest($expected, $input); } /** * @return iterable */ public static function provideFixPhp80Cases(): iterable { yield 'fn throw' => [ ' throw new MyError();', ' (throw new MyError());', ]; yield 'match' => [ " 'An apple', 'cake' => 'Some cake', }; ", " 'An apple', 'cake' => 'Some cake', }; ", ]; yield 'wrapped FN with return types' => [ ' 123456; ', ' (123456); ', ]; yield [ 'value + 3;', 'value) + 3;', ]; } /** * @dataProvider provideFixPhp81Cases * * @requires PHP 8.1 */ public function testFixPhp81(string $expected, ?string $input = null): void { $this->fixer->configure( [ 'statements' => [ 'break', 'clone', 'continue', 'echo_print', 'return', 'switch_case', 'yield', 'yield_from', 'others', ], ], ); $this->doTest($expected, $input); } /** * @return iterable */ public static function provideFixPhp81Cases(): iterable { yield [ 'value;', 'value;', ]; yield 'wrapped FN with return types' => [ ' new C(); ', ' (new C()); ', ]; yield 'wrapped FN with return types and ref' => [ ' new F(); ', ' (new F()); ', ]; yield [ 'fixer->configure($configuration); $this->doTest($expected, $input); } /** * @return iterable */ public static function provideFixPre84Cases(): iterable { yield 'access' => [ '{$c->d}($e)[1](2){$f}->$c[1]();', '{$c->d}($e)[1](2){$f}->$c[1]());', ['statements' => ['others']], ]; } }