Skip to content

Commit 8394094

Browse files
committed
Add PhpUnitRequiresExplicitConstraintFixer
1 parent 7df724a commit 8394094

File tree

4 files changed

+303
-1
lines changed

4 files changed

+303
-1
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# CHANGELOG for PHP CS Fixer: custom fixers
22

3+
## v3.24.0
4+
- Add PhpUnitRequiresExplicitConstraintFixer
5+
36
## v3.23.0
47
- Add ClassConstantUsageFixer
58

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[![Latest stable version](https://img.shields.io/packagist/v/kubawerlos/php-cs-fixer-custom-fixers.svg?label=current%20version)](https://packagist.org/packages/kubawerlos/php-cs-fixer-custom-fixers)
66
[![PHP version](https://img.shields.io/packagist/php-v/kubawerlos/php-cs-fixer-custom-fixers.svg)](https://php.net)
77
[![License](https://img.shields.io/github/license/kubawerlos/php-cs-fixer-custom-fixers.svg)](LICENSE)
8-
![Tests](https://img.shields.io/badge/tests-3570-brightgreen.svg)
8+
![Tests](https://img.shields.io/badge/tests-3602-brightgreen.svg)
99
[![Downloads](https://img.shields.io/packagist/dt/kubawerlos/php-cs-fixer-custom-fixers.svg)](https://packagist.org/packages/kubawerlos/php-cs-fixer-custom-fixers)
1010

1111
[![CI status](https://github.com/kubawerlos/php-cs-fixer-custom-fixers/actions/workflows/ci.yaml/badge.svg)](https://github.com/kubawerlos/php-cs-fixer-custom-fixers/actions/workflows/ci.yaml)
@@ -463,6 +463,19 @@ PHPUnit `fail`, `markTestIncomplete` and `markTestSkipped` functions must not be
463463
}
464464
```
465465

466+
#### PhpUnitRequiresExplicitConstraintFixer
467+
PHPUnit `@requires` assertion must have explicit version constraint.
468+
```diff
469+
<?php
470+
class FooTest extends TestCase {
471+
/**
472+
- * @requires PHP 8.4
473+
+ * @requires PHP >= 8.4
474+
*/
475+
public function testFoo() {}
476+
}
477+
```
478+
466479
#### PhpdocArrayStyleFixer
467480
Generic array style should be used in PHPDoc.
468481
DEPRECATED: use `phpdoc_array_type` instead.
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php declare(strict_types=1);
2+
3+
/*
4+
* This file is part of PHP CS Fixer: custom fixers.
5+
*
6+
* (c) 2018 Kuba Werłos
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace PhpCsFixerCustomFixers\Fixer;
13+
14+
use PhpCsFixer\FixerDefinition\CodeSample;
15+
use PhpCsFixer\FixerDefinition\FixerDefinition;
16+
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
17+
use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator;
18+
use PhpCsFixer\Preg;
19+
use PhpCsFixer\Tokenizer\Token;
20+
use PhpCsFixer\Tokenizer\Tokens;
21+
22+
final class PhpUnitRequiresExplicitConstraintFixer extends AbstractFixer
23+
{
24+
public function getDefinition(): FixerDefinitionInterface
25+
{
26+
return new FixerDefinition(
27+
'PHPUnit `@requires` assertion must have explicit version constraint.',
28+
[new CodeSample(
29+
<<<'PHP'
30+
<?php
31+
class FooTest extends TestCase {
32+
/**
33+
* @requires PHP 8.4
34+
*/
35+
public function testFoo() {}
36+
}
37+
38+
PHP,
39+
)],
40+
);
41+
}
42+
43+
public function getPriority(): int
44+
{
45+
return 0;
46+
}
47+
48+
public function isCandidate(Tokens $tokens): bool
49+
{
50+
return $tokens->isAllTokenKindsFound([\T_CLASS, \T_EXTENDS, \T_FUNCTION]);
51+
}
52+
53+
public function isRisky(): bool
54+
{
55+
return false;
56+
}
57+
58+
public function fix(\SplFileInfo $file, Tokens $tokens): void
59+
{
60+
$phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator();
61+
62+
/** @var list<int> $indices */
63+
foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indices) {
64+
$this->fixClass($tokens, $indices[0], $indices[1]);
65+
}
66+
}
67+
68+
private function fixClass(Tokens $tokens, int $index, int $endIndex): void
69+
{
70+
while ($index < $endIndex) {
71+
$index = $tokens->getNextTokenOfKind($index, ['{', [\T_FUNCTION]]);
72+
if ($index === null || $index >= $endIndex) {
73+
return;
74+
}
75+
76+
if ($tokens[$index]->equals('{')) {
77+
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
78+
continue;
79+
}
80+
81+
self::fixMethod($tokens, $index);
82+
}
83+
}
84+
85+
private static function fixMethod(Tokens $tokens, int $index): void
86+
{
87+
$phpDocIndex = $tokens->getPrevTokenOfKind($index, [';', [\T_DOC_COMMENT]]);
88+
if ($phpDocIndex === null || !$tokens[$phpDocIndex]->isGivenKind(\T_DOC_COMMENT)) {
89+
return;
90+
}
91+
92+
$tokens[$phpDocIndex] = new Token([
93+
\T_DOC_COMMENT,
94+
95+
Preg::replaceCallback(
96+
'/(@requires\\s+\\S+\\s+)(.+?)(\\s*)$/m',
97+
static function (array $matches): string {
98+
\assert(\is_string($matches[1]));
99+
\assert(\is_string($matches[2]));
100+
\assert(\is_string($matches[3]));
101+
if (Preg::match('/^[\\d\\.-]+(dev|(RC|alpha|beta)[\\d\\.])?$/', $matches[2])) {
102+
$matches[2] = '>= ' . $matches[2];
103+
}
104+
105+
return $matches[1] . $matches[2] . $matches[3];
106+
},
107+
$tokens[$phpDocIndex]->getContent(),
108+
),
109+
]);
110+
}
111+
}
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
<?php declare(strict_types=1);
2+
3+
/*
4+
* This file is part of PHP CS Fixer: custom fixers.
5+
*
6+
* (c) 2018 Kuba Werłos
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace Tests\Fixer;
13+
14+
/**
15+
* @internal
16+
*
17+
* @covers \PhpCsFixerCustomFixers\Fixer\PhpUnitRequiresExplicitConstraintFixer
18+
*/
19+
final class PhpUnitRequiresExplicitConstraintFixerTest extends AbstractFixerTestCase
20+
{
21+
public function testIsRisky(): void
22+
{
23+
self::assertRiskiness(false);
24+
}
25+
26+
/**
27+
* @dataProvider provideFixCases
28+
*/
29+
public function testFix(string $expected, ?string $input = null): void
30+
{
31+
$this->doTest($expected, $input);
32+
}
33+
34+
/**
35+
* @return iterable<array{0: string, 1?: string}>
36+
*/
37+
public static function provideFixCases(): iterable
38+
{
39+
yield 'PHPDoc on variable before class' => [<<<'PHP'
40+
<?php
41+
/**
42+
* The file @requires PHP 8.4
43+
*/
44+
$minPHPVersion = '8.4';
45+
/**
46+
* Foo test starts here
47+
*/
48+
class FooTest extends TestCase {
49+
public function testFoo() {}
50+
}
51+
PHP];
52+
53+
yield 'PHPDoc on property' => [<<<'PHP'
54+
<?php
55+
class FooTest extends TestCase {
56+
/**
57+
* @requires PHP 8.4
58+
*/
59+
private $prop;
60+
public function testFoo() {}
61+
}
62+
PHP];
63+
64+
yield 'anonymous class' => [
65+
<<<'PHP'
66+
<?php
67+
class FooTest extends TestCase {
68+
/**
69+
* @requires PHP >= 8.1
70+
*/
71+
public function testFoo1() {}
72+
public function testFo2o() {
73+
new class extends TestCase {
74+
/**
75+
* @requires PHP 8.2
76+
*/
77+
public function testX() {}
78+
};
79+
}
80+
/**
81+
* @requires PHP >= 8.3
82+
*/
83+
public function testFoo3() {}
84+
}
85+
PHP,
86+
<<<'PHP'
87+
<?php
88+
class FooTest extends TestCase {
89+
/**
90+
* @requires PHP 8.1
91+
*/
92+
public function testFoo1() {}
93+
public function testFo2o() {
94+
new class extends TestCase {
95+
/**
96+
* @requires PHP 8.2
97+
*/
98+
public function testX() {}
99+
};
100+
}
101+
/**
102+
* @requires PHP 8.3
103+
*/
104+
public function testFoo3() {}
105+
}
106+
PHP,
107+
];
108+
109+
foreach (self::getFixCases() as $key => $fixCase) {
110+
yield '[class] ' . $key => \array_map(
111+
static fn (string $case): string => \sprintf(
112+
<<<'PHP'
113+
<?php
114+
%s
115+
class FooTest extends TestCase {
116+
public function testFoo() {}
117+
}
118+
PHP,
119+
$case,
120+
),
121+
$fixCase,
122+
);
123+
yield '[method] ' . $key => \array_map(
124+
static fn (string $case): string => \sprintf(
125+
<<<'PHP'
126+
<?php
127+
class FooTest extends TestCase {
128+
%s
129+
public function testFoo() {}
130+
}
131+
PHP,
132+
$case,
133+
),
134+
$fixCase,
135+
);
136+
}
137+
}
138+
139+
/**
140+
* @return iterable<string, array{0: string, 1?: string}>
141+
*/
142+
private static function getFixCases(): iterable
143+
{
144+
yield 'no PHPDoc' => [''];
145+
146+
yield 'single annotation' => [
147+
'/**
148+
* @requires PHP >= 8.4
149+
*/',
150+
'/**
151+
* @requires PHP 8.4
152+
*/',
153+
];
154+
155+
yield 'annotation with trailing spaces' => [
156+
'/**
157+
* @requires PHP >= 8.4 ' . '
158+
*/',
159+
'/**
160+
* @requires PHP 8.4 ' . '
161+
*/',
162+
];
163+
164+
yield 'multiple annotations' => [
165+
'/**
166+
* @requires PHP >= 7
167+
* @requires PHPUnit >= 11
168+
*/',
169+
'/**
170+
* @requires PHP 7
171+
* @requires PHPUnit 11
172+
*/',
173+
];
174+
}
175+
}

0 commit comments

Comments
 (0)