Skip to content

Commit 012980d

Browse files
committed
Refactor PathMatcher and StreamWrapper for improved clarity and consistency; update comments and standardize arrow function syntax
1 parent 2874e86 commit 012980d

2 files changed

Lines changed: 35 additions & 39 deletions

File tree

src/Internal/PathMatcher.php

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public static function normalizePath(string|false|null $path): string
5757
}
5858

5959
/**
60-
* Collapses relative directory traversals (..) into absolute canonical paths.
60+
* Collapses relative directory traversals (..) into canonical paths.
6161
*/
6262
public static function canonicalizePath(string $path): string
6363
{
@@ -74,6 +74,7 @@ public static function canonicalizePath(string $path): string
7474
if ($part === '' && \count($absolutes) === 0) {
7575
$absolutes[] = '';
7676
}
77+
7778
continue;
7879
}
7980

@@ -93,12 +94,16 @@ public static function canonicalizePath(string $path): string
9394

9495
/**
9596
* Determines whether a given path is located within a vendor directory.
97+
* Strictly collapses '..' traversals so 'vendor/composer/../../tests' is not treated as a vendor file.
9698
*/
97-
public static function isVendorPath(string $normalizedPath, string $normalizedRaw = ''): bool
99+
public static function isVendorPath(string $normalizedPath, string $rawPath = ''): bool
98100
{
99-
return str_starts_with($normalizedPath, 'vendor/')
100-
|| str_contains($normalizedPath, '/vendor/')
101-
|| ($normalizedRaw !== '' && (str_starts_with($normalizedRaw, 'vendor/') || str_contains($normalizedRaw, '/vendor/')));
101+
$canon = self::canonicalizePath($normalizedPath);
102+
$canonRaw = $rawPath !== '' ? self::canonicalizePath(self::normalizePath($rawPath)) : '';
103+
104+
return str_starts_with($canon, 'vendor/')
105+
|| str_contains($canon, '/vendor/')
106+
|| ($canonRaw !== '' && (str_starts_with($canonRaw, 'vendor/') || str_contains($canonRaw, '/vendor/')));
102107
}
103108

104109
/**
@@ -110,13 +115,11 @@ public static function isCachePath(string $normalizedPath): bool
110115
self::$cachedCacheDir = rtrim(self::normalizePath(CacheManager::getCacheDir()), '/') . '/';
111116
}
112117

113-
return str_starts_with($normalizedPath, self::$cachedCacheDir);
118+
return str_starts_with(self::canonicalizePath($normalizedPath), self::$cachedCacheDir);
114119
}
115120

116121
/**
117122
* Determines whether a path belongs to TypePHP's own internal engine source files.
118-
* In vendor mode: skips the entire library package.
119-
* In development mode: skips only actual internal subdirectories, allowing test fixtures to be tested.
120123
*/
121124
public static function isLibraryInternal(string $normalizedPath): bool
122125
{
@@ -130,11 +133,13 @@ public static function isLibraryInternal(string $normalizedPath): bool
130133
return false;
131134
}
132135

136+
$canon = self::canonicalizePath($normalizedPath);
137+
133138
if (str_contains($libSrcDir, '/vendor/')) {
134-
return str_starts_with($normalizedPath, $libSrcDir);
139+
return str_starts_with($canon, $libSrcDir);
135140
}
136141

137-
if (str_starts_with($normalizedPath, $libSrcDir)) {
142+
if (str_starts_with($canon, $libSrcDir)) {
138143
$internalDirs = [
139144
$libSrcDir . 'Internal/',
140145
$libSrcDir . 'Contract/',
@@ -149,7 +154,7 @@ public static function isLibraryInternal(string $normalizedPath): bool
149154
];
150155

151156
foreach ($internalDirs as $dir) {
152-
if (str_starts_with($normalizedPath, $dir)) {
157+
if (str_starts_with($canon, $dir)) {
153158
return true;
154159
}
155160
}
@@ -194,6 +199,7 @@ public static function isPathIncluded(
194199
?string $baseDir = null
195200
): bool {
196201
$baseDir = $baseDir !== null ? self::normalizePath($baseDir) : Config::getProjectRoot();
202+
$normalizedPath = self::canonicalizePath($normalizedPath);
197203
$normalizedRaw = $rawPath !== '' ? self::canonicalizePath(self::normalizePath($rawPath)) : '';
198204

199205
$includes = self::getCompiledPatterns($includeGlobs, $baseDir, 'include');
@@ -203,7 +209,8 @@ public static function isPathIncluded(
203209
if ($isVendor) {
204210
$hasExplicitVendorWhitelist = false;
205211
foreach ($includes as $compiled) {
206-
if (str_starts_with($compiled['pattern'], 'vendor/') &&
212+
if (
213+
str_starts_with($compiled['pattern'], 'vendor/') &&
207214
(preg_match($compiled['regex'], $normalizedPath) === 1 || ($normalizedRaw !== '' && preg_match($compiled['regex'], $normalizedRaw) === 1))
208215
) {
209216
$hasExplicitVendorWhitelist = true;

src/Internal/StreamWrapper.php

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -123,13 +123,12 @@ public static function transformSource(string $source, string $filePath = ''): s
123123

124124
$parser = (new ParserFactory())->createForNewestSupportedVersion();
125125

126-
try {
126+
try {
127127
$oldStmts = $parser->parse($source);
128128
if ($oldStmts === null) {
129129
return $source;
130130
}
131131
} catch (\Throwable $e) {
132-
fwrite(STDERR, "[TYPEPHP_DEBUG AST_PARSE_ERROR] File: $filePath | Error: " . $e->getMessage() . " on line " . $e->getLine() . "\n");
133132
return $source;
134133
}
135134

@@ -192,7 +191,7 @@ public function stream_open(string $path, string $mode, int $options, ?string &$
192191
}
193192

194193
self::unregister();
195-
$exists = self::silent(fn() => file_exists($path));
194+
$exists = self::silent(fn () => file_exists($path));
196195
$resolvedPath = $exists ? realpath($path) : false;
197196
self::register();
198197

@@ -202,19 +201,11 @@ public function stream_open(string $path, string $mode, int $options, ?string &$
202201

203202
$normalizedResolved = str_replace('\\', '/', $resolvedPath);
204203

205-
$isApp = self::isApplicationFile($path, $resolvedPath);
206-
$isReadOnly = self::isReadOnlyCall();
207-
208-
// >>> DEBUG PROBE <<<
209-
if (str_contains($path, 'Container') || str_contains($path, 'Producer') || str_contains($path, 'GenericsAndInheritance')) {
210-
fwrite(STDERR, "[TYPEPHP_DEBUG stream_open] Path: $path | isApp: " . ($isApp ? 'TRUE' : 'FALSE') . " | isReadOnly: " . ($isReadOnly ? 'TRUE' : 'FALSE') . " | CacheEnabled: " . (self::$cacheEnabled ? 'TRUE' : 'FALSE') . "\n");
211-
}
212-
213-
if (! $isApp) {
204+
if (! self::isApplicationFile($path, $resolvedPath)) {
214205
return $this->openDirectHandle($normalizedResolved, $mode);
215206
}
216207

217-
if ($isReadOnly) {
208+
if (self::isReadOnlyCall()) {
218209
return $this->openDirectHandle($normalizedResolved, $mode);
219210
}
220211

@@ -236,7 +227,7 @@ private function openDirectHandle(string $targetFile, string $mode): bool
236227
{
237228
self::unregister();
238229
/** @var resource|false $handle */
239-
$handle = self::silent(fn() => fopen($targetFile, $mode));
230+
$handle = self::silent(fn () => fopen($targetFile, $mode));
240231
$this->handle = $handle !== false ? $handle : null;
241232
self::register();
242233

@@ -363,7 +354,6 @@ public function stream_close(): void
363354

364355
/**
365356
* High-speed stat resolution with $O(1)$ memoization cache.
366-
* Never caches false (negative lookups) so newly created directories and files are immediately discovered.
367357
*
368358
* @return array<int|string, int>|false
369359
*/
@@ -376,10 +366,9 @@ public function url_stat(string $path, int $flags): array|false
376366

377367
self::unregister();
378368
/** @var array<int|string, int>|false $result */
379-
$result = self::silent(fn() => stat($path));
369+
$result = self::silent(fn () => stat($path));
380370
self::register();
381371

382-
// Only cache positive results (existing files/dirs)
383372
if ($result !== false) {
384373
self::$statCache[$normalized] = $result;
385374
}
@@ -399,11 +388,11 @@ public function stream_metadata(string $path, int $option, mixed $value): bool
399388
$valueArray = \is_array($value) ? $value : [];
400389
$time = $valueArray[0] ?? time();
401390
$atime = $valueArray[1] ?? $time;
402-
$result = (bool) self::silent(fn() => touch($path, (int) $time, (int) $atime));
391+
$result = (bool) self::silent(fn () => touch($path, (int) $time, (int) $atime));
403392
} elseif ($option === STREAM_META_ACCESS) {
404393
/** @var int $mode */
405394
$mode = \is_int($value) ? $value : 0777;
406-
$result = (bool) self::silent(fn() => chmod($path, $mode));
395+
$result = (bool) self::silent(fn () => chmod($path, $mode));
407396
}
408397
self::register();
409398

@@ -414,7 +403,7 @@ public function dir_opendir(string $path, int $options): bool
414403
{
415404
self::unregister();
416405
/** @var resource|false $dh */
417-
$dh = self::silent(fn() => opendir($path));
406+
$dh = self::silent(fn () => opendir($path));
418407
$this->dirHandle = $dh !== false ? $dh : null;
419408
self::register();
420409

@@ -457,7 +446,7 @@ public function mkdir(string $path, int $mode, int $options): bool
457446
unset(self::$statCache[$normalized]);
458447

459448
self::unregister();
460-
$result = (bool) self::silent(fn() => mkdir($path, $mode, (bool) ($options & STREAM_MKDIR_RECURSIVE)));
449+
$result = (bool) self::silent(fn () => mkdir($path, $mode, (bool) ($options & STREAM_MKDIR_RECURSIVE)));
461450
self::register();
462451

463452
return $result;
@@ -469,7 +458,7 @@ public function rmdir(string $path, int $options): bool
469458
unset(self::$statCache[$normalized]);
470459

471460
self::unregister();
472-
$result = (bool) self::silent(fn() => rmdir($path));
461+
$result = (bool) self::silent(fn () => rmdir($path));
473462
self::register();
474463

475464
return $result;
@@ -481,7 +470,7 @@ public function unlink(string $path): bool
481470
unset(self::$statCache[$normalized]);
482471

483472
self::unregister();
484-
$result = (bool) self::silent(fn() => unlink($path));
473+
$result = (bool) self::silent(fn () => unlink($path));
485474
self::register();
486475

487476
return $result;
@@ -494,7 +483,7 @@ public function rename(string $pathFrom, string $pathTo): bool
494483
unset(self::$statCache[$normFrom], self::$statCache[$normTo]);
495484

496485
self::unregister();
497-
$result = (bool) self::silent(fn() => rename($pathFrom, $pathTo));
486+
$result = (bool) self::silent(fn () => rename($pathFrom, $pathTo));
498487
self::register();
499488

500489
return $result;
@@ -525,7 +514,7 @@ private static function isReadOnlyCall(): bool
525514
*/
526515
private static function silent(callable $callback): mixed
527516
{
528-
set_error_handler(fn() => true);
517+
set_error_handler(fn () => true);
529518

530519
try {
531520
return $callback();
@@ -598,7 +587,7 @@ private function openCachedStream(string $resolvedPath, string $mode): bool
598587
{
599588
$cacheDir = self::$cacheDir;
600589
if (! is_dir($cacheDir)) {
601-
self::silent(fn() => mkdir($cacheDir, 0777, true));
590+
self::silent(fn () => mkdir($cacheDir, 0777, true));
602591
}
603592

604593
$cachedFile = CacheManager::getCachedFilePath($resolvedPath);
@@ -680,4 +669,4 @@ private static function extractAndSeedFileMetadata(array $stmts, string $filePat
680669

681670
SpecialTypeResolver::seedFileMetadata($filePath, $namespace, $imports, $classTraitUseDocs);
682671
}
683-
}
672+
}

0 commit comments

Comments
 (0)