-
-
Notifications
You must be signed in to change notification settings - Fork 336
/
InlineSimplePropertyAnnotationRector.php
156 lines (131 loc) · 4.29 KB
/
InlineSimplePropertyAnnotationRector.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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
<?php
declare(strict_types=1);
namespace Rector\CodingStyle\Rector\Property;
use PhpParser\Comment\Doc;
use PhpParser\Node;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\Property;
use Rector\Core\Contract\Rector\AllowEmptyConfigurableRectorInterface;
use Rector\Core\Rector\AbstractRector;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
use Webmozart\Assert\Assert;
/**
* @see \Rector\Tests\CodingStyle\Rector\Property\InlineSimplePropertyAnnotationRector\InlineSimplePropertyAnnotationRectorTest
*
* rector-src dev note:
*
* Do not register to coding-style config/set/coding-style.php set
* as it will always conflict with ECS use of \PhpCsFixer\Fixer\Phpdoc\PhpdocLineSpanFixer
* so rectify CI will always rolled back the change
*/
final class InlineSimplePropertyAnnotationRector extends AbstractRector implements AllowEmptyConfigurableRectorInterface
{
/**
* @var string[]
*/
private array $annotationsToConsiderForInlining = ['@var', '@phpstan-var', '@psalm-var'];
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Inline simple @var annotations (or other annotations) when they are the only thing in the phpdoc',
[
new ConfiguredCodeSample(
<<<'CODE_SAMPLE'
final class SomeClass
{
/**
* @phpstan-var string
*/
private const TEXT = 'text';
/**
* @var DateTime[]
*/
private ?array $dateTimes;
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
final class SomeClass
{
/** @phpstan-var string */
private const TEXT = 'text';
/** @var DateTime[] */
private ?array $dateTimes;
}
CODE_SAMPLE
,
['var', 'phpstan-var'],
),
]
);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [Property::class, ClassConst::class];
}
/**
* @param mixed[] $configuration
*/
public function configure(array $configuration): void
{
Assert::allString($configuration);
$this->annotationsToConsiderForInlining = array_map(
static fn (string $annotation): string => '@' . ltrim($annotation, '@'),
$configuration
);
}
/**
* @param Property|ClassConst $node
*/
public function refactor(Node $node): ?Node
{
if ($this->shouldSkipNode($node)) {
return null;
}
$comments = $node->getAttribute(AttributeKey::COMMENTS, []);
if ((is_countable($comments) ? count($comments) : 0) !== 1) {
return null;
}
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
if (count($phpDocInfo->getPhpDocNode()->children) > 1) {
return null;
}
$phpDocNode = $phpDocInfo->getPhpDocNode();
$tags = $phpDocNode->getTags();
if (count($tags) !== 1) {
return null;
}
// The first value may not be at index 0
$phpDocTagNode = reset($tags);
if (! in_array($phpDocTagNode->name, $this->annotationsToConsiderForInlining, true)) {
return null;
}
if (str_contains((string) $phpDocTagNode, "\n")) {
return null;
}
// Handle edge cases where stringified tag is not same as it was originally
/** @var Doc $comment */
$comment = $comments[0];
if (! str_contains($comment->getText(), (string) $phpDocTagNode)) {
return null;
}
// Creating new node is the only way to enforce the "singleLined" property AFAIK
$newPhpDocInfo = $this->phpDocInfoFactory->createEmpty($node);
$newPhpDocInfo->makeSingleLined();
$newPhpDocNode = $newPhpDocInfo->getPhpDocNode();
$newPhpDocNode->children = [$phpDocTagNode];
return $node;
}
private function shouldSkipNode(ClassConst|Property $node): bool
{
if ($node instanceof Property && count($node->props) !== 1) {
return true;
}
return $node instanceof ClassConst && count($node->consts) !== 1;
}
}