Skip to content

Commit 33749c0

Browse files
committed
Refactor file pattern matching in StreamWrapper and improve test coverage for FileFilter exclusions and make sure vendor/src will never included and only in current directory src
1 parent ea86c19 commit 33749c0

3 files changed

Lines changed: 35 additions & 15 deletions

File tree

src/Contract/FileFilter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ private static function compileGlobToRegex(string $glob, string $baseDir): strin
156156
} elseif (str_starts_with($glob, '**')) {
157157
$pattern = '.*' . substr($regex, 4) . '$';
158158
} else {
159-
$pattern = '(^' . preg_quote($baseDir . '/', '#') . '|^.*\/)' . $regex . '$';
159+
$pattern = '^' . preg_quote($baseDir . '/', '#') . $regex . '$';
160160
}
161161

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

src/Internal/StreamWrapper.php

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public static function unregister(): void
111111
public static function transformSource(string $source, string $filePath = ''): string
112112
{
113113
// Respect per-file suppression tag unless respect_ignore_tags is false
114-
if ((bool) (Config::get()['respect_ignore_tags'] ?? true) && (str_contains($source, '@typephp-ignore-file') || str_contains($source, '@typephp-disable-file'))) {
114+
if (Config::isRespectIgnoreTagsEnabled() && (str_contains($source, '@typephp-ignore-file') || str_contains($source, '@typephp-disable-file'))) {
115115
return $source;
116116
}
117117

@@ -242,16 +242,6 @@ public function stream_lock(int $operation): bool
242242
return true;
243243
}
244244

245-
// CRITICAL: PHPStan's internal stub narrows flock's $operation parameter to int<0, 7>.
246-
// At PHP runtime, valid bitwise lock operations (such as LOCK_UN = 8 or LOCK_UN | LOCK_NB = 12)
247-
// range from 1 to 15. Passing 0 to flock() causes PHP to throw a warning ("must be one of LOCK_SH,
248-
// LOCK_EX, or LOCK_UN").
249-
//
250-
// CONSEQUENCE OF IGNORING: if it bypass PHPStan's narrow stub check here.
251-
// RUNTIME SAFETY: The guard clause above ($operation < 1 || $operation > 15) guarantees that invalid
252-
// operations (like 0) never reach flock(), preserving full runtime safety and compatibility
253-
// with Pest/PHPUnit result-caching (file_put_contents).
254-
//
255245
// @phpstan-ignore argument.type
256246
return @flock($this->handle, $operation);
257247
}
@@ -464,7 +454,7 @@ private static function compileGlobToRegex(string $glob): string
464454
} elseif (str_starts_with($glob, '**')) {
465455
$pattern = '.*' . substr($regex, 4) . '$';
466456
} else {
467-
$pattern = '(^' . preg_quote(self::$baseDir . '/', '#') . '|^.*\/)' . $regex . '$';
457+
$pattern = '^' . preg_quote(self::$baseDir . '/', '#') . $regex . '$';
468458
}
469459

470460
return '#' . $pattern . '#i';
@@ -507,7 +497,7 @@ private static function isReadOnlyCall(): bool
507497
*/
508498
private static function isApplicationFile(string $path, string|false $resolvedPath): bool
509499
{
510-
if (! (bool) (Config::get()['enabled'] ?? true)) {
500+
if (! Config::isEnabled()) {
511501
return false; // TypePHP is globally disabled!
512502
}
513503

@@ -661,4 +651,4 @@ private static function extractAndSeedFileMetadata(array $stmts, string $filePat
661651

662652
SpecialTypeResolver::seedFileMetadata($filePath, $namespace, $imports, $classTraitUseDocs);
663653
}
664-
}
654+
}

tests/Internal/StreamWrapperTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
declare(strict_types=1);
44

5+
use TypePHP\Contract\FileFilter;
6+
use TypePHP\Internal\Config;
57
use TypePHP\Internal\StreamWrapper;
68

79
describe('StreamWrapper Unit Tests', function () {
@@ -53,4 +55,32 @@ function testGen(): Generator
5355
->and($transformed)->toContain('RuntimeTypeChecker::checkSend')
5456
;
5557
});
58+
59+
test('strictly isolates vendor files with nested src directories when application includes specific src subpackages', function () {
60+
Config::set([
61+
'include' => [
62+
'src/**',
63+
'src/Core/**',
64+
'src/Storefront/**',
65+
'src/Administration/**',
66+
],
67+
'exclude' => [
68+
'vendor/**',
69+
'storage/**',
70+
'var/**',
71+
'cache/**',
72+
],
73+
]);
74+
75+
$projectRoot = Config::getProjectRoot();
76+
77+
$vendorFile = str_replace('\\', '/', $projectRoot . '/vendor/doctrine/dbal/src/Core/Table.php');
78+
$appFile = str_replace('\\', '/', $projectRoot . '/src/Core/Framework/Util.php');
79+
80+
expect(FileFilter::isFileExcluded($vendorFile))->toBeTrue()
81+
->and(FileFilter::isFileExcluded($appFile))->toBeFalse()
82+
;
83+
84+
Config::reset();
85+
});
5686
});

0 commit comments

Comments
 (0)