Skip to content

Commit

Permalink
Merge pull request #25 from PHPCSStandards/feature/disallow-use-sniff…
Browse files Browse the repository at this point in the history
…s-split-error-codes

DisallowUseClass/Function/Const: make the error codes more modular.
  • Loading branch information
jrfnl committed Feb 17, 2020
2 parents 7d98100 + 800701e commit 858f78a
Show file tree
Hide file tree
Showing 18 changed files with 957 additions and 80 deletions.
132 changes: 118 additions & 14 deletions Universal/Sniffs/UseStatements/DisallowUseClassSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
use PHPCSUtils\Utils\Namespaces;
use PHPCSUtils\Utils\UseStatements;

/**
Expand All @@ -28,6 +29,42 @@
class DisallowUseClassSniff implements Sniff
{

/**
* Name of the "Use import source" metric.
*
* @since 1.0.0
*
* @var string
*/
const METRIC_NAME_SRC = 'Use import statement source for class/interface/trait';

/**
* Name of the "Use import with/without alias" metric.
*
* @since 1.0.0
*
* @var string
*/
const METRIC_NAME_ALIAS = 'Use import statement for class/interface/trait';

/**
* Keep track of which file is being scanned.
*
* @since 1.0.0
*
* @var string
*/
private $currentFile = '';

/**
* Keep track of the current namespace.
*
* @since 1.0.0
*
* @var string
*/
private $currentNamespace = '';

/**
* Returns an array of tokens this test wants to listen for.
*
Expand All @@ -37,7 +74,10 @@ class DisallowUseClassSniff implements Sniff
*/
public function register()
{
return [\T_USE];
return [
\T_USE,
\T_NAMESPACE,
];
}

/**
Expand All @@ -53,6 +93,26 @@ public function register()
*/
public function process(File $phpcsFile, $stackPtr)
{
$file = $phpcsFile->getFilename();
if ($file !== $this->currentFile) {
// Reset the current namespace for each new file.
$this->currentFile = $file;
$this->currentNamespace = '';
}

$tokens = $phpcsFile->getTokens();

// Get the name of the current namespace.
if ($tokens[$stackPtr]['code'] === \T_NAMESPACE) {
$namespaceName = Namespaces::getDeclaredName($phpcsFile, $stackPtr);
if ($namespaceName !== false) {
$this->currentNamespace = $namespaceName;
}

return;
}

// Ok, so this is a T_USE token.
try {
$statements = UseStatements::splitImportUseStatement($phpcsFile, $stackPtr);
} catch (RuntimeException $e) {
Expand All @@ -65,7 +125,6 @@ public function process(File $phpcsFile, $stackPtr)
return;
}

$tokens = $phpcsFile->getTokens();
$endOfStatement = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG], ($stackPtr + 1));

foreach ($statements['name'] as $alias => $fullName) {
Expand All @@ -79,29 +138,74 @@ public function process(File $phpcsFile, $stackPtr)

$next = $phpcsFile->findNext(Tokens::$emptyTokens, ($reportPtr + 1), $endOfStatement, true);
if ($next !== false && $tokens[$next]['code'] === \T_NS_SEPARATOR) {
// Namespace level with same name. Continue searching
// Namespace level with same name. Continue searching.
continue;
}

break;
} while (true);

$error = 'Use import statements for classes/traits/interfaces are not allowed.';
$error .= ' Found import statement for: "%s"';
$data = [$fullName, $alias];
/*
* Build the error message and code.
*
* Check whether this is a non-namespaced (global) import and check whether this is an
* import from within the same namespace.
*
* Takes incorrect use statements with leading backslash into account.
* Takes case-INsensitivity of namespaces names into account.
*
* The "GlobalNamespace" error code takes precedence over the "SameNamespace" error code
* in case this is a non-namespaced file.
*/

$error = 'Use import statements for class/interface/trait%s are not allowed.';
$error .= ' Found import statement for: "%s"';
$errorCode = 'Found';
$data = [
'',
$fullName,
];

$globalNamespace = false;
$sameNamespace = false;
if (\strpos($fullName, '\\', 1) === false) {
$globalNamespace = true;
$errorCode = 'FromGlobalNamespace';
$data[0] = ' from the global namespace';

$offsetFromEnd = (\strlen($alias) + 1);
if (\substr($fullName, -$offsetFromEnd) === '\\' . $alias) {
$phpcsFile->recordMetric($reportPtr, 'Use import statement for class/interface/trait', 'without alias');
$phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_SRC, 'global namespace');
} elseif ($this->currentNamespace !== ''
&& (\stripos($fullName, $this->currentNamespace . '\\') === 0
|| \stripos($fullName, '\\' . $this->currentNamespace . '\\') === 0)
) {
$sameNamespace = true;
$errorCode = 'FromSameNamespace';
$data[0] = ' from the same namespace';

$phpcsFile->addError($error, $reportPtr, 'FoundWithoutAlias', $data);
continue;
$phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_SRC, 'same namespace');
} else {
$phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_SRC, 'different namespace');
}

$phpcsFile->recordMetric($reportPtr, 'Use import statement for class/interface/trait', 'with alias');
$hasAlias = false;
$lastLeaf = \strtolower(\substr($fullName, -(\strlen($alias) + 1)));
$aliasLC = \strtolower($alias);
if ($lastLeaf !== $aliasLC && $lastLeaf !== '\\' . $aliasLC) {
$hasAlias = true;
$error .= ' with alias: "%s"';
$errorCode .= 'WithAlias';
$data[] = $alias;

$phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_ALIAS, 'with alias');
} else {
$phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_ALIAS, 'without alias');
}

if ($errorCode === 'Found') {
$errorCode = 'FoundWithoutAlias';
}

$error .= ' with alias: "%s"';
$phpcsFile->addError($error, $reportPtr, 'FoundWithAlias', $data);
$phpcsFile->addError($error, $reportPtr, $errorCode, $data);
}
}
}
132 changes: 118 additions & 14 deletions Universal/Sniffs/UseStatements/DisallowUseConstSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
use PHPCSUtils\Utils\Namespaces;
use PHPCSUtils\Utils\UseStatements;

/**
Expand All @@ -28,6 +29,42 @@
class DisallowUseConstSniff implements Sniff
{

/**
* Name of the "Use import source" metric.
*
* @since 1.0.0
*
* @var string
*/
const METRIC_NAME_SRC = 'Use import statement source for constant';

/**
* Name of the "Use import with/without alias" metric.
*
* @since 1.0.0
*
* @var string
*/
const METRIC_NAME_ALIAS = 'Use import statement for constant';

/**
* Keep track of which file is being scanned.
*
* @since 1.0.0
*
* @var string
*/
private $currentFile = '';

/**
* Keep track of the current namespace.
*
* @since 1.0.0
*
* @var string
*/
private $currentNamespace = '';

/**
* Returns an array of tokens this test wants to listen for.
*
Expand All @@ -37,7 +74,10 @@ class DisallowUseConstSniff implements Sniff
*/
public function register()
{
return [\T_USE];
return [
\T_USE,
\T_NAMESPACE,
];
}

/**
Expand All @@ -53,6 +93,26 @@ public function register()
*/
public function process(File $phpcsFile, $stackPtr)
{
$file = $phpcsFile->getFilename();
if ($file !== $this->currentFile) {
// Reset the current namespace for each new file.
$this->currentFile = $file;
$this->currentNamespace = '';
}

$tokens = $phpcsFile->getTokens();

// Get the name of the current namespace.
if ($tokens[$stackPtr]['code'] === \T_NAMESPACE) {
$namespaceName = Namespaces::getDeclaredName($phpcsFile, $stackPtr);
if ($namespaceName !== false) {
$this->currentNamespace = $namespaceName;
}

return;
}

// Ok, so this is a T_USE token.
try {
$statements = UseStatements::splitImportUseStatement($phpcsFile, $stackPtr);
} catch (RuntimeException $e) {
Expand All @@ -65,7 +125,6 @@ public function process(File $phpcsFile, $stackPtr)
return;
}

$tokens = $phpcsFile->getTokens();
$endOfStatement = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG], ($stackPtr + 1));

foreach ($statements['const'] as $alias => $fullName) {
Expand All @@ -79,29 +138,74 @@ public function process(File $phpcsFile, $stackPtr)

$next = $phpcsFile->findNext(Tokens::$emptyTokens, ($reportPtr + 1), $endOfStatement, true);
if ($next !== false && $tokens[$next]['code'] === \T_NS_SEPARATOR) {
// Namespace level with same name. Continue searching
// Namespace level with same name. Continue searching.
continue;
}

break;
} while (true);

$error = 'Use import statements for constants are not allowed.';
$error .= ' Found import statement for: "%s"';
$data = [$fullName, $alias];
/*
* Build the error message and code.
*
* Check whether this is a non-namespaced (global) import and check whether this is an
* import from within the same namespace.
*
* Takes incorrect use statements with leading backslash into account.
* Takes case-INsensitivity of namespaces names into account.
*
* The "GlobalNamespace" error code takes precedence over the "SameNamespace" error code
* in case this is a non-namespaced file.
*/

$error = 'Use import statements for constants%s are not allowed.';
$error .= ' Found import statement for: "%s"';
$errorCode = 'Found';
$data = [
'',
$fullName,
];

$globalNamespace = false;
$sameNamespace = false;
if (\strpos($fullName, '\\', 1) === false) {
$globalNamespace = true;
$errorCode = 'FromGlobalNamespace';
$data[0] = ' from the global namespace';

$offsetFromEnd = (\strlen($alias) + 1);
if (\substr($fullName, -$offsetFromEnd) === '\\' . $alias) {
$phpcsFile->recordMetric($reportPtr, 'Use import statement for constant', 'without alias');
$phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_SRC, 'global namespace');
} elseif ($this->currentNamespace !== ''
&& (\stripos($fullName, $this->currentNamespace . '\\') === 0
|| \stripos($fullName, '\\' . $this->currentNamespace . '\\') === 0)
) {
$sameNamespace = true;
$errorCode = 'FromSameNamespace';
$data[0] = ' from the same namespace';

$phpcsFile->addError($error, $reportPtr, 'FoundWithoutAlias', $data);
continue;
$phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_SRC, 'same namespace');
} else {
$phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_SRC, 'different namespace');
}

$phpcsFile->recordMetric($reportPtr, 'Use import statement for constant', 'with alias');
$hasAlias = false;
$lastLeaf = \strtolower(\substr($fullName, -(\strlen($alias) + 1)));
$aliasLC = \strtolower($alias);
if ($lastLeaf !== $aliasLC && $lastLeaf !== '\\' . $aliasLC) {
$hasAlias = true;
$error .= ' with alias: "%s"';
$errorCode .= 'WithAlias';
$data[] = $alias;

$phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_ALIAS, 'with alias');
} else {
$phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_ALIAS, 'without alias');
}

if ($errorCode === 'Found') {
$errorCode = 'FoundWithoutAlias';
}

$error .= ' with alias: "%s"';
$phpcsFile->addError($error, $reportPtr, 'FoundWithAlias', $data);
$phpcsFile->addError($error, $reportPtr, $errorCode, $data);
}
}
}
Loading

0 comments on commit 858f78a

Please sign in to comment.