Skip to content

fix: detect PHPUnit #[Test] attribute with fully-qualified name#121

Open
zoispag wants to merge 1 commit into
zed-extensions:mainfrom
zoispag:fix/phpunit-fqdn-attribute-runnable
Open

fix: detect PHPUnit #[Test] attribute with fully-qualified name#121
zoispag wants to merge 1 commit into
zed-extensions:mainfrom
zoispag:fix/phpunit-fqdn-attribute-runnable

Conversation

@zoispag
Copy link
Copy Markdown

@zoispag zoispag commented May 12, 2026

Problem

When a PHPUnit test method is annotated with the fully-qualified attribute form:

#[\PHPUnit\Framework\Attributes\Test]
public function it_does_something(): void { ... }

Zed does not show the ▷ gutter play button, so the test cannot be run from the editor. Only the short imported form #[Test] triggers the runnable.

Root cause

The runnables.scm query for the #[Test] case only matches a simple name node inside the attribute:

(attribute (name) @_attribute)
(#eq? @_attribute "Test")

For a fully-qualified attribute, the tree-sitter PHP grammar produces a qualified_name node instead of a bare name. According to the grammar definition, qualified_name has the structure:

(qualified_name
  prefix: (... \PHPUnit\Framework\Attributes\ ...)
  (name) "Test"   <-- direct child, NOT inside the prefix
)

The namespace segments (PHPUnit, Framework, Attributes) are nested inside the prefix field, while the class name (Test) is a direct name child of qualified_name. The existing query never reaches this node.

Fix

Add an alternative branch (qualified_name (name) @_attribute) inside the attribute match so both the short and fully-qualified forms are handled:

(attribute
    [
        (name) @_attribute
        (qualified_name (name) @_attribute)
    ]
)
(#eq? @_attribute "Test")

This covers:

  • #[Test] — short form (existing, unchanged)
  • #[PHPUnit\Framework\Attributes\Test] — relative qualified form
  • #[\PHPUnit\Framework\Attributes\Test] — fully-qualified form (global namespace prefix)

Extends the phpunit-test runnable query to also match methods annotated
with a fully-qualified attribute such as:

    #[\PHPUnit\Framework\Attributes\Test]

Previously only the short form `#[Test]` was matched, because the query
only handled `(attribute (name))`. For a FQDN attribute the tree-sitter
node is a `qualified_name` whose direct `name` child is the class name
("Test"); the namespace segments live inside the nested `prefix` field
and are not matched.

The fix adds an alternative branch `(qualified_name (name) @_attribute)`
alongside the existing `(name) @_attribute` inside the attribute match,
so both forms produce the gutter play button.
@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented May 12, 2026

We require contributors to sign our Contributor License Agreement, and we don't have @zoispag on file. You can sign our CLA at https://zed.dev/cla. Once you've signed, post a comment here that says '@cla-bot check'.

@zoispag
Copy link
Copy Markdown
Author

zoispag commented May 12, 2026

@cla-bot check

@cla-bot cla-bot Bot added the cla-signed label May 12, 2026
@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented May 12, 2026

The cla-bot has been summoned, and re-checked this pull request!

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant