@@ -28,7 +28,10 @@ class TagFilter extends ComplexFilter
2828
2929 public function __construct (string $ filterString )
3030 {
31- $ this ->filterString = trim ($ filterString );
31+ $ filterString = trim ($ filterString );
32+ $ fixedFilterString = $ this ->fixLegacyFilterStringWithoutPrefixes ($ filterString );
33+ // @todo trigger a deprecation here $filterString !== $fixedFilterString
34+ $ this ->filterString = $ fixedFilterString ;
3235
3336 if (preg_match ('/\s/u ' , $ this ->filterString )) {
3437 trigger_error (
@@ -38,6 +41,40 @@ public function __construct(string $filterString)
3841 }
3942 }
4043
44+ /**
45+ * Fix tag expressions where the filter string does not include the `@` prefixes.
46+ *
47+ * e.g. `new TagFilter('wip&&~slow')` rather than `new TagFilter('@wip&&~@slow')`. These were historically
48+ * supported, although not officially, and have been reinstated to solve a BC issue. This syntax will be deprecated
49+ * and removed in future.
50+ */
51+ private function fixLegacyFilterStringWithoutPrefixes (string $ filterString ): string
52+ {
53+ if ($ filterString === '' ) {
54+ return '' ;
55+ }
56+
57+ $ allParts = [];
58+ foreach (explode ('&& ' , $ filterString ) as $ andTags ) {
59+ $ allParts [] = implode (
60+ ', ' ,
61+ array_map (
62+ fn (string $ tag ): string => match (true ) {
63+ // Valid - tag filter contains the `@` prefix
64+ str_starts_with ($ tag , '@ ' ),
65+ str_starts_with ($ tag , '~@ ' ) => $ tag ,
66+ // Invalid / legacy cases - insert the missing `@` prefix in the right place
67+ str_starts_with ($ tag , '~ ' ) => '~@ ' . substr ($ tag , 1 ),
68+ default => '@ ' . $ tag ,
69+ },
70+ explode (', ' , $ andTags ),
71+ ),
72+ );
73+ }
74+
75+ return implode ('&& ' , $ allParts );
76+ }
77+
4178 /**
4279 * Filters feature according to the filter.
4380 *
0 commit comments