-
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #244 from PHPCSStandards/feature/new-no-useless-al…
…iases-sniff ✨ New `Universal.UseStatements.NoUselessAliases` sniff
- Loading branch information
Showing
5 changed files
with
423 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<?xml version="1.0"?> | ||
<documentation xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:noNamespaceSchemaLocation="https://phpcsstandards.github.io/PHPCSDevTools/phpcsdocs.xsd" | ||
title="No Useless Aliases" | ||
> | ||
<standard> | ||
<![CDATA[ | ||
Detects useless aliases for import use statements. | ||
Aliasing something to the same name as the original construct is considered useless. | ||
Note: as OO and function names in PHP are case-insensitive, aliasing to the same name, using a different case is also considered useless. | ||
]]> | ||
</standard> | ||
<code_comparison> | ||
<code title="Valid: Import use statement with an alias to a different name."> | ||
<![CDATA[ | ||
use Vendor\Package\ClassName as AnotherName; | ||
use function functionName as my_function; | ||
use const SOME_CONSTANT as MY_CONSTANT; | ||
]]> | ||
</code> | ||
<code title="Invalid: Import use statement with an alias to the same name."> | ||
<![CDATA[ | ||
use Vendor\Package\ClassName as ClassName; | ||
use function functionName as FunctionName; | ||
use const SOME_CONSTANT as SOME_CONSTANT; | ||
]]> | ||
</code> | ||
</code_comparison> | ||
</documentation> |
164 changes: 164 additions & 0 deletions
164
Universal/Sniffs/UseStatements/NoUselessAliasesSniff.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
<?php | ||
/** | ||
* PHPCSExtra, a collection of sniffs and standards for use with PHP_CodeSniffer. | ||
* | ||
* @package PHPCSExtra | ||
* @copyright 2023 PHPCSExtra Contributors | ||
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3 | ||
* @link https://github.com/PHPCSStandards/PHPCSExtra | ||
*/ | ||
|
||
namespace PHPCSExtra\Universal\Sniffs\UseStatements; | ||
|
||
use PHP_CodeSniffer\Sniffs\Sniff; | ||
use PHP_CodeSniffer\Files\File; | ||
use PHP_CodeSniffer\Util\Tokens; | ||
use PHPCSUtils\Utils\NamingConventions; | ||
use PHPCSUtils\Utils\UseStatements; | ||
|
||
/** | ||
* Detects useless aliases for import use statements. | ||
* | ||
* Aliasing something to the same name as the original construct is considered useless. | ||
* Note: as OO and function names in PHP are case-insensitive, aliasing to the same name, | ||
* using a different case is also considered useless. | ||
* | ||
* @since 1.1.0 | ||
*/ | ||
final class NoUselessAliasesSniff implements Sniff | ||
{ | ||
|
||
/** | ||
* Name of the "Use import source" metric. | ||
* | ||
* @since 1.1.0 | ||
* | ||
* @var string | ||
*/ | ||
const METRIC_NAME = 'Import use statement type'; | ||
|
||
/** | ||
* Returns an array of tokens this test wants to listen for. | ||
* | ||
* @since 1.1.0 | ||
* | ||
* @return array | ||
*/ | ||
public function register() | ||
{ | ||
return [\T_USE]; | ||
} | ||
|
||
/** | ||
* Processes this test, when one of its tokens is encountered. | ||
* | ||
* @since 1.1.0 | ||
* | ||
* @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) | ||
{ | ||
if (UseStatements::isImportUse($phpcsFile, $stackPtr) === false) { | ||
// Closure or trait use statement. Bow out. | ||
return; | ||
} | ||
|
||
$endOfStatement = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG], ($stackPtr + 1)); | ||
if ($endOfStatement === false) { | ||
// Parse error or live coding. | ||
return; | ||
} | ||
|
||
$hasAliases = $phpcsFile->findNext(\T_AS, ($stackPtr + 1), $endOfStatement); | ||
if ($hasAliases === false) { | ||
// This use import statement does not alias anything, bow out. | ||
return; | ||
} | ||
|
||
$useStatements = UseStatements::splitImportUseStatement($phpcsFile, $stackPtr); | ||
if (\count($useStatements, \COUNT_RECURSIVE) <= 3) { | ||
// No statements found. Shouldn't be possible, but still. Bow out. | ||
return; | ||
} | ||
|
||
$tokens = $phpcsFile->getTokens(); | ||
|
||
// Collect all places where aliases are used in this use statement. | ||
$aliasPtrs = []; | ||
$currentAs = $hasAliases; | ||
do { | ||
$aliasPtr = $phpcsFile->findNext(Tokens::$emptyTokens, ($currentAs + 1), null, true); | ||
if ($aliasPtr !== false && $tokens[$aliasPtr]['code'] === \T_STRING) { | ||
$aliasPtrs[$currentAs] = $aliasPtr; | ||
} | ||
|
||
$currentAs = $phpcsFile->findNext(\T_AS, ($currentAs + 1), $endOfStatement); | ||
} while ($currentAs !== false); | ||
|
||
// Now check the names in each use statement for useless aliases. | ||
foreach ($useStatements as $type => $statements) { | ||
foreach ($statements as $alias => $fqName) { | ||
$unqualifiedName = \ltrim(\substr($fqName, \strrpos($fqName, '\\')), '\\'); | ||
|
||
$uselessAlias = false; | ||
if ($type === 'const') { | ||
// Do a case-sensitive comparison for constants. | ||
if ($unqualifiedName === $alias) { | ||
$uselessAlias = true; | ||
} | ||
} elseif (NamingConventions::isEqual($unqualifiedName, $alias)) { | ||
$uselessAlias = true; | ||
} | ||
|
||
if ($uselessAlias === false) { | ||
continue; | ||
} | ||
|
||
// Now check if this is actually used as an alias or just the actual name. | ||
foreach ($aliasPtrs as $asPtr => $aliasPtr) { | ||
if ($tokens[$aliasPtr]['content'] !== $alias) { | ||
continue; | ||
} | ||
|
||
// Make sure this is really the right one. | ||
$prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($asPtr - 1), null, true); | ||
if ($tokens[$prev]['code'] !== \T_STRING | ||
|| $tokens[$prev]['content'] !== $unqualifiedName | ||
) { | ||
continue; | ||
} | ||
|
||
$error = 'Useless alias "%s" found for import of "%s"'; | ||
$code = 'Found'; | ||
$data = [$alias, $fqName]; | ||
|
||
// Okay, so this is the one which should be flagged. | ||
$hasComments = $phpcsFile->findNext(Tokens::$commentTokens, ($prev + 1), $aliasPtr); | ||
if ($hasComments !== false) { | ||
// Don't auto-fix if there are comments. | ||
$phpcsFile->addError($error, $aliasPtr, $code, $data); | ||
break; | ||
} | ||
|
||
$fix = $phpcsFile->addFixableError($error, $aliasPtr, $code, $data); | ||
|
||
if ($fix === true) { | ||
$phpcsFile->fixer->beginChangeset(); | ||
|
||
for ($i = ($prev + 1); $i <= $aliasPtr; $i++) { | ||
$phpcsFile->fixer->replaceToken($i, ''); | ||
} | ||
|
||
$phpcsFile->fixer->endChangeset(); | ||
} | ||
|
||
break; | ||
} | ||
} | ||
} | ||
} | ||
} |
84 changes: 84 additions & 0 deletions
84
Universal/Tests/UseStatements/NoUselessAliasesUnitTest.inc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
<?php | ||
|
||
// Ignore as not import use. | ||
$closure = function () use ($bar) { | ||
return $bar; | ||
}; | ||
|
||
class Foo { | ||
use MyNamespace\Bar; | ||
} | ||
|
||
// Ignore, no aliases. | ||
use MyNamespace\MyClass; | ||
use function MyNamespace\MyFunction; | ||
use const MyNamespace\MyConst; | ||
|
||
// Ignore, aliased to different name. | ||
use MyNamespace\MyClass as YourClass; | ||
use function MyNamespace\MyFunction as YourFunction; | ||
use const MyNamespace\MyConst as YourConst; | ||
|
||
// Ignore, constant aliased to same name, but different case. | ||
use const MyNamespace\MyConst as MYCONST; | ||
|
||
// These should be flagged. | ||
use MyNamespace\NotAutoFixable /*comment*/ as NotAutoFixable; | ||
use MyNamespace\NotAutoFixableEither | ||
as | ||
// phpcs:ignore Stnd.Cat.Sniff -- for reasons. | ||
notAutofixableEither; | ||
|
||
use MyNamespace\MyClass as MyClass; | ||
use MyNamespace\MyClass as MYCLASS; | ||
use MyNamespace\MyClass as myclass; | ||
|
||
use function MyNamespace\MyFunction as MyFunction; | ||
use function MyNamespace\MyFunction | ||
as | ||
myfunction; | ||
|
||
use const MyNamespace\MyConst as MyConst; | ||
|
||
// Verify that the error is thrown on the correct token/line for multi and group use statements. | ||
use function foo\math\sin, | ||
foo\math\cos as Cos, // Error. | ||
foo\math\cosh; | ||
|
||
use some\namespacing\{ | ||
SomeClassA as SomeOtherClass, // OK. | ||
deeper\level\SomeClassB, | ||
another\level\SomeClassC as SomeClassC // Error. | ||
}; | ||
|
||
use const foo\math\PI, | ||
// Comment. | ||
foo\math\GOLDEN_RATIO as GOLDEN_RATIO; | ||
|
||
use Some\NS\ { | ||
ClassName as className, // Error. | ||
function SubLevel\functionName as FunctionName, // Error. | ||
const Constants\MYCONSTANT as MYCONSTANT, // Error. | ||
const Constants\CONSTANT_NAME as Constant_Name, // OK. | ||
}; | ||
|
||
// Verify handling of non-ascii names. | ||
use Vendor\Package\Déjàvü as Dejavu; // OK. | ||
use Vendor\Package\Déjàvü as DÉJÀVÜ; // Ok. | ||
use Vendor\Package\Déjàvü as Déjàvü; // Error. | ||
use Vendor\Package\Déjàvü as déJàVü; // Error. | ||
|
||
use function 💩💩 as 💩; // OK. | ||
use function 💩💩 as 💩💩; // Error. | ||
|
||
// Verify handing with (illegal) duplicate aliases. | ||
use function foo\math\sin as Cos, // OK. | ||
foo\math\cos as Cos; // Error. | ||
|
||
// Intentional parse error. | ||
use function as ; | ||
|
||
// Intentional parse error. | ||
// This has to be the last test in the file. | ||
use MyNS\Level\{ | ||
Something, |
82 changes: 82 additions & 0 deletions
82
Universal/Tests/UseStatements/NoUselessAliasesUnitTest.inc.fixed
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
<?php | ||
|
||
// Ignore as not import use. | ||
$closure = function () use ($bar) { | ||
return $bar; | ||
}; | ||
|
||
class Foo { | ||
use MyNamespace\Bar; | ||
} | ||
|
||
// Ignore, no aliases. | ||
use MyNamespace\MyClass; | ||
use function MyNamespace\MyFunction; | ||
use const MyNamespace\MyConst; | ||
|
||
// Ignore, aliased to different name. | ||
use MyNamespace\MyClass as YourClass; | ||
use function MyNamespace\MyFunction as YourFunction; | ||
use const MyNamespace\MyConst as YourConst; | ||
|
||
// Ignore, constant aliased to same name, but different case. | ||
use const MyNamespace\MyConst as MYCONST; | ||
|
||
// These should be flagged. | ||
use MyNamespace\NotAutoFixable /*comment*/ as NotAutoFixable; | ||
use MyNamespace\NotAutoFixableEither | ||
as | ||
// phpcs:ignore Stnd.Cat.Sniff -- for reasons. | ||
notAutofixableEither; | ||
|
||
use MyNamespace\MyClass; | ||
use MyNamespace\MyClass; | ||
use MyNamespace\MyClass; | ||
|
||
use function MyNamespace\MyFunction; | ||
use function MyNamespace\MyFunction; | ||
|
||
use const MyNamespace\MyConst; | ||
|
||
// Verify that the error is thrown on the correct token/line for multi and group use statements. | ||
use function foo\math\sin, | ||
foo\math\cos, // Error. | ||
foo\math\cosh; | ||
|
||
use some\namespacing\{ | ||
SomeClassA as SomeOtherClass, // OK. | ||
deeper\level\SomeClassB, | ||
another\level\SomeClassC // Error. | ||
}; | ||
|
||
use const foo\math\PI, | ||
// Comment. | ||
foo\math\GOLDEN_RATIO; | ||
|
||
use Some\NS\ { | ||
ClassName, // Error. | ||
function SubLevel\functionName, // Error. | ||
const Constants\MYCONSTANT, // Error. | ||
const Constants\CONSTANT_NAME as Constant_Name, // OK. | ||
}; | ||
|
||
// Verify handling of non-ascii names. | ||
use Vendor\Package\Déjàvü as Dejavu; // OK. | ||
use Vendor\Package\Déjàvü as DÉJÀVÜ; // Ok. | ||
use Vendor\Package\Déjàvü; // Error. | ||
use Vendor\Package\Déjàvü; // Error. | ||
|
||
use function 💩💩 as 💩; // OK. | ||
use function 💩💩; // Error. | ||
|
||
// Verify handing with (illegal) duplicate aliases. | ||
use function foo\math\sin as Cos, // OK. | ||
foo\math\cos; // Error. | ||
|
||
// Intentional parse error. | ||
use function as ; | ||
|
||
// Intentional parse error. | ||
// This has to be the last test in the file. | ||
use MyNS\Level\{ | ||
Something, |
Oops, something went wrong.