Skip to content

Commit bc54f88

Browse files
committed
Refactor DocblockExtractor and DocblockNormalizer; remove unused template tag handling and streamline normalization logic
Enhance PathMatcher; improve vendor path handling and remove deprecated methods Update StreamWrapper; refine path inclusion checks and improve file handling logic
1 parent 0fab7e6 commit bc54f88

4 files changed

Lines changed: 43 additions & 97 deletions

File tree

src/Contract/DocblockExtractor.php

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,6 @@ public static function extractTemplates(PhpDocNode $node): array
175175
}
176176
}
177177

178-
foreach ($node->getTags() as $tagNode) {
179-
if ($tagNode->value instanceof TemplateTagValueNode && ! isset($templates[$tagNode->value->name])) {
180-
$templates[$tagNode->value->name] = $tagNode->value;
181-
}
182-
}
183-
184178
return $templates;
185179
}
186180

@@ -218,19 +212,6 @@ public static function extractTemplateVariances(PhpDocNode $node): array
218212
}
219213
}
220214

221-
foreach ($node->getTags() as $tagNode) {
222-
if ($tagNode->value instanceof TemplateTagValueNode && ! isset($variances[$tagNode->value->name])) {
223-
$lowerTag = strtolower($tagNode->name);
224-
if (str_contains($lowerTag, 'covariant')) {
225-
$variances[$tagNode->value->name] = 'covariant';
226-
} elseif (str_contains($lowerTag, 'contravariant')) {
227-
$variances[$tagNode->value->name] = 'contravariant';
228-
} else {
229-
$variances[$tagNode->value->name] = 'invariant';
230-
}
231-
}
232-
}
233-
234215
return $variances;
235216
}
236217

@@ -242,14 +223,29 @@ public static function extractTemplateVariances(PhpDocNode $node): array
242223
public static function getInheritedTags(PhpDocNode $node): array
243224
{
244225
$tags = [];
226+
$tagNames = [
227+
'@extends',
228+
'@template-extends',
229+
'@phpstan-extends',
230+
'@psalm-extends',
231+
'@implements',
232+
'@template-implements',
233+
'@phpstan-implements',
234+
'@psalm-implements',
235+
'@use',
236+
'@template-use',
237+
'@phpstan-use',
238+
];
245239

246-
foreach ($node->getTags() as $tagNode) {
247-
if (
248-
$tagNode->value instanceof \PHPStan\PhpDocParser\Ast\PhpDoc\ExtendsTagValueNode ||
249-
$tagNode->value instanceof \PHPStan\PhpDocParser\Ast\PhpDoc\ImplementsTagValueNode ||
250-
$tagNode->value instanceof \PHPStan\PhpDocParser\Ast\PhpDoc\UsesTagValueNode
251-
) {
252-
$tags[] = $tagNode->value;
240+
foreach ($tagNames as $name) {
241+
foreach ($node->getTagsByName($name) as $tagNode) {
242+
if (
243+
$tagNode->value instanceof \PHPStan\PhpDocParser\Ast\PhpDoc\ExtendsTagValueNode ||
244+
$tagNode->value instanceof \PHPStan\PhpDocParser\Ast\PhpDoc\ImplementsTagValueNode ||
245+
$tagNode->value instanceof \PHPStan\PhpDocParser\Ast\PhpDoc\UsesTagValueNode
246+
) {
247+
$tags[] = $tagNode->value;
248+
}
253249
}
254250
}
255251

@@ -428,4 +424,4 @@ public static function extractMagicMethodContract(string $doc, string $methodNam
428424

429425
return null;
430426
}
431-
}
427+
}

src/Internal/DocblockNormalizer.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,6 @@ final class DocblockNormalizer
2929
public static function normalize(string $doc): string
3030
{
3131
$doc = preg_replace('/(@(?:phpstan|psalm)-type\s+[a-zA-Z0-9_\x80-\xff]+)\s*=\s*/', '$1 ', $doc) ?? $doc;
32-
$doc = preg_replace('/@(template-(?:covariant|contravariant))/', '@phpstan-$1', $doc) ?? $doc;
33-
$doc = preg_replace('/@(template-extends|template-implements|template-use)/', '@phpstan-$1', $doc) ?? $doc;
34-
$doc = preg_replace('/@use(\s+[\\\\a-zA-Z0-9_\x80-\xff]+<)/', '@phpstan-use$1', $doc) ?? $doc;
3532
$doc = preg_replace('/(callable|Closure)\s*\(([^)]*)\)(?!\s*:)/', '$1($2): mixed', $doc) ?? $doc;
3633
$doc = preg_replace('/(\\\\?[a-zA-Z_\x80-\xff][\\\\a-zA-Z0-9_\x80-\xff]*::[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\s*(\??:)/', '"$1"$2', $doc) ?? $doc;
3734

@@ -55,4 +52,4 @@ function (array $matches): string {
5552
$doc
5653
) ?? $doc;
5754
}
58-
}
55+
}

src/Internal/PathMatcher.php

Lines changed: 10 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,12 @@ public static function canonicalizePath(string $path): string
102102

103103
/**
104104
* Determines whether a given path is located within a vendor directory.
105+
* Correctly handles relative traversal paths like `vendor/composer/../../tests/`.
105106
*/
106107
public static function isVendorPath(string $normalizedPath, string $rawPath = ''): bool
107108
{
108109
$canon = self::canonicalizePath($normalizedPath);
109-
$canonRaw = $rawPath !== '' ? self::canonicalizePath($rawPath) : '';
110+
$canonRaw = $rawPath !== '' ? self::canonicalizePath(self::normalizePath($rawPath)) : '';
110111

111112
return str_starts_with($canon, 'vendor/')
112113
|| str_contains($canon, '/vendor/')
@@ -122,7 +123,9 @@ public static function isCachePath(string $normalizedPath): bool
122123
self::$cachedCacheDir = rtrim(self::normalizePath(CacheManager::getCacheDir()), '/') . '/';
123124
}
124125

125-
return str_starts_with($normalizedPath, self::$cachedCacheDir);
126+
$canon = self::canonicalizePath($normalizedPath);
127+
128+
return str_starts_with($canon, self::$cachedCacheDir);
126129
}
127130

128131
/**
@@ -140,11 +143,13 @@ public static function isLibraryInternal(string $normalizedPath): bool
140143
return false;
141144
}
142145

146+
$canon = self::canonicalizePath($normalizedPath);
147+
143148
if (str_contains($libSrcDir, '/vendor/')) {
144-
return str_starts_with($normalizedPath, $libSrcDir);
149+
return str_starts_with($canon, $libSrcDir);
145150
}
146151

147-
if (str_starts_with($normalizedPath, $libSrcDir)) {
152+
if (str_starts_with($canon, $libSrcDir)) {
148153
$internalDirs = [
149154
$libSrcDir . 'Internal/',
150155
$libSrcDir . 'Contract/',
@@ -159,7 +164,7 @@ public static function isLibraryInternal(string $normalizedPath): bool
159164
];
160165

161166
foreach ($internalDirs as $dir) {
162-
if (str_starts_with($normalizedPath, $dir)) {
167+
if (str_starts_with($canon, $dir)) {
163168
return true;
164169
}
165170
}
@@ -192,61 +197,6 @@ public static function hasIncludeMatchingPrefix(string $prefix, array $includes)
192197
return self::$includePrefixCache[$cacheKey] = false;
193198
}
194199

195-
/**
196-
* Determines whether a directory path is a dynamic writable cache/log directory.
197-
*/
198-
public static function isDynamicWritablePath(string $normalizedPath): bool
199-
{
200-
$canon = self::canonicalizePath($normalizedPath);
201-
202-
return str_contains($canon, '/var/cache/') || str_starts_with($canon, 'var/cache/')
203-
|| str_contains($canon, '/var/log/') || str_starts_with($canon, 'var/log/')
204-
|| str_contains($canon, '/storage/') || str_starts_with($canon, 'storage/')
205-
|| str_contains($canon, '/cache/') || str_starts_with($canon, 'cache/');
206-
}
207-
208-
/**
209-
* High-speed $O(1)$ string pre-filter to determine if a raw path can possibly be included,
210-
* while respecting user whitelists for vendor, var, and storage directories.
211-
*/
212-
public static function mayPathBeIncluded(string $normalizedPath): bool
213-
{
214-
$canon = self::canonicalizePath($normalizedPath);
215-
216-
if (str_contains($canon, '/node_modules/') || str_starts_with($canon, 'node_modules/')) {
217-
return false;
218-
}
219-
220-
if (self::isCachePath($canon)) {
221-
return false;
222-
}
223-
224-
$config = Config::get();
225-
/** @var array<int, string> $includes */
226-
$includes = \is_array($config['include'] ?? null) ? $config['include'] : ['**'];
227-
228-
if (str_contains($canon, '/vendor/') || str_starts_with($canon, 'vendor/')) {
229-
if (! self::hasIncludeMatchingPrefix('vendor/', $includes)) {
230-
return false;
231-
}
232-
}
233-
234-
if (str_contains($canon, '/var/cache/') || str_starts_with($canon, 'var/cache/')
235-
|| str_contains($canon, '/var/log/') || str_starts_with($canon, 'var/log/')) {
236-
if (! self::hasIncludeMatchingPrefix('var/', $includes)) {
237-
return false;
238-
}
239-
}
240-
241-
if (str_contains($canon, '/storage/') || str_starts_with($canon, 'storage/')) {
242-
if (! self::hasIncludeMatchingPrefix('storage/', $includes)) {
243-
return false;
244-
}
245-
}
246-
247-
return true;
248-
}
249-
250200
/**
251201
* Converts a glob pattern into an absolute anchored regex pattern.
252202
*/

src/Internal/StreamWrapper.php

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,6 @@ public static function unregister(): void
114114
*/
115115
public static function transformSource(string $source, string $filePath = ''): string
116116
{
117-
// Respect per-file suppression tag unless respect_ignore_tags is false
118117
if (Config::isRespectIgnoreTagsEnabled() && (str_contains($source, '@typephp-ignore-file') || str_contains($source, '@typephp-disable-file'))) {
119118
return $source;
120119
}
@@ -185,21 +184,25 @@ public static function transformSource(string $source, string $filePath = ''): s
185184
*/
186185
public function stream_open(string $path, string $mode, int $options, ?string &$openedPath): bool
187186
{
188-
if ($mode !== 'r' && $mode !== 'rb' && $mode !== 'rt') {
187+
if (! str_ends_with(strtolower($path), '.php')) {
189188
return $this->openDirectHandle($path, $mode);
190189
}
191190

192-
if (! str_ends_with(strtolower($path), '.php')) {
191+
if (! Config::isEnabled()) {
193192
return $this->openDirectHandle($path, $mode);
194193
}
195194

196-
if (! Config::isEnabled()) {
195+
$config = Config::get();
196+
/** @var array<int, string> $includes */
197+
$includes = \is_array($config['include'] ?? null) ? $config['include'] : ['**'];
198+
$isVendor = PathMatcher::isVendorPath($path);
199+
200+
if ($isVendor && ! PathMatcher::hasIncludeMatchingPrefix('vendor/', $includes)) {
197201
return $this->openDirectHandle($path, $mode);
198202
}
199203

200204
$normalizedRaw = str_replace('\\', '/', $path);
201-
202-
if (! PathMatcher::mayPathBeIncluded($normalizedRaw)) {
205+
if (str_contains($normalizedRaw, '/node_modules/') || str_starts_with($normalizedRaw, 'node_modules/')) {
203206
return $this->openDirectHandle($path, $mode);
204207
}
205208

0 commit comments

Comments
 (0)