Skip to content

Rule php_unit_test_class_requires_covers not working like in Php-Cs-Fixer #4

@clement-gouin

Description

@clement-gouin

Hi, I'm trying to import my rules from Php-Cs-Fixer to ECS.

I've been encountering a weird case that I will try to explain.

TL:DR; The php_unit_test_class_requires_covers rule will detect annotations but not attributes with ECS (and works fine in Php-Cs-Fixer) due to namespace "injection" in lower-cased strings.

Minimal project to reproduce bug

src/MyClass.php

<?php

declare(strict_types=1);

namespace App;

final class MyClass {}

tests/MyClassTest.php

<?php

declare(strict_types=1);

namespace Tests;

use App\MyClass;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;

#[CoversClass(MyClass::class)]
final class MyClassTest extends TestCase {}

composer.json

{
  "name": "app",
  "autoload": {
    "psr-4": {
      "App\\": "src/"
    }
  },
  "autoload-dev": {
    "psr-4": {
      "Tests\\": "tests/"
    }
  },
  "require": {
    "php": ">=8.4"
  },
  "require-dev": {
    "phpunit/phpunit": "^13.0",
    "symplify/easy-coding-standard": "^13.0",
    "friendsofphp/php-cs-fixer": "^3.94"
  }
}

Php-CS-Fixer behavior

.php-cs-fixer.php

<?php

declare(strict_types=1);

use PhpCsFixer\Config;
use PhpCsFixer\Finder;

$finder = new Finder()
    ->in([
        __DIR__ . '/src',
        __DIR__ . '/tests',
    ]);

return new Config()
    ->setRules([
        'php_unit_test_class_requires_covers' => true,
    ])
    ->setFinder($finder);

Result of vendor/bin/php-cs-fixer check

PHP CS Fixer 3.94.2 7th Gear by Fabien Potencier, Dariusz Ruminski and contributors.
PHP runtime: 8.4.18
Loaded config default from "/app/.php-cs-fixer.php".
Running analysis on 15 cores with 10 files per process.
Using cache file ".php-cs-fixer.cache".
 2/2 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

Easy-Coding-Standard behavior

ecs.php

<?php

declare(strict_types=1);

use PhpCsFixer\Fixer\PhpUnit\PhpUnitTestClassRequiresCoversFixer;
use Symplify\EasyCodingStandard\Config\ECSConfig;

return ECSConfig::configure()
    ->withPaths([
        __DIR__ . '/src',
        __DIR__ . '/tests'
    ])
    ->withRules([
        PhpUnitTestClassRequiresCoversFixer::class,
    ])
;

Result of vendor/bin/ecs check

 2/2 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%


1) tests/MyClassTest.php

    ---------- begin diff ----------
@@ -8,5 +8,8 @@
 use PHPUnit\Framework\Attributes\CoversClass;
 use PHPUnit\Framework\TestCase;

+/**
+ * @coversNothing
+ */
 #[CoversClass(MyClass::class)]
 final class MyClassTest extends TestCase {}
    ----------- end diff -----------


Applied checkers:

 * PhpCsFixer\Fixer\PhpUnit\PhpUnitTestClassRequiresCoversFixer
                                                                                                                        
 [WARNING] 1 error is fixable! Just add "--fix" to console command and rerun to apply.                                  

Why I think it happens

The PhpUnitTestClassRequiresCoversFixer checks annotations and attributes by using:

vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.php

        $this->ensureIsDocBlockWithAnnotation(
            $tokens,
            $classIndex,
            'coversNothing',
            [
                'covers',
                'coversDefaultClass',
                'coversNothing',
            ],
            [
                'phpunit\framework\attributes\coversclass',
                'phpunit\framework\attributes\coversnothing',
                'phpunit\framework\attributes\coversmethod',
                'phpunit\framework\attributes\coversfunction',
                'phpunit\framework\attributes\coverstrait',
            ],
        );

which will detect if the strtolower of the requested attributes match the list in DocBlockAnnotationTrait::isPreventedByAttribute

But in the "tempered" version of PhpUnitTestClassRequiresCoversFixer in ECS it becomes:

vendor/symplify/easy-coding-standard/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.php

        $this->ensureIsDocBlockWithAnnotation(
            $tokens,
            $classIndex,
            'coversNothing',
            [
                'covers',
                'coversDefaultClass',
                'coversNothing',
            ],
            [
                'ECSPrefix202601\\phpunit\\framework\\attributes\\coversclass',
                'ECSPrefix202601\\phpunit\\framework\\attributes\\coversnothing',
                'ECSPrefix202601\\phpunit\\framework\\attributes\\coversmethod',
                'ECSPrefix202601\\phpunit\\framework\\attributes\\coversfunction',
                'ECSPrefix202601\\phpunit\\framework\\attributes\\coverstrait',
            ],
        );

Which will never match the attributes.

I think its caused by the prefix-code.sh that mishandle these lines.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions