Skip to content

Commit a3d7045

Browse files
committed
Improve PHPUnit runnable detection
- Require a base_clause on the name-convention (*Test) patterns: a PHPUnit test class always extends TestCase (directly or transitively), so a *Test class with no `extends` is not PHPUnit and is no longer tagged phpunit-test (it is most likely a test from another framework). - Disambiguate method-level #[Test] by the imported attribute (PHPUnit\Framework\Attributes\Test) via the file's `use`, and by a fully-qualified #[\PHPUnit\Framework\Attributes\Test], instead of the class name — so it no longer misfires on another framework's method-level #[Test]. This supersedes zed-extensions#121, which matched any attribute whose last name segment is "Test" (and would also tag e.g. #[\Testo\Test] or #[\App\Test] as PHPUnit).
1 parent 4121451 commit a3d7045

1 file changed

Lines changed: 73 additions & 24 deletions

File tree

languages/php/runnables.scm

Lines changed: 73 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
; Class that follow the naming convention of PHPUnit test classes
22
; and that doesn't have the abstract modifier
3+
; and extends a base class (PHPUnit test classes always inherit from TestCase,
4+
; directly or transitively; a *Test class with no `extends` at all is not
5+
; PHPUnit — most likely a Testo test — so requiring a base_clause avoids
6+
; tagging those as phpunit-test)
37
; and have a method that follow the naming convention of PHPUnit test methods
48
; and the method is public
59
(
@@ -9,6 +13,7 @@
913
.
1014
name: (_) @_name
1115
(#match? @_name ".*Test$")
16+
(base_clause)
1217
body: (declaration_list
1318
(method_declaration
1419
(visibility_modifier)? @_visibility
@@ -23,6 +28,8 @@
2328

2429
; Class that follow the naming convention of PHPUnit test classes
2530
; and that doesn't have the abstract modifier
31+
; and extends a base class (see note above — filters out inheritance-less
32+
; Testo classes)
2633
; and have a method that has the @test annotation
2734
; and the method is public
2835
(
@@ -32,6 +39,7 @@
3239
.
3340
name: (_) @_name
3441
(#match? @_name ".*Test$")
42+
(base_clause)
3543
body: (declaration_list
3644
((comment) @_comment
3745
(#match? @_comment ".*@test\\b.*")
@@ -47,44 +55,85 @@
4755
(#set! tag phpunit-test)
4856
)
4957

50-
; Class that follow the naming convention of PHPUnit test classes
51-
; and that doesn't have the abstract modifier
52-
; and have a method that has the #[Test] attribute
53-
; and the method is public
58+
; Method carrying the #[Test] attribute, disambiguated to PHPUnit via the
59+
; file's `use` import. Both PHPUnit (PHPUnit\Framework\Attributes\Test) and
60+
; Testo (Testo\Test) expose a method-level #[Test]; since PHP forbids two
61+
; imports sharing an alias, the presence of `use PHPUnit\Framework\Attributes\Test`
62+
; proves the attribute is PHPUnit's. This replaces the old class-name (*Test)
63+
; gate so that a Testo file's #[Test] methods are no longer tagged phpunit-test.
64+
; (Trade-off: a PHPUnit file importing the attribute via a group use or writing
65+
; it fully-qualified won't match here — it still gets class-level buttons.)
66+
;
67+
; Form 1: no namespace, or `namespace X;` — `use` and class are siblings.
5468
(
55-
(class_declaration
56-
(_)* @_modifier
57-
(#not-any-eq? @_modifier "abstract")
58-
.
59-
name: (_) @_name
60-
(#match? @_name ".*Test$")
61-
body: (declaration_list
62-
(method_declaration
63-
(attribute_list
64-
(attribute_group
65-
(attribute (name) @_attribute)
66-
)
67-
)
68-
(#eq? @_attribute "Test")
69-
(visibility_modifier)? @_visibility
70-
(#eq? @_visibility "public")
71-
name: (_) @run
72-
(#not-match? @run "^test.*")
73-
)
74-
)
69+
(program
70+
(namespace_use_declaration
71+
(namespace_use_clause (qualified_name) @_use))
72+
(#eq? @_use "PHPUnit\\Framework\\Attributes\\Test")
73+
(class_declaration
74+
body: (declaration_list
75+
(method_declaration
76+
attributes: (attribute_list
77+
(attribute_group
78+
(attribute (name) @_attribute)))
79+
(#eq? @_attribute "Test")
80+
(visibility_modifier)? @_visibility
81+
(#eq? @_visibility "public")
82+
name: (_) @run
83+
(#not-match? @run "^test.*"))))
84+
) @_phpunit-test
85+
(#set! tag phpunit-test)
86+
)
87+
88+
; Form 2: braced `namespace X { ... }`.
89+
(
90+
(namespace_definition
91+
body: (compound_statement
92+
(namespace_use_declaration
93+
(namespace_use_clause (qualified_name) @_use))
94+
(#eq? @_use "PHPUnit\\Framework\\Attributes\\Test")
95+
(class_declaration
96+
body: (declaration_list
97+
(method_declaration
98+
attributes: (attribute_list
99+
(attribute_group
100+
(attribute (name) @_attribute)))
101+
(#eq? @_attribute "Test")
102+
(visibility_modifier)? @_visibility
103+
(#eq? @_visibility "public")
104+
name: (_) @run
105+
(#not-match? @run "^test.*")))))
75106
) @_phpunit-test
76107
(#set! tag phpunit-test)
77108
)
78109

79110
; Class that follow the naming convention of PHPUnit test classes
80111
; and that doesn't have the abstract modifier
112+
; and extends a base class (see note above — filters out inheritance-less
113+
; Testo classes)
81114
(
82115
(class_declaration
83116
(_)* @_modifier
84117
(#not-any-eq? @_modifier "abstract")
85118
.
86119
name: (_) @run
87120
(#match? @run ".*Test$")
121+
(base_clause)
122+
) @_phpunit-test
123+
(#set! tag phpunit-test)
124+
)
125+
126+
; Method carrying a fully-qualified `#[\PHPUnit\Framework\Attributes\Test]`
127+
; attribute — self-identifying, so no `use` correlation is needed.
128+
(
129+
(method_declaration
130+
attributes: (attribute_list
131+
(attribute_group
132+
(attribute (qualified_name) @_attribute)))
133+
(#eq? @_attribute "\\PHPUnit\\Framework\\Attributes\\Test")
134+
(visibility_modifier)? @_visibility
135+
(#eq? @_visibility "public")
136+
name: (_) @run
88137
) @_phpunit-test
89138
(#set! tag phpunit-test)
90139
)

0 commit comments

Comments
 (0)