Skip to content

Commit ab9accb

Browse files
authored
[post] Rector 2.2 iterable docblocks (#3347)
* kick off * post * grammarly * iterables in tests * misc * last
1 parent 4c30191 commit ab9accb

13 files changed

Lines changed: 915 additions & 345 deletions

File tree

composer.lock

Lines changed: 642 additions & 339 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rector.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
codeQuality: true,
1616
codingStyle: true,
1717
typeDeclarations: true,
18+
typeDeclarationDocblocks: true,
1819
privatization: true,
1920
naming: true,
2021
rectorPreset: true,
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
---
2+
id: 80
3+
title: "Rector 2.2: New rules for Array Docblocks"
4+
perex: |
5+
As you know, we provide an [upgrade services](https://getrector.com/hire-team) to speed up the modernization of codebases. Part of this service is getting PHPStan to level 8 with no baseline (only edge cases).
6+
7+
Level 6 is known for requesting more detailed types over `array`,
8+
`iterable` or `Iterator` type hints. Bare `mixed` or `array` should be replaced with explicit key/value types, e.g., `string[]` or `array<int, SomeObject>`.
9+
10+
At first, we did this work manually. Later, we made custom Rector rules that we kept private.
11+
12+
Today, we are open-sourcing these rules to help you with the same task.
13+
---
14+
15+
<div class="alert alert-warning mt-3 mb-5">
16+
This feature is experimental. We look for your feedback in real projects. Found a glitch, or do you expect a different output? Let us known <a href="https://github.com/rectorphp/rector/issues">on GitHub</a>.
17+
</div>
18+
19+
We designed these rules to avoid filling useless types like `mixed`, `mixed[]`, or `array`. If the Rector doesn't know better, it will skip these cases. We want to fill those types that humans would do to improve code readability and static analysis.
20+
21+
<br>
22+
23+
Let's look at a few examples that are missing detailed types and that Rector can improve now:
24+
25+
## 1. Known Return Scalar
26+
27+
```php
28+
function getNames(): array
29+
{
30+
return ['John', 'Jane'];
31+
}
32+
```
33+
34+
<br>
35+
36+
Now this is straightforward; it can be improved to:
37+
38+
```diff
39+
+/**
40+
+ * @return string[]
41+
+ */
42+
function getNames(): array
43+
{
44+
// ...
45+
}
46+
```
47+
48+
Why do this manually in 100s of places, if Rector can do it for you?
49+
50+
<br>
51+
52+
## 2. Known Return Objects
53+
54+
Let's look at another example:
55+
56+
```php
57+
function getUsers(): array
58+
{
59+
$user = [];
60+
61+
$users[] = new User('John');
62+
$users[] = new User('Jane');
63+
64+
return $users;
65+
}
66+
```
67+
68+
<br>
69+
70+
No-brainer:
71+
72+
```diff
73+
+/**
74+
+ * @return User[]
75+
+ */
76+
function getUsers(): array
77+
{
78+
// ...
79+
}
80+
```
81+
82+
<br>
83+
84+
## 3. Known Shared Object Type
85+
86+
What if there are multiple different objects that all share a single contract interface?
87+
88+
```php
89+
final class ExtensionProvider
90+
{
91+
public function provide(): array
92+
{
93+
return [
94+
new FirstExtension(),
95+
new SecondExtension(),
96+
];
97+
}
98+
}
99+
```
100+
101+
<br>
102+
103+
104+
In a real project, we would have to open all of those classes, check parent classes and interfaces, and try to find the first common one. Now we don't have to, Rector does it for us:
105+
106+
```diff
107+
+ /**
108+
+ * @return ExtensionInterface[]
109+
+ */
110+
public function provide(): array
111+
{
112+
// ...
113+
}
114+
```
115+
116+
<br>
117+
118+
## 4. Known `array_map()` return
119+
120+
We can infer the type from functions like `array_map()`:
121+
122+
```diff
123+
+/**
124+
+ * @return string[]
125+
+ */
126+
public function getNames(array $users): array
127+
{
128+
return array_map(fn (User $user): string => $user->getName(), $users);
129+
}
130+
```
131+
132+
<br>
133+
134+
## 5. Known private method types
135+
136+
What if the method is private and is called only in a local class? We can now collect all the method calls and learn their type:
137+
138+
```diff
139+
final class IncomeCalculator
140+
{
141+
public function addCompanyTips(): void
142+
{
143+
$this->addTips([100, 200, 300]);
144+
}
145+
146+
public function addPersonalTips(): void
147+
{
148+
$this->addTips([50, 150]);
149+
}
150+
151+
+ /**
152+
+ * @param int[] $tips
153+
+ */
154+
private function addTips(array $tips): void
155+
{
156+
}
157+
}
158+
```
159+
160+
...and many more. Right now, the initial set contains **[15 rules](https://github.com/rectorphp/rector-src/blob/main/src/Config/Level/TypeDeclarationDocblocksLevel.php)**, and we plan to extend it further. Got an idea for an obvious rule that you keep doing manually and is not covered yet? Let us know.
161+
162+
<br>
163+
164+
## Smart Override
165+
166+
Rector is smart enough to keep detailed types, but override those dummy ones:
167+
168+
```diff
169+
/**
170+
- * @return mixed[]
171+
+ * @return string[]
172+
*/
173+
function getNames(): array
174+
{
175+
return ['one', 'two']
176+
}
177+
```
178+
179+
<br>
180+
181+
## Start with Levels
182+
183+
The best way to start using this set is via [level feature](/documentation/levels). Add this single line to your `rector.php` config:
184+
185+
```php
186+
<?php
187+
188+
use Rector\Config\RectorConfig;
189+
190+
return RectorConfig::configure()
191+
->withTypeCoverageDocblockLevel(0);
192+
```
193+
194+
<br>
195+
196+
And take it one level at a time:
197+
198+
```diff
199+
<?php
200+
201+
use Rector\Config\RectorConfig;
202+
203+
return RectorConfig::configure()
204+
- ->withTypeCoverageDocblockLevel(0);
205+
+ ->withTypeCoverageDocblockLevel(1);
206+
```
207+
208+
<br>
209+
210+
In a rush or feeling lucky? Add full set:
211+
212+
```php
213+
<?php
214+
215+
use Rector\Config\RectorConfig;
216+
217+
return RectorConfig::configure()
218+
->withPreparedSets(typeDeclarationDocblocks: true);
219+
```
220+
221+
<br>
222+
223+
We've put a lot of work into making rules balanced and reliable, but it's still in the early testing phase. Give it a go, let us know how slim your baseline got after a single Rector run.
224+
225+
<br>
226+
227+
Happy coding!

resources/docs/levels.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,22 @@ vendor/bin/rector
5353

5454
Only five files? We can do that in a day. We create a pull request, get a review, and merge. The next day, we can continue with level 1. You get the idea.
5555

56+
## Type Coverage and Type Coverage Docblocks Levels
57+
58+
Rector offer ruleset to fill known type declarations.
59+
60+
Since Rector 2.2, it also offers another ruleset to fill docblock `@param`, `@return` or `@var` types, where native type hints are not possible, e.g. `string[]`.
61+
62+
```php
63+
<?php
64+
65+
use Rector\Config\RectorConfig;
66+
67+
return RectorConfig::configure()
68+
->withTypeCoverageLevel(0);
69+
->withTypeCoverageDocblockLevel(0);
70+
```
71+
5672
## Dead Code, Code Quality and Coding Style Levels
5773

5874
Are you done with the type level and reached [99 % type coverage](https://github.com/tomasVotruba/type-coverage)? It's time to move on to dead code removal and to improve code quality and coding style.
@@ -65,8 +81,6 @@ Again, we avoid full-blown prepared set, and make use of level methods:
6581
use Rector\Config\RectorConfig;
6682

6783
return RectorConfig::configure()
68-
->withPaths([__DIR__ . '/src', __DIR__ . '/tests'])
69-
->withTypeCoverageLevel(0)
7084
->withDeadCodeLevel(0)
7185
->withCodeQualityLevel(0)
7286
->withCodingStyleLevel(0);

src/DemoRunner.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public function processRectorRun(AbstractRectorRun $rectorRun): void
5555
}
5656

5757
/**
58-
* @return mixed[]
58+
* @return array<string, mixed>
5959
*/
6060
private function processFilesContents(string $fileContent, string $rectorConfig): array
6161
{

src/Entity/AbstractRectorRun.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ public function __construct(
1818
protected readonly Uuid $uuid,
1919
protected readonly string $content,
2020
protected readonly string $runnablePhp,
21-
/** @var array<string, mixed> */
21+
/**
22+
* @var array<string, mixed>
23+
*/
2224
protected array $jsonResult = [],
2325
protected string|null $fatalErrorMessage = null
2426
) {

src/Livewire/FindRuleComponent.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ private function countRulesNotInSet(): int
110110
/** @var RuleFilter $ruleFilter */
111111
$ruleFilter = app(RuleFilter::class);
112112

113-
return count($ruleFilter->filter($rectorFinder->find(), null, RuleFilter::NOT_IN_SET, $this->activeRectorSetGroup));
113+
return count(
114+
$ruleFilter->filter($rectorFinder->find(), null, RuleFilter::NOT_IN_SET, $this->activeRectorSetGroup)
115+
);
114116
}
115117
}

src/Logging/RectorFuleSearchLogger.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ private function isSQLInjection(?string $query): bool
5151
}
5252

5353
$lowerQuery = strtolower($query);
54-
return array_any(self::EXCLUDED_QUERIES, fn($excludedQuery): bool => str_contains($lowerQuery, (string) $excludedQuery));
54+
return array_any(
55+
self::EXCLUDED_QUERIES,
56+
fn ($excludedQuery): bool => str_contains($lowerQuery, (string) $excludedQuery)
57+
);
5558
}
5659
}

tests/Controller/DemoControllerTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ public function testValidRectorConfig(): void
150150
$testResponse->assertSessionHasNoErrors();
151151
}
152152

153+
/**
154+
* @return Iterator<(array<int, array<string, string>>|array<int, string>)>
155+
*/
153156
public static function provideTestFormSubmitData(): Iterator
154157
{
155158
// Send empty form

tests/GitHubMagicLink/LinkFactory/FixtureLinkFactory/FixtureLinkFactoryTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ public function test(RectorRun $rectorRun, string $expectedLink): void
3434
$this->assertStringMatchesFormatFile($expectedLink, $testFixtureLink . PHP_EOL);
3535
}
3636

37+
/**
38+
* @return Iterator<array<int, (RectorRun|string)>>
39+
*/
3740
public static function provideData(): Iterator
3841
{
3942
$rectorRun = DummyRectorRunFactory::create();

0 commit comments

Comments
 (0)