From b265092abb9fe229ebc5eb9e950644abfd2d62c0 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 28 Jan 2024 07:05:58 +0100 Subject: [PATCH] PHP 8.3 | Generic/LowerCaseType: add support for typed constants This sniff is specifically targeted at type declarations. PHP 8.3 introduces typed constants. This means that the sniff now also needs to check OO constant declarations. Fixed now. Includes tests. --- .../Generic/Sniffs/PHP/LowerCaseTypeSniff.php | 68 +++++++++++- .../Tests/PHP/LowerCaseTypeUnitTest.inc | 30 ++++++ .../Tests/PHP/LowerCaseTypeUnitTest.inc.fixed | 30 ++++++ .../Tests/PHP/LowerCaseTypeUnitTest.php | 100 ++++++++++-------- 4 files changed, 181 insertions(+), 47 deletions(-) diff --git a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php index 74103c805c..7fb3b85840 100644 --- a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php @@ -87,7 +87,7 @@ public function process(File $phpcsFile, $stackPtr) } /* - * Check property types. + * Check OO constant and property types. */ if (isset(Tokens::$ooScopeTokens[$tokens[$stackPtr]['code']]) === true) { @@ -97,7 +97,7 @@ public function process(File $phpcsFile, $stackPtr) for ($i = ($tokens[$stackPtr]['scope_opener'] + 1); $i < $tokens[$stackPtr]['scope_closer']; $i++) { // Skip over potentially large docblocks. - if ($tokens[$i]['code'] === \T_DOC_COMMENT_OPEN_TAG + if ($tokens[$i]['code'] === T_DOC_COMMENT_OPEN_TAG && isset($tokens[$i]['comment_closer']) === true ) { $i = $tokens[$i]['comment_closer']; @@ -105,14 +105,74 @@ public function process(File $phpcsFile, $stackPtr) } // Skip over function declarations and everything nested within. - if ($tokens[$i]['code'] === \T_FUNCTION + if ($tokens[$i]['code'] === T_FUNCTION && isset($tokens[$i]['scope_closer']) === true ) { $i = $tokens[$i]['scope_closer']; continue; } - if ($tokens[$i]['code'] !== \T_VARIABLE) { + if ($tokens[$i]['code'] === T_CONST) { + $ignore = Tokens::$emptyTokens; + $ignore[T_NULLABLE] = T_NULLABLE; + + $startOfType = $phpcsFile->findNext($ignore, ($i + 1), null, true); + if ($startOfType === false) { + // Parse error/live coding. Nothing to do. Rest of loop is moot. + return; + } + + $assignmentOperator = $phpcsFile->findNext([T_EQUAL, T_SEMICOLON], ($startOfType + 1)); + if ($assignmentOperator === false || $tokens[$assignmentOperator]['code'] !== T_EQUAL) { + // Parse error/live coding. Nothing to do. Rest of loop is moot. + return; + } + + $constName = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($assignmentOperator - 1), null, true); + if ($startOfType !== $constName) { + $endOfType = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($constName - 1), null, true); + + $type = ''; + $isUnionType = false; + $isIntersectionType = false; + for ($j = $startOfType; $j <= $endOfType; $j++) { + if (isset($ignore[$tokens[$j]['code']]) === true) { + continue; + } + + if ($tokens[$j]['code'] === T_TYPE_UNION) { + $isUnionType = true; + } + + if ($tokens[$j]['code'] === T_TYPE_INTERSECTION) { + $isIntersectionType = true; + } + + $type .= $tokens[$j]['content']; + } + + $error = 'PHP constant type declarations must be lowercase; expected "%s" but found "%s"'; + $errorCode = 'ConstantTypeFound'; + + if ($isIntersectionType === true) { + // Intersection types don't support simple types. + } else if ($isUnionType === true) { + $this->processUnionType( + $phpcsFile, + $startOfType, + $endOfType, + $error, + $errorCode + ); + } else if (isset($this->phpTypes[strtolower($type)]) === true) { + $this->processType($phpcsFile, $startOfType, $type, $error, $errorCode); + } + }//end if + + continue; + }//end if + + if ($tokens[$i]['code'] !== T_VARIABLE) { continue; } diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc index 56393c0b24..6674970e10 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc @@ -95,6 +95,36 @@ $arrow = fn (Int $a, String $b, BOOL $c, Array $d, Foo\Bar $e) : Float => $a * $ $cl = function (False $a, TRUE $b, Null $c): ?True {}; +class TypedClassConstants +{ + const UNTYPED = null; + const FLOAT = 'Reserved keyword as name is valid and should not be changed'; + const OBJECT = 'Reserved keyword as name is valid and should not be changed'; + + const ClassName FIRST = null; + public const Int SECOND = 0; + private const ?BOOL THIRD = false; + public const Self FOURTH = null; +} +interface TypedInterfaceConstants +{ + protected const PaRenT FIRST = null; + private const ARRAY SECOND = []; + public const Float THIRD = 2.5; + final const ?STRING FOURTH = 'fourth'; +} +trait TypedTraitConstants { + const IterablE FIRST = null; + const Object SECOND = null; + const Mixed THIRD = 'third'; +} +enum TypedEnumConstants { + public const Iterable|FALSE|NULL FIRST = null; + protected const SELF|Parent /* comment */ |\Fully\Qualified\ClassName|UnQualifiedClass SECOND = null; + private const ClassName|/*comment*/Float|STRING|False THIRD = 'third'; + public const sTRing | aRRaY | FaLSe FOURTH = 'fourth'; +} + // Intentional error, should be ignored by the sniff. interface PropertiesNotAllowed { public $notAllowed; diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed index c1055c726b..59e4af8352 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed @@ -95,6 +95,36 @@ $arrow = fn (int $a, string $b, bool $c, array $d, Foo\Bar $e) : float => $a * $ $cl = function (false $a, true $b, null $c): ?true {}; +class TypedClassConstants +{ + const UNTYPED = null; + const FLOAT = 'Reserved keyword as name is valid and should not be changed'; + const OBJECT = 'Reserved keyword as name is valid and should not be changed'; + + const ClassName FIRST = null; + public const int SECOND = 0; + private const ?bool THIRD = false; + public const self FOURTH = null; +} +interface TypedInterfaceConstants +{ + protected const parent FIRST = null; + private const array SECOND = []; + public const float THIRD = 2.5; + final const ?string FOURTH = 'fourth'; +} +trait TypedTraitConstants { + const iterable FIRST = null; + const object SECOND = null; + const mixed THIRD = 'third'; +} +enum TypedEnumConstants { + public const iterable|false|null FIRST = null; + protected const self|parent /* comment */ |\Fully\Qualified\ClassName|UnQualifiedClass SECOND = null; + private const ClassName|/*comment*/float|string|false THIRD = 'third'; + public const string | array | false FOURTH = 'fourth'; +} + // Intentional error, should be ignored by the sniff. interface PropertiesNotAllowed { public $notAllowed; diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php index 0eaf7f5319..c5cfbb18aa 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php @@ -31,48 +31,62 @@ final class LowerCaseTypeUnitTest extends AbstractSniffUnitTest public function getErrorList() { return [ - 14 => 1, - 15 => 1, - 16 => 1, - 17 => 1, - 18 => 1, - 21 => 4, - 22 => 3, - 23 => 3, - 25 => 1, - 26 => 2, - 27 => 2, - 32 => 4, - 36 => 1, - 37 => 1, - 38 => 1, - 39 => 1, - 43 => 2, - 44 => 1, - 46 => 1, - 49 => 1, - 51 => 2, - 53 => 1, - 55 => 2, - 60 => 1, - 61 => 1, - 62 => 1, - 63 => 1, - 64 => 1, - 65 => 1, - 66 => 1, - 67 => 1, - 68 => 1, - 69 => 1, - 71 => 3, - 72 => 2, - 73 => 3, - 74 => 3, - 78 => 3, - 82 => 2, - 85 => 1, - 94 => 5, - 96 => 4, + 14 => 1, + 15 => 1, + 16 => 1, + 17 => 1, + 18 => 1, + 21 => 4, + 22 => 3, + 23 => 3, + 25 => 1, + 26 => 2, + 27 => 2, + 32 => 4, + 36 => 1, + 37 => 1, + 38 => 1, + 39 => 1, + 43 => 2, + 44 => 1, + 46 => 1, + 49 => 1, + 51 => 2, + 53 => 1, + 55 => 2, + 60 => 1, + 61 => 1, + 62 => 1, + 63 => 1, + 64 => 1, + 65 => 1, + 66 => 1, + 67 => 1, + 68 => 1, + 69 => 1, + 71 => 3, + 72 => 2, + 73 => 3, + 74 => 3, + 78 => 3, + 82 => 2, + 85 => 1, + 94 => 5, + 96 => 4, + 105 => 1, + 106 => 1, + 107 => 1, + 111 => 1, + 112 => 1, + 113 => 1, + 114 => 1, + 117 => 1, + 118 => 1, + 119 => 1, + 122 => 3, + 123 => 2, + 124 => 3, + 125 => 3, ]; }//end getErrorList() @@ -89,7 +103,7 @@ public function getErrorList() public function getWarningList() { // Warning from getMemberProperties() about parse error. - return [100 => 1]; + return [130 => 1]; }//end getWarningList()