From 83b5e2237eeef2c4b7df6c0db021cbfc2de1b402 Mon Sep 17 00:00:00 2001 From: Jakub Vrana Date: Wed, 17 Sep 2025 18:28:40 +0200 Subject: [PATCH] SlevomatCodingStandard.PHP.UselessParentheses: Checks useless parentheses around (new class())->f() via new option "enableCheckAroundNew" --- .../Sniffs/PHP/UselessParenthesesSniff.php | 18 +++++++++++++- doc/php.md | 2 ++ .../PHP/UselessParenthesesSniffTest.php | 24 +++++++++++++++++++ ...sParenthesesCheckAroundNewErrors.fixed.php | 5 ++++ ...uselessParenthesesCheckAroundNewErrors.php | 5 ++++ .../PHP/data/uselessParenthesesNoErrors.php | 3 +-- 6 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 tests/Sniffs/PHP/data/uselessParenthesesCheckAroundNewErrors.fixed.php create mode 100644 tests/Sniffs/PHP/data/uselessParenthesesCheckAroundNewErrors.php diff --git a/SlevomatCodingStandard/Sniffs/PHP/UselessParenthesesSniff.php b/SlevomatCodingStandard/Sniffs/PHP/UselessParenthesesSniff.php index 45780d225..3161a715f 100644 --- a/SlevomatCodingStandard/Sniffs/PHP/UselessParenthesesSniff.php +++ b/SlevomatCodingStandard/Sniffs/PHP/UselessParenthesesSniff.php @@ -54,7 +54,9 @@ use const T_MODULUS; use const T_MULTIPLY; use const T_NEW; +use const T_NULLSAFE_OBJECT_OPERATOR; use const T_OBJECT_CAST; +use const T_OBJECT_OPERATOR; use const T_OPEN_PARENTHESIS; use const T_PARENT; use const T_PLUS; @@ -103,6 +105,8 @@ class UselessParenthesesSniff implements Sniff public bool $ignoreComplexTernaryConditions = false; + public bool $enableCheckAroundNew = false; + /** * @return array */ @@ -611,7 +615,19 @@ private function checkParenthesesAroundNew(File $phpcsFile, int $parenthesisOpen $tokens[$parenthesisOpenerPointer]['parenthesis_closer'] + 1, ); if (!in_array($tokens[$pointerAfterParenthesisCloser]['code'], [T_COMMA, T_SEMICOLON, T_CLOSE_SHORT_ARRAY], true)) { - return; + if (!in_array($tokens[$pointerAfterParenthesisCloser]['code'], [T_OBJECT_OPERATOR, T_NULLSAFE_OBJECT_OPERATOR], true)) { + return; + } + if (!$this->enableCheckAroundNew) { + return; + } + $pointerBeforeParenthesisCloser = TokenHelper::findPreviousEffective( + $phpcsFile, + $tokens[$parenthesisOpenerPointer]['parenthesis_closer'] - 1, + ); + if ($tokens[$pointerBeforeParenthesisCloser]['type'] !== 'T_CLOSE_PARENTHESIS') { + return; + } } $fix = $phpcsFile->addFixableError('Useless parentheses.', $parenthesisOpenerPointer, self::CODE_USELESS_PARENTHESES); diff --git a/doc/php.md b/doc/php.md index 43604714e..959a6982d 100644 --- a/doc/php.md +++ b/doc/php.md @@ -74,6 +74,8 @@ Looks for useless parentheses. Sniff provides the following settings: * `ignoreComplexTernaryConditions` (default: `false`): ignores complex ternary conditions - condition must contain `&&`, `||` etc. or end of line. +* `enableCheckAroundNew` (default: `false`): enables check of useless parentheses around `(new class())->call()`. + #### SlevomatCodingStandard.PHP.UselessSemicolon 🔧 diff --git a/tests/Sniffs/PHP/UselessParenthesesSniffTest.php b/tests/Sniffs/PHP/UselessParenthesesSniffTest.php index 2bfe11892..11ac0d010 100644 --- a/tests/Sniffs/PHP/UselessParenthesesSniffTest.php +++ b/tests/Sniffs/PHP/UselessParenthesesSniffTest.php @@ -26,6 +26,30 @@ public function testErrors(): void self::assertAllFixedInFile($report); } + public function testCheckAroundNewDisabled(): void + { + $report = self::checkFile(__DIR__ . '/data/uselessParenthesesCheckAroundNewErrors.php', [ + 'enableCheckAroundNew' => false, + ]); + + self::assertNoSniffErrorInFile($report); + } + + public function testCheckAroundNewEnabled(): void + { + $report = self::checkFile(__DIR__ . '/data/uselessParenthesesCheckAroundNewErrors.php', [ + 'enableCheckAroundNew' => true, + ]); + + self::assertSame(2, $report->getErrorCount()); + + foreach ([3, 5] as $line) { + self::assertSniffError($report, $line, UselessParenthesesSniff::CODE_USELESS_PARENTHESES); + } + + self::assertAllFixedInFile($report); + } + public function testNoErrorsWithIgnoredComplexTernaryConditions(): void { $report = self::checkFile(__DIR__ . '/data/uselessParenthesesNoErrorsWithIgnoredComplexTernaryConditions.php', [ diff --git a/tests/Sniffs/PHP/data/uselessParenthesesCheckAroundNewErrors.fixed.php b/tests/Sniffs/PHP/data/uselessParenthesesCheckAroundNewErrors.fixed.php new file mode 100644 index 000000000..0f39a0a2e --- /dev/null +++ b/tests/Sniffs/PHP/data/uselessParenthesesCheckAroundNewErrors.fixed.php @@ -0,0 +1,5 @@ += 8.4 + +$response = new Response()->withStatus(200); +$response = (new Response)->withStatus(200); +$ip = new RemoteAddress()?->getIpAddress(); diff --git a/tests/Sniffs/PHP/data/uselessParenthesesCheckAroundNewErrors.php b/tests/Sniffs/PHP/data/uselessParenthesesCheckAroundNewErrors.php new file mode 100644 index 000000000..b93cfec02 --- /dev/null +++ b/tests/Sniffs/PHP/data/uselessParenthesesCheckAroundNewErrors.php @@ -0,0 +1,5 @@ += 8.4 + +$response = (new Response())->withStatus(200); +$response = (new Response)->withStatus(200); +$ip = (new RemoteAddress())?->getIpAddress(); diff --git a/tests/Sniffs/PHP/data/uselessParenthesesNoErrors.php b/tests/Sniffs/PHP/data/uselessParenthesesNoErrors.php index be4f9bb22..c7c332e8d 100644 --- a/tests/Sniffs/PHP/data/uselessParenthesesNoErrors.php +++ b/tests/Sniffs/PHP/data/uselessParenthesesNoErrors.php @@ -177,8 +177,7 @@ function ($value) { echo 'Hello' . ($foo ? ' There' : $fn()); -$response = (new Response())->withStatus(200); -$ip = (new RemoteAddress())?->getIpAddress(); +doSomething((new Response())); echo ~(1 - 1);