diff --git a/CHANGELOG.md b/CHANGELOG.md index 89cf178..d9f4063 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## Unreleased + +### Fixed +- **Unanchored built-in directory exclude patterns dropped legitimate directories**: `ext/`, `examples?/`, `tests?/`, `php4/`, `dev-bin/`, `.github/`, `.gitlab/` behaved as substring matches, so e.g. `Text/` and `Context/` were treated as `ext/` and silently omitted from the scoped output. The seven patterns now anchor to path-start or immediately after `/`. + +--- + ## 1.2.5 - 2026-04-07 ### Fixed diff --git a/src/Config/Config.php b/src/Config/Config.php index 0b39331..e2e3a77 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -24,14 +24,14 @@ class Config '/\\.phpunit/i', '/\\.editorconfig$/', '/\\.gitignore$/', - '/\\.github\\//i', - '/\\.gitlab\\//i', - '/examples?\\//i', - '/ext\\//i', - '/php4\\//i', - '/tests?\\//i', + '/(?:^|\\/)\\.github\\//i', + '/(?:^|\\/)\\.gitlab\\//i', + '/(?:^|\\/)examples?\\//i', + '/(?:^|\\/)ext\\//i', + '/(?:^|\\/)php4\\//i', + '/(?:^|\\/)tests?\\//i', '/\\bbin\\//i', - '/dev-bin\\//i', + '/(?:^|\\/)dev-bin\\//i', '/Makefile$/', '/phpunit\\.xml(\\.dist)?$/i', '/\\.travis\\.yml$/', diff --git a/tests/Unit/Config/BuiltInExcludePatternsTest.php b/tests/Unit/Config/BuiltInExcludePatternsTest.php new file mode 100644 index 0000000..b858411 --- /dev/null +++ b/tests/Unit/Config/BuiltInExcludePatternsTest.php @@ -0,0 +1,95 @@ + 'Test\\Deps', + 'packages' => [], + ], '/tmp')->getExcludePatterns(); + + foreach ( + [ + self::P_GITHUB, + self::P_GITLAB, + self::P_EXAMPLES, + self::P_EXT, + self::P_PHP4, + self::P_TESTS, + self::P_DEV_BIN, + ] as $pattern + ) { + $this->assertContains($pattern, $patterns); + } + } + + /** + * @dataProvider directoryPatternMatchProvider + */ + public function testBuiltInDirectoryPatternMatchesExpectedPaths( + string $pattern, + string $relativePath, + bool $shouldMatch + ): void { + $this->assertSame($shouldMatch, preg_match($pattern, $relativePath) === 1); + } + + /** + * @return array + */ + public static function directoryPatternMatchProvider(): array + { + return [ + '.github at root matches' => [self::P_GITHUB, '.github/workflows/ci.yml', true], + '.github nested matches' => [self::P_GITHUB, 'src/.github/foo.php', true], + 'notgithub/ does not match' => [self::P_GITHUB, 'notgithub/foo.php', false], + + '.gitlab at root matches' => [self::P_GITLAB, '.gitlab/ci.yml', true], + '.gitlab nested matches' => [self::P_GITLAB, 'src/.gitlab/foo.php', true], + 'notgitlab/ does not match' => [self::P_GITLAB, 'notgitlab/foo.php', false], + + 'example/ at root matches' => [self::P_EXAMPLES, 'example/foo.php', true], + 'examples/ at root matches' => [self::P_EXAMPLES, 'examples/foo.php', true], + 'examples/ nested matches' => [self::P_EXAMPLES, 'src/examples/foo.php', true], + 'MyExamples/ does not match' => [self::P_EXAMPLES, 'MyExamples/foo.php', false], + 'bad-examples/ does not match' => [self::P_EXAMPLES, 'bad-examples/foo.php', false], + 'nested MyExamples/ does not match' => [self::P_EXAMPLES, 'src/MyExamples/foo.php', false], + + 'ext/ at root matches' => [self::P_EXT, 'ext/Foo.php', true], + 'ext/ nested matches' => [self::P_EXT, 'src/ext/Foo.php', true], + 'Text/ does not match' => [self::P_EXT, 'Text/Foo.php', false], + 'Context/ does not match' => [self::P_EXT, 'Context/Foo.php', false], + 'nested Text/ does not match' => [self::P_EXT, 'src/Text/Foo.php', false], + + 'php4/ at root matches' => [self::P_PHP4, 'php4/foo.php', true], + 'php4/ nested matches' => [self::P_PHP4, 'src/php4/foo.php', true], + 'graphql4/ does not match' => [self::P_PHP4, 'graphql4/foo.php', false], + + 'test/ at root matches' => [self::P_TESTS, 'test/foo.php', true], + 'tests/ at root matches' => [self::P_TESTS, 'tests/foo.php', true], + 'tests/ nested matches' => [self::P_TESTS, 'src/tests/foo.php', true], + 'UnitTests/ does not match' => [self::P_TESTS, 'UnitTests/foo.php', false], + 'apitest/ does not match' => [self::P_TESTS, 'apitest/foo.php', false], + + 'dev-bin/ at root matches' => [self::P_DEV_BIN, 'dev-bin/foo.php', true], + 'dev-bin/ nested matches' => [self::P_DEV_BIN, 'src/dev-bin/foo.php', true], + 'my-dev-bin/ does not match' => [self::P_DEV_BIN, 'my-dev-bin/foo.php', false], + ]; + } +} diff --git a/tests/Unit/Config/ConfigTest.php b/tests/Unit/Config/ConfigTest.php index 71b6822..7da22b2 100644 --- a/tests/Unit/Config/ConfigTest.php +++ b/tests/Unit/Config/ConfigTest.php @@ -65,8 +65,8 @@ public function testDefaultValues(): void $this->assertSame([], $config->getExcludePackages()); // Built-in patterns are always included $this->assertContains('/\\.md$/i', $config->getExcludePatterns()); - $this->assertContains('/examples?\\//i', $config->getExcludePatterns()); - $this->assertContains('/ext\\//i', $config->getExcludePatterns()); + $this->assertContains('/(?:^|\\/)examples?\\//i', $config->getExcludePatterns()); + $this->assertContains('/(?:^|\\/)ext\\//i', $config->getExcludePatterns()); $this->assertSame(['views', 'templates', 'resources'], $config->getExcludeDirectories()); $this->assertFalse($config->shouldDeleteVendorPackages()); $this->assertTrue($config->shouldUpdateCallSites());