Skip to content

Commit ec52285

Browse files
committed
Add ParenthesizeNegatedInstanceofRector
1 parent a6f0ce5 commit ec52285

File tree

7 files changed

+219
-0
lines changed

7 files changed

+219
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\Instanceof_\Rector\BooleanNot\ParenthesizeNegatedInstanceofRector;
6+
7+
use PHPUnit\Framework\Attributes\DataProvider;
8+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
9+
10+
final class AddParenthesesTest extends AbstractRectorTestCase
11+
{
12+
#[DataProvider('provideData')]
13+
public function test(string $filePath): void
14+
{
15+
$this->doTestFile($filePath);
16+
}
17+
18+
public static function provideData(): \Iterator
19+
{
20+
return self::yieldFilesFromDirectory(__DIR__ . '/FixtureAdd');
21+
}
22+
23+
public function provideConfigFilePath(): string
24+
{
25+
return __DIR__ . '/config/add_parentheses.php';
26+
}
27+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
!$foo instanceof Foo;
4+
5+
?>
6+
-----
7+
<?php
8+
9+
!($foo instanceof Foo);
10+
11+
?>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
!($foo instanceof Foo);
4+
5+
?>
6+
-----
7+
<?php
8+
9+
!$foo instanceof Foo;
10+
11+
?>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\Instanceof_\Rector\BooleanNot\ParenthesizeNegatedInstanceofRector;
6+
7+
use PHPUnit\Framework\Attributes\DataProvider;
8+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
9+
10+
final class RemoveParenthesesTest extends AbstractRectorTestCase
11+
{
12+
#[DataProvider('provideData')]
13+
public function test(string $filePath): void
14+
{
15+
$this->doTestFile($filePath);
16+
}
17+
18+
public static function provideData(): \Iterator
19+
{
20+
return self::yieldFilesFromDirectory(__DIR__ . '/FixtureRemove');
21+
}
22+
23+
public function provideConfigFilePath(): string
24+
{
25+
return __DIR__ . '/config/remove_parentheses.php';
26+
}
27+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\Instanceof_\Rector\BooleanNot\ParenthesizeNegatedInstanceofRector;
7+
8+
return static function (RectorConfig $rectorConfig): void {
9+
$rectorConfig->ruleWithConfiguration(ParenthesizeNegatedInstanceofRector::class, [
10+
'mode' => ParenthesizeNegatedInstanceofRector::ADD_PARENTHESES,
11+
]);
12+
};
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\Instanceof_\Rector\BooleanNot\ParenthesizeNegatedInstanceofRector;
7+
8+
return static function (RectorConfig $rectorConfig): void {
9+
$rectorConfig->ruleWithConfiguration(ParenthesizeNegatedInstanceofRector::class, [
10+
'mode' => ParenthesizeNegatedInstanceofRector::REMOVE_PARENTHESES,
11+
]);
12+
};
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<?php
2+
3+
namespace Rector\Instanceof_\Rector\BooleanNot;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr\BooleanNot;
7+
use PhpParser\Node\Expr\Instanceof_;
8+
use Rector\Contract\Rector\ConfigurableRectorInterface;
9+
use Rector\NodeTypeResolver\Node\AttributeKey;
10+
use Rector\Rector\AbstractRector;
11+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
12+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
13+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
14+
use Webmozart\Assert\Assert;
15+
16+
/**
17+
* @see \Rector\Tests\Instanceof_\Rector\BooleanNot\ParenthesizeNegatedInstanceofRector\AddParenthesesTest
18+
* @see \Rector\Tests\Instanceof_\Rector\BooleanNot\ParenthesizeNegatedInstanceofRector\RemoveParenthesesTest
19+
*/
20+
final class ParenthesizeNegatedInstanceofRector extends AbstractRector implements ConfigurableRectorInterface
21+
{
22+
public const ADD_PARENTHESES = 'add_parentheses';
23+
24+
public const REMOVE_PARENTHESES = 'remove_parentheses';
25+
26+
private string $mode = self::ADD_PARENTHESES;
27+
28+
public function getRuleDefinition(): RuleDefinition
29+
{
30+
return new RuleDefinition(
31+
'Add or remove parentheses around negated instanceof checks',
32+
[
33+
new CodeSample(
34+
<<<'CODE_SAMPLE'
35+
if (!$foo instanceof Foo) {}
36+
CODE_SAMPLE
37+
,
38+
<<<'CODE_SAMPLE'
39+
if (!($foo instanceof Foo)) {}
40+
CODE_SAMPLE
41+
,
42+
),
43+
new ConfiguredCodeSample(
44+
<<<'CODE_SAMPLE'
45+
if (!$foo instanceof Foo) {}
46+
CODE_SAMPLE
47+
,
48+
<<<'CODE_SAMPLE'
49+
if (!($foo instanceof Foo)) {}
50+
CODE_SAMPLE
51+
,
52+
[
53+
'mode' => self::ADD_PARENTHESES,
54+
],
55+
),
56+
new ConfiguredCodeSample(
57+
<<<'CODE_SAMPLE'
58+
if (!($foo instanceof Foo)) {}
59+
CODE_SAMPLE
60+
,
61+
<<<'CODE_SAMPLE'
62+
if (!$foo instanceof Foo) {}
63+
CODE_SAMPLE
64+
,
65+
[
66+
'mode' => self::REMOVE_PARENTHESES,
67+
],
68+
),
69+
],
70+
);
71+
}
72+
73+
public function configure(array $configuration): void
74+
{
75+
if ($configuration !== []) {
76+
Assert::keyExists($configuration, 'mode');
77+
Assert::oneOf($configuration['mode'], [self::ADD_PARENTHESES, self::REMOVE_PARENTHESES]);
78+
}
79+
80+
$this->mode = $configuration['mode'] ?? $this->mode;
81+
}
82+
83+
public function getNodeTypes(): array
84+
{
85+
return [BooleanNot::class];
86+
}
87+
88+
/**
89+
* @param BooleanNot $node
90+
*/
91+
public function refactor(Node $node): ?BooleanNot
92+
{
93+
if (! $node->expr instanceof Instanceof_) {
94+
return null;
95+
}
96+
97+
$oldTokens = $this->file->getOldTokens();
98+
99+
$tokensStartWithOpeningParens = ((string) $oldTokens[$node->getStartTokenPos() + 1]) === '(';
100+
$tokensEndWithClosingParens = ((string) $oldTokens[$node->getEndTokenPos()]) === ')';
101+
102+
$alreadyWrapped = $tokensStartWithOpeningParens && $tokensEndWithClosingParens;
103+
104+
if ($this->mode === self::ADD_PARENTHESES && $alreadyWrapped === true) {
105+
return null;
106+
}
107+
108+
if ($this->mode === self::ADD_PARENTHESES) {
109+
$node->expr->setAttribute(AttributeKey::WRAPPED_IN_PARENTHESES, true);
110+
return $node;
111+
}
112+
113+
if ($alreadyWrapped === false) {
114+
return null;
115+
}
116+
117+
return new BooleanNot($node->expr);
118+
}
119+
}

0 commit comments

Comments
 (0)