Browse Source

feat: Deprecate `NewWithBracesFixer` and proxy to `NewWithParenthesesFixer` (#7331)

Andreas Möller 1 year ago
parent
commit
2abddefba3

+ 1 - 1
dev-tools/phpstan/baseline.php

@@ -299,7 +299,7 @@ $ignoreErrors[] = [
 $ignoreErrors[] = [
 	'message' => '#^Only booleans are allowed in an if condition, mixed given\\.$#',
 	'count' => 2,
-	'path' => __DIR__ . '/../../src/Fixer/Operator/NewWithBracesFixer.php',
+	'path' => __DIR__ . '/../../src/Fixer/Operator/NewWithParenthesesFixer.php',
 ];
 $ignoreErrors[] = [
 	'message' => '#^Only booleans are allowed in a negated boolean, mixed given\\.$#',

+ 20 - 1
doc/list.rst

@@ -1340,6 +1340,25 @@ List of Available Rules
 
    All instances created with ``new`` keyword must (not) be followed by braces.
 
+   *warning deprecated*   Use ``new_with_parentheses`` instead.
+
+   Configuration options:
+
+   - | ``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``
+
+
+   `Source PhpCsFixer\\Fixer\\Operator\\NewWithBracesFixer <./../src/Fixer/Operator/NewWithBracesFixer.php>`_
+-  `new_with_parentheses <./rules/operator/new_with_parentheses.rst>`_
+
+   All instances created with ``new`` keyword must (not) be followed by parentheses.
+
    Configuration options:
 
    - | ``named_class``
@@ -1354,7 +1373,7 @@ List of Available Rules
 
    Part of rule sets `@PER <./ruleSets/PER.rst>`_ `@PER-CS <./ruleSets/PER-CS.rst>`_ `@PER-CS1.0 <./ruleSets/PER-CS1.0.rst>`_ `@PER-CS2.0 <./ruleSets/PER-CS2.0.rst>`_ `@PSR12 <./ruleSets/PSR12.rst>`_ `@PhpCsFixer <./ruleSets/PhpCsFixer.rst>`_ `@Symfony <./ruleSets/Symfony.rst>`_
 
-   `Source PhpCsFixer\\Fixer\\Operator\\NewWithBracesFixer <./../src/Fixer/Operator/NewWithBracesFixer.php>`_
+   `Source PhpCsFixer\\Fixer\\Operator\\NewWithParenthesesFixer <./../src/Fixer/Operator/NewWithParenthesesFixer.php>`_
 -  `non_printable_character <./rules/basic/non_printable_character.rst>`_
 
    Remove Zero-width space (ZWSP), Non-breaking space (NBSP) and other invisible unicode symbols.

+ 1 - 1
doc/ruleSets/PSR12.rst

@@ -27,7 +27,7 @@ Rules
 - `declare_equal_normalize <./../rules/language_construct/declare_equal_normalize.rst>`_
 - `lowercase_cast <./../rules/cast_notation/lowercase_cast.rst>`_
 - `lowercase_static_reference <./../rules/casing/lowercase_static_reference.rst>`_
-- `new_with_braces <./../rules/operator/new_with_braces.rst>`_
+- `new_with_parentheses <./../rules/operator/new_with_parentheses.rst>`_
 - `no_blank_lines_after_class_opening <./../rules/class_notation/no_blank_lines_after_class_opening.rst>`_
 - `no_leading_import_slash <./../rules/import/no_leading_import_slash.rst>`_
 - `no_whitespace_in_blank_line <./../rules/whitespace/no_whitespace_in_blank_line.rst>`_

+ 4 - 1
doc/rules/index.rst

@@ -551,9 +551,12 @@ Operator
 - `long_to_shorthand_operator <./operator/long_to_shorthand_operator.rst>`_
 
   Shorthand notation for operators should be used if possible.
-- `new_with_braces <./operator/new_with_braces.rst>`_
+- `new_with_braces <./operator/new_with_braces.rst>`_ *(deprecated)*
 
   All instances created with ``new`` keyword must (not) be followed by braces.
+- `new_with_parentheses <./operator/new_with_parentheses.rst>`_
+
+  All instances created with ``new`` keyword must (not) be followed by parentheses.
 - `no_space_around_double_colon <./operator/no_space_around_double_colon.rst>`_
 
   There must be no space around double colons (also called Scope Resolution Operator or Paamayim Nekudotayim).

+ 8 - 14
doc/rules/operator/new_with_braces.rst

@@ -4,6 +4,14 @@ Rule ``new_with_braces``
 
 All instances created with ``new`` keyword must (not) be followed by braces.
 
+Warning
+-------
+
+This rule is deprecated and will be removed in the next major version
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You should use ``new_with_parentheses`` instead.
+
 Configuration
 -------------
 
@@ -71,17 +79,3 @@ With configuration: ``['named_class' => false]``.
 
    -$x = new X();
    +$x = new X;
-
-Rule sets
----------
-
-The rule is part of the following rule sets:
-
-- `@PER <./../../ruleSets/PER.rst>`_
-- `@PER-CS <./../../ruleSets/PER-CS.rst>`_
-- `@PER-CS1.0 <./../../ruleSets/PER-CS1.0.rst>`_
-- `@PER-CS2.0 <./../../ruleSets/PER-CS2.0.rst>`_
-- `@PSR12 <./../../ruleSets/PSR12.rst>`_
-- `@PhpCsFixer <./../../ruleSets/PhpCsFixer.rst>`_
-- `@Symfony <./../../ruleSets/Symfony.rst>`_
-

+ 88 - 0
doc/rules/operator/new_with_parentheses.rst

@@ -0,0 +1,88 @@
+=============================
+Rule ``new_with_parentheses``
+=============================
+
+All instances created with ``new`` keyword must (not) be followed by
+parentheses.
+
+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
+    <?php
+
+   -$x = new X;
+   -$y = new class {};
+   +$x = new X();
+   +$y = new class() {};
+
+Example #2
+~~~~~~~~~~
+
+With configuration: ``['anonymous_class' => false]``.
+
+.. code-block:: diff
+
+   --- Original
+   +++ New
+    <?php
+
+   -$y = new class() {};
+   +$y = new class {};
+
+Example #3
+~~~~~~~~~~
+
+With configuration: ``['named_class' => false]``.
+
+.. code-block:: diff
+
+   --- Original
+   +++ New
+    <?php
+
+   -$x = new X();
+   +$x = new X;
+
+Rule sets
+---------
+
+The rule is part of the following rule sets:
+
+- `@PER <./../../ruleSets/PER.rst>`_
+- `@PER-CS <./../../ruleSets/PER-CS.rst>`_
+- `@PER-CS1.0 <./../../ruleSets/PER-CS1.0.rst>`_
+- `@PER-CS2.0 <./../../ruleSets/PER-CS2.0.rst>`_
+- `@PSR12 <./../../ruleSets/PSR12.rst>`_
+- `@PhpCsFixer <./../../ruleSets/PhpCsFixer.rst>`_
+- `@Symfony <./../../ruleSets/Symfony.rst>`_
+

+ 1 - 1
src/Fixer/ClassNotation/ClassDefinitionFixer.php

@@ -102,7 +102,7 @@ $foo = new class(){};
      * {@inheritdoc}
      *
      * Must run before BracesFixer, SingleLineEmptyBodyFixer.
-     * Must run after NewWithBracesFixer.
+     * Must run after NewWithBracesFixer, NewWithParenthesesFixer.
      */
     public function getPriority(): int
     {

+ 33 - 146
src/Fixer/Operator/NewWithBracesFixer.php

@@ -14,38 +14,38 @@ declare(strict_types=1);
 
 namespace PhpCsFixer\Fixer\Operator;
 
-use PhpCsFixer\AbstractFixer;
+use PhpCsFixer\AbstractProxyFixer;
 use PhpCsFixer\Fixer\ConfigurableFixerInterface;
-use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
+use PhpCsFixer\Fixer\DeprecatedFixerInterface;
 use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
-use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
-use PhpCsFixer\FixerDefinition\CodeSample;
 use PhpCsFixer\FixerDefinition\FixerDefinition;
 use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
-use PhpCsFixer\Tokenizer\CT;
-use PhpCsFixer\Tokenizer\Token;
-use PhpCsFixer\Tokenizer\Tokens;
 
 /**
  * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
+ *
+ * @deprecated
  */
-final class NewWithBracesFixer extends AbstractFixer implements ConfigurableFixerInterface
+final class NewWithBracesFixer extends AbstractProxyFixer implements ConfigurableFixerInterface, DeprecatedFixerInterface
 {
+    private NewWithParenthesesFixer $newWithParenthesesFixer;
+
+    public function __construct()
+    {
+        $this->newWithParenthesesFixer = new NewWithParenthesesFixer();
+
+        parent::__construct();
+    }
+
     public function getDefinition(): FixerDefinitionInterface
     {
+        $fixerDefinition = $this->newWithParenthesesFixer->getDefinition();
+
         return new FixerDefinition(
             'All instances created with `new` keyword must (not) be followed by braces.',
-            [
-                new CodeSample("<?php\n\n\$x = new X;\n\$y = new class {};\n"),
-                new CodeSample(
-                    "<?php\n\n\$y = new class() {};\n",
-                    ['anonymous_class' => false]
-                ),
-                new CodeSample(
-                    "<?php\n\n\$x = new X();\n",
-                    ['named_class' => false]
-                ),
-            ]
+            $fixerDefinition->getCodeSamples(),
+            $fixerDefinition->getDescription(),
+            $fixerDefinition->getRiskyDescription(),
         );
     }
 
@@ -56,145 +56,32 @@ final class NewWithBracesFixer extends AbstractFixer implements ConfigurableFixe
      */
     public function getPriority(): int
     {
-        return 37;
+        return $this->newWithParenthesesFixer->getPriority();
     }
 
-    public function isCandidate(Tokens $tokens): bool
+    public function configure(array $configuration): void
     {
-        return $tokens->isTokenKindFound(T_NEW);
-    }
-
-    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
-    {
-        static $nextTokenKinds = null;
-
-        if (null === $nextTokenKinds) {
-            $nextTokenKinds = [
-                '?',
-                ';',
-                ',',
-                '(',
-                ')',
-                '[',
-                ']',
-                ':',
-                '<',
-                '>',
-                '+',
-                '-',
-                '*',
-                '/',
-                '%',
-                '&',
-                '^',
-                '|',
-                [T_CLASS],
-                [T_IS_SMALLER_OR_EQUAL],
-                [T_IS_GREATER_OR_EQUAL],
-                [T_IS_EQUAL],
-                [T_IS_NOT_EQUAL],
-                [T_IS_IDENTICAL],
-                [T_IS_NOT_IDENTICAL],
-                [T_CLOSE_TAG],
-                [T_LOGICAL_AND],
-                [T_LOGICAL_OR],
-                [T_LOGICAL_XOR],
-                [T_BOOLEAN_AND],
-                [T_BOOLEAN_OR],
-                [T_SL],
-                [T_SR],
-                [T_INSTANCEOF],
-                [T_AS],
-                [T_DOUBLE_ARROW],
-                [T_POW],
-                [T_SPACESHIP],
-                [CT::T_ARRAY_SQUARE_BRACE_OPEN],
-                [CT::T_ARRAY_SQUARE_BRACE_CLOSE],
-                [CT::T_BRACE_CLASS_INSTANTIATION_OPEN],
-                [CT::T_BRACE_CLASS_INSTANTIATION_CLOSE],
-            ];
-
-            if (\defined('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG')) { // @TODO: drop condition when PHP 8.1+ is required
-                $nextTokenKinds[] = [T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG];
-                $nextTokenKinds[] = [T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG];
-            }
-        }
-
-        for ($index = $tokens->count() - 3; $index > 0; --$index) {
-            if (!$tokens[$index]->isGivenKind(T_NEW)) {
-                continue;
-            }
+        $this->newWithParenthesesFixer->configure($configuration);
 
-            $nextIndex = $tokens->getNextTokenOfKind($index, $nextTokenKinds);
-
-            // new anonymous class definition
-            if ($tokens[$nextIndex]->isGivenKind(T_CLASS)) {
-                $nextIndex = $tokens->getNextMeaningfulToken($nextIndex);
-
-                if ($this->configuration['anonymous_class']) {
-                    $this->ensureBracesAt($tokens, $nextIndex);
-                } else {
-                    $this->ensureNoBracesAt($tokens, $nextIndex);
-                }
-
-                continue;
-            }
-
-            // entrance into array index syntax - need to look for exit
-
-            while ($tokens[$nextIndex]->equals('[') || $tokens[$nextIndex]->isGivenKind(CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN)) {
-                $nextIndex = $tokens->findBlockEnd(Tokens::detectBlockType($tokens[$nextIndex])['type'], $nextIndex);
-                $nextIndex = $tokens->getNextMeaningfulToken($nextIndex);
-            }
-
-            if ($this->configuration['named_class']) {
-                $this->ensureBracesAt($tokens, $nextIndex);
-            } else {
-                $this->ensureNoBracesAt($tokens, $nextIndex);
-            }
-        }
+        parent::configure($configuration);
     }
 
-    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
+    public function getSuccessorsNames(): array
     {
-        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(),
-        ]);
+        return [
+            $this->newWithParenthesesFixer->getName(),
+        ];
     }
 
-    private function ensureBracesAt(Tokens $tokens, int $index): void
+    protected function createProxyFixers(): array
     {
-        $token = $tokens[$index];
-
-        if (!$token->equals('(') && !$token->isObjectOperator()) {
-            $tokens->insertAt(
-                $tokens->getPrevMeaningfulToken($index) + 1,
-                [new Token('('), new Token(')')]
-            );
-        }
+        return [
+            $this->newWithParenthesesFixer,
+        ];
     }
 
-    private function ensureNoBracesAt(Tokens $tokens, int $index): void
+    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
     {
-        if (!$tokens[$index]->equals('(')) {
-            return;
-        }
-
-        $closingIndex = $tokens->getNextMeaningfulToken($index);
-
-        // constructor has arguments - braces can not be removed
-        if (!$tokens[$closingIndex]->equals(')')) {
-            return;
-        }
-
-        $tokens->clearTokenAndMergeSurroundingWhitespace($closingIndex);
-        $tokens->clearTokenAndMergeSurroundingWhitespace($index);
+        return $this->newWithParenthesesFixer->createConfigurationDefinition();
     }
 }

+ 200 - 0
src/Fixer/Operator/NewWithParenthesesFixer.php

@@ -0,0 +1,200 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of PHP CS Fixer.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+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;
+use PhpCsFixer\Tokenizer\CT;
+use PhpCsFixer\Tokenizer\Token;
+use PhpCsFixer\Tokenizer\Tokens;
+
+/**
+ * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
+ */
+final class NewWithParenthesesFixer extends AbstractFixer implements ConfigurableFixerInterface
+{
+    public function getDefinition(): FixerDefinitionInterface
+    {
+        return new FixerDefinition(
+            'All instances created with `new` keyword must (not) be followed by parentheses.',
+            [
+                new CodeSample("<?php\n\n\$x = new X;\n\$y = new class {};\n"),
+                new CodeSample(
+                    "<?php\n\n\$y = new class() {};\n",
+                    ['anonymous_class' => false]
+                ),
+                new CodeSample(
+                    "<?php\n\n\$x = new X();\n",
+                    ['named_class' => false]
+                ),
+            ]
+        );
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * Must run before ClassDefinitionFixer.
+     */
+    public function getPriority(): int
+    {
+        return 37;
+    }
+
+    public function isCandidate(Tokens $tokens): bool
+    {
+        return $tokens->isTokenKindFound(T_NEW);
+    }
+
+    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
+    {
+        static $nextTokenKinds = null;
+
+        if (null === $nextTokenKinds) {
+            $nextTokenKinds = [
+                '?',
+                ';',
+                ',',
+                '(',
+                ')',
+                '[',
+                ']',
+                ':',
+                '<',
+                '>',
+                '+',
+                '-',
+                '*',
+                '/',
+                '%',
+                '&',
+                '^',
+                '|',
+                [T_CLASS],
+                [T_IS_SMALLER_OR_EQUAL],
+                [T_IS_GREATER_OR_EQUAL],
+                [T_IS_EQUAL],
+                [T_IS_NOT_EQUAL],
+                [T_IS_IDENTICAL],
+                [T_IS_NOT_IDENTICAL],
+                [T_CLOSE_TAG],
+                [T_LOGICAL_AND],
+                [T_LOGICAL_OR],
+                [T_LOGICAL_XOR],
+                [T_BOOLEAN_AND],
+                [T_BOOLEAN_OR],
+                [T_SL],
+                [T_SR],
+                [T_INSTANCEOF],
+                [T_AS],
+                [T_DOUBLE_ARROW],
+                [T_POW],
+                [T_SPACESHIP],
+                [CT::T_ARRAY_SQUARE_BRACE_OPEN],
+                [CT::T_ARRAY_SQUARE_BRACE_CLOSE],
+                [CT::T_BRACE_CLASS_INSTANTIATION_OPEN],
+                [CT::T_BRACE_CLASS_INSTANTIATION_CLOSE],
+            ];
+
+            if (\defined('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG')) { // @TODO: drop condition when PHP 8.1+ is required
+                $nextTokenKinds[] = [T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG];
+                $nextTokenKinds[] = [T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG];
+            }
+        }
+
+        for ($index = $tokens->count() - 3; $index > 0; --$index) {
+            if (!$tokens[$index]->isGivenKind(T_NEW)) {
+                continue;
+            }
+
+            $nextIndex = $tokens->getNextTokenOfKind($index, $nextTokenKinds);
+
+            // new anonymous class definition
+            if ($tokens[$nextIndex]->isGivenKind(T_CLASS)) {
+                $nextIndex = $tokens->getNextMeaningfulToken($nextIndex);
+
+                if ($this->configuration['anonymous_class']) {
+                    $this->ensureParenthesesAt($tokens, $nextIndex);
+                } else {
+                    $this->ensureNoParenthesesAt($tokens, $nextIndex);
+                }
+
+                continue;
+            }
+
+            // entrance into array index syntax - need to look for exit
+
+            while ($tokens[$nextIndex]->equals('[') || $tokens[$nextIndex]->isGivenKind(CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN)) {
+                $nextIndex = $tokens->findBlockEnd(Tokens::detectBlockType($tokens[$nextIndex])['type'], $nextIndex);
+                $nextIndex = $tokens->getNextMeaningfulToken($nextIndex);
+            }
+
+            if ($this->configuration['named_class']) {
+                $this->ensureParenthesesAt($tokens, $nextIndex);
+            } else {
+                $this->ensureNoParenthesesAt($tokens, $nextIndex);
+            }
+        }
+    }
+
+    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 ensureParenthesesAt(Tokens $tokens, int $index): void
+    {
+        $token = $tokens[$index];
+
+        if (!$token->equals('(') && !$token->isObjectOperator()) {
+            $tokens->insertAt(
+                $tokens->getPrevMeaningfulToken($index) + 1,
+                [new Token('('), new Token(')')]
+            );
+        }
+    }
+
+    private function ensureNoParenthesesAt(Tokens $tokens, int $index): void
+    {
+        if (!$tokens[$index]->equals('(')) {
+            return;
+        }
+
+        $closingIndex = $tokens->getNextMeaningfulToken($index);
+
+        // constructor has arguments - parentheses can not be removed
+        if (!$tokens[$closingIndex]->equals(')')) {
+            return;
+        }
+
+        $tokens->clearTokenAndMergeSurroundingWhitespace($closingIndex);
+        $tokens->clearTokenAndMergeSurroundingWhitespace($index);
+    }
+}

+ 1 - 1
src/RuleSet/Sets/PSR12Set.php

@@ -42,7 +42,7 @@ final class PSR12Set extends AbstractRuleSetDescription
             'declare_equal_normalize' => true,
             'lowercase_cast' => true,
             'lowercase_static_reference' => true,
-            'new_with_braces' => true,
+            'new_with_parentheses' => true,
             'no_blank_lines_after_class_opening' => true,
             'no_leading_import_slash' => true,
             'no_whitespace_in_blank_line' => true,

Some files were not shown because too many files changed in this diff