Skip to content

Commit b2da2f7

Browse files
committed
Support character groups
1 parent fb031fa commit b2da2f7

File tree

3 files changed

+102
-47
lines changed

3 files changed

+102
-47
lines changed

src/Util/FileMatcher.php

+39-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ public static function match(string $path, FileMatcherPattern $pattern): bool
2424
self::assertIsAbsolute($path);
2525

2626
$regex = self::toRegEx($pattern->path);
27-
dump($pattern->path, $regex, $path);
2827

2928
return preg_match($regex, $path) !== 0;
3029
}
@@ -46,9 +45,26 @@ public static function toRegEx($glob, $flags = 0): string
4645
$c = $glob[$i];
4746

4847
switch ($c) {
48+
case '[':
49+
$regex .= '[';
50+
$inSquare = true;
51+
if (isset($glob[$i + 1]) && '^' === $glob[$i + 1]) {
52+
$regex .= '^';
53+
++$i;
54+
}
55+
break;
56+
case ']':
57+
$regex .= $inSquare ? ']' : '\\]';
58+
$inSquare = false;
59+
break;
4960
case '?':
5061
$regex .= '.';
5162
break;
63+
case '!':
64+
if ($glob[$i - 1] === '[') {
65+
$regex .= '^';
66+
break;
67+
}
5268

5369
// the PHPUnit file iterator will match all
5470
// files within a wildcard, not just until the
@@ -76,13 +92,35 @@ public static function toRegEx($glob, $flags = 0): string
7692
}
7793
$regex .= '/';
7894
break;
95+
case '\\':
96+
if (isset($glob[$i + 1])) {
97+
switch ($glob[$i + 1]) {
98+
case '*':
99+
case '?':
100+
case '[':
101+
case ']':
102+
case '\\':
103+
$regex .= '\\'.$glob[$i + 1];
104+
++$i;
105+
break;
106+
107+
default:
108+
$regex .= '\\\\';
109+
}
110+
} else {
111+
$regex .= '\\\\';
112+
}
113+
break;
114+
79115
default:
80116
$regex .= $c;
81117
break;
82118
}
83119
}
84120

85121
if ($inSquare) {
122+
// do nothing
123+
dump($regex);
86124
throw new InvalidArgumentException(sprintf(
87125
'Invalid glob: missing ] in %s',
88126
$glob
@@ -104,4 +142,3 @@ private static function assertIsAbsolute(string $path): void
104142
}
105143
}
106144
}
107-

tests/unit/Util/FileMatcherTest.php

+59-45
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,12 @@ public function testExceptionIfPathIsNotAbsolute(): void
2929
#[DataProvider('provideQuestionMark')]
3030
#[DataProvider('provideCharacterGroup')]
3131
#[DataProvider('provideRelativePathSegments')]
32-
public function testMatch(FileMatcherPattern $pattern, array $matchMap): void
32+
public function testMatch(FileMatcherPattern $pattern, array $matchMap, ?string $skip = null): void
3333
{
34+
if ($skip) {
35+
self::markTestSkipped($skip);
36+
}
37+
3438
self::assertMap($pattern, $matchMap);
3539
}
3640

@@ -175,8 +179,6 @@ public static function provideGlobstar(): Generator
175179
],
176180
];
177181

178-
// TODO: this edge case
179-
return;
180182
// PHPUnit will match ALL directories within `/foo` with `/foo/A**`
181183
// however it will NOT match anything with `/foo/Aa**`
182184
//
@@ -193,6 +195,7 @@ public static function provideGlobstar(): Generator
193195
'/baz/emm/foo/bad' => false,
194196
'/baz/emm/foo/bad/boo' => false,
195197
],
198+
'PHPUnit edge case',
196199
];
197200
}
198201

@@ -290,6 +293,10 @@ public static function provideQuestionMark(): Generator
290293
*/
291294
public static function provideCharacterGroup(): Generator
292295
{
296+
// TODO: POSIX will interpret an unterminated [ group as a literal while
297+
// Regex will crash -- we'd need to look ahead to see if the [ is
298+
// terminated if we continue using Regex.
299+
//
293300
yield 'unterminated char group' => [
294301
new FileMatcherPattern('/[AB'),
295302
[
@@ -298,6 +305,7 @@ public static function provideCharacterGroup(): Generator
298305
'/[AB' => true,
299306
'/[AB/foo' => true,
300307
],
308+
'Unterminated square bracket',
301309
];
302310
yield 'single char leaf' => [
303311
new FileMatcherPattern('/[A]'),
@@ -310,7 +318,7 @@ public static function provideCharacterGroup(): Generator
310318
new FileMatcherPattern('/a/[B]/c'),
311319
[
312320
'/a' => false,
313-
'/a/B' => true,
321+
'/a/B' => false,
314322
'/a/B/c' => true,
315323
'/a/Z/c' => false,
316324
],
@@ -319,7 +327,7 @@ public static function provideCharacterGroup(): Generator
319327
new FileMatcherPattern('/a/[ABC]/c'),
320328
[
321329
'/a' => false,
322-
'/a/A' => true,
330+
'/a/A' => false,
323331
'/a/B/c' => true,
324332
'/a/C/c' => true,
325333
'/a/Z/c' => false,
@@ -338,7 +346,6 @@ public static function provideCharacterGroup(): Generator
338346
];
339347

340348
// https://man7.org/linux/man-pages/man7/glob.7.html
341-
// example from glob manpage
342349
yield 'square bracket in char group' => [
343350
new FileMatcherPattern('/[][!]'),
344351
[
@@ -349,6 +356,7 @@ public static function provideCharacterGroup(): Generator
349356
'/a' => false,
350357
'/' => false,
351358
],
359+
'Unterminated square bracket 2',
352360
];
353361

354362
yield 'match ranges' => [
@@ -380,8 +388,10 @@ public static function provideCharacterGroup(): Generator
380388
yield 'dash in group' => [
381389
new FileMatcherPattern('/a/[-]/c'),
382390
[
383-
'/a/-' => true,
384-
'/a/-/fo' => true,
391+
'/a/-' => false,
392+
'/a/-/c' => true,
393+
'/a/-/ca/d' => false,
394+
'/a/-/c/da' => true,
385395
'/a/a/fo' => false,
386396
],
387397
];
@@ -390,26 +400,27 @@ public static function provideCharacterGroup(): Generator
390400
new FileMatcherPattern('/a/[-a-c]/c'),
391401
[
392402
'/a/a' => false,
393-
'/a/-' => true,
403+
'/a/-' => false,
404+
'/a/-/c' => true,
394405
'/a/d' => false,
395406
'/a/-b/c' => false,
396-
'/a/a/fo' => true,
397-
'/a/c/fo' => true,
398-
'/a/d/fo' => false,
407+
'/a/a/c/fo' => true,
408+
'/a/c/fo' => false,
409+
'/a/d/c' => false,
399410
],
400411
];
401412

402413
yield 'range infix dash' => [
403414
new FileMatcherPattern('/a/[a-c-e-f]/c'),
404415
[
405416
'/a/a' => false,
406-
'/a/-' => true,
407-
'/a/-/a' => true,
408-
'/a/c/a' => true,
409-
'/a/a/a' => true,
410-
'/a/d/a' => false,
411-
'/a/e/a' => true,
412-
'/a/g/a' => false,
417+
'/a/-/c' => true,
418+
'/a/-/a' => false,
419+
'/a/c/c' => true,
420+
'/a/a/c' => true,
421+
'/a/d/c' => false,
422+
'/a/e/c' => true,
423+
'/a/g/c' => false,
413424
'/a/-/c' => true,
414425
],
415426
];
@@ -418,13 +429,13 @@ public static function provideCharacterGroup(): Generator
418429
new FileMatcherPattern('/a/[a-ce-f-]/c'),
419430
[
420431
'/a/a' => false,
421-
'/a/-' => true,
422-
'/a/-/a' => true,
423-
'/a/c/a' => true,
424-
'/a/a/a' => true,
425-
'/a/d/a' => false,
426-
'/a/e/a' => true,
427-
'/a/g/a' => false,
432+
'/a/-/c' => true,
433+
'/a/-/c' => true,
434+
'/a/c/c' => true,
435+
'/a/a/c' => true,
436+
'/a/d/c' => false,
437+
'/a/e/c' => true,
438+
'/a/g/c' => false,
428439
'/a/-/c' => true,
429440
],
430441
];
@@ -433,30 +444,30 @@ public static function provideCharacterGroup(): Generator
433444
new FileMatcherPattern('/a/[!a]/c'),
434445
[
435446
'/a/a' => false,
436-
'/a/a/b' => false,
437-
'/a/b/b' => true,
438-
'/a/0/b' => true,
439-
'/a/0a/b' => false,
447+
'/a/a/c' => false,
448+
'/a/b/c' => true,
449+
'/a/0/c' => true,
450+
'/a/0a/c' => false,
440451
]
441452
];
442453

443454
yield 'complementation multi char' => [
444455
new FileMatcherPattern('/a/[!abc]/c'),
445456
[
446-
'/a/a/b' => false,
447-
'/a/b/b' => false,
448-
'/a/c/b' => false,
449-
'/a/d/b' => true,
457+
'/a/a/c' => false,
458+
'/a/b/c' => false,
459+
'/a/c/c' => false,
460+
'/a/d/c' => true,
450461
]
451462
];
452463

453464
yield 'complementation range' => [
454465
new FileMatcherPattern('/a/[!a-c]/c'),
455466
[
456-
'/a/a/b' => false,
457-
'/a/b/b' => false,
458-
'/a/c/b' => false,
459-
'/a/d/b' => true,
467+
'/a/a/c' => false,
468+
'/a/b/c' => false,
469+
'/a/c/c' => false,
470+
'/a/d/c' => true,
460471
]
461472
];
462473

@@ -466,7 +477,8 @@ public static function provideCharacterGroup(): Generator
466477
'/a/[!a-c]/c' => true,
467478
'/a/[!a-c]/c/d' => true,
468479
'/b/[!a-c]/c/d' => false,
469-
]
480+
],
481+
'Regex escaping',
470482
];
471483

472484
// TODO: test all the character clases
@@ -479,7 +491,8 @@ public static function provideCharacterGroup(): Generator
479491
'/a/1/c' => true,
480492
'/a/2/c' => true,
481493
'/b/!/c' => false,
482-
]
494+
],
495+
'Named character classes',
483496
];
484497

485498
// TODO: all of these?
@@ -492,7 +505,8 @@ public static function provideCharacterGroup(): Generator
492505
[
493506
'/a/á/c' => true,
494507
'/a/a/c' => false,
495-
]
508+
],
509+
'Collating symbols',
496510
];
497511

498512
// TODO: all of these?
@@ -506,8 +520,8 @@ public static function provideCharacterGroup(): Generator
506520
[
507521
'/a/á/c' => true,
508522
'/a/a/c' => true,
509-
]
510-
523+
],
524+
'Equaivalence class expressions',
511525
];
512526
}
513527

@@ -522,8 +536,8 @@ public static function provideRelativePathSegments(): Generator
522536
[
523537
'/a/a/c' => true,
524538
'/a/b/c' => true,
525-
]
526-
539+
],
540+
'Relative path segments',
527541
];
528542
}
529543
/**

tools/map

+4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#!/usr/bin/env php
22
<?php
33

4+
// This file is temporary to this pull request and it here to provide
5+
// a convenient way to explore the existing behavior.
6+
47
use PHPUnit\TextUI\Configuration\FileCollection;
58
use PHPUnit\TextUI\Configuration\FilterDirectory;
69
use PHPUnit\TextUI\Configuration\FilterDirectoryCollection;
@@ -10,6 +13,7 @@ use PHPUnit\TextUI\Configuration\SourceMapper;
1013
require __DIR__ . '/../vendor/autoload.php';
1114

1215
$dirs = $argv[1];
16+
var_dump($dirs);
1317

1418
$map = (new SourceMapper())->map(new Source(
1519
baseline: null,

0 commit comments

Comments
 (0)