Skip to content

Commit

Permalink
Implement AddConstructorParentCallRector (#3430)
Browse files Browse the repository at this point in the history
* Implement AddConstructorParentCallRector

* Move to Strict namespace

* Fix bug when params have same name but different type

* Fix tests
  • Loading branch information
jackbentley committed Mar 9, 2023
1 parent 9d33ac9 commit f87827c
Show file tree
Hide file tree
Showing 10 changed files with 357 additions and 3 deletions.
25 changes: 23 additions & 2 deletions build/target-repository/docs/rector_rules_overview.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 412 Rules Overview
# 413 Rules Overview

<br>

Expand Down Expand Up @@ -60,7 +60,7 @@

- [Restoration](#restoration) (4)

- [Strict](#strict) (5)
- [Strict](#strict) (6)

- [Transform](#transform) (34)

Expand Down Expand Up @@ -7526,6 +7526,27 @@ Rename file to respect class name

## Strict

### AddConstructorParentCallRector

Fixer for PHPStan reports by strict type rule - "PHPStan\Rules\Classes\RequireParentConstructCallRule"

- class: [`Rector\Strict\Rector\ClassMethod\AddConstructorParentCallRector`](../rules/Strict/Rector/ClassMethod/AddConstructorParentCallRector.php)

```diff
class SunshineCommand extends ParentClassWithConstructor
{
- public function __construct()
+ public function __construct(ParentDependency $parentDependency)
{
$value = 5;
+
+ parent::__construct($parentDependency);
}
}
```

<br>

### BooleanInBooleanNotRuleFixerRector

Fixer for PHPStan reports by strict type rule - "PHPStan\Rules\BooleansInConditions\BooleanInBooleanNotRule"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\Strict\Rector\ClassMethod\AddConstructorParentCallRector;

use Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class AddConstructorParentCallRectorTest extends AbstractRectorTestCase
{
#[DataProvider('provideData')]
public function test(string $filePath): void
{
$this->doTestFile($filePath);
}

public static function provideData(): Iterator
{
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Rector\Tests\Strict\Rector\ClassMethod\AddConstructorParentCallRector\Fixture;

use Rector\Tests\Strict\Rector\ClassMethod\AddConstructorParentCallRector\Source\ParentClassWithConstructor;

class SunshineCommand extends ParentClassWithConstructor
{
public function __construct()
{
$value = 5;
}
}

?>
-----
<?php

namespace Rector\Tests\Strict\Rector\ClassMethod\AddConstructorParentCallRector\Fixture;

use Rector\Tests\Strict\Rector\ClassMethod\AddConstructorParentCallRector\Source\ParentClassWithConstructor;

class SunshineCommand extends ParentClassWithConstructor
{
public function __construct(\stdClass $stdClass)
{
$value = 5;
parent::__construct($stdClass);
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Rector\Tests\Strict\Rector\ClassMethod\AddConstructorParentCallRector\Fixture;

use Rector\Tests\Strict\Rector\ClassMethod\AddConstructorParentCallRector\Source\ParentClassWithConstructor;

class SunshineCommand extends ParentClassWithConstructor
{
public function __construct(private \stdClass $stdClass)
{
$value = 5;
}
}

?>
-----
<?php

namespace Rector\Tests\Strict\Rector\ClassMethod\AddConstructorParentCallRector\Fixture;

use Rector\Tests\Strict\Rector\ClassMethod\AddConstructorParentCallRector\Source\ParentClassWithConstructor;

class SunshineCommand extends ParentClassWithConstructor
{
public function __construct(private \stdClass $stdClass)
{
$value = 5;
parent::__construct($stdClass);
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Rector\Tests\Strict\Rector\ClassMethod\AddConstructorParentCallRector\Fixture;

use Rector\Tests\Strict\Rector\ClassMethod\AddConstructorParentCallRector\Source\ParentClassWithConstructor;

class SunshineCommand extends ParentClassWithConstructor
{
public function __construct(private string $stdClass)
{
$value = 5;
}
}

?>
-----
<?php

namespace Rector\Tests\Strict\Rector\ClassMethod\AddConstructorParentCallRector\Fixture;

use Rector\Tests\Strict\Rector\ClassMethod\AddConstructorParentCallRector\Source\ParentClassWithConstructor;

class SunshineCommand extends ParentClassWithConstructor
{
public function __construct(\stdClass $stdClass1, private string $stdClass)
{
$value = 5;
parent::__construct($stdClass1);
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Rector\Tests\DependencyInjection\Rector\ClassMethod\AddMethodParentCallRector\Fixture;

use Rector\Tests\DependencyInjection\Rector\ClassMethod\AddMethodParentCallRector\Source\ParentClassWithNewConstructor;

class SkipAlreadyHas extends ParentClassWithNewConstructor
{
public function __construct(\stdClass $stdClass)
{
$value = 5;

parent::__construct($stdClass);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\Strict\Rector\ClassMethod\AddConstructorParentCallRector\Source;

class ParentClassWithConstructor
{
/**
* @var int
*/
private $defaultValue;

public function __construct(private \stdClass $stdClass)
{
$this->defaultValue = 5;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\Strict\Rector\ClassMethod\AddConstructorParentCallRector;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->rule(AddConstructorParentCallRector::class);
};
123 changes: 123 additions & 0 deletions rules/Strict/Rector/ClassMethod/AddConstructorParentCallRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php

declare(strict_types=1);

namespace Rector\Strict\Rector\ClassMethod;

use PhpParser\Node;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Analyser\Scope;
use Rector\Core\Enum\ObjectReference;
use Rector\Core\NodeManipulator\Dependency\DependencyClassMethodDecorator;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\ValueObject\MethodName;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* Fixer Rector for PHPStan rule:
* https://github.com/phpstan/phpstan-strict-rules/blob/b7dd96a5503919a43b3cd06a2dced9d4252492f2/src/Rules/Classes/RequireParentConstructCallRule.php
*
* @see \Rector\Tests\Strict\Rector\ClassMethod\AddConstructorParentCallRector\AddConstructorParentCallRectorTest
*/
final class AddConstructorParentCallRector extends AbstractRector
{
public function __construct(
private readonly DependencyClassMethodDecorator $dependencyClassMethodDecorator,
) {
}

public function getRuleDefinition(): RuleDefinition
{
$errorMessage = sprintf(
'Fixer for PHPStan reports by strict type rule - "%s"',
'PHPStan\Rules\Classes\RequireParentConstructCallRule'
);
return new RuleDefinition(
$errorMessage,
[
new CodeSample(
<<<'CODE_SAMPLE'
class SunshineCommand extends ParentClassWithConstructor
{
public function __construct()
{
$value = 5;
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
class SunshineCommand extends ParentClassWithConstructor
{
public function __construct(ParentDependency $parentDependency)
{
$value = 5;
parent::__construct($parentDependency);
}
}
CODE_SAMPLE
),
]
);
}

/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [ClassMethod::class];
}

/**
* @param ClassMethod $node
*/
public function refactor(Node $node): ?Node
{
$class = $this->betterNodeFinder->findParentType($node, ClassLike::class);
if (! $class instanceof Class_) {
return null;
}

if (! $this->isName($node, MethodName::CONSTRUCT)) {
return null;
}

$scope = $node->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
return null;
}

if ($this->hasParentCallOfMethod($node)) {
return null;
}

$this->dependencyClassMethodDecorator->decorateConstructorWithParentDependencies($class, $node, $scope);

return $node;
}

/**
* Looks for "parent::__construct"
*/
private function hasParentCallOfMethod(ClassMethod $classMethod): bool
{
return (bool) $this->betterNodeFinder->findFirst((array) $classMethod->stmts, function (Node $node): bool {
if (! $node instanceof StaticCall) {
return false;
}

if (! $this->isName($node->class, ObjectReference::PARENT)) {
return false;
}

return $this->isName($node->name, MethodName::CONSTRUCT);
});
}
}
Loading

0 comments on commit f87827c

Please sign in to comment.