diff --git a/src/Standards/Generic/Sniffs/CodeAnalysis/UnconditionalIfStatementSniff.php b/src/Standards/Generic/Sniffs/CodeAnalysis/UnconditionalIfStatementSniff.php index db71a9676b..99223dadbc 100644 --- a/src/Standards/Generic/Sniffs/CodeAnalysis/UnconditionalIfStatementSniff.php +++ b/src/Standards/Generic/Sniffs/CodeAnalysis/UnconditionalIfStatementSniff.php @@ -75,7 +75,7 @@ public function process(File $phpcsFile, $stackPtr) for (; $next <= $end; ++$next) { $code = $tokens[$next]['code']; - if (isset(Tokens::$emptyTokens[$code]) === true) { + if (isset(Tokens::$emptyTokens[$code]) === true || $code === T_NS_SEPARATOR) { continue; } 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 17e81850cb..4998739233 100644 --- a/src/Standards/Generic/Sniffs/ControlStructures/DisallowYodaConditionsSniff.php +++ b/src/Standards/Generic/Sniffs/ControlStructures/DisallowYodaConditionsSniff.php @@ -164,6 +164,8 @@ public function isArrayStatic(File $phpcsFile, $arrayToken) T_COMMA => T_COMMA, T_TRUE => T_TRUE, T_FALSE => T_FALSE, + T_NULL => T_NULL, + T_NS_SEPARATOR => T_NS_SEPARATOR, ]; for ($i = ($start + 1); $i < $end; $i++) { 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 d7f2e7e4b0..32424ee19e 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 27053c4d65..57378df468 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] ) { } @@ -185,3 +185,14 @@ echo match ($text) { 1 >= $value; 1 <= $value; 1 <=> $value; + +// 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 64a487d512..5afa5e89b0 100644 --- a/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.php +++ b/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.php @@ -72,6 +72,10 @@ public function getErrorList() 185 => 1, 186 => 1, 187 => 1, + 191 => 1, + 194 => 1, + 195 => 2, + 198 => 1, ]; }//end getErrorList() diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.1.inc b/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.1.inc index e31f86208f..699c615107 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.1.inc +++ b/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.1.inc @@ -177,3 +177,7 @@ abstract class SkipOverPHP84AbstractProperties { abstract MyType|TRUE $propA {get;} protected abstract NULL|MyClass $propB {set;} } + +$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 bdb7e215ee..883daf883a 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.1.inc.fixed +++ b/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.1.inc.fixed @@ -177,3 +177,7 @@ abstract class SkipOverPHP84AbstractProperties { abstract MyType|TRUE $propA {get;} protected abstract NULL|MyClass $propB {set;} } + +$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 e4a6b80f79..1bff8da475 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.php +++ b/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.php @@ -70,6 +70,9 @@ public function getErrorList($testFile='') 169 => 1, 171 => 1, 173 => 1, + 181 => 1, + 182 => 1, + 183 => 1, ]; case 'LowerCaseConstantUnitTest.js': diff --git a/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc b/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc index 9004e02454..02b56d65a5 100644 --- a/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc +++ b/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc @@ -118,3 +118,7 @@ abstract class SkipOverPHP84AbstractProperties { abstract MyType|true $propA {get;} protected abstract null|MyClass $propB {set;} } + +$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 6fca3c63e0..146e8a0c9a 100644 --- a/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc.fixed @@ -118,3 +118,7 @@ abstract class SkipOverPHP84AbstractProperties { abstract MyType|true $propA {get;} protected abstract null|MyClass $propB {set;} } + +$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 7738b94d79..c1bb909b5c 100644 --- a/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.php +++ b/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.php @@ -55,6 +55,9 @@ public function getErrorList() 112 => 1, 113 => 1, 114 => 1, + 122 => 1, + 123 => 1, + 124 => 1, ]; }//end getErrorList() diff --git a/src/Standards/PEAR/Sniffs/Functions/ValidDefaultValueSniff.php b/src/Standards/PEAR/Sniffs/Functions/ValidDefaultValueSniff.php index 9952daa44d..34643acbee 100644 --- a/src/Standards/PEAR/Sniffs/Functions/ValidDefaultValueSniff.php +++ b/src/Standards/PEAR/Sniffs/Functions/ValidDefaultValueSniff.php @@ -54,11 +54,14 @@ public function process(File $phpcsFile, $stackPtr) } if (array_key_exists('default', $param) === true) { - $defaultFound = true; + $defaultFound = true; + $defaultValueLc = strtolower($param['default']); // Check if the arg is type hinted and using NULL for the default. // This does not make the argument optional - it just allows NULL // to be passed in. - if ($param['type_hint'] !== '' && strtolower($param['default']) === 'null') { + if ($param['type_hint'] !== '' + && ($defaultValueLc === 'null' || $defaultValueLc === '\null') + ) { $defaultFound = false; } diff --git a/src/Standards/PEAR/Tests/Functions/ValidDefaultValueUnitTest.1.inc b/src/Standards/PEAR/Tests/Functions/ValidDefaultValueUnitTest.1.inc index f134c16e01..587e70cfe5 100644 --- a/src/Standards/PEAR/Tests/Functions/ValidDefaultValueUnitTest.1.inc +++ b/src/Standards/PEAR/Tests/Functions/ValidDefaultValueUnitTest.1.inc @@ -114,3 +114,7 @@ class ConstructorPropertyPromotionMixedWithNormalParams { mixed $requiredParam, ) {} } + +// Safeguard correct handling of FQN null as default value. +function foo(Foo $foo = \null, $bar) {} +function foo(Foo $foo = \null, Foz $foz = \NULL, $bar = true, $baz) {} diff --git a/src/Standards/PEAR/Tests/Functions/ValidDefaultValueUnitTest.php b/src/Standards/PEAR/Tests/Functions/ValidDefaultValueUnitTest.php index abc2e19d7a..fdbb0662ca 100644 --- a/src/Standards/PEAR/Tests/Functions/ValidDefaultValueUnitTest.php +++ b/src/Standards/PEAR/Tests/Functions/ValidDefaultValueUnitTest.php @@ -46,6 +46,7 @@ public function getErrorList($testFile='') 101 => 1, 106 => 1, 114 => 1, + 120 => 1, ]; default: diff --git a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php index 4c0d6bcb7e..f1b9044f10 100644 --- a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php @@ -67,6 +67,11 @@ public function process(File $phpcsFile, $stackPtr) $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); + // Allow for PHP 8.4+ fully qualified use of exit/die. + if ($tokens[$stackPtr]['code'] === \T_EXIT && $tokens[$prev]['code'] === \T_NS_SEPARATOR) { + $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prev - 1), null, true); + } + // Tokens which can be used in inline expressions need special handling. if (isset($this->expressionTokens[$tokens[$stackPtr]['code']]) === true) { // If this token is preceded by a logical operator, it only relates to one line diff --git a/src/Standards/Squiz/Tests/Operators/ComparisonOperatorUsageUnitTest.inc b/src/Standards/Squiz/Tests/Operators/ComparisonOperatorUsageUnitTest.inc index 8522438dcf..60d426e3d6 100644 --- a/src/Standards/Squiz/Tests/Operators/ComparisonOperatorUsageUnitTest.inc +++ b/src/Standards/Squiz/Tests/Operators/ComparisonOperatorUsageUnitTest.inc @@ -136,3 +136,9 @@ if (empty($argTags > 0)) { } myFunction($var1 === true ? "" : "foobar"); + +// 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/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc index 4b1d1ca650..87a1409bf5 100644 --- a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc @@ -311,7 +311,7 @@ function parseError2() { // All logical operators are allowed with inline expressions (but this was not correctly handled by the sniff). function exitExpressionsWithLogicalOperators() { $condition = false; - $condition || exit(); + $condition || \exit(); $condition or die(); $condition = true; @@ -327,7 +327,7 @@ function exitExpressionsWithLogicalOperators() { function exitExpressionsInTernary() { $value = $myValue ? $myValue : exit(); $value = $myValue ?: exit(); - $value = $var == 'foo' ? 'bar' : die( 'world' ); + $value = $var == 'foo' ? 'bar' : \die( 'world' ); $value = (!$myValue ) ? exit() : $myValue; $value = $var != 'foo' ? die( 'world' ) : 'bar'; @@ -418,3 +418,17 @@ $closure = function () echo 'foo'; return; // This return should be flagged as not required. }; + +function fqnExitWithCodeAfter() { + do_something(); + exit(); + do_something_else(); +} + +function fqnExitInControlStructureWithCodeAfter() { + if(do_something()) { + die(); + do_something_else(); + } + do_something_else(); +} diff --git a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php index 4b8e0f074f..8cab9d048b 100644 --- a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php +++ b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php @@ -90,6 +90,8 @@ public function getWarningList($testFile='') 406 => 1, 412 => 1, 419 => 1, + 425 => 1, + 431 => 1, ]; case 'NonExecutableCodeUnitTest.2.inc': diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index a00c918ddf..dea4438575 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -694,6 +694,25 @@ protected function tokenize($string) break; } } + + // Fully Qualified `\exit`/`\die` should be preserved. + if ($token[0] === T_EXIT + && $finalTokens[$lastNotEmptyToken]['code'] === T_NS_SEPARATOR + ) { + for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) { + if (isset(Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) { + continue; + } + + if ($finalTokens[$i]['code'] !== T_STRING + && $finalTokens[$i]['code'] !== T_NAMESPACE + ) { + $preserveKeyword = true; + } + + break; + } + } }//end if // Types in typed constants should not be touched, but the constant name should be. @@ -1355,6 +1374,38 @@ protected function tokenize($string) $name = substr($name, 10); } + // Special case keywords which can be used in fully qualified form. + if ($token[0] === T_NAME_FULLY_QUALIFIED) { + $specialCasedType = null; + $nameLc = strtolower($name); + if ($nameLc === 'exit' || $nameLc === 'die') { + $specialCasedType = 'T_EXIT'; + } else if ($nameLc === 'true') { + $specialCasedType = 'T_TRUE'; + } else if ($nameLc === 'false') { + $specialCasedType = 'T_FALSE'; + } else if ($nameLc === 'null') { + $specialCasedType = 'T_NULL'; + } + + if ($specialCasedType !== null) { + $newToken = []; + $newToken['code'] = constant($specialCasedType); + $newToken['type'] = $specialCasedType; + $newToken['content'] = $name; + $finalTokens[$newStackPtr] = $newToken; + ++$newStackPtr; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = Tokens::tokenName($token[0]); + $content = Common::prepareForOutput($token[1]); + echo "\t\t* token $stackPtr split into individual tokens T_NS_SEPARATOR + $specialCasedType".PHP_EOL; + } + + continue; + } + }//end if + $parts = explode('\\', $name); $partCount = count($parts); $lastPart = ($partCount - 1); @@ -2523,16 +2574,37 @@ function return types. We want to keep the parenthesis map clean, } else if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true && $finalTokens[$lastNotEmptyToken]['code'] !== T_CONST ) { - $preserveTstring = true; + $preserveTstring = true; + $tokenContentLower = strtolower($token[1]); // Special case for syntax like: return new self/new parent // where self/parent should not be a string. - $tokenContentLower = strtolower($token[1]); if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW && ($tokenContentLower === 'self' || $tokenContentLower === 'parent') ) { $preserveTstring = false; } + + // Special case for fully qualified \true, \false and \null + // where true/false/null should not be a string. + // Note: if this is the _start_ of a longer namespaced name, this will undone again later. + if ($finalTokens[$lastNotEmptyToken]['code'] === T_NS_SEPARATOR + && ($tokenContentLower === 'true' || $tokenContentLower === 'false' || $tokenContentLower === 'null') + ) { + for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) { + if (isset(Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) { + continue; + } + + if ($finalTokens[$i]['code'] !== T_STRING + && $finalTokens[$i]['code'] !== T_NAMESPACE + ) { + $preserveTstring = false; + } + + break; + } + } } else if ($finalTokens[$lastNotEmptyToken]['content'] === '&') { // Function names for functions declared to return by reference. for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) { @@ -3629,6 +3701,7 @@ protected function processAdditional() } else if ($this->tokens[$i]['code'] === T_TRUE || $this->tokens[$i]['code'] === T_FALSE || $this->tokens[$i]['code'] === T_NULL + || $this->tokens[$i]['code'] === T_EXIT ) { for ($x = ($i + 1); $x < $numTokens; $x++) { if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) { diff --git a/tests/Core/Files/File/FindStartOfStatementTest.inc b/tests/Core/Files/File/FindStartOfStatementTest.inc index 5b601075a7..681289eff8 100644 --- a/tests/Core/Files/File/FindStartOfStatementTest.inc +++ b/tests/Core/Files/File/FindStartOfStatementTest.inc @@ -162,6 +162,10 @@ switch ($foo) { /* testInsideCaseGotoStatement */ goto myLabel; + case 7: + /* testInsideCaseFullyQualifiedDieStatement */ + \die(1); + /* testDefaultStatement */ default: /* testInsideDefaultContinueStatement */ diff --git a/tests/Core/Files/File/FindStartOfStatementTest.php b/tests/Core/Files/File/FindStartOfStatementTest.php index 4e0916dd00..01e0735588 100644 --- a/tests/Core/Files/File/FindStartOfStatementTest.php +++ b/tests/Core/Files/File/FindStartOfStatementTest.php @@ -669,6 +669,12 @@ public static function dataFindStartInsideSwitchCaseDefaultStatements() 'targets' => T_SEMICOLON, 'expectedTarget' => T_GOTO, ], + 'Namespace separator for "die" should be start for contents - close parenthesis' => [ + // Note: not sure if this is actually correct - should this be the open parenthesis ? + 'testMarker' => '/* testInsideCaseFullyQualifiedDieStatement */', + 'targets' => T_CLOSE_PARENTHESIS, + 'expectedTarget' => T_NS_SEPARATOR, + ], 'Default keyword should be start of default statement - default itself' => [ 'testMarker' => '/* testDefaultStatement */', 'targets' => T_DEFAULT, diff --git a/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.inc b/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.inc index e019dfe8a3..3a591f5aae 100644 --- a/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.inc +++ b/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.inc @@ -186,6 +186,11 @@ $a = isset($a); /* testUnsetIsKeyword */ unset($a); +/* testFullyQualifiedDieIsKeyword */ +\die; +/* testFullyQualifiedExitIsKeyword */ +\exit($foo); + /* testIncludeIsKeyword */ include 'file.php'; /* testIncludeOnceIsKeyword */ diff --git a/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.php b/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.php index 09ff9050f1..b2d14a9d92 100644 --- a/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.php +++ b/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.php @@ -449,6 +449,14 @@ public static function dataKeywords() 'testMarker' => '/* testUnsetIsKeyword */', 'expectedTokenType' => 'T_UNSET', ], + '\\die: statement (fully qualified)' => [ + 'testMarker' => '/* testFullyQualifiedDieIsKeyword */', + 'expectedTokenType' => 'T_EXIT', + ], + '\\exit: statement (fully qualified)' => [ + 'testMarker' => '/* testFullyQualifiedExitIsKeyword */', + 'expectedTokenType' => 'T_EXIT', + ], 'include' => [ 'testMarker' => '/* testIncludeIsKeyword */', diff --git a/tests/Core/Tokenizers/PHP/EnumCaseTest.inc b/tests/Core/Tokenizers/PHP/EnumCaseTest.inc index 13b87242e1..adcce68179 100644 --- a/tests/Core/Tokenizers/PHP/EnumCaseTest.inc +++ b/tests/Core/Tokenizers/PHP/EnumCaseTest.inc @@ -92,4 +92,6 @@ enum Foo: string { case DEFAULT = 'default'; /* testKeywordAsEnumCaseNameShouldBeString7 */ case ARRAY = 'array'; + /* testKeywordAsEnumCaseNameShouldBeString8 */ + case EXIT = 'exit'; } diff --git a/tests/Core/Tokenizers/PHP/EnumCaseTest.php b/tests/Core/Tokenizers/PHP/EnumCaseTest.php index 6836ea05e5..656a24c3c0 100644 --- a/tests/Core/Tokenizers/PHP/EnumCaseTest.php +++ b/tests/Core/Tokenizers/PHP/EnumCaseTest.php @@ -142,6 +142,7 @@ public static function dataKeywordAsEnumCaseNameShouldBeString() '"false" as case name' => ['/* testKeywordAsEnumCaseNameShouldBeString5 */'], '"default" as case name' => ['/* testKeywordAsEnumCaseNameShouldBeString6 */'], '"array" as case name' => ['/* testKeywordAsEnumCaseNameShouldBeString7 */'], + '"exit" as case name' => ['/* testKeywordAsEnumCaseNameShouldBeString8 */'], ]; }//end dataKeywordAsEnumCaseNameShouldBeString() diff --git a/tests/Core/Tokenizers/PHP/ExitKeywordTest.inc b/tests/Core/Tokenizers/PHP/ExitKeywordTest.inc new file mode 100644 index 0000000000..d3c1518375 --- /dev/null +++ b/tests/Core/Tokenizers/PHP/ExitKeywordTest.inc @@ -0,0 +1,106 @@ +exit; + +/* testNotDieOOPropertyAccess */ +$obj->DIE; + +/* testNotExitOOMethodCall */ +$obj->exit(); + +/* testNotDieOOMethodCall */ +$obj->die(); + +class NotReserved { + /* testNotExitOOConstDeclaration */ + const exit = 10; + + /* testNotDieOOConstDeclaration */ + const die = 'status'; + + /* testNotExitOOMethodDeclaration */ + function Exit() {} + + /* testNotDieOOMethodDeclaration */ + function die() {} +} + +/* testNotExitParamName */ +callMe(exit: 10); + +/* testNotDieParamName */ +callMe(die: 'status'); + +/* testNotExitNamespacedName */ +use My\exit\NameA; + +/* testNotDieNamespacedName */ +use My\die\NameB; + +/* testExitAsFQConstant */ +// Intentional parse error. This is not allowed in PHP, but that's not the concern of the tokenizer. Should still be handled correctly. +\exit; + +/* testDieAsFQConstant */ +// Intentional parse error. This is not allowed in PHP, but that's not the concern of the tokenizer. Should still be handled correctly. +\die; + +/* testNotExitConstantDeclaration */ +// Intentional parse error. This is not allowed in PHP, but that's not the concern of the tokenizer. Should still be handled correctly. +const exit = 10; + +/* testNotDieConstantDeclaration */ +// Intentional parse error. This is not allowed in PHP, but that's not the concern of the tokenizer. Should still be handled correctly. +const die = 'status'; + +/* testNotExitFunctionDeclaration */ +// Intentional parse error. This is not allowed in PHP, but that's not the concern of the tokenizer. Should still be handled correctly. +function exit() {} + +/* testNotDieFunctionDeclaration */ +// Intentional parse error. This is not allowed in PHP, but that's not the concern of the tokenizer. Should still be handled correctly. +function die() {} diff --git a/tests/Core/Tokenizers/PHP/ExitKeywordTest.php b/tests/Core/Tokenizers/PHP/ExitKeywordTest.php new file mode 100644 index 0000000000..f1dc7aad5b --- /dev/null +++ b/tests/Core/Tokenizers/PHP/ExitKeywordTest.php @@ -0,0 +1,229 @@ + + * @copyright 2024 PHPCSStandards and contributors + * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Tokenizers\PHP; + +use PHP_CodeSniffer\Tests\Core\Tokenizers\AbstractTokenizerTestCase; + +final class ExitKeywordTest extends AbstractTokenizerTestCase +{ + + + /** + * Test the retokenization of the `exit`/`die` keywords to T_EXIT. + * + * @param string $testMarker The comment prefacing the target token. + * @param string $testContent The token content to look for. + * + * @dataProvider dataExitIsKeyword + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testExitIsKeyword($testMarker, $testContent) + { + $tokens = $this->phpcsFile->getTokens(); + + $token = $this->getTargetToken($testMarker, [T_EXIT, T_STRING], $testContent); + $tokenArray = $tokens[$token]; + + $this->assertSame(T_EXIT, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_EXIT (code)'); + $this->assertSame('T_EXIT', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_EXIT (type)'); + + }//end testExitIsKeyword() + + + /** + * Data provider. + * + * @see testExitIsKeyword() + * + * @return array> + */ + public static function dataExitIsKeyword() + { + return [ + 'exit as constant' => [ + 'testMarker' => '/* testExitAsConstant */', + 'testContent' => 'exit', + ], + 'die as constant' => [ + 'testMarker' => '/* testDieAsConstant */', + 'testContent' => 'die', + ], + 'exit as constant; mixed case' => [ + 'testMarker' => '/* testExitAsConstantMixedCase */', + 'testContent' => 'Exit', + ], + 'die as constant; uppercase' => [ + 'testMarker' => '/* testDieAsConstantUppercase */', + 'testContent' => 'DIE', + ], + 'exit as function call; no parameters' => [ + 'testMarker' => '/* testExitAsFunctionCallNoParam */', + 'testContent' => 'exit', + ], + 'die as function call; no parameters' => [ + 'testMarker' => '/* testDieAsFunctionCallNoParam */', + 'testContent' => 'die', + ], + 'exit as function call; with parameters' => [ + 'testMarker' => '/* testExitAsFunctionCallWithParam */', + 'testContent' => 'exit', + ], + 'die as function call; with parameters' => [ + 'testMarker' => '/* testDieAsFunctionCallWithParam */', + 'testContent' => 'die', + ], + 'exit as function call; uppercase' => [ + 'testMarker' => '/* testExitAsFunctionCallUppercase */', + 'testContent' => 'EXIT', + ], + 'die as function call; mixed case' => [ + 'testMarker' => '/* testDieAsFunctionCallMixedCase */', + 'testContent' => 'dIE', + ], + 'exit as fully qualified function call; with parameters' => [ + 'testMarker' => '/* testExitAsFQFunctionCallWithParam */', + 'testContent' => 'exit', + ], + 'die as fully qualified function call; no parameters' => [ + 'testMarker' => '/* testDieAsFQFunctionCallNoParam */', + 'testContent' => 'die', + ], + 'exit as fully qualified constant (illegal)' => [ + 'testMarker' => '/* testExitAsFQConstant */', + 'testContent' => 'exit', + ], + 'die as fully qualified constant (illegal)' => [ + 'testMarker' => '/* testDieAsFQConstant */', + 'testContent' => 'die', + ], + ]; + + }//end dataExitIsKeyword() + + + /** + * Verify that the retokenization of `T_EXIT` tokens doesn't negatively impact the tokenization + * of `T_STRING` tokens with the contents "exit" or "die" which aren't in actual fact the keyword. + * + * @param string $testMarker The comment prefacing the target token. + * @param string $testContent The token content to look for. + * @param string $expected The expected token type. Defaults to `T_STRING`. + * + * @dataProvider dataNotExitKeyword + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testNotExitKeyword($testMarker, $testContent, $expected='T_STRING') + { + $tokens = $this->phpcsFile->getTokens(); + $tokenCode = constant($expected); + + $token = $this->getTargetToken($testMarker, [T_EXIT, $tokenCode], $testContent); + $tokenArray = $tokens[$token]; + + $this->assertSame($tokenCode, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not '.$expected.' (code)'); + $this->assertSame($expected, $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not '.$expected.' (type)'); + + }//end testNotExitKeyword() + + + /** + * Data provider. + * + * @see testNotExitKeyword() + * + * @return array> + */ + public static function dataNotExitKeyword() + { + return [ + 'exit not keyword: OO constant use' => [ + 'testMarker' => '/* testNotExitOOConstantAccess */', + 'testContent' => 'exit', + ], + 'die not keyword: OO constant use' => [ + 'testMarker' => '/* testNotDieOOConstantAccess */', + 'testContent' => 'die', + ], + 'exit not keyword: OO property access' => [ + 'testMarker' => '/* testNotExitOOPropertyAccess */', + 'testContent' => 'exit', + ], + 'die not keyword: OO property access' => [ + 'testMarker' => '/* testNotDieOOPropertyAccess */', + 'testContent' => 'DIE', + ], + 'exit not keyword: OO method call' => [ + 'testMarker' => '/* testNotExitOOMethodCall */', + 'testContent' => 'exit', + ], + 'die not keyword: OO method call' => [ + 'testMarker' => '/* testNotDieOOMethodCall */', + 'testContent' => 'die', + ], + 'exit not keyword: OO constant declaration' => [ + 'testMarker' => '/* testNotExitOOConstDeclaration */', + 'testContent' => 'exit', + ], + 'die not keyword: OO constant declaration' => [ + 'testMarker' => '/* testNotDieOOConstDeclaration */', + 'testContent' => 'die', + ], + 'exit not keyword: OO method declaration' => [ + 'testMarker' => '/* testNotExitOOMethodDeclaration */', + 'testContent' => 'Exit', + ], + 'die not keyword: OO method declaration' => [ + 'testMarker' => '/* testNotDieOOMethodDeclaration */', + 'testContent' => 'die', + ], + 'exit not keyword: parameter label for named param' => [ + 'testMarker' => '/* testNotExitParamName */', + 'testContent' => 'exit', + 'expected' => 'T_PARAM_NAME', + ], + 'die not keyword: parameter label for named param' => [ + 'testMarker' => '/* testNotDieParamName */', + 'testContent' => 'die', + 'expected' => 'T_PARAM_NAME', + ], + 'exit not keyword: part of a namespaced name' => [ + 'testMarker' => '/* testNotExitNamespacedName */', + 'testContent' => 'exit', + ], + 'die not keyword: part of a namespaced name' => [ + 'testMarker' => '/* testNotDieNamespacedName */', + 'testContent' => 'die', + ], + 'exit not keyword: global constant declaration (illegal)' => [ + 'testMarker' => '/* testNotExitConstantDeclaration */', + 'testContent' => 'exit', + ], + 'die not keyword: global constant declaration (illegal)' => [ + 'testMarker' => '/* testNotDieConstantDeclaration */', + 'testContent' => 'die', + ], + 'exit not keyword: global function declaration (illegal)' => [ + 'testMarker' => '/* testNotExitFunctionDeclaration */', + 'testContent' => 'exit', + ], + 'die not keyword: global function declaration (illegal)' => [ + 'testMarker' => '/* testNotDieFunctionDeclaration */', + 'testContent' => 'die', + ], + ]; + + }//end dataNotExitKeyword() + + +}//end class diff --git a/tests/Core/Tokenizers/PHP/NamedFunctionCallArgumentsTest.inc b/tests/Core/Tokenizers/PHP/NamedFunctionCallArgumentsTest.inc index 2f1d20bf08..7513ad70bc 100644 --- a/tests/Core/Tokenizers/PHP/NamedFunctionCallArgumentsTest.inc +++ b/tests/Core/Tokenizers/PHP/NamedFunctionCallArgumentsTest.inc @@ -106,6 +106,22 @@ $closure = $cond ? function() : bool {return true;} : function() : int {return 1 /* testTernaryWithArrowFunctionsAndReturnTypes */ $fn = $cond ? fn() : bool => true : fn() : int => 123; +/* testPHP84Exit */ +// Exit is a language construct, not a function prior to PHP 8.4. Prior to PHP 8.4, named params were not supported, handle it anyway. +exit(status: $value); + +/* testPHP84Die */ +// Die is a language construct, not a function prior to PHP 8.4. Prior to PHP 8.4, named params were not supported, handle it anyway. +die(status: $value); + +/* testPHP84FullyQualifiedExit */ +// Exit is a language construct, not a function prior to PHP 8.4. Prior to PHP 8.4, named params were not supported, handle it anyway. +\exit(status: $value); + +/* testPHP84FullyQualifiedDie */ +// Die is a language construct, not a function prior to PHP 8.4. Prior to PHP 8.4, named params were not supported, handle it anyway. +\die(status: $value); + /* testCompileErrorNamedBeforePositional */ // Not the concern of PHPCS. Should still be handled. @@ -131,10 +147,6 @@ test(param1:, param2:); // Parse error. Ignore. function_name($variableStoringParamName: $value); -/* testParseErrorExit */ -// Exit is a language construct, not a function. Named params not supported, handle it anyway. -exit(status: $value); - /* testParseErrorEmpty */ // Empty is a language construct, not a function. Named params not supported, handle it anyway. empty(variable: $value); diff --git a/tests/Core/Tokenizers/PHP/NamedFunctionCallArgumentsTest.php b/tests/Core/Tokenizers/PHP/NamedFunctionCallArgumentsTest.php index 768e41a98b..d5259e42c8 100644 --- a/tests/Core/Tokenizers/PHP/NamedFunctionCallArgumentsTest.php +++ b/tests/Core/Tokenizers/PHP/NamedFunctionCallArgumentsTest.php @@ -214,6 +214,30 @@ public static function dataNamedFunctionCallArguments() '_valid', ], ], + 'PHP 8.4+: named arg in exit()' => [ + 'testMarker' => '/* testPHP84Exit */', + 'parameters' => [ + 'status', + ], + ], + 'PHP 8.4+: named arg in die()' => [ + 'testMarker' => '/* testPHP84Die */', + 'parameters' => [ + 'status', + ], + ], + 'PHP 8.4+: named arg in fully qualified exit()' => [ + 'testMarker' => '/* testPHP84FullyQualifiedExit */', + 'parameters' => [ + 'status', + ], + ], + 'PHP 8.4+: named arg in fully qualified die()' => [ + 'testMarker' => '/* testPHP84FullyQualifiedDie */', + 'parameters' => [ + 'status', + ], + ], // Coding errors which should still be handled. 'invalid: named arg before positional (compile error)' => [ @@ -253,12 +277,6 @@ public static function dataNamedFunctionCallArguments() 'param2', ], ], - 'invalid: named arg in exit() (parse error)' => [ - 'testMarker' => '/* testParseErrorExit */', - 'parameters' => [ - 'status', - ], - ], 'invalid: named arg in empty() (parse error)' => [ 'testMarker' => '/* testParseErrorEmpty */', 'parameters' => [ diff --git a/tests/Core/Tokenizers/PHP/OtherContextSensitiveKeywordsTest.inc b/tests/Core/Tokenizers/PHP/OtherContextSensitiveKeywordsTest.inc index 028592f76b..9acb05a366 100644 --- a/tests/Core/Tokenizers/PHP/OtherContextSensitiveKeywordsTest.inc +++ b/tests/Core/Tokenizers/PHP/OtherContextSensitiveKeywordsTest.inc @@ -2,12 +2,12 @@ class OtherContextSensitiveKeywords { - const /* testParent */ PARENT = 'PARENT'; - const /* testSelf */ SELF = 'SELF'; + const /* testKeywordParentAsConstantNameShouldBeString */ PARENT = 'PARENT'; + const /* testKeywordSelfAsConstantNameShouldBeString */ SELF = 'SELF'; - const /* testFalse */ FALSE = 'FALSE', - const /* testTrue */ TRUE = 'TRUE', - const /* testNull */ NULL = 'NULL', + const /* testKeywordFalseAsConstantNameShouldBeString */ FALSE = 'FALSE', + const /* testKeywordTrueAsConstantNameShouldBeString */ TRUE = 'TRUE', + const /* testKeywordNullAsConstantNameShouldBeString */ NULL = 'NULL', } abstract class SomeClass @@ -245,3 +245,17 @@ class DNFTypes extends Something { ) : parent|(A&B) => $param->get(); } } + +/* testFalseIsKeywordUppercase */ +if ($a === FALSE) { +/* testTrueIsKeywordMixedCase */ +} elseif ( $b === True) { +/* testNullIsKeywordUppercase */ +} elseif ($c === NULL) {} + +/* testFullyQualifiedFalseIsKeyword */ +$a = \false; +/* testFullyQualifiedTrueIsKeyword */ +$b = \true; +/* testFullyQualifiedNullIsKeyword */ +$c = \null; diff --git a/tests/Core/Tokenizers/PHP/OtherContextSensitiveKeywordsTest.php b/tests/Core/Tokenizers/PHP/OtherContextSensitiveKeywordsTest.php index 35c8997148..12c70c9d1b 100644 --- a/tests/Core/Tokenizers/PHP/OtherContextSensitiveKeywordsTest.php +++ b/tests/Core/Tokenizers/PHP/OtherContextSensitiveKeywordsTest.php @@ -67,11 +67,11 @@ public function testStrings($testMarker) public static function dataStrings() { return [ - 'constant declaration: parent' => ['/* testParent */'], - 'constant declaration: self' => ['/* testSelf */'], - 'constant declaration: false' => ['/* testFalse */'], - 'constant declaration: true' => ['/* testTrue */'], - 'constant declaration: null' => ['/* testNull */'], + 'constant declaration: parent' => ['/* testKeywordParentAsConstantNameShouldBeString */'], + 'constant declaration: self' => ['/* testKeywordSelfAsConstantNameShouldBeString */'], + 'constant declaration: false' => ['/* testKeywordFalseAsConstantNameShouldBeString */'], + 'constant declaration: true' => ['/* testKeywordTrueAsConstantNameShouldBeString */'], + 'constant declaration: null' => ['/* testKeywordNullAsConstantNameShouldBeString */'], 'function declaration with return by ref: self' => ['/* testKeywordSelfAfterFunctionByRefShouldBeString */'], 'function declaration with return by ref: parent' => ['/* testKeywordParentAfterFunctionByRefShouldBeString */'], @@ -713,6 +713,30 @@ public static function dataKeywords() 'expectedTokenType' => 'T_PARENT', ], + 'false: in comparison, uppercase' => [ + 'testMarker' => '/* testFalseIsKeywordUppercase */', + 'expectedTokenType' => 'T_FALSE', + ], + 'true: in comparison, mixed case' => [ + 'testMarker' => '/* testTrueIsKeywordMixedCase */', + 'expectedTokenType' => 'T_TRUE', + ], + 'null: in comparison, uppercase' => [ + 'testMarker' => '/* testNullIsKeywordUppercase */', + 'expectedTokenType' => 'T_NULL', + ], + 'false: in assignment, fully qualified' => [ + 'testMarker' => '/* testFullyQualifiedFalseIsKeyword */', + 'expectedTokenType' => 'T_FALSE', + ], + 'true: in assignment, fully qualified' => [ + 'testMarker' => '/* testFullyQualifiedTrueIsKeyword */', + 'expectedTokenType' => 'T_TRUE', + ], + 'null: in assignment, fully qualified' => [ + 'testMarker' => '/* testFullyQualifiedNullIsKeyword */', + 'expectedTokenType' => 'T_NULL', + ], ]; }//end dataKeywords() diff --git a/tests/Core/Tokenizers/PHP/UndoNamespacedNameSingleTokenTest.inc b/tests/Core/Tokenizers/PHP/UndoNamespacedNameSingleTokenTest.inc index 65c551a84f..50dccbdbc7 100644 --- a/tests/Core/Tokenizers/PHP/UndoNamespacedNameSingleTokenTest.inc +++ b/tests/Core/Tokenizers/PHP/UndoNamespacedNameSingleTokenTest.inc @@ -118,7 +118,7 @@ class MyClass /* testDoubleColonPartiallyQualified */ $value = Level\ClassName::CONSTANT_NAME['key']; - + /* testInstanceOfRelative */ $is = $obj instanceof namespace\ClassName; @@ -133,6 +133,59 @@ class MyClass } } +function testHandlingExitDieTrueFalseNull() { + /* testExitInNamespacedNameStart */ + echo Exit\Name; + /* testExitInNamespacedNameMiddle */ + echo \Not\exit\Name; + /* testExitInNamespacedNameEnd */ + echo \Not\Exit; + /* testFullyQualifiedExitFunctionCall */ + \Exit(); + /* testFullyQualifiedExitConstant */ + // This is a parse error in PHP, but that's not our concern + \exit; + + /* testDieInNamespacedNameStart */ + echo \Die\Name; + /* testDieInNamespacedNameMiddle */ + echo \Not\die\Name; + /* testDieInNamespacedNameEnd */ + echo \Not\DIE; + /* testFullyQualifiedDieFunctionCall */ + \die(); + /* testFullyQualifiedDieConstant */ + // This is a parse error in PHP, but that's not our concern + \DIE; + + /* testFalseInNamespacedNameStart */ + echo False\Name; + /* testFalseInNamespacedNameMiddle */ + echo \Not\false\Name; + /* testFalseInNamespacedNameEnd */ + echo \Not\FALSE; + /* testFullyQualifiedFalse */ + echo \false; + + /* testTrueInNamespacedNameStart */ + echo \True\Name; + /* testTrueInNamespacedNameMiddle */ + echo \Not\true\Name; + /* testTrueInNamespacedNameEnd */ + echo \Not\True; + /* testFullyQualifiedTrue */ + echo \TRUE; + + /* testNullInNamespacedNameStart */ + echo Null\Name; + /* testNullInNamespacedNameMiddle */ + echo \Not\Null\Name; + /* testNullInNamespacedNameEnd */ + echo \Not\null; + /* testFullyQualifiedNull */ + echo \Null; +} + /* testInvalidInPHP8Whitespace */ namespace \ Sublevel \ function_name(); diff --git a/tests/Core/Tokenizers/PHP/UndoNamespacedNameSingleTokenTest.php b/tests/Core/Tokenizers/PHP/UndoNamespacedNameSingleTokenTest.php index 18a88fe8e9..12ead6131b 100644 --- a/tests/Core/Tokenizers/PHP/UndoNamespacedNameSingleTokenTest.php +++ b/tests/Core/Tokenizers/PHP/UndoNamespacedNameSingleTokenTest.php @@ -1172,6 +1172,529 @@ public static function dataIdentifierTokenization() ], ], ], + 'exit in partially qualified name (start)' => [ + 'testMarker' => '/* testExitInNamespacedNameStart */', + 'expectedTokens' => [ + [ + 'type' => 'T_STRING', + 'content' => 'Exit', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Name', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + 'exit in fully qualified name (middle)' => [ + 'testMarker' => '/* testExitInNamespacedNameMiddle */', + 'expectedTokens' => [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Not', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'exit', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Name', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + 'exit in fully qualified name (end)' => [ + 'testMarker' => '/* testExitInNamespacedNameEnd */', + 'expectedTokens' => [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Not', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Exit', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + 'PHP 8.4 exit as function call, fully qualified' => [ + 'testMarker' => '/* testFullyQualifiedExitFunctionCall */', + 'expectedTokens' => [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_EXIT', + 'content' => 'Exit', + ], + [ + 'type' => 'T_OPEN_PARENTHESIS', + 'content' => '(', + ], + ], + ], + 'exit as constant, fully qualified (illegal)' => [ + 'testMarker' => '/* testFullyQualifiedExitConstant */', + 'expectedTokens' => [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_EXIT', + 'content' => 'exit', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + 'die in partially qualified name (start)' => [ + 'testMarker' => '/* testDieInNamespacedNameStart */', + 'expectedTokens' => [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Die', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Name', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + 'die in fully qualified name (middle)' => [ + 'testMarker' => '/* testDieInNamespacedNameMiddle */', + 'expectedTokens' => [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Not', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'die', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Name', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + 'die in fully qualified name (end)' => [ + 'testMarker' => '/* testDieInNamespacedNameEnd */', + 'expectedTokens' => [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Not', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'DIE', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + 'PHP 8.4 die as function call, fully qualified' => [ + 'testMarker' => '/* testFullyQualifiedDieFunctionCall */', + 'expectedTokens' => [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_EXIT', + 'content' => 'die', + ], + [ + 'type' => 'T_OPEN_PARENTHESIS', + 'content' => '(', + ], + ], + ], + 'die as constant, fully qualified (illegal)' => [ + 'testMarker' => '/* testFullyQualifiedDieConstant */', + 'expectedTokens' => [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_EXIT', + 'content' => 'DIE', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + 'false in partially qualified name (start)' => [ + 'testMarker' => '/* testFalseInNamespacedNameStart */', + 'expectedTokens' => [ + [ + 'type' => 'T_STRING', + 'content' => 'False', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Name', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + 'false in fully qualified name (middle)' => [ + 'testMarker' => '/* testFalseInNamespacedNameMiddle */', + 'expectedTokens' => [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Not', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'false', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Name', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + 'false in fully qualified name (end)' => [ + 'testMarker' => '/* testFalseInNamespacedNameEnd */', + 'expectedTokens' => [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Not', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'FALSE', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + 'false, fully qualified' => [ + 'testMarker' => '/* testFullyQualifiedFalse */', + 'expectedTokens' => [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_FALSE', + 'content' => 'false', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + 'true in partially qualified name (start)' => [ + 'testMarker' => '/* testTrueInNamespacedNameStart */', + 'expectedTokens' => [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'True', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Name', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + 'true in fully qualified name (middle)' => [ + 'testMarker' => '/* testTrueInNamespacedNameMiddle */', + 'expectedTokens' => [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Not', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'true', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Name', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + 'true in fully qualified name (end)' => [ + 'testMarker' => '/* testTrueInNamespacedNameEnd */', + 'expectedTokens' => [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Not', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'True', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + 'true, fully qualified' => [ + 'testMarker' => '/* testFullyQualifiedTrue */', + 'expectedTokens' => [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_TRUE', + 'content' => 'TRUE', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + 'null in partially qualified name (start)' => [ + 'testMarker' => '/* testNullInNamespacedNameStart */', + 'expectedTokens' => [ + [ + 'type' => 'T_STRING', + 'content' => 'Null', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Name', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + 'null in fully qualified name (middle)' => [ + 'testMarker' => '/* testNullInNamespacedNameMiddle */', + 'expectedTokens' => [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Not', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Null', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Name', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + 'null in fully qualified name (end)' => [ + 'testMarker' => '/* testNullInNamespacedNameEnd */', + 'expectedTokens' => [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Not', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'null', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + 'null, fully qualified' => [ + 'testMarker' => '/* testFullyQualifiedNull */', + 'expectedTokens' => [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_NULL', + 'content' => 'Null', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + 'function call, namespace relative, with whitespace (invalid in PHP 8)' => [ 'testMarker' => '/* testInvalidInPHP8Whitespace */', 'expectedTokens' => [ diff --git a/tests/Core/Tokenizers/Tokenizer/RecurseScopeMapCaseKeywordConditionsTest.inc b/tests/Core/Tokenizers/Tokenizer/RecurseScopeMapCaseKeywordConditionsTest.inc index 5a0debcd02..c41aeddbed 100644 --- a/tests/Core/Tokenizers/Tokenizer/RecurseScopeMapCaseKeywordConditionsTest.inc +++ b/tests/Core/Tokenizers/Tokenizer/RecurseScopeMapCaseKeywordConditionsTest.inc @@ -95,6 +95,8 @@ enum Foo: string { case DEFAULT = 'default'; /* testKeywordAsEnumCaseNameShouldBeString7 */ case ARRAY = 'array'; + /* testKeywordAsEnumCaseNameShouldBeString8 */ + case EXIT = 'exit'; } // Test for https://github.com/squizlabs/PHP_CodeSniffer/issues/497 diff --git a/tests/Core/Tokenizers/Tokenizer/RecurseScopeMapCaseKeywordConditionsTest.php b/tests/Core/Tokenizers/Tokenizer/RecurseScopeMapCaseKeywordConditionsTest.php index ca70a34c13..2b185c5721 100644 --- a/tests/Core/Tokenizers/Tokenizer/RecurseScopeMapCaseKeywordConditionsTest.php +++ b/tests/Core/Tokenizers/Tokenizer/RecurseScopeMapCaseKeywordConditionsTest.php @@ -263,6 +263,7 @@ public static function dataKeywordAsEnumCaseNameShouldBeString() '"false" as case name' => ['/* testKeywordAsEnumCaseNameShouldBeString5 */'], '"default" as case name' => ['/* testKeywordAsEnumCaseNameShouldBeString6 */'], '"array" as case name' => ['/* testKeywordAsEnumCaseNameShouldBeString7 */'], + '"exit" as case name' => ['/* testKeywordAsEnumCaseNameShouldBeString8 */'], ]; }//end dataKeywordAsEnumCaseNameShouldBeString()