Skip to content

Commit 777ed98

Browse files
committed
Support attributes
1 parent c28f11e commit 777ed98

File tree

4 files changed

+145
-7
lines changed

4 files changed

+145
-7
lines changed

README.md

Lines changed: 1 addition & 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-3602-brightgreen.svg)
8+
![Tests](https://img.shields.io/badge/tests-3606-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)

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"friendsofphp/php-cs-fixer": "^3.61.1"
1717
},
1818
"require-dev": {
19-
"phpunit/phpunit": "^9.6.22 || 10.5.45 || ^11.5.7"
19+
"phpunit/phpunit": "^9.6.22 || 10.5.45 || ^11.5.7",
20+
"symfony/var-dumper": "^7.2"
2021
},
2122
"autoload": {
2223
"psr-4": {

src/Fixer/PhpUnitRequiresExplicitConstraintFixer.php

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
1717
use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator;
1818
use PhpCsFixer\Preg;
19+
use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis;
20+
use PhpCsFixer\Tokenizer\Analyzer\AttributeAnalyzer;
21+
use PhpCsFixer\Tokenizer\Analyzer\FullyQualifiedNameAnalyzer;
22+
use PhpCsFixer\Tokenizer\CT;
1923
use PhpCsFixer\Tokenizer\Token;
2024
use PhpCsFixer\Tokenizer\Tokens;
2125

@@ -84,14 +88,24 @@ private function fixClass(Tokens $tokens, int $index, int $endIndex): void
8488

8589
private static function fixMethod(Tokens $tokens, int $index): void
8690
{
87-
$phpDocIndex = $tokens->getPrevTokenOfKind($index, [';', [\T_DOC_COMMENT]]);
88-
if ($phpDocIndex === null || !$tokens[$phpDocIndex]->isGivenKind(\T_DOC_COMMENT)) {
91+
$index = $tokens->getPrevTokenOfKind($index, [';', [\T_DOC_COMMENT], [CT::T_ATTRIBUTE_CLOSE]]);
92+
if ($index === null || $tokens[$index]->equals(';')) {
8993
return;
9094
}
9195

92-
$tokens[$phpDocIndex] = new Token([
93-
\T_DOC_COMMENT,
96+
if ($tokens[$index]->isGivenKind(\T_DOC_COMMENT)) {
97+
self::fixPhpDoc($tokens, $index);
98+
}
99+
100+
if ($tokens[$index]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) {
101+
self::fixAttribute($tokens, $index);
102+
}
103+
}
94104

105+
private static function fixPhpDoc(Tokens $tokens, int $index): void
106+
{
107+
$tokens[$index] = new Token([
108+
\T_DOC_COMMENT,
95109
Preg::replaceCallback(
96110
'/(@requires\\s+\\S+\\s+)(.+?)(\\s*)$/m',
97111
static function (array $matches): string {
@@ -104,8 +118,49 @@ static function (array $matches): string {
104118

105119
return $matches[1] . $matches[2] . $matches[3];
106120
},
107-
$tokens[$phpDocIndex]->getContent(),
121+
$tokens[$index]->getContent(),
108122
),
109123
]);
110124
}
125+
126+
private static function fixAttribute(Tokens $tokens, int $index): void
127+
{
128+
$fullyQualifiedNameAnalyzer = new FullyQualifiedNameAnalyzer($tokens);
129+
foreach (AttributeAnalyzer::collect($tokens, $tokens->findBlockStart(Tokens::BLOCK_TYPE_ATTRIBUTE, $index)) as $attributeAnalysis) {
130+
foreach ($attributeAnalysis->getAttributes() as $attribute) {
131+
$attributeName = \strtolower($fullyQualifiedNameAnalyzer->getFullyQualifiedName($attribute['name'], $attribute['start'], NamespaceUseAnalysis::TYPE_CLASS));
132+
if (
133+
$attributeName === 'phpunit\\framework\\attributes\\requiresphp'
134+
|| $attributeName === 'phpunit\\framework\\attributes\\requiresphpunit'
135+
) {
136+
$stringIndex = $tokens->getPrevMeaningfulToken($attribute['end']);
137+
\assert(\is_int($stringIndex));
138+
if (!$tokens[$stringIndex]->isGivenKind(\T_CONSTANT_ENCAPSED_STRING)) {
139+
continue;
140+
}
141+
142+
$openParenthesisIndex = $tokens->getPrevMeaningfulToken($stringIndex);
143+
\assert(\is_int($openParenthesisIndex));
144+
if (!$tokens[$openParenthesisIndex]->equals('(')) {
145+
continue;
146+
}
147+
148+
$quote = \substr($tokens[$stringIndex]->getContent(), -1, 1);
149+
$tokens[$stringIndex] = new Token([
150+
\T_CONSTANT_ENCAPSED_STRING,
151+
$quote . self::fixString(\substr($tokens[$stringIndex]->getContent(), 1, -1)) . $quote,
152+
]);
153+
}
154+
}
155+
}
156+
}
157+
158+
private static function fixString(string $string): string
159+
{
160+
if (Preg::match('/^[\\d\\.-]+(dev|(RC|alpha|beta)[\\d\\.])?$/', $string)) {
161+
return '>= ' . $string;
162+
}
163+
164+
return $string;
165+
}
111166
}

tests/Fixer/PhpUnitRequiresExplicitConstraintFixerTest.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,88 @@ public function testFoo() {}
136136
}
137137
}
138138

139+
/**
140+
* @dataProvider provideFix80Cases
141+
*
142+
* @requires PHP >= 8.0
143+
*/
144+
public function testFix80(string $expected, ?string $input = null): void
145+
{
146+
$this->doTest($expected, $input);
147+
}
148+
149+
/**
150+
* @return iterable<array{0: string, 1?: string}>
151+
*/
152+
public static function provideFix80Cases(): iterable
153+
{
154+
yield 'attribute' => [
155+
'<?php class FooTest extends TestCase {
156+
#[
157+
\\PHPUnit\\Framework\\Attributes\\RequiresPhp(\'>= 8.4\'),
158+
\\PHPUnit\\Framework\\Attributes\\RequiresPhpunit(">= 11")
159+
]
160+
public function testFoo(): void {}
161+
}',
162+
'<?php class FooTest extends TestCase {
163+
#[
164+
\\PHPUnit\\Framework\\Attributes\\RequiresPhp(\'8.4\'),
165+
\\PHPUnit\\Framework\\Attributes\\RequiresPhpunit("11")
166+
]
167+
public function testFoo(): void {}
168+
}',
169+
];
170+
171+
yield 'attribute with float' => [
172+
'<?php class FooTest extends TestCase {
173+
#[
174+
\\PHPUnit\\Framework\\Attributes\\RequiresPhp(8.4),
175+
\\PHPUnit\\Framework\\Attributes\\RequiresPhpunit(">= 11")
176+
]
177+
public function testFoo(): void {}
178+
}',
179+
'<?php class FooTest extends TestCase {
180+
#[
181+
\\PHPUnit\\Framework\\Attributes\\RequiresPhp(8.4),
182+
\\PHPUnit\\Framework\\Attributes\\RequiresPhpunit("11")
183+
]
184+
public function testFoo(): void {}
185+
}',
186+
];
187+
188+
yield 'attribute with concatenation' => [
189+
'<?php class FooTest extends TestCase {
190+
#[
191+
\\PHPUnit\\Framework\\Attributes\\RequiresPhp(">=" . "8.4"),
192+
\\PHPUnit\\Framework\\Attributes\\RequiresPhpunit(">= 11")
193+
]
194+
public function testFoo(): void {}
195+
}',
196+
'<?php class FooTest extends TestCase {
197+
#[
198+
\\PHPUnit\\Framework\\Attributes\\RequiresPhp(">=" . "8.4"),
199+
\\PHPUnit\\Framework\\Attributes\\RequiresPhpunit("11")
200+
]
201+
public function testFoo(): void {}
202+
}',
203+
];
204+
205+
yield 'attributes' => [
206+
'<?php class FooTest extends TestCase {
207+
#[\\LeaveMeAlone("1.2")]
208+
#[\\PHPUnit\\Framework\\Attributes\\RequiresPhp(\'>= 8.4\')]
209+
#[\\PHPUnit\\Framework\\Attributes\\RequiresPhpunit(">= 11")]
210+
public function testFoo(): void {}
211+
}',
212+
'<?php class FooTest extends TestCase {
213+
#[\\LeaveMeAlone("1.2")]
214+
#[\\PHPUnit\\Framework\\Attributes\\RequiresPhp(\'8.4\')]
215+
#[\\PHPUnit\\Framework\\Attributes\\RequiresPhpunit("11")]
216+
public function testFoo(): void {}
217+
}',
218+
];
219+
}
220+
139221
/**
140222
* @return iterable<string, array{0: string, 1?: string}>
141223
*/

0 commit comments

Comments
 (0)