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.
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_coversrule 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.phptests/MyClassTest.phpcomposer.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.phpResult of
vendor/bin/php-cs-fixer checkEasy-Coding-Standard behavior
ecs.phpResult of
vendor/bin/ecs check2/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
PhpUnitTestClassRequiresCoversFixerchecks annotations and attributes by using:vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.phpwhich will detect if the
strtolowerof the requested attributes match the list inDocBlockAnnotationTrait::isPreventedByAttributeBut in the "tempered" version of
PhpUnitTestClassRequiresCoversFixerin ECS it becomes:vendor/symplify/easy-coding-standard/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.phpWhich will never match the attributes.
I think its caused by the
prefix-code.shthat mishandle these lines.