Skip to content

Commit bb9f4c1

Browse files
committed
Enhance FileFilter and StreamWrapper to support vendor directory exclusions in pattern matching
1 parent 33749c0 commit bb9f4c1

2 files changed

Lines changed: 34 additions & 12 deletions

File tree

src/Contract/FileFilter.php

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,16 @@ final class FileFilter
2020
private static array $pathFilterCache = [];
2121

2222
/**
23-
* Pre-compiled include regex patterns and match lengths.
23+
* Pre-compiled include regex patterns, raw patterns, and match lengths.
2424
*
25-
* @var array<int, array{len: int, regex: string}>|null
25+
* @var array<int, array{pattern: string, len: int, regex: string}>|null
2626
*/
2727
private static ?array $compiledIncludes = null;
2828

2929
/**
30-
* Pre-compiled exclude regex patterns and match lengths.
30+
* Pre-compiled exclude regex patterns, raw patterns, and match lengths.
3131
*
32-
* @var array<int, array{len: int, regex: string}>|null
32+
* @var array<int, array{pattern: string, len: int, regex: string}>|null
3333
*/
3434
private static ?array $compiledExcludes = null;
3535

@@ -83,16 +83,26 @@ public static function isFileExcluded(string|false|null $fileName): bool
8383
}
8484

8585
$longestIncludeMatch = 0;
86-
/** @var array<int, array{len: int, regex: string}> $includes */
86+
$isVendorPath = str_contains($normalizedPath, '/vendor/');
87+
88+
/** @var array<int, array{pattern: string, len: int, regex: string}> $includes */
8789
$includes = self::$compiledIncludes;
8890
foreach ($includes as $compiled) {
91+
$isExplicitVendorInclude = str_starts_with($compiled['pattern'], 'vendor/');
92+
$isWildcard = ($compiled['pattern'] === '*' || $compiled['pattern'] === '**');
93+
94+
// Application include rules (like src/**, src/Core/**) never match inside vendor directories
95+
if ($isVendorPath && ! $isExplicitVendorInclude && ! $isWildcard) {
96+
continue;
97+
}
98+
8999
if (preg_match($compiled['regex'], $normalizedPath) === 1) {
90100
$longestIncludeMatch = max($longestIncludeMatch, $compiled['len']);
91101
}
92102
}
93103

94104
$longestExcludeMatch = 0;
95-
/** @var array<int, array{len: int, regex: string}> $excludes */
105+
/** @var array<int, array{pattern: string, len: int, regex: string}> $excludes */
96106
$excludes = self::$compiledExcludes;
97107
foreach ($excludes as $compiled) {
98108
if (preg_match($compiled['regex'], $normalizedPath) === 1) {
@@ -122,6 +132,7 @@ private static function compilePatterns(): void
122132
if (\is_string($pattern)) {
123133
$trimmed = trim($pattern);
124134
self::$compiledIncludes[] = [
135+
'pattern' => $trimmed,
125136
'len' => \strlen($trimmed),
126137
'regex' => self::compileGlobToRegex($trimmed, $baseDir),
127138
];
@@ -133,6 +144,7 @@ private static function compilePatterns(): void
133144
if (\is_string($pattern)) {
134145
$trimmed = trim($pattern);
135146
self::$compiledExcludes[] = [
147+
'pattern' => $trimmed,
136148
'len' => \strlen($trimmed),
137149
'regex' => self::compileGlobToRegex($trimmed, $baseDir),
138150
];
@@ -153,10 +165,10 @@ private static function compileGlobToRegex(string $glob, string $baseDir): strin
153165

154166
if ($isAbsolute) {
155167
$pattern = '^' . $regex . '$';
156-
} elseif (str_starts_with($glob, '**')) {
157-
$pattern = '.*' . substr($regex, 4) . '$';
168+
} elseif ($glob === '*' || $glob === '**' || str_starts_with($glob, '**')) {
169+
$pattern = '.*' . ($glob === '*' || $glob === '**' ? '' : substr($regex, 4)) . '$';
158170
} else {
159-
$pattern = '^' . preg_quote($baseDir . '/', '#') . $regex . '$';
171+
$pattern = '(^' . preg_quote($baseDir . '/', '#') . '|^.*\/)' . $regex . '$';
160172
}
161173

162174
return '#' . $pattern . '#i';

src/Internal/StreamWrapper.php

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -451,10 +451,10 @@ private static function compileGlobToRegex(string $glob): string
451451

452452
if ($isAbsolute) {
453453
$pattern = '^' . $regex . '$';
454-
} elseif (str_starts_with($glob, '**')) {
455-
$pattern = '.*' . substr($regex, 4) . '$';
454+
} elseif ($glob === '*' || $glob === '**' || str_starts_with($glob, '**')) {
455+
$pattern = '.*' . ($glob === '*' || $glob === '**' ? '' : substr($regex, 4)) . '$';
456456
} else {
457-
$pattern = '^' . preg_quote(self::$baseDir . '/', '#') . $regex . '$';
457+
$pattern = '(^' . preg_quote(self::$baseDir . '/', '#') . '|^.*\/)' . $regex . '$';
458458
}
459459

460460
return '#' . $pattern . '#i';
@@ -522,7 +522,17 @@ private static function isApplicationFile(string $path, string|false $resolvedPa
522522
}
523523

524524
$longestIncludeMatch = 0;
525+
$isVendorPath = str_contains($normalizedPath, '/vendor/');
526+
525527
foreach (self::$includeRawPatterns as $pattern => $regex) {
528+
$isExplicitVendorInclude = str_starts_with($pattern, 'vendor/');
529+
$isWildcard = ($pattern === '*' || $pattern === '**');
530+
531+
// Application include rules (like src/**, src/Core/**) never match inside vendor directories
532+
if ($isVendorPath && ! $isExplicitVendorInclude && ! $isWildcard) {
533+
continue;
534+
}
535+
526536
if (preg_match($regex, $normalizedPath) === 1) {
527537
$longestIncludeMatch = max($longestIncludeMatch, \strlen($pattern));
528538
}

0 commit comments

Comments
 (0)