diff --git a/doc/rules/operator/new_with_braces.rst b/doc/rules/operator/new_with_braces.rst index 491c366bf69..a5ce4d4958e 100644 --- a/doc/rules/operator/new_with_braces.rst +++ b/doc/rules/operator/new_with_braces.rst @@ -4,18 +4,73 @@ Rule ``new_with_braces`` All instances created with new keyword must be followed by braces. +Configuration +------------- + +``named_class`` +~~~~~~~~~~~~~~~ + +Whether named classes should be followed by parentheses. + +Allowed types: ``bool`` + +Default value: ``true`` + +``anonymous_class`` +~~~~~~~~~~~~~~~~~~~ + +Whether anonymous classes should be followed by parentheses. + +Allowed types: ``bool`` + +Default value: ``true`` + Examples -------- Example #1 ~~~~~~~~~~ +*Default* configuration. + .. code-block:: diff --- Original +++ New - - false]``. + +.. code-block:: diff + + --- Original + +++ New + false]``. + +.. code-block:: diff + + --- Original + +++ New + `_ rule set will enable the ``new_with_braces`` rule. + Using the `@PSR12 <./../../ruleSets/PSR12.rst>`_ rule set will enable the ``new_with_braces`` rule with the default config. @PhpCsFixer - Using the `@PhpCsFixer <./../../ruleSets/PhpCsFixer.rst>`_ rule set will enable the ``new_with_braces`` rule. + Using the `@PhpCsFixer <./../../ruleSets/PhpCsFixer.rst>`_ rule set will enable the ``new_with_braces`` rule with the default config. @Symfony - Using the `@Symfony <./../../ruleSets/Symfony.rst>`_ rule set will enable the ``new_with_braces`` rule. + Using the `@Symfony <./../../ruleSets/Symfony.rst>`_ rule set will enable the ``new_with_braces`` rule with the default config. diff --git a/src/Fixer/Operator/NewWithBracesFixer.php b/src/Fixer/Operator/NewWithBracesFixer.php index 7b31a752f8f..91421d03abe 100644 --- a/src/Fixer/Operator/NewWithBracesFixer.php +++ b/src/Fixer/Operator/NewWithBracesFixer.php @@ -15,6 +15,10 @@ namespace PhpCsFixer\Fixer\Operator; use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; @@ -25,7 +29,7 @@ /** * @author Dariusz RumiƄski */ -final class NewWithBracesFixer extends AbstractFixer +final class NewWithBracesFixer extends AbstractFixer implements ConfigurableFixerInterface { /** * {@inheritdoc} @@ -34,7 +38,17 @@ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'All instances created with new keyword must be followed by braces.', - [new CodeSample(" false] + ), + new CodeSample( + " false] + ), + ] ); } @@ -110,8 +124,28 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void // new anonymous class definition if ($nextToken->isGivenKind(T_CLASS)) { - if (!$tokens[$tokens->getNextMeaningfulToken($nextIndex)]->equals('(')) { - $this->insertBracesAfter($tokens, $nextIndex); + $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); + $nextToken = $tokens[$nextIndex]; + + // add braces to anonymous class definition + if ($this->configuration['anonymous_class'] && !$nextToken->equals('(')) { + $this->insertBracesAfter($tokens, $tokens->getPrevMeaningfulToken($nextIndex)); + + continue; + } + + // remove braces from anonymous class definition + if (!$this->configuration['anonymous_class'] && $nextToken->equals('(')) { + $openingIndex = $nextIndex; + $closingIndex = $tokens->getNextMeaningfulToken($openingIndex); + + // constructor has arguments - braces can not be removed + if (null === $closingIndex || !$tokens[$closingIndex]->equals(')')) { + continue; + } + + $tokens->clearAt($closingIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($openingIndex); } continue; @@ -129,15 +163,49 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $nextToken = $tokens[$nextIndex]; } - // new statement with () - nothing to do - if ($nextToken->equals('(') || $nextToken->isObjectOperator()) { + // new statement without () + if ($this->configuration['named_class']) { + if (!$nextToken->equals('(') && !$nextToken->isObjectOperator()) { + $this->insertBracesAfter($tokens, $tokens->getPrevMeaningfulToken($nextIndex)); + } + + continue; + } + + if (!$tokens[$nextIndex]->equals('(')) { + continue; + } + + $openingIndex = $nextIndex; + $closingIndex = $tokens->getNextMeaningfulToken($openingIndex); + + // constructor has arguments - braces can not be removed + if (null === $closingIndex || !$tokens[$closingIndex]->equals(')')) { continue; } - $this->insertBracesAfter($tokens, $tokens->getPrevMeaningfulToken($nextIndex)); + $tokens->clearAt($closingIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($openingIndex); } } + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('named_class', 'Whether named classes should be followed by parentheses.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder('anonymous_class', 'Whether anonymous classes should be followed by parentheses.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + ]); + } + private function insertBracesAfter(Tokens $tokens, int $index): void { $tokens->insertAt(++$index, [new Token('('), new Token(')')]); diff --git a/tests/Fixer/Operator/NewWithBracesFixerTest.php b/tests/Fixer/Operator/NewWithBracesFixerTest.php index 3ea22749305..f6ebf216d6d 100644 --- a/tests/Fixer/Operator/NewWithBracesFixerTest.php +++ b/tests/Fixer/Operator/NewWithBracesFixerTest.php @@ -26,16 +26,19 @@ final class NewWithBracesFixerTest extends AbstractFixerTestCase { /** - * @dataProvider provideFixCases + * @dataProvider provideNamedWithDefaultConfigurationCases */ - public function testFix(string $expected, ?string $input = null): void + public function testFixNamedWithDefaultConfiguration(string $expected, ?string $input = null): void { $this->doTest($expected, $input); } - public function provideFixCases(): \Generator + public function provideNamedWithDefaultConfigurationCases(): \Generator { yield from [ + [' 1; + ', + ' 1; + ', + ], + ]; + } + + /** + * @dataProvider provideNamedWithoutBracesCases + */ + public function testFixNamedWithoutBraces(string $expected, ?string $input = null): void + { + $this->fixer->configure(['named_class' => false]); + $this->doTest($expected, $input); + } + + public function provideNamedWithoutBracesCases(): \Generator + { + yield from [ + ['bar["baz"];', + 'bar["baz"]();', + ], + [ + 'foo;', + 'foo;', + ], + [ + 'bar))->foo;', + 'bar))->foo;', + ], + [ + ' new DateTime, );', + ' new DateTime(), );', + ], + [ + ' new DateTime );', + ' new DateTime() );', + ], + [ + ' ', + '', + ], + [ + '> 1; + ', + '> 1; + ', + ], + [ + ' $this->startDate) {} + if (new DateTime >= $this->startDate) {} + if (new DateTime < $this->startDate) {} + if (new DateTime <= $this->startDate) {} + if (new DateTime == $this->startDate) {} + if (new DateTime != $this->startDate) {} + if (new DateTime <> $this->startDate) {} + if (new DateTime === $this->startDate) {} + if (new DateTime !== $this->startDate) {} + ', + ' $this->startDate) {} + if (new DateTime() >= $this->startDate) {} + if (new DateTime() < $this->startDate) {} + if (new DateTime() <= $this->startDate) {} + if (new DateTime() == $this->startDate) {} + if (new DateTime() != $this->startDate) {} + if (new DateTime() <> $this->startDate) {} + if (new DateTime() === $this->startDate) {} + if (new DateTime() !== $this->startDate) {} + ', + ], + [ + ' 1];', + ' 1];', + ], + [ + ' new DateTime, ];', + ' new DateTime(), ];', + ], + [ + ' new DateTime ];', + ' new DateTime() ];', + ], + [ + ' 1; + ', + ' 1; + ', + ], ]; + } + /** + * @dataProvider provideAnonymousWithDefaultConfigurationCases + */ + public function testFixAnonymousWithDefaultConfiguration(string $expected, ?string $input = null): void + { + $this->doTest($expected, $input); + } + + public function provideAnonymousWithDefaultConfigurationCases(): \Generator + { yield from [ + ['f ) extends Bar2 implements Foo{};'], + [''], [ ' 1; @@ -292,11 +554,66 @@ public function B() { ]; } + /** + * @dataProvider provideAnonymousWithoutBracesCases + */ + public function testFixAnonymousWithoutBraces(string $expected, ?string $input = null): void + { + $this->fixer->configure(['anonymous_class' => false]); + $this->doTest($expected, $input); + } + + public function provideAnonymousWithoutBracesCases(): \Generator + { + yield from [ + ['f ) extends Bar2 implements Foo{};'], + [''], + [ + ' + ', + ' + ', + ], + [ + 'doTest($expected, $input); }