Skip to content

Commit

Permalink
fix: PhpUnitTestClassRequiresCoversFixer - do not add annotation wh…
Browse files Browse the repository at this point in the history
…en there are attributes (#7880)
  • Loading branch information
kubawerlos committed Apr 15, 2024
1 parent 6a69160 commit 8a115cd
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 9 deletions.
64 changes: 61 additions & 3 deletions src/Fixer/AbstractPhpUnitFixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
use PhpCsFixer\DocBlock\DocBlock;
use PhpCsFixer\DocBlock\Line;
use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator;
use PhpCsFixer\Tokenizer\Analyzer\AttributeAnalyzer;
use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer;
use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
Expand Down Expand Up @@ -68,16 +70,22 @@ final protected function getDocBlockIndex(Tokens $tokens, int $index): int
}

/**
* @param list<string> $preventingAnnotations
* @param list<string> $preventingAnnotations
* @param list<class-string> $preventingAttributes
*/
final protected function ensureIsDockBlockWithAnnotation(
final protected function ensureIsDocBlockWithAnnotation(
Tokens $tokens,
int $index,
string $annotation,
array $preventingAnnotations
array $preventingAnnotations,
array $preventingAttributes
): void {
$docBlockIndex = $this->getDocBlockIndex($tokens, $index);

if (self::isPreventedByAttribute($tokens, $index, $preventingAttributes)) {
return;
}

if ($this->isPHPDoc($tokens, $docBlockIndex)) {
$this->updateDocBlockIfNeeded($tokens, $docBlockIndex, $annotation, $preventingAnnotations);
} else {
Expand Down Expand Up @@ -136,6 +144,56 @@ private function updateDocBlockIfNeeded(
$tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]);
}

/**
* @param list<class-string> $preventingAttributes
*/
private static function isPreventedByAttribute(Tokens $tokens, int $index, array $preventingAttributes): bool
{
if ([] === $preventingAttributes) {
return false;
}

$attributeIndex = $tokens->getPrevMeaningfulToken($index);
if (!$tokens[$attributeIndex]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) {
return false;
}
$attributeIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ATTRIBUTE, $attributeIndex);

foreach (AttributeAnalyzer::collect($tokens, $attributeIndex) as $attributeAnalysis) {
foreach ($attributeAnalysis->getAttributes() as $attribute) {
if (\in_array(self::getFullyQualifiedName($tokens, $attribute['name']), $preventingAttributes, true)) {
return true;
}
}
}

return false;
}

private static function getFullyQualifiedName(Tokens $tokens, string $name): string
{
$name = strtolower($name);

$names = [];
foreach ((new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens) as $namespaceUseAnalysis) {
$names[strtolower($namespaceUseAnalysis->getShortName())] = strtolower($namespaceUseAnalysis->getFullName());
}

foreach ($names as $shortName => $fullName) {
if ($name === $shortName) {
return $fullName;
}

if (!str_starts_with($name, $shortName.'\\')) {
continue;
}

return $fullName.substr($name, \strlen($shortName));
}

return $name;
}

/**
* @return list<Line>
*/
Expand Down
5 changes: 3 additions & 2 deletions src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,12 @@ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $en
return;
}

$this->ensureIsDockBlockWithAnnotation(
$this->ensureIsDocBlockWithAnnotation(
$tokens,
$classIndex,
'internal',
['internal']
['internal'],
[],
);
}

Expand Down
5 changes: 3 additions & 2 deletions src/Fixer/PhpUnit/PhpUnitSizeClassFixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,12 @@ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $en
return;
}

$this->ensureIsDockBlockWithAnnotation(
$this->ensureIsDocBlockWithAnnotation(
$tokens,
$classIndex,
$this->configuration['group'],
self::SIZES
self::SIZES,
[],
);
}

Expand Down
8 changes: 6 additions & 2 deletions src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,19 @@ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $en
return; // don't add `@covers` annotation for abstract base classes
}

$this->ensureIsDockBlockWithAnnotation(
$this->ensureIsDocBlockWithAnnotation(
$tokens,
$classIndex,
'coversNothing',
[
'covers',
'coversDefaultClass',
'coversNothing',
]
],
[
'phpunit\\framework\\attributes\\coversclass',
'phpunit\\framework\\attributes\\coversnothing',
],
);
}
}
89 changes: 89 additions & 0 deletions tests/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,95 @@ class FooTest extends \PHPUnit_Framework_TestCase {}
];
}

/**
* @dataProvider provideFix80Cases
*
* @requires PHP 8.0
*/
public function testFix80(string $expected, ?string $input = null): void
{
$this->doTest($expected, $input);
}

/**
* @return iterable<array{0: string, 1?: string}>
*/
public static function provideFix80Cases(): iterable
{
yield 'already with attribute CoversClass' => [
<<<'PHP'
<?php
#[PHPUnit\Framework\Attributes\CoversClass(Foo::class)]
class FooTest extends \PHPUnit_Framework_TestCase {}
PHP,
];

yield 'already with attribute CoversNothing' => [
<<<'PHP'
<?php
#[PHPUnit\Framework\Attributes\CoversNothing]
class FooTest extends \PHPUnit_Framework_TestCase {}
PHP,
];

yield 'already with imported attribute' => [
<<<'PHP'
<?php
use PHPUnit\Framework\TestCas;
use PHPUnit\Framework\Attributes\CoversClass;
#[CoversClass(Foo::class)]
class FooTest extends TestCas {}
PHP,
];

yield 'already with partially imported attribute' => [
<<<'PHP'
<?php
use PHPUnit\Framework\Attributes;
#[Attributes\CoversClass(Foo::class)]
class FooTest extends \PHPUnit_Framework_TestCase {}
PHP,
];

yield 'already with aliased attribute' => [
<<<'PHP'
<?php
use PHPUnit\Framework\Attributes\CoversClass as PHPUnitCoversClass;
#[PHPUnitCoversClass(Foo::class)]
class FooTest extends \PHPUnit_Framework_TestCase {}
PHP,
];

yield 'already with partially aliased attribute' => [
<<<'PHP'
<?php
use PHPUnit\Framework\Attributes as PHPUnitAttributes;
#[PHPUnitAttributes\CoversClass(Foo::class)]
class FooTest extends \PHPUnit_Framework_TestCase {}
PHP,
];

yield 'with attribute from different namespace' => [
<<<'PHP'
<?php
use Foo\CoversClass;
use PHPUnit\Framework\Attributes\CoversClass as PHPUnitCoversClass;
/**
* @coversNothing
*/
#[CoversClass(Foo::class)]
class FooTest extends \PHPUnit_Framework_TestCase {}
PHP,
<<<'PHP'
<?php
use Foo\CoversClass;
use PHPUnit\Framework\Attributes\CoversClass as PHPUnitCoversClass;
#[CoversClass(Foo::class)]
class FooTest extends \PHPUnit_Framework_TestCase {}
PHP,
];
}

/**
* @dataProvider provideFix82Cases
*
Expand Down

0 comments on commit 8a115cd

Please sign in to comment.