Skip to content

Commit 9a50a43

Browse files
authored
[code-quality] Add DeclareStrictTypesTestsRector to code-quality test (#566)
* add FinalizeTestCaseClassRector to phpunit code quality * [code-quality] Add DeclareStrictTypesTestsRector * register rule to code quality
1 parent 214103e commit 9a50a43

File tree

7 files changed

+229
-1
lines changed

7 files changed

+229
-1
lines changed

config/sets/phpunit-code-quality.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,10 @@
4646
use Rector\PHPUnit\CodeQuality\Rector\MethodCall\StringCastAssertStringContainsStringRector;
4747
use Rector\PHPUnit\CodeQuality\Rector\MethodCall\UseSpecificWillMethodRector;
4848
use Rector\PHPUnit\CodeQuality\Rector\MethodCall\UseSpecificWithMethodRector;
49+
use Rector\PHPUnit\CodeQuality\Rector\StmtsAwareInterface\DeclareStrictTypesTestsRector;
4950
use Rector\PHPUnit\PHPUnit60\Rector\MethodCall\GetMockBuilderGetMockToCreateMockRector;
5051
use Rector\PHPUnit\PHPUnit90\Rector\MethodCall\ReplaceAtMethodWithDesiredMatcherRector;
52+
use Rector\Privatization\Rector\Class_\FinalizeTestCaseClassRector;
5153

5254
return static function (RectorConfig $rectorConfig): void {
5355
$rectorConfig->rules([
@@ -119,6 +121,8 @@
119121
SingleMockPropertyTypeRector::class,
120122

121123
SetUpBeforeClassToSetUpRector::class,
124+
FinalizeTestCaseClassRector::class,
125+
DeclareStrictTypesTestsRector::class,
122126

123127
// prefer simple mocking
124128
GetMockBuilderGetMockToCreateMockRector::class,
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\StmtsAwareInterface\DeclareStrictTypesTestsRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class DeclareStrictTypesTestsRectorTest 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: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\StmtsAwareInterface\DeclareStrictTypesTestsRector\Fixture;
4+
5+
final class SkipNoTest
6+
{
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\StmtsAwareInterface\DeclareStrictTypesTestsRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
final class SomeTestWithoutStrict extends TestCase
8+
{
9+
public function test()
10+
{
11+
}
12+
}
13+
14+
?>
15+
-----
16+
<?php
17+
18+
declare(strict_types=1);
19+
20+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\StmtsAwareInterface\DeclareStrictTypesTestsRector\Fixture;
21+
22+
use PHPUnit\Framework\TestCase;
23+
24+
final class SomeTestWithoutStrict extends TestCase
25+
{
26+
public function test()
27+
{
28+
}
29+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\PHPUnit\CodeQuality\Rector\StmtsAwareInterface\DeclareStrictTypesTestsRector;
7+
8+
return static function (RectorConfig $rectorConfig): void {
9+
$rectorConfig->rule(DeclareStrictTypesTestsRector::class);
10+
};
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\PHPUnit\CodeQuality\Rector\StmtsAwareInterface;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Stmt;
9+
use PhpParser\Node\Stmt\Class_;
10+
use PhpParser\Node\Stmt\Nop;
11+
use PhpParser\NodeVisitor;
12+
use Rector\ChangesReporting\ValueObject\RectorWithLineChange;
13+
use Rector\Contract\PhpParser\Node\StmtsAwareInterface;
14+
use Rector\Contract\Rector\HTMLAverseRectorInterface;
15+
use Rector\PhpParser\Node\BetterNodeFinder;
16+
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
17+
use Rector\Rector\AbstractRector;
18+
use Rector\TypeDeclaration\NodeAnalyzer\DeclareStrictTypeFinder;
19+
use Rector\ValueObject\Application\File;
20+
use Rector\ValueObject\PhpVersion;
21+
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
22+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
23+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
24+
25+
/**
26+
* @see \Rector\PHPUnit\Tests\CodeQuality\Rector\StmtsAwareInterface\DeclareStrictTypesTestsRector\DeclareStrictTypesTestsRectorTest
27+
*/
28+
final class DeclareStrictTypesTestsRector extends AbstractRector implements HTMLAverseRectorInterface, MinPhpVersionInterface
29+
{
30+
public function __construct(
31+
private readonly DeclareStrictTypeFinder $declareStrictTypeFinder,
32+
private readonly TestsNodeAnalyzer $testsNodeAnalyzer,
33+
private readonly BetterNodeFinder $betterNodeFinder,
34+
) {
35+
}
36+
37+
public function getRuleDefinition(): RuleDefinition
38+
{
39+
return new RuleDefinition('Add `declare(strict_types=1)` to PHPUnit test class file', [
40+
new CodeSample(
41+
<<<'CODE_SAMPLE'
42+
use PHPUnit\Framework\TestCase;
43+
44+
final class SomeTestWithoutStrict extends TestCase
45+
{
46+
public function test()
47+
{
48+
}
49+
}
50+
CODE_SAMPLE
51+
52+
,
53+
<<<'CODE_SAMPLE'
54+
declare(strict_types=1);
55+
56+
use PHPUnit\Framework\TestCase;
57+
58+
final class SomeTestWithoutStrict extends TestCase
59+
{
60+
public function test()
61+
{
62+
}
63+
}
64+
CODE_SAMPLE
65+
),
66+
]);
67+
}
68+
69+
/**
70+
* @param Stmt[] $nodes
71+
* @return Stmt[]|null
72+
*/
73+
public function beforeTraverse(array $nodes): ?array
74+
{
75+
parent::beforeTraverse($nodes);
76+
77+
if ($this->shouldSkipNodes($nodes, $this->file)) {
78+
return null;
79+
}
80+
81+
/** @var Node $rootStmt */
82+
$rootStmt = current($nodes);
83+
84+
// when first stmt is Declare_, verify if there is strict_types definition already,
85+
// as multiple declare is allowed, with declare(strict_types=1) only allowed on very first stmt
86+
if ($this->declareStrictTypeFinder->hasDeclareStrictTypes($rootStmt)) {
87+
return null;
88+
}
89+
90+
if (! $this->hasPHPUnitTestClass($nodes)) {
91+
return null;
92+
}
93+
94+
$rectorWithLineChange = new RectorWithLineChange(self::class, $rootStmt->getStartLine());
95+
$this->file->addRectorClassWithLine($rectorWithLineChange);
96+
97+
return [$this->nodeFactory->createDeclaresStrictType(), new Nop(), ...$nodes];
98+
}
99+
100+
/**
101+
* @return array<class-string<Node>>
102+
*/
103+
public function getNodeTypes(): array
104+
{
105+
return [StmtsAwareInterface::class];
106+
}
107+
108+
/**
109+
* @param StmtsAwareInterface $node
110+
*/
111+
public function refactor(Node $node): int
112+
{
113+
// workaround, as Rector now only hooks to specific nodes, not arrays
114+
// avoid traversing, as we already handled in beforeTraverse()
115+
return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
116+
}
117+
118+
public function provideMinPhpVersion(): int
119+
{
120+
return PhpVersion::PHP_70;
121+
}
122+
123+
/**
124+
* @param Stmt[] $nodes
125+
*/
126+
private function shouldSkipNodes(array $nodes, File $file): bool
127+
{
128+
if ($this->skipper->shouldSkipElementAndFilePath(self::class, $file->getFilePath())) {
129+
return true;
130+
}
131+
132+
if (str_starts_with($file->getFileContent(), '#!')) {
133+
return true;
134+
}
135+
136+
return $nodes === [];
137+
}
138+
139+
/**
140+
* @param Stmt[] $nodes
141+
*/
142+
private function hasPHPUnitTestClass(array $nodes): bool
143+
{
144+
$class = $this->betterNodeFinder->findFirstNonAnonymousClass($nodes);
145+
if (! $class instanceof Class_) {
146+
return false;
147+
}
148+
149+
return $this->testsNodeAnalyzer->isInTestClass($class);
150+
}
151+
}

src/NodeAnalyzer/TestsNodeAnalyzer.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ public function __construct(
3434
public function isInTestClass(Node $node): bool
3535
{
3636
$classReflection = $this->reflectionResolver->resolveClassReflection($node);
37-
3837
if (! $classReflection instanceof ClassReflection) {
3938
return false;
4039
}

0 commit comments

Comments
 (0)