-
Notifications
You must be signed in to change notification settings - Fork 19
/
PhpUnitNoUselessReturnFixer.php
120 lines (100 loc) · 4.3 KB
/
PhpUnitNoUselessReturnFixer.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
<?php
declare(strict_types = 1);
namespace PhpCsFixerCustomFixers\Fixer;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator;
use PhpCsFixer\Tokenizer\Tokens;
use PhpCsFixer\Utils;
use PhpCsFixerCustomFixers\TokenRemover;
final class PhpUnitNoUselessReturnFixer extends AbstractFixer
{
private const FUNCTION_TOKENS = [[T_STRING, 'fail'], [T_STRING, 'markTestIncomplete'], [T_STRING, 'markTestSkipped']];
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
\sprintf(
"PHPUnit's functions %s should not be followed directly by return.",
Utils::naturalLanguageJoinWithBackticks(\array_map(
static function (array $token): string {
return $token[1];
},
self::FUNCTION_TOKENS
))
),
[new CodeSample('<?php
class FooTest extends TestCase {
public function testFoo() {
$this->markTestSkipped();
return;
}
}
')],
'They will throw exception anyway.',
"when PHPUnit's native methods are overridden"
);
}
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isTokenKindFound(T_STRING);
}
public function isRisky(): bool
{
return true;
}
public function fix(\SplFileInfo $file, Tokens $tokens): void
{
$phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator();
foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) {
$this->removeUselessReturns($tokens, $indexes[0], $indexes[1]);
}
}
public function getPriority(): int
{
return -21;
}
private function removeUselessReturns(Tokens $tokens, int $startIndex, int $endIndex): void
{
for ($index = $startIndex; $index < $endIndex; $index++) {
if (!$tokens[$index]->equalsAny(self::FUNCTION_TOKENS, false)) {
continue;
}
/** @var int $openingBraceIndex */
$openingBraceIndex = $tokens->getNextMeaningfulToken($index);
if (!$tokens[$openingBraceIndex]->equals('(')) {
continue;
}
/** @var int $operatorIndex */
$operatorIndex = $tokens->getPrevMeaningfulToken($index);
/** @var int $referenceIndex */
$referenceIndex = $tokens->getPrevMeaningfulToken($operatorIndex);
if (!($tokens[$operatorIndex]->isGivenKind(T_OBJECT_OPERATOR) && $tokens[$referenceIndex]->equals([T_VARIABLE, '$this'], false))
&& !($tokens[$operatorIndex]->isGivenKind(T_DOUBLE_COLON) && $tokens[$referenceIndex]->equals([T_STRING, 'self'], false))
&& !($tokens[$operatorIndex]->isGivenKind(T_DOUBLE_COLON) && $tokens[$referenceIndex]->isGivenKind(T_STATIC))
) {
continue;
}
$closingBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openingBraceIndex);
/** @var int $semicolonIndex */
$semicolonIndex = $tokens->getNextMeaningfulToken($closingBraceIndex);
if (!$tokens[$semicolonIndex]->equals(';')) {
continue;
}
/** @var int $returnIndex */
$returnIndex = $tokens->getNextMeaningfulToken($semicolonIndex);
if (!$tokens[$returnIndex]->isGivenKind(T_RETURN)) {
continue;
}
/** @var int $semicolonAfterReturnIndex */
$semicolonAfterReturnIndex = $tokens->getNextTokenOfKind($returnIndex, [';', '(']);
while ($tokens[$semicolonAfterReturnIndex]->equals('(')) {
$closingBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $semicolonAfterReturnIndex);
/** @var int $semicolonAfterReturnIndex */
$semicolonAfterReturnIndex = $tokens->getNextTokenOfKind($closingBraceIndex, [';', '(']);
}
$tokens->clearRange($returnIndex, $semicolonAfterReturnIndex - 1);
TokenRemover::removeWithLinesIfPossible($tokens, $semicolonAfterReturnIndex);
}
}
}