Skip to content

Commit a198239

Browse files
committed
Enhance vendor path handling in FileFilter and StreamWrapper to support explicit vendor whitelisting and improve test isolation with reset methods in Config and StreamWrapper.
1 parent 12077df commit a198239

3 files changed

Lines changed: 51 additions & 8 deletions

File tree

src/Contract/FileFilter.php

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,31 @@ public static function isFileExcluded(string|false|null $fileName): bool
8282
self::compilePatterns();
8383
}
8484

85-
$longestIncludeMatch = 0;
8685
$isVendorPath = str_contains($normalizedPath, '/vendor/');
86+
if ($isVendorPath) {
87+
$hasExplicitVendorWhitelist = false;
88+
/** @var array<int, array{pattern: string, len: int, regex: string}> $includes */
89+
$includes = self::$compiledIncludes;
90+
foreach ($includes as $compiled) {
91+
if (str_starts_with($compiled['pattern'], 'vendor/') && preg_match($compiled['regex'], $normalizedPath) === 1) {
92+
$hasExplicitVendorWhitelist = true;
93+
94+
break;
95+
}
96+
}
97+
98+
if (! $hasExplicitVendorWhitelist) {
99+
return self::$pathFilterCache[$normalizedPath] = true; // Instantly exclude!
100+
}
101+
}
87102

103+
$longestIncludeMatch = 0;
88104
/** @var array<int, array{pattern: string, len: int, regex: string}> $includes */
89105
$includes = self::$compiledIncludes;
90106
foreach ($includes as $compiled) {
91107
$isExplicitVendorInclude = str_starts_with($compiled['pattern'], 'vendor/');
92108
$isWildcard = ($compiled['pattern'] === '*' || $compiled['pattern'] === '**');
93109

94-
// Application include rules (like src/**, src/Core/**) never match inside vendor directories
95110
if ($isVendorPath && ! $isExplicitVendorInclude && ! $isWildcard) {
96111
continue;
97112
}
@@ -173,4 +188,4 @@ private static function compileGlobToRegex(string $glob, string $baseDir): strin
173188

174189
return '#' . $pattern . '#i';
175190
}
176-
}
191+
}

src/Internal/Config.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ final class Config
2323
*/
2424
private static ?array $cachedConfig = null;
2525

26+
/**
27+
* Cached absolute project root path.
28+
*/
2629
private static ?string $projectRoot = null;
2730

2831
private static bool $enabled = true;
@@ -208,6 +211,7 @@ public static function set(array $config): void
208211

209212
ContractParser::reset();
210213
FileFilter::reset();
214+
StreamWrapper::reset();
211215
}
212216

213217
/**
@@ -228,6 +232,7 @@ public static function reset(): void
228232
TemplateManager::reset();
229233
HierarchyResolver::reset();
230234
FileFilter::reset();
235+
StreamWrapper::reset();
231236
}
232237

233238
/**
@@ -244,4 +249,4 @@ private static function syncFlags(array $config): void
244249
self::$magicMethods = (bool) ($config['magic_methods'] ?? true);
245250
self::$respectIgnoreTags = (bool) ($config['respect_ignore_tags'] ?? true);
246251
}
247-
}
252+
}

src/Internal/StreamWrapper.php

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,17 @@ final class StreamWrapper implements StreamWrapperInterface
5353

5454
private static string $cacheDir = '';
5555

56+
/**
57+
* Resets the compiled pattern cache. Useful for test isolation.
58+
*/
59+
public static function reset(): void
60+
{
61+
self::$isInitialized = false;
62+
self::$includeRawPatterns = [];
63+
self::$excludeRawPatterns = [];
64+
self::$baseDir = '';
65+
}
66+
5667
/**
5768
* @param array<string, mixed> $config
5869
*/
@@ -515,20 +526,32 @@ private static function isApplicationFile(string $path, string|false $resolvedPa
515526
return false;
516527
}
517528

518-
// Unconditionally prevent double-parsing cached files!
519529
$normalizedCacheDir = rtrim(str_replace('\\', '/', self::$cacheDir), '/') . '/';
520530
if (str_starts_with($normalizedPath, $normalizedCacheDir)) {
521531
return false;
522532
}
523533

524-
$longestIncludeMatch = 0;
525534
$isVendorPath = str_contains($normalizedPath, '/vendor/');
535+
if ($isVendorPath) {
536+
$hasExplicitVendorWhitelist = false;
537+
foreach (self::$includeRawPatterns as $pattern => $regex) {
538+
if (str_starts_with($pattern, 'vendor/') && preg_match($regex, $normalizedPath) === 1) {
539+
$hasExplicitVendorWhitelist = true;
526540

541+
break;
542+
}
543+
}
544+
545+
if (! $hasExplicitVendorWhitelist) {
546+
return false;
547+
}
548+
}
549+
550+
$longestIncludeMatch = 0;
527551
foreach (self::$includeRawPatterns as $pattern => $regex) {
528552
$isExplicitVendorInclude = str_starts_with($pattern, 'vendor/');
529553
$isWildcard = ($pattern === '*' || $pattern === '**');
530554

531-
// Application include rules (like src/**, src/Core/**) never match inside vendor directories
532555
if ($isVendorPath && ! $isExplicitVendorInclude && ! $isWildcard) {
533556
continue;
534557
}
@@ -661,4 +684,4 @@ private static function extractAndSeedFileMetadata(array $stmts, string $filePat
661684

662685
SpecialTypeResolver::seedFileMetadata($filePath, $namespace, $imports, $classTraitUseDocs);
663686
}
664-
}
687+
}

0 commit comments

Comments
 (0)