Skip to content

Commit 7864bf3

Browse files
committed
Added UselessVariableSniff
1 parent d0d2417 commit 7864bf3

File tree

5 files changed

+323
-0
lines changed

5 files changed

+323
-0
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,10 @@ Looks for useless semicolons.
173173

174174
Looks for unused variables.
175175

176+
#### SlevomatCodingStandard.Variables.UselessVariable
177+
178+
Looks for useless variables.
179+
176180
#### SlevomatCodingStandard.Exceptions.DeadCatch
177181

178182
This sniff finds unreachable catch blocks:
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SlevomatCodingStandard\Sniffs\Variables;
4+
5+
use PHP_CodeSniffer\Files\File;
6+
use PHP_CodeSniffer\Sniffs\Sniff;
7+
use SlevomatCodingStandard\Helpers\TokenHelper;
8+
use const T_AND_EQUAL;
9+
use const T_CONCAT_EQUAL;
10+
use const T_DIV_EQUAL;
11+
use const T_EQUAL;
12+
use const T_MINUS_EQUAL;
13+
use const T_MOD_EQUAL;
14+
use const T_MUL_EQUAL;
15+
use const T_OR_EQUAL;
16+
use const T_PLUS_EQUAL;
17+
use const T_POW_EQUAL;
18+
use const T_RETURN;
19+
use const T_SEMICOLON;
20+
use const T_SL_EQUAL;
21+
use const T_SR_EQUAL;
22+
use const T_VARIABLE;
23+
use const T_XOR_EQUAL;
24+
use function array_reverse;
25+
use function count;
26+
use function in_array;
27+
use function sprintf;
28+
29+
class UselessVariableSniff implements Sniff
30+
{
31+
32+
public const CODE_USELESS_VARIABLE = 'UselessVariable';
33+
34+
/**
35+
* @return mixed[]
36+
*/
37+
public function register(): array
38+
{
39+
return [
40+
T_RETURN,
41+
];
42+
}
43+
44+
/**
45+
* @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
46+
* @param \PHP_CodeSniffer\Files\File $phpcsFile
47+
* @param int $returnPointer
48+
*/
49+
public function process(File $phpcsFile, $returnPointer): void
50+
{
51+
$tokens = $phpcsFile->getTokens();
52+
53+
/** @var int $variablePointer */
54+
$variablePointer = TokenHelper::findNextEffective($phpcsFile, $returnPointer + 1);
55+
if ($tokens[$variablePointer]['code'] !== T_VARIABLE) {
56+
return;
57+
}
58+
59+
$pointerAfterVariable = TokenHelper::findNextEffective($phpcsFile, $variablePointer + 1);
60+
if ($tokens[$pointerAfterVariable]['code'] !== T_SEMICOLON) {
61+
return;
62+
}
63+
64+
$variableName = $tokens[$variablePointer]['content'];
65+
66+
$previousVariablePointer = null;
67+
for ($i = $returnPointer - 1; $i >= 0; $i--) {
68+
if (
69+
in_array($tokens[$i]['code'], TokenHelper::$functionTokenCodes, true)
70+
&& $this->isInSameScope($phpcsFile, $tokens[$i]['scope_opener'] + 1, $returnPointer)
71+
) {
72+
return;
73+
}
74+
75+
if ($tokens[$i]['code'] !== T_VARIABLE) {
76+
continue;
77+
}
78+
79+
if ($tokens[$i]['content'] !== $variableName) {
80+
continue;
81+
}
82+
83+
if (!$this->isInSameScope($phpcsFile, $i, $variablePointer)) {
84+
continue;
85+
}
86+
87+
$previousVariablePointer = $i;
88+
break;
89+
}
90+
91+
if ($previousVariablePointer === null) {
92+
return;
93+
}
94+
95+
$pointerAfterPreviousVariable = TokenHelper::findNextEffective($phpcsFile, $previousVariablePointer + 1);
96+
if (!in_array($tokens[$pointerAfterPreviousVariable]['code'], [
97+
T_EQUAL,
98+
T_PLUS_EQUAL,
99+
T_MINUS_EQUAL,
100+
T_MUL_EQUAL,
101+
T_DIV_EQUAL,
102+
T_POW_EQUAL,
103+
T_MOD_EQUAL,
104+
T_AND_EQUAL,
105+
T_OR_EQUAL,
106+
T_XOR_EQUAL,
107+
T_SL_EQUAL,
108+
T_SR_EQUAL,
109+
T_CONCAT_EQUAL,
110+
], true)) {
111+
return;
112+
}
113+
114+
for ($i = $variablePointer + 1; $i < count($tokens); $i++) {
115+
if (
116+
in_array($tokens[$i]['code'], TokenHelper::$functionTokenCodes, true)
117+
&& $this->isInSameScope($phpcsFile, $variablePointer, $i)
118+
) {
119+
$i = $tokens[$i]['scope_closer'];
120+
continue;
121+
}
122+
123+
if ($tokens[$i]['code'] !== T_VARIABLE) {
124+
continue;
125+
}
126+
127+
if ($tokens[$i]['content'] !== $variableName) {
128+
continue;
129+
}
130+
131+
if ($this->isInSameScope($phpcsFile, $variablePointer, $i)) {
132+
return;
133+
}
134+
}
135+
136+
$phpcsFile->addError(sprintf('Useless variable %s.', $variableName), $previousVariablePointer, self::CODE_USELESS_VARIABLE);
137+
}
138+
139+
private function isInSameScope(File $phpcsFile, int $firstPointer, int $secondPointer): bool
140+
{
141+
$tokens = $phpcsFile->getTokens();
142+
143+
$getScopeLevel = function (int $pointer) use ($tokens): int {
144+
foreach (array_reverse($tokens[$pointer]['conditions'], true) as $conditionPointer => $conditionTokenCode) {
145+
if (!in_array($conditionTokenCode, TokenHelper::$functionTokenCodes, true)) {
146+
continue;
147+
}
148+
149+
return $tokens[$conditionPointer]['level'];
150+
}
151+
152+
return $tokens[$pointer]['level'];
153+
};
154+
155+
return $getScopeLevel($firstPointer) === $getScopeLevel($secondPointer);
156+
}
157+
158+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SlevomatCodingStandard\Sniffs\Variables;
4+
5+
use SlevomatCodingStandard\Sniffs\TestCase;
6+
7+
class UselessVariableSniffTest extends TestCase
8+
{
9+
10+
public function testNoErrors(): void
11+
{
12+
$report = self::checkFile(__DIR__ . '/data/uselessVariableNoErrors.php');
13+
self::assertNoSniffErrorInFile($report);
14+
}
15+
16+
public function testErrors(): void
17+
{
18+
$report = self::checkFile(__DIR__ . '/data/uselessVariableErrors.php');
19+
20+
self::assertSame(15, $report->getErrorCount());
21+
22+
self::assertSniffError($report, 4, UselessVariableSniff::CODE_USELESS_VARIABLE, 'Useless variable $a.');
23+
self::assertSniffError($report, 9, UselessVariableSniff::CODE_USELESS_VARIABLE, 'Useless variable $b.');
24+
self::assertSniffError($report, 14, UselessVariableSniff::CODE_USELESS_VARIABLE, 'Useless variable $c.');
25+
self::assertSniffError($report, 19, UselessVariableSniff::CODE_USELESS_VARIABLE, 'Useless variable $d.');
26+
self::assertSniffError($report, 24, UselessVariableSniff::CODE_USELESS_VARIABLE, 'Useless variable $e.');
27+
self::assertSniffError($report, 29, UselessVariableSniff::CODE_USELESS_VARIABLE, 'Useless variable $f.');
28+
self::assertSniffError($report, 34, UselessVariableSniff::CODE_USELESS_VARIABLE, 'Useless variable $g.');
29+
self::assertSniffError($report, 39, UselessVariableSniff::CODE_USELESS_VARIABLE, 'Useless variable $h.');
30+
self::assertSniffError($report, 44, UselessVariableSniff::CODE_USELESS_VARIABLE, 'Useless variable $i.');
31+
self::assertSniffError($report, 49, UselessVariableSniff::CODE_USELESS_VARIABLE, 'Useless variable $j.');
32+
self::assertSniffError($report, 54, UselessVariableSniff::CODE_USELESS_VARIABLE, 'Useless variable $k.');
33+
self::assertSniffError($report, 59, UselessVariableSniff::CODE_USELESS_VARIABLE, 'Useless variable $l.');
34+
self::assertSniffError($report, 64, UselessVariableSniff::CODE_USELESS_VARIABLE, 'Useless variable $m.');
35+
self::assertSniffError($report, 69, UselessVariableSniff::CODE_USELESS_VARIABLE, 'Useless variable $n.');
36+
self::assertSniffError($report, 77, UselessVariableSniff::CODE_USELESS_VARIABLE, 'Useless variable $o.');
37+
}
38+
39+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
function () {
4+
$a = true;
5+
return $a;
6+
};
7+
8+
function () {
9+
$b += 1;
10+
return $b;
11+
};
12+
13+
function () {
14+
$c -= 1;
15+
return $c;
16+
};
17+
18+
function () {
19+
$d *= 1;
20+
return $d;
21+
};
22+
23+
function () {
24+
$e /= 1;
25+
return $e;
26+
};
27+
28+
function () {
29+
$f **= 1;
30+
return $f;
31+
};
32+
33+
function () {
34+
$g %= 1;
35+
return $g;
36+
};
37+
38+
function () {
39+
$h &= 1;
40+
return $h;
41+
};
42+
43+
function () {
44+
$i |= 1;
45+
return $i;
46+
};
47+
48+
function () {
49+
$j ^= 1;
50+
return $j;
51+
};
52+
53+
function () {
54+
$k <<= 1;
55+
return $k;
56+
};
57+
58+
function () {
59+
$l >>= 1;
60+
return $l;
61+
};
62+
63+
function () {
64+
$m .= 1;
65+
return $m;
66+
};
67+
68+
function sameVariableInDifferentScope() {
69+
$n = array_map(function () {
70+
return $n + 1;
71+
}, []);
72+
73+
return $n;
74+
}
75+
76+
function differentVariableAfterReturn() {
77+
$o = 0;
78+
79+
if (true) {
80+
return $o;
81+
}
82+
83+
$p = 1;
84+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
return $z;
4+
5+
$a = null;
6+
7+
function returnWithoutVariable() {
8+
return true;
9+
}
10+
11+
function varibleOutsideScope() {
12+
return $a;
13+
}
14+
15+
function moreComplexReturn() {
16+
$b = 1;
17+
return $b + 1;
18+
}
19+
20+
function notAssignment() {
21+
$c + 1;
22+
return $c;
23+
}
24+
25+
function sameVariableAfterReturn() {
26+
$d = 0;
27+
28+
if (true) {
29+
return $d;
30+
}
31+
32+
$d = 1;
33+
}
34+
35+
function differentVariable() {
36+
$e = 10;
37+
return $f;
38+
}

0 commit comments

Comments
 (0)