diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index f3183d9b..8257b73c 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -7,7 +7,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3'] + php-versions: ['7.4', '8.0', '8.1', '8.2', '8.3'] extra-tests: ['0'] # We only need to run PHPStan and Drupal core regression tests once on # the latest PHP version. @@ -82,4 +82,14 @@ jobs: # ignored temporarily, add them with the --ignore option. run: | cd drupal/core - ../../vendor/bin/phpcs -p -s --parallel=$(nproc) + # Fix Drupal core PHPCS config by replacing removed sniffs. + sed -i 's/Drupal.Classes.ClassCreateInstance/SlevomatCodingStandard.ControlStructures.NewWithParentheses/g' phpcs.xml.dist + sed -i 's/Drupal.Classes.UnusedUseStatement/SlevomatCodingStandard.Namespaces.UnusedUses/g' phpcs.xml.dist + sed -i '//a \ ' phpcs.xml.dist + find . -type f -name "*.php" -exec sed -i 's#// phpcs:ignore Drupal.Classes.PropertyDeclaration, Drupal.NamingConventions.ValidVariableName.LowerCamelName, Drupal.Commenting.VariableComment.Missing#// phpcs:ignore Drupal.NamingConventions.ValidVariableName.LowerCamelName,PSR2.Classes.PropertyDeclaration.Underscore#g' {} + + find . -type f -name "*.php" -exec sed -i 's#// @codingStandardsIgnoreLine#// phpcs:ignore#g' {} + + find . -type f -name "*.php" -exec sed -i 's#// @codingStandardsIgnoreFile#// phpcs:ignoreFile#g' {} + + find . -type f -exec sed -i '/\/\/ phpcs:ignore/ s/, /,/g' {} + + find . -type f -name "*.php" -exec sed -i 's#// @codingStandardsIgnoreStart#// phpcs:disable#g' {} + + find . -type f -name "*.php" -exec sed -i 's#// @codingStandardsIgnoreEnd#// phpcs:enable#g' {} + + ../../vendor/bin/phpcs -p -s --parallel=$(nproc) --ignore=lib/Drupal/Core/Command/GenerateTheme.php,modules/mysql/tests/src/Kernel/mysql/Console/DbDumpCommandTest.php diff --git a/coder_sniffer/Drupal/Sniffs/Classes/ClassCreateInstanceSniff.php b/coder_sniffer/Drupal/Sniffs/Classes/ClassCreateInstanceSniff.php deleted file mode 100644 index 7d067fe6..00000000 --- a/coder_sniffer/Drupal/Sniffs/Classes/ClassCreateInstanceSniff.php +++ /dev/null @@ -1,125 +0,0 @@ - - */ - public function register() - { - return [T_NEW]; - - }//end register() - - - /** - * Processes this test, when one of its tokens is encountered. - * - * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token in the - * stack passed in $tokens. - * - * @return void - */ - public function process(File $phpcsFile, $stackPtr) - { - $tokens = $phpcsFile->getTokens(); - - $commaOrColon = $phpcsFile->findNext([T_SEMICOLON, T_COLON, T_COMMA], ($stackPtr + 1)); - if ($commaOrColon === false) { - // Syntax error, nothing we can do. - return; - } - - // Search for an opening parenthesis in the current statement until the - // next semicolon or comma. - $nextParenthesis = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($stackPtr + 1), $commaOrColon); - if ($nextParenthesis === false) { - $error = 'Calling class constructors must always include parentheses'; - $constructor = $phpcsFile->findNext(Tokens::EMPTY_TOKENS, ($stackPtr + 1), null, true, null, true); - // We can invoke the fixer if we know this is a static constructor - // function call or constructor calls with namespaces, example - // "new \DOMDocument;" or constructor with class names in variables - // "new $controller;". - if ($tokens[$constructor]['code'] === T_STRING - || $tokens[$constructor]['code'] === T_NS_SEPARATOR - || ($tokens[$constructor]['code'] === T_VARIABLE - && $tokens[($constructor + 1)]['code'] === T_SEMICOLON) - ) { - // Scan to the end of possible string\namespace parts. - $nextConstructorPart = $constructor; - while (true) { - $nextConstructorPart = $phpcsFile->findNext( - Tokens::EMPTY_TOKENS, - ($nextConstructorPart + 1), - null, - true, - null, - true - ); - if ($nextConstructorPart === false - || ($tokens[$nextConstructorPart]['code'] !== T_STRING - && $tokens[$nextConstructorPart]['code'] !== T_NS_SEPARATOR) - ) { - break; - } - - $constructor = $nextConstructorPart; - } - - $fix = $phpcsFile->addFixableError($error, $constructor, 'ParenthesisMissing'); - if ($fix === true) { - $phpcsFile->fixer->addContent($constructor, '()'); - } - - // We can invoke the fixer if we know this is a - // constructor call with class names in an array - // example "new $controller[$i];". - } else if ($tokens[$constructor]['code'] === T_VARIABLE - && $tokens[($constructor + 1)]['code'] === T_OPEN_SQUARE_BRACKET - ) { - // Scan to the end of possible multilevel arrays. - $nextConstructorPart = $constructor; - do { - $nextConstructorPart = $tokens[($nextConstructorPart + 1)]['bracket_closer']; - } while ($tokens[($nextConstructorPart + 1)]['code'] === T_OPEN_SQUARE_BRACKET); - - $fix = $phpcsFile->addFixableError($error, $nextConstructorPart, 'ParenthesisMissing'); - if ($fix === true) { - $phpcsFile->fixer->addContent($nextConstructorPart, '()'); - } - } else { - $phpcsFile->addError($error, $stackPtr, 'ParenthesisMissing'); - }//end if - }//end if - - }//end process() - - -}//end class diff --git a/coder_sniffer/Drupal/Sniffs/Classes/FullyQualifiedNamespaceSniff.php b/coder_sniffer/Drupal/Sniffs/Classes/FullyQualifiedNamespaceSniff.php index eac0e35a..c4ef66e6 100644 --- a/coder_sniffer/Drupal/Sniffs/Classes/FullyQualifiedNamespaceSniff.php +++ b/coder_sniffer/Drupal/Sniffs/Classes/FullyQualifiedNamespaceSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Util\Tokens; /** * Checks that class references do not use FQN but use statements. @@ -30,7 +31,10 @@ class FullyQualifiedNamespaceSniff implements Sniff */ public function register() { - return [T_NS_SEPARATOR]; + return [ + T_NAME_FULLY_QUALIFIED, + T_NAME_QUALIFIED, + ]; }//end register() @@ -61,7 +65,9 @@ public function process(File $phpcsFile, $stackPtr) // We are only interested in a backslash embedded between strings, which // means this is a class reference with more than one namespace part. - if ($tokens[($stackPtr - 1)]['code'] !== T_STRING || $tokens[($stackPtr + 1)]['code'] !== T_STRING) { + if (substr_count($tokens[$stackPtr]['content'], '\\') === 1 + && strpos($tokens[$stackPtr]['content'], '\\') === 0 + ) { return; } @@ -71,25 +77,12 @@ public function process(File $phpcsFile, $stackPtr) } // Check if this is a use statement and ignore those. - $before = $phpcsFile->findPrevious([T_STRING, T_NS_SEPARATOR, T_WHITESPACE, T_COMMA, T_AS], $stackPtr, null, true); + $before = $phpcsFile->findPrevious((Tokens::EMPTY_TOKENS + Tokens::NAME_TOKENS + [T_COMMA => T_COMMA, T_AS => T_AS]), ($stackPtr - 1), null, true); if ($tokens[$before]['code'] === T_USE || $tokens[$before]['code'] === T_NAMESPACE) { - return $phpcsFile->findNext([T_STRING, T_NS_SEPARATOR, T_WHITESPACE, T_COMMA, T_AS], ($stackPtr + 1), null, true); - } else { - $before = $phpcsFile->findPrevious([T_STRING, T_NS_SEPARATOR, T_WHITESPACE], $stackPtr, null, true); - } - - // If this is a namespaced function call then ignore this because use - // statements for functions are not possible in PHP 5.5 and lower. - $after = $phpcsFile->findNext([T_STRING, T_NS_SEPARATOR, T_WHITESPACE], $stackPtr, null, true); - if ($tokens[$after]['code'] === T_OPEN_PARENTHESIS - && $tokens[$before]['code'] !== T_NEW - && $tokens[$before]['code'] !== T_ATTRIBUTE - ) { - return ($after + 1); + return; } - $fullName = $phpcsFile->getTokensAsString(($before + 1), ($after - 1 - $before)); - $fullName = trim($fullName, "\ \n"); + $fullName = trim($tokens[$stackPtr]['content'], '\\ '); $parts = explode('\\', $fullName); $className = end($parts); @@ -101,7 +94,7 @@ public function process(File $phpcsFile, $stackPtr) $useStatement = $phpcsFile->findNext(T_USE, 0); while ($useStatement !== false && empty($tokens[$useStatement]['conditions']) === true) { $endPtr = $phpcsFile->findEndOfStatement($useStatement); - $useEnd = ($phpcsFile->findNext([T_STRING, T_NS_SEPARATOR, T_WHITESPACE], ($useStatement + 1), null, true) - 1); + $useEnd = ($phpcsFile->findNext((Tokens::EMPTY_TOKENS + Tokens::NAME_TOKENS), ($useStatement + 1), null, true) - 1); $useFullName = trim($phpcsFile->getTokensAsString(($useStatement + 1), ($useEnd - $useStatement))); // Check if use statement contains an alias. @@ -163,18 +156,11 @@ public function process(File $phpcsFile, $stackPtr) if ($fix === true) { $phpcsFile->fixer->beginChangeset(); - // Replace the fully qualified name with the local name. - for ($i = ($before + 1); $i < $after; $i++) { - if ($tokens[$i]['code'] !== T_WHITESPACE) { - $phpcsFile->fixer->replaceToken($i, ''); - } - } - // Use alias name if available. if ($aliasName !== false) { - $phpcsFile->fixer->addContentBefore(($after - 1), $aliasName); + $phpcsFile->fixer->replaceToken($stackPtr, $aliasName); } else { - $phpcsFile->fixer->addContentBefore(($after - 1), $className); + $phpcsFile->fixer->replaceToken($stackPtr, $className); } // Insert use statement at the beginning of the file if it is not there @@ -216,10 +202,6 @@ public function process(File $phpcsFile, $stackPtr) $phpcsFile->fixer->endChangeset(); }//end if - // Continue after this class reference so that errors for this are not - // flagged multiple times. - return $phpcsFile->findNext([T_STRING, T_NS_SEPARATOR], ($stackPtr + 1), null, true); - }//end process() diff --git a/coder_sniffer/Drupal/Sniffs/Classes/UnusedUseStatementSniff.php b/coder_sniffer/Drupal/Sniffs/Classes/UnusedUseStatementSniff.php deleted file mode 100644 index 8f98d4fb..00000000 --- a/coder_sniffer/Drupal/Sniffs/Classes/UnusedUseStatementSniff.php +++ /dev/null @@ -1,211 +0,0 @@ - - */ - public function register() - { - return [T_USE]; - - }//end register() - - - /** - * Processes this test, when one of its tokens is encountered. - * - * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token in - * the stack passed in $tokens. - * - * @return void - */ - public function process(File $phpcsFile, $stackPtr) - { - $tokens = $phpcsFile->getTokens(); - - // Only check use statements in the global scope. - if (empty($tokens[$stackPtr]['conditions']) === false) { - return; - } - - // Seek to the end of the statement and get the string before the semi colon. - $semiColon = $phpcsFile->findEndOfStatement($stackPtr); - if ($tokens[$semiColon]['code'] !== T_SEMICOLON) { - return; - } - - $classPtr = $phpcsFile->findPrevious( - Tokens::EMPTY_TOKENS, - ($semiColon - 1), - null, - true - ); - - if ($tokens[$classPtr]['code'] !== T_STRING) { - return; - } - - // Search where the class name is used. PHP treats class names case - // insensitive, that's why we cannot search for the exact class name string - // and need to iterate over all T_STRING tokens in the file. - $classUsed = $phpcsFile->findNext(T_STRING, ($classPtr + 1)); - $lowerClassName = strtolower($tokens[$classPtr]['content']); - - // Check if the referenced class is in the same namespace as the current - // file. If it is then the use statement is not necessary. - $namespacePtr = $phpcsFile->findPrevious([T_NAMESPACE], $stackPtr); - // Check if the use statement does aliasing with the "as" keyword. Aliasing - // is allowed even in the same namespace. - $aliasUsed = $phpcsFile->findPrevious(T_AS, ($classPtr - 1), $stackPtr); - - if ($namespacePtr !== false && $aliasUsed === false) { - $nsEnd = $phpcsFile->findNext( - [ - T_NS_SEPARATOR, - T_STRING, - T_WHITESPACE, - ], - ($namespacePtr + 1), - null, - true - ); - $namespace = trim($phpcsFile->getTokensAsString(($namespacePtr + 1), ($nsEnd - $namespacePtr - 1))); - - $useNamespacePtr = $phpcsFile->findNext([T_STRING], ($stackPtr + 1)); - $useNamespaceEnd = $phpcsFile->findNext( - [ - T_NS_SEPARATOR, - T_STRING, - ], - ($useNamespacePtr + 1), - null, - true - ); - $useNamespace = rtrim($phpcsFile->getTokensAsString($useNamespacePtr, ($useNamespaceEnd - $useNamespacePtr - 1)), '\\'); - - if (strcasecmp($namespace, $useNamespace) === 0) { - $classUsed = false; - } - }//end if - - while ($classUsed !== false) { - if (strtolower($tokens[$classUsed]['content']) === $lowerClassName) { - $beforeUsage = $phpcsFile->findPrevious( - Tokens::EMPTY_TOKENS, - ($classUsed - 1), - null, - true - ); - // If a backslash is used before the class name then this is some other - // use statement. - if (in_array( - $tokens[$beforeUsage]['code'], - [ - T_USE, - T_NS_SEPARATOR, - // If an object operator is used then this is a method call - // with the same name as the class name. Which means this is - // not referring to the class. - T_OBJECT_OPERATOR, - // Function definition, not class invocation. - T_FUNCTION, - // Static method call, not class invocation. - T_DOUBLE_COLON, - ] - ) === false - ) { - return; - } - - // Trait use statement within a class. - if ($tokens[$beforeUsage]['code'] === T_USE && empty($tokens[$beforeUsage]['conditions']) === false) { - return; - } - }//end if - - $classUsed = $phpcsFile->findNext([T_STRING], ($classUsed + 1)); - }//end while - - $warning = 'Unused use statement'; - $fix = $phpcsFile->addFixableWarning($warning, $stackPtr, 'UnusedUse'); - if ($fix === true) { - // Remove the whole use statement line. - $phpcsFile->fixer->beginChangeset(); - for ($i = $stackPtr; $i <= $semiColon; $i++) { - $phpcsFile->fixer->replaceToken($i, ''); - } - - // Also remove whitespace after the semicolon (new lines). - while (isset($tokens[$i]) === true && $tokens[$i]['code'] === T_WHITESPACE) { - $phpcsFile->fixer->replaceToken($i, ''); - if (strpos($tokens[$i]['content'], $phpcsFile->eolChar) !== false) { - break; - } - - $i++; - } - - // Replace @var data types in doc comments with the fully qualified class - // name. - $useNamespacePtr = $phpcsFile->findNext([T_STRING], ($stackPtr + 1)); - $useNamespaceEnd = $phpcsFile->findNext( - [ - T_NS_SEPARATOR, - T_STRING, - ], - ($useNamespacePtr + 1), - null, - true - ); - $fullNamespace = $phpcsFile->getTokensAsString($useNamespacePtr, ($useNamespaceEnd - $useNamespacePtr)); - - $tag = $phpcsFile->findNext(T_DOC_COMMENT_TAG, ($stackPtr + 1)); - - while ($tag !== false) { - if (($tokens[$tag]['content'] === '@var' || $tokens[$tag]['content'] === '@return') - && isset($tokens[($tag + 1)]) === true - && $tokens[($tag + 1)]['code'] === T_DOC_COMMENT_WHITESPACE - && isset($tokens[($tag + 2)]) === true - && $tokens[($tag + 2)]['code'] === T_DOC_COMMENT_STRING - && strpos($tokens[($tag + 2)]['content'], $tokens[$classPtr]['content']) === 0 - ) { - $replacement = '\\'.$fullNamespace.substr($tokens[($tag + 2)]['content'], strlen($tokens[$classPtr]['content'])); - $phpcsFile->fixer->replaceToken(($tag + 2), $replacement); - } - - $tag = $phpcsFile->findNext(T_DOC_COMMENT_TAG, ($tag + 1)); - } - - $phpcsFile->fixer->endChangeset(); - }//end if - - }//end process() - - -}//end class diff --git a/coder_sniffer/Drupal/Sniffs/Classes/UseGlobalClassSniff.php b/coder_sniffer/Drupal/Sniffs/Classes/UseGlobalClassSniff.php index 7319f104..2bf03c08 100644 --- a/coder_sniffer/Drupal/Sniffs/Classes/UseGlobalClassSniff.php +++ b/coder_sniffer/Drupal/Sniffs/Classes/UseGlobalClassSniff.php @@ -75,19 +75,20 @@ public function process(File $phpcsFile, $stackPtr) $lineStart = $stackPtr; // Iterate through a potential multiline use statement. while (false !== $lineEnd = $phpcsFile->findNext([T_SEMICOLON, T_COMMA], ($lineStart + 1), ($stmtEnd + 1))) { - // We are only interested in imports that contain no backslash, - // which means this is a class without a namespace. - // Also skip function imports. - if ($phpcsFile->findNext(T_NS_SEPARATOR, $lineStart, $lineEnd) !== false - || $phpcsFile->findNext(T_STRING, $lineStart, $lineEnd, false, 'function') !== false - ) { + // Skip function imports. + if ($phpcsFile->findNext(T_STRING, $lineStart, $lineEnd, false, 'function') !== false) { $lineStart = $lineEnd; continue; } - // The first string token is the class name. - $class = $phpcsFile->findNext(T_STRING, $lineStart, $lineEnd); + $class = $phpcsFile->findNext(Tokens::NAME_TOKENS, $lineStart, $lineEnd); $className = $tokens[$class]['content']; + if (strpos($className, '\\') !== false) { + // This is a namespaced class, skip it. + $lineStart = $lineEnd; + continue; + } + // If there is more than one string token, the last one is the alias. $alias = $phpcsFile->findPrevious(T_STRING, $lineEnd, $stackPtr); $aliasName = $tokens[$alias]['content']; @@ -125,9 +126,7 @@ public function process(File $phpcsFile, $stackPtr) // Only start looking after the end of the use statement block. $i = $bodyStart; while (false !== $i = $phpcsFile->findNext(T_STRING, ($i + 1), null, false, $aliasName)) { - if ($tokens[($i - 1)]['code'] !== T_NS_SEPARATOR) { - $phpcsFile->fixer->replaceToken($i, '\\'.$className); - } + $phpcsFile->fixer->replaceToken($i, '\\'.$className); } $phpcsFile->fixer->endChangeset(); diff --git a/coder_sniffer/Drupal/Sniffs/Classes/UseLeadingBackslashSniff.php b/coder_sniffer/Drupal/Sniffs/Classes/UseLeadingBackslashSniff.php index 200ae848..0c323f6d 100644 --- a/coder_sniffer/Drupal/Sniffs/Classes/UseLeadingBackslashSniff.php +++ b/coder_sniffer/Drupal/Sniffs/Classes/UseLeadingBackslashSniff.php @@ -61,11 +61,11 @@ public function process(File $phpcsFile, $stackPtr) true ); - if ($startPtr !== false && $tokens[$startPtr]['code'] === T_NS_SEPARATOR) { + if ($startPtr !== false && $tokens[$startPtr]['code'] === T_NAME_FULLY_QUALIFIED) { $error = 'When importing a class with "use", do not include a leading \\'; $fix = $phpcsFile->addFixableError($error, $startPtr, 'SeparatorStart'); if ($fix === true) { - $phpcsFile->fixer->replaceToken($startPtr, ''); + $phpcsFile->fixer->replaceToken($startPtr, ltrim($tokens[$startPtr]['content'], '\\')); } } diff --git a/coder_sniffer/Drupal/Sniffs/Commenting/DataTypeNamespaceSniff.php b/coder_sniffer/Drupal/Sniffs/Commenting/DataTypeNamespaceSniff.php index bed4c6d1..7602dca7 100644 --- a/coder_sniffer/Drupal/Sniffs/Commenting/DataTypeNamespaceSniff.php +++ b/coder_sniffer/Drupal/Sniffs/Commenting/DataTypeNamespaceSniff.php @@ -67,23 +67,14 @@ public function process(File $phpcsFile, $stackPtr) true ); - if ($tokens[$classPtr]['code'] !== T_STRING) { + if (in_array($tokens[$classPtr]['code'], Tokens::NAME_TOKENS) === false) { return; } // Replace @var data types in doc comments with the fully qualified class // name. - $useNamespacePtr = $phpcsFile->findNext([T_STRING], ($stackPtr + 1)); - $useNamespaceEnd = $phpcsFile->findNext( - [ - T_NS_SEPARATOR, - T_STRING, - ], - ($useNamespacePtr + 1), - null, - true - ); - $fullNamespace = $phpcsFile->getTokensAsString($useNamespacePtr, ($useNamespaceEnd - $useNamespacePtr)); + $fullNamespace = $tokens[$classPtr]['content']; + $className = substr($fullNamespace, (strrpos($fullNamespace, '\\') + 1)); $tag = $phpcsFile->findNext(T_DOC_COMMENT_TAG, ($stackPtr + 1)); @@ -96,13 +87,13 @@ public function process(File $phpcsFile, $stackPtr) && $tokens[($tag + 1)]['code'] === T_DOC_COMMENT_WHITESPACE && isset($tokens[($tag + 2)]) === true && $tokens[($tag + 2)]['code'] === T_DOC_COMMENT_STRING - && strpos($tokens[($tag + 2)]['content'], $tokens[$classPtr]['content']) === 0 + && strpos($tokens[($tag + 2)]['content'], $className) === 0 ) { $error = 'Data types in %s tags need to be fully namespaced'; $data = [$tokens[$tag]['content']]; $fix = $phpcsFile->addFixableError($error, ($tag + 2), 'DataTypeNamespace', $data); if ($fix === true) { - $replacement = '\\'.$fullNamespace.substr($tokens[($tag + 2)]['content'], strlen($tokens[$classPtr]['content'])); + $replacement = '\\'.$fullNamespace.substr($tokens[($tag + 2)]['content'], strlen($className)); $phpcsFile->fixer->replaceToken(($tag + 2), $replacement); } } diff --git a/coder_sniffer/Drupal/Sniffs/Commenting/FileCommentSniff.php b/coder_sniffer/Drupal/Sniffs/Commenting/FileCommentSniff.php index 72b490d4..fc1d8de3 100644 --- a/coder_sniffer/Drupal/Sniffs/Commenting/FileCommentSniff.php +++ b/coder_sniffer/Drupal/Sniffs/Commenting/FileCommentSniff.php @@ -138,16 +138,12 @@ public function process(File $phpcsFile, $stackPtr) if ($fix === true) { // Only PHP has a real opening tag, additional newline at the // beginning here. - if ($phpcsFile->tokenizerType === 'PHP') { - // In templates add the file doc block to the very beginning of - // the file. - if ($tokens[0]['code'] === T_INLINE_HTML) { - $phpcsFile->fixer->addContentBefore(0, "\n"); - } else { - $phpcsFile->fixer->addContent($stackPtr, "\n/**\n * @file\n */\n"); - } + // In templates add the file doc block to the very beginning of + // the file. + if ($tokens[0]['code'] === T_INLINE_HTML) { + $phpcsFile->fixer->addContentBefore(0, "\n"); } else { - $phpcsFile->fixer->addContent($stackPtr, "/**\n * @file\n */\n"); + $phpcsFile->fixer->addContent($stackPtr, "\n/**\n * @file\n */\n"); } } @@ -166,13 +162,7 @@ public function process(File $phpcsFile, $stackPtr) if ($fileTag === false) { $fix = $phpcsFile->addFixableError('Missing file doc comment', $stackPtr, 'Missing'); if ($fix === true) { - // Only PHP has a real opening tag, additional newline at the - // beginning here. - if ($phpcsFile->tokenizerType === 'PHP') { - $phpcsFile->fixer->addContent($stackPtr, "\n/**\n * @file\n */\n"); - } else { - $phpcsFile->fixer->addContent($stackPtr, "/**\n * @file\n */\n"); - } + $phpcsFile->fixer->addContent($stackPtr, "\n/**\n * @file\n */\n"); } return ($phpcsFile->numTokens + 1); diff --git a/coder_sniffer/Drupal/Sniffs/Commenting/InlineCommentSniff.php b/coder_sniffer/Drupal/Sniffs/Commenting/InlineCommentSniff.php index 49c77160..74415b79 100644 --- a/coder_sniffer/Drupal/Sniffs/Commenting/InlineCommentSniff.php +++ b/coder_sniffer/Drupal/Sniffs/Commenting/InlineCommentSniff.php @@ -83,7 +83,6 @@ public function process(File $phpcsFile, $stackPtr) T_STATIC, T_ABSTRACT, T_CONST, - T_PROPERTY, T_INCLUDE, T_INCLUDE_ONCE, T_REQUIRE, diff --git a/coder_sniffer/Drupal/Sniffs/Commenting/VariableCommentSniff.php b/coder_sniffer/Drupal/Sniffs/Commenting/VariableCommentSniff.php index aef359ee..ac7c6fb2 100644 --- a/coder_sniffer/Drupal/Sniffs/Commenting/VariableCommentSniff.php +++ b/coder_sniffer/Drupal/Sniffs/Commenting/VariableCommentSniff.php @@ -47,8 +47,6 @@ public function processMemberVar(File $phpcsFile, $stackPtr) T_STATIC => T_STATIC, T_READONLY => T_READONLY, 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, @@ -58,7 +56,7 @@ public function processMemberVar(File $phpcsFile, $stackPtr) T_FALSE => T_FALSE, T_SELF => T_SELF, T_PARENT => T_PARENT, - ] + Tokens::PHPCS_ANNOTATION_TOKENS); + ] + Tokens::PHPCS_ANNOTATION_TOKENS + Tokens::NAME_TOKENS); for ($commentEnd = ($stackPtr - 1); $commentEnd >= 0; $commentEnd--) { if (isset($ignore[$tokens[$commentEnd]['code']]) === true) { @@ -133,7 +131,7 @@ public function processMemberVar(File $phpcsFile, $stackPtr) if ($foundVar === null) { // If there's an inline type argument then you may omit the @var comment. // Check if there's a type between the variable name and the comment end. - if ($phpcsFile->findPrevious([T_STRING], $stackPtr, $commentEnd) !== false) { + if ($phpcsFile->findPrevious(Tokens::NAME_TOKENS, $stackPtr, $commentEnd) !== false) { return; } diff --git a/coder_sniffer/Drupal/Sniffs/WhiteSpace/OpenTagNewlineSniff.php b/coder_sniffer/Drupal/Sniffs/WhiteSpace/OpenTagNewlineSniff.php index 0ac20d1f..302b52ac 100644 --- a/coder_sniffer/Drupal/Sniffs/WhiteSpace/OpenTagNewlineSniff.php +++ b/coder_sniffer/Drupal/Sniffs/WhiteSpace/OpenTagNewlineSniff.php @@ -48,6 +48,11 @@ public function register() */ public function process(File $phpcsFile, $stackPtr) { + // This sniff only works when there are \n line endings. + if ($phpcsFile->eolChar !== "\n") { + return ($phpcsFile->numTokens + 1); + } + $tokens = $phpcsFile->getTokens(); // Only check the very first PHP open tag in a file, ignore any others. diff --git a/coder_sniffer/Drupal/ruleset.xml b/coder_sniffer/Drupal/ruleset.xml index 5eb990f3..7c6ad8d4 100644 --- a/coder_sniffer/Drupal/ruleset.xml +++ b/coder_sniffer/Drupal/ruleset.xml @@ -37,6 +37,10 @@ 0 + + + *.tpl.php + @@ -136,7 +140,10 @@ + + + diff --git a/coder_sniffer/DrupalPractice/Project.php b/coder_sniffer/DrupalPractice/Project.php index c7d6a6a5..464e6166 100644 --- a/coder_sniffer/DrupalPractice/Project.php +++ b/coder_sniffer/DrupalPractice/Project.php @@ -196,18 +196,9 @@ public static function isServiceClass(File $phpcsFile, $classPtr) return false; } - $nsEnd = $phpcsFile->findNext( - [ - T_NS_SEPARATOR, - T_STRING, - T_WHITESPACE, - ], - ($namespacePtr + 1), - null, - true - ); - $namespace = trim($phpcsFile->getTokensAsString(($namespacePtr + 1), ($nsEnd - $namespacePtr - 1))); - $classNameSpaced = ltrim($namespace.'\\'.$phpcsFile->getDeclarationName($classPtr), '\\'); + $nameQualifiedPtr = $phpcsFile->findNext(T_NAME_QUALIFIED, ($namespacePtr + 1)); + $namespace = ($phpcsFile->getTokens()[$nameQualifiedPtr]['content'] ?? ''); + $classNameSpaced = ltrim($namespace.'\\'.$phpcsFile->getDeclarationName($classPtr), '\\'); foreach ($services['services'] as $service) { if (isset($service['class']) === true diff --git a/coder_sniffer/DrupalPractice/Sniffs/Objects/GlobalClassSniff.php b/coder_sniffer/DrupalPractice/Sniffs/Objects/GlobalClassSniff.php index f84c74e5..e19ca290 100644 --- a/coder_sniffer/DrupalPractice/Sniffs/Objects/GlobalClassSniff.php +++ b/coder_sniffer/DrupalPractice/Sniffs/Objects/GlobalClassSniff.php @@ -181,12 +181,18 @@ protected function getFullyQualifiedName(File $phpcsFile, $className) { $useStatement = $phpcsFile->findNext(T_USE, 0); while ($useStatement !== false) { - $endPtr = $phpcsFile->findEndOfStatement($useStatement); - $useEnd = ($phpcsFile->findNext([T_STRING, T_NS_SEPARATOR, T_WHITESPACE], ($useStatement + 1), null, true) - 1); - $useFullName = trim($phpcsFile->getTokensAsString(($useStatement + 1), ($useEnd - $useStatement)), '\\ '); + $endPtr = $phpcsFile->findEndOfStatement($useStatement); + $useFullNamePtr = $phpcsFile->findNext([T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED], ($useStatement + 1), $endPtr); + if ($useFullNamePtr === false) { + // No qualified name found, skip to next use statement. + $useStatement = $phpcsFile->findNext(T_USE, ($endPtr + 1)); + continue; + } + + $useFullName = trim($phpcsFile->getTokens()[$useFullNamePtr]['content'], '\\ '); // Check if use statement contains an alias. - $asPtr = $phpcsFile->findNext(T_AS, ($useEnd + 1), $endPtr); + $asPtr = $phpcsFile->findNext(T_AS, ($useFullNamePtr + 1), $endPtr); if ($asPtr !== false) { $aliasName = trim($phpcsFile->getTokensAsString(($asPtr + 1), ($endPtr - 1 - $asPtr))); if ($aliasName === $className) { diff --git a/coder_sniffer/DrupalPractice/Sniffs/Objects/GlobalDrupalSniff.php b/coder_sniffer/DrupalPractice/Sniffs/Objects/GlobalDrupalSniff.php index 4148ba8e..91245f85 100644 --- a/coder_sniffer/DrupalPractice/Sniffs/Objects/GlobalDrupalSniff.php +++ b/coder_sniffer/DrupalPractice/Sniffs/Objects/GlobalDrupalSniff.php @@ -51,7 +51,7 @@ class GlobalDrupalSniff implements Sniff */ public function register() { - return [T_STRING]; + return [T_NAME_FULLY_QUALIFIED]; }//end register() @@ -71,7 +71,7 @@ public function process(File $phpcsFile, $stackPtr) // We are only interested in Drupal:: static method calls, not in the global // scope. - if ($tokens[$stackPtr]['content'] !== 'Drupal' + if ($tokens[$stackPtr]['content'] !== '\Drupal' || $tokens[($stackPtr + 1)]['code'] !== T_DOUBLE_COLON || isset($tokens[($stackPtr + 2)]) === false || $tokens[($stackPtr + 2)]['code'] !== T_STRING diff --git a/composer.json b/composer.json index 5858f899..c9e0ec26 100644 --- a/composer.json +++ b/composer.json @@ -14,12 +14,12 @@ ], "license": "GPL-2.0-or-later", "require": { - "php": ">=7.2", + "php": ">=7.4", "ext-mbstring": "*", "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1 || ^1.0.0", - "sirbrillig/phpcs-variable-analysis": "^2.11.7", - "slevomat/coding-standard": "^8.11", - "squizlabs/php_codesniffer": "^3.13", + "sirbrillig/phpcs-variable-analysis": "^2.13", + "slevomat/coding-standard": "^8.24", + "squizlabs/php_codesniffer": "^4.0", "symfony/yaml": ">=3.4.0" }, "autoload": { @@ -36,6 +36,6 @@ }, "require-dev": { "phpstan/phpstan": "^1.7.12", - "phpunit/phpunit": "^8.0" + "phpunit/phpunit": "^9.0" } } diff --git a/phpcs.xml.dist b/phpcs.xml.dist index f48d02d1..650e6c16 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -106,7 +106,6 @@ - @@ -154,7 +153,9 @@ - + + + diff --git a/tests/Drupal/Classes/ClassCreateInstanceUnitTest.php b/tests/Drupal/Classes/ClassCreateInstanceUnitTest.php deleted file mode 100644 index e220ea96..00000000 --- a/tests/Drupal/Classes/ClassCreateInstanceUnitTest.php +++ /dev/null @@ -1,59 +0,0 @@ - - */ - protected function getErrorList(string $testFile): array - { - return [ - 3 => 1, - 4 => 1, - 5 => 1, - 6 => 1, - 8 => 1, - 9 => 1, - 10 => 1, - 11 => 1, - 12 => 1, - 13 => 1, - 14 => 1, - 16 => 1, - 31 => 1, - ]; - - }//end getErrorList() - - - /** - * Returns the lines where warnings should occur. - * - * The key of the array should represent the line number and the value - * should represent the number of warnings that should occur on that line. - * - * @param string $testFile The name of the file being tested. - * - * @return array - */ - protected function getWarningList(string $testFile): array - { - return []; - - }//end getWarningList() - - -}//end class diff --git a/tests/Drupal/Classes/ClassFileNameUnitTest.php b/tests/Drupal/Classes/ClassFileNameUnitTest.php index 7cc4349a..c1fd3269 100644 --- a/tests/Drupal/Classes/ClassFileNameUnitTest.php +++ b/tests/Drupal/Classes/ClassFileNameUnitTest.php @@ -59,7 +59,7 @@ protected function getTestFiles($testFileBase): array return [ __DIR__.'/drupal8/ClassFileNameUnitTest.php', __DIR__.'/drupal8/drupal8.behat.inc', - __DIR__.'/drupal7/class_fle_name_test.module', + __DIR__.'/drupal7/class_file_name_test.module', __DIR__.'/drupal8/markdownFile.md', ]; diff --git a/tests/Drupal/Classes/FullyQualifiedNamespaceUnitTest.inc b/tests/Drupal/Classes/FullyQualifiedNamespaceUnitTest.inc index 1819ef46..5ddd6d05 100644 --- a/tests/Drupal/Classes/FullyQualifiedNamespaceUnitTest.inc +++ b/tests/Drupal/Classes/FullyQualifiedNamespaceUnitTest.inc @@ -8,8 +8,8 @@ use Test\Bar; use Test\NotUsed; use Test\Alias as TestAlias; -use Test\MultiLine as MultiLineAlias, - Test\MultiLineSecond; +use Test\MultiLine as MultiLineAlias; +use Test\MultiLineSecond; /** * Example. diff --git a/tests/Drupal/Classes/FullyQualifiedNamespaceUnitTest.inc.fixed b/tests/Drupal/Classes/FullyQualifiedNamespaceUnitTest.inc.fixed index 004f9eaa..277c94b8 100644 --- a/tests/Drupal/Classes/FullyQualifiedNamespaceUnitTest.inc.fixed +++ b/tests/Drupal/Classes/FullyQualifiedNamespaceUnitTest.inc.fixed @@ -6,11 +6,11 @@ */ use Test\MultiLineSecond; +use Test\MultiLine; use Test\Foo; use Test\NotUsed; use Test\Bar; use Test\Alias as TestAlias; -use Test\MultiLine as MultiLineAlias; /** * Example. @@ -69,7 +69,7 @@ class Example { /** * Description. */ - public function test8(MultiLineAlias $multiLine, MultiLineSecond $multiLineSecond) { + public function test8(MultiLine $multiLine, MultiLineSecond $multiLineSecond) { } diff --git a/tests/Drupal/Classes/UnusedUseStatementUnitTest.php b/tests/Drupal/Classes/UnusedUseStatementUnitTest.php deleted file mode 100644 index 70950b9e..00000000 --- a/tests/Drupal/Classes/UnusedUseStatementUnitTest.php +++ /dev/null @@ -1,60 +0,0 @@ - - */ - protected function getErrorList(string $testFile): array - { - return []; - - }//end getErrorList() - - - /** - * Returns the lines where warnings should occur. - * - * The key of the array should represent the line number and the value - * should represent the number of warnings that should occur on that line. - * - * @param string $testFile The name of the file being tested. - * - * @return array - */ - protected function getWarningList(string $testFile): array - { - return [ - 5 => 1, - 6 => 1, - 7 => 1, - 10 => 1, - 11 => 1, - 12 => 1, - 14 => 1, - 16 => 1, - 17 => 1, - 19 => 1, - 20 => 1, - 21 => 1, - 22 => 1, - 23 => 1, - ]; - - }//end getWarningList() - - -}//end class diff --git a/tests/Drupal/Classes/UseGlobalClassUnitTest.inc.fixed b/tests/Drupal/Classes/UseGlobalClassUnitTest.inc.fixed index a1518555..40081c19 100644 --- a/tests/Drupal/Classes/UseGlobalClassUnitTest.inc.fixed +++ b/tests/Drupal/Classes/UseGlobalClassUnitTest.inc.fixed @@ -8,7 +8,6 @@ use Namespaced\TestClass; use Namespaced\TestClassSecond as NamespacedAlias; use Namespaced\MultiLine2 as MultiLineAlias2; -use function count; /** * Example. diff --git a/tests/Drupal/CoderSniffUnitTest.php b/tests/Drupal/CoderSniffUnitTest.php index 4210b88b..e4fb08fb 100644 --- a/tests/Drupal/CoderSniffUnitTest.php +++ b/tests/Drupal/CoderSniffUnitTest.php @@ -17,7 +17,6 @@ use PHP_CodeSniffer\Ruleset; use PHP_CodeSniffer\Files\LocalFile; use PHP_CodeSniffer\Exceptions\RuntimeException; -use PHP_CodeSniffer\Util\Common; use PHP_CodeSniffer\Util\Tokens; use PHPUnit\Framework\TestCase; @@ -62,8 +61,6 @@ abstract class CoderSniffUnitTest extends TestCase */ public function setUp(): void { - $class = get_class($this); - $this->rootDir = __DIR__.'/../../'; $this->testsDir = __DIR__.'/'; // Required to pull in all the defines from the tokens file. @@ -89,7 +86,7 @@ public function setUp(): void * * @return array */ - protected function getTestFiles($testFileBase): array + protected function getTestFiles(string $testFileBase): array { $testFiles = []; @@ -140,7 +137,7 @@ final public function testSniff() $this->markTestSkipped(); } - $sniffCode = Common::getSniffCode(get_class($this)); + $sniffCode = $this->getSniffCode(static::class); list($standardName, $categoryName, $sniffName) = explode('.', $sniffCode); // In the case where we are running all the sniffs, the standard will @@ -423,6 +420,45 @@ public function generateFailureMessages(LocalFile $file): array }//end generateFailureMessages() + /** + * Given a test class name, returns the code for the sniff. + * + * Forked from PHP_CodeSniffer\Util\Common to work with our test files. + * + * @param string $testClass The fully qualified test class name. + * + * @return string + * + * @throws \InvalidArgumentException When $testClass is not a non-empty string. + * @throws \InvalidArgumentException When $testClass is not a valid FQN for a test class. + */ + public function getSniffCode(string $testClass): string + { + if ($testClass === '') { + throw new \InvalidArgumentException('The $testClass parameter must be a non-empty string'); + } + + $parts = explode('\\', $testClass); + $partsCount = count($parts); + + $sniff = $parts[($partsCount - 1)]; + + if ($sniff !== 'UnitTest' && substr($sniff, -8) === 'UnitTest') { + // Unit test class name. + $sniff = substr($sniff, 0, -8); + } else { + throw new \InvalidArgumentException( + 'The $testClass parameter was not passed a fully qualified sniff(test) class name. Received: '.$testClass + ); + } + + $standard = $parts[($partsCount - 4)]; + $category = $parts[($partsCount - 2)]; + return $standard.'.'.$category.'.'.$sniff; + + }//end getSniffCode() + + /** * Set a list of CLI values before the file is tested. * diff --git a/tests/Drupal/bad/BadUnitTest.php b/tests/Drupal/bad/BadUnitTest.php index 4312e07b..bd0ebf86 100644 --- a/tests/Drupal/bad/BadUnitTest.php +++ b/tests/Drupal/bad/BadUnitTest.php @@ -29,7 +29,7 @@ protected function getErrorList(string $testFile): array switch ($testFile) { case 'bad_crlf.inc': return [ - 1 => 2, + 1 => 1, 8 => 1, ]; case 'bad.info': @@ -367,6 +367,43 @@ protected function getErrorList(string $testFile): array 872 => 1, 876 => 2, ]; + case 'ClassCreateInstanceUnitTest.inc': + return [ + 3 => 1, + 4 => 1, + 5 => 1, + 6 => 1, + 8 => 1, + 9 => 1, + 10 => 1, + 11 => 1, + 12 => 2, + 13 => 2, + 14 => 2, + 16 => 1, + 31 => 1, + ]; + case 'UnusedUseStatementUnitTest.inc': + return [ + 5 => 1, + 6 => 1, + 7 => 1, + 10 => 1, + 11 => 1, + 12 => 1, + 14 => 1, + 16 => 1, + 17 => 2, + 19 => 1, + 20 => 1, + 21 => 1, + 22 => 1, + 23 => 1, + 35 => 1, + 56 => 1, + 85 => 1, + 98 => 1, + ]; }//end switch return []; diff --git a/tests/Drupal/Classes/ClassCreateInstanceUnitTest.inc b/tests/Drupal/bad/ClassCreateInstanceUnitTest.inc similarity index 100% rename from tests/Drupal/Classes/ClassCreateInstanceUnitTest.inc rename to tests/Drupal/bad/ClassCreateInstanceUnitTest.inc diff --git a/tests/Drupal/Classes/ClassCreateInstanceUnitTest.inc.fixed b/tests/Drupal/bad/ClassCreateInstanceUnitTest.inc.fixed similarity index 100% rename from tests/Drupal/Classes/ClassCreateInstanceUnitTest.inc.fixed rename to tests/Drupal/bad/ClassCreateInstanceUnitTest.inc.fixed diff --git a/tests/Drupal/Classes/UnusedUseStatementUnitTest.inc b/tests/Drupal/bad/UnusedUseStatementUnitTest.inc similarity index 100% rename from tests/Drupal/Classes/UnusedUseStatementUnitTest.inc rename to tests/Drupal/bad/UnusedUseStatementUnitTest.inc diff --git a/tests/Drupal/Classes/UnusedUseStatementUnitTest.inc.fixed b/tests/Drupal/bad/UnusedUseStatementUnitTest.inc.fixed similarity index 96% rename from tests/Drupal/Classes/UnusedUseStatementUnitTest.inc.fixed rename to tests/Drupal/bad/UnusedUseStatementUnitTest.inc.fixed index 2c620518..93b5fe68 100644 --- a/tests/Drupal/Classes/UnusedUseStatementUnitTest.inc.fixed +++ b/tests/Drupal/bad/UnusedUseStatementUnitTest.inc.fixed @@ -26,7 +26,7 @@ class Pum { /** * Aliased type that is otherwise unused. * - * @var \Some\Data\VarName2 + * @var AliasVarName2 */ protected $y; @@ -71,7 +71,7 @@ class Pum { protected function test6($x) { /** @var \Some\Data\VarName $y */ $y = $x['test']; - /** @var \Some\Data\VarName2 $z */ + /** @var AliasVarName2 $z */ $z = $x['test2']; return $y; } diff --git a/tests/Drupal/bad/bad_crlf.inc.fixed b/tests/Drupal/bad/bad_crlf.inc.fixed index ad434228..1fd9757b 100644 --- a/tests/Drupal/bad/bad_crlf.inc.fixed +++ b/tests/Drupal/bad/bad_crlf.inc.fixed @@ -5,4 +5,6 @@ namespace Drupal\example\Controller; /** * Foo. */ -class ExampleController {} +class ExampleController { + +} diff --git a/tests/Drupal/good/good.php b/tests/Drupal/good/good.php index f9747220..43d2ebdd 100644 --- a/tests/Drupal/good/good.php +++ b/tests/Drupal/good/good.php @@ -1397,17 +1397,6 @@ public function test() { } -// Namespaced function call is allowed because PHP 5.5 and lower do not support -// use statements for functions. -$default_config = [ - 'verify' => TRUE, - 'timeout' => 30, - 'headers' => [ - 'User-Agent' => 'Drupal/' . \Drupal::VERSION . ' (+https://www.drupal.org/) ' . \GuzzleHttp\default_user_agent(), - ], - 'handler' => $stack, -]; - // camelCase and snake_case variables are allowed. $snake_case = 1; $camelCase = 1; @@ -2065,3 +2054,23 @@ function pdo_weird_return_type($param) { * Comments are allowed to end in 3 dots... */ function comment_test_dots() {} + +/** + * Executes the page caching before the main kernel takes over the request. + */ +class PageCache implements HttpKernelInterface { + + /** + * The wrapped HTTP kernel. + */ + protected \Closure $httpKernel; + + /** + * The entity for this result. + * + * @var \Drupal\Core\Entity\EntityInterface + */ + // phpcs:ignore Drupal.NamingConventions.ValidVariableName.LowerCamelName,PSR2.Classes.PropertyDeclaration.Underscore + public $_entity = NULL; + +}