diff --git a/conf/config.neon b/conf/config.neon index 7dd13295de..8f7006699e 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -499,6 +499,9 @@ services: - class: PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedDirectorySourceLocatorRepository + - + implement: PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedPsrAutoloaderLocatorFactory + - implement: PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedSingleFileSourceLocatorFactory diff --git a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php index 8ac02389b6..1dbe18062b 100644 --- a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php +++ b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php @@ -125,6 +125,14 @@ public function create(): SourceLocator $locators[] = $this->optimizedSingleFileSourceLocatorRepository->getOrCreate($this->singleReflectionFile); } + foreach ($this->composerAutoloaderProjectPaths as $composerAutoloaderProjectPath) { + $locator = $this->composerJsonAndInstalledJsonSourceLocatorMaker->create($composerAutoloaderProjectPath); + if ($locator === null) { + continue; + } + $locators[] = $locator; + } + $analysedDirectories = []; $analysedFiles = []; @@ -156,13 +164,6 @@ public function create(): SourceLocator }); $locators[] = new SkipClassAliasSourceLocator(new PhpInternalSourceLocator($astLocator, $this->phpstormStubsSourceStubber)); $locators[] = $this->autoloadSourceLocator; - foreach ($this->composerAutoloaderProjectPaths as $composerAutoloaderProjectPath) { - $locator = $this->composerJsonAndInstalledJsonSourceLocatorMaker->create($composerAutoloaderProjectPath); - if ($locator === null) { - continue; - } - $locators[] = $locator; - } $locators[] = new PhpInternalSourceLocator($astLocator, $this->reflectionSourceStubber); $locators[] = new EvaledCodeSourceLocator($astLocator, $this->reflectionSourceStubber); diff --git a/src/Reflection/BetterReflection/SourceLocator/ComposerJsonAndInstalledJsonSourceLocatorMaker.php b/src/Reflection/BetterReflection/SourceLocator/ComposerJsonAndInstalledJsonSourceLocatorMaker.php index 7fd42ad80f..0f794e6ec3 100644 --- a/src/Reflection/BetterReflection/SourceLocator/ComposerJsonAndInstalledJsonSourceLocatorMaker.php +++ b/src/Reflection/BetterReflection/SourceLocator/ComposerJsonAndInstalledJsonSourceLocatorMaker.php @@ -5,18 +5,28 @@ use Nette\Utils\Json; use PHPStan\File\FileReader; use Roave\BetterReflection\SourceLocator\Type\AggregateSourceLocator; +use Roave\BetterReflection\SourceLocator\Type\Composer\Psr\Psr0Mapping; +use Roave\BetterReflection\SourceLocator\Type\Composer\Psr\Psr4Mapping; use Roave\BetterReflection\SourceLocator\Type\SourceLocator; class ComposerJsonAndInstalledJsonSourceLocatorMaker { + private \PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedDirectorySourceLocatorRepository $optimizedDirectorySourceLocatorRepository; + private \PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedSingleFileSourceLocatorRepository $optimizedSingleFileSourceLocatorRepository; + private \PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedPsrAutoloaderLocatorFactory $optimizedPsrAutoloaderLocatorFactory; + public function __construct( - OptimizedSingleFileSourceLocatorRepository $optimizedSingleFileSourceLocatorRepository + OptimizedDirectorySourceLocatorRepository $optimizedDirectorySourceLocatorRepository, + OptimizedSingleFileSourceLocatorRepository $optimizedSingleFileSourceLocatorRepository, + OptimizedPsrAutoloaderLocatorFactory $optimizedPsrAutoloaderLocatorFactory ) { + $this->optimizedDirectorySourceLocatorRepository = $optimizedDirectorySourceLocatorRepository; $this->optimizedSingleFileSourceLocatorRepository = $optimizedSingleFileSourceLocatorRepository; + $this->optimizedPsrAutoloaderLocatorFactory = $optimizedPsrAutoloaderLocatorFactory; } public function create(string $projectInstallationPath): ?SourceLocator @@ -48,6 +58,17 @@ public function create(string $projectInstallationPath): ?SourceLocator $installed = $installedJson['packages'] ?? $installedJson; + $classMapPaths = array_merge( + $this->prefixPaths($this->packageToClassMapPaths($composer), $projectInstallationPath . '/'), + ...array_map(function (array $package) use ($projectInstallationPath, $installedJsonDirectoryPath): array { + return $this->prefixPaths( + $this->packageToClassMapPaths($package), + $this->packagePrefixPath($projectInstallationPath, $installedJsonDirectoryPath, $package) + ); + }, $installed) + ); + $classMapFiles = array_filter($classMapPaths, 'is_file'); + $classMapDirectories = array_filter($classMapPaths, 'is_dir'); $filePaths = array_merge( $this->prefixPaths($this->packageToFilePaths($composer), $projectInstallationPath . '/'), ...array_map(function (array $package) use ($projectInstallationPath, $installedJsonDirectoryPath): array { @@ -59,7 +80,42 @@ public function create(string $projectInstallationPath): ?SourceLocator ); $locators = []; - foreach ($filePaths as $file) { + $locators[] = $this->optimizedPsrAutoloaderLocatorFactory->create( + Psr4Mapping::fromArrayMappings(array_merge_recursive( + $this->prefixWithInstallationPath($this->packageToPsr4AutoloadNamespaces($composer), $projectInstallationPath), + ...array_map(function (array $package) use ($projectInstallationPath, $installedJsonDirectoryPath): array { + return $this->prefixWithPackagePath( + $this->packageToPsr4AutoloadNamespaces($package), + $projectInstallationPath, + $installedJsonDirectoryPath, + $package + ); + }, $installed) + )) + ); + + $locators[] = $this->optimizedPsrAutoloaderLocatorFactory->create( + Psr0Mapping::fromArrayMappings(array_merge_recursive( + $this->prefixWithInstallationPath($this->packageToPsr0AutoloadNamespaces($composer), $projectInstallationPath), + ...array_map(function (array $package) use ($projectInstallationPath, $installedJsonDirectoryPath): array { + return $this->prefixWithPackagePath( + $this->packageToPsr0AutoloadNamespaces($package), + $projectInstallationPath, + $installedJsonDirectoryPath, + $package + ); + }, $installed) + )) + ); + + foreach ($classMapDirectories as $classMapDirectory) { + if (!is_dir($classMapDirectory)) { + continue; + } + $locators[] = $this->optimizedDirectorySourceLocatorRepository->getOrCreate($classMapDirectory); + } + + foreach (array_merge($classMapFiles, $filePaths) as $file) { if (!is_file($file)) { continue; } @@ -69,6 +125,40 @@ public function create(string $projectInstallationPath): ?SourceLocator return new AggregateSourceLocator($locators); } + /** + * @param mixed[] $package + * + * @return array> + */ + private function packageToPsr4AutoloadNamespaces(array $package): array + { + return array_map(static function ($namespacePaths): array { + return (array) $namespacePaths; + }, $package['autoload']['psr-4'] ?? []); + } + + /** + * @param mixed[] $package + * + * @return array> + */ + private function packageToPsr0AutoloadNamespaces(array $package): array + { + return array_map(static function ($namespacePaths): array { + return (array) $namespacePaths; + }, $package['autoload']['psr-0'] ?? []); + } + + /** + * @param mixed[] $package + * + * @return array + */ + private function packageToClassMapPaths(array $package): array + { + return $package['autoload']['classmap'] ?? []; + } + /** * @param mixed[] $package * @@ -95,6 +185,33 @@ private function packagePrefixPath( return $projectInstallationPath . '/vendor/' . $package['name'] . '/'; } + /** + * @param array> $paths + * @param array> $package + * + * @return array> + */ + private function prefixWithPackagePath(array $paths, string $projectInstallationPath, string $installedJsonDirectoryPath, array $package): array + { + $prefix = $this->packagePrefixPath($projectInstallationPath, $installedJsonDirectoryPath, $package); + + return array_map(function (array $paths) use ($prefix): array { + return $this->prefixPaths($paths, $prefix); + }, $paths); + } + + /** + * @param array> $paths + * + * @return array> + */ + private function prefixWithInstallationPath(array $paths, string $trimmedInstallationPath): array + { + return array_map(function (array $paths) use ($trimmedInstallationPath): array { + return $this->prefixPaths($paths, $trimmedInstallationPath . '/'); + }, $paths); + } + /** * @param array $paths * diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedPsrAutoloaderLocator.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedPsrAutoloaderLocator.php new file mode 100644 index 0000000000..7b67698360 --- /dev/null +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedPsrAutoloaderLocator.php @@ -0,0 +1,54 @@ +mapping = $mapping; + $this->optimizedSingleFileSourceLocatorRepository = $optimizedSingleFileSourceLocatorRepository; + } + + public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection + { + foreach ($this->mapping->resolvePossibleFilePaths($identifier) as $file) { + if (!file_exists($file)) { + continue; + } + + $reflection = $this->optimizedSingleFileSourceLocatorRepository->getOrCreate($file)->locateIdentifier($reflector, $identifier); + if ($reflection === null) { + continue; + } + + return $reflection; + } + + return null; + } + + /** + * @return Reflection[] + */ + public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array + { + return []; // todo + } + +} diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedPsrAutoloaderLocatorFactory.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedPsrAutoloaderLocatorFactory.php new file mode 100644 index 0000000000..36f31e94a7 --- /dev/null +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedPsrAutoloaderLocatorFactory.php @@ -0,0 +1,12 @@ +