coder-8.x-3.x-dev/coder_sniffer/Drupal/Sniffs/Classes/FullyQualifiedNamespaceSniff.php
coder_sniffer/Drupal/Sniffs/Classes/FullyQualifiedNamespaceSniff.php
<?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;
use PHP_CodeSniffer\Util\Tokens;
/**
* 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_NAME_FULLY_QUALIFIED,
T_NAME_QUALIFIED,
];
}
/**
* 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 one namespace part.
if (substr_count($tokens[$stackPtr]['content'], '\\') === 1
&& strpos($tokens[$stackPtr]['content'], '\\') === 0
) {
return;
}
// Skip names in PHP attributes, no standards defined yet.
if (isset($tokens[$stackPtr]['attribute_closer']) === true) {
return $tokens[$stackPtr]['attribute_closer'];
}
// Check if this is a use statement and ignore those.
$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;
}
$fullName = trim($tokens[$stackPtr]['content'], '\\ ');
$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((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.
$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();
// Use alias name if available.
if ($aliasName !== false) {
$phpcsFile->fixer->replaceToken($stackPtr, $aliasName);
} else {
$phpcsFile->fixer->replaceToken($stackPtr, $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 a namespace declaration and add it there.
$namespace = $phpcsFile->findNext(T_NAMESPACE, 0);
if ($namespace !== false) {
$beginning = $phpcsFile->findEndOfStatement($namespace);
$phpcsFile->fixer->addContent($beginning, "\n\n$use\n");
} else {
// Check if there is an @file comment.
$fileComment = $phpcsFile->findNext(T_WHITESPACE, 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(0, "\n\n$use\n");
}
}
}
}//end if
$phpcsFile->fixer->endChangeset();
}//end if
}
}
