99 */
1010namespace PHPUnit \Util ;
1111
12- use PHPUnit \Exception ;
12+ use function array_key_last ;
13+ use function array_pop ;
14+ use function count ;
15+ use function preg_match ;
16+ use function preg_quote ;
17+ use function sprintf ;
18+ use function strlen ;
19+ use function substr ;
1320use RuntimeException ;
1421
1522/**
1623 * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
1724 *
1825 * @internal This class is not covered by the backward compatibility promise for PHPUnit
26+ *
1927 * @phpstan-type token array{self::T_*,string}
2028 */
2129final readonly class FileMatcher
2230{
23- private const T_BRACKET_OPEN = 'bracket_open ' ;
24- private const T_BRACKET_CLOSE = 'bracket_close ' ;
25- private const T_BANG = 'bang ' ;
26- private const T_HYPHEN = 'hyphen ' ;
27- private const T_ASTERIX = 'asterix ' ;
28- private const T_SLASH = 'slash ' ;
29- private const T_BACKSLASH = 'backslash ' ;
30- private const T_CHAR = 'char ' ;
31+ private const T_BRACKET_OPEN = 'bracket_open ' ;
32+ private const T_BRACKET_CLOSE = 'bracket_close ' ;
33+ private const T_BANG = 'bang ' ;
34+ private const T_HYPHEN = 'hyphen ' ;
35+ private const T_ASTERIX = 'asterix ' ;
36+ private const T_SLASH = 'slash ' ;
37+ private const T_BACKSLASH = 'backslash ' ;
38+ private const T_CHAR = 'char ' ;
3139 private const T_GREEDY_GLOBSTAR = 'greedy_globstar ' ;
32- private const T_QUERY = 'query ' ;
33- private const T_GLOBSTAR = 'globstar ' ;
34-
40+ private const T_QUERY = 'query ' ;
41+ private const T_GLOBSTAR = 'globstar ' ;
3542
3643 public static function match (string $ path , FileMatcherPattern $ pattern ): bool
3744 {
3845 self ::assertIsAbsolute ($ path );
3946
4047 $ regex = self ::toRegEx ($ pattern ->path );
4148
42- $ result = preg_match ($ regex , $ path ) !== 0 ;
43- return $ result ;
49+ return preg_match ($ regex , $ path ) !== 0 ;
4450 }
4551
4652 /**
47- * Based on webmozart/glob
53+ * Based on webmozart/glob.
4854 *
49- * @return string The regular expression for matching the glob.
55+ * @return string the regular expression for matching the glob
5056 */
5157 public static function toRegEx ($ glob , $ flags = 0 ): string
5258 {
@@ -65,31 +71,31 @@ public static function toRegEx($glob, $flags = 0): string
6571 // literal directory separator
6672 self ::T_SLASH => '/ ' ,
6773 self ::T_QUERY => '. ' ,
68- self ::T_BANG => '^ ' ,
74+ self ::T_BANG => '^ ' ,
6975
7076 // match any segment up until the next directory separator
71- self ::T_ASTERIX => '[^/]* ' ,
77+ self ::T_ASTERIX => '[^/]* ' ,
7278 self ::T_GREEDY_GLOBSTAR => '.* ' ,
73- self ::T_GLOBSTAR => '/([^/]+/)* ' ,
74- self ::T_BRACKET_OPEN => '[ ' ,
75- self ::T_BRACKET_CLOSE => '] ' ,
76- self ::T_HYPHEN => '- ' ,
77- default => '' ,
79+ self ::T_GLOBSTAR => '/([^/]+/)* ' ,
80+ self ::T_BRACKET_OPEN => '[ ' ,
81+ self ::T_BRACKET_CLOSE => '] ' ,
82+ self ::T_HYPHEN => '- ' ,
83+ default => '' ,
7884 };
7985 }
8086 $ regex .= '(/|$) ' ;
8187 dump ($ tokens );
8288 dump ($ regex );
8389
84- return '{^ ' . $ regex. '} ' ;
90+ return '{^ ' . $ regex . '} ' ;
8591 }
8692
8793 private static function assertIsAbsolute (string $ path ): void
8894 {
8995 if (substr ($ path , 0 , 1 ) !== '/ ' ) {
9096 throw new RuntimeException (sprintf (
9197 'Path "%s" must be absolute ' ,
92- $ path
98+ $ path,
9399 ));
94100 }
95101 }
@@ -100,21 +106,21 @@ private static function assertIsAbsolute(string $path): void
100106 private static function tokenize (string $ glob ): array
101107 {
102108 $ length = strlen ($ glob );
103-
109+
104110 $ tokens = [];
105-
106- for ($ i = 0 ; $ i < $ length ; ++ $ i ) {
111+
112+ for ($ i = 0 ; $ i < $ length ; $ i ++ ) {
107113 $ c = $ glob [$ i ];
108-
114+
109115 $ tokens [] = match ($ c ) {
110- '[ ' => [self ::T_BRACKET_OPEN , $ c ],
111- '] ' => [self ::T_BRACKET_CLOSE , $ c ],
112- '? ' => [self ::T_QUERY , $ c ],
113- '- ' => [self ::T_HYPHEN , $ c ],
114- '! ' => [self ::T_BANG , $ c ],
115- '* ' => [self ::T_ASTERIX , $ c ],
116- '/ ' => [self ::T_SLASH , $ c ],
117- '\\' => [self ::T_BACKSLASH , $ c ],
116+ '[ ' => [self ::T_BRACKET_OPEN , $ c ],
117+ '] ' => [self ::T_BRACKET_CLOSE , $ c ],
118+ '? ' => [self ::T_QUERY , $ c ],
119+ '- ' => [self ::T_HYPHEN , $ c ],
120+ '! ' => [self ::T_BANG , $ c ],
121+ '* ' => [self ::T_ASTERIX , $ c ],
122+ '/ ' => [self ::T_SLASH , $ c ],
123+ '\\' => [self ::T_BACKSLASH , $ c ],
118124 default => [self ::T_CHAR , $ c ],
119125 };
120126 }
@@ -124,25 +130,28 @@ private static function tokenize(string $glob): array
124130
125131 /**
126132 * @param list<token> $tokens
133+ *
127134 * @return list<token>
128135 */
129136 private static function processTokens (array $ tokens ): array
130137 {
131138 $ resolved = [];
132- $ escaped = false ;
139+ $ escaped = false ;
133140 $ brackets = [];
134141
135142 for ($ offset = 0 ; $ offset < count ($ tokens ); $ offset ++) {
136143 [$ type , $ char ] = $ tokens [$ offset ];
137144
138145 if ($ type === self ::T_BACKSLASH && false === $ escaped ) {
139146 $ escaped = true ;
147+
140148 continue ;
141149 }
142150
143151 if ($ escaped === true ) {
144152 $ resolved [] = [self ::T_CHAR , $ char ];
145- $ escaped = false ;
153+ $ escaped = false ;
154+
146155 continue ;
147156 }
148157
@@ -155,10 +164,11 @@ private static function processTokens(array $tokens): array
155164
156165 // we eat the two `*` in addition to the slash
157166 $ offset += 3 ;
167+
158168 continue ;
159169 }
160170
161- // greedy globstar (trailing?)
171+ // greedy globstar (trailing?)
162172 // TODO: this should probably only apply at the end of the string according to the webmozart implementation and therefore would be "T_TRAILING_GLOBSTAR"
163173 if (
164174 $ type === self ::T_SLASH &&
@@ -168,43 +178,51 @@ private static function processTokens(array $tokens): array
168178
169179 // we eat the two `*` in addition to the slash
170180 $ offset += 2 ;
181+
171182 continue ;
172183 }
173184
174185 if ($ type === self ::T_ASTERIX && ($ tokens [$ offset + 1 ][0 ] ?? null ) === self ::T_ASTERIX ) {
175186 $ resolved [] = [self ::T_CHAR , $ char ];
176187 $ resolved [] = [self ::T_CHAR , $ char ];
188+
177189 continue ;
178190 }
179191
180192 // complementation - only parse BANG if it is at the start of a character group
181193 if ($ type === self ::T_BANG && isset ($ resolved [array_key_last ($ resolved )]) && $ resolved [array_key_last ($ resolved )][0 ] === self ::T_BRACKET_OPEN ) {
182194 $ resolved [] = [self ::T_BANG , '! ' ];
195+
183196 continue ;
184197 }
185198
186199 // if this was _not_ a bang preceded by a `[` token then convert it
187200 // to a literal char
188201 if ($ type === self ::T_BANG ) {
189202 $ resolved [] = [self ::T_CHAR , $ char ];
203+
190204 continue ;
191205 }
192206
193207 if ($ type === self ::T_BRACKET_OPEN && $ tokens [$ offset + 1 ][0 ] === self ::T_BRACKET_CLOSE ) {
194208 $ resolved [] = [self ::T_BRACKET_OPEN , $ char ];
195209 $ brackets [] = array_key_last ($ resolved );
196210 $ resolved [] = [self ::T_CHAR , $ char ];
211+
197212 continue ;
198213 }
214+
199215 if ($ type === self ::T_BRACKET_OPEN ) {
200216 $ resolved [] = [$ type , $ char ];
201217 $ brackets [] = array_key_last ($ resolved );
218+
202219 continue ;
203220 }
204221
205222 if ($ type === self ::T_BRACKET_CLOSE ) {
206223 array_pop ($ brackets );
207224 $ resolved [] = [$ type , $ char ];
225+
208226 continue ;
209227 }
210228
0 commit comments