clean install

This commit is contained in:
2024-12-10 15:08:16 +01:00
commit e14eb2d8fd
31193 changed files with 3555714 additions and 0 deletions

View File

@@ -0,0 +1,125 @@
<?php
/**
* Class create instance Test.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Classes;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;
/**
* Class create instance Test.
*
* Checks the declaration of the class is correct.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class ClassCreateInstanceSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
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::$emptyTokens, ($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::$emptyTokens,
($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

View File

@@ -0,0 +1,195 @@
<?php
/**
* Class Declaration Test.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Classes;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Standards\PSR2\Sniffs\Classes\ClassDeclarationSniff as PSR2ClassDeclarationSniff;
use PHP_CodeSniffer\Util\Tokens;
/**
* Class Declaration Test.
*
* Checks the declaration of the class is correct.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class ClassDeclarationSniff extends PSR2ClassDeclarationSniff
{
/**
* {@inheritdoc}
*
* @var integer
*/
public $indent = 2;
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [
T_CLASS,
T_INTERFACE,
T_TRAIT,
T_ENUM,
];
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param integer $stackPtr The position of the current token in the
* stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$errorData = [strtolower($tokens[$stackPtr]['content'])];
if (isset($tokens[$stackPtr]['scope_opener']) === false) {
$error = 'Possible parse error: %s missing opening or closing brace';
$phpcsFile->addWarning($error, $stackPtr, 'MissingBrace', $errorData);
return;
}
$openingBrace = $tokens[$stackPtr]['scope_opener'];
$next = $phpcsFile->findNext(T_WHITESPACE, ($openingBrace + 1), null, true);
if ($tokens[$next]['line'] === $tokens[$openingBrace]['line'] && $tokens[$next]['code'] !== T_CLOSE_CURLY_BRACKET) {
$error = 'Opening brace must be the last content on the line';
$fix = $phpcsFile->addFixableError($error, $openingBrace, 'ContentAfterBrace');
if ($fix === true) {
$phpcsFile->fixer->addNewline($openingBrace);
}
}
$previous = $phpcsFile->findPrevious(T_WHITESPACE, ($openingBrace - 1), null, true);
$declarationLine = $tokens[$previous]['line'];
$braceLine = $tokens[$openingBrace]['line'];
$lineDifference = ($braceLine - $declarationLine);
if ($lineDifference > 0) {
$error = 'Opening brace should be on the same line as the declaration';
$fix = $phpcsFile->addFixableError($error, $openingBrace, 'BraceOnNewLine');
if ($fix === true) {
$phpcsFile->fixer->beginChangeset();
for ($i = ($previous + 1); $i < $openingBrace; $i++) {
$phpcsFile->fixer->replaceToken($i, '');
}
$phpcsFile->fixer->addContent($previous, ' ');
$phpcsFile->fixer->endChangeset();
}
return;
}
$openingBrace = $tokens[$stackPtr]['scope_opener'];
if ($tokens[($openingBrace - 1)]['code'] !== T_WHITESPACE) {
$length = 0;
} else if ($tokens[($openingBrace - 1)]['content'] === "\t") {
$length = '\t';
} else {
$length = strlen($tokens[($openingBrace - 1)]['content']);
}
if ($length !== 1) {
$error = 'Expected 1 space before opening brace; found %s';
$data = [$length];
$fix = $phpcsFile->addFixableError($error, $openingBrace, 'SpaceBeforeBrace', $data);
if ($fix === true) {
if ($length === 0) {
$phpcsFile->fixer->replaceToken(($openingBrace), ' {');
} else {
$phpcsFile->fixer->replaceToken(($openingBrace - 1), ' ');
}
}
}
// Now call the open spacing method from PSR2.
$this->processOpen($phpcsFile, $stackPtr);
$this->processClose($phpcsFile, $stackPtr);
}//end process()
/**
* Processes the closing section of a class declaration.
*
* @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 processClose(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
// Just in case.
if (isset($tokens[$stackPtr]['scope_closer']) === false) {
return;
}
// Check that the closing brace comes right after the code body.
$closeBrace = $tokens[$stackPtr]['scope_closer'];
$prevContent = $phpcsFile->findPrevious(T_WHITESPACE, ($closeBrace - 1), null, true);
if ($prevContent !== $tokens[$stackPtr]['scope_opener']
&& $tokens[$prevContent]['line'] !== ($tokens[$closeBrace]['line'] - 2)
// If the class only contains a comment no extra line is needed.
&& isset(Tokens::$commentTokens[$tokens[$prevContent]['code']]) === false
// Enums are allowed to enclose the cases without an extra line.
&& $tokens[$stackPtr]['code'] !== T_ENUM
) {
$error = 'The closing brace for the %s must have an empty line before it';
$data = [$tokens[$stackPtr]['content']];
$fix = $phpcsFile->addFixableError($error, $closeBrace, 'CloseBraceAfterBody', $data);
if ($fix === true) {
$phpcsFile->fixer->beginChangeset();
for ($i = ($prevContent + 1); $i < $closeBrace; $i++) {
$phpcsFile->fixer->replaceToken($i, '');
}
$phpcsFile->fixer->replaceToken($closeBrace, $phpcsFile->eolChar.$phpcsFile->eolChar.$tokens[$closeBrace]['content']);
$phpcsFile->fixer->endChangeset();
}
}//end if
// Check the closing brace is on it's own line, but allow
// for comments like "//end class".
$nextContent = $phpcsFile->findNext(T_COMMENT, ($closeBrace + 1), null, true);
if ($tokens[$nextContent]['content'] !== $phpcsFile->eolChar
&& $tokens[$nextContent]['line'] === $tokens[$closeBrace]['line']
) {
$type = strtolower($tokens[$stackPtr]['content']);
$error = 'Closing %s brace must be on a line by itself';
$data = [$tokens[$stackPtr]['content']];
$phpcsFile->addError($error, $closeBrace, 'CloseBraceSameLine', $data);
}
}//end processClose()
}//end class

View File

@@ -0,0 +1,90 @@
<?php
/**
* Largely copied from
* PHP_CodeSniffer\Standards\Squiz\Sniffs\Classes\ClassFileNameSniff.
*
* Extended to support anonymous classes and Drupal core version.
*/
namespace Drupal\Sniffs\Classes;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
use DrupalPractice\Project;
class ClassFileNameSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [
T_CLASS,
T_INTERFACE,
T_TRAIT,
T_ENUM,
];
}//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 int
*/
public function process(File $phpcsFile, $stackPtr)
{
// This check only applies to Drupal 8+, in Drupal 7 we can have classes
// in all kinds of files.
if (Project::getCoreVersion($phpcsFile) < 8) {
return ($phpcsFile->numTokens + 1);
}
$fullPath = basename($phpcsFile->getFilename());
$fileName = substr($fullPath, 0, strrpos($fullPath, '.'));
if ($fileName === '') {
// No filename probably means STDIN, so we can't do this check.
return ($phpcsFile->numTokens + 1);
}
// If the file is not a php file, we do not care about how it looks,
// since we care about psr-4.
$extension = pathinfo($fullPath, PATHINFO_EXTENSION);
if ($extension !== 'php') {
return ($phpcsFile->numTokens + 1);
}
$tokens = $phpcsFile->getTokens();
$decName = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($tokens[$decName]['code'] === T_STRING
&& $tokens[$decName]['content'] !== $fileName
) {
$error = '%s name doesn\'t match filename; expected "%s %s"';
$data = [
ucfirst($tokens[$stackPtr]['content']),
$tokens[$stackPtr]['content'],
$fileName,
];
$phpcsFile->addError($error, $stackPtr, 'NoMatch', $data);
}
// Only check the first class in a file, we don't care about helper
// classes in tests for example.
return ($phpcsFile->numTokens + 1);
}//end process()
}//end class

View File

@@ -0,0 +1,212 @@
<?php
/**
* \Drupal\Sniffs\Classes\FullyQualifiedNamespaceSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Classes;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
/**
* Checks that class references do not use FQN but use statements.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class FullyQualifiedNamespaceSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [T_NS_SEPARATOR];
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
* token was found.
* @param int $stackPtr The position in the PHP_CodeSniffer
* file's token stack where the token
* was found.
*
* @return void|int Optionally returns a stack pointer. The sniff will not be
* called again on the current file until the returned stack
* pointer is reached. Return $phpcsFile->numTokens + 1 to skip
* the rest of the file.
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
// Skip this sniff in *api.php files because they want to have fully
// qualified names for documentation purposes.
if (substr($phpcsFile->getFilename(), -8) === '.api.php') {
return ($phpcsFile->numTokens + 1);
}
// We are only interested in a backslash embedded between strings, which
// means this is a class reference with more than once namespace part.
if ($tokens[($stackPtr - 1)]['code'] !== T_STRING || $tokens[($stackPtr + 1)]['code'] !== T_STRING) {
return;
}
// 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);
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) {
return ($after + 1);
}
$fullName = $phpcsFile->getTokensAsString(($before + 1), ($after - 1 - $before));
$fullName = trim($fullName, "\ \n");
$parts = explode('\\', $fullName);
$className = end($parts);
// Check if there is a use statement already for this class and
// namespace.
$conflict = false;
$alreadyUsed = false;
$aliasName = false;
$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);
$useFullName = trim($phpcsFile->getTokensAsString(($useStatement + 1), ($useEnd - $useStatement)));
// Check if use statement contains an alias.
$asPtr = $phpcsFile->findNext(T_AS, ($useEnd + 1), $endPtr);
if ($asPtr !== false) {
$aliasName = trim($phpcsFile->getTokensAsString(($asPtr + 1), ($endPtr - 1 - $asPtr)));
}
if (strcasecmp($useFullName, $fullName) === 0) {
$alreadyUsed = true;
break;
}
$parts = explode('\\', $useFullName);
$useClassName = end($parts);
// Check if the resulting classname would conflict with another
// use statement.
if ($aliasName === $className || $useClassName === $className) {
$conflict = true;
break;
}
$aliasName = false;
// Check if we're currently in a multi-use statement.
if ($tokens[$endPtr]['code'] === T_COMMA) {
$useStatement = $endPtr;
continue;
}
$useStatement = $phpcsFile->findNext(T_USE, ($endPtr + 1));
}//end while
if ($conflict === false) {
$classStatement = $phpcsFile->findNext(T_CLASS, 0);
while ($classStatement !== false) {
$afterClassStatement = $phpcsFile->findNext(T_WHITESPACE, ($classStatement + 1), null, true);
// Check for 'class ClassName' declarations.
if ($tokens[$afterClassStatement]['code'] === T_STRING) {
$declaredName = $tokens[$afterClassStatement]['content'];
if ($declaredName === $className) {
$conflict = true;
break;
}
}
$classStatement = $phpcsFile->findNext(T_CLASS, ($classStatement + 1));
}
}
$error = 'Namespaced classes/interfaces/traits should be referenced with use statements';
if ($conflict === true) {
$fix = false;
$phpcsFile->addError($error, $stackPtr, 'UseStatementMissing');
} else {
$fix = $phpcsFile->addFixableError($error, $stackPtr, 'UseStatementMissing');
}
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);
} else {
$phpcsFile->fixer->addContentBefore(($after - 1), $className);
}
// Insert use statement at the beginning of the file if it is not there
// already. Also check if another sniff (for example
// UnusedUseStatementSniff) has already deleted the use statement, then
// we need to add it back.
if ($alreadyUsed === false
|| $phpcsFile->fixer->getTokenContent($useStatement) !== $tokens[$useStatement]['content']
) {
if ($aliasName !== false) {
$use = "use $fullName as $aliasName;";
} else {
$use = "use $fullName;";
}
// Check if there is a group of use statements and add it there.
$useStatement = $phpcsFile->findNext(T_USE, 0);
if ($useStatement !== false && empty($tokens[$useStatement]['conditions']) === true) {
$phpcsFile->fixer->addContentBefore($useStatement, "$use\n");
} else {
// Check if there is an @file comment.
$beginning = 0;
$fileComment = $phpcsFile->findNext(T_WHITESPACE, ($beginning + 1), null, true);
if ($tokens[$fileComment]['code'] === T_DOC_COMMENT_OPEN_TAG) {
$beginning = $tokens[$fileComment]['comment_closer'];
$phpcsFile->fixer->addContent($beginning, "\n\n$use\n");
} else {
$phpcsFile->fixer->addContent($beginning, "$use\n");
}
}
}//end if
$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()
}//end class

View File

@@ -0,0 +1,60 @@
<?php
/**
* \Drupal\Sniffs\Classes\InterfaceNameSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Classes;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
/**
* Checks that interface names end with "Interface".
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class InterfaceNameSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [T_INTERFACE];
}//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();
$namePtr = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true);
$name = $tokens[$namePtr]['content'];
if (substr($name, -9) !== 'Interface') {
$warn = 'Interface names should always have the suffix "Interface"';
$phpcsFile->addWarning($warn, $namePtr, 'InterfaceSuffix');
}
}//end process()
}//end class

View File

@@ -0,0 +1,115 @@
<?php
/**
* Verifies that properties are declared correctly.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Classes;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\AbstractVariableSniff;
use PHP_CodeSniffer\Util\Tokens;
/**
* Largely copied from
* \PHP_CodeSniffer\Standards\PSR2\Sniffs\Classes\PropertyDeclarationSniff to have a fixer
* for the var keyword.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class PropertyDeclarationSniff extends AbstractVariableSniff
{
/**
* Processes the function tokens within the class.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
* @param int $stackPtr The position where the token was found.
*
* @return void
*/
protected function processMemberVar(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
if ($tokens[$stackPtr]['content'][1] === '_') {
$error = 'Property name "%s" should not be prefixed with an underscore to indicate visibility';
$data = [$tokens[$stackPtr]['content']];
$phpcsFile->addWarning($error, $stackPtr, 'Underscore', $data);
}
// Detect multiple properties defined at the same time. Throw an error
// for this, but also only process the first property in the list so we don't
// repeat errors.
$find = Tokens::$scopeModifiers;
$find = array_merge($find, [T_VARIABLE, T_VAR, T_SEMICOLON]);
$prev = $phpcsFile->findPrevious($find, ($stackPtr - 1));
if ($tokens[$prev]['code'] === T_VARIABLE) {
return;
}
if ($tokens[$prev]['code'] === T_VAR) {
$error = 'The var keyword must not be used to declare a property';
$fix = $phpcsFile->addFixableError($error, $stackPtr, 'VarUsed');
if ($fix === true) {
$phpcsFile->fixer->replaceToken($prev, 'public');
}
}
$next = $phpcsFile->findNext([T_VARIABLE, T_SEMICOLON], ($stackPtr + 1));
if ($tokens[$next]['code'] === T_VARIABLE) {
$error = 'There must not be more than one property declared per statement';
$phpcsFile->addError($error, $stackPtr, 'Multiple');
}
$modifier = $phpcsFile->findPrevious(Tokens::$scopeModifiers, $stackPtr);
if (($modifier === false) || ($tokens[$modifier]['line'] !== $tokens[$stackPtr]['line'])) {
$error = 'Visibility must be declared on property "%s"';
$data = [$tokens[$stackPtr]['content']];
$phpcsFile->addError($error, $stackPtr, 'ScopeMissing', $data);
}
}//end processMemberVar()
/**
* Processes normal variables.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
* @param int $stackPtr The position where the token was found.
*
* @return void
*/
protected function processVariable(File $phpcsFile, $stackPtr)
{
/*
We don't care about normal variables.
*/
}//end processVariable()
/**
* Processes variables in double quoted strings.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
* @param int $stackPtr The position where the token was found.
*
* @return void
*/
protected function processVariableInString(File $phpcsFile, $stackPtr)
{
/*
We don't care about normal variables.
*/
}//end processVariableInString()
}//end class

View File

@@ -0,0 +1,217 @@
<?php
/**
* \Drupal\Sniffs\Classes\UnusedUseStatementSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Classes;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;
/**
* Checks for "use" statements that are not needed in a file.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class UnusedUseStatementSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
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::$emptyTokens,
($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) {
// If the name is used in a PHP 7 function return type declaration
// stop.
if ($tokens[$classUsed]['code'] === T_RETURN_TYPE) {
return;
}
$beforeUsage = $phpcsFile->findPrevious(
Tokens::$emptyTokens,
($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, T_RETURN_TYPE], ($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

View File

@@ -0,0 +1,142 @@
<?php
/**
* \Drupal\Sniffs\Classes\UseGlobalClassSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Classes;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;
/**
* Checks non-namespaced classes are referenced by FQN, not imported.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class UseGlobalClassSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
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 PHP_CodeSniffer file where the
* token was found.
* @param int $stackPtr The position in the PHP_CodeSniffer
* file's token stack where the token
* was found.
*
* @return void|int Optionally returns a stack pointer. The sniff will not be
* called again on the current file until the returned stack
* pointer is reached. Return $phpcsFile->numTokens + 1 to skip
* the rest of the file.
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
// Make sure this is not a closure USE group.
$next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($tokens[$next]['code'] === T_OPEN_PARENTHESIS) {
return;
}
// Find the first declaration, marking the end of the use statements.
$bodyStart = $phpcsFile->findNext([T_CLASS, T_INTERFACE, T_TRAIT, T_ENUM, T_FUNCTION], 0);
// Ensure we are in the global scope, to exclude trait use statements.
if (empty($tokens[$stackPtr]['conditions']) === false) {
return;
}
// End of the full statement.
$stmtEnd = $phpcsFile->findNext(T_SEMICOLON, $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
) {
$lineStart = $lineEnd;
continue;
}
// The first string token is the class name.
$class = $phpcsFile->findNext(T_STRING, $lineStart, $lineEnd);
$className = $tokens[$class]['content'];
// 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'];
$error = 'Non-namespaced classes/interfaces/traits should not be referenced with use statements';
$fix = $phpcsFile->addFixableError($error, $class, 'RedundantUseStatement');
if ($fix === true) {
$phpcsFile->fixer->beginChangeset();
// Remove the entire line by default.
$start = $lineStart;
$end = $lineEnd;
$next = $phpcsFile->findNext(T_WHITESPACE, ($end + 1), null, true);
if ($tokens[$lineStart]['code'] === T_COMMA) {
// If there are lines before this one,
// then leave the ending delimiter in place.
$end = ($lineEnd - 1);
} else if ($tokens[$lineEnd]['code'] === T_COMMA) {
// If there are lines after, but not before,
// then leave the use keyword.
$start = $class;
} else if ($tokens[$next]['code'] === T_USE) {
// If the whole statement is removed, and there is one after it,
// then also remove the linebreaks.
$end = ($next - 1);
}
for ($i = $start; $i <= $end; $i++) {
$phpcsFile->fixer->replaceToken($i, '');
}
// Find all usages of the class, and add a leading backslash.
// 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->endChangeset();
}//end if
$lineStart = $lineEnd;
}//end while
}//end process()
}//end class

View File

@@ -0,0 +1,75 @@
<?php
/**
* \Drupal\Sniffs\Classes\UseLeadingBackslashSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Classes;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;
/**
* Use statements to import classes must not begin with "\".
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class UseLeadingBackslashSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
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;
}
$startPtr = $phpcsFile->findNext(
Tokens::$emptyTokens,
($stackPtr + 1),
null,
true
);
if ($startPtr !== false && $tokens[$startPtr]['code'] === T_NS_SEPARATOR) {
$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, '');
}
}
}//end process()
}//end class