Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include excluded files #2593

Merged
merged 7 commits into from Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
@@ -1,6 +1,9 @@
Changelog
=========

Bug fixes:
- Fixing include and exclude patterns #2593 @mamazu

## 2024-03-09

Features:
Expand Down Expand Up @@ -51,7 +54,7 @@ Bug fixes:

Documentation:

- Added Helix LSP instructions #2581 @lens0021
- Added Helix LSP instructions #2581 @lens0021
- Fix typos in Behat #2534 @vuon9
- Fix broken external links #2500 @einenlum

Expand Down
59 changes: 41 additions & 18 deletions lib/Filesystem/Domain/FileList.php
Expand Up @@ -86,30 +86,50 @@ public function byExtensions(array $extensions): self
}

/**
* @param string[] $globPatterns
* @param string[] $includePatterns
* @param string[] $excludePatterns
*/
public function excludePatterns(array $globPatterns): self
public function includeAndExclude(array $includePatterns = [], array $excludePatterns = []): self
{
return $this->filter(function (SplFileInfo $info) use ($globPatterns) {
foreach ($globPatterns as $pattern) {
if (Glob::match($info->getPathname(), $pattern)) {
return false;
}
$inclusionMap = [];
if ($includePatterns === []) {
$inclusionMap['/**/*'] = true;
}

foreach ($includePatterns as $includePattern) {
$inclusionMap[$includePattern] = true;
}
foreach ($excludePatterns as $excludePattern) {
$inclusionMap[$excludePattern] = false;
}

// Sort map by keys so that more specific paths are getting matched first
uksort($inclusionMap, function (string $x, string $y) {
$partsX = explode(DIRECTORY_SEPARATOR, $x);
$partsY = explode(DIRECTORY_SEPARATOR, $y);
dantleech marked this conversation as resolved.
Show resolved Hide resolved
// Longer paths should come first
$countDiff = -(count($partsX) <=> count($partsY));
dantleech marked this conversation as resolved.
Show resolved Hide resolved
if ($countDiff !== 0) {
return $countDiff;
}

return true;
foreach ($partsX as $i => $pathPartX) {
if ($pathPartX === '**' || $pathPartX === '*') {
return 1;
}

$compare = strcmp($pathPartX, $partsY[$i]);
if($compare !== 0) {
return $compare;
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add a commenta about what this is doing? i.e. if the segments are the same length then... ??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly should I comment here. If they are the same then their order doesn't matter.

Copy link
Collaborator

@dantleech dantleech Mar 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explaining what the different branches mean. It's not obvious what ==='** and ==='*' mean and why we return. what happens with baz* or *baz or with */baz/**/boo*?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah wildcards within a path aren't handled. That's a valid point.

return 0;
dantleech marked this conversation as resolved.
Show resolved Hide resolved
});
}

/**
* @param string[] $globPatterns
*/
public function includePatterns(array $globPatterns): self
{
return $this->filter(function (SplFileInfo $info) use ($globPatterns) {
foreach ($globPatterns as $pattern) {
if (Glob::match($info->getPathname(), $pattern)) {
return true;
return $this->filter(function (SplFileInfo $info) use ($inclusionMap): bool {
foreach($inclusionMap as $glob => $isIncluded) {
if (Glob::match($info->getPathname(), $glob)) {
return $isIncluded;
}
}

Expand All @@ -133,6 +153,9 @@ public function named(string $name): self
)));
}

/**
* @param Closure(SplFileInfo): bool $closure
*/
public function filter(Closure $closure): self
{
return new self(new CallbackFilterIterator($this->iterator, $closure));
Expand Down
118 changes: 112 additions & 6 deletions lib/Filesystem/Tests/Unit/Domain/FileListTest.php
Expand Up @@ -2,6 +2,7 @@

namespace Phpactor\Filesystem\Tests\Unit\Domain;

use Generator;
use Phpactor\Filesystem\Domain\FileList;
use Phpactor\Filesystem\Domain\FilePath;
use Phpactor\Filesystem\Tests\IntegrationTestCase;
Expand Down Expand Up @@ -121,9 +122,16 @@ public function testExcludesFilesMatchingPatterns(): void
FilePath::fromString('/vendor/foo/bar/src/foo.php'),
]);

self::assertCount(2, $list->excludePatterns([
'/vendor/**/tests/*',
]));
self::assertEquals(
[
FilePath::fromString('/vendor/foo/bar/src/bar.php'),
FilePath::fromString('/vendor/foo/bar/src/foo.php'),
],
iterator_to_array($list->includeAndExclude(
includePatterns: ['/**/*'],
excludePatterns: [ '/vendor/**/tests/*']
))
);
}

public function testIncldesFilesMatchingPatterns(): void
Expand All @@ -135,9 +143,107 @@ public function testIncldesFilesMatchingPatterns(): void
FilePath::fromString('/vendor/foo/bar/src/foo.php'),
]);

self::assertCount(2, $list->excludePatterns([
'/vendor/**/tests/*',
]));
self::assertEquals(
[
FilePath::fromString('/vendor/foo/bar/tests/bartest.php'),
FilePath::fromString('/vendor/foo/bar/tests/footest.php'),
],
iterator_to_array($list->includeAndExclude(
includePatterns: [ '/vendor/**/tests/*'],
))
);
}

public function testIncludesEverythingByDefault(): void
{
$list = FileList::fromFilePaths([
FilePath::fromString('/vendor/cache/important/bartest.php'),
FilePath::fromString('/vendor/cache/important/footest.php'),
FilePath::fromString('/vendor/cache/bar.php'),
FilePath::fromString('/vendor/cache/foo.php'),
])->includeAndExclude(
includePatterns: [],
excludePatterns: [],
);

self::assertEquals(
[
FilePath::fromString('/vendor/cache/important/bartest.php'),
FilePath::fromString('/vendor/cache/important/footest.php'),
FilePath::fromString('/vendor/cache/bar.php'),
FilePath::fromString('/vendor/cache/foo.php'),
],
iterator_to_array($list)
);
}
public function testIncludesExcludePatterns(): void
{

$list = FileList::fromFilePaths([
FilePath::fromString('/vendor/cache/important/bartest.php'),
FilePath::fromString('/vendor/cache/important/footest.php'),
FilePath::fromString('/vendor/cache/bar.php'),
FilePath::fromString('/vendor/cache/foo.php'),
])->includeAndExclude(
includePatterns: [ '/src/**/*', '/vendor/cache/important/**/*'],
excludePatterns: [ '/vendor/**/*' ],
);

self::assertEquals(
[
FilePath::fromString('/vendor/cache/important/bartest.php'),
FilePath::fromString('/vendor/cache/important/footest.php'),
],
iterator_to_array($list)
);
}

/**
@param array<FilePath> $fileList
@param array<string> $includePatterns
@param array<string> $excludePatterns
@param array<FilePath> $expected
* @dataProvider provideExcludesWithShortFolderName()
*/
mamazu marked this conversation as resolved.
Show resolved Hide resolved
public function testExcludesWithShortFolderName(
array $fileList,
array $includePatterns,
array $excludePatterns,
array $expected,
): void {
$list = FileList::fromFilePaths($fileList)->includeAndExclude(
includePatterns:$includePatterns,
excludePatterns: $excludePatterns
);

self::assertEquals($expected, array_map(fn (FilePath $x) => (string) $x, iterator_to_array($list)));
}

public function provideExcludesWithShortFolderName(): Generator
{
yield 'ascii file name' => [
[
FilePath::fromString('/src/package/test.php'),
FilePath::fromString('/src/a/test.php'),
],
[ '/src/**/*'],
[ '/src/a/*' ],
[
'/src/package/test.php',
],
];

yield 'unicode file name' => [
[
FilePath::fromString('/src/package/test.php'),
FilePath::fromString('/src/ü/test.php'),
],
[ '/src/**/*'],
[ '/src/ü/*' ],
[
'/src/package/test.php',
],
];
}

public function testContainingString(): void
Expand Down
13 changes: 4 additions & 9 deletions lib/Indexer/Adapter/Filesystem/FilesystemFileListProvider.php
Expand Up @@ -30,15 +30,10 @@ public function provideFileList(Index $index, ?string $subPath = null): FileList
return FileList::fromSingleFilePath($subPath);
}

$files = $this->filesystem->fileList()->byExtensions($this->supportedExtensions);

if ($this->includePatterns) {
$files = $files->includePatterns($this->includePatterns);
}

if ($this->excludePatterns) {
$files = $files->excludePatterns($this->excludePatterns);
}
$files = $this->filesystem->fileList()
->byExtensions($this->supportedExtensions)
->includeAndExclude($this->includePatterns, $this->excludePatterns)
;

if ($subPath) {
$files = $files->within(FilePath::fromString($subPath));
Expand Down
4 changes: 1 addition & 3 deletions lib/Indexer/Extension/IndexerExtension.php
Expand Up @@ -224,9 +224,7 @@ private function registerModel(ContainerBuilder $container): void
->setIncludePatterns($container->get(self::SERVICE_INDEXER_INCLUDE_PATTERNS))
/** @phpstan-ignore-next-line */
->setSupportedExtensions($container->parameter(self::PARAM_SUPPORTED_EXTENSIONS)->value())
->setFollowSymlinks(
(bool) $container->getParameter(self::PARAM_INDEXER_FOLLOW_SYMLINKS),
)
->setFollowSymlinks($container->parameter(self::PARAM_INDEXER_FOLLOW_SYMLINKS)->bool())
->setStubPaths($container->getParameter(self::PARAM_STUB_PATHS));
});

Expand Down