Repository: PHP-CS-Fixer/PHP-CS-Fixer (php)

Issue:
Introduce `new_expression_parentheses` fixer for PHP 8.4
### Feature request

PHP 8.4 allows to omit the `new` expression parentheses ([RFC](https://wiki.php.net/rfc/new_without_parentheses)).

Right now PHP CS Fixer has 2 rules dealing with parentheses:
- [no_unneeded_control_parentheses](https://cs.symfony.com/doc/rules/control_structure/no_unneeded_control_parentheses.html) fixes statements, so it seems to be a bad fit for the `new` expressions.
- [new_with_parentheses](https://cs.symfony.com/doc/rules/operator/new_with_parentheses.html) is about arguments' parentheses, not about new expression parentheses.

Here's what @Wirone proposed in https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/discussions/8069#discussioncomment-9685707:

> ... deprecating `new_with_parentheses` in favour of `new_parentheses` and providing 2 options there: `invoke` (for `new Foo()`) and `chain` (for `(new Foo)->`). Options' names can differ, it's only an idea. Both options should be tri-state: `bool|null`, which means you can enforce parentheses (`true`), remove them (`false`) or leave them as-is (`null`).

I like this idea. However, I would name options `arguments: ?bool` and `expression: ?bool`.

WDYT?

### Link to Discussion where feature request was formed

https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/discussions/8069

### Contribution Checks

- [X] I have verified if this feature request was already discussed
- [X] I am familiar with ["Feature or bug?"](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/feature-or-bug.rst)

Pull request description:
feat: add `NewExpressionParenthesesFixer`
Fixes #8213

Patch:
diff --git a/doc/rules/index.rst b/doc/rules/index.rst
index 27645251c..fa49e5645 100644
--- a/doc/rules/index.rst
+++ b/doc/rules/index.rst
@@ -575,6 +575,9 @@ Operator
 - `long_to_shorthand_operator <./operator/long_to_shorthand_operator.rst>`_ *(risky)*
 
   Shorthand notation for operators should be used if possible.
+- `new_expression_parentheses <./operator/new_expression_parentheses.rst>`_
+
+  All ``new`` expressions with a further call must (not) be wrapped in parentheses.
 - `new_with_braces <./operator/new_with_braces.rst>`_ *(deprecated)*
 
   All instances created with ``new`` keyword must (not) be followed by braces.
diff --git a/doc/rules/operator/new_expression_parentheses.rst b/doc/rules/operator/new_expression_parentheses.rst
new file mode 100644
index 000000000..2e0aeb9fb
--- /dev/null
+++ b/doc/rules/operator/new_expression_parentheses.rst
@@ -0,0 +1,85 @@
+===================================
+Rule ``new_expression_parentheses``
+===================================
+
+All ``new`` expressions with a further call must (not) be wrapped in
+parentheses.
+
+Configuration
+-------------
+
+``use_parentheses``
+~~~~~~~~~~~~~~~~~~~
+
+Whether ``new`` expressions with a further call should be wrapped in parentheses
+or not.
+
+Allowed types: ``bool``
+
+Default value: ``false``
+
+Examples
+--------
+
+Example #1
+~~~~~~~~~~
+
+*Default* configuration.
+
+.. code-block:: diff
+
+   --- Original
+   +++ New
+    <?php
+
+   -(new Foo())->bar();
+   +new Foo()->bar();
+
+Example #2
+~~~~~~~~~~
+
+*Default* configuration.
+
+.. code-block:: diff
+
+   --- Original
+   +++ New
+    <?php
+
+   -(new class {})->bar();
+   +new class {}->bar();
+
+Example #3
+~~~~~~~~~~
+
+With configuration: ``['use_parentheses' => true]``.
+
+.. code-block:: diff
+
+   --- Original
+   +++ New
+    <?php
+
+   -new Foo()->bar();
+   +(new Foo())->bar();
+
+Example #4
+~~~~~~~~~~
+
+With configuration: ``['use_parentheses' => true]``.
+
+.. code-block:: diff
+
+   --- Original
+   +++ New
+    <?php
+
+   -new class {}->bar();
+   +(new class {})->bar();
+References
+----------
+
+- Fixer class: `PhpCsFixer\\Fixer\\Operator\\NewExpressionParenthesesFixer <./../../../src/Fixer/Operator/NewExpressionParenthesesFixer.php>`_
+- Test class: `PhpCsFixer\\Tests\\Fixer\\Operator\\NewExpressionParenthesesFixerTest <./../../../tests/Fixer/Operator/NewExpressionParenthesesFixerTest.php>`_
+
+The test class defines officially supported behaviour. Each test case is a part of our backward compatibility promise.
diff --git a/src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.php b/src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.php
index b3771cfbb..69f5d3fdf 100644
--- a/src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.php
+++ b/src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.php
@@ -185,7 +185,7 @@ public function getDefinition(): FixerDefinitionInterface
     /**
      * {@inheritdoc}
      *
-     * Must run before ConcatSpaceFixer, NoTrailingWhitespaceFixer.
+     * Must run before ConcatSpaceFixer, NewExpressionParenthesesFixer, NoTrailingWhitespaceFixer.
      * Must run after ModernizeTypesCastingFixer, NoAlternativeSyntaxFixer.
      */
     public function getPriority(): int
diff --git a/src/Fixer/Operator/NewExpressionParenthesesFixer.php b/src/Fixer/Operator/NewExpressionParenthesesFixer.php
new file mode 100644
index 000000000..bed2f70ed
--- /dev/null
+++ b/src/Fixer/Operator/NewExpressionParenthesesFixer.php
@@ -0,0 +1,239 @@
+<?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\Fixer\ConfigurableFixerTrait;
+use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
+use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
+use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
+use PhpCsFixer\FixerDefinition\FixerDefinition;
+use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
+use PhpCsFixer\FixerDefinition\VersionSpecification;
+use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
+use PhpCsFixer\Tokenizer\CT;
+use PhpCsFixer\Tokenizer\Token;
+use PhpCsFixer\Tokenizer\Tokens;
+
+/**
+ * @author Valentin Udaltsov <udaltsov.valentin@gmail.com>
+ *
+ * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
+ *
+ * @phpstan-type _AutogeneratedInputConfiguration array{
+ *  use_parentheses?: bool,
+ * }
+ * @phpstan-type _AutogeneratedComputedConfiguration array{
+ *  use_parentheses: bool,
+ * }
+ */
+final class NewExpressionParenthesesFixer extends AbstractFixer implements ConfigurableFixerInterface
+{
+    /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
+    use ConfigurableFixerTrait;
+
+    public function getDefinition(): FixerDefinitionInterface
+    {
+        return new FixerDefinition(
+            'All `new` expressions with a further call must (not) be wrapped in parentheses.',
+            [
+                new VersionSpecificCodeSample(
+                    "<?php\n\n(new Foo())->bar();\n",
+                    new VersionSpecification(8_04_00)
+                ),
+                new VersionSpecificCodeSample(
+                    "<?php\n\n(new class {})->bar();\n",
+                    new VersionSpecification(8_04_00)
+                ),
+                new VersionSpecificCodeSample(
+                    "<?php\n\nnew Foo()->bar();\n",
+                    new VersionSpecification(8_04_00),
+                    ['use_parentheses' => true]
+                ),
+                new VersionSpecificCodeSample(
+                    "<?php\n\nnew class {}->bar();\n",
+                    new VersionSpecification(8_04_00),
+                    ['use_parentheses' => true]
+                ),
+            ]
+        );
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * Must run after NewWithParenthesesFixer, NoUnneededControlParenthesesFixer.
+     */
+    public function getPriority(): int
+    {
+        return 29;
+    }
+
+    public function isCandidate(Tokens $tokens): bool
+    {
+        return \PHP_VERSION_ID >= 8_04_00 && $tokens->isTokenKindFound(T_NEW);
+    }
+
+    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
+    {
+        return new FixerConfigurationResolver([
+            (new FixerOptionBuilder('use_parentheses', 'Whether `new` expressions with a further call should be wrapped in parentheses or not.'))
+                ->setAllowedTypes(['bool'])
+                ->setDefault(false)
+                ->getOption(),
+        ]);
+    }
+
+    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
+    {
+        $useParentheses = $this->configuration['use_parentheses'];
+
+        for ($index = $tokens->count() - 3; $index > 0; --$index) {
+            if (!$tokens[$index]->isGivenKind(T_NEW)) {
+                continue;
+            }
+
+            $classStartIndex = $tokens->getNextMeaningfulToken($index);
+
+            if (null === $classStartIndex) {
+                return;
+            }
+
+            // anonymous class
+            if ($tokens[$classStartIndex]->isGivenKind(T_CLASS)) {
+                $nextIndex = $tokens->getNextMeaningfulToken($classStartIndex);
+
+                if ($tokens[$nextIndex]->equals('(')) {
+                    $nextIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextIndex);
+                } else {
+                    $nextIndex = $classStartIndex;
+                }
+
+                $bodyStartIndex = $tokens->getNextTokenOfKind($nextIndex, ['{']);
+                $bodyEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $bodyStartIndex);
+
+                if ($useParentheses) {
+                    $this->ensureWrappedInParentheses($tokens, $index, $bodyEndIndex);
+                } else {
+                    $this->ensureNotWrappedInParentheses($tokens, $index, $bodyEndIndex);
+                }
+
+                continue;
+            }
+
+            // named class
+            $classEndIndex = $this->findClassEndIndex($tokens, $classStartIndex);
+
+            if (null === $classEndIndex) {
+                continue;
+            }
+
+            $nextIndex = $tokens->getNextMeaningfulToken($classEndIndex);
+
+            if (!$tokens[$nextIndex]->equals('(')) {
+                // If arguments' parentheses are absent then either this new expression is not further called
+                // and does not need parentheses, or we cannot omit its parentheses due to the grammar rules.
+                continue;
+            }
+
+            $argsEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextIndex);
+
+            if ($useParentheses) {
+                $this->ensureWrappedInParentheses($tokens, $index, $argsEndIndex);
+            } else {
+                $this->ensureNotWrappedInParentheses($tokens, $index, $argsEndIndex);
+            }
+        }
+    }
+
+    private function ensureWrappedInParentheses(Tokens $tokens, int $exprStartIndex, int $exprEndIndex): void
+    {
+        $prevIndex = $tokens->getPrevMeaningfulToken($exprStartIndex);
+        $nextIndex = $tokens->getNextMeaningfulToken($exprEndIndex);
+
+        if ($tokens[$prevIndex]->isGivenKind(CT::T_BRACE_CLASS_INSTANTIATION_OPEN)
+            && $tokens[$nextIndex]->isGivenKind(CT::T_BRACE_CLASS_INSTANTIATION_CLOSE)
+        ) {
+            return;
+        }
+
+        if (!$tokens[$nextIndex]->isObjectOperator() && !$tokens[$nextIndex]->isGivenKind(T_PAAMAYIM_NEKUDOTAYIM)) {
+            return;
+        }
+
+        $tokens->insertAt($exprStartIndex, [new Token([CT::T_BRACE_CLASS_INSTANTIATION_OPEN, '('])]);
+        $tokens->insertAt($exprEndIndex + 2, [new Token([CT::T_BRACE_CLASS_INSTANTIATION_CLOSE, ')'])]);
+    }
+
+    private function ensureNotWrappedInParentheses(Tokens $tokens, int $exprStartIndex, int $exprEndIndex): void
+    {
+        $prevIndex = $tokens->getPrevMeaningfulToken($exprStartIndex);
+        $nextIndex = $tokens->getNextMeaningfulToken($exprEndIndex);
+
+        if (!$tokens[$prevIndex]->isGivenKind(CT::T_BRACE_CLASS_INSTANTIATION_OPEN)
+            || !$tokens[$nextIndex]->isGivenKind(CT::T_BRACE_CLASS_INSTANTIATION_CLOSE)
+        ) {
+            return;
+        }
+
+        $operatorIndex = $tokens->getNextMeaningfulToken($nextIndex);
+
+        if (!$tokens[$operatorIndex]->isObjectOperator() && !$tokens[$operatorIndex]->isGivenKind(T_PAAMAYIM_NEKUDOTAYIM)) {
+            return;
+        }
+
+        $tokens->clearTokenAndMergeSurroundingWhitespace($prevIndex);
+        $tokens->clearTokenAndMergeSurroundingWhitespace($nextIndex);
+    }
+
+    private function findClassEndIndex(Tokens $tokens, int $index): ?int
+    {
+        // (expression) class name
+        if ($tokens[$index]->equals('(')) {
+            return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
+        }
+
+        // regular class name or $variable class name
+        static $nextTokens = [
+            [T_STRING],
+            [T_NS_SEPARATOR],
+            [CT::T_NAMESPACE_OPERATOR],
+            [T_VARIABLE],
+            '$',
+            [CT::T_DYNAMIC_VAR_BRACE_OPEN],
+            '[',
+            [T_OBJECT_OPERATOR],
+            [T_NULLSAFE_OBJECT_OPERATOR],
+            [T_PAAMAYIM_NEKUDOTAYIM],
+        ];
+
+        if (!$tokens[$index]->equalsAny($nextTokens)) {
+            return null;
+        }
+
+        while ($tokens[$index]->equalsAny($nextTokens)) {
+            $blockType = Tokens::detectBlockType($tokens[$index]);
+
+            if (null !== $blockType) {
+                $index = $tokens->findBlockEnd($blockType['type'], $index);
+            }
+
+            $index = $tokens->getNextMeaningfulToken($index);
+        }
+
+        return $index - 1;
+    }
+}
diff --git a/src/Fixer/Operator/NewWithParenthesesFixer.php b/src/Fixer/Operator/NewWithParenthesesFixer.php
index 0398bb144..691f67f17 100644
--- a/src/Fixer/Operator/NewWithParenthesesFixer.php
+++ b/src/Fixer/Operator/NewWithParenthesesFixer.php
@@ -67,11 +67,11 @@ public function getDefinition(): FixerDefinitionInterface
     /**
      * {@inheritdoc}
      *
-     * Must run before ClassDefinitionFixer.
+     * Must run before ClassDefinitionFixer, NewExpressionParenthesesFixer.
      */
     public function getPriority(): int
     {
-        return 37;
+        return 38;
     }
 
     public function isCandidate(Tokens $tokens): bool


Test patch:
diff --git a/tests/AutoReview/FixerFactoryTest.php b/tests/AutoReview/FixerFactoryTest.php
index e5672f888..4fbbfbf44 100644
--- a/tests/AutoReview/FixerFactoryTest.php
+++ b/tests/AutoReview/FixerFactoryTest.php
@@ -567,6 +567,7 @@ private static function getFixersPriorityGraph(): array
             ],
             'new_with_parentheses' => [
                 'class_definition',
+                'new_expression_parentheses',
             ],
             'no_alias_functions' => [
                 'implode_call',
@@ -661,6 +662,7 @@ private static function getFixersPriorityGraph(): array
             ],
             'no_unneeded_control_parentheses' => [
                 'concat_space',
+                'new_expression_parentheses',
                 'no_trailing_whitespace',
             ],
             'no_unneeded_curly_braces' => [
diff --git a/tests/Fixer/Operator/NewExpressionParenthesesFixerTest.php b/tests/Fixer/Operator/NewExpressionParenthesesFixerTest.php
new file mode 100644
index 000000000..9ec2b1aa7
--- /dev/null
+++ b/tests/Fixer/Operator/NewExpressionParenthesesFixerTest.php
@@ -0,0 +1,232 @@
+<?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\Tests\Fixer\Operator;
+
+use PhpCsFixer\Tests\Test\AbstractFixerTestCase;
+
+/**
+ * @author Valentin Udaltsov <udaltsov.valentin@gmail.com>
+ *
+ * @internal
+ *
+ * @covers \PhpCsFixer\Fixer\Operator\NewExpressionParenthesesFixer
+ *
+ * @extends AbstractFixerTestCase<\PhpCsFixer\Fixer\Operator\NewExpressionParenthesesFixer>
+ *
+ * @phpstan-import-type _AutogeneratedInputConfiguration from \PhpCsFixer\Fixer\Operator\NewExpressionParenthesesFixer
+ */
+final class NewExpressionParenthesesFixerTest extends AbstractFixerTestCase
+{
+    /**
+     * @param _AutogeneratedInputConfiguration $configuration
+     *
+     * @dataProvider provideFixCases
+     *
+     * @requires PHP 8.4
+     */
+    public function testFix(string $expected, ?string $input = null, array $configuration = []): void
+    {
+        $this->fixer->configure($configuration);
+        $this->doTest($expected, $input);
+    }
+
+    /**
+     * @return iterable<int, array{0: non-empty-string, 1?: ?non-empty-string, 2?: _AutogeneratedInputConfiguration}>
+     */
+    public static function provideFixCases(): iterable
+    {
+        // anonymous class
+        yield ['<?php (new class {});'];
+
+        yield ['<?php ((new class {}))->bar;'];
+
+        yield [
+            '<?php new class {}->bar;',
+            '<?php (new class {})->bar;',
+        ];
+
+        yield [
+            '<?php  /**/ new /**/ class /**/ {} /**/ ->bar;',
+            '<?php ( /**/ new /**/ class /**/ {} /**/ )->bar;',
+        ];
+
+        yield [
+            '<?php new class ($x, (1 + 2) / 3) {}->bar;',
+            '<?php (new class ($x, (1 + 2) / 3) {})->bar;',
+        ];
+
+        yield [
+            '<?php new class (new class {}) {}->bar;',
+            '<?php (new class (new class {}) {})->bar;',
+        ];
+
+        yield [
+            '<?php new class ($x, (1 + 2) / 3) extends stdClass implements Throwable {}->bar;',
+            '<?php (new class ($x, (1 + 2) / 3) extends stdClass implements Throwable {})->bar;',
+        ];
+
+        yield [
+            '<?php new class { public function __construct() { new class {}->bar; } }->bar;',
+            '<?php (new class { public function __construct() { (new class {})->bar; } })->bar;',
+        ];
+
+        yield ['<?php (new class {})->bar;', null, ['use_parentheses' => true]];
+
+        yield [
+            '<?php (new class {})->bar;',
+            '<?php new class {}->bar;',
+            ['use_parentheses' => true],
+        ];
+
+        // regular class name
+        yield ['<?php (new stdClass());'];
+
+        yield ['<?php ((new stdClass()));'];
+
+        yield [
+            '<?php new stdClass()->bar;',
+            '<?php (new stdClass())->bar;',
+        ];
+
+        yield [
+            '<?php new \stdClass()->bar;',
+            '<?php (new \stdClass())->bar;',
+        ];
+
+        yield [
+            '<?php new namespace\Foo()->bar;',
+            '<?php (new namespace\Foo())->bar;',
+        ];
+
+        yield [
+            '<?php new Name\Space\Foo()->bar;',
+            '<?php (new Name\Space\Foo())->bar;',
+        ];
+
+        yield [
+            '<?php new \Name\Space\Foo()->bar;',
+            '<?php (new \Name\Space\Foo())->bar;',
+        ];
+
+        yield [
+            '<?php  /**/ new /**/ \Name\Space\Foo /**/ () /**/ ->bar;',
+            '<?php ( /**/ new /**/ \Name\Space\Foo /**/ () /**/ )->bar;',
+        ];
+
+        yield [
+            '<?php new Foo($x, (1 + 2) / 3)->bar;',
+            '<?php (new Foo($x, (1 + 2) / 3))->bar;',
+        ];
+
+        yield [
+            '<?php new Foo(new Baz()->m())->bar;',
+            '<?php (new Foo((new Baz())->m()))->bar;',
+        ];
+
+        yield ['<?php (new Foo)->bar;'];
+
+        yield ['<?php (new Foo())->bar;', null, ['use_parentheses' => true]];
+
+        yield [
+            '<?php (new Foo())->bar;',
+            '<?php new Foo()->bar;',
+            ['use_parentheses' => true],
+        ];
+
+        // $variable class name
+        yield [
+            '<?php new $class()->bar;',
+            '<?php (new $class())->bar;',
+        ];
+
+        yield [
+            '<?php  /**/ new /**/ $class /**/ ( /**/ ) /**/ ->bar;',
+            '<?php ( /**/ new /**/ $class /**/ ( /**/ ) /**/ )->bar;',
+        ];
+
+        yield [
+            '<?php new $$var()->bar;',
+            '<?php (new $$var())->bar;',
+        ];
+
+        yield [
+            '<?php new ${"var"}()->bar;',
+            '<?php (new ${"var"}())->bar;',
+        ];
+
+        yield [
+            '<?php new $obj->prop()->bar;',
+            '<?php (new $obj->prop())->bar;',
+        ];
+
+        yield [
+            '<?php new $obj?->prop()->bar;',
+            '<?php (new $obj?->prop())->bar;',
+        ];
+
+        yield [
+            '<?php new $obj::$prop()->bar;',
+            '<?php (new $obj::$prop())->bar;',
+        ];
+
+        yield [
+            '<?php new SomeClass::$prop()->bar;',
+            '<?php (new SomeClass::$prop())->bar;',
+        ];
+
+        yield [
+            '<?php new $obj["x"]()->bar;',
+            '<?php (new $obj["x"]())->bar;',
+        ];
+
+        yield [
+            '<?php new $obj[1][2]()->bar;',
+            '<?php (new $obj[1][2]())->bar;',
+        ];
+
+        yield ['<?php (new $class)->bar;', null, ['use_parentheses' => true]];
+
+        yield [
+            '<?php (new $class())->bar;',
+            '<?php new $class()->bar;',
+            ['use_parentheses' => true],
+        ];
+
+        // (expression) class name
+        yield ['<?php (new (foo()));'];
+
+        yield ['<?php ((new (foo())));'];
+
+        yield [
+            '<?php new (foo())()->bar;',
+            '<?php (new (foo())())->bar;',
+        ];
+
+        yield [
+            '<?php  /**/ new /**/ ( /**/ foo() /**/ ) /**/ ( /**/ ) /**/ ->bar;',
+            '<?php ( /**/ new /**/ ( /**/ foo() /**/ ) /**/ ( /**/ ) /**/ )->bar;',
+        ];
+
+        yield ['<?php (new (foo()))->bar;'];
+
+        yield ['<?php (new (foo()))->bar;', null, ['use_parentheses' => true]];
+
+        yield [
+            '<?php (new (foo())())->bar;',
+            '<?php new (foo())()->bar;',
+            ['use_parentheses' => true],
+        ];
+    }
+}
diff --git a/tests/Fixtures/Integration/priority/new_with_parentheses,new_expression_parentheses.test b/tests/Fixtures/Integration/priority/new_with_parentheses,new_expression_parentheses.test
new file mode 100644
index 000000000..4643c9321
--- /dev/null
+++ b/tests/Fixtures/Integration/priority/new_with_parentheses,new_expression_parentheses.test
@@ -0,0 +1,13 @@
+--TEST--
+Integration of fixers: new_with_parentheses,new_expression_parentheses.
+--RULESET--
+{"new_with_parentheses": true, "new_expression_parentheses": true}
+--REQUIREMENTS--
+{"php": 80400}
+--EXPECT--
+<?php
+new Foo()->bar;
+
+--INPUT--
+<?php
+(new Foo)->bar;
diff --git a/tests/Fixtures/Integration/priority/no_unneeded_control_parentheses,new_expression_parentheses.test b/tests/Fixtures/Integration/priority/no_unneeded_control_parentheses,new_expression_parentheses.test
new file mode 100644
index 000000000..b76fd7faf
--- /dev/null
+++ b/tests/Fixtures/Integration/priority/no_unneeded_control_parentheses,new_expression_parentheses.test
@@ -0,0 +1,13 @@
+--TEST--
+Integration of fixers: no_unneeded_control_parentheses,new_expression_parentheses.
+--RULESET--
+{"no_unneeded_control_parentheses": {"statements": ["others"]}, "new_expression_parentheses": true}
+--REQUIREMENTS--
+{"php": 80400}
+--EXPECT--
+<?php
+new Foo()->bar;
+
+--INPUT--
+<?php
+(((new Foo())))->bar;