Skip to content

Commit e4a442e

Browse files
authored
Remove the model property from Factories and add extends annotation (#128)
* Remove the model property and add the extends annotation with the model name * Extract AddExtendsAnnotationToModelFactoriesRector and RemoveModelPropertyFromFactoriesRector rules
1 parent b21968e commit e4a442e

12 files changed

+436
-1
lines changed

config/sets/laravel90.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Rector\Renaming\ValueObject\MethodCallRename;
1111
use Rector\Visibility\Rector\ClassMethod\ChangeMethodVisibilityRector;
1212
use Rector\Visibility\ValueObject\ChangeMethodVisibility;
13+
use RectorLaravel\Rector\Class_\AddExtendsAnnotationToModelFactoriesRector;
1314
use RectorLaravel\Rector\PropertyFetch\ReplaceFakerInstanceWithHelperRector;
1415

1516
# see https://laravel.com/docs/9.x/upgrade
@@ -70,6 +71,9 @@
7071
// https://github.com/laravel/framework/commit/7746337149a7ffd6b4a862d9bd54593cf3520708
7172
$rectorConfig->rule(ReplaceFakerInstanceWithHelperRector::class);
7273

74+
// https://github.com/laravel/framework/pull/39169
75+
$rectorConfig->rule(AddExtendsAnnotationToModelFactoriesRector::class);
76+
7377
$rectorConfig
7478
->ruleWithConfiguration(RenameMethodRector::class, [
7579
// https://github.com/laravel/framework/commit/9b4f011fb95c70444812f61d46c8e21fb5b66dd9

docs/rector_rules_overview.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# 37 Rules Overview
1+
# 39 Rules Overview
22

33
## AddArgumentDefaultValueRector
44

@@ -40,6 +40,26 @@ return static function (RectorConfig $rectorConfig): void {
4040

4141
<br>
4242

43+
## AddExtendsAnnotationToModelFactoriesRector
44+
45+
Adds the `@extends` annotation to Factories.
46+
47+
- class: [`RectorLaravel\Rector\Class_\AddExtendsAnnotationToModelFactoriesRector`](../src/Rector/Class_/AddExtendsAnnotationToModelFactoriesRector.php)
48+
49+
```diff
50+
use Illuminate\Database\Eloquent\Factories\Factory;
51+
52+
+/**
53+
+ * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
54+
+ */
55+
class UserFactory extends Factory
56+
{
57+
protected $model = \App\Models\User::class;
58+
}
59+
```
60+
61+
<br>
62+
4363
## AddGenericReturnTypeToRelationsRector
4464

4565
Add generic return type to relations in child of `Illuminate\Database\Eloquent\Model`
@@ -795,6 +815,23 @@ It will removes the dump data just like dd or dump functions from the code.`
795815

796816
<br>
797817

818+
## RemoveModelPropertyFromFactoriesRector
819+
820+
Removes the `$model` property from Factories.
821+
822+
- class: [`RectorLaravel\Rector\Class_\RemoveModelPropertyFromFactoriesRector`](../src/Rector/Class_/RemoveModelPropertyFromFactoriesRector.php)
823+
824+
```diff
825+
use Illuminate\Database\Eloquent\Factories\Factory;
826+
827+
class UserFactory extends Factory
828+
{
829+
- protected $model = \App\Models\User::class;
830+
}
831+
```
832+
833+
<br>
834+
798835
## ReplaceFakerInstanceWithHelperRector
799836

800837
Replace `$this->faker` with the `fake()` helper function in Factories
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace RectorLaravel\Rector\Class_;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Expr;
9+
use PhpParser\Node\Expr\ClassConstFetch;
10+
use PhpParser\Node\Scalar\String_;
11+
use PhpParser\Node\Stmt\Class_;
12+
use PhpParser\Node\Stmt\Property;
13+
use PHPStan\PhpDocParser\Ast\PhpDoc\ExtendsTagValueNode;
14+
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
15+
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
16+
use PHPStan\Type\ObjectType;
17+
use Rector\BetterPhpDocParser\ValueObject\Type\FullyQualifiedIdentifierTypeNode;
18+
use Rector\Core\Rector\AbstractRector;
19+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
20+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
21+
22+
/**
23+
* @changelog https://github.com/laravel/framework/pull/39169
24+
*
25+
* @see \RectorLaravel\Tests\Rector\Class_\AddExtendsAnnotationToModelFactoriesRector\AddExtendsAnnotationToModelFactoriesRectorTest
26+
*/
27+
final class AddExtendsAnnotationToModelFactoriesRector extends AbstractRector
28+
{
29+
private const EXTENDS_TAG_NAME = '@extends';
30+
31+
private const FACTORY_CLASS_NAME = 'Illuminate\Database\Eloquent\Factories\Factory';
32+
33+
public function getRuleDefinition(): RuleDefinition
34+
{
35+
return new RuleDefinition('Adds the @extends annotation to Factories.', [
36+
new CodeSample(
37+
<<<'CODE_SAMPLE'
38+
use Illuminate\Database\Eloquent\Factories\Factory;
39+
40+
class UserFactory extends Factory
41+
{
42+
protected $model = \App\Models\User::class;
43+
}
44+
CODE_SAMPLE
45+
46+
,
47+
<<<'CODE_SAMPLE'
48+
use Illuminate\Database\Eloquent\Factories\Factory;
49+
50+
/**
51+
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
52+
*/
53+
class UserFactory extends Factory
54+
{
55+
protected $model = \App\Models\User::class;
56+
}
57+
CODE_SAMPLE
58+
),
59+
]);
60+
}
61+
62+
/**
63+
* @return array<class-string<Node>>
64+
*/
65+
public function getNodeTypes(): array
66+
{
67+
return [Class_::class];
68+
}
69+
70+
/**
71+
* @param Class_ $node
72+
*/
73+
public function refactor(Node $node): ?Node
74+
{
75+
if (! $this->isObjectType($node, new ObjectType(self::FACTORY_CLASS_NAME))) {
76+
return null;
77+
}
78+
79+
foreach ($node->stmts as $stmt) {
80+
if (! $stmt instanceof Property) {
81+
continue;
82+
}
83+
84+
if (! $this->isName($stmt, 'model')) {
85+
continue;
86+
}
87+
88+
$this->addExtendsPhpDocTag($node, $stmt);
89+
90+
break;
91+
}
92+
93+
return $node;
94+
}
95+
96+
public function addExtendsPhpDocTag(Node $node, Property $property): void
97+
{
98+
if ($property->props === []) {
99+
return;
100+
}
101+
102+
$modelName = $this->getModelName($property->props[0]->default);
103+
104+
if ($modelName === null) {
105+
return;
106+
}
107+
108+
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
109+
110+
if ($phpDocInfo->hasByName(self::EXTENDS_TAG_NAME)) {
111+
return;
112+
}
113+
114+
$phpDocTagNode = new PhpDocTagNode(self::EXTENDS_TAG_NAME, new ExtendsTagValueNode(
115+
new GenericTypeNode(
116+
new FullyQualifiedIdentifierTypeNode(self::FACTORY_CLASS_NAME),
117+
[new FullyQualifiedIdentifierTypeNode($modelName)]
118+
),
119+
''
120+
));
121+
122+
$phpDocInfo->addPhpDocTagNode($phpDocTagNode);
123+
}
124+
125+
private function getModelName(?Expr $defaultProp): ?string
126+
{
127+
if ($defaultProp instanceof ClassConstFetch) {
128+
return $this->getName($defaultProp->class);
129+
}
130+
131+
if ($defaultProp instanceof String_) {
132+
return $defaultProp->value;
133+
}
134+
135+
return null;
136+
}
137+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace RectorLaravel\Rector\Class_;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Stmt\Class_;
9+
use PhpParser\Node\Stmt\Property;
10+
use PHPStan\Type\ObjectType;
11+
use Rector\Core\Rector\AbstractRector;
12+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
13+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
14+
15+
/**
16+
* @changelog https://github.com/laravel/framework/pull/39310
17+
*
18+
* @see \RectorLaravel\Tests\Rector\Class_\RemoveModelPropertyFromFactoriesRector\RemoveModelPropertyFromFactoriesRectorTest
19+
*/
20+
final class RemoveModelPropertyFromFactoriesRector extends AbstractRector
21+
{
22+
public function getRuleDefinition(): RuleDefinition
23+
{
24+
return new RuleDefinition('Removes the $model property from Factories.', [
25+
new CodeSample(
26+
<<<'CODE_SAMPLE'
27+
use Illuminate\Database\Eloquent\Factories\Factory;
28+
29+
class UserFactory extends Factory
30+
{
31+
protected $model = \App\Models\User::class;
32+
}
33+
CODE_SAMPLE
34+
35+
,
36+
<<<'CODE_SAMPLE'
37+
use Illuminate\Database\Eloquent\Factories\Factory;
38+
39+
class UserFactory extends Factory
40+
{
41+
}
42+
CODE_SAMPLE
43+
),
44+
]);
45+
}
46+
47+
/**
48+
* @return array<class-string<Node>>
49+
*/
50+
public function getNodeTypes(): array
51+
{
52+
return [Class_::class];
53+
}
54+
55+
/**
56+
* @param Class_ $node
57+
*/
58+
public function refactor(Node $node): ?Node
59+
{
60+
if (! $this->isObjectType($node, new ObjectType('Illuminate\Database\Eloquent\Factories\Factory'))) {
61+
return null;
62+
}
63+
64+
foreach ($node->stmts as $index => $stmt) {
65+
if (! $stmt instanceof Property) {
66+
continue;
67+
}
68+
69+
if (! $this->isName($stmt, 'model')) {
70+
continue;
71+
}
72+
73+
unset($node->stmts[$index]);
74+
75+
break;
76+
}
77+
78+
return $node;
79+
}
80+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace RectorLaravel\Tests\Rector\Class_\AddExtendsAnnotationToModelFactoriesRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class AddExtendsAnnotationToModelFactoriesRectorTest extends AbstractRectorTestCase
12+
{
13+
#[DataProvider('provideData')]
14+
public function test(string $filePath): void
15+
{
16+
$this->doTestFile($filePath);
17+
}
18+
19+
public static function provideData(): Iterator
20+
{
21+
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
22+
}
23+
24+
public function provideConfigFilePath(): string
25+
{
26+
return __DIR__ . '/config/configured_rule.php';
27+
}
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\Class_\AddExtendsAnnotationToModelFactoriesRector\Fixture;
4+
5+
use Illuminate\Database\Eloquent\Factories\Factory;
6+
7+
class UserFactory extends Factory
8+
{
9+
protected $model = \App\Models\User::class;
10+
}
11+
12+
?>
13+
-----
14+
<?php
15+
16+
namespace RectorLaravel\Tests\Rector\Class_\AddExtendsAnnotationToModelFactoriesRector\Fixture;
17+
18+
use Illuminate\Database\Eloquent\Factories\Factory;
19+
20+
/**
21+
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
22+
*/
23+
class UserFactory extends Factory
24+
{
25+
protected $model = \App\Models\User::class;
26+
}
27+
28+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\Class_\AddExtendsAnnotationToModelFactoriesRector\Fixture;
4+
5+
use Illuminate\Database\Eloquent\Factories\Factory;
6+
7+
/**
8+
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
9+
*/
10+
class UserFactory extends Factory
11+
{
12+
protected $model = \App\Models\User::class;
13+
}
14+
15+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\Class_\AddExtendsAnnotationToModelFactoriesRector\Fixture;
4+
5+
use Illuminate\Database\Eloquent\Factories\Factory;
6+
7+
class UserFactory extends Factory
8+
{
9+
protected $model = 'App\Models\User';
10+
}
11+
12+
?>
13+
-----
14+
<?php
15+
16+
namespace RectorLaravel\Tests\Rector\Class_\AddExtendsAnnotationToModelFactoriesRector\Fixture;
17+
18+
use Illuminate\Database\Eloquent\Factories\Factory;
19+
20+
/**
21+
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
22+
*/
23+
class UserFactory extends Factory
24+
{
25+
protected $model = 'App\Models\User';
26+
}
27+
28+
?>

0 commit comments

Comments
 (0)