From b9eb41c6694e9e2c30a1609e920784409f933c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Fr=C3=B6mer?= Date: Tue, 4 Jan 2022 16:11:23 +0100 Subject: [PATCH 1/2] Resolve #252: Integrate symfony/console and split out composer plugin implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Andreas Frömer --- CHANGELOG.md | 8 +- Makefile | 4 +- bin/composer-unused | 16 +-- composer.json | 16 ++- config/service_manager.php | 2 + docker-compose.yml | 2 + phpstan.neon | 1 + src/Command/CollectConsumedSymbolsCommand.php | 2 +- .../CollectConsumedSymbolsCommandHandler.php | 4 +- ...lectFilteredDependenciesCommandHandler.php | 5 +- ...lectRequiredDependenciesCommandHandler.php | 13 ++- .../LoadRequiredDependenciesCommand.php | 10 +- src/Composer/Config.php | 48 ++++++++ src/Composer/ConfigFactory.php | 29 +++++ src/Composer/Link.php | 22 ++++ src/Composer/LocalRepository.php | 46 ++++++++ src/Composer/Package.php | 71 ++++++++++++ src/Console/Command/UnusedCommand.php | 40 +++++-- src/Console/Command/UnusedCommandFactory.php | 2 + src/Dependency/InvalidDependency.php | 6 +- src/Dependency/RequiredDependency.php | 4 +- src/PackageResolver.php | 22 ++-- src/Symbol/ConsumedSymbolLoaderBuilder.php | 6 +- src/Symbol/ProvidedSymbolLoaderBuilder.php | 5 +- src/UnusedPlugin.php | 68 ----------- tests/Integration/Di/ServiceContainerTest.php | 11 -- tests/Integration/UnusedCommandTest.php | 107 ++++++------------ .../Collection/DependencyCollectionTest.php | 2 +- .../Dependency/RequiredDependencyTest.php | 19 ++-- .../vendor/dummy/test-package/composer.json | 5 + .../vendor/dummy/test-package/composer.json | 5 + 31 files changed, 367 insertions(+), 234 deletions(-) create mode 100644 src/Composer/Config.php create mode 100644 src/Composer/ConfigFactory.php create mode 100644 src/Composer/Link.php create mode 100644 src/Composer/LocalRepository.php create mode 100644 src/Composer/Package.php delete mode 100644 src/UnusedPlugin.php create mode 100644 tests/assets/TestProjects/IgnoreExcludedPackages/vendor/dummy/test-package/composer.json create mode 100644 tests/assets/TestProjects/IgnorePatternPackages/vendor/dummy/test-package/composer.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 756e4524..67570f4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,14 @@ ## [Unreleased] - TBA ### Added - +- Added CLI argument `composer-json` which can be used to parse external projects. + This will default to the current working directory. ### Changed - +- Change `bin/composer-unused` to be used as standalone binary +- Package type is now `library` instead of `composer-plugin` ### Removed +- Removed ability to work as `composer-plugin` (will be supported using `composer-unused/composer-unused-plugin`) +- Dropped support for php `7.3` ## [0.7.7] - 2021-07-26 ### Added diff --git a/Makefile b/Makefile index 7317ad50..63591f8b 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -PHP_VERSION=8.0 +PHP_VERSION=7.4 up: ## Run all containers in all versions docker compose up -d @@ -39,7 +39,7 @@ box: ## Compile /build/composer-unused.phar docker compose run php$(PHP_VERSION) php box.phar compile ssh: ## SSH into container - docker compose run $(PHP_VERSION) /bin/sh + docker compose run php$(PHP_VERSION) /bin/sh help: ## Displays this list of targets with descriptions @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}' diff --git a/bin/composer-unused b/bin/composer-unused index 581f043e..3ad7071a 100755 --- a/bin/composer-unused +++ b/bin/composer-unused @@ -2,10 +2,9 @@ register(IOInterface::class, $application->getIO()); - $container->register(Composer::class, $application->getComposer()); - $application->add($container->get(UnusedCommand::class)); - - // Add 'unused' command if necessary - if (!isset($argv[1]) || $argv[1] !== 'unused') { - $argv = array_merge([$argv[0], 'unused'], array_slice($argv, 1)); - } + $application->setDefaultCommand('unused', true); $application->run(new ArgvInput($argv)); })($argv); diff --git a/composer.json b/composer.json index ae99529e..129b554e 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "icanhazstring/composer-unused", - "type": "composer-plugin", + "type": "library", "description": "Show unused packages by scanning your code", "keywords": [ "composer", @@ -17,17 +17,18 @@ } ], "require": { - "php": "^7.3 || ^8.0", - "composer-plugin-api": "^2.0", - "composer-unused/symbol-parser": "^0.1.3", + "php": "^7.4 || ^8.0", + "composer-unused/contracts": "dev-main", + "composer-unused/symbol-parser": "dev-main", "nikic/php-parser": "^4.13", "psr/container": "^1.0 || ^2.0", - "psr/log": "^1.1 || ^2 || ^3" + "psr/log": "^1.1 || ^2 || ^3", + "symfony/console": "^4.4 || ^5.4 || ^6.0", + "symfony/serializer": "^4.4 || ^5.4 || ^6.0" }, "require-dev": { "ext-ds": "*", "ext-zend-opcache": "*", - "composer/composer": "^2.2.3", "jangregor/phpstan-prophecy": "^0.8.1", "phpspec/prophecy-phpunit": "^2.0.1", "phpstan/phpstan": "^0.12.99", @@ -39,9 +40,6 @@ "preferred-install": "dist", "sort-packages": true }, - "extra": { - "class": "Icanhazstring\\Composer\\Unused\\UnusedPlugin" - }, "autoload": { "psr-4": { "Icanhazstring\\Composer\\Unused\\": "src" diff --git a/config/service_manager.php b/config/service_manager.php index 5886b570..b01d3e65 100644 --- a/config/service_manager.php +++ b/config/service_manager.php @@ -8,6 +8,7 @@ use Icanhazstring\Composer\Unused\Command\Handler\Factory\CollectConsumedSymbolsCommandHandlerFactory; use Icanhazstring\Composer\Unused\Command\Handler\Factory\CollectFilteredDependenciesCommandHandlerFactory; use Icanhazstring\Composer\Unused\Command\Handler\Factory\CollectRequiredDependenciesCommandHandlerFactory; +use Icanhazstring\Composer\Unused\Composer\ConfigFactory; use Icanhazstring\Composer\Unused\Console\Command\UnusedCommand; use Icanhazstring\Composer\Unused\Console\Command\UnusedCommandFactory; use Icanhazstring\Composer\Unused\Di\InvokableFactory; @@ -24,5 +25,6 @@ ConsumedSymbolLoaderBuilder::class => InvokableFactory::class, ProvidedSymbolLoaderBuilder::class => InvokableFactory::class, PackageResolver::class => InvokableFactory::class, + ConfigFactory::class => InvokableFactory::class, ] ]; diff --git a/docker-compose.yml b/docker-compose.yml index 1ac34105..2ef84c5c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,8 @@ services: container_name: composer-unused-7.4 volumes: - .:/docker/composer-unused:rw + - ../contracts:/docker/contracts:rw + - ../symbol-parser:/docker/symbol-parser:rw tty: true php8.0: diff --git a/phpstan.neon b/phpstan.neon index bce0f27d..ac12398b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -9,6 +9,7 @@ parameters: level: max inferPrivatePropertyTypeFromConstructor: true paths: + - bin/ - src/ - tests/ tmpDir: data diff --git a/src/Command/CollectConsumedSymbolsCommand.php b/src/Command/CollectConsumedSymbolsCommand.php index c68f1e98..dc06da84 100644 --- a/src/Command/CollectConsumedSymbolsCommand.php +++ b/src/Command/CollectConsumedSymbolsCommand.php @@ -4,7 +4,7 @@ namespace Icanhazstring\Composer\Unused\Command; -use Composer\Package\PackageInterface; +use ComposerUnused\Contracts\PackageInterface; final class CollectConsumedSymbolsCommand { diff --git a/src/Command/Handler/CollectConsumedSymbolsCommandHandler.php b/src/Command/Handler/CollectConsumedSymbolsCommandHandler.php index d2bc0543..7229b717 100644 --- a/src/Command/Handler/CollectConsumedSymbolsCommandHandler.php +++ b/src/Command/Handler/CollectConsumedSymbolsCommandHandler.php @@ -29,7 +29,7 @@ public function __construct(ConsumedSymbolLoaderBuilder $consumedSymbolLoaderBui public function collect(CollectConsumedSymbolsCommand $command): Generator { $package = $command->getPackage(); - $symbolLoader = $this->consumedSymbolLoaderBuilder->build($command->getPackageRoot()); + $symbolLoader = $this->consumedSymbolLoaderBuilder->build(); $rootNamespaces = array_merge( array_keys($package->getAutoload()['psr-0'] ?? []), @@ -38,7 +38,7 @@ public function collect(CollectConsumedSymbolsCommand $command): Generator yield from $this->filterRootPackageSymbols( $rootNamespaces, - $symbolLoader->load($package) + $symbolLoader->withBaseDir($command->getPackageRoot())->load($package) ); } diff --git a/src/Command/Handler/CollectFilteredDependenciesCommandHandler.php b/src/Command/Handler/CollectFilteredDependenciesCommandHandler.php index 147c79b4..b5b3ac91 100644 --- a/src/Command/Handler/CollectFilteredDependenciesCommandHandler.php +++ b/src/Command/Handler/CollectFilteredDependenciesCommandHandler.php @@ -6,6 +6,7 @@ use Icanhazstring\Composer\Unused\Command\FilterDependencyCollectionCommand; use Icanhazstring\Composer\Unused\Dependency\DependencyCollection; +use Icanhazstring\Composer\Unused\Dependency\DependencyInterface; final class CollectFilteredDependenciesCommandHandler { @@ -14,11 +15,11 @@ public function collect(FilterDependencyCollectionCommand $command): DependencyC $namedExclusion = $command->getNamedExclusion(); $patternExclusion = $command->getPatternExclusion(); - return $command->getRequiredDependencyCollection()->filter(static function ($dependency) use ( + return $command->getRequiredDependencyCollection()->filter(static function (DependencyInterface $dependency) use ( $namedExclusion, $patternExclusion ) { - if (in_array($dependency->getName(), $namedExclusion)) { + if (in_array($dependency->getName(), $namedExclusion, true)) { return false; } diff --git a/src/Command/Handler/CollectRequiredDependenciesCommandHandler.php b/src/Command/Handler/CollectRequiredDependenciesCommandHandler.php index bbc3e8e8..083686c7 100644 --- a/src/Command/Handler/CollectRequiredDependenciesCommandHandler.php +++ b/src/Command/Handler/CollectRequiredDependenciesCommandHandler.php @@ -30,7 +30,7 @@ public function __construct( public function collect(LoadRequiredDependenciesCommand $command): DependencyCollection { $dependencyCollection = new DependencyCollection(); - $providedSymbolLoader = $this->providedSymbolLoaderBuilder->build($command->getBaseDir()); + $providedSymbolLoader = $this->providedSymbolLoaderBuilder->build(); foreach ($command->getPackageLinks() as $require) { $composerPackage = $this->packageResolver->resolve( @@ -39,15 +39,22 @@ public function collect(LoadRequiredDependenciesCommand $command): DependencyCol ); if ($composerPackage === null) { - $dependencyCollection->add(new InvalidDependency($require, 'Unable to locate package')); + $dependencyCollection->add( + new InvalidDependency( + $require, + 'Dependency can\'t be located. Maybe not installed?' + ) + ); continue; } + $packageBaseDir = $command->getBaseDir() . DIRECTORY_SEPARATOR . $composerPackage->getName(); + $dependencyCollection->add( new RequiredDependency( $composerPackage, (new SymbolList())->addAll( - $providedSymbolLoader->load($composerPackage) + $providedSymbolLoader->withBaseDir($packageBaseDir)->load($composerPackage) ) ) ); diff --git a/src/Command/LoadRequiredDependenciesCommand.php b/src/Command/LoadRequiredDependenciesCommand.php index 06859cd1..9cb53b37 100644 --- a/src/Command/LoadRequiredDependenciesCommand.php +++ b/src/Command/LoadRequiredDependenciesCommand.php @@ -4,20 +4,20 @@ namespace Icanhazstring\Composer\Unused\Command; -use Composer\Package\Link; -use Composer\Repository\RepositoryInterface; +use ComposerUnused\Contracts\LinkInterface; +use ComposerUnused\Contracts\RepositoryInterface; final class LoadRequiredDependenciesCommand { /** @var string */ private $baseDir; - /** @var array */ + /** @var array */ private $packageLinks; /** @var RepositoryInterface */ private $packageRepository; /** - * @param array $packageLinks + * @param array $packageLinks */ public function __construct(string $baseDir, array $packageLinks, RepositoryInterface $packageRepository) { @@ -32,7 +32,7 @@ public function getBaseDir(): string } /** - * @return array + * @return array */ public function getPackageLinks(): array { diff --git a/src/Composer/Config.php b/src/Composer/Config.php new file mode 100644 index 00000000..6f8433f1 --- /dev/null +++ b/src/Composer/Config.php @@ -0,0 +1,48 @@ + */ + protected array $config = []; + protected string $name; + /** @var array */ + private array $require = []; + /** @var array */ + private array $autoload = []; + + public function getName(): string + { + return $this->name; + } + + /** + * @return array + */ + public function getRequire(): array + { + return $this->require; + } + + /** + * @return array + */ + public function getAutoload(): array + { + return $this->autoload; + } + + public function get(string $property): string + { + $value = $this->config[$property] ?? null; + + if ($property === 'vendor-dir') { + $value = $value ?? 'vendor'; + } + + return $value; + } +} diff --git a/src/Composer/ConfigFactory.php b/src/Composer/ConfigFactory.php new file mode 100644 index 00000000..73dbcda6 --- /dev/null +++ b/src/Composer/ConfigFactory.php @@ -0,0 +1,29 @@ +serializer = new Serializer($normalizers, $encoders); + } + + public function fromComposerJson(string $jsonContent): Config + { + return $this->serializer->deserialize($jsonContent, Config::class, 'json'); + } +} diff --git a/src/Composer/Link.php b/src/Composer/Link.php new file mode 100644 index 00000000..873305c4 --- /dev/null +++ b/src/Composer/Link.php @@ -0,0 +1,22 @@ +target = $target; + } + + public function getTarget(): string + { + return $this->target; + } +} diff --git a/src/Composer/LocalRepository.php b/src/Composer/LocalRepository.php new file mode 100644 index 00000000..0f540f26 --- /dev/null +++ b/src/Composer/LocalRepository.php @@ -0,0 +1,46 @@ +vendorDir = $vendorDir; + $encoders = [new JsonEncoder()]; + $normalizers = [new PropertyNormalizer(null, new CamelCaseToSnakeCaseNameConverter())]; + + $this->serializer = new Serializer($normalizers, $encoders); + } + + public function findPackage(string $name): ?PackageInterface + { + $packageDir = $this->vendorDir . DIRECTORY_SEPARATOR . $name; + $packageComposerJson = $packageDir . DIRECTORY_SEPARATOR . 'composer.json'; + + if (!is_dir($packageDir) && !file_exists($packageComposerJson)) { + return null; + } + + $jsonContent = file_get_contents($packageComposerJson); + + if ($jsonContent === false) { + return null; + } + + $config = $this->serializer->deserialize($jsonContent, Config::class, 'json'); + return Package::fromConfig($config); + } +} diff --git a/src/Composer/Package.php b/src/Composer/Package.php new file mode 100644 index 00000000..d3e89500 --- /dev/null +++ b/src/Composer/Package.php @@ -0,0 +1,71 @@ + */ + private array $requires = []; + /** @var array */ + private array $suggests = []; + /** @phpstan-var array{psr-0?: array, psr-4?: array, classmap?: list, files?: list} */ + private array $autoload = []; + + public static function fromConfig(Config $config): self + { + $package = new self($config->getName()); + $package->autoload = $config->getAutoload(); + $package->requires = array_map(static function (string $name) { + return new Link($name); + }, array_keys($config->getRequire())); + + return $package; + } + + public function __construct(string $name) + { + $this->name = $name; + } + + public function getAutoload(): array + { + return $this->autoload; + } + + public function getName(): string + { + return $this->name; + } + + public function getRequires(): array + { + return $this->requires; + } + + public function getSuggests(): array + { + return $this->suggests; + } + + /** + * @param array $requires + */ + public function setRequires(array $requires): void + { + $this->requires = $requires; + } + + /** + * @param array $suggests + */ + public function setSuggests(array $suggests): void + { + $this->suggests = $suggests; + } +} diff --git a/src/Console/Command/UnusedCommand.php b/src/Console/Command/UnusedCommand.php index 510876c2..9b662081 100644 --- a/src/Console/Command/UnusedCommand.php +++ b/src/Console/Command/UnusedCommand.php @@ -4,17 +4,21 @@ namespace Icanhazstring\Composer\Unused\Console\Command; -use Composer\Command\BaseCommand; use Icanhazstring\Composer\Unused\Command\CollectConsumedSymbolsCommand; use Icanhazstring\Composer\Unused\Command\FilterDependencyCollectionCommand; use Icanhazstring\Composer\Unused\Command\Handler\CollectConsumedSymbolsCommandHandler; use Icanhazstring\Composer\Unused\Command\Handler\CollectFilteredDependenciesCommandHandler; use Icanhazstring\Composer\Unused\Command\Handler\CollectRequiredDependenciesCommandHandler; use Icanhazstring\Composer\Unused\Command\LoadRequiredDependenciesCommand; +use Icanhazstring\Composer\Unused\Composer\ConfigFactory; +use Icanhazstring\Composer\Unused\Composer\LocalRepository; +use Icanhazstring\Composer\Unused\Composer\Package; use Icanhazstring\Composer\Unused\Dependency\DependencyCollection; use Icanhazstring\Composer\Unused\Dependency\DependencyInterface; use Icanhazstring\Composer\Unused\Dependency\InvalidDependency; use Icanhazstring\Composer\Unused\Dependency\RequiredDependency; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -24,7 +28,7 @@ use const DIRECTORY_SEPARATOR; -final class UnusedCommand extends BaseCommand +final class UnusedCommand extends Command { /** @var CollectConsumedSymbolsCommandHandler */ private $collectConsumedSymbolsCommandHandler; @@ -32,13 +36,16 @@ final class UnusedCommand extends BaseCommand private $collectRequiredDependenciesCommandHandler; /** @var CollectFilteredDependenciesCommandHandler */ private $collectFilteredDependenciesCommandHandler; + private ConfigFactory $configFactory; public function __construct( + ConfigFactory $configFactory, CollectConsumedSymbolsCommandHandler $collectConsumedSymbolsCommandHandler, CollectRequiredDependenciesCommandHandler $collectRequiredDependenciesCommandHandler, CollectFilteredDependenciesCommandHandler $collectFilteredDependenciesCommandHandler ) { parent::__construct('unused'); + $this->configFactory = $configFactory; $this->collectConsumedSymbolsCommandHandler = $collectConsumedSymbolsCommandHandler; $this->collectRequiredDependenciesCommandHandler = $collectRequiredDependenciesCommandHandler; $this->collectFilteredDependenciesCommandHandler = $collectFilteredDependenciesCommandHandler; @@ -50,6 +57,13 @@ protected function configure(): void 'Show unused packages by scanning and comparing package namespaces against your source.' ); + $this->addArgument( + 'composer-json', + InputArgument::OPTIONAL, + 'Provide a composer.json to be scanned', + getcwd() . DIRECTORY_SEPARATOR . 'composer.json' + ); + $this->addOption( 'excludeDir', null, @@ -83,15 +97,23 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $composer = $this->getComposer(); + $composerJsonPath = $input->getArgument('composer-json'); - if ($composer === null) { - // TODO IO Error + if (!file_exists($composerJsonPath) && !is_readable($composerJsonPath)) { return 1; } - $baseDir = dirname($composer->getConfig()->getConfigSource()->getName()); - $rootPackage = $composer->getPackage(); + $composerJson = file_get_contents($composerJsonPath); + + if ($composerJson === false) { + return 1; + } + + $config = $this->configFactory->fromComposerJson($composerJson); + $baseDir = dirname($composerJsonPath); + + $rootPackage = Package::fromConfig($config); + $localRepository = new LocalRepository($baseDir . DIRECTORY_SEPARATOR . $config->get('vendor-dir')); $consumedSymbols = $this->collectConsumedSymbolsCommandHandler->collect( new CollectConsumedSymbolsCommand( @@ -102,9 +124,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int $unfilteredRequiredDependencyCollection = $this->collectRequiredDependenciesCommandHandler->collect( new LoadRequiredDependenciesCommand( - $baseDir . DIRECTORY_SEPARATOR . $composer->getConfig()->get('vendor-dir'), + $baseDir . DIRECTORY_SEPARATOR . $config->get('vendor-dir'), $rootPackage->getRequires(), - $composer->getRepositoryManager()->getLocalRepository() + $localRepository ) ); diff --git a/src/Console/Command/UnusedCommandFactory.php b/src/Console/Command/UnusedCommandFactory.php index 8bb56122..be341e09 100644 --- a/src/Console/Command/UnusedCommandFactory.php +++ b/src/Console/Command/UnusedCommandFactory.php @@ -7,6 +7,7 @@ use Icanhazstring\Composer\Unused\Command\Handler\CollectConsumedSymbolsCommandHandler; use Icanhazstring\Composer\Unused\Command\Handler\CollectFilteredDependenciesCommandHandler; use Icanhazstring\Composer\Unused\Command\Handler\CollectRequiredDependenciesCommandHandler; +use Icanhazstring\Composer\Unused\Composer\ConfigFactory; use Psr\Container\ContainerInterface; final class UnusedCommandFactory @@ -14,6 +15,7 @@ final class UnusedCommandFactory public function __invoke(ContainerInterface $container): UnusedCommand { return new UnusedCommand( + $container->get(ConfigFactory::class), $container->get(CollectConsumedSymbolsCommandHandler::class), $container->get(CollectRequiredDependenciesCommandHandler::class), $container->get(CollectFilteredDependenciesCommandHandler::class), diff --git a/src/Dependency/InvalidDependency.php b/src/Dependency/InvalidDependency.php index 0e97ea45..e648fb6d 100644 --- a/src/Dependency/InvalidDependency.php +++ b/src/Dependency/InvalidDependency.php @@ -4,17 +4,17 @@ namespace Icanhazstring\Composer\Unused\Dependency; -use Composer\Package\Link; +use ComposerUnused\Contracts\LinkInterface; use ComposerUnused\SymbolParser\Symbol\SymbolInterface; final class InvalidDependency implements DependencyInterface { - /** @var Link */ + /** @var LinkInterface */ private $linkedPackage; /** @var string */ private $reason; - public function __construct(Link $linkedPackage, string $reason) + public function __construct(LinkInterface $linkedPackage, string $reason) { $this->linkedPackage = $linkedPackage; $this->reason = $reason; diff --git a/src/Dependency/RequiredDependency.php b/src/Dependency/RequiredDependency.php index 88ad8e56..5e1a1f4c 100644 --- a/src/Dependency/RequiredDependency.php +++ b/src/Dependency/RequiredDependency.php @@ -4,7 +4,7 @@ namespace Icanhazstring\Composer\Unused\Dependency; -use Composer\Package\PackageInterface; +use ComposerUnused\Contracts\PackageInterface; use ComposerUnused\SymbolParser\Symbol\SymbolInterface; use ComposerUnused\SymbolParser\Symbol\SymbolListInterface; @@ -62,7 +62,7 @@ public function requires(DependencyInterface $dependency): bool public function suggests(DependencyInterface $dependency): bool { - return array_key_exists($dependency->getName(), $this->package->getSuggests()); + return in_array($dependency->getName(), $this->package->getSuggests(), true); } public function requiredBy(DependencyInterface $dependency): void diff --git a/src/PackageResolver.php b/src/PackageResolver.php index fc48c28f..c4f16c34 100644 --- a/src/PackageResolver.php +++ b/src/PackageResolver.php @@ -4,28 +4,24 @@ namespace Icanhazstring\Composer\Unused; -use Composer\Package\Link; -use Composer\Package\Package; -use Composer\Package\PackageInterface; -use Composer\Repository\RepositoryInterface; +use ComposerUnused\Contracts\LinkInterface; +use ComposerUnused\Contracts\PackageInterface; +use ComposerUnused\Contracts\RepositoryInterface; +use Icanhazstring\Composer\Unused\Composer\Package; final class PackageResolver { public function resolve( - Link $package, + LinkInterface $packageLink, RepositoryInterface $repository ): ?PackageInterface { - $isPhp = strpos($package->getTarget(), 'php') === 0; - $isExtension = strpos($package->getTarget(), 'ext-') === 0; + $isPhp = strpos($packageLink->getTarget(), 'php') === 0; + $isExtension = strpos($packageLink->getTarget(), 'ext-') === 0; if ($isPhp || $isExtension) { - return new Package( - strtolower($package->getTarget()), - '*', - '*' - ); + return new Package(strtolower($packageLink->getTarget())); } - return $repository->findPackage($package->getTarget(), $package->getConstraint()); + return $repository->findPackage($packageLink->getTarget()); } } diff --git a/src/Symbol/ConsumedSymbolLoaderBuilder.php b/src/Symbol/ConsumedSymbolLoaderBuilder.php index 09d96e55..3acccb9b 100644 --- a/src/Symbol/ConsumedSymbolLoaderBuilder.php +++ b/src/Symbol/ConsumedSymbolLoaderBuilder.php @@ -5,6 +5,7 @@ namespace Icanhazstring\Composer\Unused\Symbol; use ComposerUnused\SymbolParser\File\FileContentProvider; +use ComposerUnused\SymbolParser\Parser\PHP\AutoloadType; use ComposerUnused\SymbolParser\Parser\PHP\ConsumedSymbolCollector; use ComposerUnused\SymbolParser\Parser\PHP\Strategy\ClassConstStrategy; use ComposerUnused\SymbolParser\Parser\PHP\Strategy\NewStrategy; @@ -22,7 +23,7 @@ final class ConsumedSymbolLoaderBuilder { - public function build(string $packageRoot): SymbolLoaderInterface + public function build(): SymbolLoaderInterface { $usedSymbolCollector = new ConsumedSymbolCollector( [ @@ -49,9 +50,8 @@ public function build(string $packageRoot): SymbolLoaderInterface ); return new FileSymbolLoader( - $packageRoot, $fileSymbolProvider, - ['classmap', 'files', 'psr-0', 'psr-4'] + AutoloadType::all() ); } } diff --git a/src/Symbol/ProvidedSymbolLoaderBuilder.php b/src/Symbol/ProvidedSymbolLoaderBuilder.php index 4e647864..b3efa6ef 100644 --- a/src/Symbol/ProvidedSymbolLoaderBuilder.php +++ b/src/Symbol/ProvidedSymbolLoaderBuilder.php @@ -5,6 +5,7 @@ namespace Icanhazstring\Composer\Unused\Symbol; use ComposerUnused\SymbolParser\File\FileContentProvider; +use ComposerUnused\SymbolParser\Parser\PHP\AutoloadType; use ComposerUnused\SymbolParser\Parser\PHP\DefinedSymbolCollector; use ComposerUnused\SymbolParser\Parser\PHP\SymbolNameParser; use ComposerUnused\SymbolParser\Symbol\Loader\CompositeSymbolLoader; @@ -17,7 +18,7 @@ final class ProvidedSymbolLoaderBuilder { - public function build(string $packageRoot): SymbolLoaderInterface + public function build(): SymbolLoaderInterface { $symbolNameParser = new SymbolNameParser( (new ParserFactory())->create(ParserFactory::ONLY_PHP7), @@ -33,7 +34,7 @@ public function build(string $packageRoot): SymbolLoaderInterface [ new ExtensionSymbolLoader(), new PsrSymbolLoader(), - new FileSymbolLoader($packageRoot, $fileSymbolProvider, ['classmap', 'files']) + new FileSymbolLoader($fileSymbolProvider, [AutoloadType::CLASSMAP, AutoloadType::FILES]) ] ); } diff --git a/src/UnusedPlugin.php b/src/UnusedPlugin.php deleted file mode 100644 index 41d65487..00000000 --- a/src/UnusedPlugin.php +++ /dev/null @@ -1,68 +0,0 @@ -container = $plugin->container; - } - } - - public function activate(Composer $composer, IOInterface $io): void - { - $this->container = require __DIR__ . '/../config/container.php'; - - $this->container->register(IOInterface::class, $io); - $this->container->register(Composer::class, $composer); - } - - public function deactivate(Composer $composer, IOInterface $io): void - { - } - - public function uninstall(Composer $composer, IOInterface $io): void - { - } - - public function getCapabilities(): array - { - return [ - Plugin\Capability\CommandProvider::class => self::class - ]; - } - - /** - * @return array|BaseCommand[] - * @throws Exception - */ - public function getCommands(): array - { - return [ - $this->container->get(UnusedCommand::class) - ]; - } -} diff --git a/tests/Integration/Di/ServiceContainerTest.php b/tests/Integration/Di/ServiceContainerTest.php index 644b425e..d6d8a768 100644 --- a/tests/Integration/Di/ServiceContainerTest.php +++ b/tests/Integration/Di/ServiceContainerTest.php @@ -4,10 +4,6 @@ namespace Icanhazstring\Composer\Test\Unused\Integration\Di; -use Composer\Composer; -use Composer\IO\IOInterface; -use Composer\Repository\RepositoryManager; -use Composer\Repository\WritableRepositoryInterface; use Icanhazstring\Composer\Unused\Di\ServiceContainer; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; @@ -25,13 +21,6 @@ public function itShouldLoadServiceContainer(): void { /** @var ServiceContainer $container */ $container = require __DIR__ . '/../../../config/container.php'; - $container->register(IOInterface::class, $this->prophesize(IOInterface::class)->reveal()); - $repositoryManager = $this->prophesize(RepositoryManager::class); - $repositoryManager->getLocalRepository()->willReturn($this->prophesize(WritableRepositoryInterface::class)->reveal()); - $composer = $this->prophesize(Composer::class); - $composer->getRepositoryManager()->willReturn($repositoryManager->reveal()); - $container->register(Composer::class, $composer->reveal()); - $services = require __DIR__ . '/../../../config/service_manager.php'; foreach ($services['factories'] as $type => $factory) { diff --git a/tests/Integration/UnusedCommandTest.php b/tests/Integration/UnusedCommandTest.php index d987b28b..7a7047bc 100644 --- a/tests/Integration/UnusedCommandTest.php +++ b/tests/Integration/UnusedCommandTest.php @@ -4,15 +4,10 @@ namespace Icanhazstring\Composer\Test\Unused\Integration; -use Composer\Composer; -use Composer\Console\Application; -use Composer\IO\IOInterface; use Icanhazstring\Composer\Unused\Console\Command\UnusedCommand; use Icanhazstring\Composer\Unused\Di\ServiceContainer; use PHPUnit\Framework\TestCase; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\BufferedOutput; -use Symfony\Component\Console\Output\NullOutput; +use Symfony\Component\Console\Tester\CommandTester; class UnusedCommandTest extends TestCase { @@ -24,36 +19,17 @@ protected function setUp(): void $this->container = require __DIR__ . '/../../config/container.php'; } - private function getApplication(): Application - { - $application = new Application(); - $application->setAutoExit(false); - - $io = $application->getIO(); - /** @var Composer $composer */ - $composer = $application->getComposer(); - - $this->container->register(IOInterface::class, $io); - $this->container->register(Composer::class, $composer); - $application->add($this->container->get(UnusedCommand::class)); - - return $application; - } - /** * @test */ public function itShouldHaveZeroExitCodeOnEmptyRequirements(): void { chdir(__DIR__ . '/../assets/TestProjects/EmptyRequire'); + $commandTester = new CommandTester($this->container->get(UnusedCommand::class)); + + $exitCode = $commandTester->execute([]); - self::assertEquals( - 0, - $this->getApplication()->run( - new ArrayInput(['unused']), - new NullOutput() - ) - ); + self::assertSame(0, $exitCode); } /** @@ -62,14 +38,11 @@ public function itShouldHaveZeroExitCodeOnEmptyRequirements(): void public function itShouldNotReportPHPAsUnused(): void { chdir(__DIR__ . '/../assets/TestProjects/OnlyLanguageRequirement'); + $commandTester = new CommandTester($this->container->get(UnusedCommand::class)); - self::assertEquals( - 0, - $this->getApplication()->run( - new ArrayInput(['unused']), - new NullOutput() - ) - ); + $exitCode = $commandTester->execute([]); + + self::assertSame(0, $exitCode); } /** @@ -78,14 +51,11 @@ public function itShouldNotReportPHPAsUnused(): void public function itShouldNotReportExtDsAsUnused(): void { chdir(__DIR__ . '/../assets/TestProjects/ExtDsRequirement'); + $commandTester = new CommandTester($this->container->get(UnusedCommand::class)); + $exitCode = $commandTester->execute([]); - self::assertEquals( - 0, - $this->getApplication()->run( - new ArrayInput(['unused']), - new NullOutput() - ) - ); + self::assertSame(0, $exitCode); + self::assertStringContainsString('Found 2 used, 0 unused and 0 ignored packages', $commandTester->getDisplay()); } /** @@ -94,14 +64,11 @@ public function itShouldNotReportExtDsAsUnused(): void public function itShouldNoReportUnusedWithAutoloadFilesWithRequire(): void { chdir(__DIR__ . '/../assets/TestProjects/AutoloadFilesWithRequire'); + $commandTester = new CommandTester($this->container->get(UnusedCommand::class)); + $exitCode = $commandTester->execute([]); - self::assertEquals( - 0, - $this->getApplication()->run( - new ArrayInput(['unused']), - new NullOutput() - ) - ); + self::assertSame(0, $exitCode); + self::assertStringContainsString('Found 2 used, 0 unused and 0 ignored packages', $commandTester->getDisplay()); } /** @@ -110,15 +77,12 @@ public function itShouldNoReportUnusedWithAutoloadFilesWithRequire(): void public function itShouldNotReportSpecialPackages(): void { chdir(__DIR__ . '/../assets/TestProjects/IgnoreSpecialPackages'); + $commandTester = new CommandTester($this->container->get(UnusedCommand::class)); + $exitCode = $commandTester->execute([]); - $output = new BufferedOutput(); - - $this->getApplication()->run( - new ArrayInput(['unused']), - $output - ); - - self::assertStringNotContainsString('composer-plugin-api', $output->fetch()); + self::assertSame(0, $exitCode); + self::assertStringNotContainsString('composer-plugin-api', $commandTester->getDisplay()); + self::assertStringContainsString('Found 0 used, 0 unused and 0 ignored packages', $commandTester->getDisplay()); } /** @@ -127,15 +91,12 @@ public function itShouldNotReportSpecialPackages(): void public function itShouldNotReportExcludedPackages(): void { chdir(__DIR__ . '/../assets/TestProjects/IgnoreExcludedPackages'); + $commandTester = new CommandTester($this->container->get(UnusedCommand::class)); + $exitCode = $commandTester->execute(['--excludePackage' => ['dummy/test-package']]); - $output = new BufferedOutput(); - - $this->getApplication()->run( - new ArrayInput(['unused', '--excludePackage' => ['dummy/test-package']]), - $output - ); - - self::assertStringNotContainsString('dummy/test-package', $output->fetch()); + self::assertSame(0, $exitCode); + self::assertStringNotContainsString('dummy/test-package', $commandTester->getDisplay()); + self::assertStringContainsString('Found 0 used, 0 unused and 0 ignored packages', $commandTester->getDisplay()); } /** @@ -144,14 +105,12 @@ public function itShouldNotReportExcludedPackages(): void public function itShouldNotReportPatternExcludedPackages(): void { chdir(__DIR__ . '/../assets/TestProjects/IgnorePatternPackages'); + $commandTester = new CommandTester($this->container->get(UnusedCommand::class)); + $exitCode = $commandTester->execute([]); - $output = new BufferedOutput(); - - $this->getApplication()->run( - new ArrayInput(['unused']), - $output - ); - - self::assertStringNotContainsString('-implementation', $output->fetch()); + self::assertSame(1, $exitCode); + self::assertStringNotContainsString('-implementation', $commandTester->getDisplay()); + self::assertStringContainsString('dummy/test-package', $commandTester->getDisplay()); + self::assertStringContainsString('Found 0 used, 1 unused and 0 ignored packages', $commandTester->getDisplay()); } } diff --git a/tests/Unit/Collection/DependencyCollectionTest.php b/tests/Unit/Collection/DependencyCollectionTest.php index 1c15a54a..536a157d 100644 --- a/tests/Unit/Collection/DependencyCollectionTest.php +++ b/tests/Unit/Collection/DependencyCollectionTest.php @@ -4,7 +4,7 @@ namespace Icanhazstring\Composer\Test\Unused\Unit\Collection; -use Composer\Package\PackageInterface; +use ComposerUnused\Contracts\PackageInterface; use Icanhazstring\Composer\Unused\Dependency\DependencyCollection; use Icanhazstring\Composer\Unused\Dependency\RequiredDependency; use ComposerUnused\SymbolParser\Symbol\SymbolListInterface; diff --git a/tests/Unit/Dependency/RequiredDependencyTest.php b/tests/Unit/Dependency/RequiredDependencyTest.php index 5762bc45..caf3ae65 100644 --- a/tests/Unit/Dependency/RequiredDependencyTest.php +++ b/tests/Unit/Dependency/RequiredDependencyTest.php @@ -4,10 +4,9 @@ namespace Icanhazstring\Composer\Test\Unused\Unit\Dependency; -use Composer\Package\Link; -use Composer\Package\Package; -use Composer\Package\PackageInterface; -use Composer\Semver\Constraint\ConstraintInterface; +use ComposerUnused\Contracts\PackageInterface; +use Icanhazstring\Composer\Unused\Composer\Link; +use Icanhazstring\Composer\Unused\Composer\Package; use Icanhazstring\Composer\Unused\Dependency\RequiredDependency; use ComposerUnused\SymbolParser\Symbol\Symbol; use ComposerUnused\SymbolParser\Symbol\SymbolList; @@ -48,13 +47,13 @@ public function itShouldProvideSymbol(): void public function itShouldRequireDependency(): void { $rootRequirement = new RequiredDependency( - new Package('root/requirement', '1.0.0', '1.0.0'), + new Package('root/requirement'), new SymbolList() ); - $requiredPackage = new Package('required/pacakge', '1.0.0', '1.0.0'); + $requiredPackage = new Package('required/pacakge'); $requiredPackage->setRequires([ - 'root/requirement' => new Link('', 'root/requirement', $this->createStub(ConstraintInterface::class)) + 'root/requirement' => new Link('root/requirement') ]); $requiredDependency = new RequiredDependency($requiredPackage, new SymbolList()); @@ -68,12 +67,12 @@ public function itShouldRequireDependency(): void public function itShouldSuggestDependency(): void { $rootRequirement = new RequiredDependency( - new Package('root/requirement', '1.0.0', '1.0.0'), + new Package('root/requirement'), new SymbolList() ); - $requiredPackage = new Package('required/pacakge', '1.0.0', '1.0.0'); - $requiredPackage->setSuggests(['root/requirement' => '*']); + $requiredPackage = new Package('required/pacakge'); + $requiredPackage->setSuggests(['root/requirement']); $requiredDependency = new RequiredDependency($requiredPackage, new SymbolList()); diff --git a/tests/assets/TestProjects/IgnoreExcludedPackages/vendor/dummy/test-package/composer.json b/tests/assets/TestProjects/IgnoreExcludedPackages/vendor/dummy/test-package/composer.json new file mode 100644 index 00000000..b2922546 --- /dev/null +++ b/tests/assets/TestProjects/IgnoreExcludedPackages/vendor/dummy/test-package/composer.json @@ -0,0 +1,5 @@ +{ + "name": "dummy/test-package", + "require": { + } +} diff --git a/tests/assets/TestProjects/IgnorePatternPackages/vendor/dummy/test-package/composer.json b/tests/assets/TestProjects/IgnorePatternPackages/vendor/dummy/test-package/composer.json new file mode 100644 index 00000000..b2922546 --- /dev/null +++ b/tests/assets/TestProjects/IgnorePatternPackages/vendor/dummy/test-package/composer.json @@ -0,0 +1,5 @@ +{ + "name": "dummy/test-package", + "require": { + } +} From 03bc086a1129c8d30e8792dc6c092d0c0e37c4bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Fr=C3=B6mer?= Date: Tue, 4 Jan 2022 16:19:20 +0100 Subject: [PATCH 2/2] Add missing suggests from config to package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Andreas Frömer --- src/Composer/Config.php | 10 ++++++++++ src/Composer/Package.php | 1 + 2 files changed, 11 insertions(+) diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 6f8433f1..e345a3f1 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -13,6 +13,8 @@ final class Config private array $require = []; /** @var array */ private array $autoload = []; + /** @var array */ + private array $suggests = []; public function getName(): string { @@ -35,6 +37,14 @@ public function getAutoload(): array return $this->autoload; } + /** + * @return array + */ + public function getSuggests(): array + { + return $this->suggests; + } + public function get(string $property): string { $value = $this->config[$property] ?? null; diff --git a/src/Composer/Package.php b/src/Composer/Package.php index d3e89500..f085de48 100644 --- a/src/Composer/Package.php +++ b/src/Composer/Package.php @@ -24,6 +24,7 @@ public static function fromConfig(Config $config): self $package->requires = array_map(static function (string $name) { return new Link($name); }, array_keys($config->getRequire())); + $package->suggests = array_keys($config->getSuggests()); return $package; }