Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
981 changes: 642 additions & 339 deletions composer.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
codeQuality: true,
codingStyle: true,
typeDeclarations: true,
typeDeclarationDocblocks: true,
privatization: true,
naming: true,
rectorPreset: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
---
id: 80
title: "Rector 2.2: New rules for Array Docblocks"
perex: |
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).

Level 6 is known for requesting more detailed types over `array`,
`iterable` or `Iterator` type hints. Bare `mixed` or `array` should be replaced with explicit key/value types, e.g., `string[]` or `array<int, SomeObject>`.

At first, we did this work manually. Later, we made custom Rector rules that we kept private.

Today, we are open-sourcing these rules to help you with the same task.
---

<div class="alert alert-warning mt-3 mb-5">
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>.
</div>

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.

<br>

Let's look at a few examples that are missing detailed types and that Rector can improve now:

## 1. Known Return Scalar

```php
function getNames(): array
{
return ['John', 'Jane'];
}
```

<br>

Now this is straightforward; it can be improved to:

```diff
+/**
+ * @return string[]
+ */
function getNames(): array
{
// ...
}
```

Why do this manually in 100s of places, if Rector can do it for you?

<br>

## 2. Known Return Objects

Let's look at another example:

```php
function getUsers(): array
{
$user = [];

$users[] = new User('John');
$users[] = new User('Jane');

return $users;
}
```

<br>

No-brainer:

```diff
+/**
+ * @return User[]
+ */
function getUsers(): array
{
// ...
}
```

<br>

## 3. Known Shared Object Type

What if there are multiple different objects that all share a single contract interface?

```php
final class ExtensionProvider
{
public function provide(): array
{
return [
new FirstExtension(),
new SecondExtension(),
];
}
}
```

<br>


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:

```diff
+ /**
+ * @return ExtensionInterface[]
+ */
public function provide(): array
{
// ...
}
```

<br>

## 4. Known `array_map()` return

We can infer the type from functions like `array_map()`:

```diff
+/**
+ * @return string[]
+ */
public function getNames(array $users): array
{
return array_map(fn (User $user): string => $user->getName(), $users);
}
```

<br>

## 5. Known private method types

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:

```diff
final class IncomeCalculator
{
public function addCompanyTips(): void
{
$this->addTips([100, 200, 300]);
}

public function addPersonalTips(): void
{
$this->addTips([50, 150]);
}

+ /**
+ * @param int[] $tips
+ */
private function addTips(array $tips): void
{
}
}
```

...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.

<br>

## Smart Override

Rector is smart enough to keep detailed types, but override those dummy ones:

```diff
/**
- * @return mixed[]
+ * @return string[]
*/
function getNames(): array
{
return ['one', 'two']
}
```

<br>

## Start with Levels

The best way to start using this set is via [level feature](/documentation/levels). Add this single line to your `rector.php` config:

```php
<?php

use Rector\Config\RectorConfig;

return RectorConfig::configure()
->withTypeCoverageDocblockLevel(0);
```

<br>

And take it one level at a time:

```diff
<?php

use Rector\Config\RectorConfig;

return RectorConfig::configure()
- ->withTypeCoverageDocblockLevel(0);
+ ->withTypeCoverageDocblockLevel(1);
```

<br>

In a rush or feeling lucky? Add full set:

```php
<?php

use Rector\Config\RectorConfig;

return RectorConfig::configure()
->withPreparedSets(typeDeclarationDocblocks: true);
```

<br>

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.

<br>

Happy coding!
18 changes: 16 additions & 2 deletions resources/docs/levels.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@ vendor/bin/rector

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.

## Type Coverage and Type Coverage Docblocks Levels

Rector offer ruleset to fill known type declarations.

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[]`.

```php
<?php

use Rector\Config\RectorConfig;

return RectorConfig::configure()
->withTypeCoverageLevel(0);
->withTypeCoverageDocblockLevel(0);
```

## Dead Code, Code Quality and Coding Style Levels

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.
Expand All @@ -65,8 +81,6 @@ Again, we avoid full-blown prepared set, and make use of level methods:
use Rector\Config\RectorConfig;

return RectorConfig::configure()
->withPaths([__DIR__ . '/src', __DIR__ . '/tests'])
->withTypeCoverageLevel(0)
->withDeadCodeLevel(0)
->withCodeQualityLevel(0)
->withCodingStyleLevel(0);
Expand Down
2 changes: 1 addition & 1 deletion src/DemoRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public function processRectorRun(AbstractRectorRun $rectorRun): void
}

/**
* @return mixed[]
* @return array<string, mixed>
*/
private function processFilesContents(string $fileContent, string $rectorConfig): array
{
Expand Down
4 changes: 3 additions & 1 deletion src/Entity/AbstractRectorRun.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ public function __construct(
protected readonly Uuid $uuid,
protected readonly string $content,
protected readonly string $runnablePhp,
/** @var array<string, mixed> */
/**
* @var array<string, mixed>
*/
protected array $jsonResult = [],
protected string|null $fatalErrorMessage = null
) {
Expand Down
4 changes: 3 additions & 1 deletion src/Livewire/FindRuleComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ private function countRulesNotInSet(): int
/** @var RuleFilter $ruleFilter */
$ruleFilter = app(RuleFilter::class);

return count($ruleFilter->filter($rectorFinder->find(), null, RuleFilter::NOT_IN_SET, $this->activeRectorSetGroup));
return count(
$ruleFilter->filter($rectorFinder->find(), null, RuleFilter::NOT_IN_SET, $this->activeRectorSetGroup)
);
}
}
5 changes: 4 additions & 1 deletion src/Logging/RectorFuleSearchLogger.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ private function isSQLInjection(?string $query): bool
}

$lowerQuery = strtolower($query);
return array_any(self::EXCLUDED_QUERIES, fn($excludedQuery): bool => str_contains($lowerQuery, (string) $excludedQuery));
return array_any(
self::EXCLUDED_QUERIES,
fn ($excludedQuery): bool => str_contains($lowerQuery, (string) $excludedQuery)
);
}
}
3 changes: 3 additions & 0 deletions tests/Controller/DemoControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ public function testValidRectorConfig(): void
$testResponse->assertSessionHasNoErrors();
}

/**
* @return Iterator<(array<int, array<string, string>>|array<int, string>)>
*/
public static function provideTestFormSubmitData(): Iterator
{
// Send empty form
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ public function test(RectorRun $rectorRun, string $expectedLink): void
$this->assertStringMatchesFormatFile($expectedLink, $testFixtureLink . PHP_EOL);
}

/**
* @return Iterator<array<int, (RectorRun|string)>>
*/
public static function provideData(): Iterator
{
$rectorRun = DummyRectorRunFactory::create();
Expand Down
3 changes: 3 additions & 0 deletions tests/SmokeTests/ControllerSmokeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ public function test(string $url, int $expectedStatusCode): void
$this->assertSame($expectedStatusCode, $testResponse->getStatusCode(), (string) $testResponse->getContent());
}

/**
* @return Iterator<(array<int, int>|array<int, string>)>
*/
public static function provideData(): Iterator
{
yield ['/', 200];
Expand Down
3 changes: 3 additions & 0 deletions tests/Validator/ErrorMessageNormalizerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public function test(string $errorMessage, string $expectedNormalizedMessage): v
$this->assertSame($expectedNormalizedMessage, $normalizedMessage);
}

/**
* @return Iterator<array<int, string>>
*/
public static function provideDataForTest(): Iterator
{
yield ['message', 'message'];
Expand Down
6 changes: 6 additions & 0 deletions tests/Validator/Rules/HasRectorRule/HasRectorRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ public function test(string $filePath): void
);
}

/**
* @return Iterator<array<int, string>>
*/
public static function provideValidData(): Iterator
{
yield [__DIR__ . '/Fixture/valid/simple_rule.php'];
Expand All @@ -53,6 +56,9 @@ public function testInvalid(string $filePath): void
);
}

/**
* @return Iterator<array<int, string>>
*/
public static function provideInvalidData(): Iterator
{
yield [__DIR__ . '/Fixture/invalid/invalid_config.php'];
Expand Down