Skip to content

Commit

Permalink
Add SetUpBeforeClassToSetUpRector
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba committed Jul 30, 2024
1 parent 887a20a commit 8acf739
Show file tree
Hide file tree
Showing 9 changed files with 337 additions and 2 deletions.
33 changes: 32 additions & 1 deletion docs/rector_rules_overview.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 54 Rules Overview
# 55 Rules Overview

## AddCoversClassAttributeRector

Expand Down Expand Up @@ -912,6 +912,37 @@ Replace `@test` with prefixed function

<br>

## SetUpBeforeClassToSetUpRector

Change `setUpBeforeClass()` to `setUp()` if not needed

- class: [`Rector\PHPUnit\CodeQuality\Rector\Class_\SetUpBeforeClassToSetUpRector`](../rules/CodeQuality/Rector/Class_/SetUpBeforeClassToSetUpRector.php)

```diff
use PHPUnit\Framework\TestCase;

final class SomeTest extends TestCase
{
- private static $someService;
+ private $someService;

- public static function setUpBeforeClass(): void
+ protected function setUp(): void
{
- self::$someService = new SomeService();
+ $this->someService = new SomeService();
}

public function test()
{
- $result = self::$someService->getValue();
+ $result = $this->someService->getValue();
}
}
```

<br>

## SimplifyForeachInstanceOfRector

Simplify unnecessary foreach check of instances
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\SetUpBeforeClassToSetUpRector\Fixture;

use CodeQuality\Rector\Class_\SetUpBeforeClassToSetUpRector\Source\SomeService;
use PHPUnit\Framework\TestCase;

final class KeepUnrelatedMethod extends TestCase
{
private static $someService;

public static function prepare(): void
{
self::$someService = new SomeService();
}

public function test()
{
$result = self::$someService->getValue();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\SetUpBeforeClassToSetUpRector\Fixture;

use CodeQuality\Rector\Class_\SetUpBeforeClassToSetUpRector\Source\SomeService;
use PHPUnit\Framework\TestCase;

final class SkipOtherProperty extends TestCase
{
private static $otherProperty;

public static function setUpBeforeClass(): void
{
$someService = new SomeService();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\SetUpBeforeClassToSetUpRector\Fixture;

use CodeQuality\Rector\Class_\SetUpBeforeClassToSetUpRector\Source\SomeService;
use PHPUnit\Framework\TestCase;

final class SomeTest extends TestCase
{
private static $someService;

public static function setUpBeforeClass(): void
{
self::$someService = new SomeService();
}

public function test()
{
$result = self::$someService->getValue();
}
}

?>
-----
<?php

namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\SetUpBeforeClassToSetUpRector\Fixture;

use CodeQuality\Rector\Class_\SetUpBeforeClassToSetUpRector\Source\SomeService;
use PHPUnit\Framework\TestCase;

final class SomeTest extends TestCase
{
private $someService;

protected function setUp(): void
{
$this->someService = new SomeService();
}

public function test()
{
$result = $this->someService->getValue();
}
}

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

declare(strict_types=1);

namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\SetUpBeforeClassToSetUpRector;

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

final class SetUpBeforeClassToSetUpRectorTest 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,8 @@
<?php

namespace CodeQuality\Rector\Class_\SetUpBeforeClassToSetUpRector\Source;

final class SomeService
{

}
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\PHPUnit\CodeQuality\Rector\Class_\SetUpBeforeClassToSetUpRector;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->rule(SetUpBeforeClassToSetUpRector::class);
};
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public function refactor(Node $node): ?Node
$possibleTestClassNames = $this->resolveSourceClassNames($className);
$matchingTestClassName = $this->matchExistingClassName($possibleTestClassNames);

if ($matchingTestClassName === null) {
if (! is_string($matchingTestClassName)) {
return null;
}

Expand Down
174 changes: 174 additions & 0 deletions rules/CodeQuality/Rector/Class_/SetUpBeforeClassToSetUpRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
<?php

declare(strict_types=1);

namespace Rector\PHPUnit\CodeQuality\Rector\Class_;

use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticPropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
use Rector\Rector\AbstractRector;
use Rector\ValueObject\MethodName;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* @see \Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\SetUpBeforeClassToSetUpRector\SetUpBeforeClassToSetUpRectorTest
*/
final class SetUpBeforeClassToSetUpRector extends AbstractRector
{
public function __construct(
private readonly TestsNodeAnalyzer $testsNodeAnalyzer,
) {
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Change setUpBeforeClass() to setUp() if not needed', [
new CodeSample(
<<<'CODE_SAMPLE'
use PHPUnit\Framework\TestCase;
final class SomeTest extends TestCase
{
private static $someService;
public static function setUpBeforeClass(): void
{
self::$someService = new SomeService();
}
public function test()
{
$result = self::$someService->getValue();
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
use PHPUnit\Framework\TestCase;
final class SomeTest extends TestCase
{
private $someService;
protected function setUp(): void
{
$this->someService = new SomeService();
}
public function test()
{
$result = $this->someService->getValue();
}
}
CODE_SAMPLE
,
),
]);
}

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

/**
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
$className = $this->getName($node);
if ($className === null) {
return null;
}

if (! $this->testsNodeAnalyzer->isInTestClass($node)) {
return null;
}

$setUpBeforeClassMethod = $node->getMethod(MethodName::SET_UP_BEFORE_CLASS);
if (! $setUpBeforeClassMethod instanceof ClassMethod) {
return null;
}

$changedPropertyNames = [];

// replace static property fetches
$this->traverseNodesWithCallable($setUpBeforeClassMethod, function (Node $node) use (
&$changedPropertyNames
) {
if (! $node instanceof Assign) {
return null;
}

if (! $node->var instanceof StaticPropertyFetch) {
return null;
}

$staticPropertyFetch = $node->var;
if (! $this->isName($staticPropertyFetch->class, 'self')) {
return null;
}

$node->var = new PropertyFetch(new Variable('this'), $staticPropertyFetch->name);
$propertyName = $this->getName($staticPropertyFetch->name);
if (! is_string($propertyName)) {
return null;
}

$changedPropertyNames[] = $propertyName;
});

if ($changedPropertyNames === []) {
return null;
}

// remove static flag
$setUpBeforeClassMethod->flags -= Class_::MODIFIER_STATIC;

// remove public flag
$setUpBeforeClassMethod->flags -= Class_::MODIFIER_PUBLIC;

// make protected
$setUpBeforeClassMethod->flags += Class_::MODIFIER_PROTECTED;

$setUpBeforeClassMethod->name = new Identifier('setUp');

foreach ($node->getProperties() as $property) {
if (! $property->isStatic()) {
continue;
}

if ($this->isNames($property, $changedPropertyNames)) {
$property->flags -= Class_::MODIFIER_STATIC;
}
}

// replace same property access in the class
$this->traverseNodesWithCallable($node->getMethods(), function (Node $node) use (
$changedPropertyNames
): ?PropertyFetch {
if (! $node instanceof StaticPropertyFetch) {
return null;
}

if (! $this->isNames($node, $changedPropertyNames)) {
return null;
}

return new PropertyFetch(new Variable('this'), $node->name);
});

return $node;
}
}

0 comments on commit 8acf739

Please sign in to comment.