You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
AddSeeTestAnnotationRector adds a duplicate @see when the existing annotation is imported #9875
AddSeeTestAnnotationRector adds a second @see annotation when the first one is imported rather than fully qualified.
The rule writes @see \App\Tests\FooTest. php-cs-fixer's fully_qualified_strict_types, which PER-CS enables, then shortens that to @see FooTest and adds a use. On the next run the rule no longer recognizes its own annotation and appends another, so the count grows on every run.
hasAlreadySeeAnnotation() compares the raw docblock text against the fully qualified name, so the short form never matches:
This was reported once before in #9795 and closed because the reporter's test classes turned out to be in the wrong namespace, so the reproduction never held up. The one below does — the test class is found, which is what makes the rule add the duplicate.
Minimal PHP Code Causing Issue
With App\Tests\FooTest present and rectorPreset: true:
matchExistingClassName() goes through ReflectionProvider::hasClass(), so the test class has to be autoloadable. The demo runs one snippet with no autoloader, so there is no FooTest to find and it returns null every time. Any demo link is going to say "no change" no matter what I put in it.
Smallest thing I could get it to fail on — four files, 2.6.5:
composer dump-autoload && vendor/bin/rector process --dry-run gives:
/**
* @see FooTest
+ * @see \App\Tests\FooTest
*/
final class Foo {}
The tag is already there, just imported. hasAlreadySeeAnnotation() compares the raw docblock text to '\\' . $testCaseClassName, so FooTest never equals \App\Tests\FooTest and it adds a second one.
The fallback in shouldSkipClass() misses it too, since hasClass('FooTest') is false for the short name.
I suspect that's what happened in #9795 as well — if the test class isn't autoloadable under one of the names the resolver generates, the rule quietly does nothing and looks fixed.
Worth noting it compounds: php-cs-fixer's fully_qualified_strict_types imports @see targets by default, so on a project running both, every run leaves one more tag behind.
Sorry, i don't know what I did wrong, since it wasn't able to reproduce it in the dmeo myself. Thanks @samsonasik for helping out and providing the fix!
Activity
TomasVotruba commentedon Aug 31, 2026
Thank you for your report!
We'll need an isolated failing demo link from: http://getrector.com/demo,
that way we can reproduce the bug.
Brainshaker95 commentedon Aug 31, 2026
The demo can't show this one. The rule bails out before it gets anywhere near the broken part:
https://github.com/rectorphp/rector-phpunit/blob/c4e571ee6d61aa0bf68346bab12b85e2df3aead2/rules/CodeQuality/Rector/Class_/AddSeeTestAnnotationRector.php#L96-L99
matchExistingClassName()goes throughReflectionProvider::hasClass(), so the test class has to be autoloadable. The demo runs one snippet with no autoloader, so there is noFooTestto find and it returns null every time. Any demo link is going to say "no change" no matter what I put in it.Smallest thing I could get it to fail on — four files, 2.6.5:
composer.json
{ "name": "acme/see-repro", "autoload": { "psr-4": { "App\\": "src/" } } }rector.php
src/Tests/FooTest.php
src/Foo.php
composer dump-autoload && vendor/bin/rector process --dry-rungives:/** * @see FooTest + * @see \App\Tests\FooTest */ final class Foo {}The tag is already there, just imported.
hasAlreadySeeAnnotation()compares the raw docblock text to'\\' . $testCaseClassName, soFooTestnever equals\App\Tests\FooTestand it adds a second one.The fallback in
shouldSkipClass()misses it too, sincehasClass('FooTest')is false for the short name.I suspect that's what happened in #9795 as well — if the test class isn't autoloadable under one of the names the resolver generates, the rule quietly does nothing and looks fixed.
Worth noting it compounds: php-cs-fixer's
fully_qualified_strict_typesimports@seetargets by default, so on a project running both, every run leaves one more tag behind.samsonasik commentedon Sep 1, 2026
Reproduced at https://getrector.com/demo/6e45b252-53d3-4331-9904-7f684490d544
samsonasik commentedon Sep 1, 2026
I created PR:
for it.
Brainshaker95 commentedon Sep 1, 2026
Sorry, i don't know what I did wrong, since it wasn't able to reproduce it in the dmeo myself. Thanks @samsonasik for helping out and providing the fix!
samsonasik commentedon Sep 1, 2026
@Brainshaker95 that's fine. Please try latest
"rector/rector": "dev-main"in case there is still issue with it.