From 5fd4059f1b727849ad424b884619e9808d38a9fa Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 28 Jan 2024 00:13:55 +0100 Subject: [PATCH] PHP 8.3 | PSR12/ClassInstantiation: allow for readonly anonymous classes The sniff is supposed to ignore anonymous class instantiations completely as otherwise it could create a conflict between this sniff and the PSR12 sniff checking anonymous class declarations. As things were, however, the sniff would add parentheses after the `new` keyword or after an attribute if an anonymous class was declared as `readonly`, as allowed since PHP 8.3. Fixed now. Includes minor simplification - the sniff would previously _jump over_ attributes attached to anonymous classes to get to the `class` keyword only to bow out later for anonymous classes anyway. Now, the sniff will bow out straight away when either an attribute, the `readonly` keyword or the `class` keyword for an anonymous class declaration is encountered. As `readonly` cannot be used as class name and attributes cannot be attached to (non-anonymous) class instantiations, this should maintain the previous behaviour, while also allowing for PHP 8.3 readonly anonymous classes and it should yield a very small performance benefit as well. Includes unit tests. --- .../Sniffs/Classes/ClassInstantiationSniff.php | 15 ++++++--------- .../Tests/Classes/ClassInstantiationUnitTest.inc | 4 ++++ .../Classes/ClassInstantiationUnitTest.inc.fixed | 4 ++++ 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/Standards/PSR12/Sniffs/Classes/ClassInstantiationSniff.php b/src/Standards/PSR12/Sniffs/Classes/ClassInstantiationSniff.php index 2130b8ddbc..048cb60ccf 100644 --- a/src/Standards/PSR12/Sniffs/Classes/ClassInstantiationSniff.php +++ b/src/Standards/PSR12/Sniffs/Classes/ClassInstantiationSniff.php @@ -64,12 +64,14 @@ public function process(File $phpcsFile, $stackPtr) continue; } - // Skip over potential attributes for anonymous classes. + // Bow out when this is an anonymous class. + // Anonymous classes are the only situation which would allow for an attribute + // or for the readonly keyword between "new" and the class "name". if ($tokens[$i]['code'] === T_ATTRIBUTE - && isset($tokens[$i]['attribute_closer']) === true + || $tokens[$i]['code'] === T_READONLY + || $tokens[$i]['code'] === T_ANON_CLASS ) { - $i = $tokens[$i]['attribute_closer']; - continue; + return; } if ($tokens[$i]['code'] === T_OPEN_SQUARE_BRACKET @@ -87,11 +89,6 @@ public function process(File $phpcsFile, $stackPtr) return; } - if ($tokens[$classNameEnd]['code'] === T_ANON_CLASS) { - // Ignore anon classes. - return; - } - if ($tokens[$classNameEnd]['code'] === T_OPEN_PARENTHESIS) { // Using parenthesis. return; diff --git a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc index 9fd1548072..264116779f 100644 --- a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc +++ b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc @@ -45,3 +45,7 @@ $anonWithAttribute = new #[SomeAttribute('summary')] class { $foo = new parent(); $foo = new parent; + +// PHP 8.3: safeguard that the sniff ignores anonymous classes, even when declared as readonly. +$anon = new readonly class {}; +$anon = new #[MyAttribute] readonly class {}; diff --git a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc.fixed b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc.fixed index aa9d0c7209..1e580f45d9 100644 --- a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc.fixed +++ b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc.fixed @@ -45,3 +45,7 @@ $anonWithAttribute = new #[SomeAttribute('summary')] class { $foo = new parent(); $foo = new parent(); + +// PHP 8.3: safeguard that the sniff ignores anonymous classes, even when declared as readonly. +$anon = new readonly class {}; +$anon = new #[MyAttribute] readonly class {};