Skip to content

AddSeeTestAnnotationRector adds a duplicate @see when the existing annotation is imported #9875

Description

@Brainshaker95

Bug Report

Subject Details
Rector version v2.6.5

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:

https://github.com/rectorphp/rector-phpunit/blob/c4e571ee6d61aa0bf68346bab12b85e2df3aead2/rules/CodeQuality/Rector/Class_/AddSeeTestAnnotationRector.php#L164-L169

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:

namespace App;

use App\Tests\FooTest;

/**
 * @see FooTest
 */
final class Foo {}
 /**
  * @see FooTest
+ * @see \App\Tests\FooTest
  */

Expected Behaviour

Skip the class — the annotation is already there, just imported. Resolving the name against the file's use statements before comparing would do it.

Activity

  1. TomasVotruba commented on Aug 31, 2026

    @TomasVotruba
    Member

    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.

  2. Brainshaker95 commented on Aug 31, 2026

    @Brainshaker95
    Author

    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 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.json

    {
        "name": "acme/see-repro",
        "autoload": {
            "psr-4": {
                "App\\": "src/"
            }
        }
    }

    rector.php

    <?php
    
    declare(strict_types=1);
    
    use Rector\Config\RectorConfig;
    
    return RectorConfig::configure()
        ->withPaths([__DIR__ . '/src'])
        ->withPreparedSets(rectorPreset: true);

    src/Tests/FooTest.php

    <?php
    
    declare(strict_types=1);
    
    namespace App\Tests;
    
    final class FooTest {}

    src/Foo.php

    <?php
    
    declare(strict_types=1);
    
    namespace App;
    
    use App\Tests\FooTest;
    
    /**
     * @see FooTest
     */
    final class Foo {}

    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.

  3. samsonasik commented on Sep 1, 2026

    @samsonasik
    Member
  4. Brainshaker95 commented on Sep 1, 2026

    @Brainshaker95
    Author

    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!

  5. samsonasik commented on Sep 1, 2026

    @samsonasik
    Member

    @Brainshaker95 that's fine. Please try latest "rector/rector": "dev-main" in case there is still issue with it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions