diff --git a/src/Composer/Command/CompletionTrait.php b/src/Composer/Command/CompletionTrait.php index 8fa7632d5..5134aac3a 100644 --- a/src/Composer/Command/CompletionTrait.php +++ b/src/Composer/Command/CompletionTrait.php @@ -66,7 +66,7 @@ trait CompletionTrait if ($locker->isLocked()) { $platformRepo = new PlatformRepository(array(), $locker->getPlatformOverrides()); } else { - $platformRepo = new PlatformRepository(array(), $composer->getConfig()->get('platform') ?: array()); + $platformRepo = new PlatformRepository(array(), $composer->getConfig()->get('platform')); } if ($input->getCompletionValue() === '') { // to reduce noise, when no text is yet entered we list only two entries for ext- and lib- prefixes @@ -115,36 +115,43 @@ trait CompletionTrait $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories()); $results = []; + $showVendors = false; if (!str_contains($input->getCompletionValue(), '/')) { $results = $repos->search('^' . preg_quote($input->getCompletionValue()), RepositoryInterface::SEARCH_VENDOR); - $vendors = true; + $showVendors = true; } // if we get a single vendor, we expand it into its contents already if (\count($results) <= 1) { $results = $repos->search('^'.preg_quote($input->getCompletionValue()), RepositoryInterface::SEARCH_NAME); - $vendors = false; + $showVendors = false; } $results = array_column($results, 'name'); - if ($vendors) { + if ($showVendors) { $results = array_map(function (string $name): string { return $name.'/'; }, $results); // sort shorter results first to avoid auto-expanding the completion to a longer string than needed usort($results, function (string $a, string $b) { - return \strlen($a) - \strlen($b); + $lenA = \strlen($a); + $lenB = \strlen($b); + if ($lenA === $lenB) { + return $a <=> $b; + } + + return $lenA - $lenB; }); $pinned = []; // ensure if the input is an exact match that it is always in the result set $completionInput = $input->getCompletionValue().'/'; - if (in_array($completionInput, $results, true)) { + if (false !== ($exactIndex = array_search($completionInput, $results, true))) { $pinned[] = $completionInput; - array_splice($results, array_search($completionInput, $results, true), 1); + array_splice($results, $exactIndex, 1); } return array_merge($pinned, array_slice($results, 0, $max - \count($pinned))); @@ -177,7 +184,7 @@ trait CompletionTrait private function suggestPlatformPackage(): \Closure { return function (CompletionInput $input): array { - $repos = new PlatformRepository([], $this->requireComposer()->getConfig()->get('platform') ?? []); + $repos = new PlatformRepository([], $this->requireComposer()->getConfig()->get('platform')); $pattern = BasePackage::packageNameToRegexp($input->getCompletionValue().'*'); return array_filter(array_map(function (PackageInterface $package) { diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 23356c267..e04c0fdcf 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -159,7 +159,7 @@ EOT $preferSource, $preferDist, !$input->getOption('no-dev'), - $input->getOption('repository') ?: $input->getOption('repository-url'), + \count($input->getOption('repository')) > 0 ? $input->getOption('repository') : $input->getOption('repository-url'), $input->getOption('no-plugins'), $input->getOption('no-scripts'), $input->getOption('no-progress'), @@ -197,7 +197,7 @@ EOT $repositories = (array) $repositories; } - $platformRequirementFilter = $platformRequirementFilter ?: PlatformRequirementFilterFactory::ignoreNothing(); + $platformRequirementFilter = $platformRequirementFilter ?? PlatformRequirementFilterFactory::ignoreNothing(); // we need to manually load the configuration to pass the auth credentials to the io interface! $io->loadConfiguration($config); @@ -424,7 +424,7 @@ EOT } } - $platformOverrides = $config->get('platform') ?: array(); + $platformOverrides = $config->get('platform'); $platformRepo = new PlatformRepository(array(), $platformOverrides); // find the latest version if there are multiple diff --git a/src/Composer/Command/GlobalCommand.php b/src/Composer/Command/GlobalCommand.php index 902ed4cb2..e9195ca1e 100644 --- a/src/Composer/Command/GlobalCommand.php +++ b/src/Composer/Command/GlobalCommand.php @@ -119,6 +119,10 @@ EOT private function prepareSubcommandInput(InputInterface $input, bool $quiet = false): StringInput { + if (!method_exists($input, '__toString')) { + throw new \LogicException('Expected an Input instance that is stringable, got '.get_class($input)); + } + // The COMPOSER env var should not apply to the global execution scope if (Platform::getEnv('COMPOSER')) { Platform::clearEnv('COMPOSER'); diff --git a/src/Composer/Command/PackageDiscoveryTrait.php b/src/Composer/Command/PackageDiscoveryTrait.php index 99fb0f39b..c8ab0e9bf 100644 --- a/src/Composer/Command/PackageDiscoveryTrait.php +++ b/src/Composer/Command/PackageDiscoveryTrait.php @@ -99,7 +99,7 @@ trait PackageDiscoveryTrait foreach ($requires as $requirement) { if (!isset($requirement['version'])) { // determine the best version automatically - list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $platformRepo, $preferredStability, null, null, $fixed); + list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $platformRepo, $preferredStability, $fixed); $requirement['version'] = $version; // replace package name from packagist.org @@ -268,7 +268,7 @@ trait PackageDiscoveryTrait * @throws \InvalidArgumentException * @return array{string, string} name version */ - private function findBestVersionAndNameForPackage(InputInterface $input, string $name, ?PlatformRepository $platformRepo = null, string $preferredStability = 'stable', ?string $requiredVersion = null, ?string $minimumStability = null, bool $fixed = false): array + private function findBestVersionAndNameForPackage(InputInterface $input, string $name, ?PlatformRepository $platformRepo = null, string $preferredStability = 'stable', bool $fixed = false): array { // handle ignore-platform-reqs flag if present if ($input->hasOption('ignore-platform-reqs') && $input->hasOption('ignore-platform-req')) { @@ -278,17 +278,17 @@ trait PackageDiscoveryTrait } // find the latest version allowed in this repo set - $repoSet = $this->getRepositorySet($input, $minimumStability); + $repoSet = $this->getRepositorySet($input); $versionSelector = new VersionSelector($repoSet, $platformRepo); - $effectiveMinimumStability = $minimumStability ?? $this->getMinimumStability($input); + $effectiveMinimumStability = $this->getMinimumStability($input); - $package = $versionSelector->findBestCandidate($name, $requiredVersion, $preferredStability, $platformRequirementFilter); + $package = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter); if (false === $package) { // platform packages can not be found in the pool in versions other than the local platform's has // so if platform reqs are ignored we just take the user's word for it if ($platformRequirementFilter->isIgnored($name)) { - return array($name, $requiredVersion ?: '*'); + return array($name, '*'); } // Check if it is a virtual package provided by others @@ -308,17 +308,16 @@ trait PackageDiscoveryTrait } // Check whether the package requirements were the problem - if (!($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter) && false !== ($candidate = $versionSelector->findBestCandidate($name, $requiredVersion, $preferredStability, PlatformRequirementFilterFactory::ignoreAll()))) { + if (!($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter) && false !== ($candidate = $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll()))) { throw new \InvalidArgumentException(sprintf( - 'Package %s%s has requirements incompatible with your PHP version, PHP extensions and Composer version' . $this->getPlatformExceptionDetails($candidate, $platformRepo), - $name, - is_string($requiredVersion) ? ' at version '.$requiredVersion : '' + 'Package %s has requirements incompatible with your PHP version, PHP extensions and Composer version' . $this->getPlatformExceptionDetails($candidate, $platformRepo), + $name )); } // Check whether the minimum stability was the problem but the package exists - if (false !== ($package = $versionSelector->findBestCandidate($name, $requiredVersion, $preferredStability, $platformRequirementFilter, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES))) { + if (false !== ($package = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES))) { // we must first verify if a valid package would be found in a lower priority repository - if (false !== ($allReposPackage = $versionSelector->findBestCandidate($name, $requiredVersion, $preferredStability, $platformRequirementFilter, RepositorySet::ALLOW_SHADOWED_REPOSITORIES))) { + if (false !== ($allReposPackage = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter, RepositorySet::ALLOW_SHADOWED_REPOSITORIES))) { throw new \InvalidArgumentException( 'Package '.$name.' exists in '.$allReposPackage->getRepository()->getRepoName().' and '.$package->getRepository()->getRepoName().' which has a higher repository priority. The packages from the higher priority repository do not match your minimum-stability and are therefore not installable. That repository is canonical so the lower priority repo\'s packages are not installable. See https://getcomposer.org/repoprio for details and assistance.' ); @@ -330,21 +329,6 @@ trait PackageDiscoveryTrait $effectiveMinimumStability )); } - // Check whether the required version was the problem - if (is_string($requiredVersion) && false !== ($package = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter))) { - // we must first verify if a valid package would be found in a lower priority repository - if (false !== ($allReposPackage = $versionSelector->findBestCandidate($name, $requiredVersion, $preferredStability, PlatformRequirementFilterFactory::ignoreNothing(), RepositorySet::ALLOW_SHADOWED_REPOSITORIES))) { - throw new \InvalidArgumentException( - 'Package '.$name.' exists in '.$allReposPackage->getRepository()->getRepoName().' and '.$package->getRepository()->getRepoName().' which has a higher repository priority. The packages from the higher priority repository do not match your constraint and are therefore not installable. That repository is canonical so the lower priority repo\'s packages are not installable. See https://getcomposer.org/repoprio for details and assistance.' - ); - } - - throw new \InvalidArgumentException(sprintf( - 'Could not find package %s in a version matching "%s" and a stability matching "'.$effectiveMinimumStability.'".', - $name, - $requiredVersion - )); - } // Check whether the PHP version was the problem for all versions if (!$platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter && false !== ($candidate = $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll(), RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES))) { $additional = ''; diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index d96a409fe..1fbb444b9 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -187,7 +187,7 @@ EOT // init repos $platformOverrides = array(); if ($composer) { - $platformOverrides = $composer->getConfig()->get('platform') ?: array(); + $platformOverrides = $composer->getConfig()->get('platform'); } $platformRepo = new PlatformRepository(array(), $platformOverrides); $lockedRepo = null; diff --git a/src/Composer/Command/SuggestsCommand.php b/src/Composer/Command/SuggestsCommand.php index 0dd1653b4..0e28b6089 100644 --- a/src/Composer/Command/SuggestsCommand.php +++ b/src/Composer/Command/SuggestsCommand.php @@ -65,7 +65,7 @@ EOT $installedRepos[] = new PlatformRepository(array(), $locker->getPlatformOverrides()); $installedRepos[] = $locker->getLockedRepository(!$input->getOption('no-dev')); } else { - $installedRepos[] = new PlatformRepository(array(), $composer->getConfig()->get('platform') ?: array()); + $installedRepos[] = new PlatformRepository(array(), $composer->getConfig()->get('platform')); $installedRepos[] = $composer->getRepositoryManager()->getLocalRepository(); } diff --git a/src/Composer/Console/Input/InputArgument.php b/src/Composer/Console/Input/InputArgument.php index b9ee541f6..69c6dfb02 100644 --- a/src/Composer/Console/Input/InputArgument.php +++ b/src/Composer/Console/Input/InputArgument.php @@ -59,7 +59,7 @@ class InputArgument extends BaseInputArgument public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { $values = $this->suggestedValues; - if ($values instanceof \Closure && !\is_array($values = $values($input, $suggestions))) { + if ($values instanceof \Closure && !\is_array($values = $values($input, $suggestions))) { // @phpstan-ignore-line throw new LogicException(sprintf('Closure for option "%s" must return an array. Got "%s".', $this->getName(), get_debug_type($values))); } if ([] !== $values) { diff --git a/src/Composer/Console/Input/InputOption.php b/src/Composer/Console/Input/InputOption.php index 70654c0d8..2d370ba51 100644 --- a/src/Composer/Console/Input/InputOption.php +++ b/src/Composer/Console/Input/InputOption.php @@ -62,7 +62,7 @@ class InputOption extends BaseInputOption public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { $values = $this->suggestedValues; - if ($values instanceof \Closure && !\is_array($values = $values($input, $suggestions))) { + if ($values instanceof \Closure && !\is_array($values = $values($input, $suggestions))) { // @phpstan-ignore-line throw new LogicException(sprintf('Closure for argument "%s" must return an array. Got "%s".', $this->getName(), get_debug_type($values))); } if ([] !== $values) { diff --git a/tests/Composer/Test/CompletionFunctionalTest.php b/tests/Composer/Test/CompletionFunctionalTest.php index 422abea79..36aa3fe8d 100644 --- a/tests/Composer/Test/CompletionFunctionalTest.php +++ b/tests/Composer/Test/CompletionFunctionalTest.php @@ -27,34 +27,34 @@ class CompletionFunctionalTest extends TestCase */ public function getCommandSuggestions(): iterable { - $randomProject = '104corp/cache'; + $randomVendor = 'a/'; $installedPackages = ['composer/semver', 'psr/log']; $preferInstall = ['dist', 'source', 'auto']; - yield ['archive ', [$randomProject]]; + yield ['archive ', [$randomVendor]]; yield ['archive symfony/http-', ['symfony/http-kernel', 'symfony/http-foundation']]; yield ['archive --format ', ['tar', 'zip']]; - yield ['create-project ', [$randomProject]]; + yield ['create-project ', [$randomVendor]]; yield ['create-project symfony/skeleton --prefer-install ', $preferInstall]; yield ['depends ', $installedPackages]; yield ['why ', $installedPackages]; - yield ['exec ', ['composer', 'compile']]; + yield ['exec ', ['composer', 'jsonlint', 'phpstan', 'phpstan.phar', 'simple-phpunit', 'validate-json']]; yield ['browse ', $installedPackages]; yield ['home -H ', $installedPackages]; - yield ['init --require ', [$randomProject]]; - yield ['init --require-dev foo/bar --require-dev ', [$randomProject]]; + yield ['init --require ', [$randomVendor]]; + yield ['init --require-dev foo/bar --require-dev ', [$randomVendor]]; yield ['install --prefer-install ', $preferInstall]; yield ['install ', $installedPackages]; yield ['outdated ', $installedPackages]; - yield ['prohibits ', [$randomProject]]; + yield ['prohibits ', [$randomVendor]]; yield ['why-not symfony/http-ker', ['symfony/http-kernel']]; yield ['reinstall --prefer-install ', $preferInstall]; @@ -63,7 +63,7 @@ class CompletionFunctionalTest extends TestCase yield ['remove ', $installedPackages]; yield ['require --prefer-install ', $preferInstall]; - yield ['require ', [$randomProject]]; + yield ['require ', [$randomVendor]]; yield ['require --dev symfony/http-', ['symfony/http-kernel', 'symfony/http-foundation']]; yield ['run-script ', ['compile', 'test', 'phpstan']];