diff --git a/src/Standards/Generic/Sniffs/CodeAnalysis/UnconditionalIfStatementSniff.php b/src/Standards/Generic/Sniffs/CodeAnalysis/UnconditionalIfStatementSniff.php index 7471be83f2..091c461ec4 100644 --- a/src/Standards/Generic/Sniffs/CodeAnalysis/UnconditionalIfStatementSniff.php +++ b/src/Standards/Generic/Sniffs/CodeAnalysis/UnconditionalIfStatementSniff.php @@ -77,6 +77,13 @@ public function process(File $phpcsFile, $stackPtr) if (isset(Tokens::EMPTY_TOKENS[$code]) === true) { continue; + } + + if ($code === T_NAME_FULLY_QUALIFIED) { + $compareReadyKeyword = strtolower($tokens[$next]['content']); + if ($compareReadyKeyword !== '\true' && $compareReadyKeyword !== '\false') { + $goodCondition = true; + } } else if ($code !== T_TRUE && $code !== T_FALSE) { $goodCondition = true; } diff --git a/src/Standards/Generic/Sniffs/ControlStructures/DisallowYodaConditionsSniff.php b/src/Standards/Generic/Sniffs/ControlStructures/DisallowYodaConditionsSniff.php index 6d0ffdac1d..5c62af9295 100644 --- a/src/Standards/Generic/Sniffs/ControlStructures/DisallowYodaConditionsSniff.php +++ b/src/Standards/Generic/Sniffs/ControlStructures/DisallowYodaConditionsSniff.php @@ -55,12 +55,24 @@ public function process(File $phpcsFile, $stackPtr) T_LNUMBER, T_DNUMBER, T_CONSTANT_ENCAPSED_STRING, + T_NAME_FULLY_QUALIFIED, ]; if (in_array($tokens[$previousIndex]['code'], $relevantTokens, true) === false) { return; } + // Special case: T_NAME_FULLY_QUALIFIED is only a "relevant" token when it is for a FQN true/false/null. + if ($tokens[$previousIndex]['code'] === T_NAME_FULLY_QUALIFIED) { + $compareReadyKeyword = strtolower($tokens[$previousIndex]['content']); + if ($compareReadyKeyword !== '\true' + && $compareReadyKeyword !== '\false' + && $compareReadyKeyword !== '\null' + ) { + return; + } + } + if ($tokens[$previousIndex]['code'] === T_CLOSE_SHORT_ARRAY) { $previousIndex = $tokens[$previousIndex]['bracket_opener']; if ($this->isArrayStatic($phpcsFile, $previousIndex) === false) { @@ -164,6 +176,7 @@ public function isArrayStatic(File $phpcsFile, $arrayToken) T_COMMA => T_COMMA, T_TRUE => T_TRUE, T_FALSE => T_FALSE, + T_NULL => T_NULL, ]; for ($i = ($start + 1); $i < $end; $i++) { @@ -172,10 +185,21 @@ public function isArrayStatic(File $phpcsFile, $arrayToken) continue; } + // Special case: T_NAME_FULLY_QUALIFIED is only a "static" token when it is for a FQN true/false/null. + if ($tokens[$i]['code'] === T_NAME_FULLY_QUALIFIED) { + $compareReadyKeyword = strtolower($tokens[$i]['content']); + if ($compareReadyKeyword === '\true' + || $compareReadyKeyword === '\false' + || $compareReadyKeyword === '\null' + ) { + continue; + } + } + if (isset($staticTokens[$tokens[$i]['code']]) === false) { return false; } - } + }//end for return true; diff --git a/src/Standards/Generic/Sniffs/PHP/LowerCaseConstantSniff.php b/src/Standards/Generic/Sniffs/PHP/LowerCaseConstantSniff.php index 4defc405e4..5490a7407b 100644 --- a/src/Standards/Generic/Sniffs/PHP/LowerCaseConstantSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/LowerCaseConstantSniff.php @@ -43,8 +43,6 @@ class LowerCaseConstantSniff implements Sniff T_NAME_QUALIFIED => T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED => T_NAME_FULLY_QUALIFIED, T_NAME_RELATIVE => T_NAME_RELATIVE, - T_NS_SEPARATOR => T_NS_SEPARATOR, - T_NAMESPACE => T_NAMESPACE, T_TYPE_UNION => T_TYPE_UNION, T_TYPE_INTERSECTION => T_TYPE_INTERSECTION, T_TYPE_OPEN_PARENTHESIS => T_TYPE_OPEN_PARENTHESIS, @@ -62,6 +60,9 @@ public function register() { $targets = $this->targets; + // Allow for "fully qualified" true/false/null. + $targets[] = T_NAME_FULLY_QUALIFIED; + // Register scope modifiers to filter out property type declarations. $targets += Tokens::SCOPE_MODIFIERS; $targets[] = T_VAR; @@ -97,6 +98,13 @@ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); + // If this is a fully qualified name, check if it is FQN true/false/null. + if ($tokens[$stackPtr]['code'] === T_NAME_FULLY_QUALIFIED + && $this->isFQNTrueFalseNull($phpcsFile, $stackPtr) === false + ) { + return; + } + // Skip over potential type declarations for constants. if ($tokens[$stackPtr]['code'] === T_CONST) { // Constant must always have a value assigned to it, so we can just look for the assignment @@ -180,7 +188,10 @@ public function process(File $phpcsFile, $stackPtr) } for ($i = $param['default_token']; $i < $paramEnd; $i++) { - if (isset($this->targets[$tokens[$i]['code']]) === true) { + if (isset($this->targets[$tokens[$i]['code']]) === true + || ($tokens[$i]['code'] === T_NAME_FULLY_QUALIFIED + && $this->isFQNTrueFalseNull($phpcsFile, $i) === true) + ) { $this->processConstant($phpcsFile, $i); } } @@ -196,6 +207,28 @@ public function process(File $phpcsFile, $stackPtr) }//end process() + /** + * Check if a fully qualified name is a fully qualified true/false/null. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the T_NAME_FULLY_QUALIFIED token in the + * stack passed in $tokens. + * + * @return bool + */ + protected function isFQNTrueFalseNull(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + // Check for fully qualified true/false/null only. + $compareReadyKeyword = strtolower($tokens[$stackPtr]['content']); + return ($compareReadyKeyword === '\true' + || $compareReadyKeyword === '\false' + || $compareReadyKeyword === '\null'); + + }//end isFQNTrueFalseNull() + + /** * Processes a non-type declaration constant. * diff --git a/src/Standards/Generic/Tests/CodeAnalysis/UnconditionalIfStatementUnitTest.1.inc b/src/Standards/Generic/Tests/CodeAnalysis/UnconditionalIfStatementUnitTest.1.inc index 58b604f738..dfeed37d70 100644 --- a/src/Standards/Generic/Tests/CodeAnalysis/UnconditionalIfStatementUnitTest.1.inc +++ b/src/Standards/Generic/Tests/CodeAnalysis/UnconditionalIfStatementUnitTest.1.inc @@ -11,3 +11,8 @@ if (true) { if (file_exists(__FILE__) === true) { } + +// Check handling of case and FQN state. +if (\true) { +} else if (\FALSE) { +} diff --git a/src/Standards/Generic/Tests/CodeAnalysis/UnconditionalIfStatementUnitTest.php b/src/Standards/Generic/Tests/CodeAnalysis/UnconditionalIfStatementUnitTest.php index 5b4805ff8f..932f435fbf 100644 --- a/src/Standards/Generic/Tests/CodeAnalysis/UnconditionalIfStatementUnitTest.php +++ b/src/Standards/Generic/Tests/CodeAnalysis/UnconditionalIfStatementUnitTest.php @@ -50,9 +50,11 @@ public function getWarningList($testFile='') switch ($testFile) { case 'UnconditionalIfStatementUnitTest.1.inc': return [ - 3 => 1, - 5 => 1, - 7 => 1, + 3 => 1, + 5 => 1, + 7 => 1, + 16 => 1, + 17 => 1, ]; default: diff --git a/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.inc b/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.inc index b9cad4d3ea..4457268a88 100644 --- a/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.inc +++ b/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.inc @@ -7,8 +7,8 @@ if ($value == true) {} if (true === $value) {} if (true == $value) {} -if($value === true){} -if($value == true){} +if($value === false){} +if($value == false){} if(false === $value){} if(!false == $value || true !== $value){} @@ -139,8 +139,8 @@ if (is_array($val) && array('foo', 'bar') === array($foo, $bar) && ['foo', 'bar'] === [$foo, $bar] && array('foo' => true, 'bar' => false) === array(getContents()) - && ['foo' => true, 'bar' => false] === array(getContents()) - && array(getContents()) === ['foo' => true, 'bar' => false] + && ['foo' => true, 'bar' => \false, 'baz' => null] === array(getContents()) + && array(getContents()) === ['foo' => \true, 'bar' => false, 'baz' => \null] ) { } @@ -192,3 +192,14 @@ if(Partially\qualified('foo') === 10){} if(1.5 === Partially\qualified(true)){} if(namespace\relative(false) === null){} if('string' === namespace\relative(null)){} + +// Handle FQN true/false/null the same as plain true/false/null. +if ($value === \true) {} +if (\true === $value) {} + +if($value == \FALSE){} +if(\FALSE === $value){} +if(!\false == $value || true !== $value){} + +if($value === \Null){} +if(\Null == $value){} diff --git a/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.php b/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.php index 510bd7d52e..ff5761fae2 100644 --- a/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.php +++ b/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.php @@ -75,6 +75,10 @@ public function getErrorList() 190 => 1, 192 => 1, 194 => 1, + 198 => 1, + 201 => 1, + 202 => 2, + 205 => 1, ]; }//end getErrorList() diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.1.inc b/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.1.inc index a6a75a7e87..752cd345e5 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.1.inc +++ b/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.1.inc @@ -160,3 +160,7 @@ class SkipOverPHP84FinalProperties { final MyType|FALSE $propA; private static final NULL|MyClass $propB; } + +$a = \NULL; +$a = \falSe; +$a = \True; diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.1.inc.fixed b/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.1.inc.fixed index 2cc52294cd..b44ae194e3 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.1.inc.fixed +++ b/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.1.inc.fixed @@ -160,3 +160,7 @@ class SkipOverPHP84FinalProperties { final MyType|FALSE $propA; private static final NULL|MyClass $propB; } + +$a = \null; +$a = \false; +$a = \true; diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.php b/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.php index d225e72efa..3ea63ef1a7 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.php +++ b/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.php @@ -66,6 +66,9 @@ public function getErrorList($testFile='') 129 => 1, 149 => 1, 153 => 1, + 164 => 1, + 165 => 1, + 166 => 1, ]; default: diff --git a/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc b/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc index 7d81893c66..6e02a1bec7 100644 --- a/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc +++ b/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc @@ -105,3 +105,7 @@ class SkipOverPHP84FinalProperties { final MyType|false $propA; private static final null|MyClass $propB; } + +$a = \null; +$a = \falSe; +$a = \True; diff --git a/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc.fixed b/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc.fixed index 26e20f290e..922efce458 100644 --- a/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc.fixed @@ -105,3 +105,7 @@ class SkipOverPHP84FinalProperties { final MyType|false $propA; private static final null|MyClass $propB; } + +$a = \NULL; +$a = \FALSE; +$a = \TRUE; diff --git a/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.php b/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.php index 6a90c1805e..e9f1716da2 100644 --- a/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.php +++ b/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.php @@ -31,27 +31,30 @@ final class UpperCaseConstantUnitTest extends AbstractSniffTestCase public function getErrorList() { return [ - 7 => 1, - 10 => 1, - 15 => 1, - 16 => 1, - 23 => 1, - 26 => 1, - 31 => 1, - 32 => 1, - 39 => 1, - 42 => 1, - 47 => 1, - 48 => 1, - 70 => 1, - 71 => 1, - 85 => 1, - 87 => 1, - 88 => 1, - 90 => 2, - 92 => 2, - 93 => 1, - 98 => 2, + 7 => 1, + 10 => 1, + 15 => 1, + 16 => 1, + 23 => 1, + 26 => 1, + 31 => 1, + 32 => 1, + 39 => 1, + 42 => 1, + 47 => 1, + 48 => 1, + 70 => 1, + 71 => 1, + 85 => 1, + 87 => 1, + 88 => 1, + 90 => 2, + 92 => 2, + 93 => 1, + 98 => 2, + 109 => 1, + 110 => 1, + 111 => 1, ]; }//end getErrorList() diff --git a/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc b/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc index d0a12f4697..b0296dfcc4 100644 --- a/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc +++ b/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc @@ -93,3 +93,6 @@ function fooH(): ? // Fatal error: null cannot be marked as nullable, but that's not the concern of this sniff. function fooI(): ? null {} + +// Ensure nullable FQN true/false are handled correctly. +function fqnTrueFalseNull(? \FALSE $paramA, ? \Null $paramB) : ? \true {} diff --git a/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc.fixed b/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc.fixed index a8ffe2136c..68a21dfc86 100644 --- a/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc.fixed +++ b/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc.fixed @@ -90,3 +90,6 @@ function fooH(): ?false {} // Fatal error: null cannot be marked as nullable, but that's not the concern of this sniff. function fooI(): ?null {} + +// Ensure nullable FQN true/false are handled correctly. +function fqnTrueFalseNull(?\FALSE $paramA, ?\Null $paramB) : ?\true {} diff --git a/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.php b/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.php index 6b15a963c6..416d79fb3a 100644 --- a/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.php +++ b/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.php @@ -49,6 +49,7 @@ protected function getErrorList() 90 => 1, 91 => 1, 95 => 1, + 98 => 3, ]; }//end getErrorList() diff --git a/src/Standards/Squiz/Sniffs/Operators/ComparisonOperatorUsageSniff.php b/src/Standards/Squiz/Sniffs/Operators/ComparisonOperatorUsageSniff.php index 346b9a6517..ed36226762 100644 --- a/src/Standards/Squiz/Sniffs/Operators/ComparisonOperatorUsageSniff.php +++ b/src/Standards/Squiz/Sniffs/Operators/ComparisonOperatorUsageSniff.php @@ -174,6 +174,13 @@ public function process(File $phpcsFile, $stackPtr) $foundBooleans++; } + if ($tokens[$i]['code'] === T_NAME_FULLY_QUALIFIED) { + $compareReadyKeyword = strtolower($tokens[$i]['content']); + if ($compareReadyKeyword === '\true' || $compareReadyKeyword === '\false') { + $foundBooleans++; + } + } + if ($tokens[$i]['code'] === T_BOOLEAN_AND || $tokens[$i]['code'] === T_BOOLEAN_OR ) { diff --git a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc index 08b15e2c44..3889d9d35c 100644 --- a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc @@ -499,3 +499,21 @@ class AllowMoreForSelectivelyIgnoringDisallowedTags { } // phpcs:enable Squiz.Commenting.VariableComment + +class StandaloneFQNNullTrueFalseTypes +{ + /** + * @var null + */ + public \Null $variableName = null; + + /** + * @var true + */ + protected \true $variableName = true; + + /** + * @var false + */ + private \FALSE $variableName = false; +} diff --git a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc.fixed index 36fe4688cb..de2b109f6b 100644 --- a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc.fixed @@ -499,3 +499,21 @@ class AllowMoreForSelectivelyIgnoringDisallowedTags { } // phpcs:enable Squiz.Commenting.VariableComment + +class StandaloneFQNNullTrueFalseTypes +{ + /** + * @var null + */ + public \Null $variableName = null; + + /** + * @var true + */ + protected \true $variableName = true; + + /** + * @var false + */ + private \FALSE $variableName = false; +} diff --git a/src/Standards/Squiz/Tests/Operators/ComparisonOperatorUsageUnitTest.inc b/src/Standards/Squiz/Tests/Operators/ComparisonOperatorUsageUnitTest.inc index 49084540d7..1e18526738 100644 --- a/src/Standards/Squiz/Tests/Operators/ComparisonOperatorUsageUnitTest.inc +++ b/src/Standards/Squiz/Tests/Operators/ComparisonOperatorUsageUnitTest.inc @@ -146,3 +146,9 @@ if (unqualified($argTags > 0)) {} if (Partially\qualified($argTags > 0)) {} if (\Fully\qualified($argTags > 0)) {} if (namespace\relative($argTags > 0)) {} + +// Verify that FQN true/false are handled the same as unqualified. +if (true) {} +if (\true) {} +for ($var1 = 10; FALSE; $var1--) {} +for ($var1 = 10; \FALSE; $var1--) {} diff --git a/tests/Core/Tokenizers/PHP/BackfillFnTokenTest.inc b/tests/Core/Tokenizers/PHP/BackfillFnTokenTest.inc index e6d17388be..d2fa116310 100644 --- a/tests/Core/Tokenizers/PHP/BackfillFnTokenTest.inc +++ b/tests/Core/Tokenizers/PHP/BackfillFnTokenTest.inc @@ -107,6 +107,15 @@ fn(array $a) : True => $a; /* testNullReturnType */ fn(array $a) : null => $a; +/* testFQNFalseReturnType */ +fn(array $a) : \FALSE => $a; + +/* testFQNTrueReturnType */ +fn(array $a) : \true => $a; + +/* testFQNNullReturnType */ +fn(array $a) : \Null => $a; + /* testUnionParamType */ $arrowWithUnionParam = fn(int|float $param) : SomeClass => new SomeClass($param); diff --git a/tests/Core/Tokenizers/PHP/BackfillFnTokenTest.php b/tests/Core/Tokenizers/PHP/BackfillFnTokenTest.php index cbe3cfd42f..3501c3c87d 100644 --- a/tests/Core/Tokenizers/PHP/BackfillFnTokenTest.php +++ b/tests/Core/Tokenizers/PHP/BackfillFnTokenTest.php @@ -455,30 +455,39 @@ public function testKeywordReturnTypes($testMarker) public static function dataKeywordReturnTypes() { return [ - 'self' => [ + 'self' => [ 'testMarker' => '/* testSelfReturnType */', ], - 'parent' => [ + 'parent' => [ 'testMarker' => '/* testParentReturnType */', ], - 'callable' => [ + 'callable' => [ 'testMarker' => '/* testCallableReturnType */', ], - 'array' => [ + 'array' => [ 'testMarker' => '/* testArrayReturnType */', ], - 'static' => [ + 'static' => [ 'testMarker' => '/* testStaticReturnType */', ], - 'false' => [ + 'false' => [ 'testMarker' => '/* testFalseReturnType */', ], - 'true' => [ + 'true' => [ 'testMarker' => '/* testTrueReturnType */', ], - 'null' => [ + 'null' => [ 'testMarker' => '/* testNullReturnType */', ], + 'FQN false' => [ + 'testMarker' => '/* testFQNFalseReturnType */', + ], + 'FQN true' => [ + 'testMarker' => '/* testFQNTrueReturnType */', + ], + 'FQN null' => [ + 'testMarker' => '/* testFQNNullReturnType */', + ], ]; }//end dataKeywordReturnTypes() diff --git a/tests/Core/Tokenizers/PHP/BitwiseOrTest.inc b/tests/Core/Tokenizers/PHP/BitwiseOrTest.inc index c2c4508e43..d733fb402b 100644 --- a/tests/Core/Tokenizers/PHP/BitwiseOrTest.inc +++ b/tests/Core/Tokenizers/PHP/BitwiseOrTest.inc @@ -187,6 +187,15 @@ function trueTypeReturn($param): array|true|null {} /* testTypeUnionPHP82TrueLast */ $closure = function ($param): array|true {} +/* testTypeUnionFQNTrue */ +function FQNTrueTypeParam(\True|null $param) {} + +/* testTypeUnionFQNFalse */ +function FQNFalseTypeReturn($param): array|\false|null {} + +/* testTypeUnionFQNNull */ +$closure = function ($param): array|\NULL {}; + /* testLiveCoding */ // Intentional parse error. This has to be the last test in the file. return function( type| diff --git a/tests/Core/Tokenizers/PHP/BitwiseOrTest.php b/tests/Core/Tokenizers/PHP/BitwiseOrTest.php index def661648c..35ce86df7d 100644 --- a/tests/Core/Tokenizers/PHP/BitwiseOrTest.php +++ b/tests/Core/Tokenizers/PHP/BitwiseOrTest.php @@ -155,6 +155,9 @@ public static function dataTypeUnion() 'type for function param with true type first' => ['/* testTypeUnionPHP82TrueFirst */'], 'return type for function with true type middle' => ['/* testTypeUnionPHP82TrueMiddle */'], 'return type for closure with true type last' => ['/* testTypeUnionPHP82TrueLast */'], + 'type for function param with FQN true' => ['/* testTypeUnionFQNTrue */'], + 'return type for function with FQN false' => ['/* testTypeUnionFQNFalse */'], + 'return type for closure with FQN null' => ['/* testTypeUnionFQNNull */'], ]; }//end dataTypeUnion() diff --git a/tests/Core/Tokenizers/PHP/DNFTypesTest.inc b/tests/Core/Tokenizers/PHP/DNFTypesTest.inc index c1a38c79e5..6a0078b74c 100644 --- a/tests/Core/Tokenizers/PHP/DNFTypesTest.inc +++ b/tests/Core/Tokenizers/PHP/DNFTypesTest.inc @@ -225,6 +225,12 @@ abstract class DNFTypes { // Illegal type: segments which are strict subsets of others are disallowed, but that's not the concern of the tokenizer. public function identifierNamesReturnFQ( ) /* testDNFTypeReturnFullyQualified */ : (\Fully\Qualified\NameA&\Fully\Qualified\NameB)|\Fully\Qualified\NameB {} + + public function FQNTrueTypeParam(/* testDNFTypeFQNTrue */ \True|(A&D) $param) {} + + public function FQNFalseTypeReturn($param) /* testDNFTypeFQNFalse */ : (A&D)|\false|null { + $closure = function ($param): /* testDNFTypeFQNNull */ (A&D)|\NULL {}; + } } function globalFunctionWithSpreadAndReference( diff --git a/tests/Core/Tokenizers/PHP/DNFTypesTest.php b/tests/Core/Tokenizers/PHP/DNFTypesTest.php index 1e0a78f2b2..1670151f7a 100644 --- a/tests/Core/Tokenizers/PHP/DNFTypesTest.php +++ b/tests/Core/Tokenizers/PHP/DNFTypesTest.php @@ -496,6 +496,15 @@ public static function dataDNFTypeParentheses() 'OO method return type: fully qualified classes' => [ 'testMarker' => '/* testDNFTypeReturnFullyQualified */', ], + 'OO method param type: fully qualified true' => [ + 'testMarker' => '/* testDNFTypeFQNTrue */', + ], + 'OO method return type: fully qualified false' => [ + 'testMarker' => '/* testDNFTypeFQNFalse */', + ], + 'closure return type: fully qualified null' => [ + 'testMarker' => '/* testDNFTypeFQNNull */', + ], 'function param type: with reference' => [ 'testMarker' => '/* testDNFTypeWithReference */', ], diff --git a/tests/Core/Tokenizers/PHP/NamespacedNameSingleTokenTest.inc b/tests/Core/Tokenizers/PHP/NamespacedNameSingleTokenTest.inc index 886b6ed04e..97945a3b13 100644 --- a/tests/Core/Tokenizers/PHP/NamespacedNameSingleTokenTest.inc +++ b/tests/Core/Tokenizers/PHP/NamespacedNameSingleTokenTest.inc @@ -133,6 +133,15 @@ class MyClass /* testInstanceOfPartiallyQualified */ $is = $obj instanceof Partially\ClassName; + + /* testFullyQualifiedNull */ + $a = \NULL; + + /* testFullyQualifiedFalse */ + $a = \false; + + /* testFullyQualifiedTrue */ + $a = \True; } } diff --git a/tests/Core/Tokenizers/PHP/NamespacedNameSingleTokenTest.php b/tests/Core/Tokenizers/PHP/NamespacedNameSingleTokenTest.php index 575da62f00..f2ef8fb155 100644 --- a/tests/Core/Tokenizers/PHP/NamespacedNameSingleTokenTest.php +++ b/tests/Core/Tokenizers/PHP/NamespacedNameSingleTokenTest.php @@ -868,6 +868,45 @@ public static function dataIdentifierTokenization() ], ], ], + 'fully qualified "null"' => [ + 'testMarker' => '/* testFullyQualifiedNull */', + 'expectedTokens' => [ + [ + 'type' => 'T_NAME_FULLY_QUALIFIED', + 'content' => '\NULL', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + 'fully qualified "false"' => [ + 'testMarker' => '/* testFullyQualifiedFalse */', + 'expectedTokens' => [ + [ + 'type' => 'T_NAME_FULLY_QUALIFIED', + 'content' => '\false', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + 'fully qualified "true"' => [ + 'testMarker' => '/* testFullyQualifiedTrue */', + 'expectedTokens' => [ + [ + 'type' => 'T_NAME_FULLY_QUALIFIED', + 'content' => '\True', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], 'function call, namespace relative, with whitespace (invalid in PHP 8)' => [ 'testMarker' => '/* testInvalidInPHP8Whitespace */', 'expectedTokens' => [ diff --git a/tests/Core/Tokenizers/PHP/TypeIntersectionTest.inc b/tests/Core/Tokenizers/PHP/TypeIntersectionTest.inc index fadc0df85a..13f90863fe 100644 --- a/tests/Core/Tokenizers/PHP/TypeIntersectionTest.inc +++ b/tests/Core/Tokenizers/PHP/TypeIntersectionTest.inc @@ -162,6 +162,15 @@ function &fn(/* testTypeIntersectionNonArrowFunctionDeclaration */ Foo&Bar $some /* testTypeIntersectionWithInvalidTypes */ function (int&string $var) {}; +/* testTypeIntersectionInvalidFQNTrue */ +function FQNTrueTypeParam(\True&MyClass $param) {} + +/* testTypeIntersectionInvalidFQNFalse */ +function FQNFalseTypeReturn($param): \SomeOtherClass&\false&MyClass {} + +/* testTypeIntersectionInvalidFQNNull */ +$closure = function ($param): MyClass&\NULL {}; + /* testLiveCoding */ // Intentional parse error. This has to be the last test in the file. return function( Foo& diff --git a/tests/Core/Tokenizers/PHP/TypeIntersectionTest.php b/tests/Core/Tokenizers/PHP/TypeIntersectionTest.php index b191d7ebd4..eaed941c08 100644 --- a/tests/Core/Tokenizers/PHP/TypeIntersectionTest.php +++ b/tests/Core/Tokenizers/PHP/TypeIntersectionTest.php @@ -151,6 +151,9 @@ public static function dataTypeIntersection() 'return type for arrow function' => ['/* testTypeIntersectionArrowReturnType */'], 'type for function parameter, return by ref' => ['/* testTypeIntersectionNonArrowFunctionDeclaration */'], 'type for function parameter with invalid types' => ['/* testTypeIntersectionWithInvalidTypes */'], + 'type for method parameter, includes (invalid) FQN true' => ['/* testTypeIntersectionInvalidFQNTrue */'], + 'return type for method, includes (invalid) FQN false' => ['/* testTypeIntersectionInvalidFQNFalse */'], + 'return type for closure, includes (invalid) FQN null' => ['/* testTypeIntersectionInvalidFQNNull */'], ]; }//end dataTypeIntersection()