Skip to content

Commit f391652

Browse files
authored
feat: PhpUnitAttributesFixer - add option to keep annotations (#8090)
1 parent 8c49ab6 commit f391652

File tree

3 files changed

+135
-23
lines changed

3 files changed

+135
-23
lines changed

doc/rules/php_unit/php_unit_attributes.rst

+42
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,27 @@ Rule ``php_unit_attributes``
44

55
PHPUnit attributes must be used over their respective PHPDoc-based annotations.
66

7+
Configuration
8+
-------------
9+
10+
``keep_annotations``
11+
~~~~~~~~~~~~~~~~~~~~
12+
13+
Whether to keep annotations or not. This may be helpful for projects that
14+
support PHP before version 8 or PHPUnit before version 10.
15+
16+
Allowed types: ``bool``
17+
18+
Default value: ``false``
19+
720
Examples
821
--------
922

1023
Example #1
1124
~~~~~~~~~~
1225

26+
*Default* configuration.
27+
1328
.. code-block:: diff
1429
1530
--- Original
@@ -31,6 +46,33 @@ Example #1
3146
+ #[\PHPUnit\Framework\Attributes\RequiresPhp('8.0')]
3247
public function testSomething($expected, $actual) {}
3348
}
49+
50+
Example #2
51+
~~~~~~~~~~
52+
53+
With configuration: ``['keep_annotations' => true]``.
54+
55+
.. code-block:: diff
56+
57+
--- Original
58+
+++ New
59+
<?php
60+
/**
61+
* @covers \VendorName\Foo
62+
* @internal
63+
*/
64+
+#[\PHPUnit\Framework\Attributes\CoversClass(\VendorName\Foo::class)]
65+
final class FooTest extends TestCase {
66+
/**
67+
* @param int $expected
68+
* @param int $actual
69+
* @dataProvider giveMeSomeData
70+
* @requires PHP 8.0
71+
*/
72+
+ #[\PHPUnit\Framework\Attributes\DataProvider('giveMeSomeData')]
73+
+ #[\PHPUnit\Framework\Attributes\RequiresPhp('8.0')]
74+
public function testSomething($expected, $actual) {}
75+
}
3476
References
3577
----------
3678

src/Fixer/PhpUnit/PhpUnitAttributesFixer.php

+52-22
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
use PhpCsFixer\DocBlock\DocBlock;
1919
use PhpCsFixer\Fixer\AbstractPhpUnitFixer;
2020
use PhpCsFixer\Fixer\AttributeNotation\OrderedAttributesFixer;
21+
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
22+
use PhpCsFixer\Fixer\ConfigurableFixerTrait;
23+
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
24+
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
25+
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
2126
use PhpCsFixer\FixerDefinition\FixerDefinition;
2227
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
2328
use PhpCsFixer\FixerDefinition\VersionSpecification;
@@ -31,9 +36,21 @@
3136

3237
/**
3338
* @author Kuba Werłos <[email protected]>
39+
*
40+
* @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
41+
*
42+
* @phpstan-type _AutogeneratedInputConfiguration array{
43+
* keep_annotations?: bool
44+
* }
45+
* @phpstan-type _AutogeneratedComputedConfiguration array{
46+
* keep_annotations: bool
47+
* }
3448
*/
35-
final class PhpUnitAttributesFixer extends AbstractPhpUnitFixer
49+
final class PhpUnitAttributesFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface
3650
{
51+
/** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
52+
use ConfigurableFixerTrait;
53+
3754
/** @var array<string, string> */
3855
private array $fixingMap;
3956

@@ -45,29 +62,29 @@ public function __construct()
4562

4663
public function getDefinition(): FixerDefinitionInterface
4764
{
65+
$codeSample = <<<'PHP'
66+
<?php
67+
/**
68+
* @covers \VendorName\Foo
69+
* @internal
70+
*/
71+
final class FooTest extends TestCase {
72+
/**
73+
* @param int $expected
74+
* @param int $actual
75+
* @dataProvider giveMeSomeData
76+
* @requires PHP 8.0
77+
*/
78+
public function testSomething($expected, $actual) {}
79+
}
80+
81+
PHP;
82+
4883
return new FixerDefinition(
4984
'PHPUnit attributes must be used over their respective PHPDoc-based annotations.',
5085
[
51-
new VersionSpecificCodeSample(
52-
<<<'PHP'
53-
<?php
54-
/**
55-
* @covers \VendorName\Foo
56-
* @internal
57-
*/
58-
final class FooTest extends TestCase {
59-
/**
60-
* @param int $expected
61-
* @param int $actual
62-
* @dataProvider giveMeSomeData
63-
* @requires PHP 8.0
64-
*/
65-
public function testSomething($expected, $actual) {}
66-
}
67-
68-
PHP,
69-
new VersionSpecification(8_00_00),
70-
),
86+
new VersionSpecificCodeSample($codeSample, new VersionSpecification(8_00_00)),
87+
new VersionSpecificCodeSample($codeSample, new VersionSpecification(8_00_00), ['keep_annotations' => true]),
7188
],
7289
);
7390
}
@@ -87,6 +104,16 @@ public function getPriority(): int
87104
return 8;
88105
}
89106

107+
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
108+
{
109+
return new FixerConfigurationResolver([
110+
(new FixerOptionBuilder('keep_annotations', 'Whether to keep annotations or not. This may be helpful for projects that support PHP before version 8 or PHPUnit before version 10.'))
111+
->setAllowedTypes(['bool'])
112+
->setDefault(false)
113+
->getOption(),
114+
]);
115+
}
116+
90117
protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
91118
{
92119
$classIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_CLASS]]);
@@ -136,7 +163,10 @@ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $en
136163
}
137164

138165
$tokens->insertSlices([$index + 1 => $tokensToInsert]);
139-
$annotation->remove();
166+
167+
if (!$this->configuration['keep_annotations']) {
168+
$annotation->remove();
169+
}
140170
}
141171

142172
if ('' === $docBlock->getContent()) {

tests/Fixer/PhpUnit/PhpUnitAttributesFixerTest.php

+41-1
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,21 @@
2222
* @covers \PhpCsFixer\Fixer\PhpUnit\PhpUnitAttributesFixer
2323
*
2424
* @extends AbstractFixerTestCase<\PhpCsFixer\Fixer\PhpUnit\PhpUnitAttributesFixer>
25+
*
26+
* @phpstan-import-type _AutogeneratedInputConfiguration from \PhpCsFixer\Fixer\PhpUnit\PhpUnitAttributesFixer
2527
*/
2628
final class PhpUnitAttributesFixerTest extends AbstractFixerTestCase
2729
{
2830
/**
2931
* @requires PHP 8.0
3032
*
3133
* @dataProvider provideFixCases
34+
*
35+
* @param _AutogeneratedInputConfiguration $configuration
3236
*/
33-
public function testFix(string $expected, ?string $input = null): void
37+
public function testFix(string $expected, ?string $input = null, array $configuration = []): void
3438
{
39+
$this->fixer->configure($configuration);
3540
$this->doTest($expected, $input);
3641
}
3742

@@ -617,6 +622,41 @@ class TheTest extends \PHPUnit\Framework\TestCase {}
617622
class TheTest extends \PHPUnit\Framework\TestCase {}
618623
PHP,
619624
];
625+
626+
yield 'keep annotations' => [
627+
<<<'PHP'
628+
<?php
629+
class FooTest extends \PHPUnit\Framework\TestCase {
630+
/**
631+
* @copyright ACME Corporation
632+
* @dataProvider provideFooCases
633+
* @requires PHP ^8.2
634+
* @requires OS Linux|Darwin
635+
*/
636+
#[\PHPUnit\Framework\Attributes\DataProvider('provideFooCases')]
637+
#[\PHPUnit\Framework\Attributes\RequiresPhp('^8.2')]
638+
#[\PHPUnit\Framework\Attributes\RequiresOperatingSystem('Linux|Darwin')]
639+
public function testFoo($x) { self::assertTrue($x); }
640+
public static function provideFooCases() { yield [true]; yield [false]; }
641+
}
642+
PHP,
643+
<<<'PHP'
644+
<?php
645+
class FooTest extends \PHPUnit\Framework\TestCase {
646+
/**
647+
* @copyright ACME Corporation
648+
* @dataProvider provideFooCases
649+
* @requires PHP ^8.2
650+
* @requires OS Linux|Darwin
651+
*/
652+
public function testFoo($x) { self::assertTrue($x); }
653+
public static function provideFooCases() { yield [true]; yield [false]; }
654+
}
655+
PHP,
656+
[
657+
'keep_annotations' => true,
658+
],
659+
];
620660
}
621661

622662
/**

0 commit comments

Comments
 (0)