diff --git a/README.rst b/README.rst index 8529e17bb69..9ee6854857a 100644 --- a/README.rst +++ b/README.rst @@ -1170,6 +1170,10 @@ Choose from the list of available rules: A final class must not have final methods. +* **no_unneeded_import_alias** [@PhpCsFixer] + + Remove unneeded alias in ``use`` clauses. + * **no_unreachable_default_argument_value** [@PhpCsFixer:risky] In function arguments there must not be arguments with default values diff --git a/src/Fixer/Import/NoUnneededImportAliasFixer.php b/src/Fixer/Import/NoUnneededImportAliasFixer.php new file mode 100644 index 00000000000..5815c3e261c --- /dev/null +++ b/src/Fixer/Import/NoUnneededImportAliasFixer.php @@ -0,0 +1,100 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Import; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; +use PhpCsFixer\Tokenizer\Tokens; + +final class NoUnneededImportAliasFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Remove unneeded alias in `use` clauses.', + [ + new CodeSample( + 'isAllTokenKindsFound([T_USE, T_AS]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens, false); + + foreach ($useDeclarations as $declaration) { + if (!$declaration->isAliased()) { + continue; + } + $shortNameStartPos = strrpos($declaration->getFullName(), '\\'); + $shortName = false === $shortNameStartPos ? $declaration->getFullName() : substr($declaration->getFullName(), $shortNameStartPos + 1); + + if ($declaration->getShortName() !== $shortName) { + continue; + } + + $this->removeAlias($tokens, $declaration); + } + } + + private function removeAlias(Tokens $tokens, NamespaceUseAnalysis $declaration) + { + $asIndex = $tokens->getNextTokenOfKind($declaration->getStartIndex(), [[T_AS]]); + if (null === $asIndex || $asIndex > $declaration->getEndIndex()) { + return; + } + + for ($i = $tokens->getPrevMeaningfulToken($asIndex) + 1; $i <= $declaration->getEndIndex() - 1; ++$i) { + if ($tokens[$i]->isWhitespace() && !($tokens[$i - 1]->isComment() || $tokens[$i + 1]->isComment() || $tokens[$i + 1]->isGivenKind([T_CLOSE_TAG]))) { + $tokens->clearAt($i); + } elseif ($tokens[$i]->isGivenKind([T_AS, T_STRING])) { + $tokens->clearAt($i); + } + } + } +} diff --git a/src/RuleSet.php b/src/RuleSet.php index 580cc75a45d..74b2cd281bb 100644 --- a/src/RuleSet.php +++ b/src/RuleSet.php @@ -238,6 +238,7 @@ final class RuleSet implements RuleSetInterface 'no_superfluous_elseif' => true, 'no_unneeded_curly_braces' => true, 'no_unneeded_final_method' => true, + 'no_unneeded_import_alias' => true, 'no_unset_cast' => true, 'no_useless_else' => true, 'no_useless_return' => true, diff --git a/src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.php b/src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.php index 7b3f4412bfa..960f4a54aaa 100644 --- a/src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.php +++ b/src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.php @@ -24,32 +24,34 @@ final class NamespaceUsesAnalyzer { /** * @param Tokens $tokens + * @param bool $skipMultipleUseStatement * * @return NamespaceUseAnalysis[] */ - public function getDeclarationsFromTokens(Tokens $tokens) + public function getDeclarationsFromTokens(Tokens $tokens, $skipMultipleUseStatement = true) { $tokenAnalyzer = new TokensAnalyzer($tokens); $useIndexes = $tokenAnalyzer->getImportUseIndexes(); - return $this->getDeclarations($tokens, $useIndexes); + return $this->getDeclarations($tokens, $useIndexes, $skipMultipleUseStatement); } /** * @param Tokens $tokens * @param array $useIndexes + * @param bool $skipMultipleUseStatement * * @return NamespaceUseAnalysis[] */ - private function getDeclarations(Tokens $tokens, array $useIndexes) + private function getDeclarations(Tokens $tokens, array $useIndexes, $skipMultipleUseStatement = true) { $uses = []; foreach ($useIndexes as $index) { $endIndex = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]); - $analysis = $this->parseDeclaration($tokens, $index, $endIndex); - if ($analysis) { - $uses[] = $analysis; + $analyses = $this->parseDeclaration($tokens, $index, $endIndex, $skipMultipleUseStatement); + if ($analyses) { + $uses = array_merge($uses, $analyses); } } @@ -60,20 +62,36 @@ private function getDeclarations(Tokens $tokens, array $useIndexes) * @param Tokens $tokens * @param int $startIndex * @param int $endIndex + * @param bool $skipMultipleUseStatement * - * @return null|NamespaceUseAnalysis + * @return null|NamespaceUseAnalysis[] */ - private function parseDeclaration(Tokens $tokens, $startIndex, $endIndex) + private function parseDeclaration(Tokens $tokens, $startIndex, $endIndex, $skipMultipleUseStatement = true) { + $namespaceUseAnalyses = []; $fullName = $shortName = ''; $aliased = false; $type = NamespaceUseAnalysis::TYPE_CLASS; for ($i = $startIndex; $i <= $endIndex; ++$i) { $token = $tokens[$i]; - if ($token->equals(',') || $token->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE)) { + if ($token->equals(',')) { + if ($skipMultipleUseStatement) { + return null; + } + $namespaceUseAnalyses[] = new NamespaceUseAnalysis( + trim($fullName), + $shortName, + $aliased, + $startIndex, + $i, + $type + ); + $fullName = $shortName = ''; + $aliased = false; + $startIndex = $i + 1; + } elseif ($token->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE)) { // do not touch group use declarations until the logic of this is added (for example: `use some\a\{ClassD};`) - // ignore multiple use statements that should be split into few separate statements (for example: `use BarB, BarC as C;`) return null; } @@ -99,7 +117,7 @@ private function parseDeclaration(Tokens $tokens, $startIndex, $endIndex) } } - return new NamespaceUseAnalysis( + $namespaceUseAnalyses[] = new NamespaceUseAnalysis( trim($fullName), $shortName, $aliased, @@ -107,5 +125,7 @@ private function parseDeclaration(Tokens $tokens, $startIndex, $endIndex) $endIndex, $type ); + + return $namespaceUseAnalyses; } } diff --git a/tests/Fixer/Import/NoUnneededImportAliasFixerTest.php b/tests/Fixer/Import/NoUnneededImportAliasFixerTest.php new file mode 100644 index 00000000000..88e185b3ef1 --- /dev/null +++ b/tests/Fixer/Import/NoUnneededImportAliasFixerTest.php @@ -0,0 +1,201 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tests\Fixer\Import; + +use PhpCsFixer\Tests\Test\AbstractFixerTestCase; + +/** + * @internal + * + * @covers \PhpCsFixer\Fixer\Import\NoUnneededImportAliasFixer + */ +final class NoUnneededImportAliasFixerTest extends AbstractFixerTestCase +{ + /** + * @param string $expected + * @param null|string $input + * + * @dataProvider provideFixCases + */ + public function testFix($expected, $input = null) + { + $this->doTest($expected, $input); + } + + public function provideFixCases() + { + return [ + 'simple' => [ + <<<'EOF' + [ + <<<'EOF' + [ + <<<'EOF' + [ + <<<'EOF' + [ + <<<'EOF' + [ + <<<'EOF' + [ + <<<'EOF' + [ + 'inline content', + 'inline content', + ], + 'close_tag_2' => [ + 'inline content', + 'inline content', + ], + 'close_tag_3' => [ + '', + '', + ], + 'close_tag_4' => [ + '', + '', + ], + 'close_tag_5' => [ + '', + '', + ], + 'close_tag_6' => [ + '', + '', + ], + ]; + } +} diff --git a/tests/Tokenizer/Analyzer/NamespaceUsesAnalyzerTest.php b/tests/Tokenizer/Analyzer/NamespaceUsesAnalyzerTest.php index 31717564c88..95d91ff6e8f 100644 --- a/tests/Tokenizer/Analyzer/NamespaceUsesAnalyzerTest.php +++ b/tests/Tokenizer/Analyzer/NamespaceUsesAnalyzerTest.php @@ -29,21 +29,22 @@ final class NamespaceUsesAnalyzerTest extends TestCase /** * @param string $code * @param array $expected + * @param bool $skipMultipleUseStatement * * @dataProvider provideNamespaceUsesCases */ - public function testUsesFromTokens($code, $expected) + public function testUsesFromTokens($code, $expected, $skipMultipleUseStatement = true) { $tokens = Tokens::fromCode($code); $analyzer = new NamespaceUsesAnalyzer(); - static::assertSame(serialize($expected), serialize($analyzer->getDeclarationsFromTokens($tokens))); + static::assertSame(serialize($expected), serialize($analyzer->getDeclarationsFromTokens($tokens, $skipMultipleUseStatement))); } public function provideNamespaceUsesCases() { return [ - ['