From d43bd254ace7452530bbc09c77f7027e142ba97a Mon Sep 17 00:00:00 2001 From: lacatoire Date: Wed, 15 Apr 2026 22:15:14 +0200 Subject: [PATCH 1/2] Treat leading $variable as a valid @see reference Previously `@see $varname` failed FQSEN resolution and was surfaced as an InvalidTag (and in the PHP 8.0/8.1 versions mentioned in #335 it even aborted parsing). The `See` factory now detects a bare variable identifier and wraps it in a new `Reference\\Variable` alongside the existing `Reference\\Fqsen` and `Reference\\Url`, so global-variable references survive parsing and round-trip. Fixes #335 --- src/DocBlock/Tags/Reference/Variable.php | 38 ++++++++++++++++++++++++ src/DocBlock/Tags/See.php | 6 ++++ tests/unit/DocBlock/Tags/SeeTest.php | 32 ++++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 src/DocBlock/Tags/Reference/Variable.php diff --git a/src/DocBlock/Tags/Reference/Variable.php b/src/DocBlock/Tags/Reference/Variable.php new file mode 100644 index 00000000..32b22c7a --- /dev/null +++ b/src/DocBlock/Tags/Reference/Variable.php @@ -0,0 +1,38 @@ +name = $name; + } + + public function __toString(): string + { + return $this->name; + } +} diff --git a/src/DocBlock/Tags/See.php b/src/DocBlock/Tags/See.php index f8242d47..edcbc160 100644 --- a/src/DocBlock/Tags/See.php +++ b/src/DocBlock/Tags/See.php @@ -18,6 +18,7 @@ use phpDocumentor\Reflection\DocBlock\Tags\Reference\Fqsen as FqsenRef; use phpDocumentor\Reflection\DocBlock\Tags\Reference\Reference; use phpDocumentor\Reflection\DocBlock\Tags\Reference\Url; +use phpDocumentor\Reflection\DocBlock\Tags\Reference\Variable; use phpDocumentor\Reflection\Fqsen; use phpDocumentor\Reflection\FqsenResolver; use phpDocumentor\Reflection\Types\Context as TypeContext; @@ -62,6 +63,11 @@ public static function create( return new static(new Url($parts[0]), $description); } + // Variables are not addressable through an FQSEN but are a valid target for {@}see, e.g. a global `$varname`. + if (preg_match('/^\$[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/', $parts[0])) { + return new static(new Variable($parts[0]), $description); + } + return new static(new FqsenRef(self::resolveFqsen($parts[0], $typeResolver, $context)), $description); } diff --git a/tests/unit/DocBlock/Tags/SeeTest.php b/tests/unit/DocBlock/Tags/SeeTest.php index dcae48b0..6886f669 100644 --- a/tests/unit/DocBlock/Tags/SeeTest.php +++ b/tests/unit/DocBlock/Tags/SeeTest.php @@ -20,6 +20,7 @@ use phpDocumentor\Reflection\DocBlock\Tags\Reference\Fqsen as FqsenRef; use phpDocumentor\Reflection\DocBlock\Tags\Reference\Fqsen as TagsFqsen; use phpDocumentor\Reflection\DocBlock\Tags\Reference\Url as UrlRef; +use phpDocumentor\Reflection\DocBlock\Tags\Reference\Variable as VariableRef; use phpDocumentor\Reflection\Fqsen; use phpDocumentor\Reflection\FqsenResolver; use phpDocumentor\Reflection\Types\Context; @@ -253,6 +254,37 @@ public function testFactoryMethodWithUrl(): void $this->assertSame($description, $fixture->getDescription()); } + /** + * @uses \phpDocumentor\Reflection\DocBlock\Tags\See:: + * @uses \phpDocumentor\Reflection\DocBlock\DescriptionFactory + * @uses \phpDocumentor\Reflection\FqsenResolver + * @uses \phpDocumentor\Reflection\DocBlock\Description + * @uses \phpDocumentor\Reflection\DocBlock\Tags\Reference\Variable + * @uses \phpDocumentor\Reflection\Types\Context + * + * @covers ::create + */ + public function testFactoryMethodWithVariable(): void + { + $descriptionFactory = m::mock(DescriptionFactory::class); + $resolver = m::mock(FqsenResolver::class); + $context = new Context(''); + + $description = new Description('My Description'); + + $descriptionFactory + ->shouldReceive('create')->with('My Description', $context)->andReturn($description); + + $resolver->shouldNotReceive('resolve'); + + $fixture = See::create('$varname My Description', $resolver, $descriptionFactory, $context); + + $this->assertSame('$varname My Description', (string) $fixture); + $this->assertInstanceOf(VariableRef::class, $fixture->getReference()); + $this->assertSame('$varname', (string) $fixture->getReference()); + $this->assertSame($description, $fixture->getDescription()); + } + /** * @uses \phpDocumentor\Reflection\DocBlock\Tags\See:: * @uses \phpDocumentor\Reflection\DocBlock\DescriptionFactory From 0ac77770a24cd27c5052c9b841d1992d3ba9b2d4 Mon Sep 17 00:00:00 2001 From: lacatoire Date: Wed, 15 Apr 2026 22:16:40 +0200 Subject: [PATCH 2/2] Replace invalid inline tag in Reference\\Variable docblock --- src/DocBlock/Tags/Reference/Variable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DocBlock/Tags/Reference/Variable.php b/src/DocBlock/Tags/Reference/Variable.php index 32b22c7a..0cbffd76 100644 --- a/src/DocBlock/Tags/Reference/Variable.php +++ b/src/DocBlock/Tags/Reference/Variable.php @@ -17,7 +17,7 @@ /** * Variable reference used by {@see \phpDocumentor\Reflection\DocBlock\Tags\See} to refer to a variable that - * is not addressable through an FQSEN, typically a global variable such as {@example @see $varname}. + * is not addressable through an FQSEN, typically a global variable referenced with {@}see $varname. */ final class Variable implements Reference {