From c88960791d9a007c8205bf58f38e7bffb9462f97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20Perr=C3=83=3F=3F=C3=83=3F=C3=82=C2=A9al?= Date: Sun, 17 Nov 2019 11:05:05 +0100 Subject: [PATCH] Guess dependency of class symbols using installed packages. Use BetterReflection. Do not work with functions nor constants. Do not support https://getcomposer.org/doc/06-config.md#vendor-dir --- composer.json | 2 + .../Cli/CheckCommand.php | 2 + .../DependencyGuesser/DependencyGuesser.php | 5 ++ .../GuessFromComposerInstalledJson.php | 63 +++++++++++++++++++ .../GuessFromComposerInstalledJsonTest.php | 42 +++++++++++++ 5 files changed, 114 insertions(+) create mode 100644 src/ComposerRequireChecker/DependencyGuesser/GuessFromComposerInstalledJson.php create mode 100644 test/ComposerRequireCheckerTest/DependencyGuesser/GuessFromComposerInstalledJsonTest.php diff --git a/composer.json b/composer.json index 67cc2ce2..c4a3e935 100644 --- a/composer.json +++ b/composer.json @@ -31,6 +31,8 @@ "ext-phar": "*", "nikic/php-parser": "^4.2", "ocramius/package-versions": "^1.4.0", + "phpdocumentor/type-resolver": "^0.4", + "roave/better-reflection": "^3.5.0", "symfony/console": "^4.3.6", "webmozart/glob": "^4.1" }, diff --git a/src/ComposerRequireChecker/Cli/CheckCommand.php b/src/ComposerRequireChecker/Cli/CheckCommand.php index b6e26c44..54ba3d44 100644 --- a/src/ComposerRequireChecker/Cli/CheckCommand.php +++ b/src/ComposerRequireChecker/Cli/CheckCommand.php @@ -7,6 +7,7 @@ use ComposerRequireChecker\DefinedSymbolsLocator\LocateDefinedSymbolsFromASTRoots; use ComposerRequireChecker\DefinedSymbolsLocator\LocateDefinedSymbolsFromExtensions; use ComposerRequireChecker\DependencyGuesser\DependencyGuesser; +use ComposerRequireChecker\DependencyGuesser\GuessFromComposerInstalledJson; use ComposerRequireChecker\FileLocator\LocateComposerPackageDirectDependenciesSourceFiles; use ComposerRequireChecker\FileLocator\LocateComposerPackageSourceFiles; use ComposerRequireChecker\FileLocator\LocateFilesByGlobPattern; @@ -116,6 +117,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $table = new Table($output); $table->setHeaders(['unknown symbol', 'guessed dependency']); $guesser = new DependencyGuesser($options); + $guesser->addGuesser(new GuessFromComposerInstalledJson(dirname($composerJson))); foreach ($unknownSymbols as $unknownSymbol) { $guessedDependencies = []; foreach ($guesser($unknownSymbol) as $guessedDependency) { diff --git a/src/ComposerRequireChecker/DependencyGuesser/DependencyGuesser.php b/src/ComposerRequireChecker/DependencyGuesser/DependencyGuesser.php index ac1714b2..20bbe9fd 100644 --- a/src/ComposerRequireChecker/DependencyGuesser/DependencyGuesser.php +++ b/src/ComposerRequireChecker/DependencyGuesser/DependencyGuesser.php @@ -17,6 +17,11 @@ public function __construct(?Options $options = null) $this->guessers[] = new GuessFromLoadedExtensions($options); } + public function addGuesser(GuesserInterface $guesser): void + { + $this->guessers[] = $guesser; + } + public function __invoke($symbolName): \Generator { foreach ($this->guessers as $guesser) { diff --git a/src/ComposerRequireChecker/DependencyGuesser/GuessFromComposerInstalledJson.php b/src/ComposerRequireChecker/DependencyGuesser/GuessFromComposerInstalledJson.php new file mode 100644 index 00000000..46a2a2ec --- /dev/null +++ b/src/ComposerRequireChecker/DependencyGuesser/GuessFromComposerInstalledJson.php @@ -0,0 +1,63 @@ +sourceLocator = new MemoizingSourceLocator( + (new MakeLocatorForInstalledJson())( + $projectPath, + (new BetterReflection())->astLocator() + ) + ); + + $this->reflector = new ClassReflector($this->sourceLocator); + + // @todo: support https://getcomposer.org/doc/06-config.md#vendor-dir; useless as BetterReflection does not, at this moment. + $cleanPath = preg_quote(str_replace(DIRECTORY_SEPARATOR, '/', $projectPath) . '/' . 'vendor', '@'); + + $this->pathRegex = '@^' . $cleanPath . '/(?:composer/\.\./)?([^/]+/[^/]+)/@'; + } + + public function __invoke(string $symbolName): \Generator + { + $reflection = $this->sourceLocator->locateIdentifier($this->reflector, new Identifier($symbolName, new IdentifierType(IdentifierType::IDENTIFIER_CLASS))); + + if (!($reflection instanceof ReflectionClass)) { + return; + } + + $path = $reflection->getFileName(); + + if (preg_match($this->pathRegex, $path, $captures)) { + yield $captures[1]; + } + } +} diff --git a/test/ComposerRequireCheckerTest/DependencyGuesser/GuessFromComposerInstalledJsonTest.php b/test/ComposerRequireCheckerTest/DependencyGuesser/GuessFromComposerInstalledJsonTest.php new file mode 100644 index 00000000..48cf56ec --- /dev/null +++ b/test/ComposerRequireCheckerTest/DependencyGuesser/GuessFromComposerInstalledJsonTest.php @@ -0,0 +1,42 @@ +guesser = new GuessFromComposerInstalledJson(dirname(__DIR__, 3)); + } + + public function testGuessVendorClass(): void + { + $result = ($this->guesser)(TestCase::class); + + self::assertNotEmpty($result); + self::assertContains('phpunit/phpunit', $result); + } + + public function testDoNotGuessVendorFunction(): void + { + $result = iterator_to_array(($this->guesser)('DeepCopy\deep_copy')); + + self::assertEmpty($result); + } + + + public function testDoNotGuessClassFromProject(): void + { + $result = iterator_to_array(($this->guesser)(GuessFromComposerInstalledJson::class)); + + self::assertEmpty($result); + } +}