diff --git a/.tools/visual-tests/screenshots/index--dark.png b/.tools/visual-tests/screenshots/index--dark.png index ae5aff118c..6034c96f97 100644 Binary files a/.tools/visual-tests/screenshots/index--dark.png and b/.tools/visual-tests/screenshots/index--dark.png differ diff --git a/.tools/visual-tests/screenshots/index.png b/.tools/visual-tests/screenshots/index.png index 0515939a17..2c3a6cb8dc 100644 Binary files a/.tools/visual-tests/screenshots/index.png and b/.tools/visual-tests/screenshots/index.png differ diff --git a/.tools/visual-tests/screenshots/structure_article_edit--dark.png b/.tools/visual-tests/screenshots/structure_article_edit--dark.png index 18007f7c02..558e295a33 100644 Binary files a/.tools/visual-tests/screenshots/structure_article_edit--dark.png and b/.tools/visual-tests/screenshots/structure_article_edit--dark.png differ diff --git a/.tools/visual-tests/screenshots/structure_article_edit.png b/.tools/visual-tests/screenshots/structure_article_edit.png index 2727b2e3bc..f0aff791df 100644 Binary files a/.tools/visual-tests/screenshots/structure_article_edit.png and b/.tools/visual-tests/screenshots/structure_article_edit.png differ diff --git a/.tools/visual-tests/screenshots/structure_category_edit--dark.png b/.tools/visual-tests/screenshots/structure_category_edit--dark.png index 658fa577f9..c4f3a74440 100644 Binary files a/.tools/visual-tests/screenshots/structure_category_edit--dark.png and b/.tools/visual-tests/screenshots/structure_category_edit--dark.png differ diff --git a/.tools/visual-tests/screenshots/structure_category_edit.png b/.tools/visual-tests/screenshots/structure_category_edit.png index d0d9833b54..95a6f2aa0a 100644 Binary files a/.tools/visual-tests/screenshots/structure_category_edit.png and b/.tools/visual-tests/screenshots/structure_category_edit.png differ diff --git a/.tools/visual-tests/screenshots/structure_slice_edit--dark.png b/.tools/visual-tests/screenshots/structure_slice_edit--dark.png index 1a2bc8e487..75f18d29e1 100644 Binary files a/.tools/visual-tests/screenshots/structure_slice_edit--dark.png and b/.tools/visual-tests/screenshots/structure_slice_edit--dark.png differ diff --git a/.tools/visual-tests/screenshots/structure_slice_edit.png b/.tools/visual-tests/screenshots/structure_slice_edit.png index 0a2793ed1e..24158794a5 100644 Binary files a/.tools/visual-tests/screenshots/structure_slice_edit.png and b/.tools/visual-tests/screenshots/structure_slice_edit.png differ diff --git a/src/ClassDiscovery.php b/src/ClassDiscovery.php index 115e2dc1b4..49d58e6bad 100644 --- a/src/ClassDiscovery.php +++ b/src/ClassDiscovery.php @@ -19,6 +19,7 @@ use function implode; use function is_array; use function is_dir; +use function json_decode; use function realpath; use function sort; use function str_replace; @@ -254,14 +255,18 @@ private function getRelevantPaths(): array $paths = []; - // PSR-4 directories of the root composer package (project-level code) + // PSR-4 directories of the root composer package (project-level code), excluding vendor and the + // root's autoload-dev dirs (tests, dev tooling such as rector rules). Dev-only code never carries + // runtime attributes — and reflecting it needlessly autoloads dev-only vendor code (e.g. touching a + // custom rector rule pulls in rector's scoped vendor). $rootPath = realpath(InstalledVersions::getRootPackage()['install_path']); if (false !== $rootPath) { $vendorPrefix = $rootPath . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR; + $devDirs = $this->getRootAutoloadDevDirs($rootPath); foreach ($this->classLoader->getPrefixesPsr4() as $dirs) { foreach ($dirs as $dir) { $realDir = (string) realpath($dir); - if ('' !== $realDir && !str_starts_with($realDir, $vendorPrefix)) { + if ('' !== $realDir && !str_starts_with($realDir, $vendorPrefix) && !isset($devDirs[$realDir])) { $paths[] = $realDir . DIRECTORY_SEPARATOR; } } @@ -279,6 +284,37 @@ private function getRelevantPaths(): array return $this->relevantPaths = $paths; } + /** + * Returns the realpaths of the root package's autoload-dev PSR-4 directories, keyed for set lookup. + * + * These hold dev-only code (tests, dev tooling) that must be excluded from discovery: it is never + * registered at runtime, and a `--no-dev` install would not even autoload it. + * + * @return array + */ + private function getRootAutoloadDevDirs(string $rootPath): array + { + $json = File::get($rootPath . DIRECTORY_SEPARATOR . 'composer.json'); + if (null === $json) { + return []; + } + + /** @var array{autoload-dev?: array{psr-4?: array>}} $data */ + $data = json_decode($json, true); + + $dirs = []; + foreach ($data['autoload-dev']['psr-4'] ?? [] as $value) { + foreach ((array) $value as $dir) { + $realDir = realpath($rootPath . DIRECTORY_SEPARATOR . $dir); + if (false !== $realDir) { + $dirs[$realDir] = true; + } + } + } + + return $dirs; + } + /** @return list */ private function scanDirectory(string $dir, string $namespace): array {