Skip to content

Commit d516355

Browse files
Merge pull request #31 from VincentLanglet/staging
Staging
2 parents 31b207c + 659ebcf commit d516355

File tree

5 files changed

+301
-0
lines changed

5 files changed

+301
-0
lines changed
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
<?php
2+
3+
namespace Symfony3Custom\Sniffs\Namespaces;
4+
5+
use PHP_CodeSniffer\Files\File;
6+
use PHP_CodeSniffer\Sniffs\Sniff;
7+
use PHP_CodeSniffer\Util\Tokens;
8+
9+
/**
10+
* Checks for "use" statements that are not needed in a file.
11+
*/
12+
class UnusedUseSniff implements Sniff
13+
{
14+
/**
15+
* Returns an array of tokens this test wants to listen for.
16+
*
17+
* @return array
18+
*/
19+
public function register()
20+
{
21+
return array(T_USE);
22+
}
23+
24+
/**
25+
* Processes this test, when one of its tokens is encountered.
26+
*
27+
* @param File $phpcsFile The file being scanned.
28+
* @param int $stackPtr The position of the current token in the stack passed in $tokens.
29+
*
30+
* @return void
31+
*/
32+
public function process(File $phpcsFile, $stackPtr)
33+
{
34+
$tokens = $phpcsFile->getTokens();
35+
36+
// Only check use statements in the global scope.
37+
if (empty($tokens[$stackPtr]['conditions']) === false) {
38+
return;
39+
}
40+
41+
// Seek to the end of the statement and get the string before the semi colon.
42+
$semiColon = $phpcsFile->findEndOfStatement($stackPtr);
43+
if (T_SEMICOLON !== $tokens[$semiColon]['code']) {
44+
return;
45+
}
46+
47+
$classPtr = $phpcsFile->findPrevious(
48+
Tokens::$emptyTokens,
49+
($semiColon - 1),
50+
null,
51+
true
52+
);
53+
54+
if (T_STRING !== $tokens[$classPtr]['code']) {
55+
return;
56+
}
57+
58+
// Search where the class name is used. PHP treats class names case insensitive,
59+
// that's why we cannot search for the exact class name string
60+
// and need to iterate over all T_STRING tokens in the file.
61+
$classUsed = $phpcsFile->findNext(T_STRING, ($classPtr + 1));
62+
$lowerClassName = strtolower($tokens[$classPtr]['content']);
63+
64+
// Check if the referenced class is in the same namespace as the current file.
65+
// If it is then the use statement is not necessary.
66+
$namespacePtr = $phpcsFile->findPrevious([T_NAMESPACE], $stackPtr);
67+
68+
// Check if the use statement does aliasing with the "as" keyword.
69+
// Aliasing is allowed even in the same namespace.
70+
$aliasUsed = $phpcsFile->findPrevious(T_AS, ($classPtr - 1), $stackPtr);
71+
72+
if (false !== $namespacePtr && false === $aliasUsed) {
73+
$nsEnd = $phpcsFile->findNext(
74+
[T_NS_SEPARATOR, T_STRING, T_WHITESPACE],
75+
($namespacePtr + 1),
76+
null,
77+
true
78+
);
79+
80+
$namespace = trim($phpcsFile->getTokensAsString(($namespacePtr + 1), ($nsEnd - $namespacePtr - 1)));
81+
82+
$useNamespacePtr = $phpcsFile->findNext([T_STRING], ($stackPtr + 1));
83+
$useNamespaceEnd = $phpcsFile->findNext(
84+
[T_NS_SEPARATOR, T_STRING],
85+
($useNamespacePtr + 1),
86+
null,
87+
true
88+
);
89+
90+
$useNamespace = rtrim(
91+
$phpcsFile->getTokensAsString(
92+
$useNamespacePtr,
93+
($useNamespaceEnd - $useNamespacePtr - 1)
94+
),
95+
'\\'
96+
);
97+
98+
if (strcasecmp($namespace, $useNamespace) === 0) {
99+
$classUsed = false;
100+
}
101+
}
102+
103+
while (false !== $classUsed) {
104+
if (strtolower($tokens[$classUsed]['content']) === $lowerClassName) {
105+
// If the name is used in a PHP 7 function return type declaration stop.
106+
if (T_RETURN_TYPE === $tokens[$classUsed]['code']) {
107+
return;
108+
}
109+
110+
$beforeUsage = $phpcsFile->findPrevious(
111+
Tokens::$emptyTokens,
112+
($classUsed - 1),
113+
null,
114+
true
115+
);
116+
117+
// If a backslash is used before the class name then this is some other use statement.
118+
if (T_USE !== $tokens[$beforeUsage]['code'] && T_NS_SEPARATOR !== $tokens[$beforeUsage]['code']) {
119+
return;
120+
}
121+
122+
// Trait use statement within a class.
123+
if (T_USE === $tokens[$beforeUsage]['code'] && false === empty($tokens[$beforeUsage]['conditions'])) {
124+
return;
125+
}
126+
}
127+
128+
$classUsed = $phpcsFile->findNext(
129+
[T_STRING, T_RETURN_TYPE],
130+
($classUsed + 1)
131+
);
132+
}
133+
134+
// More checks
135+
foreach ($tokens as $token) {
136+
// Check for doc params @...
137+
if ('T_DOC_COMMENT_TAG' === $token['type']) {
138+
// Handle comment tag as @Route(..) or @ORM\Id
139+
if (preg_match('/^@'.$lowerClassName.'(?![a-zA-Z])/i', $token['content']) === 1) {
140+
return;
141+
};
142+
}
143+
144+
// Check for @param Truc or @return Machin
145+
if ('T_DOC_COMMENT_STRING' === $token['type']) {
146+
if (trim(strtolower($token['content'])) === $lowerClassName
147+
// Handle @var Machin[]|Machine|AnotherMachin $machin
148+
|| preg_match('/^'.$lowerClassName.'(\|| |\[)/i', trim($token['content'])) === 1
149+
|| preg_match('/(\|| )'.$lowerClassName.'(\|| |\[)/i', trim($token['content'])) === 1
150+
|| preg_match('/(\|| )'.$lowerClassName.'$/i', trim($token['content'])) === 1) {
151+
$beforeUsage = $phpcsFile->findPrevious(
152+
Tokens::$emptyTokens,
153+
($classUsed - 1),
154+
null,
155+
true
156+
);
157+
158+
// If a backslash is used before the class name then this is some other use statement.
159+
if (T_USE !== $tokens[$beforeUsage]['code'] && T_NS_SEPARATOR !== $tokens[$beforeUsage]['code']) {
160+
return;
161+
}
162+
}
163+
}
164+
}
165+
166+
$fix = $phpcsFile->addFixableError('Unused use statement', $stackPtr, 'UnusedUse');
167+
if (true === $fix) {
168+
// Remove the whole use statement line.
169+
$phpcsFile->fixer->beginChangeset();
170+
for ($i = $stackPtr; $i <= $semiColon; $i++) {
171+
$phpcsFile->fixer->replaceToken($i, '');
172+
}
173+
174+
// Also remove whitespace after the semicolon (new lines).
175+
while (true === isset($tokens[$i]) && T_WHITESPACE === $tokens[$i]['code']) {
176+
$phpcsFile->fixer->replaceToken($i, '');
177+
if (strpos($tokens[$i]['content'], $phpcsFile->eolChar) !== false) {
178+
break;
179+
}
180+
181+
$i++;
182+
}
183+
184+
$phpcsFile->fixer->endChangeset();
185+
}
186+
}
187+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace MyProject;
4+
5+
use BarClass as Bar;
6+
use Foo;
7+
use Route;
8+
use ORM;
9+
use Truc;
10+
use Machin;
11+
use Machine;
12+
use Unused;
13+
use Client;
14+
use Clients;
15+
use PasClient;
16+
17+
class Container
18+
{
19+
/**
20+
* @Foo
21+
* @Route("/{id}")
22+
* @ORM\Column(type="integer")
23+
*
24+
* @param Toto $toto
25+
* @var Truc $truc
26+
* @return Machin|Machine
27+
*/
28+
function test (Bar $bar)
29+
{
30+
/** @var Client|Clients[]|PasClient $client */
31+
32+
return;
33+
}
34+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace MyProject;
4+
5+
use BarClass as Bar;
6+
use Foo;
7+
use Route;
8+
use ORM;
9+
use Truc;
10+
use Machin;
11+
use Machine;
12+
use Client;
13+
use Clients;
14+
use PasClient;
15+
16+
class Container
17+
{
18+
/**
19+
* @Foo
20+
* @Route("/{id}")
21+
* @ORM\Column(type="integer")
22+
*
23+
* @param Toto $toto
24+
* @var Truc $truc
25+
* @return Machin|Machine
26+
*/
27+
function test (Bar $bar)
28+
{
29+
/** @var Client|Clients[]|PasClient $client */
30+
31+
return;
32+
}
33+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace Symfony3Custom\Tests\Namespaces;
4+
5+
use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;
6+
7+
/**
8+
* Unit test class for the UnusedUse sniff.
9+
*
10+
* @group Symfony3Custom
11+
*/
12+
class UnusedUseUnitTest extends AbstractSniffUnitTest
13+
{
14+
/**
15+
* Returns the lines where errors should occur.
16+
*
17+
* The key of the array should represent the line number and the value
18+
* should represent the number of errors that should occur on that line.
19+
*
20+
* @return array<int, int>
21+
*/
22+
public function getErrorList()
23+
{
24+
return array(
25+
12 => 1,
26+
);
27+
}
28+
29+
/**
30+
* Returns the lines where warnings should occur.
31+
*
32+
* The key of the array should represent the line number and the value
33+
* should represent the number of errors that should occur on that line.
34+
*
35+
* @return array(int => int)
36+
*/
37+
public function getWarningList()
38+
{
39+
return array();
40+
}
41+
}

docs/standards.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,9 @@ we do not respect this rule:
130130
```
131131
<rule ref="Symfony3Custom.Namespaces.AlphabeticallySortedUse"/>
132132
```
133+
134+
- Unused `use` statement should be removed
135+
136+
```
137+
<rule ref="Symfony3Custom.Namespaces.UnusedUse"/>
138+
```

0 commit comments

Comments
 (0)