Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 0 additions & 29 deletions src/Skipper/FileSystem/FnMatchPathNormalizer.php

This file was deleted.

18 changes: 0 additions & 18 deletions src/Skipper/Fnmatcher.php

This file was deleted.

79 changes: 57 additions & 22 deletions src/Skipper/Matcher/FileInfoMatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,13 @@

namespace Rector\Skipper\Matcher;

use Rector\Skipper\FileSystem\FnMatchPathNormalizer;
use Rector\Skipper\FileSystem\PathNormalizer;
use Rector\Skipper\Fnmatcher;
use Rector\Skipper\RealpathMatcher;

/**
* @see \Rector\Tests\Skipper\Matcher\FileInfoMatcherTest
*/
final readonly class FileInfoMatcher
final class FileInfoMatcher
{
public function __construct(
private FnMatchPathNormalizer $fnMatchPathNormalizer,
private Fnmatcher $fnmatcher,
private RealpathMatcher $realpathMatcher
) {
}

/**
* @param string[] $filePatterns
*/
public function doesFileInfoMatchPatterns(string $filePath, array $filePatterns): bool
{
return $this->matchPattern($filePath, $filePatterns) !== null;
}

/**
* Returns the original (un-normalized) pattern that matched, so callers can report the exact
* configured path. Returns null when no pattern matches.
Expand Down Expand Up @@ -58,7 +40,7 @@ private function doesFileMatchPattern(string $filePath, string $ignoredPath): bo
return true;
}

$ignoredPath = $this->fnMatchPathNormalizer->normalizeForFnmatch($ignoredPath);
$ignoredPath = $this->normalizeForFnmatch($ignoredPath);
if ($ignoredPath === '') {
return false;
}
Expand All @@ -71,10 +53,63 @@ private function doesFileMatchPattern(string $filePath, string $ignoredPath): bo
return true;
}

if ($this->fnmatcher->match($ignoredPath, $filePath)) {
if ($this->matchFnmatch($ignoredPath, $filePath)) {
return true;
}

return $this->matchRealpath($ignoredPath, $filePath);
}

private function normalizeForFnmatch(string $path): string
{
if (str_ends_with($path, '*') || str_starts_with($path, '*')) {
return '*' . trim($path, '*') . '*';
}

if (str_contains($path, '..')) {
$realPath = realpath($path);
if ($realPath === false) {
return '';
}

return PathNormalizer::normalize($realPath);
}

return $path;
}

private function matchFnmatch(string $matchingPath, string $filePath): bool
{
if (fnmatch($matchingPath, $filePath)) {
return true;
}

// in case of relative compare
return fnmatch('*/' . $matchingPath, $filePath);
}

private function matchRealpath(string $matchingPath, string $filePath): bool
{
$realPathMatchingPath = realpath($matchingPath);
if ($realPathMatchingPath === false) {
return false;
}

$realpathFilePath = realpath($filePath);
if ($realpathFilePath === false) {
return false;
}

$normalizedMatchingPath = PathNormalizer::normalize($realPathMatchingPath);
$normalizedFilePath = PathNormalizer::normalize($realpathFilePath);

// skip define direct path exactly equal
if ($normalizedMatchingPath === $normalizedFilePath) {
return true;
}

return $this->realpathMatcher->match($ignoredPath, $filePath);
// ensure add / suffix to ensure no same prefix directory
$suffixedMatchingPath = rtrim($normalizedMatchingPath, '/') . '/';
return str_starts_with($normalizedFilePath, $suffixedMatchingPath);
}
}
35 changes: 0 additions & 35 deletions src/Skipper/RealpathMatcher.php

This file was deleted.

35 changes: 0 additions & 35 deletions src/Skipper/SkipVoter/ClassSkipVoter.php

This file was deleted.

11 changes: 5 additions & 6 deletions src/Skipper/Skipper/PathSkipper.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,12 @@ public function __construct(

public function shouldSkip(string $filePath): bool
{
foreach ($this->skippedPathsResolver->resolve() as $skippedPath) {
if ($this->fileInfoMatcher->doesFileInfoMatchPatterns($filePath, [$skippedPath])) {
$this->usedSkipCollector->markUsed($skippedPath);
return true;
}
$matchedPath = $this->fileInfoMatcher->matchPattern($filePath, $this->skippedPathsResolver->resolve());
if ($matchedPath === null) {
return false;
}

return false;
$this->usedSkipCollector->markUsed($matchedPath);
return true;
}
}
11 changes: 7 additions & 4 deletions src/Skipper/Skipper/Skipper.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
namespace Rector\Skipper\Skipper;

use PhpParser\Node;
use PHPStan\Reflection\ReflectionProvider;
use Rector\Contract\Rector\RectorInterface;
use Rector\ProcessAnalyzer\RectifiedAnalyzer;
use Rector\Skipper\SkipVoter\ClassSkipVoter;
use Rector\Skipper\SkipCriteriaResolver\SkippedClassResolver;
use Rector\Skipper\ValueObject\SkipMatch;

/**
Expand All @@ -19,7 +20,9 @@
public function __construct(
private RectifiedAnalyzer $rectifiedAnalyzer,
private PathSkipper $pathSkipper,
private ClassSkipVoter $classSkipVoter,
private SkipSkipper $skipSkipper,
private SkippedClassResolver $skippedClassResolver,
private ReflectionProvider $reflectionProvider,
private UsedSkipCollector $usedSkipCollector,
) {
}
Expand Down Expand Up @@ -51,11 +54,11 @@ public function shouldSkipElementAndFilePath(string|object $element, string $fil
*/
public function matchSkip(string|object $element, string $filePath): ?SkipMatch
{
if (! $this->classSkipVoter->match($element)) {
if (! is_object($element) && ! $this->reflectionProvider->hasClass($element)) {
return null;
}

return $this->classSkipVoter->matchSkip($element, $filePath);
return $this->skipSkipper->match($element, $filePath, $this->skippedClassResolver->resolve());
}

public function markSkipUsed(SkipMatch $skipMatch): void
Expand Down
43 changes: 0 additions & 43 deletions tests/Skipper/FileSystem/FnMatchPathNormalizerTest.php

This file was deleted.

32 changes: 29 additions & 3 deletions tests/Skipper/Matcher/FileInfoMatcherTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Rector\Tests\Skipper\Matcher;

use Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use Rector\Skipper\Matcher\FileInfoMatcher;
use Rector\Testing\PHPUnit\AbstractLazyTestCase;

Expand Down Expand Up @@ -33,9 +35,33 @@ public function testMatchPatternReturnsNullWhenNoPatternMatches(): void
$this->assertNull($matchedPattern);
}

public function testDoesFileInfoMatchPatternsStillReportsBoolean(): void
#[DataProvider('providePatterns')]
public function testPatternNormalization(string $filePath, string $filePattern, bool $shouldMatch): void
{
$this->assertTrue($this->fileInfoMatcher->doesFileInfoMatchPatterns('/project/src/Foo.php', ['*/src/*']));
$this->assertFalse($this->fileInfoMatcher->doesFileInfoMatchPatterns('/project/src/Foo.php', ['*/tests/*']));
$matchedPattern = $this->fileInfoMatcher->matchPattern($filePath, [$filePattern]);

$this->assertSame($shouldMatch ? $filePattern : null, $matchedPattern);
}

/**
* @return Iterator<array{string, string, bool}>
*/
public static function providePatterns(): Iterator
{
// a pattern without asterisk is used as is, and matches the path suffix
yield ['/project/path/with/no/asterisk', 'path/with/no/asterisk', true];
yield ['/project/path/with/no/asterisk', 'path/with/another/asterisk', false];

// an asterisk on either end is padded to both ends
yield ['/project/path/with/asterisk/begin/Foo.php', '*path/with/asterisk/begin', true];
yield ['/project/path/with/asterisk/end/Foo.php', 'path/with/asterisk/end*', true];

// ".." in a pattern is resolved against the real path
yield [__DIR__ . '/Fixture/path/in/it/KeepThisFile.txt', __DIR__ . '/Fixture/path/with/../in/it', true];
yield [__DIR__ . '/Fixture/in/it/KeepThisFile.txt', __DIR__ . '/Fixture/path/with/../../in/it', true];
yield [__DIR__ . '/Fixture/in/it/KeepThisFile.txt', __DIR__ . '/Fixture/path/with/../in/it', false];

// a ".." pattern that resolves to nothing never matches
yield [__DIR__ . '/Fixture/in/it/KeepThisFile.txt', __DIR__ . '/Fixture/missing/../nope', false];
}
}
Loading