diff --git a/src/Expectation.php b/src/Expectation.php index 69bf52f3e..95619a955 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -434,6 +434,39 @@ public function any(): Any return new Any; } + /** + * Excludes files whose paths contain any of the given path fragments from arch analysis. + * + * @param list $paths + */ + public function ignorePaths(array $paths): PendingArchExpectation + { + $normalizedPaths = array_values(array_filter(array_map( + static fn (string $path): string => trim(str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path), DIRECTORY_SEPARATOR), + $paths, + ), static fn (string $path): bool => $path !== '')); + + return new PendingArchExpectation($this, [ + static function (ObjectDescription $object) use ($normalizedPaths): bool { + if (isset($object->path)) { + $normalizedObjectPath = trim(str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $object->path), DIRECTORY_SEPARATOR); + } else { + // Fallback for objects without an initialized path (e.g., vendor objects); + // use the class name as a path approximation. + $normalizedObjectPath = trim(str_replace('\\', DIRECTORY_SEPARATOR, $object->name), DIRECTORY_SEPARATOR); + } + + foreach ($normalizedPaths as $path) { + if (str_contains($normalizedObjectPath, $path)) { + return true; + } + } + + return false; + }, + ]); + } + /** * Asserts that the given expectation target use the given dependencies. * diff --git a/tests/Features/Expect/ignorePaths.php b/tests/Features/Expect/ignorePaths.php new file mode 100644 index 000000000..352f51892 --- /dev/null +++ b/tests/Features/Expect/ignorePaths.php @@ -0,0 +1,52 @@ +ignorePaths(['database/migrations']) + ->toUseStrictTypes(); +}); + +test('multiple ignored paths', function () { + expect('Tests\Fixtures\Arch\IgnorePaths\Multiple') + ->ignorePaths(['database/migrations', 'vendor']) + ->toUseStrictTypes(); +}); + +test('nested directory ignored', function () { + expect('Tests\Fixtures\Arch\IgnorePaths\Single') + ->ignorePaths(['database']) + ->toUseStrictTypes(); +}); + +test('partial path matching', function () { + expect('Tests\Fixtures\Arch\IgnorePaths\Single') + ->ignorePaths(['migrations']) + ->toUseStrictTypes(); +}); + +test('windows path separators', function () { + expect('Tests\Fixtures\Arch\IgnorePaths\Single') + ->ignorePaths(['database\\migrations']) + ->toUseStrictTypes(); +}); + +test('non-ignored files still analyzed', function () { + expect('Tests\Fixtures\Arch\IgnorePaths\Dirty') + ->ignorePaths(['database/migrations']) + ->toUseStrictTypes(); +})->throws(ArchExpectationFailedException::class); + +test('violations caught in all paths without ignorePaths', function () { + expect('Tests\Fixtures\Arch\IgnorePaths\Single') + ->toUseStrictTypes(); +})->throws(ArchExpectationFailedException::class); + +test('chainable with not', function () { + expect('Tests\Fixtures\Arch\IgnorePaths\Single') + ->ignorePaths(['database/migrations']) + ->not->toBeAbstract(); +}); diff --git a/tests/Fixtures/Arch/IgnorePaths/Dirty/DirtyService.php b/tests/Fixtures/Arch/IgnorePaths/Dirty/DirtyService.php new file mode 100644 index 000000000..4ef5ffd2b --- /dev/null +++ b/tests/Fixtures/Arch/IgnorePaths/Dirty/DirtyService.php @@ -0,0 +1,5 @@ +