From a4eccd3bae3413ac7b51b123b51ff366368bedca Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 9 Apr 2025 08:09:08 +0200 Subject: [PATCH] PHP 8.4 | PSR2/PropertyDeclaration: add support for final properties Includes handling the modifier order when `final` is used. This introduces a new `FinalAfterVisibility` error code. Includes tests. Note: the modifier keyword order checks could probably do with some optimization, but that can be handled later. --- .../Classes/PropertyDeclarationSniff.php | 29 +++++++++++++++++-- .../Classes/PropertyDeclarationUnitTest.inc | 11 +++++++ .../PropertyDeclarationUnitTest.inc.fixed | 11 +++++++ .../Classes/PropertyDeclarationUnitTest.php | 5 ++++ 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/Standards/PSR2/Sniffs/Classes/PropertyDeclarationSniff.php b/src/Standards/PSR2/Sniffs/Classes/PropertyDeclarationSniff.php index 29d7023ea9..70828bb991 100644 --- a/src/Standards/PSR2/Sniffs/Classes/PropertyDeclarationSniff.php +++ b/src/Standards/PSR2/Sniffs/Classes/PropertyDeclarationSniff.php @@ -43,6 +43,7 @@ protected function processMemberVar(File $phpcsFile, $stackPtr) $find[] = T_VARIABLE; $find[] = T_VAR; $find[] = T_READONLY; + $find[] = T_FINAL; $find[] = T_SEMICOLON; $find[] = T_OPEN_CURLY_BRACKET; @@ -130,12 +131,36 @@ protected function processMemberVar(File $phpcsFile, $stackPtr) * * Ref: https://www.php-fig.org/per/coding-style/#46-modifier-keywords * - * At this time (PHP 8.2), inheritance modifiers cannot be applied to properties and - * the `static` and `readonly` modifiers are mutually exclusive and cannot be used together. + * The `static` and `readonly` modifiers are mutually exclusive and cannot be used together. * * Based on that, the below modifier keyword order checks are sufficient (for now). */ + if ($propertyInfo['scope_specified'] === true && $propertyInfo['is_final'] === true) { + $scopePtr = $phpcsFile->findPrevious(Tokens::$scopeModifiers, ($stackPtr - 1)); + $finalPtr = $phpcsFile->findPrevious(T_FINAL, ($stackPtr - 1)); + if ($finalPtr > $scopePtr) { + $error = 'The final declaration must come before the visibility declaration'; + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'FinalAfterVisibility'); + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + + for ($i = ($finalPtr + 1); $finalPtr < $stackPtr; $i++) { + if ($tokens[$i]['code'] !== T_WHITESPACE) { + break; + } + + $phpcsFile->fixer->replaceToken($i, ''); + } + + $phpcsFile->fixer->replaceToken($finalPtr, ''); + $phpcsFile->fixer->addContentBefore($scopePtr, $tokens[$finalPtr]['content'].' '); + + $phpcsFile->fixer->endChangeset(); + } + } + }//end if + if ($propertyInfo['scope_specified'] === true && $propertyInfo['is_static'] === true) { $scopePtr = $phpcsFile->findPrevious(Tokens::$scopeModifiers, ($stackPtr - 1)); $staticPtr = $phpcsFile->findPrevious(T_STATIC, ($stackPtr - 1)); diff --git a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc index 3e086c6f22..4db25459cc 100644 --- a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc +++ b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc @@ -85,3 +85,14 @@ class ReadOnlyProp { readonly protected ?string $wrongOrder2; } + +class FinalProperties { + final public int $foo, + $bar, + $var = null; + + final protected (D|N)|false $foo; + final array $foo; + public FINAL ?int $wrongOrder1; + static protected final ?string $wrongOrder2; +} diff --git a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc.fixed b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc.fixed index c4e22fc18b..fd5d9fa59e 100644 --- a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc.fixed +++ b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc.fixed @@ -82,3 +82,14 @@ class ReadOnlyProp { protected readonly ?string $wrongOrder2; } + +class FinalProperties { + final public int $foo, + $bar, + $var = null; + + final protected (D|N)|false $foo; + final array $foo; + FINAL public ?int $wrongOrder1; + final protected static ?string $wrongOrder2; +} diff --git a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.php b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.php index bf7dc29a31..6310098525 100644 --- a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.php +++ b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.php @@ -56,6 +56,11 @@ public function getErrorList() 82 => 1, 84 => 1, 86 => 1, + 90 => 1, + 94 => 1, + 95 => 1, + 96 => 1, + 97 => 2, ]; }//end getErrorList()