diff --git a/.gitignore b/.gitignore index 4a485fb2..0af9abb9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ .DS_Store wp-cli.local.yml -node_modules/ -vendor/ +/node_modules +/vendor *.zip *.tar.gz *.swp diff --git a/src/IterableCodeExtractor.php b/src/IterableCodeExtractor.php index 531b2021..f0206ebe 100644 --- a/src/IterableCodeExtractor.php +++ b/src/IterableCodeExtractor.php @@ -205,6 +205,10 @@ function ( $file, $key, $iterator ) use ( $include, $exclude, $extensions ) { /** @var RecursiveCallbackFilterIterator $iterator */ /** @var SplFileInfo $file */ + // Normalize include and exclude paths. + $include = array_map( 'static::trim_leading_slash', $include ); + $exclude = array_map( 'static::trim_leading_slash', $exclude ); + // If no $include is passed everything gets the weakest possible matching score. $inclusion_score = empty( $include ) ? 0.1 : static::calculateMatchScore( $file, $include ); $exclusion_score = static::calculateMatchScore( $file, $exclude ); @@ -214,12 +218,12 @@ function ( $file, $key, $iterator ) use ( $include, $exclude, $extensions ) { return true; } - if ( 0 === $inclusion_score || $exclusion_score > $inclusion_score ) { + if ( ( 0 === $inclusion_score || $exclusion_score > $inclusion_score ) && $iterator->hasChildren() ) { // Always include directories that may have matching children even if they are excluded. - return $iterator->hasChildren() && static::containsMatchingChildren( $file, $include ); + return static::containsMatchingChildren( $file, $include ); } - return ( $file->isFile() && in_array( $file->getExtension(), $extensions, true ) ); + return ( ( $inclusion_score >= $exclusion_score ) && $file->isFile() && in_array( $file->getExtension(), $extensions, true ) ); } ), RecursiveIteratorIterator::CHILD_FIRST @@ -238,4 +242,14 @@ function ( $file, $key, $iterator ) use ( $include, $exclude, $extensions ) { return $filtered_files; } + + /** + * Trim leading slash from a path. + * + * @param string $path Path to trim. + * @return string Trimmed path. + */ + private static function trim_leading_slash( $path ) { + return ltrim( $path, '/' ); + } } diff --git a/tests/IterableCodeExtractorTest.php b/tests/IterableCodeExtractorTest.php index d5d29e76..9f25cb46 100644 --- a/tests/IterableCodeExtractorTest.php +++ b/tests/IterableCodeExtractorTest.php @@ -120,7 +120,32 @@ public function test_can_return_all_directory_files_sorted() { static::$base . 'foo/bar/foo/bar/foo/bar/deep_directory_also_included.php', static::$base . 'foo/bar/foofoo/included.js', static::$base . 'hoge/should_NOT_be_included.js', + static::$base . 'vendor/vendor-file.php', ); $this->assertEquals( $expected, $result ); } + + public function test_can_include_file_in_excluded_folder() { + $includes = [ 'vendor/vendor-file.php' ]; + $excludes = [ 'vendor' ]; + $result = IterableCodeExtractor::getFilesFromDirectory( self::$base, $includes, $excludes, [ 'php', 'js' ] ); + $expected = static::$base . 'vendor/vendor-file.php'; + $this->assertContains( $expected, $result ); + } + + public function test_can_include_file_in_excluded_folder_with_leading_slash() { + $includes = [ '/vendor/vendor-file.php' ]; + $excludes = [ 'vendor' ]; + $result = IterableCodeExtractor::getFilesFromDirectory( self::$base, $includes, $excludes, [ 'php', 'js' ] ); + $expected = static::$base . 'vendor/vendor-file.php'; + $this->assertContains( $expected, $result ); + } + + public function test_can_include_file_in_excluded_folder_by_wildcard() { + $includes = [ 'vendor/**' ]; + $excludes = [ 'vendor' ]; + $result = IterableCodeExtractor::getFilesFromDirectory( self::$base, $includes, $excludes, [ 'php', 'js' ] ); + $expected = static::$base . 'vendor/vendor-file.php'; + $this->assertContains( $expected, $result ); + } } diff --git a/tests/data/vendor/vendor-file.php b/tests/data/vendor/vendor-file.php new file mode 100644 index 00000000..e69de29b