From 5cca22d2c0d8e690041dcde2a120b0cc06fbb5bd Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 6 Apr 2025 22:50:32 +0200 Subject: [PATCH 01/41] Util/Tokens: introduce new `$nameTokens` array As both tests as well as sniffs will need to refer to these tokens quite a lot, providing a predefined token array to make this more straight-forward both within PHPCS itself, as well as for external standards seems appropriate. --- src/Util/Tokens.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index 420bda4b85..4fe33227d4 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -566,6 +566,18 @@ final class Tokens T_NOWDOC => T_NOWDOC, ]; + /** + * Tokens used for "names", be it namespace, OO, function or constant names. + * + * @var array + */ + public static $nameTokens = [ + T_STRING => T_STRING, + T_NAME_QUALIFIED => T_NAME_QUALIFIED, + T_NAME_FULLY_QUALIFIED => T_NAME_FULLY_QUALIFIED, + T_NAME_RELATIVE => T_NAME_RELATIVE, + ]; + /** * Tokens that represent the names of called functions. * From e71ecf8cf7c753bb5b9741a1450caac857973970 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 7 Jul 2020 09:09:55 +0200 Subject: [PATCH 02/41] PHP 8.0 | Tokenizer/PHP: namespaced names as single token As per the proposal in squizlabs/PHP_CodeSniffer 3041. This effectively backfills the new PHP 8.0 tokenization of identifier names for all supported PHP versions in PHPCS 4.x. Note: this backfills takes into account that reserved keywords are now allowed to be used in namespaced names. For that reason no check is done of the token _type_ of the token between namespace separators, only on the contents. Includes extensive unit tests to ensure the correct re-tokenization as well as that the rest of the tokenization is not adversely affected by this change. Includes making sure that the following (re-)tokenizations are still stable: * Nullable vs ternary. --- src/Tokenizers/PHP.php | 142 ++--- src/Tokenizers/Tokenizer.php | 7 - ....inc => NamespacedNameSingleTokenTest.inc} | 17 +- ....php => NamespacedNameSingleTokenTest.php} | 549 ++++-------------- 4 files changed, 214 insertions(+), 501 deletions(-) rename tests/Core/Tokenizers/PHP/{UndoNamespacedNameSingleTokenTest.inc => NamespacedNameSingleTokenTest.inc} (90%) rename tests/Core/Tokenizers/PHP/{UndoNamespacedNameSingleTokenTest.php => NamespacedNameSingleTokenTest.php} (67%) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 64a6504a6f..65207e44c8 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -16,6 +16,13 @@ class PHP extends Tokenizer { + /** + * Regular expression to check if a given identifier name is valid for use in PHP. + * + * @var string + */ + private const PHP_LABEL_REGEX = '`^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$`'; + /** * A list of tokens that are allowed to open a scope. * @@ -1298,78 +1305,89 @@ protected function tokenize($string) }//end if /* - As of PHP 8.0 fully qualified, partially qualified and namespace relative - identifier names are tokenized differently. - This "undoes" the new tokenization so the tokenization will be the same in - in PHP 5, 7 and 8. + Before PHP 8.0, namespaced names were not tokenized as a single token. + + Note: reserved keywords are allowed within the "single token" names, so + no check is done on the token type following a namespace separator _on purpose_. + As long as it is not an empty token and the token contents complies with the + "name" requirements in PHP, we'll accept it. */ - if (PHP_VERSION_ID >= 80000 + if (PHP_VERSION_ID < 80000 && $tokenIsArray === true - && ($token[0] === T_NAME_QUALIFIED - || $token[0] === T_NAME_FULLY_QUALIFIED - || $token[0] === T_NAME_RELATIVE) + && ($token[0] === T_STRING + || $token[0] === T_NAMESPACE + || ($token[0] === T_NS_SEPARATOR + && isset($tokens[($stackPtr + 1)]) === true + && is_array($tokens[($stackPtr + 1)]) === true + && isset(Tokens::$emptyTokens[$tokens[($stackPtr + 1)][0]]) === false + && preg_match(self::PHP_LABEL_REGEX, $tokens[($stackPtr + 1)][1]) === 1)) ) { - $name = $token[1]; - - if ($token[0] === T_NAME_FULLY_QUALIFIED) { - $newToken = []; - $newToken['code'] = T_NS_SEPARATOR; - $newToken['type'] = 'T_NS_SEPARATOR'; - $newToken['content'] = '\\'; - $finalTokens[$newStackPtr] = $newToken; - ++$newStackPtr; + $nameStart = $stackPtr; + $i = $stackPtr; + $newToken = []; + $newToken['content'] = $token[1]; - $name = ltrim($name, '\\'); - } + switch ($token[0]) { + case T_STRING: + $newToken['code'] = T_NAME_QUALIFIED; + $newToken['type'] = 'T_NAME_QUALIFIED'; + break; + case T_NAMESPACE: + $newToken['code'] = T_NAME_RELATIVE; + $newToken['type'] = 'T_NAME_RELATIVE'; + break; + case T_NS_SEPARATOR: + $newToken['code'] = T_NAME_FULLY_QUALIFIED; + $newToken['type'] = 'T_NAME_FULLY_QUALIFIED'; - if ($token[0] === T_NAME_RELATIVE) { - $newToken = []; - $newToken['code'] = T_NAMESPACE; - $newToken['type'] = 'T_NAMESPACE'; - $newToken['content'] = substr($name, 0, 9); - $finalTokens[$newStackPtr] = $newToken; - ++$newStackPtr; + if (is_array($tokens[($i - 1)]) === true + && isset(Tokens::$emptyTokens[$tokens[($i - 1)][0]]) === false + && preg_match(self::PHP_LABEL_REGEX, $tokens[($i - 1)][1]) === 1 + ) { + // The namespaced name starts with a reserved keyword. Move one token back. + $newToken['code'] = T_NAME_QUALIFIED; + $newToken['type'] = 'T_NAME_QUALIFIED'; + $newToken['content'] = $tokens[($i - 1)][1]; + --$nameStart; + --$i; + break; + } - $newToken = []; - $newToken['code'] = T_NS_SEPARATOR; - $newToken['type'] = 'T_NS_SEPARATOR'; - $newToken['content'] = '\\'; - $finalTokens[$newStackPtr] = $newToken; - ++$newStackPtr; + ++$i; + $newToken['content'] .= $tokens[$i][1]; + break; + }//end switch - $name = substr($name, 10); + while (isset($tokens[($i + 1)], $tokens[($i + 2)]) === true + && is_array($tokens[($i + 1)]) === true && $tokens[($i + 1)][0] === T_NS_SEPARATOR + && is_array($tokens[($i + 2)]) === true + && isset(Tokens::$emptyTokens[$tokens[($i + 2)][0]]) === false + && preg_match(self::PHP_LABEL_REGEX, $tokens[($i + 2)][1]) === 1 + ) { + $newToken['content'] .= $tokens[($i + 1)][1].$tokens[($i + 2)][1]; + $i = ($i + 2); } - $parts = explode('\\', $name); - $partCount = count($parts); - $lastPart = ($partCount - 1); + if ($i !== $nameStart) { + if ($nameStart !== $stackPtr) { + // This must be a qualified name starting with a reserved keyword. + // We need to overwrite the previously set final token. + --$newStackPtr; + } - foreach ($parts as $i => $part) { - $newToken = []; - $newToken['code'] = T_STRING; - $newToken['type'] = 'T_STRING'; - $newToken['content'] = $part; $finalTokens[$newStackPtr] = $newToken; - ++$newStackPtr; + $newStackPtr++; + $stackPtr = $i; - if ($i !== $lastPart) { - $newToken = []; - $newToken['code'] = T_NS_SEPARATOR; - $newToken['type'] = 'T_NS_SEPARATOR'; - $newToken['content'] = '\\'; - $finalTokens[$newStackPtr] = $newToken; - ++$newStackPtr; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = $newToken['type']; + $content = $newToken['content']; + StatusWriter::write("* token $nameStart to $i ($content) retokenized to $type", 2); } - } - - if (PHP_CODESNIFFER_VERBOSITY > 1) { - $type = Tokens::tokenName($token[0]); - $content = Common::prepareForOutput($token[1]); - StatusWriter::write("* token $stackPtr split into individual tokens; was: $type => $content", 2); - } - continue; + continue; + }//end if }//end if /* @@ -2000,10 +2018,7 @@ protected function tokenize($string) continue; } - if ($tokenType === T_STRING - || $tokenType === T_NAME_FULLY_QUALIFIED - || $tokenType === T_NAME_RELATIVE - || $tokenType === T_NAME_QUALIFIED + if (isset(Tokens::$nameTokens[$tokenType]) === true || $tokenType === T_ARRAY || $tokenType === T_NAMESPACE || $tokenType === T_NS_SEPARATOR @@ -2016,10 +2031,7 @@ protected function tokenize($string) && isset($lastRelevantNonEmpty) === false) || ($lastRelevantNonEmpty === T_ARRAY && $tokenType === '(') - || (($lastRelevantNonEmpty === T_STRING - || $lastRelevantNonEmpty === T_NAME_FULLY_QUALIFIED - || $lastRelevantNonEmpty === T_NAME_RELATIVE - || $lastRelevantNonEmpty === T_NAME_QUALIFIED) + || (isset(Tokens::$nameTokens[$lastRelevantNonEmpty]) === true && ($tokenType === T_DOUBLE_COLON || $tokenType === '(' || $tokenType === ':')) diff --git a/src/Tokenizers/Tokenizer.php b/src/Tokenizers/Tokenizer.php index 95bb44a585..eb10765173 100644 --- a/src/Tokenizers/Tokenizer.php +++ b/src/Tokenizers/Tokenizer.php @@ -1073,13 +1073,6 @@ private function recurseScopeMap($stackPtr, $depth=1, &$ignore=0) continue; } - if ($tokenType === T_NAMESPACE) { - // PHP namespace keywords are special because they can be - // used as blocks but also inline as operators. - // So if we find them nested inside another opener, just skip them. - continue; - } - if ($tokenType === T_FUNCTION && $this->tokens[$stackPtr]['code'] !== T_FUNCTION ) { diff --git a/tests/Core/Tokenizers/PHP/UndoNamespacedNameSingleTokenTest.inc b/tests/Core/Tokenizers/PHP/NamespacedNameSingleTokenTest.inc similarity index 90% rename from tests/Core/Tokenizers/PHP/UndoNamespacedNameSingleTokenTest.inc rename to tests/Core/Tokenizers/PHP/NamespacedNameSingleTokenTest.inc index 65c551a84f..886b6ed04e 100644 --- a/tests/Core/Tokenizers/PHP/UndoNamespacedNameSingleTokenTest.inc +++ b/tests/Core/Tokenizers/PHP/NamespacedNameSingleTokenTest.inc @@ -6,6 +6,9 @@ namespace Package; /* testNamespaceDeclarationWithLevels */ namespace Vendor\SubLevel\Domain; +/* testNamespaceDeclarationWithReservedKeywords */ +namespace For\Include\Fn; + /* testUseStatement */ use ClassName; @@ -49,8 +52,8 @@ class MyClass \Fully\Qualified, /* testImplementsUnqualified */ Unqualified, - /* testImplementsPartiallyQualified */ - Sub\Level\Name + /* testImplementsPartiallyQualifiedWithReservedKeyword */ + Exit\Level\Name { /* testFunctionName */ public function function_name( @@ -73,8 +76,8 @@ class MyClass /* testFunctionCallRelative */ echo NameSpace\function_name(); - /* testFunctionCallFQN */ - echo \Vendor\Package\function_name(); + /* testFunctionCallFQNWithReservedKeyword */ + echo \Vendor\Foreach\function_name(); /* testFunctionCallUnqualified */ echo function_name(); @@ -118,7 +121,7 @@ class MyClass /* testDoubleColonPartiallyQualified */ $value = Level\ClassName::CONSTANT_NAME['key']; - + /* testInstanceOfRelative */ $is = $obj instanceof namespace\ClassName; @@ -145,3 +148,7 @@ $value = \Fully \Name // comment :: function_name(); + +/* testInvalidDoubleBackslash */ +// Intentional parse error. +$obj = new \\SomeClass(); diff --git a/tests/Core/Tokenizers/PHP/UndoNamespacedNameSingleTokenTest.php b/tests/Core/Tokenizers/PHP/NamespacedNameSingleTokenTest.php similarity index 67% rename from tests/Core/Tokenizers/PHP/UndoNamespacedNameSingleTokenTest.php rename to tests/Core/Tokenizers/PHP/NamespacedNameSingleTokenTest.php index 18a88fe8e9..575da62f00 100644 --- a/tests/Core/Tokenizers/PHP/UndoNamespacedNameSingleTokenTest.php +++ b/tests/Core/Tokenizers/PHP/NamespacedNameSingleTokenTest.php @@ -5,9 +5,9 @@ * As of PHP 8, identifier names are tokenized differently, depending on them being * either fully qualified, partially qualified or relative to the current namespace. * - * This test file safeguards that in PHPCS 3.x this new form of tokenization is "undone" - * and the tokenization of these identifier names is the same in all PHP versions - * based on how these names were tokenized in PHP 5/7. + * This test file safeguards that in PHPCS 4.x this new form of tokenization is correctly + * backfilled and that the tokenization of these identifier names is the same in all + * PHP versions based on how these names are tokenized in PHP 8. * * {@link https://wiki.php.net/rfc/namespaced_names_as_token} * {@link https://github.com/squizlabs/PHP_CodeSniffer/issues/3041} @@ -20,14 +20,13 @@ namespace PHP_CodeSniffer\Tests\Core\Tokenizers\PHP; use PHP_CodeSniffer\Tests\Core\Tokenizers\AbstractTokenizerTestCase; -use PHP_CodeSniffer\Util\Tokens; -final class UndoNamespacedNameSingleTokenTest extends AbstractTokenizerTestCase +final class NamespacedNameSingleTokenTest extends AbstractTokenizerTestCase { /** - * Test that identifier names are tokenized the same across PHP versions, based on the PHP 5/7 tokenization. + * Test that identifier names are tokenized the same across PHP versions, based on the PHP 8 tokenization. * * @param string $testMarker The comment prefacing the test. * @param array> $expectedTokens The tokenization expected. @@ -46,7 +45,7 @@ public function testIdentifierTokenization($testMarker, $expectedTokens) $this->assertSame( constant($tokenInfo['type']), $tokens[$identifier]['code'], - 'Token tokenized as '.Tokens::tokenName($tokens[$identifier]['code']).', not '.$tokenInfo['type'].' (code)' + 'Token tokenized as '.$tokens[$identifier]['type'].', not '.$tokenInfo['type'].' (code)' ); $this->assertSame( $tokenInfo['type'], @@ -74,6 +73,14 @@ public static function dataIdentifierTokenization() 'namespace declaration' => [ 'testMarker' => '/* testNamespaceDeclaration */', 'expectedTokens' => [ + [ + 'type' => 'T_NAMESPACE', + 'content' => 'namespace', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], [ 'type' => 'T_STRING', 'content' => 'Package', @@ -88,24 +95,37 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testNamespaceDeclarationWithLevels */', 'expectedTokens' => [ [ - 'type' => 'T_STRING', - 'content' => 'Vendor', + 'type' => 'T_NAMESPACE', + 'content' => 'namespace', ], [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', + 'type' => 'T_WHITESPACE', + 'content' => ' ', ], [ - 'type' => 'T_STRING', - 'content' => 'SubLevel', + 'type' => 'T_NAME_QUALIFIED', + 'content' => 'Vendor\SubLevel\Domain', ], [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', + 'type' => 'T_SEMICOLON', + 'content' => ';', ], + ], + ], + 'namespace declaration, uses reserved keywords in name' => [ + 'testMarker' => '/* testNamespaceDeclarationWithReservedKeywords */', + 'expectedTokens' => [ [ - 'type' => 'T_STRING', - 'content' => 'Domain', + 'type' => 'T_NAMESPACE', + 'content' => 'namespace', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_NAME_QUALIFIED', + 'content' => 'For\Include\Fn', ], [ 'type' => 'T_SEMICOLON', @@ -130,24 +150,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testUseStatementWithLevels */', 'expectedTokens' => [ [ - 'type' => 'T_STRING', - 'content' => 'Vendor', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Level', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Domain', + 'type' => 'T_NAME_QUALIFIED', + 'content' => 'Vendor\Level\Domain', ], [ 'type' => 'T_SEMICOLON', @@ -188,24 +192,8 @@ public static function dataIdentifierTokenization() 'content' => ' ', ], [ - 'type' => 'T_STRING', - 'content' => 'Vendor', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Level', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'function_in_ns', + 'type' => 'T_NAME_QUALIFIED', + 'content' => 'Vendor\Level\function_in_ns', ], [ 'type' => 'T_SEMICOLON', @@ -246,24 +234,8 @@ public static function dataIdentifierTokenization() 'content' => ' ', ], [ - 'type' => 'T_STRING', - 'content' => 'Vendor', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Level', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'OTHER_CONSTANT', + 'type' => 'T_NAME_QUALIFIED', + 'content' => 'Vendor\Level\OTHER_CONSTANT', ], [ 'type' => 'T_SEMICOLON', @@ -288,16 +260,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testMultiUsePartiallyQualified */', 'expectedTokens' => [ [ - 'type' => 'T_STRING', - 'content' => 'Sublevel', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'PartiallyClassName', + 'type' => 'T_NAME_QUALIFIED', + 'content' => 'Sublevel\PartiallyClassName', ], [ 'type' => 'T_SEMICOLON', @@ -309,16 +273,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testGroupUseStatement */', 'expectedTokens' => [ [ - 'type' => 'T_STRING', - 'content' => 'Vendor', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Level', + 'type' => 'T_NAME_QUALIFIED', + 'content' => 'Vendor\Level', ], [ 'type' => 'T_NS_SEPARATOR', @@ -405,16 +361,8 @@ public static function dataIdentifierTokenization() 'content' => ' ', ], [ - 'type' => 'T_STRING', - 'content' => 'Sub', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'YetAnotherDomain', + 'type' => 'T_NAME_QUALIFIED', + 'content' => 'Sub\YetAnotherDomain', ], [ 'type' => 'T_COMMA', @@ -438,16 +386,8 @@ public static function dataIdentifierTokenization() 'content' => ' ', ], [ - 'type' => 'T_STRING', - 'content' => 'SubLevelA', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'function_grouped_too', + 'type' => 'T_NAME_QUALIFIED', + 'content' => 'SubLevelA\function_grouped_too', ], [ 'type' => 'T_COMMA', @@ -471,16 +411,8 @@ public static function dataIdentifierTokenization() 'content' => ' ', ], [ - 'type' => 'T_STRING', - 'content' => 'SubLevelB', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'CONSTANT_GROUPED_TOO', + 'type' => 'T_NAME_QUALIFIED', + 'content' => 'SubLevelB\CONSTANT_GROUPED_TOO', ], [ 'type' => 'T_COMMA', @@ -519,28 +451,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testExtendedFQN */', 'expectedTokens' => [ [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Vendor', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Level', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'FQN', + 'type' => 'T_NAME_FULLY_QUALIFIED', + 'content' => '\Vendor\Level\FQN', ], [ 'type' => 'T_WHITESPACE', @@ -553,16 +465,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testImplementsRelative */', 'expectedTokens' => [ [ - 'type' => 'T_NAMESPACE', - 'content' => 'namespace', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Name', + 'type' => 'T_NAME_RELATIVE', + 'content' => 'namespace\Name', ], [ 'type' => 'T_COMMA', @@ -574,20 +478,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testImplementsFQN */', 'expectedTokens' => [ [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Fully', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Qualified', + 'type' => 'T_NAME_FULLY_QUALIFIED', + 'content' => '\Fully\Qualified', ], [ 'type' => 'T_COMMA', @@ -608,28 +500,12 @@ public static function dataIdentifierTokenization() ], ], ], - 'class declaration, implements partially qualified name' => [ - 'testMarker' => '/* testImplementsPartiallyQualified */', + 'class declaration, implements partially qualified name (with reserved keyword)' => [ + 'testMarker' => '/* testImplementsPartiallyQualifiedWithReservedKeyword */', 'expectedTokens' => [ [ - 'type' => 'T_STRING', - 'content' => 'Sub', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Level', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Name', + 'type' => 'T_NAME_QUALIFIED', + 'content' => 'Exit\Level\Name', ], [ 'type' => 'T_WHITESPACE', @@ -655,16 +531,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testTypeDeclarationRelative */', 'expectedTokens' => [ [ - 'type' => 'T_NAMESPACE', - 'content' => 'namespace', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Name', + 'type' => 'T_NAME_RELATIVE', + 'content' => 'namespace\Name', ], [ 'type' => 'T_TYPE_UNION', @@ -680,28 +548,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testTypeDeclarationFQN */', 'expectedTokens' => [ [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Fully', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Qualified', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Name', + 'type' => 'T_NAME_FULLY_QUALIFIED', + 'content' => '\Fully\Qualified\Name', ], [ 'type' => 'T_WHITESPACE', @@ -734,16 +582,8 @@ public static function dataIdentifierTokenization() 'content' => '?', ], [ - 'type' => 'T_STRING', - 'content' => 'Sublevel', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Name', + 'type' => 'T_NAME_QUALIFIED', + 'content' => 'Sublevel\Name', ], [ 'type' => 'T_WHITESPACE', @@ -759,12 +599,8 @@ public static function dataIdentifierTokenization() 'content' => '?', ], [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Name', + 'type' => 'T_NAME_FULLY_QUALIFIED', + 'content' => '\Name', ], [ 'type' => 'T_WHITESPACE', @@ -776,16 +612,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testFunctionCallRelative */', 'expectedTokens' => [ [ - 'type' => 'T_NAMESPACE', - 'content' => 'NameSpace', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'function_name', + 'type' => 'T_NAME_RELATIVE', + 'content' => 'NameSpace\function_name', ], [ 'type' => 'T_OPEN_PARENTHESIS', @@ -793,32 +621,12 @@ public static function dataIdentifierTokenization() ], ], ], - 'function call, fully qualified name' => [ - 'testMarker' => '/* testFunctionCallFQN */', + 'function call, fully qualified name (with reserved keyword)' => [ + 'testMarker' => '/* testFunctionCallFQNWithReservedKeyword */', 'expectedTokens' => [ [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Vendor', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Package', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'function_name', + 'type' => 'T_NAME_FULLY_QUALIFIED', + 'content' => '\Vendor\Foreach\function_name', ], [ 'type' => 'T_OPEN_PARENTHESIS', @@ -843,16 +651,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testFunctionCallPartiallyQualified */', 'expectedTokens' => [ [ - 'type' => 'T_STRING', - 'content' => 'Level', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'function_name', + 'type' => 'T_NAME_QUALIFIED', + 'content' => 'Level\function_name', ], [ 'type' => 'T_OPEN_PARENTHESIS', @@ -864,24 +664,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testCatchRelative */', 'expectedTokens' => [ [ - 'type' => 'T_NAMESPACE', - 'content' => 'namespace', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'SubLevel', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Exception', + 'type' => 'T_NAME_RELATIVE', + 'content' => 'namespace\SubLevel\Exception', ], [ 'type' => 'T_WHITESPACE', @@ -893,12 +677,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testCatchFQN */', 'expectedTokens' => [ [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Exception', + 'type' => 'T_NAME_FULLY_QUALIFIED', + 'content' => '\Exception', ], [ 'type' => 'T_WHITESPACE', @@ -923,16 +703,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testCatchPartiallyQualified */', 'expectedTokens' => [ [ - 'type' => 'T_STRING', - 'content' => 'Level', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Exception', + 'type' => 'T_NAME_QUALIFIED', + 'content' => 'Level\Exception', ], [ 'type' => 'T_WHITESPACE', @@ -944,16 +716,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testNewRelative */', 'expectedTokens' => [ [ - 'type' => 'T_NAMESPACE', - 'content' => 'namespace', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'ClassName', + 'type' => 'T_NAME_RELATIVE', + 'content' => 'namespace\ClassName', ], [ 'type' => 'T_OPEN_PARENTHESIS', @@ -965,20 +729,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testNewFQN */', 'expectedTokens' => [ [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Vendor', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'ClassName', + 'type' => 'T_NAME_FULLY_QUALIFIED', + 'content' => '\Vendor\ClassName', ], [ 'type' => 'T_OPEN_PARENTHESIS', @@ -1003,16 +755,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testNewPartiallyQualified */', 'expectedTokens' => [ [ - 'type' => 'T_STRING', - 'content' => 'Level', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'ClassName', + 'type' => 'T_NAME_QUALIFIED', + 'content' => 'Level\ClassName', ], [ 'type' => 'T_SEMICOLON', @@ -1024,16 +768,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testDoubleColonRelative */', 'expectedTokens' => [ [ - 'type' => 'T_NAMESPACE', - 'content' => 'namespace', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'ClassName', + 'type' => 'T_NAME_RELATIVE', + 'content' => 'namespace\ClassName', ], [ 'type' => 'T_DOUBLE_COLON', @@ -1045,12 +781,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testDoubleColonFQN */', 'expectedTokens' => [ [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'ClassName', + 'type' => 'T_NAME_FULLY_QUALIFIED', + 'content' => '\ClassName', ], [ 'type' => 'T_DOUBLE_COLON', @@ -1075,16 +807,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testDoubleColonPartiallyQualified */', 'expectedTokens' => [ [ - 'type' => 'T_STRING', - 'content' => 'Level', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'ClassName', + 'type' => 'T_NAME_QUALIFIED', + 'content' => 'Level\ClassName', ], [ 'type' => 'T_DOUBLE_COLON', @@ -1096,16 +820,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testInstanceOfRelative */', 'expectedTokens' => [ [ - 'type' => 'T_NAMESPACE', - 'content' => 'namespace', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'ClassName', + 'type' => 'T_NAME_RELATIVE', + 'content' => 'namespace\ClassName', ], [ 'type' => 'T_SEMICOLON', @@ -1117,20 +833,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testInstanceOfFQN */', 'expectedTokens' => [ [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Full', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'ClassName', + 'type' => 'T_NAME_FULLY_QUALIFIED', + 'content' => '\Full\ClassName', ], [ 'type' => 'T_CLOSE_PARENTHESIS', @@ -1155,16 +859,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testInstanceOfPartiallyQualified */', 'expectedTokens' => [ [ - 'type' => 'T_STRING', - 'content' => 'Partially', - ], - [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'ClassName', + 'type' => 'T_NAME_QUALIFIED', + 'content' => 'Partially\ClassName', ], [ 'type' => 'T_SEMICOLON', @@ -1226,12 +922,8 @@ public static function dataIdentifierTokenization() 'testMarker' => '/* testInvalidInPHP8Comments */', 'expectedTokens' => [ [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Fully', + 'type' => 'T_NAME_FULLY_QUALIFIED', + 'content' => '\Fully', ], [ 'type' => 'T_WHITESPACE', @@ -1252,12 +944,8 @@ public static function dataIdentifierTokenization() 'content' => ' ', ], [ - 'type' => 'T_NS_SEPARATOR', - 'content' => '\\', - ], - [ - 'type' => 'T_STRING', - 'content' => 'Qualified', + 'type' => 'T_NAME_FULLY_QUALIFIED', + 'content' => '\Qualified', ], [ 'type' => 'T_WHITESPACE', @@ -1281,18 +969,31 @@ public static function dataIdentifierTokenization() 'type' => 'T_WHITESPACE', 'content' => ' ', ], + [ + 'type' => 'T_NAME_FULLY_QUALIFIED', + 'content' => '\Name', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + 'invalid name, double backslash' => [ + 'testMarker' => '/* testInvalidDoubleBackslash */', + 'expectedTokens' => [ [ 'type' => 'T_NS_SEPARATOR', 'content' => '\\', ], [ - 'type' => 'T_STRING', - 'content' => 'Name', + 'type' => 'T_NAME_FULLY_QUALIFIED', + 'content' => '\SomeClass', ], [ - 'type' => 'T_WHITESPACE', - 'content' => ' -', + 'type' => 'T_OPEN_PARENTHESIS', + 'content' => '(', ], ], ], From 11afb05995e20ed72fb7c5b863c6a1ada367fd62 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 5 Apr 2025 04:23:26 +0200 Subject: [PATCH 03/41] Tokenizer/PHP: update union/intersection/DNF type token tokenization to allow for PHP 8 identifier tokens Includes updated test expectations for the typed constants tests. --- src/Tokenizers/PHP.php | 20 +++---- .../Tokenizers/PHP/TypedConstantsTest.php | 59 +++++-------------- 2 files changed, 24 insertions(+), 55 deletions(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 65207e44c8..761b15b818 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -3107,17 +3107,15 @@ protected function processAdditional() All type related tokens will be converted in one go as soon as this section is hit. */ - $allowed = [ - T_STRING => T_STRING, - T_CALLABLE => T_CALLABLE, - T_SELF => T_SELF, - T_PARENT => T_PARENT, - T_STATIC => T_STATIC, - T_FALSE => T_FALSE, - T_TRUE => T_TRUE, - T_NULL => T_NULL, - T_NAMESPACE => T_NAMESPACE, - T_NS_SEPARATOR => T_NS_SEPARATOR, + $allowed = Tokens::$nameTokens; + $allowed += [ + T_CALLABLE => T_CALLABLE, + T_SELF => T_SELF, + T_PARENT => T_PARENT, + T_STATIC => T_STATIC, + T_FALSE => T_FALSE, + T_TRUE => T_TRUE, + T_NULL => T_NULL, ]; $suspectedType = null; diff --git a/tests/Core/Tokenizers/PHP/TypedConstantsTest.php b/tests/Core/Tokenizers/PHP/TypedConstantsTest.php index 6968c4ef8c..d40fa797f1 100644 --- a/tests/Core/Tokenizers/PHP/TypedConstantsTest.php +++ b/tests/Core/Tokenizers/PHP/TypedConstantsTest.php @@ -208,24 +208,19 @@ public static function dataTypedConstant() 'simple type: fully qualified name' => [ 'testMarker' => '/* testClassConstTypedClassFullyQualified */', 'sequence' => [ - T_NS_SEPARATOR, - T_STRING, + T_NAME_FULLY_QUALIFIED, ], ], 'simple type: namespace relative name' => [ 'testMarker' => '/* testClassConstTypedClassNamespaceRelative */', 'sequence' => [ - T_NAMESPACE, - T_NS_SEPARATOR, - T_STRING, + T_NAME_RELATIVE, ], ], 'simple type: partially qualified name' => [ 'testMarker' => '/* testClassConstTypedClassPartiallyQualified */', 'sequence' => [ - T_STRING, - T_NS_SEPARATOR, - T_STRING, + T_NAME_QUALIFIED, ], ], 'simple type: parent' => [ @@ -334,24 +329,19 @@ public static function dataNullableTypedConstant() 'nullable type: fully qualified name' => [ 'testMarker' => '/* testTraitConstTypedNullableClassFullyQualified */', 'sequence' => [ - T_NS_SEPARATOR, - T_STRING, + T_NAME_FULLY_QUALIFIED, ], ], 'nullable type: namespace relative name' => [ 'testMarker' => '/* testTraitConstTypedNullableClassNamespaceRelative */', 'sequence' => [ - T_NAMESPACE, - T_NS_SEPARATOR, - T_STRING, + T_NAME_RELATIVE, ], ], 'nullable type: partially qualified name' => [ 'testMarker' => '/* testTraitConstTypedNullableClassPartiallyQualified */', 'sequence' => [ - T_STRING, - T_NS_SEPARATOR, - T_STRING, + T_NAME_QUALIFIED, ], ], 'nullable type: parent' => [ @@ -440,22 +430,15 @@ public static function dataUnionTypedConstant() 'sequence' => [ T_STRING, T_TYPE_UNION, - T_NAMESPACE, - T_NS_SEPARATOR, - T_STRING, + T_NAME_RELATIVE, ], ], 'union type: FQN|Partial' => [ 'testMarker' => '/* testInterfaceConstTypedUnionFullyQualifiedPartiallyQualified */', 'sequence' => [ - T_NS_SEPARATOR, - T_STRING, - T_NS_SEPARATOR, - T_STRING, + T_NAME_FULLY_QUALIFIED, T_TYPE_UNION, - T_STRING, - T_NS_SEPARATOR, - T_STRING, + T_NAME_QUALIFIED, ], ], ]; @@ -485,22 +468,15 @@ public static function dataIntersectionTypedConstant() 'sequence' => [ T_STRING, T_TYPE_INTERSECTION, - T_NAMESPACE, - T_NS_SEPARATOR, - T_STRING, + T_NAME_RELATIVE, ], ], 'intersection type: FQN&Partial' => [ 'testMarker' => '/* testEnumConstTypedIntersectFullyQualifiedPartiallyQualified */', 'sequence' => [ - T_NS_SEPARATOR, - T_STRING, - T_NS_SEPARATOR, - T_STRING, + T_NAME_FULLY_QUALIFIED, T_TYPE_INTERSECTION, - T_STRING, - T_NS_SEPARATOR, - T_STRING, + T_NAME_QUALIFIED, ], ], ]; @@ -628,17 +604,12 @@ public static function dataDNFTypedConstant() 'testMarker' => '/* testAnonClassConstDNFTypeFQNRelativePartiallyQualified */', 'sequence' => [ T_TYPE_OPEN_PARENTHESIS, - T_NS_SEPARATOR, - T_STRING, + T_NAME_FULLY_QUALIFIED, T_TYPE_INTERSECTION, - T_NAMESPACE, - T_NS_SEPARATOR, - T_STRING, + T_NAME_RELATIVE, T_TYPE_CLOSE_PARENTHESIS, T_TYPE_UNION, - T_STRING, - T_NS_SEPARATOR, - T_STRING, + T_NAME_QUALIFIED, ], ], 'DNF type: invalid self/parent/static' => [ From 434ef2f5d5e6723a81928510f9ded040e4489860 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 1 Sep 2020 22:42:07 +0200 Subject: [PATCH 04/41] Tokenizer/PHP: update the arrow function backfill to allow for PHP 8 identifier tokens Includes updating the unit tests for the new reality and adding some additional tests. Note: while the tests which were testing the handling of the `fn` keyword when used in namespaced names are now no longer testing the polyfill for arrow functions, I do believe the test in itself still has value, which is why I've kept it and adjusted the `testNotAnArrowFunction()` method to allow for expecting one of the name tokens. --- src/Tokenizers/PHP.php | 4 +- .../Tokenizers/PHP/BackfillFnTokenTest.inc | 6 ++ .../Tokenizers/PHP/BackfillFnTokenTest.php | 71 +++++++++++++++---- 3 files changed, 63 insertions(+), 18 deletions(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 761b15b818..fac5165f51 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -2771,12 +2771,11 @@ protected function processAdditional() && isset($this->tokens[$x]['parenthesis_closer']) === true ) { $ignore = Tokens::$emptyTokens; + $ignore += Tokens::$nameTokens; $ignore += [ T_ARRAY => T_ARRAY, T_CALLABLE => T_CALLABLE, T_COLON => T_COLON, - T_NAMESPACE => T_NAMESPACE, - T_NS_SEPARATOR => T_NS_SEPARATOR, T_NULL => T_NULL, T_TRUE => T_TRUE, T_FALSE => T_FALSE, @@ -2784,7 +2783,6 @@ protected function processAdditional() T_PARENT => T_PARENT, T_SELF => T_SELF, T_STATIC => T_STATIC, - T_STRING => T_STRING, T_TYPE_UNION => T_TYPE_UNION, T_TYPE_INTERSECTION => T_TYPE_INTERSECTION, T_TYPE_OPEN_PARENTHESIS => T_TYPE_OPEN_PARENTHESIS, diff --git a/tests/Core/Tokenizers/PHP/BackfillFnTokenTest.inc b/tests/Core/Tokenizers/PHP/BackfillFnTokenTest.inc index 8930d4fbfc..e6d17388be 100644 --- a/tests/Core/Tokenizers/PHP/BackfillFnTokenTest.inc +++ b/tests/Core/Tokenizers/PHP/BackfillFnTokenTest.inc @@ -71,6 +71,12 @@ $a = [ /* testYield */ $a = fn($x) => yield 'k' => $x; +/* testReturnTypeNullableFullyQualifiedClassName */ +$fn = fn($x) : ?\My\NS\ClassName => $x; + +/* testReturnTypeNullablePartiallyQualifiedClassName */ +$fn = fn($x) : ?NS\ClassName => $x; + /* testNullableUnqualifiedClassName */ $a = fn(?\DateTime $x) : ?\DateTime => $x; diff --git a/tests/Core/Tokenizers/PHP/BackfillFnTokenTest.php b/tests/Core/Tokenizers/PHP/BackfillFnTokenTest.php index d5cf019b87..2d84ee9ca1 100644 --- a/tests/Core/Tokenizers/PHP/BackfillFnTokenTest.php +++ b/tests/Core/Tokenizers/PHP/BackfillFnTokenTest.php @@ -10,6 +10,7 @@ namespace PHP_CodeSniffer\Tests\Core\Tokenizers\PHP; use PHP_CodeSniffer\Tests\Core\Tokenizers\AbstractTokenizerTestCase; +use PHP_CodeSniffer\Util\Tokens; final class BackfillFnTokenTest extends AbstractTokenizerTestCase { @@ -361,6 +362,38 @@ public function testYield() }//end testYield() + /** + * Test arrow functions that use nullable type with FQN class name. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testReturnTypeNullableFullyQualifiedClassName() + { + $token = $this->getTargetToken('/* testReturnTypeNullableFullyQualifiedClassName */', T_FN); + $this->backfillHelper($token); + $this->scopePositionTestHelper($token, 10, 13); + + }//end testReturnTypeNullableFullyQualifiedClassName() + + + /** + * Test arrow functions that use nullable type with partially qualified class name. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testReturnTypeNullablePartiallyQualifiedClassName() + { + $token = $this->getTargetToken('/* testReturnTypeNullablePartiallyQualifiedClassName */', T_FN); + $this->backfillHelper($token); + $this->scopePositionTestHelper($token, 10, 13); + + }//end testReturnTypeNullablePartiallyQualifiedClassName() + + /** * Test arrow functions that use nullable type with unqualified class name. * @@ -372,7 +405,7 @@ public function testNullableUnqualifiedClassName() { $token = $this->getTargetToken('/* testNullableUnqualifiedClassName */', T_FN); $this->backfillHelper($token); - $this->scopePositionTestHelper($token, 15, 18); + $this->scopePositionTestHelper($token, 13, 16); }//end testNullableUnqualifiedClassName() @@ -388,7 +421,7 @@ public function testNamespaceRelativeClassNameInTypes() { $token = $this->getTargetToken('/* testNamespaceRelativeClassNameInTypes */', T_FN); $this->backfillHelper($token); - $this->scopePositionTestHelper($token, 16, 19); + $this->scopePositionTestHelper($token, 12, 15); }//end testNamespaceRelativeClassNameInTypes() @@ -542,7 +575,7 @@ public function testIntersectionReturnType() { $token = $this->getTargetToken('/* testIntersectionReturnType */', T_FN); $this->backfillHelper($token); - $this->scopePositionTestHelper($token, 12, 20); + $this->scopePositionTestHelper($token, 11, 19); }//end testIntersectionReturnType() @@ -574,7 +607,7 @@ public function testDNFReturnType() { $token = $this->getTargetToken('/* testDNFReturnType */', T_FN); $this->backfillHelper($token); - $this->scopePositionTestHelper($token, 16, 29); + $this->scopePositionTestHelper($token, 15, 27); }//end testDNFReturnType() @@ -783,25 +816,30 @@ public function testNestedInMethod() /** - * Verify that "fn" keywords which are not arrow functions get tokenized as T_STRING and don't + * Verify that "fn" keywords which are not arrow functions get tokenized as identifier names and don't * have the extra token array indexes. * - * @param string $testMarker The comment prefacing the target token. - * @param string $testContent The token content to look for. + * @param string $testMarker The comment prefacing the target token. + * @param string $testContent The token content to look for. + * @param string $expectedType Optional. The token type which is expected (not T_FN). + * Defaults to `T_STRING`. * * @dataProvider dataNotAnArrowFunction * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional * * @return void */ - public function testNotAnArrowFunction($testMarker, $testContent='fn') + public function testNotAnArrowFunction($testMarker, $testContent='fn', $expectedType='T_STRING') { - $tokens = $this->phpcsFile->getTokens(); + $targetTypes = Tokens::$nameTokens; + $targetTypes += [T_FN => T_FN]; + $target = $this->getTargetToken($testMarker, $targetTypes, $testContent); - $token = $this->getTargetToken($testMarker, [T_STRING, T_FN], $testContent); - $tokenArray = $tokens[$token]; + $tokens = $this->phpcsFile->getTokens(); + $tokenArray = $tokens[$target]; - $this->assertSame('T_STRING', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING'); + $this->assertSame(constant($expectedType), $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not '.$expectedType.' (code)'); + $this->assertSame($expectedType, $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not '.$expectedType.' (type)'); $this->assertArrayNotHasKey('scope_condition', $tokenArray, 'Scope condition is set'); $this->assertArrayNotHasKey('scope_opener', $tokenArray, 'Scope opener is set'); @@ -863,11 +901,14 @@ public static function dataNotAnArrowFunction() 'testContent' => 'FN', ], 'name of a (namespaced) function, context: partially qualified function call' => [ - 'testMarker' => '/* testNonArrowNamespacedFunctionCall */', - 'testContent' => 'Fn', + 'testMarker' => '/* testNonArrowNamespacedFunctionCall */', + 'testContent' => 'MyNS\Sub\Fn', + 'expectedType' => 'T_NAME_QUALIFIED', ], 'name of a (namespaced) function, context: namespace relative function call' => [ - 'testMarker' => '/* testNonArrowNamespaceOperatorFunctionCall */', + 'testMarker' => '/* testNonArrowNamespaceOperatorFunctionCall */', + 'testContent' => 'namespace\fn', + 'expectedType' => 'T_NAME_RELATIVE', ], 'name of a function, context: declaration with union types for param and return' => [ 'testMarker' => '/* testNonArrowFunctionNameWithUnionTypes */', From 692580b4edb9c50159ef9814cd076184e1977d14 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 3 Nov 2020 00:24:10 +0100 Subject: [PATCH 05/41] Tokenizer/PHP: update the short array tokenization to allow for PHP 8 identifier tokens Includes adding some additional tests. --- src/Tokenizers/PHP.php | 4 ++-- tests/Core/Tokenizers/PHP/ShortArrayTest.inc | 9 +++++++++ tests/Core/Tokenizers/PHP/ShortArrayTest.php | 3 +++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index fac5165f51..7b61af3f3f 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -2964,14 +2964,14 @@ protected function processAdditional() // Unless there is a variable or a bracket before this token, // it is the start of an array being defined using the short syntax. $isShortArray = false; - $allowed = [ + $allowed = Tokens::$nameTokens; + $allowed += [ T_CLOSE_SQUARE_BRACKET => T_CLOSE_SQUARE_BRACKET, T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET, T_CLOSE_PARENTHESIS => T_CLOSE_PARENTHESIS, T_VARIABLE => T_VARIABLE, T_OBJECT_OPERATOR => T_OBJECT_OPERATOR, T_NULLSAFE_OBJECT_OPERATOR => T_NULLSAFE_OBJECT_OPERATOR, - T_STRING => T_STRING, T_CONSTANT_ENCAPSED_STRING => T_CONSTANT_ENCAPSED_STRING, T_DOUBLE_QUOTED_STRING => T_DOUBLE_QUOTED_STRING, ]; diff --git a/tests/Core/Tokenizers/PHP/ShortArrayTest.inc b/tests/Core/Tokenizers/PHP/ShortArrayTest.inc index 60b23a51cc..04cc912e60 100644 --- a/tests/Core/Tokenizers/PHP/ShortArrayTest.inc +++ b/tests/Core/Tokenizers/PHP/ShortArrayTest.inc @@ -45,6 +45,15 @@ $var ClassName::CONSTANT_NAME[2]; /* testMagicConstantDereferencing */ $var = __FILE__[0]; +/* testPartiallyQualifiedConstantDereferencing */ +$var = MyNS\MY_CONSTANT[1]; + +/* testFQNConstantDereferencing */ +$var = \MY_CONSTANT[1]; + +/* testNamespaceRelativeConstantDereferencing */ +$var = namespace\MY_CONSTANT[1]; + /* testArrayAccessCurlyBraces */ $var = $array{'key'}['key']; diff --git a/tests/Core/Tokenizers/PHP/ShortArrayTest.php b/tests/Core/Tokenizers/PHP/ShortArrayTest.php index da9c7c1040..741da620d9 100644 --- a/tests/Core/Tokenizers/PHP/ShortArrayTest.php +++ b/tests/Core/Tokenizers/PHP/ShortArrayTest.php @@ -69,6 +69,9 @@ public static function dataSquareBrackets() 'global constant dereferencing' => ['/* testConstantDereferencing */'], 'class constant dereferencing' => ['/* testClassConstantDereferencing */'], 'magic constant dereferencing' => ['/* testMagicConstantDereferencing */'], + 'partially qualified constant dereferencing' => ['/* testPartiallyQualifiedConstantDereferencing */'], + 'fully qualified constant dereferencing' => ['/* testFQNConstantDereferencing */'], + 'namespace relative constant dereferencing' => ['/* testNamespaceRelativeConstantDereferencing */'], 'array access with curly braces' => ['/* testArrayAccessCurlyBraces */'], 'array literal dereferencing' => ['/* testArrayLiteralDereferencing */'], 'short array literal dereferencing' => ['/* testShortArrayLiteralDereferencing */'], From a152c9de122f0d571e3a7b296151aecf8a3e6341 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 8 Apr 2025 20:45:58 +0200 Subject: [PATCH 06/41] Tokenizer/PHP: update various tests to allow for PHP 8 identifier tokens Note: while some tests which were testing the handling of the specific keywords when used in namespaced names are now no longer testing the polyfill for that keyword, I do believe those tests in itself still have value, which is why I've kept them and adjusted the `testNot[KEYWORD]()` methods to allow for expecting one of the name tokens. Includes removing one test which really didn't have value anymore. --- tests/Core/Tokenizers/PHP/AttributesTest.php | 38 +++++++---------- .../Core/Tokenizers/PHP/BackfillEnumTest.php | 30 ++++++++----- .../Tokenizers/PHP/BackfillMatchTokenTest.php | 37 +++++++++------- .../Tokenizers/PHP/BackfillReadonlyTest.php | 36 ++++++++++------ .../PHP/ContextSensitiveKeywordsTest.inc | 3 -- .../PHP/ContextSensitiveKeywordsTest.php | 4 -- .../Tokenizers/PHP/DefaultKeywordTest.php | 42 +++++++++++++------ tests/Core/Tokenizers/PHP/GotoLabelTest.php | 11 +++-- .../PHP/NamedFunctionCallArgumentsTest.inc | 2 +- .../PHP/NamedFunctionCallArgumentsTest.php | 2 +- ...seScopeMapDefaultKeywordConditionsTest.php | 30 ++++++++----- 11 files changed, 137 insertions(+), 98 deletions(-) diff --git a/tests/Core/Tokenizers/PHP/AttributesTest.php b/tests/Core/Tokenizers/PHP/AttributesTest.php index 5e2ea0aeda..d95959264b 100644 --- a/tests/Core/Tokenizers/PHP/AttributesTest.php +++ b/tests/Core/Tokenizers/PHP/AttributesTest.php @@ -256,15 +256,10 @@ public static function dataAttribute() 'function attribute; using partially qualified and fully qualified class names' => [ 'testMarker' => '/* testFqcnAttribute */', 'tokenCodes' => [ - T_STRING, - T_NS_SEPARATOR, - T_STRING, + T_NAME_QUALIFIED, T_COMMA, T_WHITESPACE, - T_NS_SEPARATOR, - T_STRING, - T_NS_SEPARATOR, - T_STRING, + T_NAME_FULLY_QUALIFIED, T_OPEN_PARENTHESIS, T_CONSTANT_ENCAPSED_STRING, T_CLOSE_PARENTHESIS, @@ -616,9 +611,7 @@ public function testNestedAttributes() { $tokens = $this->phpcsFile->getTokens(); $tokenCodes = [ - T_STRING, - T_NS_SEPARATOR, - T_STRING, + T_NAME_QUALIFIED, T_OPEN_PARENTHESIS, T_FN, T_WHITESPACE, @@ -642,7 +635,8 @@ public function testNestedAttributes() ]; // Calculate the number of tokens between opener and closer (excluding the opener, including the closer). - $outerAttributeLength = (count($tokenCodes) + 1); + $outerAttributeLength = (count($tokenCodes) + 1); + $nestedAttributeOffset = 6; $attribute = $this->getTargetToken('/* testNestedAttributes */', T_ATTRIBUTE); $this->assertArrayHasKey('attribute_closer', $tokens[$attribute]); @@ -656,8 +650,8 @@ public function testNestedAttributes() $this->assertSame($tokens[$attribute]['attribute_closer'], $tokens[$closer]['attribute_closer']); $this->assertArrayNotHasKey('nested_attributes', $tokens[$attribute]); - $this->assertArrayHasKey('nested_attributes', $tokens[($attribute + 8)]); - $this->assertSame([$attribute => ($attribute + $outerAttributeLength)], $tokens[($attribute + 8)]['nested_attributes']); + $this->assertArrayHasKey('nested_attributes', $tokens[($attribute + $nestedAttributeOffset)]); + $this->assertSame([$attribute => ($attribute + $outerAttributeLength)], $tokens[($attribute + $nestedAttributeOffset)]['nested_attributes']); $test = function (array $tokens, $outerAttributeLength, $nestedMap) use ($attribute) { foreach ($tokens as $token) { @@ -667,23 +661,23 @@ public function testNestedAttributes() } }; - // Length here is 8 (nested attribute offset) + 5 (real length). - $innerAttributeLength = (8 + 5); + // Length here is 6 (nested attribute offset) + 5 (real length). + $innerAttributeLength = ($nestedAttributeOffset + 5); - $test(array_slice($tokens, ($attribute + 1), 7), $outerAttributeLength, [$attribute => $attribute + $outerAttributeLength]); - $test(array_slice($tokens, ($attribute + 8), 1), $innerAttributeLength, [$attribute => $attribute + $outerAttributeLength]); + $test(array_slice($tokens, ($attribute + 1), 5), $outerAttributeLength, [$attribute => $attribute + $outerAttributeLength]); + $test(array_slice($tokens, ($attribute + $nestedAttributeOffset), 1), $innerAttributeLength, [$attribute => $attribute + $outerAttributeLength]); $test( - array_slice($tokens, ($attribute + 9), 4), + array_slice($tokens, ($attribute + 7), 4), $innerAttributeLength, [ - $attribute => $attribute + $outerAttributeLength, - $attribute + 8 => $attribute + 13, + $attribute => $attribute + $outerAttributeLength, + $attribute + $nestedAttributeOffset => $attribute + $innerAttributeLength, ] ); - $test(array_slice($tokens, ($attribute + 13), 1), $innerAttributeLength, [$attribute => $attribute + $outerAttributeLength]); - $test(array_slice($tokens, ($attribute + 14), 10), $outerAttributeLength, [$attribute => $attribute + $outerAttributeLength]); + $test(array_slice($tokens, ($attribute + 11), 1), $innerAttributeLength, [$attribute => $attribute + $outerAttributeLength]); + $test(array_slice($tokens, ($attribute + 12), 10), $outerAttributeLength, [$attribute => $attribute + $outerAttributeLength]); $map = array_map( static function ($token) { diff --git a/tests/Core/Tokenizers/PHP/BackfillEnumTest.php b/tests/Core/Tokenizers/PHP/BackfillEnumTest.php index 9b313ca694..47a4ed3a64 100644 --- a/tests/Core/Tokenizers/PHP/BackfillEnumTest.php +++ b/tests/Core/Tokenizers/PHP/BackfillEnumTest.php @@ -10,6 +10,7 @@ namespace PHP_CodeSniffer\Tests\Core\Tokenizers\PHP; use PHP_CodeSniffer\Tests\Core\Tokenizers\AbstractTokenizerTestCase; +use PHP_CodeSniffer\Util\Tokens; final class BackfillEnumTest extends AbstractTokenizerTestCase { @@ -126,24 +127,29 @@ public static function dataEnums() /** - * Test that "enum" when not used as the keyword is still tokenized as `T_STRING`. + * Test that "enum" when not used as the keyword is still tokenized as identifier names. * - * @param string $testMarker The comment which prefaces the target token in the test file. - * @param string $testContent The token content to look for. + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param string $testContent The token content to look for. + * @param string $expectedType Optional. The token type which is expected (not T_FN). + * Defaults to `T_STRING`. * * @dataProvider dataNotEnums * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize * * @return void */ - public function testNotEnums($testMarker, $testContent) + public function testNotEnums($testMarker, $testContent, $expectedType='T_STRING') { + $targetTypes = Tokens::$nameTokens; + $targetTypes += [T_ENUM => T_ENUM]; + $target = $this->getTargetToken($testMarker, $targetTypes, $testContent); + $tokens = $this->phpcsFile->getTokens(); - $target = $this->getTargetToken($testMarker, [T_ENUM, T_STRING], $testContent); $tokenArray = $tokens[$target]; - $this->assertSame(T_STRING, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (code)'); - $this->assertSame('T_STRING', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (type)'); + $this->assertSame(constant($expectedType), $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not '.$expectedType.' (code)'); + $this->assertSame($expectedType, $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not '.$expectedType.' (type)'); }//end testNotEnums() @@ -187,8 +193,9 @@ public static function dataNotEnums() 'testContent' => 'Enum', ], 'not enum - part of namespace named enum' => [ - 'testMarker' => '/* testEnumUsedAsPartOfNamespaceName */', - 'testContent' => 'Enum', + 'testMarker' => '/* testEnumUsedAsPartOfNamespaceName */', + 'testContent' => 'My\Enum\Collection', + 'expectedType' => 'T_NAME_QUALIFIED', ], 'not enum - class instantiation for class enum' => [ 'testMarker' => '/* testEnumUsedInObjectInitialization */', @@ -199,8 +206,9 @@ public static function dataNotEnums() 'testContent' => 'enum', ], 'not enum - namespace relative function call' => [ - 'testMarker' => '/* testEnumAsFunctionCallWithNamespace */', - 'testContent' => 'enum', + 'testMarker' => '/* testEnumAsFunctionCallWithNamespace */', + 'testContent' => 'namespace\enum', + 'expectedType' => 'T_NAME_RELATIVE', ], 'not enum - class constant fetch with enum as class name' => [ 'testMarker' => '/* testClassConstantFetchWithEnumAsClassName */', diff --git a/tests/Core/Tokenizers/PHP/BackfillMatchTokenTest.php b/tests/Core/Tokenizers/PHP/BackfillMatchTokenTest.php index 8214ea1a91..07a335c058 100644 --- a/tests/Core/Tokenizers/PHP/BackfillMatchTokenTest.php +++ b/tests/Core/Tokenizers/PHP/BackfillMatchTokenTest.php @@ -199,11 +199,13 @@ public static function dataMatchExpression() /** - * Verify that "match" keywords which are not match control structures get tokenized as T_STRING + * Verify that "match" keywords which are not match control structures get tokenized as identifier names * and don't have the extra token array indexes. * - * @param string $testMarker The comment prefacing the target token. - * @param string $testContent The token content to look for. + * @param string $testMarker The comment prefacing the target token. + * @param string $testContent The token content to look for. + * @param string $expectedType Optional. The token type which is expected (not T_FN). + * Defaults to `T_STRING`. * * @dataProvider dataNotAMatchStructure * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize @@ -211,15 +213,17 @@ public static function dataMatchExpression() * * @return void */ - public function testNotAMatchStructure($testMarker, $testContent='match') + public function testNotAMatchStructure($testMarker, $testContent='match', $expectedType='T_STRING') { - $tokens = $this->phpcsFile->getTokens(); + $targetTypes = Tokens::$nameTokens; + $targetTypes += [T_MATCH => T_MATCH]; + $target = $this->getTargetToken($testMarker, $targetTypes, $testContent); - $token = $this->getTargetToken($testMarker, [T_STRING, T_MATCH], $testContent); - $tokenArray = $tokens[$token]; + $tokens = $this->phpcsFile->getTokens(); + $tokenArray = $tokens[$target]; - $this->assertSame(T_STRING, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (code)'); - $this->assertSame('T_STRING', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (type)'); + $this->assertSame(constant($expectedType), $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not '.$expectedType.' (code)'); + $this->assertSame($expectedType, $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not '.$expectedType.' (type)'); $this->assertArrayNotHasKey('scope_condition', $tokenArray, 'Scope condition is set'); $this->assertArrayNotHasKey('scope_opener', $tokenArray, 'Scope opener is set'); @@ -228,7 +232,7 @@ public function testNotAMatchStructure($testMarker, $testContent='match') $this->assertArrayNotHasKey('parenthesis_opener', $tokenArray, 'Parenthesis opener is set'); $this->assertArrayNotHasKey('parenthesis_closer', $tokenArray, 'Parenthesis closer is set'); - $next = $this->phpcsFile->findNext(Tokens::$emptyTokens, ($token + 1), null, true); + $next = $this->phpcsFile->findNext(Tokens::$emptyTokens, ($target + 1), null, true); if ($next !== false && $tokens[$next]['code'] === T_OPEN_PARENTHESIS) { $this->assertArrayNotHasKey('parenthesis_owner', $tokenArray, 'Parenthesis owner is set for opener after'); } @@ -268,10 +272,14 @@ public static function dataNotAMatchStructure() 'testMarker' => '/* testNoMatchPropertyAccess */', ], 'namespaced_function_call' => [ - 'testMarker' => '/* testNoMatchNamespacedFunctionCall */', + 'testMarker' => '/* testNoMatchNamespacedFunctionCall */', + 'testContent' => 'MyNS\Sub\match', + 'expectedType' => 'T_NAME_QUALIFIED', ], 'namespace_operator_function_call' => [ - 'testMarker' => '/* testNoMatchNamespaceOperatorFunctionCall */', + 'testMarker' => '/* testNoMatchNamespaceOperatorFunctionCall */', + 'testContent' => 'namespace\match', + 'expectedType' => 'T_NAME_RELATIVE', ], 'interface_method_declaration' => [ 'testMarker' => '/* testNoMatchInterfaceMethodDeclaration */', @@ -325,8 +333,9 @@ public static function dataNotAMatchStructure() 'testContent' => 'Match', ], 'use_statement' => [ - 'testMarker' => '/* testNoMatchInUseStatement */', - 'testContent' => 'Match', + 'testMarker' => '/* testNoMatchInUseStatement */', + 'testContent' => 'Match\me', + 'expectedType' => 'T_NAME_QUALIFIED', ], 'unsupported_inline_control_structure' => [ 'testMarker' => '/* testNoMatchMissingCurlies */', diff --git a/tests/Core/Tokenizers/PHP/BackfillReadonlyTest.php b/tests/Core/Tokenizers/PHP/BackfillReadonlyTest.php index 89dc3e19d1..f1f616cb68 100644 --- a/tests/Core/Tokenizers/PHP/BackfillReadonlyTest.php +++ b/tests/Core/Tokenizers/PHP/BackfillReadonlyTest.php @@ -10,6 +10,7 @@ namespace PHP_CodeSniffer\Tests\Core\Tokenizers\PHP; use PHP_CodeSniffer\Tests\Core\Tokenizers\AbstractTokenizerTestCase; +use PHP_CodeSniffer\Util\Tokens; final class BackfillReadonlyTest extends AbstractTokenizerTestCase { @@ -173,25 +174,30 @@ public static function dataReadonly() /** - * Test that "readonly" when not used as the keyword is still tokenized as `T_STRING`. + * Test that "readonly" when not used as the keyword is still tokenized as identifier names. * - * @param string $testMarker The comment which prefaces the target token in the test file. - * @param string $testContent Optional. The token content to look for. - * Defaults to lowercase "readonly". + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param string $testContent Optional. The token content to look for. + * Defaults to lowercase "readonly". + * @param string $expectedType Optional. The token type which is expected (not T_FN). + * Defaults to `T_STRING`. * * @dataProvider dataNotReadonly * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional * * @return void */ - public function testNotReadonly($testMarker, $testContent='readonly') + public function testNotReadonly($testMarker, $testContent='readonly', $expectedType='T_STRING') { + $targetTypes = Tokens::$nameTokens; + $targetTypes += [T_READONLY => T_READONLY]; + $target = $this->getTargetToken($testMarker, $targetTypes, $testContent); + $tokens = $this->phpcsFile->getTokens(); - $target = $this->getTargetToken($testMarker, [T_READONLY, T_STRING], $testContent); $tokenArray = $tokens[$target]; - $this->assertSame(T_STRING, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (code)'); - $this->assertSame('T_STRING', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (type)'); + $this->assertSame(constant($expectedType), $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not '.$expectedType.' (code)'); + $this->assertSame($expectedType, $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not '.$expectedType.' (type)'); }//end testNotReadonly() @@ -230,18 +236,22 @@ public static function dataNotReadonly() 'testContent' => 'Readonly', ], 'partial name of namespace, context: declaration, mixed case' => [ - 'testMarker' => '/* testReadonlyUsedAsPartOfNamespaceName */', - 'testContent' => 'Readonly', + 'testMarker' => '/* testReadonlyUsedAsPartOfNamespaceName */', + 'testContent' => 'My\Readonly\Collection', + 'expectedType' => 'T_NAME_QUALIFIED', ], 'name of a function, context: call' => [ 'testMarker' => '/* testReadonlyAsFunctionCall */', ], 'name of a namespaced function, context: partially qualified call' => [ - 'testMarker' => '/* testReadonlyAsNamespacedFunctionCall */', + 'testMarker' => '/* testReadonlyAsNamespacedFunctionCall */', + 'testContent' => 'My\NS\readonly', + 'expectedType' => 'T_NAME_QUALIFIED', ], 'name of a function, context: namespace relative call, mixed case' => [ - 'testMarker' => '/* testReadonlyAsNamespaceRelativeFunctionCall */', - 'testContent' => 'ReadOnly', + 'testMarker' => '/* testReadonlyAsNamespaceRelativeFunctionCall */', + 'testContent' => 'namespace\ReadOnly', + 'expectedType' => 'T_NAME_RELATIVE', ], 'name of a method, context: method call on object' => [ 'testMarker' => '/* testReadonlyAsMethodCall */', diff --git a/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.inc b/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.inc index 2825f26e52..fabb9b8713 100644 --- a/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.inc +++ b/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.inc @@ -222,9 +222,6 @@ $anonymousClass3 = new class /* testImplementsInAnonymousClassIsKeyword */ imple $instantiated = new /* testClassInstantiationStaticIsKeyword */ static($param); -class Foo extends /* testNamespaceInNameIsKeyword */ namespace\Exception -{} - function /* testKeywordAfterFunctionShouldBeString */ eval() {} function /* testKeywordAfterFunctionByRefShouldBeString */ &switch() {} diff --git a/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.php b/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.php index a8d746ae9f..35b47385aa 100644 --- a/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.php +++ b/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.php @@ -515,10 +515,6 @@ public static function dataKeywords() 'testMarker' => '/* testClassInstantiationStaticIsKeyword */', 'expectedTokenType' => 'T_STATIC', ], - 'namespace: operator' => [ - 'testMarker' => '/* testNamespaceInNameIsKeyword */', - 'expectedTokenType' => 'T_NAMESPACE', - ], 'static: closure declaration' => [ 'testMarker' => '/* testStaticIsKeywordBeforeClosure */', diff --git a/tests/Core/Tokenizers/PHP/DefaultKeywordTest.php b/tests/Core/Tokenizers/PHP/DefaultKeywordTest.php index c398aa6cb2..0b7ffdf31e 100644 --- a/tests/Core/Tokenizers/PHP/DefaultKeywordTest.php +++ b/tests/Core/Tokenizers/PHP/DefaultKeywordTest.php @@ -11,6 +11,7 @@ namespace PHP_CodeSniffer\Tests\Core\Tokenizers\PHP; use PHP_CodeSniffer\Tests\Core\Tokenizers\AbstractTokenizerTestCase; +use PHP_CodeSniffer\Util\Tokens; final class DefaultKeywordTest extends AbstractTokenizerTestCase { @@ -155,26 +156,33 @@ public static function dataSwitchDefault() /** * Verify that the retokenization of `T_DEFAULT` tokens in match constructs, doesn't negatively - * impact the tokenization of `T_STRING` tokens with the contents 'default' which aren't in + * impact the tokenization of identifier name tokens with the contents 'default' which aren't in * actual fact the default keyword. * - * @param string $testMarker The comment prefacing the target token. - * @param string $testContent The token content to look for. + * @param string $testMarker The comment prefacing the target token. + * @param string $testContent The token content to look for. + * @param string $expectedType Optional. The token type which is expected (not T_FN). + * Defaults to `T_STRING`. * * @dataProvider dataNotDefaultKeyword * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional * * @return void */ - public function testNotDefaultKeyword($testMarker, $testContent='DEFAULT') + public function testNotDefaultKeyword($testMarker, $testContent='DEFAULT', $expectedType='T_STRING') { - $tokens = $this->phpcsFile->getTokens(); + $targetTypes = Tokens::$nameTokens; + $targetTypes += [ + T_MATCH_DEFAULT => T_MATCH_DEFAULT, + T_DEFAULT => T_DEFAULT, + ]; + $target = $this->getTargetToken($testMarker, $targetTypes, $testContent); - $token = $this->getTargetToken($testMarker, [T_MATCH_DEFAULT, T_DEFAULT, T_STRING], $testContent); - $tokenArray = $tokens[$token]; + $tokens = $this->phpcsFile->getTokens(); + $tokenArray = $tokens[$target]; - $this->assertSame(T_STRING, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (code)'); - $this->assertSame('T_STRING', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (type)'); + $this->assertSame(constant($expectedType), $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not '.$expectedType.' (code)'); + $this->assertSame($expectedType, $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not '.$expectedType.' (type)'); }//end testNotDefaultKeyword() @@ -196,10 +204,14 @@ public static function dataNotDefaultKeyword() 'testMarker' => '/* testClassPropertyAsShortArrayKey */', ], 'namespaced-constant-as-short-array-key' => [ - 'testMarker' => '/* testNamespacedConstantAsShortArrayKey */', + 'testMarker' => '/* testNamespacedConstantAsShortArrayKey */', + 'testContent' => 'SomeNamespace\DEFAULT', + 'expectedType' => 'T_NAME_QUALIFIED', ], 'fqn-global-constant-as-short-array-key' => [ - 'testMarker' => '/* testFQNGlobalConstantAsShortArrayKey */', + 'testMarker' => '/* testFQNGlobalConstantAsShortArrayKey */', + 'testContent' => '\DEFAULT', + 'expectedType' => 'T_NAME_FULLY_QUALIFIED', ], 'class-constant-as-long-array-key' => [ 'testMarker' => '/* testClassConstantAsLongArrayKey */', @@ -234,10 +246,14 @@ public static function dataNotDefaultKeyword() 'testMarker' => '/* testClassPropertyInSwitchCase */', ], 'namespaced-constant-in-switch-case' => [ - 'testMarker' => '/* testNamespacedConstantInSwitchCase */', + 'testMarker' => '/* testNamespacedConstantInSwitchCase */', + 'testContent' => 'SomeNamespace\DEFAULT', + 'expectedType' => 'T_NAME_QUALIFIED', ], 'namespace-relative-constant-in-switch-case' => [ - 'testMarker' => '/* testNamespaceRelativeConstantInSwitchCase */', + 'testMarker' => '/* testNamespaceRelativeConstantInSwitchCase */', + 'testContent' => 'namespace\DEFAULT', + 'expectedType' => 'T_NAME_RELATIVE', ], 'class-constant-declaration' => [ diff --git a/tests/Core/Tokenizers/PHP/GotoLabelTest.php b/tests/Core/Tokenizers/PHP/GotoLabelTest.php index 643338b145..059b634408 100644 --- a/tests/Core/Tokenizers/PHP/GotoLabelTest.php +++ b/tests/Core/Tokenizers/PHP/GotoLabelTest.php @@ -191,10 +191,8 @@ public static function dataGotoDeclaration() */ public function testNotAGotoDeclaration($testMarker, $testContent, $expectedType='T_STRING') { - $targetTypes = [ - T_STRING => T_STRING, - T_GOTO_LABEL => T_GOTO_LABEL, - ]; + $targetTypes = Tokens::$nameTokens; + $targetTypes += [T_GOTO_LABEL => T_GOTO_LABEL]; $expectedCode = T_STRING; if ($expectedType !== 'T_STRING') { @@ -228,8 +226,9 @@ public static function dataNotAGotoDeclaration() 'testContent' => 'CONSTANT', ], 'not goto label - namespaced constant followed by switch-case colon' => [ - 'testMarker' => '/* testNotGotoDeclarationNamespacedConstant */', - 'testContent' => 'CONSTANT', + 'testMarker' => '/* testNotGotoDeclarationNamespacedConstant */', + 'testContent' => 'MyNS\CONSTANT', + 'expectedType' => 'T_NAME_QUALIFIED', ], 'not goto label - class constant followed by switch-case colon' => [ 'testMarker' => '/* testNotGotoDeclarationClassConstantInCase */', diff --git a/tests/Core/Tokenizers/PHP/NamedFunctionCallArgumentsTest.inc b/tests/Core/Tokenizers/PHP/NamedFunctionCallArgumentsTest.inc index 2f1d20bf08..b6434d3d93 100644 --- a/tests/Core/Tokenizers/PHP/NamedFunctionCallArgumentsTest.inc +++ b/tests/Core/Tokenizers/PHP/NamedFunctionCallArgumentsTest.inc @@ -1,7 +1,7 @@ count, self::VALUE); /* testNamedArgs */ array_fill(start_index: 0, count: 100, value: 50); diff --git a/tests/Core/Tokenizers/PHP/NamedFunctionCallArgumentsTest.php b/tests/Core/Tokenizers/PHP/NamedFunctionCallArgumentsTest.php index 768e41a98b..8c6be5bd4c 100644 --- a/tests/Core/Tokenizers/PHP/NamedFunctionCallArgumentsTest.php +++ b/tests/Core/Tokenizers/PHP/NamedFunctionCallArgumentsTest.php @@ -329,7 +329,7 @@ public static function dataOtherTstringInFunctionCall() ], 'not arg name - fully qualified constant' => [ 'testMarker' => '/* testPositionalArgs */', - 'content' => 'COUNT', + 'content' => 'count', ], 'not arg name - namespace relative constant' => [ 'testMarker' => '/* testPositionalArgs */', diff --git a/tests/Core/Tokenizers/Tokenizer/RecurseScopeMapDefaultKeywordConditionsTest.php b/tests/Core/Tokenizers/Tokenizer/RecurseScopeMapDefaultKeywordConditionsTest.php index 9b40fe1091..a38a1600ea 100644 --- a/tests/Core/Tokenizers/Tokenizer/RecurseScopeMapDefaultKeywordConditionsTest.php +++ b/tests/Core/Tokenizers/Tokenizer/RecurseScopeMapDefaultKeywordConditionsTest.php @@ -10,6 +10,7 @@ namespace PHP_CodeSniffer\Tests\Core\Tokenizers\Tokenizer; use PHP_CodeSniffer\Tests\Core\Tokenizers\AbstractTokenizerTestCase; +use PHP_CodeSniffer\Util\Tokens; final class RecurseScopeMapDefaultKeywordConditionsTest extends AbstractTokenizerTestCase { @@ -357,16 +358,21 @@ public static function dataSwitchDefault() */ public function testNotDefaultKeyword($testMarker, $testContent='DEFAULT') { - $tokens = $this->phpcsFile->getTokens(); + $targetTypes = Tokens::$nameTokens; + $targetTypes += [ + T_MATCH_DEFAULT => T_MATCH_DEFAULT, + T_DEFAULT => T_DEFAULT, + ]; + $target = $this->getTargetToken($testMarker, $targetTypes, $testContent); - $token = $this->getTargetToken($testMarker, [T_MATCH_DEFAULT, T_DEFAULT, T_STRING], $testContent); - $tokenArray = $tokens[$token]; + $tokens = $this->phpcsFile->getTokens(); + $tokenArray = $tokens[$target]; // Make sure we're looking at the right token. - $this->assertSame( - T_STRING, + $this->assertArrayHasKey( $tokenArray['code'], - sprintf('Token tokenized as %s, not T_STRING (code). Marker: %s.', $tokenArray['type'], $testMarker) + Tokens::$nameTokens, + sprintf('Token tokenized as %s, not identifier name (code). Marker: %s.', $tokenArray['type'], $testMarker) ); $this->assertArrayNotHasKey( @@ -405,10 +411,12 @@ public static function dataNotDefaultKeyword() 'testMarker' => '/* testClassPropertyAsShortArrayKey */', ], 'namespaced-constant-as-short-array-key' => [ - 'testMarker' => '/* testNamespacedConstantAsShortArrayKey */', + 'testMarker' => '/* testNamespacedConstantAsShortArrayKey */', + 'testContent' => 'SomeNamespace\DEFAULT', ], 'fqn-global-constant-as-short-array-key' => [ - 'testMarker' => '/* testFQNGlobalConstantAsShortArrayKey */', + 'testMarker' => '/* testFQNGlobalConstantAsShortArrayKey */', + 'testContent' => '\DEFAULT', ], 'class-constant-as-long-array-key' => [ 'testMarker' => '/* testClassConstantAsLongArrayKey */', @@ -443,10 +451,12 @@ public static function dataNotDefaultKeyword() 'testMarker' => '/* testClassPropertyInSwitchCase */', ], 'namespaced-constant-in-switch-case' => [ - 'testMarker' => '/* testNamespacedConstantInSwitchCase */', + 'testMarker' => '/* testNamespacedConstantInSwitchCase */', + 'testContent' => 'SomeNamespace\DEFAULT', ], 'namespace-relative-constant-in-switch-case' => [ - 'testMarker' => '/* testNamespaceRelativeConstantInSwitchCase */', + 'testMarker' => '/* testNamespaceRelativeConstantInSwitchCase */', + 'testContent' => 'namespace\DEFAULT', ], 'class-constant-declaration' => [ From dfcd0bd03adc0d92937b0ef06d106569525725af Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 18:45:33 +0200 Subject: [PATCH 07/41] File::getMethodParameters(): fix method to work with the PHP 8 identifier tokens The existing unit tests already contain tests covering this change. Includes updating token offset expectations in pre-existing tests for the change in tokenization. Note: the `T_NAMESPACE` and `T_NS_SEPARATOR` cases should, by rights, be removed what with the new tokenization. However, leaving them in place provides some tolerance for types interlaced with comments/whitespace as allowed in PHP < 8.0. As this is an often used utility method, I believe the method containing such tolerance to ensure the same results PHP cross-version is warranted. --- src/Files/File.php | 5 +- tests/Core/File/GetMethodParametersTest.php | 112 ++++++++++---------- 2 files changed, 60 insertions(+), 57 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index 3ed8f635d4..1f250e6d85 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1415,7 +1415,10 @@ public function getMethodParameters($stackPtr) } break; case T_STRING: - // This is a string, so it may be a type hint, but it could + case T_NAME_QUALIFIED: + case T_NAME_FULLY_QUALIFIED: + case T_NAME_RELATIVE: + // This is an identifier name, so it may be a type declaration, but it could // also be a constant used as a default value. $prevComma = false; for ($t = $i; $t >= $opener; $t--) { diff --git a/tests/Core/File/GetMethodParametersTest.php b/tests/Core/File/GetMethodParametersTest.php index 9e8d5bc6bc..67703ac4bc 100644 --- a/tests/Core/File/GetMethodParametersTest.php +++ b/tests/Core/File/GetMethodParametersTest.php @@ -434,7 +434,7 @@ public function testNullableTypeHint() ]; $expected[1] = [ - 'token' => 14, + 'token' => 13, 'name' => '$var2', 'content' => '?\bar $var2', 'has_attributes' => false, @@ -444,7 +444,7 @@ public function testNullableTypeHint() 'variadic_token' => false, 'type_hint' => '?\bar', 'type_hint_token' => 11, - 'type_hint_end_token' => 12, + 'type_hint_end_token' => 11, 'nullable_type' => true, 'comma_token' => false, ]; @@ -825,7 +825,7 @@ public function testNameSpacedTypeDeclaration() // Offsets are relative to the T_FUNCTION token. $expected = []; $expected[0] = [ - 'token' => 12, + 'token' => 7, 'name' => '$a', 'content' => '\Package\Sub\ClassName $a', 'has_attributes' => false, @@ -835,12 +835,12 @@ public function testNameSpacedTypeDeclaration() 'variadic_token' => false, 'type_hint' => '\Package\Sub\ClassName', 'type_hint_token' => 5, - 'type_hint_end_token' => 10, + 'type_hint_end_token' => 5, 'nullable_type' => false, - 'comma_token' => 13, + 'comma_token' => 8, ]; $expected[1] = [ - 'token' => 20, + 'token' => 13, 'name' => '$b', 'content' => '?Sub\AnotherClass $b', 'has_attributes' => false, @@ -849,8 +849,8 @@ public function testNameSpacedTypeDeclaration() 'variable_length' => false, 'variadic_token' => false, 'type_hint' => '?Sub\AnotherClass', - 'type_hint_token' => 16, - 'type_hint_end_token' => 18, + 'type_hint_token' => 11, + 'type_hint_end_token' => 11, 'nullable_type' => true, 'comma_token' => false, ]; @@ -1248,7 +1248,7 @@ public function testMessyDeclaration() // Offsets are relative to the T_FUNCTION token. $expected = []; $expected[0] = [ - 'token' => 25, + 'token' => 24, 'name' => '$a', 'content' => '// comment ?\MyNS /* comment */ @@ -1261,17 +1261,17 @@ public function testMessyDeclaration() 'variadic_token' => false, 'type_hint' => '?\MyNS\SubCat\MyClass', 'type_hint_token' => 9, - 'type_hint_end_token' => 23, + 'type_hint_end_token' => 22, 'nullable_type' => true, - 'comma_token' => 26, + 'comma_token' => 25, ]; $expected[1] = [ - 'token' => 29, + 'token' => 28, 'name' => '$b', 'content' => "\$b /* comment */ = /* comment */ 'default' /* comment*/", 'default' => "'default' /* comment*/", - 'default_token' => 37, - 'default_equal_token' => 33, + 'default_token' => 36, + 'default_equal_token' => 32, 'has_attributes' => false, 'pass_by_reference' => false, 'reference_token' => false, @@ -1281,10 +1281,10 @@ public function testMessyDeclaration() 'type_hint_token' => false, 'type_hint_end_token' => false, 'nullable_type' => false, - 'comma_token' => 40, + 'comma_token' => 39, ]; $expected[2] = [ - 'token' => 62, + 'token' => 61, 'name' => '$c', 'content' => '// phpcs:ignore Stnd.Cat.Sniff -- For reasons. ? /*comment*/ @@ -1292,12 +1292,12 @@ public function testMessyDeclaration() & /*test*/ ... /* phpcs:ignore */ $c', 'has_attributes' => false, 'pass_by_reference' => true, - 'reference_token' => 54, + 'reference_token' => 53, 'variable_length' => true, - 'variadic_token' => 58, + 'variadic_token' => 57, 'type_hint' => '?bool', - 'type_hint_token' => 50, - 'type_hint_end_token' => 50, + 'type_hint_token' => 49, + 'type_hint_end_token' => 49, 'nullable_type' => true, 'comma_token' => false, ]; @@ -1377,7 +1377,7 @@ public function testNamespaceOperatorTypeHint() // Offsets are relative to the T_FUNCTION token. $expected = []; $expected[0] = [ - 'token' => 9, + 'token' => 7, 'name' => '$var1', 'content' => '?namespace\Name $var1', 'has_attributes' => false, @@ -1387,7 +1387,7 @@ public function testNamespaceOperatorTypeHint() 'variadic_token' => false, 'type_hint' => '?namespace\Name', 'type_hint_token' => 5, - 'type_hint_end_token' => 7, + 'type_hint_end_token' => 5, 'nullable_type' => true, 'comma_token' => false, ]; @@ -1530,7 +1530,7 @@ public function testPHP8UnionTypesTwoClasses() // Offsets are relative to the T_FUNCTION token. $expected = []; $expected[0] = [ - 'token' => 11, + 'token' => 8, 'name' => '$var', 'content' => 'MyClassA|\Package\MyClassB $var', 'has_attributes' => false, @@ -1540,7 +1540,7 @@ public function testPHP8UnionTypesTwoClasses() 'variadic_token' => false, 'type_hint' => 'MyClassA|\Package\MyClassB', 'type_hint_token' => 4, - 'type_hint_end_token' => 9, + 'type_hint_end_token' => 6, 'nullable_type' => false, 'comma_token' => false, ]; @@ -2318,7 +2318,7 @@ public function testParameterAttributesInFunctionDeclaration() // Offsets are relative to the T_FUNCTION token. $expected = []; $expected[0] = [ - 'token' => 17, + 'token' => 14, 'name' => '$constructorPropPromTypedParamSingleAttribute', 'content' => '#[\MyExample\MyAttribute] private string $constructorPropPromTypedParamSingleAttribute', 'has_attributes' => true, @@ -2327,16 +2327,16 @@ public function testParameterAttributesInFunctionDeclaration() 'variable_length' => false, 'variadic_token' => false, 'type_hint' => 'string', - 'type_hint_token' => 15, - 'type_hint_end_token' => 15, + 'type_hint_token' => 12, + 'type_hint_end_token' => 12, 'nullable_type' => false, 'property_visibility' => 'private', - 'visibility_token' => 13, + 'visibility_token' => 10, 'property_readonly' => false, - 'comma_token' => 18, + 'comma_token' => 15, ]; $expected[1] = [ - 'token' => 39, + 'token' => 36, 'name' => '$typedParamSingleAttribute', 'content' => '#[MyAttr([1, 2])] Type|false @@ -2347,13 +2347,13 @@ public function testParameterAttributesInFunctionDeclaration() 'variable_length' => false, 'variadic_token' => false, 'type_hint' => 'Type|false', - 'type_hint_token' => 34, - 'type_hint_end_token' => 36, + 'type_hint_token' => 31, + 'type_hint_end_token' => 33, 'nullable_type' => false, - 'comma_token' => 40, + 'comma_token' => 37, ]; $expected[2] = [ - 'token' => 59, + 'token' => 56, 'name' => '$nullableTypedParamMultiAttribute', 'content' => '#[MyAttribute(1234), MyAttribute(5678)] ?int $nullableTypedParamMultiAttribute', 'has_attributes' => true, @@ -2362,13 +2362,13 @@ public function testParameterAttributesInFunctionDeclaration() 'variable_length' => false, 'variadic_token' => false, 'type_hint' => '?int', - 'type_hint_token' => 57, - 'type_hint_end_token' => 57, + 'type_hint_token' => 54, + 'type_hint_end_token' => 54, 'nullable_type' => true, - 'comma_token' => 60, + 'comma_token' => 57, ]; $expected[3] = [ - 'token' => 74, + 'token' => 71, 'name' => '$nonTypedParamTwoAttributes', 'content' => '#[WithoutArgument] #[SingleArgument(0)] $nonTypedParamTwoAttributes', 'has_attributes' => true, @@ -2380,23 +2380,23 @@ public function testParameterAttributesInFunctionDeclaration() 'type_hint_token' => false, 'type_hint_end_token' => false, 'nullable_type' => false, - 'comma_token' => 75, + 'comma_token' => 72, ]; $expected[4] = [ - 'token' => 95, + 'token' => 92, 'name' => '$otherParam', 'content' => '#[MyAttribute(array("key" => "value"))] &...$otherParam', 'has_attributes' => true, 'pass_by_reference' => true, - 'reference_token' => 93, + 'reference_token' => 90, 'variable_length' => true, - 'variadic_token' => 94, + 'variadic_token' => 91, 'type_hint' => '', 'type_hint_token' => false, 'type_hint_end_token' => false, 'nullable_type' => false, - 'comma_token' => 96, + 'comma_token' => 93, ]; $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); @@ -2505,7 +2505,7 @@ public function testPHP81MoreIntersectionTypes() // Offsets are relative to the T_FUNCTION token. $expected = []; $expected[0] = [ - 'token' => 16, + 'token' => 10, 'name' => '$var', 'content' => 'MyClassA&\Package\MyClassB&\Package\MyClassC $var', 'has_attributes' => false, @@ -2515,7 +2515,7 @@ public function testPHP81MoreIntersectionTypes() 'variadic_token' => false, 'type_hint' => 'MyClassA&\Package\MyClassB&\Package\MyClassC', 'type_hint_token' => 4, - 'type_hint_end_token' => 14, + 'type_hint_end_token' => 8, 'nullable_type' => false, 'comma_token' => false, ]; @@ -2679,12 +2679,12 @@ public function testPHP81NewInInitializers() 'comma_token' => 20, ]; $expected[1] = [ - 'token' => 28, + 'token' => 25, 'name' => '$newToo', 'content' => '\Package\TypeB $newToo = new \Package\TypeB(10, \'string\')', 'default' => "new \Package\TypeB(10, 'string')", - 'default_token' => 32, - 'default_equal_token' => 30, + 'default_token' => 29, + 'default_equal_token' => 27, 'has_attributes' => false, 'pass_by_reference' => false, 'reference_token' => false, @@ -2692,9 +2692,9 @@ public function testPHP81NewInInitializers() 'variadic_token' => false, 'type_hint' => '\Package\TypeB', 'type_hint_token' => 23, - 'type_hint_end_token' => 26, + 'type_hint_end_token' => 23, 'nullable_type' => false, - 'comma_token' => 44, + 'comma_token' => 38, ]; $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); @@ -2728,12 +2728,12 @@ public function testPHP82DNFTypes() 'comma_token' => 22, ]; $expected[1] = [ - 'token' => 41, + 'token' => 37, 'name' => '$obj2', 'content' => '(\Boo&\Pck\Bar)|(Boo&Baz) $obj2 = new Boo()', 'default' => 'new Boo()', - 'default_token' => 45, - 'default_equal_token' => 43, + 'default_token' => 41, + 'default_equal_token' => 39, 'has_attributes' => false, 'pass_by_reference' => false, 'reference_token' => false, @@ -2741,7 +2741,7 @@ public function testPHP82DNFTypes() 'variadic_token' => false, 'type_hint' => '(\Boo&\Pck\Bar)|(Boo&Baz)', 'type_hint_token' => 25, - 'type_hint_end_token' => 39, + 'type_hint_end_token' => 35, 'nullable_type' => false, 'comma_token' => false, ]; @@ -2807,7 +2807,7 @@ public function testPHP82DNFTypesIllegalNullable() // Offsets are relative to the T_FUNCTION token. $expected = []; $expected[0] = [ - 'token' => 27, + 'token' => 21, 'name' => '$var', 'content' => '? ( MyClassA & /*comment*/ \Package\MyClassB & \Package\MyClassC ) $var', 'has_attributes' => false, @@ -2817,7 +2817,7 @@ public function testPHP82DNFTypesIllegalNullable() 'variadic_token' => false, 'type_hint' => '?(MyClassA&\Package\MyClassB&\Package\MyClassC)', 'type_hint_token' => 5, - 'type_hint_end_token' => 25, + 'type_hint_end_token' => 19, 'nullable_type' => true, 'comma_token' => false, ]; From c5b6795586ccd647adce352534a675364ebad13f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 18:47:38 +0200 Subject: [PATCH 08/41] File::getMethodProperties(): fix method to work with the PHP 8 identifier tokens The existing unit tests already contain tests covering this change. Includes updating token offset expectations in pre-existing tests for the change in tokenization. --- src/Files/File.php | 6 ++---- tests/Core/File/GetMethodPropertiesTest.php | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index 1f250e6d85..52dbdb9c99 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1676,8 +1676,8 @@ public function getMethodProperties($stackPtr) $scopeOpener = $this->tokens[$stackPtr]['scope_opener']; } - $valid = [ - T_STRING => T_STRING, + $valid = Tokens::$nameTokens; + $valid += [ T_CALLABLE => T_CALLABLE, T_SELF => T_SELF, T_PARENT => T_PARENT, @@ -1685,8 +1685,6 @@ public function getMethodProperties($stackPtr) T_FALSE => T_FALSE, T_TRUE => T_TRUE, T_NULL => T_NULL, - T_NAMESPACE => T_NAMESPACE, - T_NS_SEPARATOR => T_NS_SEPARATOR, T_TYPE_UNION => T_TYPE_UNION, T_TYPE_INTERSECTION => T_TYPE_INTERSECTION, T_TYPE_OPEN_PARENTHESIS => T_TYPE_OPEN_PARENTHESIS, diff --git a/tests/Core/File/GetMethodPropertiesTest.php b/tests/Core/File/GetMethodPropertiesTest.php index 2dd83328ea..29ef00f7c6 100644 --- a/tests/Core/File/GetMethodPropertiesTest.php +++ b/tests/Core/File/GetMethodPropertiesTest.php @@ -342,7 +342,7 @@ public function testReturnNamespace() 'scope_specified' => false, 'return_type' => '\MyNamespace\MyClass', 'return_type_token' => 7, - 'return_type_end_token' => 10, + 'return_type_end_token' => 7, 'nullable_return_type' => false, 'is_abstract' => false, 'is_final' => false, @@ -368,7 +368,7 @@ public function testReturnMultilineNamespace() 'scope_specified' => false, 'return_type' => '\MyNamespace\MyClass\Foo', 'return_type_token' => 7, - 'return_type_end_token' => 23, + 'return_type_end_token' => 20, 'nullable_return_type' => false, 'is_abstract' => false, 'is_final' => false, @@ -420,7 +420,7 @@ public function testReturnPartiallyQualifiedName() 'scope_specified' => false, 'return_type' => 'Sub\Level\MyClass', 'return_type_token' => 7, - 'return_type_end_token' => 11, + 'return_type_end_token' => 7, 'nullable_return_type' => false, 'is_abstract' => false, 'is_final' => false, @@ -652,7 +652,7 @@ public function testNamespaceOperatorTypeHint() 'scope_specified' => false, 'return_type' => '?namespace\Name', 'return_type_token' => 9, - 'return_type_end_token' => 11, + 'return_type_end_token' => 9, 'nullable_return_type' => true, 'is_abstract' => false, 'is_final' => false, @@ -704,7 +704,7 @@ public function testPHP8UnionTypesTwoClasses() 'scope_specified' => false, 'return_type' => 'MyClassA|\Package\MyClassB', 'return_type_token' => 6, - 'return_type_end_token' => 11, + 'return_type_end_token' => 8, 'nullable_return_type' => false, 'is_abstract' => false, 'is_final' => false, @@ -1044,7 +1044,7 @@ public function testPHP81MoreIntersectionTypes() 'scope_specified' => false, 'return_type' => 'MyClassA&\Package\MyClassB&\Package\MyClassC', 'return_type_token' => 7, - 'return_type_end_token' => 17, + 'return_type_end_token' => 11, 'nullable_return_type' => false, 'is_abstract' => false, 'is_final' => false, @@ -1070,7 +1070,7 @@ public function testPHP81IntersectionArrowFunction() 'scope_specified' => false, 'return_type' => 'MyClassA&\Package\MyClassB', 'return_type_token' => 6, - 'return_type_end_token' => 11, + 'return_type_end_token' => 8, 'nullable_return_type' => false, 'is_abstract' => false, 'is_final' => false, @@ -1252,7 +1252,7 @@ public function testPHP82DNFTypeIllegalNullable() 'scope_specified' => false, 'return_type' => '?(A&\Pck\B)|bool', 'return_type_token' => 8, - 'return_type_end_token' => 17, + 'return_type_end_token' => 14, 'nullable_return_type' => true, 'is_abstract' => false, 'is_final' => false, @@ -1278,7 +1278,7 @@ public function testPHP82DNFTypeClosure() 'scope_specified' => false, 'return_type' => 'object|(namespace\Foo&Countable)', 'return_type_token' => 6, - 'return_type_end_token' => 14, + 'return_type_end_token' => 12, 'nullable_return_type' => false, 'is_abstract' => false, 'is_final' => false, @@ -1304,7 +1304,7 @@ public function testPHP82DNFTypeFn() 'scope_specified' => false, 'return_type' => 'null|(Partially\Qualified&Traversable)|void', 'return_type_token' => 6, - 'return_type_end_token' => 16, + 'return_type_end_token' => 14, 'nullable_return_type' => false, 'is_abstract' => false, 'is_final' => false, From c4540929183737eced4acf6b4c859a4a4bd151b1 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 18:49:35 +0200 Subject: [PATCH 09/41] File::getMemberProperties(): fix method to work with the PHP 8 identifier tokens The existing unit tests already contain tests covering this change. Includes updating token offset expectations in pre-existing tests for the change in tokenization. --- src/Files/File.php | 6 ++---- tests/Core/File/GetMemberPropertiesTest.php | 24 ++++++++++----------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index 52dbdb9c99..83bdabba4c 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1877,16 +1877,14 @@ public function getMemberProperties($stackPtr) if ($i < $stackPtr) { // We've found a type. - $valid = [ - T_STRING => T_STRING, + $valid = Tokens::$nameTokens; + $valid += [ T_CALLABLE => T_CALLABLE, T_SELF => T_SELF, T_PARENT => T_PARENT, T_FALSE => T_FALSE, T_TRUE => T_TRUE, T_NULL => T_NULL, - T_NAMESPACE => T_NAMESPACE, - T_NS_SEPARATOR => T_NS_SEPARATOR, T_TYPE_UNION => T_TYPE_UNION, T_TYPE_INTERSECTION => T_TYPE_INTERSECTION, T_TYPE_OPEN_PARENTHESIS => T_TYPE_OPEN_PARENTHESIS, diff --git a/tests/Core/File/GetMemberPropertiesTest.php b/tests/Core/File/GetMemberPropertiesTest.php index 31ded2d7df..ff2fd7d462 100644 --- a/tests/Core/File/GetMemberPropertiesTest.php +++ b/tests/Core/File/GetMemberPropertiesTest.php @@ -548,7 +548,7 @@ public static function dataGetMemberProperties() 'is_readonly' => false, 'is_final' => false, 'type' => '\MyNamespace\MyClass', - 'type_token' => -5, + 'type_token' => -2, 'type_end_token' => -2, 'nullable_type' => false, ], @@ -576,7 +576,7 @@ public static function dataGetMemberProperties() 'is_readonly' => false, 'is_final' => false, 'type' => '?Folder\ClassName', - 'type_token' => -4, + 'type_token' => -2, 'type_end_token' => -2, 'nullable_type' => true, ], @@ -590,7 +590,7 @@ public static function dataGetMemberProperties() 'is_readonly' => false, 'is_final' => false, 'type' => '\MyNamespace\MyClass\Foo', - 'type_token' => -18, + 'type_token' => -15, 'type_end_token' => -2, 'nullable_type' => false, ], @@ -688,7 +688,7 @@ public static function dataGetMemberProperties() 'is_readonly' => false, 'is_final' => false, 'type' => '?namespace\Name', - 'type_token' => -4, + 'type_token' => -2, 'type_end_token' => -2, 'nullable_type' => true, ], @@ -716,7 +716,7 @@ public static function dataGetMemberProperties() 'is_readonly' => false, 'is_final' => false, 'type' => 'MyClassA|\Package\MyClassB', - 'type_token' => -7, + 'type_token' => -4, 'type_end_token' => -2, 'nullable_type' => false, ], @@ -941,7 +941,7 @@ public static function dataGetMemberProperties() 'is_readonly' => true, 'is_final' => false, 'type' => '\InterfaceA|\Sub\InterfaceB|false', - 'type_token' => -11, + 'type_token' => -7, 'type_end_token' => -3, 'nullable_type' => false, ], @@ -1096,7 +1096,7 @@ public static function dataGetMemberProperties() 'is_readonly' => false, 'is_final' => false, 'type' => '\Foo&Bar', - 'type_token' => -9, + 'type_token' => -8, 'type_end_token' => -2, 'nullable_type' => false, ], @@ -1167,7 +1167,7 @@ public static function dataGetMemberProperties() 'is_readonly' => false, 'is_final' => false, 'type' => '(Foo&\Bar)|bool', - 'type_token' => -9, + 'type_token' => -8, 'type_end_token' => -2, 'nullable_type' => false, ], @@ -1181,7 +1181,7 @@ public static function dataGetMemberProperties() 'is_readonly' => true, 'is_final' => false, 'type' => 'float|(Partially\Qualified&Traversable)', - 'type_token' => -10, + 'type_token' => -8, 'type_end_token' => -2, 'nullable_type' => false, ], @@ -1195,7 +1195,7 @@ public static function dataGetMemberProperties() 'is_readonly' => true, 'is_final' => false, 'type' => '(namespace\Foo&Bar)|string', - 'type_token' => -10, + 'type_token' => -8, 'type_end_token' => -2, 'nullable_type' => false, ], @@ -1209,7 +1209,7 @@ public static function dataGetMemberProperties() 'is_readonly' => false, 'is_final' => false, 'type' => '?(A&\Pck\B)|bool', - 'type_token' => -11, + 'type_token' => -8, 'type_end_token' => -2, 'nullable_type' => true, ], @@ -1335,7 +1335,7 @@ public static function dataGetMemberProperties() 'is_readonly' => false, 'is_final' => true, 'type' => '(Foo&\Bar)|bool', - 'type_token' => -9, + 'type_token' => -8, 'type_end_token' => -2, 'nullable_type' => false, ], From 579cb918b32fbdbd498b45bfb60fbce5f5f26a63 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 18:52:03 +0200 Subject: [PATCH 10/41] File::isReference(): fix method to work with the PHP 8 identifier tokens The existing unit tests already contain tests covering this change. --- src/Files/File.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index 83bdabba4c..beebcae993 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -2085,12 +2085,10 @@ public function isReference($stackPtr) return true; } else { $skip = Tokens::$emptyTokens; - $skip[] = T_NS_SEPARATOR; + $skip += Tokens::$nameTokens; $skip[] = T_SELF; $skip[] = T_PARENT; $skip[] = T_STATIC; - $skip[] = T_STRING; - $skip[] = T_NAMESPACE; $skip[] = T_DOUBLE_COLON; $nextSignificantAfter = $this->findNext( From b2d9194e41009a521be33db6d3a72dd7b6edf9f0 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 18:58:08 +0200 Subject: [PATCH 11/41] File::findExtendedClassName(): fix method to work with the PHP 8 identifier tokens Includes adding an additional test for a PHP 8 identifier name type previously not covered by tests. --- src/Files/File.php | 7 ++----- tests/Core/File/FindExtendedClassNameTest.inc | 3 +++ tests/Core/File/FindExtendedClassNameTest.php | 4 ++++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index beebcae993..6f633501de 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -2778,11 +2778,8 @@ public function findExtendedClassName($stackPtr) return false; } - $find = [ - T_NS_SEPARATOR, - T_STRING, - T_WHITESPACE, - ]; + $find = Tokens::$nameTokens; + $find[] = T_WHITESPACE; $end = $this->findNext($find, ($extendsIndex + 1), ($classOpenerIndex + 1), true); $name = $this->getTokensAsString(($extendsIndex + 1), ($end - $extendsIndex - 1)); diff --git a/tests/Core/File/FindExtendedClassNameTest.inc b/tests/Core/File/FindExtendedClassNameTest.inc index dae2cd9692..afa3ec83d1 100644 --- a/tests/Core/File/FindExtendedClassNameTest.inc +++ b/tests/Core/File/FindExtendedClassNameTest.inc @@ -15,6 +15,9 @@ class testFECNNamespacedClass extends \PHP_CodeSniffer\Tests\Core\File\testFECNC /* testExtendsPartiallyQualifiedClass */ class testFECNQualifiedClass extends Core\File\RelativeClass {} +/* testExtendsNamespaceRelativeClass */ +class testWithNSOperator extends namespace\Bar {} + /* testNonExtendedInterface */ interface testFECNInterface {} diff --git a/tests/Core/File/FindExtendedClassNameTest.php b/tests/Core/File/FindExtendedClassNameTest.php index 273fa2c305..1ae71b8058 100644 --- a/tests/Core/File/FindExtendedClassNameTest.php +++ b/tests/Core/File/FindExtendedClassNameTest.php @@ -93,6 +93,10 @@ public static function dataExtendedClass() 'identifier' => '/* testExtendsPartiallyQualifiedClass */', 'expected' => 'Core\File\RelativeClass', ], + 'class extends namespace relative class' => [ + 'identifier' => '/* testExtendsNamespaceRelativeClass */', + 'expected' => 'namespace\Bar', + ], 'interface does not extend' => [ 'identifier' => '/* testNonExtendedInterface */', 'expected' => false, From 0283b778f47860d807e63506e849f8fa70fc8dce Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 19:02:13 +0200 Subject: [PATCH 12/41] File::findImplementedInterfaceNames(): fix method to work with the PHP 8 identifier tokens Includes adding an additional test for a PHP 8 identifier name type previously not covered by tests. --- src/Files/File.php | 9 +++------ tests/Core/File/FindImplementedInterfaceNamesTest.inc | 3 +++ tests/Core/File/FindImplementedInterfaceNamesTest.php | 7 +++++++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index 6f633501de..d93e9799c9 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -2827,12 +2827,9 @@ public function findImplementedInterfaceNames($stackPtr) return false; } - $find = [ - T_NS_SEPARATOR, - T_STRING, - T_WHITESPACE, - T_COMMA, - ]; + $find = Tokens::$nameTokens; + $find[] = T_WHITESPACE; + $find[] = T_COMMA; $end = $this->findNext($find, ($implementsIndex + 1), ($classOpenerIndex + 1), true); $name = $this->getTokensAsString(($implementsIndex + 1), ($end - $implementsIndex - 1)); diff --git a/tests/Core/File/FindImplementedInterfaceNamesTest.inc b/tests/Core/File/FindImplementedInterfaceNamesTest.inc index 3246efa2ce..50d38b88b2 100644 --- a/tests/Core/File/FindImplementedInterfaceNamesTest.inc +++ b/tests/Core/File/FindImplementedInterfaceNamesTest.inc @@ -21,6 +21,9 @@ class testFIINNamespacedClass implements \PHP_CodeSniffer\Tests\Core\File\testFI /* testImplementsPartiallyQualified */ class testFIINQualifiedClass implements Core\File\RelativeInterface {} +/* testImplementsMultipleNamespaceRelativeInterfaces */ +class testMultiImplementedNSOperator implements namespace\testInterfaceA, namespace\testInterfaceB {} + /* testClassThatExtendsAndImplements */ class testFECNClassThatExtendsAndImplements extends testFECNClass implements InterfaceA, \NameSpaced\Cat\InterfaceB {} diff --git a/tests/Core/File/FindImplementedInterfaceNamesTest.php b/tests/Core/File/FindImplementedInterfaceNamesTest.php index 7dfbf4044a..4077efef08 100644 --- a/tests/Core/File/FindImplementedInterfaceNamesTest.php +++ b/tests/Core/File/FindImplementedInterfaceNamesTest.php @@ -109,6 +109,13 @@ public static function dataImplementedInterface() 'Core\File\RelativeInterface', ], ], + 'class implements multiple interfaces, namespace relative' => [ + 'identifier' => '/* testImplementsMultipleNamespaceRelativeInterfaces */', + 'expected' => [ + 'namespace\testInterfaceA', + 'namespace\testInterfaceB', + ], + ], 'class extends and implements' => [ 'identifier' => '/* testClassThatExtendsAndImplements */', 'expected' => [ From 599f885a7aa0e4c17327c585e57a376a422282e0 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 18:55:35 +0200 Subject: [PATCH 13/41] File::find[Start|End]OfStatement(): update tests to allow for PHP 8 identifier tokens --- tests/Core/File/FindEndOfStatementTest.php | 2 +- tests/Core/File/FindStartOfStatementTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Core/File/FindEndOfStatementTest.php b/tests/Core/File/FindEndOfStatementTest.php index 5475287934..3e73fe93aa 100644 --- a/tests/Core/File/FindEndOfStatementTest.php +++ b/tests/Core/File/FindEndOfStatementTest.php @@ -196,7 +196,7 @@ public function testUseGroup() $start = $this->getTargetToken('/* testUseGroup */', T_USE); $found = self::$phpcsFile->findEndOfStatement($start); - $this->assertSame(($start + 23), $found); + $this->assertSame(($start + 21), $found); }//end testUseGroup() diff --git a/tests/Core/File/FindStartOfStatementTest.php b/tests/Core/File/FindStartOfStatementTest.php index cde94f2ba2..6da8234f9e 100644 --- a/tests/Core/File/FindStartOfStatementTest.php +++ b/tests/Core/File/FindStartOfStatementTest.php @@ -246,7 +246,7 @@ public function testUseGroup() $start = $this->getTargetToken('/* testUseGroup */', T_SEMICOLON); $found = self::$phpcsFile->findStartOfStatement($start); - $this->assertSame(($start - 23), $found); + $this->assertSame(($start - 21), $found); }//end testUseGroup() From 9f7a0a6f880db43847067c77482596ff34686620 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 6 Apr 2025 20:13:39 +0200 Subject: [PATCH 14/41] File::getTokenAsString(): update tests to allow for PHP 8 identifier tokens --- tests/Core/File/GetTokensAsStringTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Core/File/GetTokensAsStringTest.php b/tests/Core/File/GetTokensAsStringTest.php index c14db1a7ef..d993cba1c5 100644 --- a/tests/Core/File/GetTokensAsStringTest.php +++ b/tests/Core/File/GetTokensAsStringTest.php @@ -243,7 +243,7 @@ public static function dataGetTokensAsString() 'namespace' => [ 'testMarker' => '/* testNamespace */', 'startTokenType' => T_NAMESPACE, - 'length' => 8, + 'length' => 4, 'expected' => 'namespace Foo\Bar\Baz;', ], 'use-with-comments' => [ From 6c6fd3dba674d2348cdd4c431a9c25f21cfaf9f7 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 2 Sep 2020 03:52:36 +0200 Subject: [PATCH 15/41] Tokens::$functionNameTokens: add the new PHP 8 identifier name tokens Includes adding/updating tests for various sniffs using the `Tokens::$functionNameTokens` token array to make sure the behaviour is unchanged (without adding the new tokens to the token array, the behaviour would have been changed). --- .../ArbitraryParenthesesSpacingUnitTest.1.inc | 10 +++++++ ...raryParenthesesSpacingUnitTest.1.inc.fixed | 10 +++++++ .../FunctionCallSignatureUnitTest.inc | 6 ++-- .../FunctionCallSignatureUnitTest.inc.fixed | 6 ++-- .../ComparisonOperatorUsageUnitTest.inc | 10 +++++++ .../ComparisonOperatorUsageUnitTest.php | 4 +++ src/Util/Tokens.php | 29 ++++++++++--------- 7 files changed, 56 insertions(+), 19 deletions(-) diff --git a/src/Standards/Generic/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.1.inc b/src/Standards/Generic/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.1.inc index 4ce050840f..b531b36648 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.1.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.1.inc @@ -190,3 +190,13 @@ switch ( $type ) { default: break; } + +function NonArbitraryParenthesesNameSpacedNames( $foo, $bar ) { + $b = \Fully\Qualified( $something ); + $b = Partially\Qualified( $something ) {}; + $c = namespace\Relative( $arg1 , $arg2 = array( ) ); + + $a = new \Fully\Qualified( $something ); + $b = new Partially\Qualified( $something ); + $c = new namespace\Relative( $something ); +} diff --git a/src/Standards/Generic/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.1.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.1.inc.fixed index a002280811..ef3b372f2d 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.1.inc.fixed +++ b/src/Standards/Generic/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.1.inc.fixed @@ -178,3 +178,13 @@ switch ( $type ) { default: break; } + +function NonArbitraryParenthesesNameSpacedNames( $foo, $bar ) { + $b = \Fully\Qualified( $something ); + $b = Partially\Qualified( $something ) {}; + $c = namespace\Relative( $arg1 , $arg2 = array( ) ); + + $a = new \Fully\Qualified( $something ); + $b = new Partially\Qualified( $something ); + $c = new namespace\Relative( $something ); +} diff --git a/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc b/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc index fddd3cba18..6f2c90a367 100644 --- a/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc +++ b/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc @@ -317,7 +317,7 @@ foo( $e ); -test( +Partially\Qualified\test( 1,2,3,4 ); @@ -374,7 +374,7 @@ return (function ($a, $b) { function foo() { - Bar( + \Bar( function () { } ); @@ -390,7 +390,7 @@ $deprecated_functions = [ $deprecated_functions = [ 'the_category_ID' - => function_call( // 9 spaces, not 8. This is the problem line. + => namespace\function_call( // 9 spaces, not 8. This is the problem line. $a, $b ), diff --git a/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc.fixed b/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc.fixed index 1c52523075..0f84e2636f 100644 --- a/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc.fixed +++ b/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc.fixed @@ -324,7 +324,7 @@ foo( $e ); -test( +Partially\Qualified\test( 1,2,3,4 ); @@ -387,7 +387,7 @@ return (function ($a, $b) { function foo() { - Bar( + \Bar( function () { } ); @@ -403,7 +403,7 @@ $deprecated_functions = [ $deprecated_functions = [ 'the_category_ID' - => function_call( // 9 spaces, not 8. This is the problem line. + => namespace\function_call( // 9 spaces, not 8. This is the problem line. $a, $b ), diff --git a/src/Standards/Squiz/Tests/Operators/ComparisonOperatorUsageUnitTest.inc b/src/Standards/Squiz/Tests/Operators/ComparisonOperatorUsageUnitTest.inc index 8522438dcf..49084540d7 100644 --- a/src/Standards/Squiz/Tests/Operators/ComparisonOperatorUsageUnitTest.inc +++ b/src/Standards/Squiz/Tests/Operators/ComparisonOperatorUsageUnitTest.inc @@ -136,3 +136,13 @@ if (empty($argTags > 0)) { } myFunction($var1 === true ? "" : "foobar"); + +if (unqualified($argTags > 0) === true) {} +if (Partially\qualified($argTags > 0) === false) {} +if (\Fully\qualified($argTags > 0) === true) {} +if (namespace\relative($argTags > 0) === false) {} + +if (unqualified($argTags > 0)) {} +if (Partially\qualified($argTags > 0)) {} +if (\Fully\qualified($argTags > 0)) {} +if (namespace\relative($argTags > 0)) {} diff --git a/src/Standards/Squiz/Tests/Operators/ComparisonOperatorUsageUnitTest.php b/src/Standards/Squiz/Tests/Operators/ComparisonOperatorUsageUnitTest.php index 2d6dbd329c..ca5864fdde 100644 --- a/src/Standards/Squiz/Tests/Operators/ComparisonOperatorUsageUnitTest.php +++ b/src/Standards/Squiz/Tests/Operators/ComparisonOperatorUsageUnitTest.php @@ -59,6 +59,10 @@ public function getErrorList() 127 => 1, 131 => 1, 135 => 1, + 145 => 1, + 146 => 1, + 147 => 1, + 148 => 1, ]; }//end getErrorList() diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index 4fe33227d4..3179acb8cb 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -587,19 +587,22 @@ final class Tokens * @var array */ public static $functionNameTokens = [ - T_STRING => T_STRING, - T_EVAL => T_EVAL, - T_EXIT => T_EXIT, - T_INCLUDE => T_INCLUDE, - T_INCLUDE_ONCE => T_INCLUDE_ONCE, - T_REQUIRE => T_REQUIRE, - T_REQUIRE_ONCE => T_REQUIRE_ONCE, - T_ISSET => T_ISSET, - T_UNSET => T_UNSET, - T_EMPTY => T_EMPTY, - T_SELF => T_SELF, - T_PARENT => T_PARENT, - T_STATIC => T_STATIC, + T_STRING => T_STRING, + T_NAME_QUALIFIED => T_NAME_QUALIFIED, + T_NAME_FULLY_QUALIFIED => T_NAME_FULLY_QUALIFIED, + T_NAME_RELATIVE => T_NAME_RELATIVE, + T_EVAL => T_EVAL, + T_EXIT => T_EXIT, + T_INCLUDE => T_INCLUDE, + T_INCLUDE_ONCE => T_INCLUDE_ONCE, + T_REQUIRE => T_REQUIRE, + T_REQUIRE_ONCE => T_REQUIRE_ONCE, + T_ISSET => T_ISSET, + T_UNSET => T_UNSET, + T_EMPTY => T_EMPTY, + T_SELF => T_SELF, + T_PARENT => T_PARENT, + T_STATIC => T_STATIC, ]; /** From 3e405f901010b214b455bcd63d7315e07dc082be Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 19:35:59 +0200 Subject: [PATCH 16/41] Generic/DuplicateClassName: fix sniff to work with the PHP 8 identifier tokens The existing unit tests already contain tests covering this change. Includes updating one test. That test was testing that a namespace name interlaced with comments and whitespace would still be recognized as the same namespace as one without. As namespaced names are no longer allowed to have whitespace or comments within them as of PHP 8.0 and we now use the PHP 8.0 tokenization, the sniff will no longer handle that situation. Aside from that, the test still has value, so keeping it, but making sure the PHP 8.0+ parse error is removed. --- .../Classes/DuplicateClassNameSniff.php | 24 +++++++------------ .../Classes/DuplicateClassNameUnitTest.8.inc | 2 +- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php b/src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php index dd0454797d..cfa03af7df 100644 --- a/src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php +++ b/src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php @@ -63,24 +63,16 @@ public function process(File $phpcsFile, $stackPtr) // Keep track of what namespace we are in. if ($tokens[$stackPtr]['code'] === T_NAMESPACE) { $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); - if ($nextNonEmpty !== false - // Ignore namespace keyword used as operator. - && $tokens[$nextNonEmpty]['code'] !== T_NS_SEPARATOR - ) { - $namespace = ''; - for ($i = $nextNonEmpty; $i < $phpcsFile->numTokens; $i++) { - if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) { - continue; - } - - if ($tokens[$i]['code'] !== T_STRING && $tokens[$i]['code'] !== T_NS_SEPARATOR) { - break; - } - - $namespace .= $tokens[$i]['content']; + if ($nextNonEmpty !== false) { + if ($tokens[$nextNonEmpty]['code'] === T_STRING + || $tokens[$nextNonEmpty]['code'] === T_NAME_QUALIFIED + ) { + $namespace = $tokens[$nextNonEmpty]['content']; + } else if ($tokens[$nextNonEmpty]['code'] === T_OPEN_CURLY_BRACKET) { + $namespace = ''; } - $stackPtr = $i; + $stackPtr = $nextNonEmpty; } } else { $name = $phpcsFile->getDeclarationName($stackPtr); diff --git a/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.8.inc b/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.8.inc index 4ae5578269..ae6742d890 100644 --- a/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.8.inc +++ b/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.8.inc @@ -3,6 +3,6 @@ namespace Foo\Bar; class MyClass {} interface YourInterface {} -namespace Foo /*comment*/ \ /*comment*/ Bar; +namespace /*comment*/ Foo\Bar; class MyClass {} interface YourInterface {} From b24190551a18b38179022017817383f83d8311d4 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 9 Apr 2025 00:17:16 +0200 Subject: [PATCH 17/41] Generic/ForLoopWithTestFunctionCall: fix sniff to work with the PHP 8 identifier tokens Includes extra unit tests covering the change in so far it wasn't already covered. --- .../Sniffs/CodeAnalysis/ForLoopWithTestFunctionCallSniff.php | 2 +- .../CodeAnalysis/ForLoopWithTestFunctionCallUnitTest.1.inc | 4 ++++ .../CodeAnalysis/ForLoopWithTestFunctionCallUnitTest.php | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Standards/Generic/Sniffs/CodeAnalysis/ForLoopWithTestFunctionCallSniff.php b/src/Standards/Generic/Sniffs/CodeAnalysis/ForLoopWithTestFunctionCallSniff.php index 0374a8f757..b2915e5002 100644 --- a/src/Standards/Generic/Sniffs/CodeAnalysis/ForLoopWithTestFunctionCallSniff.php +++ b/src/Standards/Generic/Sniffs/CodeAnalysis/ForLoopWithTestFunctionCallSniff.php @@ -80,7 +80,7 @@ public function process(File $phpcsFile, $stackPtr) continue; } else if ($position > 1) { break; - } else if ($code !== T_VARIABLE && $code !== T_STRING) { + } else if ($code !== T_VARIABLE && isset(Tokens::$nameTokens[$code]) === false) { continue; } diff --git a/src/Standards/Generic/Tests/CodeAnalysis/ForLoopWithTestFunctionCallUnitTest.1.inc b/src/Standards/Generic/Tests/CodeAnalysis/ForLoopWithTestFunctionCallUnitTest.1.inc index 31f1a64199..5c361290de 100644 --- a/src/Standards/Generic/Tests/CodeAnalysis/ForLoopWithTestFunctionCallUnitTest.1.inc +++ b/src/Standards/Generic/Tests/CodeAnalysis/ForLoopWithTestFunctionCallUnitTest.1.inc @@ -93,3 +93,7 @@ endfor; for ($i = 0; $i < 10; $i = increment($i)) {} for ($i = initialValue(); $i < 10; $i = increment($i)) {} + +for ($i = 0; \Fully\qualified($value); $i++) {} +for ($i = 0; Partially\qualified($value); $i++) {} +for ($i = 0; namespace\relatived($value); $i++) {} diff --git a/src/Standards/Generic/Tests/CodeAnalysis/ForLoopWithTestFunctionCallUnitTest.php b/src/Standards/Generic/Tests/CodeAnalysis/ForLoopWithTestFunctionCallUnitTest.php index 74690b9f22..bf214f1ca8 100644 --- a/src/Standards/Generic/Tests/CodeAnalysis/ForLoopWithTestFunctionCallUnitTest.php +++ b/src/Standards/Generic/Tests/CodeAnalysis/ForLoopWithTestFunctionCallUnitTest.php @@ -64,6 +64,9 @@ public function getWarningList($testFile='') 66 => 1, 72 => 1, 81 => 1, + 97 => 1, + 98 => 1, + 99 => 1, ]; default: return []; From 21298f6699a87f65c1c612eb0dc817ecc641a128 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 9 Apr 2025 00:37:26 +0200 Subject: [PATCH 18/41] Generic/DisallowYodaConditions: fix sniff to work with the PHP 8 identifier tokens Includes extra unit tests covering the change in so far it wasn't already covered. --- .../ControlStructures/DisallowYodaConditionsSniff.php | 2 +- .../ControlStructures/DisallowYodaConditionsUnitTest.inc | 7 +++++++ .../ControlStructures/DisallowYodaConditionsUnitTest.php | 3 +++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Standards/Generic/Sniffs/ControlStructures/DisallowYodaConditionsSniff.php b/src/Standards/Generic/Sniffs/ControlStructures/DisallowYodaConditionsSniff.php index 666b1916e7..6a26b1f1ea 100644 --- a/src/Standards/Generic/Sniffs/ControlStructures/DisallowYodaConditionsSniff.php +++ b/src/Standards/Generic/Sniffs/ControlStructures/DisallowYodaConditionsSniff.php @@ -88,7 +88,7 @@ public function process(File $phpcsFile, $stackPtr) ); if ($beforeOpeningParenthesisIndex === false || $tokens[$beforeOpeningParenthesisIndex]['code'] !== T_ARRAY) { - if ($tokens[$beforeOpeningParenthesisIndex]['code'] === T_STRING) { + if (isset(Tokens::$nameTokens[$tokens[$beforeOpeningParenthesisIndex]['code']]) === true) { return; } diff --git a/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.inc b/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.inc index 27053c4d65..b9cad4d3ea 100644 --- a/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.inc +++ b/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.inc @@ -185,3 +185,10 @@ echo match ($text) { 1 >= $value; 1 <= $value; 1 <=> $value; + +if(\Fully\qualified('foo') === false){} +if(true === \Fully\qualified(10)){} +if(Partially\qualified('foo') === 10){} +if(1.5 === Partially\qualified(true)){} +if(namespace\relative(false) === null){} +if('string' === namespace\relative(null)){} diff --git a/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.php b/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.php index db231cdc48..510bd7d52e 100644 --- a/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.php +++ b/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.php @@ -72,6 +72,9 @@ public function getErrorList() 185 => 1, 186 => 1, 187 => 1, + 190 => 1, + 192 => 1, + 194 => 1, ]; }//end getErrorList() From 187c7d886765250a1550ce930bdf8ef15d8dcd0f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 9 Apr 2025 00:41:05 +0200 Subject: [PATCH 19/41] Generic/FunctionCallArgumentSpacing: fix sniff to work with the PHP 8 identifier tokens Includes extra unit tests covering the change in so far it wasn't already covered. --- .../FunctionCallArgumentSpacingSniff.php | 22 +++++++++---------- .../FunctionCallArgumentSpacingUnitTest.1.inc | 4 ++++ ...ionCallArgumentSpacingUnitTest.1.inc.fixed | 4 ++++ .../FunctionCallArgumentSpacingUnitTest.php | 3 +++ 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php b/src/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php index bf93c55338..9339f0c36f 100644 --- a/src/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php +++ b/src/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php @@ -24,17 +24,17 @@ class FunctionCallArgumentSpacingSniff implements Sniff */ public function register() { - return[ - T_STRING, - T_ISSET, - T_UNSET, - T_SELF, - T_STATIC, - T_PARENT, - T_VARIABLE, - T_CLOSE_CURLY_BRACKET, - T_CLOSE_PARENTHESIS, - ]; + $targets = Tokens::$nameTokens; + $targets[] = T_ISSET; + $targets[] = T_UNSET; + $targets[] = T_SELF; + $targets[] = T_STATIC; + $targets[] = T_PARENT; + $targets[] = T_VARIABLE; + $targets[] = T_CLOSE_CURLY_BRACKET; + $targets[] = T_CLOSE_PARENTHESIS; + + return $targets; }//end register() diff --git a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.1.inc b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.1.inc index 393ee10b66..a7aa8de6fe 100644 --- a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.1.inc +++ b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.1.inc @@ -197,3 +197,7 @@ $foobar = functionCallMatchParam( } , // But check the spacing again once the match expression has finished. $args ); + +$result = \Fully\qualified($arg1,$arg2); +$result = Partially\qualified($arg1 , $arg2); +$result = namespace\relative($arg1 , $arg2); diff --git a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.1.inc.fixed b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.1.inc.fixed index a50f695e45..1122576310 100644 --- a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.1.inc.fixed +++ b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.1.inc.fixed @@ -197,3 +197,7 @@ $foobar = functionCallMatchParam( }, // But check the spacing again once the match expression has finished. $args ); + +$result = \Fully\qualified($arg1, $arg2); +$result = Partially\qualified($arg1, $arg2); +$result = namespace\relative($arg1, $arg2); diff --git a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.php b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.php index a068ee6b48..752db95c0f 100644 --- a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.php +++ b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.php @@ -69,6 +69,9 @@ public function getErrorList($testFile='') 190 => 2, 191 => 2, 197 => 1, + 201 => 1, + 202 => 1, + 203 => 2, ]; default: From b09b045faad2389d69d10a16d39f65ccfc57d464 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 9 Apr 2025 00:49:46 +0200 Subject: [PATCH 20/41] Generic/UpperCaseConstantName: fix sniff to work with the PHP 8 identifier tokens Includes extra unit tests covering the change in so far it wasn't already covered. --- .../Sniffs/NamingConventions/UpperCaseConstantNameSniff.php | 3 ++- .../NamingConventions/UpperCaseConstantNameUnitTest.1.inc | 3 +++ .../Tests/NamingConventions/UpperCaseConstantNameUnitTest.php | 4 +++- tests/Core/Ruleset/PopulateTokenListenersTest.php | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Standards/Generic/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php index 41b5b61240..95884c206c 100644 --- a/src/Standards/Generic/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php +++ b/src/Standards/Generic/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php @@ -26,6 +26,7 @@ public function register() { return [ T_STRING, + T_NAME_FULLY_QUALIFIED, T_CONST, ]; @@ -83,7 +84,7 @@ public function process(File $phpcsFile, $stackPtr) }//end if // Only interested in define statements now. - if (strtolower($tokens[$stackPtr]['content']) !== 'define') { + if (strtolower(ltrim($tokens[$stackPtr]['content'], '\\')) !== 'define') { return; } diff --git a/src/Standards/Generic/Tests/NamingConventions/UpperCaseConstantNameUnitTest.1.inc b/src/Standards/Generic/Tests/NamingConventions/UpperCaseConstantNameUnitTest.1.inc index c5af2349cb..a1060b7e58 100644 --- a/src/Standards/Generic/Tests/NamingConventions/UpperCaseConstantNameUnitTest.1.inc +++ b/src/Standards/Generic/Tests/NamingConventions/UpperCaseConstantNameUnitTest.1.inc @@ -89,3 +89,6 @@ class MyClass { $a = ($cond) ? DEFINE : SOMETHING_ELSE; $object = new Define('value'); + +\define('VALID_NAME', true); +\define('invalidName', true); diff --git a/src/Standards/Generic/Tests/NamingConventions/UpperCaseConstantNameUnitTest.php b/src/Standards/Generic/Tests/NamingConventions/UpperCaseConstantNameUnitTest.php index 3249f72256..749b539691 100644 --- a/src/Standards/Generic/Tests/NamingConventions/UpperCaseConstantNameUnitTest.php +++ b/src/Standards/Generic/Tests/NamingConventions/UpperCaseConstantNameUnitTest.php @@ -48,10 +48,12 @@ public function getErrorList($testFile='') 51 => 1, 71 => 1, 73 => 1, + 94 => 1, ]; + default: return []; - } + }//end switch }//end getErrorList() diff --git a/tests/Core/Ruleset/PopulateTokenListenersTest.php b/tests/Core/Ruleset/PopulateTokenListenersTest.php index 68a30e3a4b..2dd88f905e 100644 --- a/tests/Core/Ruleset/PopulateTokenListenersTest.php +++ b/tests/Core/Ruleset/PopulateTokenListenersTest.php @@ -131,7 +131,7 @@ public static function dataSniffListensToTokenss() return [ 'Generic.NamingConventions.UpperCaseConstantName' => [ 'sniffClass' => 'PHP_CodeSniffer\\Standards\\Generic\\Sniffs\\NamingConventions\\UpperCaseConstantNameSniff', - 'expectedCount' => 2, + 'expectedCount' => 3, ], 'PSR1.Files.SideEffects' => [ 'sniffClass' => 'PHP_CodeSniffer\\Standards\\PSR1\\Sniffs\\Files\\SideEffectsSniff', From 3a3f9d1525fa07eaf70491fd48ae8c8bf6ea3fbe Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 19:50:27 +0200 Subject: [PATCH 21/41] Generic/ForbiddenFunctions: fix sniff to work with the PHP 8 identifier tokens The existing unit tests already cover this change. --- .../Sniffs/PHP/ForbiddenFunctionsSniff.php | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Standards/Generic/Sniffs/PHP/ForbiddenFunctionsSniff.php b/src/Standards/Generic/Sniffs/PHP/ForbiddenFunctionsSniff.php index b5fe8e28ba..1b733dc134 100644 --- a/src/Standards/Generic/Sniffs/PHP/ForbiddenFunctionsSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/ForbiddenFunctionsSniff.php @@ -70,7 +70,10 @@ public function register() $this->forbiddenFunctionNames[$i] = '/'.$name.'/i'; } - return [T_STRING]; + return [ + T_STRING, + T_NAME_FULLY_QUALIFIED, + ]; } // If we are not pattern matching, we need to work out what @@ -102,7 +105,10 @@ public function register() $this->forbiddenFunctionNames = array_map('strtolower', $this->forbiddenFunctionNames); $this->forbiddenFunctions = array_combine($this->forbiddenFunctionNames, $this->forbiddenFunctions); - return array_unique($register); + $targets = array_unique($register); + $targets[] = T_NAME_FULLY_QUALIFIED; + + return $targets; }//end register() @@ -132,22 +138,11 @@ public function process(File $phpcsFile, $stackPtr) T_AS => true, T_NEW => true, T_INSTEADOF => true, - T_NS_SEPARATOR => true, T_IMPLEMENTS => true, ]; $prevToken = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); - // If function call is directly preceded by a NS_SEPARATOR it points to the - // global namespace, so we should still catch it. - if ($tokens[$prevToken]['code'] === T_NS_SEPARATOR) { - $prevToken = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prevToken - 1), null, true); - if ($tokens[$prevToken]['code'] === T_STRING) { - // Not in the global namespace. - return; - } - } - if (isset($ignore[$tokens[$prevToken]['code']]) === true) { // Not a call to a PHP function. return; @@ -159,7 +154,9 @@ public function process(File $phpcsFile, $stackPtr) return; } - if ($tokens[$stackPtr]['code'] === T_STRING && $tokens[$nextToken]['code'] !== T_OPEN_PARENTHESIS) { + if (($tokens[$stackPtr]['code'] === T_STRING || $tokens[$stackPtr]['code'] === T_NAME_FULLY_QUALIFIED) + && $tokens[$nextToken]['code'] !== T_OPEN_PARENTHESIS + ) { // Not a call to a PHP function. return; } @@ -170,8 +167,11 @@ public function process(File $phpcsFile, $stackPtr) } $function = strtolower($tokens[$stackPtr]['content']); - $pattern = null; + if ($tokens[$stackPtr]['code'] === T_NAME_FULLY_QUALIFIED) { + $function = ltrim($function, '\\'); + } + $pattern = null; if ($this->patternMatch === true) { $count = 0; $pattern = preg_replace( @@ -194,7 +194,7 @@ public function process(File $phpcsFile, $stackPtr) } }//end if - $this->addError($phpcsFile, $stackPtr, $tokens[$stackPtr]['content'], $pattern); + $this->addError($phpcsFile, $stackPtr, $function, $pattern); }//end process() From 0c853adfd0439e7e43d2f1c1cb16d9d878b28c68 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 9 Apr 2025 00:56:56 +0200 Subject: [PATCH 22/41] Generic/SAPIUsage: fix sniff to work with the PHP 8 identifier tokens Includes extra unit tests covering the change in so far it wasn't already covered. Includes minor efficiency tweak. --- .../Generic/Sniffs/PHP/SAPIUsageSniff.php | 17 +++++++++++------ .../Generic/Tests/PHP/SAPIUsageUnitTest.inc | 3 +++ .../Generic/Tests/PHP/SAPIUsageUnitTest.php | 5 ++++- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/Standards/Generic/Sniffs/PHP/SAPIUsageSniff.php b/src/Standards/Generic/Sniffs/PHP/SAPIUsageSniff.php index 54b6e69ee4..555e9cdeb2 100644 --- a/src/Standards/Generic/Sniffs/PHP/SAPIUsageSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/SAPIUsageSniff.php @@ -23,7 +23,10 @@ class SAPIUsageSniff implements Sniff */ public function register() { - return [T_STRING]; + return [ + T_STRING, + T_NAME_FULLY_QUALIFIED, + ]; }//end register() @@ -41,6 +44,11 @@ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); + $function = strtolower(ltrim($tokens[$stackPtr]['content'], '\\')); + if ($function !== 'php_sapi_name') { + return; + } + $ignore = [ T_DOUBLE_COLON => true, T_OBJECT_OPERATOR => true, @@ -55,11 +63,8 @@ public function process(File $phpcsFile, $stackPtr) return; } - $function = strtolower($tokens[$stackPtr]['content']); - if ($function === 'php_sapi_name') { - $error = 'Use the PHP_SAPI constant instead of calling php_sapi_name()'; - $phpcsFile->addError($error, $stackPtr, 'FunctionFound'); - } + $error = 'Use the PHP_SAPI constant instead of calling php_sapi_name()'; + $phpcsFile->addError($error, $stackPtr, 'FunctionFound'); }//end process() diff --git a/src/Standards/Generic/Tests/PHP/SAPIUsageUnitTest.inc b/src/Standards/Generic/Tests/PHP/SAPIUsageUnitTest.inc index f0f350f374..ae232f4599 100644 --- a/src/Standards/Generic/Tests/PHP/SAPIUsageUnitTest.inc +++ b/src/Standards/Generic/Tests/PHP/SAPIUsageUnitTest.inc @@ -3,3 +3,6 @@ if (php_sapi_name() !== 'cli') {} if (PHP_SAPI !== 'cli') {} if ($object->php_sapi_name() === true) {} if ($object?->php_sapi_name() === true) {} +if (\php_sapi_name() !== 'cli') {} +if (\NotPHPNative\php_sapi_name() !== 'cli') {} +if (Qualified\php_sapi_name() !== 'cli') {} diff --git a/src/Standards/Generic/Tests/PHP/SAPIUsageUnitTest.php b/src/Standards/Generic/Tests/PHP/SAPIUsageUnitTest.php index 42eac06ed2..31b0d31e1e 100644 --- a/src/Standards/Generic/Tests/PHP/SAPIUsageUnitTest.php +++ b/src/Standards/Generic/Tests/PHP/SAPIUsageUnitTest.php @@ -30,7 +30,10 @@ final class SAPIUsageUnitTest extends AbstractSniffTestCase */ public function getErrorList() { - return [2 => 1]; + return [ + 2 => 1, + 6 => 1, + ]; }//end getErrorList() From 66552e50c21b7b7376f32dc98b8ed503bb7a5126 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 9 Apr 2025 01:19:11 +0200 Subject: [PATCH 23/41] Generic/IncrementDecrementSpacing: fix sniff to work with the PHP 8 identifier tokens Includes extra unit tests covering the change in so far it wasn't already covered. Some of the new tests cover situations previously not handled by the sniff. The fact that those are now handled, fixes issue 133. Fixes 133 --- .../WhiteSpace/IncrementDecrementSpacingSniff.php | 5 +++-- .../IncrementDecrementSpacingUnitTest.inc | 15 +++++++++++++++ .../IncrementDecrementSpacingUnitTest.inc.fixed | 14 ++++++++++++++ .../IncrementDecrementSpacingUnitTest.php | 6 ++++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/Standards/Generic/Sniffs/WhiteSpace/IncrementDecrementSpacingSniff.php b/src/Standards/Generic/Sniffs/WhiteSpace/IncrementDecrementSpacingSniff.php index 413a3f01bf..106fd75b2b 100644 --- a/src/Standards/Generic/Sniffs/WhiteSpace/IncrementDecrementSpacingSniff.php +++ b/src/Standards/Generic/Sniffs/WhiteSpace/IncrementDecrementSpacingSniff.php @@ -53,7 +53,8 @@ public function process(File $phpcsFile, $stackPtr) // Is this a pre-increment/decrement ? $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); if ($nextNonEmpty !== false - && ($tokens[$nextNonEmpty]['code'] === T_VARIABLE || $tokens[$nextNonEmpty]['code'] === T_STRING) + && ($tokens[$nextNonEmpty]['code'] === T_VARIABLE + || isset(Tokens::$nameTokens[$tokens[$nextNonEmpty]['code']]) === true) ) { if ($nextNonEmpty === ($stackPtr + 1)) { $phpcsFile->recordMetric($stackPtr, 'Spacing between in/decrementor and variable', 0); @@ -106,7 +107,7 @@ public function process(File $phpcsFile, $stackPtr) $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); if ($prevNonEmpty !== false && ($tokens[$prevNonEmpty]['code'] === T_VARIABLE - || $tokens[$prevNonEmpty]['code'] === T_STRING + || isset(Tokens::$nameTokens[$tokens[$prevNonEmpty]['code']]) === true || $tokens[$prevNonEmpty]['code'] === T_CLOSE_SQUARE_BRACKET) ) { if ($prevNonEmpty === ($stackPtr - 1)) { diff --git a/src/Standards/Generic/Tests/WhiteSpace/IncrementDecrementSpacingUnitTest.inc b/src/Standards/Generic/Tests/WhiteSpace/IncrementDecrementSpacingUnitTest.inc index 535053b0d0..b8bc9a5e54 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/IncrementDecrementSpacingUnitTest.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/IncrementDecrementSpacingUnitTest.inc @@ -41,3 +41,18 @@ getObject()->count getObject()->count++; ++ getObject()->count; ++getObject()->count; + +++ +\Fully\Qualified()->count; +++\Fully\Qualified()->count; +-- Partially\Qualified()->count; +--Partially\Qualified()->count; +++ namespace\relative()->count; +++namespace\relative()->count; + +++\ClassName::$prop; +++ \ClassName::$prop; +++Relative\ClassName::$prop; +++ Relative\ClassName::$prop; +--namespace\Relative\ClassName::$prop; +-- /*comment*/ namespace\Relative\ClassName::$prop; diff --git a/src/Standards/Generic/Tests/WhiteSpace/IncrementDecrementSpacingUnitTest.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/IncrementDecrementSpacingUnitTest.inc.fixed index c30332b25e..252934d058 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/IncrementDecrementSpacingUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/WhiteSpace/IncrementDecrementSpacingUnitTest.inc.fixed @@ -39,3 +39,17 @@ getObject()->count++; getObject()->count++; ++getObject()->count; ++getObject()->count; + +++\Fully\Qualified()->count; +++\Fully\Qualified()->count; +--Partially\Qualified()->count; +--Partially\Qualified()->count; +++namespace\relative()->count; +++namespace\relative()->count; + +++\ClassName::$prop; +++\ClassName::$prop; +++Relative\ClassName::$prop; +++Relative\ClassName::$prop; +--namespace\Relative\ClassName::$prop; +-- /*comment*/ namespace\Relative\ClassName::$prop; diff --git a/src/Standards/Generic/Tests/WhiteSpace/IncrementDecrementSpacingUnitTest.php b/src/Standards/Generic/Tests/WhiteSpace/IncrementDecrementSpacingUnitTest.php index 1ba6de3f11..e5890e3273 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/IncrementDecrementSpacingUnitTest.php +++ b/src/Standards/Generic/Tests/WhiteSpace/IncrementDecrementSpacingUnitTest.php @@ -49,6 +49,12 @@ public function getErrorList() 37 => 1, 40 => 1, 42 => 1, + 45 => 1, + 48 => 1, + 50 => 1, + 54 => 1, + 56 => 1, + 58 => 1, ]; }//end getErrorList() From bd9d3f06ee999e1e00b2ae5f9d09f50ec26fa586 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 20:03:14 +0200 Subject: [PATCH 24/41] Generic/LanguageConstructSpacing: fix sniff to work with the PHP 8 identifier tokens The existing unit tests already cover this change. --- .../Sniffs/WhiteSpace/LanguageConstructSpacingSniff.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Standards/Generic/Sniffs/WhiteSpace/LanguageConstructSpacingSniff.php b/src/Standards/Generic/Sniffs/WhiteSpace/LanguageConstructSpacingSniff.php index 90b3117991..5b6131e8e4 100644 --- a/src/Standards/Generic/Sniffs/WhiteSpace/LanguageConstructSpacingSniff.php +++ b/src/Standards/Generic/Sniffs/WhiteSpace/LanguageConstructSpacingSniff.php @@ -73,8 +73,9 @@ public function process(File $phpcsFile, $stackPtr) $content = $tokens[$stackPtr]['content']; if ($tokens[$stackPtr]['code'] === T_NAMESPACE) { $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); - if ($nextNonEmpty !== false && $tokens[$nextNonEmpty]['code'] === T_NS_SEPARATOR) { + if ($nextNonEmpty !== false && $tokens[$nextNonEmpty]['code'] === T_NAME_FULLY_QUALIFIED) { // Namespace keyword used as operator, not as the language construct. + // Note: in PHP >= 8 namespaced names no longer allow for whitespace/comments between the parts (parse error). return; } } From 894c5d905f1395e5f59099717507818deb009bd2 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 9 Apr 2025 01:23:14 +0200 Subject: [PATCH 25/41] PEAR/MultiLineCondition: fix sniff to work with the PHP 8 identifier tokens Includes extra unit tests covering the change in so far it wasn't already covered. --- .../MultiLineConditionSniff.php | 2 +- .../MultiLineConditionUnitTest.inc | 23 +++++++++++++++++++ .../MultiLineConditionUnitTest.inc.fixed | 23 +++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/Standards/PEAR/Sniffs/ControlStructures/MultiLineConditionSniff.php b/src/Standards/PEAR/Sniffs/ControlStructures/MultiLineConditionSniff.php index 323850d4fd..5715a2ab15 100644 --- a/src/Standards/PEAR/Sniffs/ControlStructures/MultiLineConditionSniff.php +++ b/src/Standards/PEAR/Sniffs/ControlStructures/MultiLineConditionSniff.php @@ -204,7 +204,7 @@ public function process(File $phpcsFile, $stackPtr) $prevLine = $tokens[$i]['line']; }//end if - if ($tokens[$i]['code'] === T_STRING) { + if (isset(Tokens::$nameTokens[$tokens[$i]['code']]) === true) { $next = $phpcsFile->findNext(T_WHITESPACE, ($i + 1), null, true); if ($tokens[$next]['code'] === T_OPEN_PARENTHESIS) { // This is a function call, so skip to the end as they diff --git a/src/Standards/PEAR/Tests/ControlStructures/MultiLineConditionUnitTest.inc b/src/Standards/PEAR/Tests/ControlStructures/MultiLineConditionUnitTest.inc index a7a3c69d70..fdf5375334 100644 --- a/src/Standards/PEAR/Tests/ControlStructures/MultiLineConditionUnitTest.inc +++ b/src/Standards/PEAR/Tests/ControlStructures/MultiLineConditionUnitTest.inc @@ -249,3 +249,26 @@ if ($IPP->errorCode() == 401 ) { return false; } + +// Should be no errors even though lines are not exactly aligned together. +// Multi-line function call takes precedence. +if ($a === true + && \Fully\Qualified( + $key, $value2 + ) +) { +} + +if ($a === true + && Partially\qualified( + $key, $value2 + ) +) { +} + +if ($a === true + && namespace\relative( + $key, $value2 + ) +) { +} diff --git a/src/Standards/PEAR/Tests/ControlStructures/MultiLineConditionUnitTest.inc.fixed b/src/Standards/PEAR/Tests/ControlStructures/MultiLineConditionUnitTest.inc.fixed index 7d56c4618d..ef146b10aa 100644 --- a/src/Standards/PEAR/Tests/ControlStructures/MultiLineConditionUnitTest.inc.fixed +++ b/src/Standards/PEAR/Tests/ControlStructures/MultiLineConditionUnitTest.inc.fixed @@ -245,3 +245,26 @@ if ($IPP->errorCode() == 401 ) { return false; } + +// Should be no errors even though lines are not exactly aligned together. +// Multi-line function call takes precedence. +if ($a === true + && \Fully\Qualified( + $key, $value2 + ) +) { +} + +if ($a === true + && Partially\qualified( + $key, $value2 + ) +) { +} + +if ($a === true + && namespace\relative( + $key, $value2 + ) +) { +} From e9f74a65f141c9fbaa1b154bcb8efde9734aeeab Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 9 Apr 2025 01:30:39 +0200 Subject: [PATCH 26/41] PSR1/SideEffects: fix sniff to work with the PHP 8 identifier tokens Includes adjusting a pre-existing test to cover this change. --- src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php | 10 ++++++---- .../PSR1/Tests/Files/SideEffectsUnitTest.16.inc | 2 +- .../PSR1/Tests/Files/SideEffectsUnitTest.17.inc | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php b/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php index 68a18d8c76..c807d8725c 100644 --- a/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php +++ b/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php @@ -201,8 +201,9 @@ private function searchForConflict($phpcsFile, $start, $end, $tokens) $i = $tokens[$i]['scope_closer']; continue; - } else if ($tokens[$i]['code'] === T_STRING - && strtolower($tokens[$i]['content']) === 'define' + } else if (($tokens[$i]['code'] === T_STRING + || $tokens[$i]['code'] === T_NAME_FULLY_QUALIFIED) + && strtolower(ltrim($tokens[$i]['content'], '\\')) === 'define' ) { $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($i - 1), null, true); if ($tokens[$prev]['code'] !== T_OBJECT_OPERATOR @@ -226,8 +227,9 @@ private function searchForConflict($phpcsFile, $start, $end, $tokens) // Special case for defined() as it can be used to see // if a constant (a symbol) should be defined or not and // doesn't need to use a full conditional block. - if ($tokens[$i]['code'] === T_STRING - && strtolower($tokens[$i]['content']) === 'defined' + if (($tokens[$i]['code'] === T_STRING + || $tokens[$i]['code'] === T_NAME_FULLY_QUALIFIED) + && strtolower(ltrim($tokens[$i]['content'], '\\')) === 'defined' ) { $openBracket = $phpcsFile->findNext(Tokens::$emptyTokens, ($i + 1), null, true); if ($openBracket !== false diff --git a/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.16.inc b/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.16.inc index 588ece5840..a4fe468824 100644 --- a/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.16.inc +++ b/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.16.inc @@ -1,2 +1,2 @@ defined('MINSIZE') or define("MAXSIZE", 100); +$my?->defined('MINSIZE') or \define("MAXSIZE", 100); diff --git a/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.17.inc b/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.17.inc index ec81bfedac..238a4c68f9 100644 --- a/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.17.inc +++ b/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.17.inc @@ -1,8 +1,8 @@ Date: Tue, 1 Sep 2020 22:19:04 +0200 Subject: [PATCH 27/41] PSR2/ClassDeclaration: fix sniff to work with the PHP 8 identifier tokens The existing unit tests already contain tests covering this change. By extension, this also fixes the `PSR12.Classes.AnonClassDeclaration` and the `Squiz.Classes.ClassDeclaration` sniffs. Includes some updated tests for those sniffs. --- .../Classes/AnonClassDeclarationUnitTest.inc | 10 +++--- .../AnonClassDeclarationUnitTest.inc.fixed | 10 +++--- .../Sniffs/Classes/ClassDeclarationSniff.php | 36 ++++--------------- .../Classes/ClassDeclarationUnitTest.inc | 2 +- .../ClassDeclarationUnitTest.inc.fixed | 2 +- 5 files changed, 19 insertions(+), 41 deletions(-) diff --git a/src/Standards/PSR12/Tests/Classes/AnonClassDeclarationUnitTest.inc b/src/Standards/PSR12/Tests/Classes/AnonClassDeclarationUnitTest.inc index 386b12c226..3f08cb9530 100644 --- a/src/Standards/PSR12/Tests/Classes/AnonClassDeclarationUnitTest.inc +++ b/src/Standards/PSR12/Tests/Classes/AnonClassDeclarationUnitTest.inc @@ -50,15 +50,15 @@ $instance6 = new class extends \Foo }; $instance7 = new class extends \Foo implements - \One, \Two, - \Three, + \One, Two, + Partially\Three, \Four, - \Five { + namespace\Five { // Class content }; if ($foo) { - $instance8 = new class extends \Foo implements + $instance8 = new class extends namespace\Foo implements \One, \Five { // Class content @@ -68,7 +68,7 @@ if ($foo) { $instance9 = new class ( $one, $two -) extends \Foo implements \One { +) extends Partially\Foo implements \One { // Class content }; diff --git a/src/Standards/PSR12/Tests/Classes/AnonClassDeclarationUnitTest.inc.fixed b/src/Standards/PSR12/Tests/Classes/AnonClassDeclarationUnitTest.inc.fixed index 28b38f7086..fce832f7b7 100644 --- a/src/Standards/PSR12/Tests/Classes/AnonClassDeclarationUnitTest.inc.fixed +++ b/src/Standards/PSR12/Tests/Classes/AnonClassDeclarationUnitTest.inc.fixed @@ -50,16 +50,16 @@ $instance6 = new class extends \Foo implements \HandleableInterface { $instance7 = new class extends \Foo implements \One, - \Two, - \Three, + Two, + Partially\Three, \Four, - \Five + namespace\Five { // Class content }; if ($foo) { - $instance8 = new class extends \Foo implements + $instance8 = new class extends namespace\Foo implements \One, \Five { @@ -70,7 +70,7 @@ if ($foo) { $instance9 = new class ( $one, $two -) extends \Foo implements \One { +) extends Partially\Foo implements \One { // Class content }; diff --git a/src/Standards/PSR2/Sniffs/Classes/ClassDeclarationSniff.php b/src/Standards/PSR2/Sniffs/Classes/ClassDeclarationSniff.php index 887c552edc..bfef222f34 100644 --- a/src/Standards/PSR2/Sniffs/Classes/ClassDeclarationSniff.php +++ b/src/Standards/PSR2/Sniffs/Classes/ClassDeclarationSniff.php @@ -280,10 +280,8 @@ public function processOpen(File $phpcsFile, $stackPtr) } } - $find = [ - T_STRING, - $keywordTokenType, - ]; + $find = Tokens::$nameTokens; + $find[$keywordTokenType] = $keywordTokenType; if ($className !== null) { $start = $className; @@ -311,17 +309,9 @@ public function processOpen(File $phpcsFile, $stackPtr) continue; } - if ($checkingImplements === true - && $multiLineImplements === true - && ($tokens[($className - 1)]['code'] !== T_NS_SEPARATOR - || ($tokens[($className - 2)]['code'] !== T_STRING - && $tokens[($className - 2)]['code'] !== T_NAMESPACE)) - ) { + if ($checkingImplements === true && $multiLineImplements === true) { $prev = $phpcsFile->findPrevious( - [ - T_NS_SEPARATOR, - T_WHITESPACE, - ], + T_WHITESPACE, ($className - 1), $implements, true @@ -400,15 +390,8 @@ public function processOpen(File $phpcsFile, $stackPtr) } } }//end if - } else if ($tokens[($className - 1)]['code'] !== T_NS_SEPARATOR - || ($tokens[($className - 2)]['code'] !== T_STRING - && $tokens[($className - 2)]['code'] !== T_NAMESPACE) - ) { - // Not part of a longer fully qualified or namespace relative class name. - if ($tokens[($className - 1)]['code'] === T_COMMA - || ($tokens[($className - 1)]['code'] === T_NS_SEPARATOR - && $tokens[($className - 2)]['code'] === T_COMMA) - ) { + } else { + if ($tokens[($className - 1)]['code'] === T_COMMA) { $error = 'Expected 1 space before "%s"; 0 found'; $data = [$tokens[$className]['content']]; $fix = $phpcsFile->addFixableError($error, ($nextComma + 1), 'NoSpaceBeforeName', $data); @@ -416,11 +399,7 @@ public function processOpen(File $phpcsFile, $stackPtr) $phpcsFile->fixer->addContentBefore(($nextComma + 1), ' '); } } else { - if ($tokens[($className - 1)]['code'] === T_NS_SEPARATOR) { - $prev = ($className - 2); - } else { - $prev = ($className - 1); - } + $prev = ($className - 1); $last = $phpcsFile->findPrevious(T_WHITESPACE, $prev, null, true); $content = $phpcsFile->getTokensAsString(($last + 1), ($prev - $last)); @@ -452,7 +431,6 @@ public function processOpen(File $phpcsFile, $stackPtr) }//end if if ($checkingImplements === true - && $tokens[($className + 1)]['code'] !== T_NS_SEPARATOR && $tokens[($className + 1)]['code'] !== T_COMMA ) { if ($n !== ($classCount - 1)) { diff --git a/src/Standards/Squiz/Tests/Classes/ClassDeclarationUnitTest.inc b/src/Standards/Squiz/Tests/Classes/ClassDeclarationUnitTest.inc index 25d8d30211..0b43f96eeb 100644 --- a/src/Standards/Squiz/Tests/Classes/ClassDeclarationUnitTest.inc +++ b/src/Standards/Squiz/Tests/Classes/ClassDeclarationUnitTest.inc @@ -56,7 +56,7 @@ class ClassOne implements ClassTwo, ClassThree { }//end class -class ClassOne implements ClassFour ,ClassFive, ClassSix +class ClassOne implements ClassFour ,\ClassFive, namespace\ClassSix { }//end class diff --git a/src/Standards/Squiz/Tests/Classes/ClassDeclarationUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Classes/ClassDeclarationUnitTest.inc.fixed index bf042f932d..e0a0dac4f9 100644 --- a/src/Standards/Squiz/Tests/Classes/ClassDeclarationUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Classes/ClassDeclarationUnitTest.inc.fixed @@ -65,7 +65,7 @@ class ClassOne implements ClassTwo, ClassThree { }//end class -class ClassOne implements ClassFour, ClassFive, ClassSix +class ClassOne implements ClassFour, \ClassFive, namespace\ClassSix { }//end class From bb933e905fd109a170a3c9cc2203a782bfae9248 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 20:06:43 +0200 Subject: [PATCH 28/41] PSR12/ClassInstantiation: fix sniff to work with the PHP 8 identifier tokens Includes some additional tests to cover the change. --- .../PSR12/Sniffs/Classes/ClassInstantiationSniff.php | 5 ++--- .../PSR12/Tests/Classes/ClassInstantiationUnitTest.inc | 5 +++++ .../PSR12/Tests/Classes/ClassInstantiationUnitTest.inc.fixed | 5 +++++ .../PSR12/Tests/Classes/ClassInstantiationUnitTest.php | 2 ++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Standards/PSR12/Sniffs/Classes/ClassInstantiationSniff.php b/src/Standards/PSR12/Sniffs/Classes/ClassInstantiationSniff.php index 048cb60ccf..279742e8e2 100644 --- a/src/Standards/PSR12/Sniffs/Classes/ClassInstantiationSniff.php +++ b/src/Standards/PSR12/Sniffs/Classes/ClassInstantiationSniff.php @@ -43,9 +43,8 @@ public function process(File $phpcsFile, $stackPtr) $tokens = $phpcsFile->getTokens(); // Find the class name. - $allowed = [ - T_STRING => T_STRING, - T_NS_SEPARATOR => T_NS_SEPARATOR, + $allowed = Tokens::$nameTokens; + $allowed += [ T_SELF => T_SELF, T_STATIC => T_STATIC, T_PARENT => T_PARENT, diff --git a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc index 264116779f..ee2156cfd3 100644 --- a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc +++ b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc @@ -49,3 +49,8 @@ $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 {}; + +$foo = new \FQN\Bar(); +$foo = new namespace\Bar(); +$foo = new \FQN\Bar; +$foo = new namespace\Bar; diff --git a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc.fixed b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc.fixed index 1e580f45d9..cf6233d53f 100644 --- a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc.fixed +++ b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc.fixed @@ -49,3 +49,8 @@ $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 {}; + +$foo = new \FQN\Bar(); +$foo = new namespace\Bar(); +$foo = new \FQN\Bar(); +$foo = new namespace\Bar(); diff --git a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.php b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.php index 816cb3b2b4..1881b73ccd 100644 --- a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.php +++ b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.php @@ -49,6 +49,8 @@ public function getErrorList() 37 => 1, 38 => 1, 47 => 1, + 55 => 1, + 56 => 1, ]; }//end getErrorList() From bbded9b1d681d4fd6dd3e2a72e4e7250978333d9 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 20:09:59 +0200 Subject: [PATCH 29/41] PSR12/ImportStatement: fix sniff to work with the PHP 8 identifier tokens The existing unit tests already contain tests covering this change. --- src/Standards/PSR12/Sniffs/Files/ImportStatementSniff.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Standards/PSR12/Sniffs/Files/ImportStatementSniff.php b/src/Standards/PSR12/Sniffs/Files/ImportStatementSniff.php index 4572855f8e..fbd08a3adf 100644 --- a/src/Standards/PSR12/Sniffs/Files/ImportStatementSniff.php +++ b/src/Standards/PSR12/Sniffs/Files/ImportStatementSniff.php @@ -60,7 +60,7 @@ public function process(File $phpcsFile, $stackPtr) $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($next + 1), null, true); } - if ($tokens[$next]['code'] !== T_NS_SEPARATOR) { + if ($tokens[$next]['code'] !== T_NAME_FULLY_QUALIFIED) { return; } @@ -68,7 +68,7 @@ public function process(File $phpcsFile, $stackPtr) $fix = $phpcsFile->addFixableError($error, $next, 'LeadingSlash'); if ($fix === true) { - $phpcsFile->fixer->replaceToken($next, ''); + $phpcsFile->fixer->replaceToken($next, ltrim($tokens[$next]['content'], '\\')); } }//end process() From d70c2a482cbbed6f6a86502db030f456f3d851c7 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 21:14:22 +0200 Subject: [PATCH 30/41] PSR12/NullableTypeDeclaration: fix sniff to work with the PHP 8 identifier tokens Includes minor update to pre-existing tests to ensure all identifier name types are covered. --- .../NullableTypeDeclarationSniff.php | 20 ++++++++++--------- .../NullableTypeDeclarationUnitTest.inc | 12 +++++------ .../NullableTypeDeclarationUnitTest.inc.fixed | 12 +++++------ 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/Standards/PSR12/Sniffs/Functions/NullableTypeDeclarationSniff.php b/src/Standards/PSR12/Sniffs/Functions/NullableTypeDeclarationSniff.php index 4314194d4c..693510edc5 100644 --- a/src/Standards/PSR12/Sniffs/Functions/NullableTypeDeclarationSniff.php +++ b/src/Standards/PSR12/Sniffs/Functions/NullableTypeDeclarationSniff.php @@ -21,15 +21,17 @@ class NullableTypeDeclarationSniff implements Sniff * @var array */ private $validTokens = [ - T_STRING => true, - T_NS_SEPARATOR => true, - T_CALLABLE => true, - T_SELF => true, - T_PARENT => true, - T_STATIC => true, - T_NULL => true, - T_FALSE => true, - T_TRUE => true, + T_STRING => true, + T_NAME_QUALIFIED => true, + T_NAME_FULLY_QUALIFIED => true, + T_NAME_RELATIVE => true, + T_CALLABLE => true, + T_SELF => true, + T_PARENT => true, + T_STATIC => true, + T_NULL => true, + T_FALSE => true, + T_TRUE => true, ]; diff --git a/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc b/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc index 056d74c3f2..d0a12f4697 100644 --- a/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc +++ b/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc @@ -8,10 +8,10 @@ class MyClass { { } - public function validWithFQN( + public function validWithQualifiedNames( ?\MyNameSpace\MyArray $array = null, - ?\MyNameSpace\MyObject $object - ): ?\MyNameSpace\MyObject + ?MyNameSpace\MyObject $object + ): ?namespace\MyObject { } @@ -26,10 +26,10 @@ class MyClass { { } - public function invalidWithFQNTooMuchWhitespace( + public function invalidWithQualifiedNamesTooMuchWhitespace( ? \MyNameSpace\MyArray $array = null, - ? \MyNameSpace\MyObject - ):? \MyNameSpace\MyObject + ? MyNameSpace\MyObject + ):? namespace\MyObject { } } diff --git a/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc.fixed b/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc.fixed index 6cc418d074..a8ffe2136c 100644 --- a/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc.fixed +++ b/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc.fixed @@ -8,10 +8,10 @@ class MyClass { { } - public function validWithFQN( + public function validWithQualifiedNames( ?\MyNameSpace\MyArray $array = null, - ?\MyNameSpace\MyObject $object - ): ?\MyNameSpace\MyObject + ?MyNameSpace\MyObject $object + ): ?namespace\MyObject { } @@ -26,10 +26,10 @@ class MyClass { { } - public function invalidWithFQNTooMuchWhitespace( + public function invalidWithQualifiedNamesTooMuchWhitespace( ?\MyNameSpace\MyArray $array = null, - ?\MyNameSpace\MyObject - ):?\MyNameSpace\MyObject + ?MyNameSpace\MyObject + ):?namespace\MyObject { } } From 2017437cc6fa2e19bb344c691d10a2b6f50cc44a Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 21:25:20 +0200 Subject: [PATCH 31/41] PSR12/CompoundNamespaceDepth: fix sniff to work with the PHP 8 identifier tokens The existing unit tests already contain tests covering this change. The tests also include a test which is a parse error in PHP >= 8.0. The current fix will handle this the same in both PHP < 8 as well as PHP 8.0+ and will throw the expected errors even so. The parse error in PHP 8 has been annotated in the test case file. --- .../Sniffs/Namespaces/CompoundNamespaceDepthSniff.php | 7 ++++++- .../Tests/Namespaces/CompoundNamespaceDepthUnitTest.inc | 1 + .../Tests/Namespaces/CompoundNamespaceDepthUnitTest.php | 4 ++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Standards/PSR12/Sniffs/Namespaces/CompoundNamespaceDepthSniff.php b/src/Standards/PSR12/Sniffs/Namespaces/CompoundNamespaceDepthSniff.php index 34e7f461a3..a67f3b70ef 100644 --- a/src/Standards/PSR12/Sniffs/Namespaces/CompoundNamespaceDepthSniff.php +++ b/src/Standards/PSR12/Sniffs/Namespaces/CompoundNamespaceDepthSniff.php @@ -62,6 +62,11 @@ public function process(File $phpcsFile, $stackPtr) continue; } + if ($tokens[$i]['code'] === T_NAME_FULLY_QUALIFIED || $tokens[$i]['code'] === T_NAME_QUALIFIED) { + $depth += substr_count($tokens[$i]['content'], '\\'); + continue; + } + if ($i === $end || $tokens[$i]['code'] === T_COMMA) { // End of a namespace. if ($depth > $this->maxDepth) { @@ -72,7 +77,7 @@ public function process(File $phpcsFile, $stackPtr) $depth = 1; } - } + }//end for }//end process() diff --git a/src/Standards/PSR12/Tests/Namespaces/CompoundNamespaceDepthUnitTest.inc b/src/Standards/PSR12/Tests/Namespaces/CompoundNamespaceDepthUnitTest.inc index 454f7fc252..8f025cb101 100644 --- a/src/Standards/PSR12/Tests/Namespaces/CompoundNamespaceDepthUnitTest.inc +++ b/src/Standards/PSR12/Tests/Namespaces/CompoundNamespaceDepthUnitTest.inc @@ -12,6 +12,7 @@ use Vendor\Package\SomeNamespace\{ ClassZ, }; +// Parse error in PHP 8.0+, but will be detected correctly cross-version anyway. use Vendor\Package\SomeNamespace\{ SubnamespaceOne /* comment */ \AnotherNamespace // comment diff --git a/src/Standards/PSR12/Tests/Namespaces/CompoundNamespaceDepthUnitTest.php b/src/Standards/PSR12/Tests/Namespaces/CompoundNamespaceDepthUnitTest.php index 787b4dec9f..bff6560993 100644 --- a/src/Standards/PSR12/Tests/Namespaces/CompoundNamespaceDepthUnitTest.php +++ b/src/Standards/PSR12/Tests/Namespaces/CompoundNamespaceDepthUnitTest.php @@ -32,8 +32,8 @@ public function getErrorList() { return [ 10 => 1, - 18 => 1, - 21 => 1, + 19 => 1, + 22 => 1, ]; }//end getErrorList() From 2ccfae22a27fa52139ef80f8d3b5e82c59e0e652 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 1 Sep 2020 20:07:02 +0200 Subject: [PATCH 32/41] Squiz/SelfMemberReference: fix sniff to work with the PHP 8 identifier tokens The existing unit tests already contain tests covering this change. The sniff has slightly different behaviour PHPCS 4.x vs the previous behaviour in PHPCS 3.x. Notable differences: * The tolerance for inline names interlaced with whitespace and comments (parse error in PHP 8) has been removed. Previously, the sniff would still fix such a name if there was a match. As of PHPCS 4.x it no longer will and won't throw an error for this anymore either. * The `getDeclarationNameWithNamespace()` method has been removed and the `getNamespaceOfScope()` method has been renamed to `getNamespaceName()`. Sniffs which extend this sniff and overload(ed) either method, will need to be aware of this. Includes: * Fixing an incorrect unit test. --- .../Classes/SelfMemberReferenceSniff.php | 118 ++++++------------ .../Classes/SelfMemberReferenceUnitTest.inc | 6 +- .../SelfMemberReferenceUnitTest.inc.fixed | 4 +- .../Classes/SelfMemberReferenceUnitTest.php | 1 - 4 files changed, 42 insertions(+), 87 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Classes/SelfMemberReferenceSniff.php b/src/Standards/Squiz/Sniffs/Classes/SelfMemberReferenceSniff.php index 8ee4de45a8..9c50e1587d 100644 --- a/src/Standards/Squiz/Sniffs/Classes/SelfMemberReferenceSniff.php +++ b/src/Standards/Squiz/Sniffs/Classes/SelfMemberReferenceSniff.php @@ -75,48 +75,40 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop return; } - } else if ($tokens[$calledClassName]['code'] === T_STRING) { - // If the class is called with a namespace prefix, build fully qualified - // namespace calls for both current scope class and requested class. - $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($calledClassName - 1), null, true); - if ($prevNonEmpty !== false && $tokens[$prevNonEmpty]['code'] === T_NS_SEPARATOR) { - $declarationName = $this->getDeclarationNameWithNamespace($tokens, $calledClassName); - $declarationName = ltrim($declarationName, '\\'); - $fullQualifiedClassName = $this->getNamespaceOfScope($phpcsFile, $currScope); - if ($fullQualifiedClassName === '\\') { - $fullQualifiedClassName = ''; - } else { - $fullQualifiedClassName .= '\\'; - } + } else if (isset(Tokens::$nameTokens[$tokens[$calledClassName]['code']]) === true) { + // Work out the fully qualified name for both the class declaration + // as well as the class usage to see if they match. + $namespaceName = $this->getNamespaceName($phpcsFile, $currScope); + if ($namespaceName === '\\') { + $namespaceName = ''; + } + + $declarationName = $namespaceName.'\\'.$phpcsFile->getDeclarationName($currScope); + $inlineName = ''; + + switch ($tokens[$calledClassName]['code']) { + case T_NAME_FULLY_QUALIFIED: + $inlineName = $tokens[$calledClassName]['content']; + break; + + case T_NAME_QUALIFIED: + case T_STRING: + $inlineName = $namespaceName.'\\'.$tokens[$calledClassName]['content']; + break; - $fullQualifiedClassName .= $phpcsFile->getDeclarationName($currScope); - } else { - $declarationName = $phpcsFile->getDeclarationName($currScope); - $fullQualifiedClassName = $tokens[$calledClassName]['content']; + case T_NAME_RELATIVE: + $inlineName = $namespaceName.substr($tokens[$calledClassName]['content'], 9); + break; } - if ($declarationName === $fullQualifiedClassName) { + if ($declarationName === $inlineName) { // Class name is the same as the current class, which is not allowed. $error = 'Must use "self::" for local static member reference'; $fix = $phpcsFile->addFixableError($error, $calledClassName, 'NotUsed'); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); - - $currentPointer = ($stackPtr - 1); - while ($tokens[$currentPointer]['code'] === T_NS_SEPARATOR - || $tokens[$currentPointer]['code'] === T_STRING - || isset(Tokens::$emptyTokens[$tokens[$currentPointer]['code']]) === true - ) { - if (isset(Tokens::$emptyTokens[$tokens[$currentPointer]['code']]) === true) { - --$currentPointer; - continue; - } - - $phpcsFile->fixer->replaceToken($currentPointer, ''); - --$currentPointer; - } - + $phpcsFile->fixer->replaceToken($calledClassName, ''); $phpcsFile->fixer->replaceToken($stackPtr, 'self::'); $phpcsFile->fixer->endChangeset(); @@ -180,38 +172,7 @@ protected function processTokenOutsideScope(File $phpcsFile, $stackPtr) /** - * Returns the declaration names for classes/interfaces/functions with a namespace. - * - * @param array $tokens Token stack for this file. - * @param int $stackPtr The position where the namespace building will start. - * - * @return string - */ - protected function getDeclarationNameWithNamespace(array $tokens, $stackPtr) - { - $nameParts = []; - $currentPointer = $stackPtr; - while ($tokens[$currentPointer]['code'] === T_NS_SEPARATOR - || $tokens[$currentPointer]['code'] === T_STRING - || isset(Tokens::$emptyTokens[$tokens[$currentPointer]['code']]) === true - ) { - if (isset(Tokens::$emptyTokens[$tokens[$currentPointer]['code']]) === true) { - --$currentPointer; - continue; - } - - $nameParts[] = $tokens[$currentPointer]['content']; - --$currentPointer; - } - - $nameParts = array_reverse($nameParts); - return implode('', $nameParts); - - }//end getDeclarationNameWithNamespace() - - - /** - * Returns the namespace declaration of a file. + * Returns the namespace name of the current scope. * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. * @param int $stackPtr The position where the search for the @@ -219,30 +180,25 @@ protected function getDeclarationNameWithNamespace(array $tokens, $stackPtr) * * @return string */ - protected function getNamespaceOfScope(File $phpcsFile, $stackPtr) + protected function getNamespaceName(File $phpcsFile, $stackPtr) { - $namespace = '\\'; - $tokens = $phpcsFile->getTokens(); + $namespace = '\\'; + $namespaceDeclaration = $phpcsFile->findPrevious(T_NAMESPACE, $stackPtr); - while (($namespaceDeclaration = $phpcsFile->findPrevious(T_NAMESPACE, $stackPtr)) !== false) { + if ($namespaceDeclaration !== false) { + $tokens = $phpcsFile->getTokens(); $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($namespaceDeclaration + 1), null, true); - if ($tokens[$nextNonEmpty]['code'] === T_NS_SEPARATOR) { - // Namespace operator. Ignore. - $stackPtr = ($namespaceDeclaration - 1); - continue; + if ($nextNonEmpty !== false + && ($tokens[$nextNonEmpty]['code'] === T_NAME_QUALIFIED + || $tokens[$nextNonEmpty]['code'] === T_STRING) + ) { + $namespace .= $tokens[$nextNonEmpty]['content']; } - - $endOfNamespaceDeclaration = $phpcsFile->findNext([T_SEMICOLON, T_OPEN_CURLY_BRACKET, T_CLOSE_TAG], $namespaceDeclaration); - $namespace = $this->getDeclarationNameWithNamespace( - $phpcsFile->getTokens(), - ($endOfNamespaceDeclaration - 1) - ); - break; } return $namespace; - }//end getNamespaceOfScope() + }//end getNamespaceName() }//end class diff --git a/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.inc b/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.inc index 4f17813866..00aa5922e0 100644 --- a/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.inc +++ b/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.inc @@ -159,16 +159,16 @@ class Nested_Anon_Class { namespace Foo\Baz { class BarFoo { public function foo() { - echo Foo\Baz\BarFoo::$prop; + echo \Foo\Baz\BarFoo::$prop; } } } -// Prevent false negative when namespace has whitespace/comments. +// Prevent false negative when namespace has whitespace/comments - no longer supported as of PHPCS 4.x. namespace Foo /*comment*/ \ Bah { class BarFoo { public function foo() { - echo Foo \ /*comment*/ Bah\BarFoo::$prop; + echo \Foo \ /*comment*/ Bah\BarFoo::$prop; } } } diff --git a/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.inc.fixed index 770429dc82..ad370890b1 100644 --- a/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.inc.fixed @@ -152,11 +152,11 @@ namespace Foo\Baz { } } -// Prevent false negative when namespace has whitespace/comments. +// Prevent false negative when namespace has whitespace/comments - no longer supported as of PHPCS 4.x. namespace Foo /*comment*/ \ Bah { class BarFoo { public function foo() { - echo /*comment*/ self::$prop; + echo \Foo \ /*comment*/ Bah\BarFoo::$prop; } } } diff --git a/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.php b/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.php index 0f4aa5cf6a..69140bc4f9 100644 --- a/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.php +++ b/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.php @@ -45,7 +45,6 @@ public function getErrorList() 140 => 1, 143 => 2, 162 => 1, - 171 => 1, 183 => 1, 197 => 1, ]; From 1529566895c1cae5c334607a2082fc8e042b04f6 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 7 Apr 2025 02:09:33 +0200 Subject: [PATCH 33/41] Squiz/FunctionCommentThrowTag: fix sniff to work with the PHP 8 identifier tokens Includes adding some extra tests to ensure all identifier name types are covered. --- .../FunctionCommentThrowTagSniff.php | 38 ++++++------------- .../FunctionCommentThrowTagUnitTest.inc | 20 +++++++++- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentThrowTagSniff.php b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentThrowTagSniff.php index b6604a7fc6..ba2e7b2643 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentThrowTagSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentThrowTagSniff.php @@ -106,44 +106,30 @@ public function process(File $phpcsFile, $stackPtr) $nextToken = $phpcsFile->findNext(Tokens::$emptyTokens, ($currPos + 1), null, true); if ($tokens[$nextToken]['code'] === T_NEW - || $tokens[$nextToken]['code'] === T_NS_SEPARATOR - || $tokens[$nextToken]['code'] === T_STRING + || isset(Tokens::$nameTokens[$tokens[$nextToken]['code']]) === true ) { if ($tokens[$nextToken]['code'] === T_NEW) { $currException = $phpcsFile->findNext( - [ - T_NS_SEPARATOR, - T_STRING, - ], - $currPos, + Tokens::$emptyTokens, + ($nextToken + 1), $stackPtrEnd, - false, - null, true ); } else { $currException = $nextToken; } - if ($currException !== false) { - $endException = $phpcsFile->findNext( - [ - T_NS_SEPARATOR, - T_STRING, - ], - ($currException + 1), - $stackPtrEnd, - true, - null, - true - ); - - if ($endException === false) { - $thrownExceptions[] = $tokens[$currException]['content']; + if ($currException !== false + && isset(Tokens::$nameTokens[$tokens[$currException]['code']]) === true + ) { + if ($tokens[$currException]['code'] === T_NAME_RELATIVE) { + // Strip the `namespace\` prefix off the exception name + // to prevent confusing the name comparison. + $thrownExceptions[] = substr($tokens[$currException]['content'], 10); } else { - $thrownExceptions[] = $phpcsFile->getTokensAsString($currException, ($endException - $currException)); + $thrownExceptions[] = $tokens[$currException]['content']; } - }//end if + } } else if ($tokens[$nextToken]['code'] === T_VARIABLE) { // Find the nearest catch block in this scope and, if the caught var // matches our re-thrown var, use the exception types being caught as diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentThrowTagUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/FunctionCommentThrowTagUnitTest.inc index 25da5021da..77946bcdd7 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentThrowTagUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentThrowTagUnitTest.inc @@ -281,7 +281,7 @@ class NamespacedException { public function fooBar2() { throw new \Foo\Bar\FooBarException(); } - + /** * @throws FooBarException */ @@ -531,3 +531,21 @@ $anon = new class { public function ImproveCommentTolerance() { throw /*comment*/ new /*comment*/ Right_Exception('Error'); } + +namespace Namespaced\Names { + class NamespacedNames { + /** + * @throws Namespaced\Names\Exception + */ + public function foo() { + throw new namespace\Exception(); + } + + /** + * @throws Namespaced\Names\Bar\FooBarException + */ + public function fooBar2() { + throw new Bar\FooBarException(); + } + } +} From 21a7d141c00d78fb6b7b8174bb887c7a28c6f883 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 1 Sep 2020 20:25:32 +0200 Subject: [PATCH 34/41] Squiz/VariableComment: fix sniff to work with the PHP 8 identifier tokens Includes extra unit test covering the change in so far it wasn't already covered. --- .../Squiz/Sniffs/Commenting/VariableCommentSniff.php | 11 ++++------- .../Tests/Commenting/VariableCommentUnitTest.inc | 7 +++++++ .../Commenting/VariableCommentUnitTest.inc.fixed | 7 +++++++ .../Tests/Commenting/VariableCommentUnitTest.php | 1 + 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Commenting/VariableCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/VariableCommentSniff.php index 6da78013ab..3eb10dd5ae 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/VariableCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/VariableCommentSniff.php @@ -41,18 +41,15 @@ public function __construct() public function processMemberVar(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); - $ignore = [ - T_PUBLIC => T_PUBLIC, - T_PRIVATE => T_PRIVATE, - T_PROTECTED => T_PROTECTED, + + $ignore = Tokens::$scopeModifiers; + $ignore += Tokens::$nameTokens; + $ignore += [ T_VAR => T_VAR, T_STATIC => T_STATIC, T_READONLY => T_READONLY, T_FINAL => T_FINAL, T_WHITESPACE => T_WHITESPACE, - T_STRING => T_STRING, - T_NS_SEPARATOR => T_NS_SEPARATOR, - T_NAMESPACE => T_NAMESPACE, T_NULLABLE => T_NULLABLE, T_TYPE_UNION => T_TYPE_UNION, T_TYPE_INTERSECTION => T_TYPE_INTERSECTION, diff --git a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc index b849029e6d..170b84fe69 100644 --- a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc @@ -453,6 +453,13 @@ class MoreMissingButSupportedTypes * @var SomeClass */ private namespace\SomeClass $variableName; + + public static ?\Folder\ClassName $FQNTypeDecl_noComment = null; + + /** + * @var \Folder\ClassName|null + */ + public ?\Folder\ClassName $FQNTypeDecl_withComment = null; } class DNFTypes diff --git a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc.fixed index 0a57d89e06..0b3c5a4b9e 100644 --- a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc.fixed @@ -453,6 +453,13 @@ class MoreMissingButSupportedTypes * @var SomeClass */ private namespace\SomeClass $variableName; + + public static ?\Folder\ClassName $FQNTypeDecl_noComment = null; + + /** + * @var \Folder\ClassName|null + */ + public ?\Folder\ClassName $FQNTypeDecl_withComment = null; } class DNFTypes diff --git a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.php b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.php index 4af31197ae..01691216ca 100644 --- a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.php +++ b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.php @@ -65,6 +65,7 @@ public function getErrorList() 364 => 1, 399 => 1, 403 => 1, + 457 => 1, ]; }//end getErrorList() From 6e33f4344c1c51df2a945e42f71242a1ae3bae49 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 7 Apr 2025 00:44:06 +0200 Subject: [PATCH 35/41] Squiz/OperatorBracket: fix sniff to work with the PHP 8 identifier tokens Includes extra unit tests covering the change in so far it wasn't already covered. --- .../Sniffs/Formatting/OperatorBracketSniff.php | 17 +++++++++-------- .../Formatting/OperatorBracketUnitTest.1.inc | 6 +++++- .../OperatorBracketUnitTest.1.inc.fixed | 6 +++++- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php b/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php index aefb01da5d..d53cb2d15e 100644 --- a/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php +++ b/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php @@ -115,13 +115,13 @@ public function process(File $phpcsFile, $stackPtr) } // Tokens that are allowed inside a bracketed operation. - $allowed = [ + $allowed = Tokens::$nameTokens; + $allowed += Tokens::$operators; + $allowed += [ T_VARIABLE => T_VARIABLE, T_LNUMBER => T_LNUMBER, T_DNUMBER => T_DNUMBER, - T_STRING => T_STRING, T_WHITESPACE => T_WHITESPACE, - T_NS_SEPARATOR => T_NS_SEPARATOR, T_SELF => T_SELF, T_STATIC => T_STATIC, T_PARENT => T_PARENT, @@ -134,8 +134,6 @@ public function process(File $phpcsFile, $stackPtr) T_BITWISE_NOT => T_BITWISE_NOT, ]; - $allowed += Tokens::$operators; - $lastBracket = false; if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { $parenthesis = array_reverse($tokens[$stackPtr]['nested_parenthesis'], true); @@ -149,7 +147,10 @@ public function process(File $phpcsFile, $stackPtr) break; } - if ($prevCode === T_STRING || $prevCode === T_SWITCH || $prevCode === T_MATCH) { + if (isset(Tokens::$nameTokens[$prevCode]) === true + || $prevCode === T_SWITCH + || $prevCode === T_MATCH + ) { // We allow simple operations to not be bracketed. // For example, ceil($one / $two). for ($prev = ($stackPtr - 1); $prev > $bracket; $prev--) { @@ -260,11 +261,9 @@ public function addMissingBracketsError($phpcsFile, $stackPtr) T_VARIABLE => true, T_LNUMBER => true, T_DNUMBER => true, - T_STRING => true, T_CONSTANT_ENCAPSED_STRING => true, T_DOUBLE_QUOTED_STRING => true, T_WHITESPACE => true, - T_NS_SEPARATOR => true, T_SELF => true, T_STATIC => true, T_OBJECT_OPERATOR => true, @@ -281,6 +280,7 @@ public function addMissingBracketsError($phpcsFile, $stackPtr) if (isset(Tokens::$emptyTokens[$tokens[$before]['code']]) === true || isset(Tokens::$operators[$tokens[$before]['code']]) === true || isset(Tokens::$castTokens[$tokens[$before]['code']]) === true + || isset(Tokens::$nameTokens[$tokens[$before]['code']]) === true || isset($allowed[$tokens[$before]['code']]) === true ) { continue; @@ -315,6 +315,7 @@ public function addMissingBracketsError($phpcsFile, $stackPtr) if (isset(Tokens::$emptyTokens[$tokens[$after]['code']]) === true || isset(Tokens::$operators[$tokens[$after]['code']]) === true || isset(Tokens::$castTokens[$tokens[$after]['code']]) === true + || isset(Tokens::$nameTokens[$tokens[$after]['code']]) === true || isset($allowed[$tokens[$after]['code']]) === true ) { continue; diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.1.inc b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.1.inc index 8e62896387..b2dd790f9c 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.1.inc @@ -146,7 +146,7 @@ $cntPages = ceil(count($items) / self::ON_PAGE); error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING); error_reporting(E_ALL & ~E_NOTICE | ~E_WARNING); -$results = $stmt->fetchAll(\PDO::FETCH_ASSOC | \PDO::FETCH_GROUP | \PDO::FETCH_UNIQUE); +$results = $stmt->fetchAll(\PDO::FETCH_ASSOC | Relative\PDO::FETCH_GROUP | namespace\PDO::FETCH_UNIQUE); $di = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS); foo(1 + 2 + 3); @@ -201,3 +201,7 @@ match ($a) { }; $cntPages = ceil(count($items) / parent::ON_PAGE); + +$nsRelative = namespace\doSomething($items + 10); +$partiallyQualified = Partially\qualified($items + 10); +$fullyQualified = \Fully\qualified($items + 10); diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.1.inc.fixed b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.1.inc.fixed index 9fa0216cb6..fe7d4ef3d3 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.1.inc.fixed +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.1.inc.fixed @@ -146,7 +146,7 @@ $cntPages = ceil(count($items) / self::ON_PAGE); error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING); error_reporting(E_ALL & ~E_NOTICE | ~E_WARNING); -$results = $stmt->fetchAll(\PDO::FETCH_ASSOC | \PDO::FETCH_GROUP | \PDO::FETCH_UNIQUE); +$results = $stmt->fetchAll(\PDO::FETCH_ASSOC | Relative\PDO::FETCH_GROUP | namespace\PDO::FETCH_UNIQUE); $di = new \RecursiveDirectoryIterator($path, (\RecursiveDirectoryIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS)); foo(1 + 2 + 3); @@ -201,3 +201,7 @@ match ($a) { }; $cntPages = ceil(count($items) / parent::ON_PAGE); + +$nsRelative = namespace\doSomething($items + 10); +$partiallyQualified = Partially\qualified($items + 10); +$fullyQualified = \Fully\qualified($items + 10); From 71c8de23d23e41d8fc86de30bad597aac4d67808 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 9 Apr 2025 01:54:58 +0200 Subject: [PATCH 36/41] Squiz/DisallowComparisonAssignment: fix sniff to work with the PHP 8 identifier tokens Includes extra unit tests covering the change in so far it wasn't already covered. Note: this was previously a false positive for this sniff! --- .../PHP/DisallowComparisonAssignmentSniff.php | 14 ++++++-------- .../PHP/DisallowComparisonAssignmentUnitTest.inc | 4 ++++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php b/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php index 57dabbf12e..d29fbaba9f 100644 --- a/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php @@ -68,18 +68,16 @@ public function process(File $phpcsFile, $stackPtr) } // Ignore function calls. - $ignore = [ - T_NULLSAFE_OBJECT_OPERATOR, - T_OBJECT_OPERATOR, - T_STRING, - T_VARIABLE, - T_WHITESPACE, - ]; + $ignore = Tokens::$nameTokens; + $ignore[] = T_NULLSAFE_OBJECT_OPERATOR; + $ignore[] = T_OBJECT_OPERATOR; + $ignore[] = T_VARIABLE; + $ignore[] = T_WHITESPACE; $next = $phpcsFile->findNext($ignore, ($stackPtr + 1), null, true); if ($tokens[$next]['code'] === T_CLOSURE || ($tokens[$next]['code'] === T_OPEN_PARENTHESIS - && $tokens[($next - 1)]['code'] === T_STRING) + && isset(Tokens::$nameTokens[$tokens[($next - 1)]['code']]) === true) ) { // Code will look like: $var = myFunction( // and will be ignored. diff --git a/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.inc b/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.inc index a07047b196..d3e0e5d58d 100644 --- a/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.inc @@ -81,3 +81,7 @@ function issue3616() { $food === 'cake' => 'This food is a cake', }; } + +$var = \Fully\qualified(!$var); +$var = Partially\qualified(!$var); +$var = namespace\relative(!$var); From 7a921cd831e6fd515cb2d2565ecce2e3339dc676 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 1 Sep 2020 20:36:43 +0200 Subject: [PATCH 37/41] Squiz/DisallowMultipleAssignments: fix sniff to work with the PHP 8 identifier tokens The existing unit tests already contain tests covering this change. --- .../Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php b/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php index 86ecde9a06..3dcd24b782 100644 --- a/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php @@ -124,10 +124,9 @@ public function process(File $phpcsFile, $stackPtr) $start = $phpcsFile->findStartOfStatement($varToken); - $allowed = Tokens::$emptyTokens; + $allowed = Tokens::$emptyTokens; + $allowed += Tokens::$nameTokens; - $allowed[T_STRING] = T_STRING; - $allowed[T_NS_SEPARATOR] = T_NS_SEPARATOR; $allowed[T_DOUBLE_COLON] = T_DOUBLE_COLON; $allowed[T_ASPERAND] = T_ASPERAND; $allowed[T_DOLLAR] = T_DOLLAR; From f80aeb301cf8d271bd4aacee9c6f33f36df093e5 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 9 Apr 2025 01:50:13 +0200 Subject: [PATCH 38/41] Squiz/DisallowSizeFunctionsInLoops: fix sniff to work with the PHP 8 identifier tokens Includes extra unit tests covering the change in so far it wasn't already covered. --- .../Squiz/Sniffs/PHP/DisallowSizeFunctionsInLoopsSniff.php | 4 ++-- .../Squiz/Tests/PHP/DisallowSizeFunctionsInLoopsUnitTest.inc | 5 +++++ .../Squiz/Tests/PHP/DisallowSizeFunctionsInLoopsUnitTest.php | 3 +++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/DisallowSizeFunctionsInLoopsSniff.php b/src/Standards/Squiz/Sniffs/PHP/DisallowSizeFunctionsInLoopsSniff.php index 95142b5f3d..b95dd61cce 100644 --- a/src/Standards/Squiz/Sniffs/PHP/DisallowSizeFunctionsInLoopsSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/DisallowSizeFunctionsInLoopsSniff.php @@ -67,8 +67,8 @@ public function process(File $phpcsFile, $stackPtr) } for ($i = ($start + 1); $i < $end; $i++) { - if ($tokens[$i]['code'] === T_STRING - && isset($this->forbiddenFunctions[$tokens[$i]['content']]) === true + if (($tokens[$i]['code'] === T_STRING || $tokens[$i]['code'] === T_NAME_FULLY_QUALIFIED) + && isset($this->forbiddenFunctions[ltrim($tokens[$i]['content'], '\\')]) === true ) { $functionName = $tokens[$i]['content']; diff --git a/src/Standards/Squiz/Tests/PHP/DisallowSizeFunctionsInLoopsUnitTest.inc b/src/Standards/Squiz/Tests/PHP/DisallowSizeFunctionsInLoopsUnitTest.inc index 56802e37aa..cb3235982a 100644 --- a/src/Standards/Squiz/Tests/PHP/DisallowSizeFunctionsInLoopsUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/DisallowSizeFunctionsInLoopsUnitTest.inc @@ -56,3 +56,8 @@ do { for ($i = 0; $i < $a->count; $i++) {} for ($i = 0; $i < $a?->count; $i++) {} + +for ($i = 0; $i < \sizeof($this->children); $i++) {} +while ($i < \count($array)) {} +do { +} while ($i < \strlen($string)); diff --git a/src/Standards/Squiz/Tests/PHP/DisallowSizeFunctionsInLoopsUnitTest.php b/src/Standards/Squiz/Tests/PHP/DisallowSizeFunctionsInLoopsUnitTest.php index d28f279ee3..a9241f35bc 100644 --- a/src/Standards/Squiz/Tests/PHP/DisallowSizeFunctionsInLoopsUnitTest.php +++ b/src/Standards/Squiz/Tests/PHP/DisallowSizeFunctionsInLoopsUnitTest.php @@ -43,6 +43,9 @@ public function getErrorList() 40 => 1, 44 => 1, 46 => 1, + 60 => 1, + 61 => 1, + 63 => 1, ]; }//end getErrorList() From 24c5688f6d0f52855247974cdf381b5b20a4ccc7 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 21:52:21 +0200 Subject: [PATCH 39/41] Squiz/LowercasePHPFunctions: fix sniff to work with the PHP 8 identifier tokens The existing unit tests already contain tests covering this change. Includes updating two tests with code which is considered a parse error in PHP 8.0. Handling that type of code is no longer supported by this sniff. --- .../Sniffs/PHP/LowercasePHPFunctionsSniff.php | 28 ++++++++----------- .../PHP/LowercasePHPFunctionsUnitTest.inc | 4 +-- .../LowercasePHPFunctionsUnitTest.inc.fixed | 4 +-- 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/LowercasePHPFunctionsSniff.php b/src/Standards/Squiz/Sniffs/PHP/LowercasePHPFunctionsSniff.php index 0b671d6291..44fbf3ab50 100644 --- a/src/Standards/Squiz/Sniffs/PHP/LowercasePHPFunctionsSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/LowercasePHPFunctionsSniff.php @@ -43,7 +43,10 @@ public function __construct() */ public function register() { - return [T_STRING]; + return [ + T_STRING, + T_NAME_FULLY_QUALIFIED, + ]; }//end register() @@ -61,7 +64,11 @@ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); - $content = $tokens[$stackPtr]['content']; + $content = $tokens[$stackPtr]['content']; + if ($tokens[$stackPtr]['code'] === T_NAME_FULLY_QUALIFIED) { + $content = ltrim($content, '\\'); + } + $contentLc = strtolower($content); if ($content === $contentLc) { return; @@ -93,7 +100,7 @@ public function process(File $phpcsFile, $stackPtr) if ($tokens[$next]['code'] !== T_OPEN_PARENTHESIS) { // Is this a use statement importing a PHP native function ? - if ($tokens[$next]['code'] !== T_NS_SEPARATOR + if ($tokens[$stackPtr]['code'] === T_STRING && $tokens[$prev]['code'] === T_STRING && $tokens[$prev]['content'] === 'function' && $prevPrev !== false @@ -120,19 +127,6 @@ public function process(File $phpcsFile, $stackPtr) return; } - if ($tokens[$prev]['code'] === T_NS_SEPARATOR) { - if ($prevPrev !== false - && ($tokens[$prevPrev]['code'] === T_STRING - || $tokens[$prevPrev]['code'] === T_NAMESPACE - || $tokens[$prevPrev]['code'] === T_NEW) - ) { - // Namespaced class/function, not an inbuilt function. - // Could potentially give false negatives for non-namespaced files - // when namespace\functionName() is encountered. - return; - } - } - if ($tokens[$prev]['code'] === T_NEW) { // Object creation, not an inbuilt function. return; @@ -158,7 +152,7 @@ public function process(File $phpcsFile, $stackPtr) $fix = $phpcsFile->addFixableError($error, $stackPtr, 'CallUppercase', $data); if ($fix === true) { - $phpcsFile->fixer->replaceToken($stackPtr, $contentLc); + $phpcsFile->fixer->replaceToken($stackPtr, strtolower($tokens[$stackPtr]['content'])); } }//end process() diff --git a/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc b/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc index 702b13de0a..99b6246ba9 100644 --- a/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc @@ -33,9 +33,9 @@ class ArrayUnique {} $sillyComments = strToLower /*comment*/ ($string); $callToGlobalFunction = \STR_REPEAT($a, 2); -$callToGlobalFunction = \ /*comment*/ str_Repeat($a, 2); +$callToGlobalFunction = \str_Repeat($a, 2); -$callToNamespacedFunction = MyNamespace /* phpcs:ignore Standard */ \STR_REPEAT($a, 2); +$callToNamespacedFunction = MyNamespace\STR_REPEAT($a, 2); $callToNamespacedFunction = namespace\STR_REPEAT($a, 2); // Could potentially be false negative. $filePath = new \File($path); diff --git a/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc.fixed b/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc.fixed index 281425c595..67aa475930 100644 --- a/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc.fixed @@ -33,9 +33,9 @@ class ArrayUnique {} $sillyComments = strtolower /*comment*/ ($string); $callToGlobalFunction = \str_repeat($a, 2); -$callToGlobalFunction = \ /*comment*/ str_repeat($a, 2); +$callToGlobalFunction = \str_repeat($a, 2); -$callToNamespacedFunction = MyNamespace /* phpcs:ignore Standard */ \STR_REPEAT($a, 2); +$callToNamespacedFunction = MyNamespace\STR_REPEAT($a, 2); $callToNamespacedFunction = namespace\STR_REPEAT($a, 2); // Could potentially be false negative. $filePath = new \File($path); From 459f9712981223ae80468cc9bc8ce198dfe84b78 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 2 Sep 2020 04:11:50 +0200 Subject: [PATCH 40/41] PEAR/FunctionCallSignature: add tests with the PHP 8 identifier tokens ... to make sure that function calls using the new tokens are handled correctly by the sniff. --- .../Functions/FunctionCallSignatureUnitTest.inc | 14 ++++++++++++++ .../FunctionCallSignatureUnitTest.inc.fixed | 17 +++++++++++++++++ .../Functions/FunctionCallSignatureUnitTest.php | 6 ++++++ 3 files changed, 37 insertions(+) diff --git a/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc b/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc index 6f2c90a367..d73725bfc6 100644 --- a/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc +++ b/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc @@ -574,3 +574,17 @@ content 'my_file.php' ); ?> + + + + 1, 573 => 1, 574 => 1, + 579 => 3, + 580 => 2, + 581 => 1, + 583 => 2, + 584 => 1, + 586 => 2, ]; }//end getErrorList() From 121c58cd491d016bc4f8427289e36df6eea09971 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 7 Apr 2025 02:54:46 +0200 Subject: [PATCH 41/41] PSR2/NamespaceDeclaration: remove redundant condition What with the change in the namespaced names tokenization, a sniff listening for `T_NAMESPACE` will no longer receive tokens for namespace relative names as those no longer use that token. Keeping the existing tests for this in place as those are still a good safeguard to prevent a regression slipping in. --- .../PSR2/Sniffs/Namespaces/NamespaceDeclarationSniff.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Standards/PSR2/Sniffs/Namespaces/NamespaceDeclarationSniff.php b/src/Standards/PSR2/Sniffs/Namespaces/NamespaceDeclarationSniff.php index 7b1dad199f..e414a495e2 100644 --- a/src/Standards/PSR2/Sniffs/Namespaces/NamespaceDeclarationSniff.php +++ b/src/Standards/PSR2/Sniffs/Namespaces/NamespaceDeclarationSniff.php @@ -11,7 +11,6 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; -use PHP_CodeSniffer\Util\Tokens; class NamespaceDeclarationSniff implements Sniff { @@ -42,12 +41,6 @@ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); - $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); - if ($tokens[$nextNonEmpty]['code'] === T_NS_SEPARATOR) { - // Namespace keyword as operator. Not a declaration. - return; - } - $end = $phpcsFile->findEndOfStatement($stackPtr); for ($i = ($end + 1); $i < ($phpcsFile->numTokens - 1); $i++) { if ($tokens[$i]['line'] === $tokens[$end]['line']) {