Merge pull request #10635 from staabm/phpstan-config
added phpstan ConfigReturnTypeExtensionpull/10757/head
commit
6b7ae1e05a
|
@ -4,6 +4,7 @@ includes:
|
||||||
- ../vendor/phpstan/phpstan-strict-rules/rules.neon
|
- ../vendor/phpstan/phpstan-strict-rules/rules.neon
|
||||||
- ../vendor/phpstan/phpstan-symfony/extension.neon
|
- ../vendor/phpstan/phpstan-symfony/extension.neon
|
||||||
- ../vendor/phpstan/phpstan-symfony/rules.neon
|
- ../vendor/phpstan/phpstan-symfony/rules.neon
|
||||||
|
- ./rules.neon # Composer-specific PHPStan extensions, can be reused by third party packages by including 'vendor/composer/composer/phpstan/rules.neon' in your phpstan config
|
||||||
- ./baseline.neon
|
- ./baseline.neon
|
||||||
- ./ignore-by-php-version.neon.php
|
- ./ignore-by-php-version.neon.php
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Composer-specific PHPStan extensions
|
||||||
|
#
|
||||||
|
# These can be reused by third party packages by including 'vendor/composer/composer/phpstan/rules.neon'
|
||||||
|
# in your phpstan config
|
||||||
|
|
||||||
|
services:
|
||||||
|
-
|
||||||
|
class: Composer\PHPStan\ConfigReturnTypeExtension
|
||||||
|
tags:
|
||||||
|
- phpstan.broker.dynamicMethodReturnTypeExtension
|
|
@ -289,7 +289,7 @@
|
||||||
"properties": {
|
"properties": {
|
||||||
"platform": {
|
"platform": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "This is an object of package name (keys) and version (values) that will be used to mock the platform packages on this machine.",
|
"description": "This is an object of package name (keys) and version (values) that will be used to mock the platform packages on this machine, the version can be set to false to make it appear like the package is not present.",
|
||||||
"additionalProperties": {
|
"additionalProperties": {
|
||||||
"type": ["string", "boolean"]
|
"type": ["string", "boolean"]
|
||||||
}
|
}
|
||||||
|
@ -326,7 +326,7 @@
|
||||||
},
|
},
|
||||||
"github-protocols": {
|
"github-protocols": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "A list of protocols to use for github.com clones, in priority order, defaults to [\"git\", \"https\", \"http\"].",
|
"description": "A list of protocols to use for github.com clones, in priority order, defaults to [\"https\", \"ssh\", \"git\"].",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
@ -347,9 +347,20 @@
|
||||||
},
|
},
|
||||||
"gitlab-token": {
|
"gitlab-token": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "An object of domain name => gitlab private tokens, typically {\"gitlab.com\":\"<token>\"}.",
|
"description": "An object of domain name => gitlab private tokens, typically {\"gitlab.com\":\"<token>\"}, or an object with username and token keys.",
|
||||||
"additionalProperties": {
|
"additionalProperties": {
|
||||||
"type": "string"
|
"type": ["string", "object"],
|
||||||
|
"required": ["username", "token"],
|
||||||
|
"properties": {
|
||||||
|
"username": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The username used for GitLab authentication"
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The token used for GitLab authentication"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gitlab-protocol": {
|
"gitlab-protocol": {
|
||||||
|
@ -512,6 +523,14 @@
|
||||||
"consumer-secret": {
|
"consumer-secret": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The consumer-secret used for OAuth authentication"
|
"description": "The consumer-secret used for OAuth authentication"
|
||||||
|
},
|
||||||
|
"access-token": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The OAuth token retrieved from Bitbucket's API, this is written by Composer and you should not set it nor modify it."
|
||||||
|
},
|
||||||
|
"access-token-expiration": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "The generated token's expiration timestamp, this is written by Composer and you should not set it nor modify it."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,7 +202,7 @@ class AutoloadGenerator
|
||||||
// See https://bugs.php.net/bug.php?id=72738
|
// See https://bugs.php.net/bug.php?id=72738
|
||||||
$basePath = $filesystem->normalizePath(realpath(realpath(Platform::getCwd())));
|
$basePath = $filesystem->normalizePath(realpath(realpath(Platform::getCwd())));
|
||||||
$vendorPath = $filesystem->normalizePath(realpath(realpath($config->get('vendor-dir'))));
|
$vendorPath = $filesystem->normalizePath(realpath(realpath($config->get('vendor-dir'))));
|
||||||
$useGlobalIncludePath = (bool) $config->get('use-include-path');
|
$useGlobalIncludePath = $config->get('use-include-path');
|
||||||
$prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true';
|
$prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true';
|
||||||
$targetDir = $vendorPath.'/'.$targetDir;
|
$targetDir = $vendorPath.'/'.$targetDir;
|
||||||
$filesystem->ensureDirectoryExists($targetDir);
|
$filesystem->ensureDirectoryExists($targetDir);
|
||||||
|
@ -408,7 +408,7 @@ EOF;
|
||||||
unlink($includeFilesFilePath);
|
unlink($includeFilesFilePath);
|
||||||
}
|
}
|
||||||
$filesystem->filePutContentsIfModified($targetDir.'/autoload_static.php', $this->getStaticFile($suffix, $targetDir, $vendorPath, $basePath));
|
$filesystem->filePutContentsIfModified($targetDir.'/autoload_static.php', $this->getStaticFile($suffix, $targetDir, $vendorPath, $basePath));
|
||||||
$checkPlatform = $config->get('platform-check') && !($this->platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter);
|
$checkPlatform = $config->get('platform-check') !== false && !($this->platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter);
|
||||||
$platformCheckContent = null;
|
$platformCheckContent = null;
|
||||||
if ($checkPlatform) {
|
if ($checkPlatform) {
|
||||||
$platformCheckContent = $this->getPlatformCheck($packageMap, $config->get('platform-check'), $devPackageNames);
|
$platformCheckContent = $this->getPlatformCheck($packageMap, $config->get('platform-check'), $devPackageNames);
|
||||||
|
@ -429,7 +429,7 @@ EOF;
|
||||||
|
|
||||||
if ($this->runScripts) {
|
if ($this->runScripts) {
|
||||||
$this->eventDispatcher->dispatchScript(ScriptEvents::POST_AUTOLOAD_DUMP, $this->devMode, array(), array(
|
$this->eventDispatcher->dispatchScript(ScriptEvents::POST_AUTOLOAD_DUMP, $this->devMode, array(), array(
|
||||||
'optimize' => (bool) $scanPsrPackages,
|
'optimize' => $scanPsrPackages,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -114,7 +114,7 @@ EOT
|
||||||
$this->outputResult($this->checkHttpProxy());
|
$this->outputResult($this->checkHttpProxy());
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($oauth = $config->get('github-oauth')) {
|
if (count($oauth = $config->get('github-oauth')) > 0) {
|
||||||
foreach ($oauth as $domain => $token) {
|
foreach ($oauth as $domain => $token) {
|
||||||
$io->write('Checking '.$domain.' oauth access: ', false);
|
$io->write('Checking '.$domain.' oauth access: ', false);
|
||||||
$this->outputResult($this->checkGithubOauth($domain, $token));
|
$this->outputResult($this->checkGithubOauth($domain, $token));
|
||||||
|
|
|
@ -237,7 +237,7 @@ EOT
|
||||||
|
|
||||||
$allowPlugins = $composer->getConfig()->get('allow-plugins');
|
$allowPlugins = $composer->getConfig()->get('allow-plugins');
|
||||||
$removedPlugins = is_array($allowPlugins) ? array_intersect(array_keys($allowPlugins), $packages) : [];
|
$removedPlugins = is_array($allowPlugins) ? array_intersect(array_keys($allowPlugins), $packages) : [];
|
||||||
if (!$dryRun && count($removedPlugins) > 0) {
|
if (!$dryRun && is_array($allowPlugins) && count($removedPlugins) > 0) {
|
||||||
if (count($allowPlugins) === count($removedPlugins)) {
|
if (count($allowPlugins) === count($removedPlugins)) {
|
||||||
$json->removeConfigSetting('allow-plugins');
|
$json->removeConfigSetting('allow-plugins');
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -301,6 +301,10 @@ class Config
|
||||||
$this->setSourceOfConfigValue($val, $key, $env);
|
$this->setSourceOfConfigValue($val, $key, $env);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($key === 'process-timeout') {
|
||||||
|
return max(0, false !== $val ? (int) $val : $this->config[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
$val = rtrim((string) $this->process(false !== $val ? $val : $this->config[$key], $flags), '/\\');
|
$val = rtrim((string) $this->process(false !== $val ? $val : $this->config[$key], $flags), '/\\');
|
||||||
$val = Platform::expandPath($val);
|
$val = Platform::expandPath($val);
|
||||||
|
|
||||||
|
@ -339,7 +343,7 @@ class Config
|
||||||
|
|
||||||
// ints without env var support
|
// ints without env var support
|
||||||
case 'cache-ttl':
|
case 'cache-ttl':
|
||||||
return (int) $this->config[$key];
|
return max(0, (int) $this->config[$key]);
|
||||||
|
|
||||||
// numbers with kb/mb/gb support, without env var support
|
// numbers with kb/mb/gb support, without env var support
|
||||||
case 'cache-files-maxsize':
|
case 'cache-files-maxsize':
|
||||||
|
@ -348,7 +352,7 @@ class Config
|
||||||
"Could not parse the value of '$key': {$this->config[$key]}"
|
"Could not parse the value of '$key': {$this->config[$key]}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$size = $matches[1];
|
$size = (float) $matches[1];
|
||||||
if (isset($matches[2])) {
|
if (isset($matches[2])) {
|
||||||
switch (strtolower($matches[2])) {
|
switch (strtolower($matches[2])) {
|
||||||
case 'g':
|
case 'g':
|
||||||
|
@ -365,15 +369,15 @@ class Config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $size;
|
return max(0, (int) $size);
|
||||||
|
|
||||||
// special cases below
|
// special cases below
|
||||||
case 'cache-files-ttl':
|
case 'cache-files-ttl':
|
||||||
if (isset($this->config[$key])) {
|
if (isset($this->config[$key])) {
|
||||||
return (int) $this->config[$key];
|
return max(0, (int) $this->config[$key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (int) $this->config['cache-ttl'];
|
return $this->get('cache-ttl');
|
||||||
|
|
||||||
case 'home':
|
case 'home':
|
||||||
return rtrim($this->process(Platform::expandPath($this->config[$key]), $flags), '/\\');
|
return rtrim($this->process(Platform::expandPath($this->config[$key]), $flags), '/\\');
|
||||||
|
|
|
@ -94,7 +94,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
|
||||||
|
|
||||||
if ($this->cache && $this->cache->gcIsNecessary()) {
|
if ($this->cache && $this->cache->gcIsNecessary()) {
|
||||||
$this->io->writeError('Running cache garbage collection', true, IOInterface::VERY_VERBOSE);
|
$this->io->writeError('Running cache garbage collection', true, IOInterface::VERY_VERBOSE);
|
||||||
$this->cache->gc((int) $config->get('cache-files-ttl'), (int) $config->get('cache-files-maxsize'));
|
$this->cache->gc($config->get('cache-files-ttl'), $config->get('cache-files-maxsize'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -190,7 +190,7 @@ class Factory
|
||||||
}
|
}
|
||||||
$config->setConfigSource(new JsonConfigSource($file));
|
$config->setConfigSource(new JsonConfigSource($file));
|
||||||
|
|
||||||
$htaccessProtect = (bool) $config->get('htaccess-protect');
|
$htaccessProtect = $config->get('htaccess-protect');
|
||||||
if ($htaccessProtect) {
|
if ($htaccessProtect) {
|
||||||
// Protect directory against web access. Since HOME could be
|
// Protect directory against web access. Since HOME could be
|
||||||
// the www-data's user home and be web-accessible it is a
|
// the www-data's user home and be web-accessible it is a
|
||||||
|
@ -646,10 +646,10 @@ class Factory
|
||||||
}
|
}
|
||||||
$httpDownloaderOptions = array();
|
$httpDownloaderOptions = array();
|
||||||
if ($disableTls === false) {
|
if ($disableTls === false) {
|
||||||
if ($config->get('cafile')) {
|
if ('' !== $config->get('cafile')) {
|
||||||
$httpDownloaderOptions['ssl']['cafile'] = $config->get('cafile');
|
$httpDownloaderOptions['ssl']['cafile'] = $config->get('cafile');
|
||||||
}
|
}
|
||||||
if ($config->get('capath')) {
|
if ('' !== $config->get('capath')) {
|
||||||
$httpDownloaderOptions['ssl']['capath'] = $config->get('capath');
|
$httpDownloaderOptions['ssl']['capath'] = $config->get('capath');
|
||||||
}
|
}
|
||||||
$httpDownloaderOptions = array_replace_recursive($httpDownloaderOptions, $options);
|
$httpDownloaderOptions = array_replace_recursive($httpDownloaderOptions, $options);
|
||||||
|
|
|
@ -141,8 +141,8 @@ abstract class BaseIO implements IOInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($gitlabToken as $domain => $token) {
|
foreach ($gitlabToken as $domain => $token) {
|
||||||
$username = is_array($token) && array_key_exists("username", $token) ? $token["username"] : $token;
|
$username = is_array($token) ? $token["username"] : $token;
|
||||||
$password = is_array($token) && array_key_exists("token", $token) ? $token["token"] : 'private-token';
|
$password = is_array($token) ? $token["token"] : 'private-token';
|
||||||
$this->checkAndSetAuthentication($domain, $username, $password);
|
$this->checkAndSetAuthentication($domain, $username, $password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,7 +156,7 @@ abstract class BaseIO implements IOInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup process timeout
|
// setup process timeout
|
||||||
ProcessExecutor::setTimeout((int) $config->get('process-timeout'));
|
ProcessExecutor::setTimeout($config->get('process-timeout'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function emergency($message, array $context = array()): void
|
public function emergency($message, array $context = array()): void
|
||||||
|
|
|
@ -0,0 +1,181 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Composer\PHPStan;
|
||||||
|
|
||||||
|
use Composer\Config;
|
||||||
|
use Composer\Json\JsonFile;
|
||||||
|
use PhpParser\Node\Expr\MethodCall;
|
||||||
|
use PHPStan\Analyser\Scope;
|
||||||
|
use PHPStan\Reflection\MethodReflection;
|
||||||
|
use PHPStan\Reflection\ParametersAcceptorSelector;
|
||||||
|
use PHPStan\Type\ArrayType;
|
||||||
|
use PHPStan\Type\BooleanType;
|
||||||
|
use PHPStan\Type\Constant\ConstantArrayType;
|
||||||
|
use PHPStan\Type\Constant\ConstantBooleanType;
|
||||||
|
use PHPStan\Type\Constant\ConstantStringType;
|
||||||
|
use PHPStan\Type\DynamicMethodReturnTypeExtension;
|
||||||
|
use PHPStan\Type\IntegerRangeType;
|
||||||
|
use PHPStan\Type\IntegerType;
|
||||||
|
use PHPStan\Type\MixedType;
|
||||||
|
use PHPStan\Type\ObjectType;
|
||||||
|
use PHPStan\Type\StringType;
|
||||||
|
use PHPStan\Type\Type;
|
||||||
|
use PHPStan\Type\TypeCombinator;
|
||||||
|
use PHPStan\Type\UnionType;
|
||||||
|
|
||||||
|
final class ConfigReturnTypeExtension implements DynamicMethodReturnTypeExtension
|
||||||
|
{
|
||||||
|
/** @var array<string, \PHPStan\Type\Type> */
|
||||||
|
private $properties = [];
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$schema = JsonFile::parseJson((string) file_get_contents(__DIR__.'/../../../res/composer-schema.json'));
|
||||||
|
/**
|
||||||
|
* @var string $prop
|
||||||
|
*/
|
||||||
|
foreach ($schema['properties']['config']['properties'] as $prop => $conf) {
|
||||||
|
$type = $this->parseType($conf, $prop);
|
||||||
|
|
||||||
|
$this->properties[$prop] = $type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClass(): string
|
||||||
|
{
|
||||||
|
return Config::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isMethodSupported(MethodReflection $methodReflection): bool
|
||||||
|
{
|
||||||
|
return strtolower($methodReflection->getName()) === 'get';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
|
||||||
|
{
|
||||||
|
$args = $methodCall->getArgs();
|
||||||
|
|
||||||
|
if (count($args) < 1) {
|
||||||
|
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
|
||||||
|
}
|
||||||
|
|
||||||
|
$keyType = $scope->getType($args[0]->value);
|
||||||
|
if ($keyType instanceof ConstantStringType) {
|
||||||
|
if (isset($this->properties[$keyType->getValue()])) {
|
||||||
|
return $this->properties[$keyType->getValue()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<mixed> $def
|
||||||
|
*/
|
||||||
|
private function parseType(array $def, string $path): Type
|
||||||
|
{
|
||||||
|
if (isset($def['type'])) {
|
||||||
|
$types = [];
|
||||||
|
foreach ((array) $def['type'] as $type) {
|
||||||
|
switch ($type) {
|
||||||
|
case 'integer':
|
||||||
|
if (in_array($path, ['process-timeout', 'cache-ttl', 'cache-files-ttl', 'cache-files-maxsize'], true)) {
|
||||||
|
$types[] = IntegerRangeType::createAllGreaterThanOrEqualTo(0);
|
||||||
|
} else {
|
||||||
|
$types[] = new IntegerType();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'string':
|
||||||
|
if ($path === 'cache-files-maxsize') {
|
||||||
|
// passthru, skip as it is always converted to int
|
||||||
|
} elseif ($path === 'discard-changes') {
|
||||||
|
$types[] = new ConstantStringType('stash');
|
||||||
|
} elseif ($path === 'use-parent-dir') {
|
||||||
|
$types[] = new ConstantStringType('prompt');
|
||||||
|
} elseif ($path === 'store-auths') {
|
||||||
|
$types[] = new ConstantStringType('prompt');
|
||||||
|
} elseif ($path === 'platform-check') {
|
||||||
|
$types[] = new ConstantStringType('php-only');
|
||||||
|
} elseif ($path === 'github-protocols') {
|
||||||
|
$types[] = new UnionType([new ConstantStringType('git'), new ConstantStringType('https'), new ConstantStringType('ssh'), new ConstantStringType('http')]);
|
||||||
|
} elseif (str_starts_with($path, 'preferred-install')) {
|
||||||
|
$types[] = new UnionType([new ConstantStringType('source'), new ConstantStringType('dist'), new ConstantStringType('auto')]);
|
||||||
|
} else {
|
||||||
|
$types[] = new StringType();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'boolean':
|
||||||
|
if ($path === 'platform.additionalProperties') {
|
||||||
|
$types[] = new ConstantBooleanType(false);
|
||||||
|
} else {
|
||||||
|
$types[] = new BooleanType();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'object':
|
||||||
|
$addlPropType = null;
|
||||||
|
if (isset($def['additionalProperties'])) {
|
||||||
|
$addlPropType = $this->parseType($def['additionalProperties'], $path.'.additionalProperties');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($def['properties'])) {
|
||||||
|
$keyNames = [];
|
||||||
|
$valTypes = [];
|
||||||
|
foreach ($def['properties'] as $propName => $propdef) {
|
||||||
|
$keyNames[] = new ConstantStringType($propName);
|
||||||
|
$valType = $this->parseType($propdef, $path.'.'.$propName);
|
||||||
|
if (!isset($def['required']) || !in_array($propName, $def['required'], true)) {
|
||||||
|
$valType = TypeCombinator::addNull($valType);
|
||||||
|
}
|
||||||
|
$valTypes[] = $valType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($addlPropType !== null) {
|
||||||
|
$types[] = new ArrayType(TypeCombinator::union(new StringType(), ...$keyNames), TypeCombinator::union($addlPropType, ...$valTypes));
|
||||||
|
} else {
|
||||||
|
$types[] = new ConstantArrayType($keyNames, $valTypes);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$types[] = new ArrayType(new StringType(), $addlPropType ?? new MixedType());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'array':
|
||||||
|
if (isset($def['items'])) {
|
||||||
|
$valType = $this->parseType($def['items'], $path.'.items');
|
||||||
|
} else {
|
||||||
|
$valType = new MixedType();
|
||||||
|
}
|
||||||
|
|
||||||
|
$types[] = new ArrayType(new IntegerType(), $valType);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$types[] = new MixedType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$type = TypeCombinator::union(...$types);
|
||||||
|
} elseif (isset($def['enum'])) {
|
||||||
|
$type = TypeCombinator::union(...array_map(function (string $value): ConstantStringType {
|
||||||
|
return new ConstantStringType($value);
|
||||||
|
}, $def['enum']));
|
||||||
|
} else {
|
||||||
|
$type = new MixedType();
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow-plugins defaults to null until July 1st 2022 for some BC hackery, but after that it is not nullable anymore
|
||||||
|
if ($path === 'allow-plugins' && time() < strtotime('2022-07-01')) {
|
||||||
|
$type = TypeCombinator::addNull($type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// default null props
|
||||||
|
if (in_array($path, ['autoloader-suffix', 'gitlab-protocol'], true)) {
|
||||||
|
$type = TypeCombinator::addNull($type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
|
}
|
|
@ -337,17 +337,17 @@ class Locker
|
||||||
/**
|
/**
|
||||||
* Locks provided data into lockfile.
|
* Locks provided data into lockfile.
|
||||||
*
|
*
|
||||||
* @param PackageInterface[] $packages array of packages
|
* @param PackageInterface[] $packages array of packages
|
||||||
* @param PackageInterface[]|null $devPackages array of dev packages or null if installed without --dev
|
* @param PackageInterface[]|null $devPackages array of dev packages or null if installed without --dev
|
||||||
* @param array<string, string> $platformReqs array of package name => constraint for required platform packages
|
* @param array<string, string> $platformReqs array of package name => constraint for required platform packages
|
||||||
* @param array<string, string> $platformDevReqs array of package name => constraint for dev-required platform packages
|
* @param array<string, string> $platformDevReqs array of package name => constraint for dev-required platform packages
|
||||||
* @param string[][] $aliases array of aliases
|
* @param string[][] $aliases array of aliases
|
||||||
* @param string $minimumStability
|
* @param string $minimumStability
|
||||||
* @param array<string, int> $stabilityFlags
|
* @param array<string, int> $stabilityFlags
|
||||||
* @param bool $preferStable
|
* @param bool $preferStable
|
||||||
* @param bool $preferLowest
|
* @param bool $preferLowest
|
||||||
* @param array<string, string> $platformOverrides
|
* @param array<string, string|false> $platformOverrides
|
||||||
* @param bool $write Whether to actually write data to disk, useful in tests and for --dry-run
|
* @param bool $write Whether to actually write data to disk, useful in tests and for --dry-run
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*
|
*
|
||||||
|
@ -386,7 +386,7 @@ class Locker
|
||||||
|
|
||||||
$lock['platform'] = $platformReqs;
|
$lock['platform'] = $platformReqs;
|
||||||
$lock['platform-dev'] = $platformDevReqs;
|
$lock['platform-dev'] = $platformDevReqs;
|
||||||
if ($platformOverrides) {
|
if (\count($platformOverrides) > 0) {
|
||||||
$lock['platform-overrides'] = $platformOverrides;
|
$lock['platform-overrides'] = $platformOverrides;
|
||||||
}
|
}
|
||||||
$lock['plugin-api-version'] = PluginInterface::PLUGIN_API_VERSION;
|
$lock['plugin-api-version'] = PluginInterface::PLUGIN_API_VERSION;
|
||||||
|
|
|
@ -51,7 +51,7 @@ class FossilDriver extends VcsDriver
|
||||||
if (Filesystem::isLocalPath($this->url) && is_dir($this->url)) {
|
if (Filesystem::isLocalPath($this->url) && is_dir($this->url)) {
|
||||||
$this->checkoutDir = $this->url;
|
$this->checkoutDir = $this->url;
|
||||||
} else {
|
} else {
|
||||||
if (!Cache::isUsable((string) $this->config->get('cache-repo-dir')) || !Cache::isUsable((string) $this->config->get('cache-vcs-dir'))) {
|
if (!Cache::isUsable($this->config->get('cache-repo-dir')) || !Cache::isUsable($this->config->get('cache-vcs-dir'))) {
|
||||||
throw new \RuntimeException('FossilDriver requires a usable cache directory, and it looks like you set it to be disabled');
|
throw new \RuntimeException('FossilDriver requires a usable cache directory, and it looks like you set it to be disabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ class GitDriver extends VcsDriver
|
||||||
$this->repoDir = $this->url;
|
$this->repoDir = $this->url;
|
||||||
$cacheUrl = realpath($this->url);
|
$cacheUrl = realpath($this->url);
|
||||||
} else {
|
} else {
|
||||||
if (!Cache::isUsable((string) $this->config->get('cache-vcs-dir'))) {
|
if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) {
|
||||||
throw new \RuntimeException('GitDriver requires a usable cache directory, and it looks like you set it to be disabled');
|
throw new \RuntimeException('GitDriver requires a usable cache directory, and it looks like you set it to be disabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -111,7 +111,7 @@ class GitLabDriver extends VcsDriver
|
||||||
}
|
}
|
||||||
$this->originUrl = $origin;
|
$this->originUrl = $origin;
|
||||||
|
|
||||||
if ($protocol = $this->config->get('gitlab-protocol')) {
|
if (is_string($protocol = $this->config->get('gitlab-protocol'))) {
|
||||||
// https treated as a synonym for http.
|
// https treated as a synonym for http.
|
||||||
if (!in_array($protocol, array('git', 'http', 'https'))) {
|
if (!in_array($protocol, array('git', 'http', 'https'))) {
|
||||||
throw new \RuntimeException('gitlab-protocol must be one of git, http.');
|
throw new \RuntimeException('gitlab-protocol must be one of git, http.');
|
||||||
|
@ -582,7 +582,7 @@ class GitLabDriver extends VcsDriver
|
||||||
$guessedDomain = !empty($match['domain']) ? $match['domain'] : $match['domain2'];
|
$guessedDomain = !empty($match['domain']) ? $match['domain'] : $match['domain2'];
|
||||||
$urlParts = explode('/', $match['parts']);
|
$urlParts = explode('/', $match['parts']);
|
||||||
|
|
||||||
if (false === self::determineOrigin((array) $config->get('gitlab-domains'), $guessedDomain, $urlParts, $match['port'])) {
|
if (false === self::determineOrigin($config->get('gitlab-domains'), $guessedDomain, $urlParts, $match['port'])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ class HgDriver extends VcsDriver
|
||||||
if (Filesystem::isLocalPath($this->url)) {
|
if (Filesystem::isLocalPath($this->url)) {
|
||||||
$this->repoDir = $this->url;
|
$this->repoDir = $this->url;
|
||||||
} else {
|
} else {
|
||||||
if (!Cache::isUsable((string) $this->config->get('cache-vcs-dir'))) {
|
if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) {
|
||||||
throw new \RuntimeException('HgDriver requires a usable cache directory, and it looks like you set it to be disabled');
|
throw new \RuntimeException('HgDriver requires a usable cache directory, and it looks like you set it to be disabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ class PerforceDriver extends VcsDriver
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Cache::isUsable((string) $this->config->get('cache-vcs-dir'))) {
|
if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) {
|
||||||
throw new \RuntimeException('PerforceDriver requires a usable cache directory, and it looks like you set it to be disabled');
|
throw new \RuntimeException('PerforceDriver requires a usable cache directory, and it looks like you set it to be disabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,9 +74,6 @@ class Git
|
||||||
}
|
}
|
||||||
|
|
||||||
$protocols = $this->config->get('github-protocols');
|
$protocols = $this->config->get('github-protocols');
|
||||||
if (!is_array($protocols)) {
|
|
||||||
throw new \RuntimeException('Config value "github-protocols" must be an array, got ' . gettype($protocols));
|
|
||||||
}
|
|
||||||
// public github, autoswitch protocols
|
// public github, autoswitch protocols
|
||||||
if (Preg::isMatch('{^(?:https?|git)://' . self::getGitHubDomainsRegex($this->config) . '/(.*)}', $url, $match)) {
|
if (Preg::isMatch('{^(?:https?|git)://' . self::getGitHubDomainsRegex($this->config) . '/(.*)}', $url, $match)) {
|
||||||
$messages = array();
|
$messages = array();
|
||||||
|
|
|
@ -90,8 +90,8 @@ class GitLab
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($token)) {
|
if (isset($token)) {
|
||||||
$username = is_array($token) && array_key_exists("username", $token) ? $token["username"] : $token;
|
$username = is_array($token) ? $token["username"] : $token;
|
||||||
$password = is_array($token) && array_key_exists("token", $token) ? $token["token"] : 'private-token';
|
$password = is_array($token) ? $token["token"] : 'private-token';
|
||||||
$this->io->setAuthentication($originUrl, $username, $password);
|
$this->io->setAuthentication($originUrl, $username, $password);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -90,9 +90,8 @@ class Url
|
||||||
// Gitlab can be installed in a non-root context (i.e. gitlab.com/foo). When downloading archives the originUrl
|
// Gitlab can be installed in a non-root context (i.e. gitlab.com/foo). When downloading archives the originUrl
|
||||||
// is the host without the path, so we look for the registered gitlab-domains matching the host here
|
// is the host without the path, so we look for the registered gitlab-domains matching the host here
|
||||||
if (
|
if (
|
||||||
is_array($config->get('gitlab-domains'))
|
false === strpos($origin, '/')
|
||||||
&& false === strpos($origin, '/')
|
&& !in_array($origin, $config->get('gitlab-domains'), true)
|
||||||
&& !in_array($origin, $config->get('gitlab-domains'))
|
|
||||||
) {
|
) {
|
||||||
foreach ($config->get('gitlab-domains') as $gitlabDomain) {
|
foreach ($config->get('gitlab-domains') as $gitlabDomain) {
|
||||||
if (0 === strpos($gitlabDomain, $origin)) {
|
if (0 === strpos($gitlabDomain, $origin)) {
|
||||||
|
|
|
@ -104,6 +104,9 @@ class AutoloadGeneratorTest extends TestCase
|
||||||
'platform-check' => function (): bool {
|
'platform-check' => function (): bool {
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
'use-include-path' => function (): bool {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->config->expects($this->atLeastOnce())
|
$this->config->expects($this->atLeastOnce())
|
||||||
|
|
|
@ -14,33 +14,26 @@ namespace Composer\Test\Repository;
|
||||||
|
|
||||||
use Composer\Repository\PathRepository;
|
use Composer\Repository\PathRepository;
|
||||||
use Composer\Test\TestCase;
|
use Composer\Test\TestCase;
|
||||||
|
use Composer\Util\HttpDownloader;
|
||||||
|
use Composer\Util\Loop;
|
||||||
use Composer\Util\Platform;
|
use Composer\Util\Platform;
|
||||||
|
use Composer\Util\ProcessExecutor;
|
||||||
|
|
||||||
class PathRepositoryTest extends TestCase
|
class PathRepositoryTest extends TestCase
|
||||||
{
|
{
|
||||||
public function testLoadPackageFromFileSystemWithIncorrectPath(): void
|
public function testLoadPackageFromFileSystemWithIncorrectPath(): void
|
||||||
{
|
{
|
||||||
self::expectException('RuntimeException');
|
self::expectException('RuntimeException');
|
||||||
$ioInterface = $this->getMockBuilder('Composer\IO\IOInterface')
|
|
||||||
->getMock();
|
|
||||||
|
|
||||||
$config = new \Composer\Config();
|
|
||||||
|
|
||||||
$repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', 'missing'));
|
$repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', 'missing'));
|
||||||
$repository = new PathRepository(array('url' => $repositoryUrl), $ioInterface, $config);
|
$repository = $this->createPathRepo(array('url' => $repositoryUrl));
|
||||||
$repository->getPackages();
|
$repository->getPackages();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLoadPackageFromFileSystemWithVersion(): void
|
public function testLoadPackageFromFileSystemWithVersion(): void
|
||||||
{
|
{
|
||||||
$ioInterface = $this->getMockBuilder('Composer\IO\IOInterface')
|
|
||||||
->getMock();
|
|
||||||
|
|
||||||
$config = new \Composer\Config();
|
|
||||||
$versionGuesser = null;
|
|
||||||
|
|
||||||
$repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', 'with-version'));
|
$repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', 'with-version'));
|
||||||
$repository = new PathRepository(array('url' => $repositoryUrl), $ioInterface, $config);
|
$repository = $this->createPathRepo(array('url' => $repositoryUrl));
|
||||||
$repository->getPackages();
|
$repository->getPackages();
|
||||||
|
|
||||||
$this->assertSame(1, $repository->count());
|
$this->assertSame(1, $repository->count());
|
||||||
|
@ -49,14 +42,8 @@ class PathRepositoryTest extends TestCase
|
||||||
|
|
||||||
public function testLoadPackageFromFileSystemWithoutVersion(): void
|
public function testLoadPackageFromFileSystemWithoutVersion(): void
|
||||||
{
|
{
|
||||||
$ioInterface = $this->getMockBuilder('Composer\IO\IOInterface')
|
|
||||||
->getMock();
|
|
||||||
|
|
||||||
$config = new \Composer\Config();
|
|
||||||
$versionGuesser = null;
|
|
||||||
|
|
||||||
$repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', 'without-version'));
|
$repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', 'without-version'));
|
||||||
$repository = new PathRepository(array('url' => $repositoryUrl), $ioInterface, $config);
|
$repository = $this->createPathRepo(array('url' => $repositoryUrl));
|
||||||
$packages = $repository->getPackages();
|
$packages = $repository->getPackages();
|
||||||
|
|
||||||
$this->assertGreaterThanOrEqual(1, $repository->count());
|
$this->assertGreaterThanOrEqual(1, $repository->count());
|
||||||
|
@ -70,14 +57,8 @@ class PathRepositoryTest extends TestCase
|
||||||
|
|
||||||
public function testLoadPackageFromFileSystemWithWildcard(): void
|
public function testLoadPackageFromFileSystemWithWildcard(): void
|
||||||
{
|
{
|
||||||
$ioInterface = $this->getMockBuilder('Composer\IO\IOInterface')
|
|
||||||
->getMock();
|
|
||||||
|
|
||||||
$config = new \Composer\Config();
|
|
||||||
$versionGuesser = null;
|
|
||||||
|
|
||||||
$repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', '*'));
|
$repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', '*'));
|
||||||
$repository = new PathRepository(array('url' => $repositoryUrl), $ioInterface, $config);
|
$repository = $this->createPathRepo(array('url' => $repositoryUrl));
|
||||||
$packages = $repository->getPackages();
|
$packages = $repository->getPackages();
|
||||||
$names = array();
|
$names = array();
|
||||||
|
|
||||||
|
@ -95,12 +76,6 @@ class PathRepositoryTest extends TestCase
|
||||||
|
|
||||||
public function testLoadPackageWithExplicitVersions(): void
|
public function testLoadPackageWithExplicitVersions(): void
|
||||||
{
|
{
|
||||||
$ioInterface = $this->getMockBuilder('Composer\IO\IOInterface')
|
|
||||||
->getMock();
|
|
||||||
|
|
||||||
$config = new \Composer\Config();
|
|
||||||
$versionGuesser = null;
|
|
||||||
|
|
||||||
$options = array(
|
$options = array(
|
||||||
'versions' => array(
|
'versions' => array(
|
||||||
'test/path-unversioned' => '4.3.2.1',
|
'test/path-unversioned' => '4.3.2.1',
|
||||||
|
@ -108,7 +83,7 @@ class PathRepositoryTest extends TestCase
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
$repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', '*'));
|
$repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', '*'));
|
||||||
$repository = new PathRepository(array('url' => $repositoryUrl, 'options' => $options), $ioInterface, $config);
|
$repository = $this->createPathRepo(array('url' => $repositoryUrl, 'options' => $options));
|
||||||
$packages = $repository->getPackages();
|
$packages = $repository->getPackages();
|
||||||
|
|
||||||
$versions = array();
|
$versions = array();
|
||||||
|
@ -130,12 +105,6 @@ class PathRepositoryTest extends TestCase
|
||||||
*/
|
*/
|
||||||
public function testUrlRemainsRelative(): void
|
public function testUrlRemainsRelative(): void
|
||||||
{
|
{
|
||||||
$ioInterface = $this->getMockBuilder('Composer\IO\IOInterface')
|
|
||||||
->getMock();
|
|
||||||
|
|
||||||
$config = new \Composer\Config();
|
|
||||||
$versionGuesser = null;
|
|
||||||
|
|
||||||
// realpath() does not fully expand the paths
|
// realpath() does not fully expand the paths
|
||||||
// PHP Bug https://bugs.php.net/bug.php?id=72642
|
// PHP Bug https://bugs.php.net/bug.php?id=72642
|
||||||
$repositoryUrl = implode(DIRECTORY_SEPARATOR, array(realpath(realpath(__DIR__)), 'Fixtures', 'path', 'with-version'));
|
$repositoryUrl = implode(DIRECTORY_SEPARATOR, array(realpath(realpath(__DIR__)), 'Fixtures', 'path', 'with-version'));
|
||||||
|
@ -143,7 +112,7 @@ class PathRepositoryTest extends TestCase
|
||||||
// PHP Bug https://bugs.php.net/bug.php?id=73797
|
// PHP Bug https://bugs.php.net/bug.php?id=73797
|
||||||
$relativeUrl = ltrim(substr($repositoryUrl, strlen(realpath(realpath(Platform::getCwd())))), DIRECTORY_SEPARATOR);
|
$relativeUrl = ltrim(substr($repositoryUrl, strlen(realpath(realpath(Platform::getCwd())))), DIRECTORY_SEPARATOR);
|
||||||
|
|
||||||
$repository = new PathRepository(array('url' => $relativeUrl), $ioInterface, $config);
|
$repository = $this->createPathRepo(array('url' => $relativeUrl));
|
||||||
$packages = $repository->getPackages();
|
$packages = $repository->getPackages();
|
||||||
|
|
||||||
$this->assertSame(1, $repository->count());
|
$this->assertSame(1, $repository->count());
|
||||||
|
@ -158,16 +127,11 @@ class PathRepositoryTest extends TestCase
|
||||||
|
|
||||||
public function testReferenceNone(): void
|
public function testReferenceNone(): void
|
||||||
{
|
{
|
||||||
$ioInterface = $this->getMockBuilder('Composer\IO\IOInterface')
|
|
||||||
->getMock();
|
|
||||||
|
|
||||||
$config = new \Composer\Config();
|
|
||||||
|
|
||||||
$options = array(
|
$options = array(
|
||||||
'reference' => 'none',
|
'reference' => 'none',
|
||||||
);
|
);
|
||||||
$repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', '*'));
|
$repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', '*'));
|
||||||
$repository = new PathRepository(array('url' => $repositoryUrl, 'options' => $options), $ioInterface, $config);
|
$repository = $this->createPathRepo(array('url' => $repositoryUrl, 'options' => $options));
|
||||||
$packages = $repository->getPackages();
|
$packages = $repository->getPackages();
|
||||||
|
|
||||||
$this->assertGreaterThanOrEqual(2, $repository->count());
|
$this->assertGreaterThanOrEqual(2, $repository->count());
|
||||||
|
@ -179,17 +143,12 @@ class PathRepositoryTest extends TestCase
|
||||||
|
|
||||||
public function testReferenceConfig(): void
|
public function testReferenceConfig(): void
|
||||||
{
|
{
|
||||||
$ioInterface = $this->getMockBuilder('Composer\IO\IOInterface')
|
|
||||||
->getMock();
|
|
||||||
|
|
||||||
$config = new \Composer\Config();
|
|
||||||
|
|
||||||
$options = array(
|
$options = array(
|
||||||
'reference' => 'config',
|
'reference' => 'config',
|
||||||
'relative' => true,
|
'relative' => true,
|
||||||
);
|
);
|
||||||
$repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', '*'));
|
$repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', '*'));
|
||||||
$repository = new PathRepository(array('url' => $repositoryUrl, 'options' => $options), $ioInterface, $config);
|
$repository = $this->createPathRepo(array('url' => $repositoryUrl, 'options' => $options));
|
||||||
$packages = $repository->getPackages();
|
$packages = $repository->getPackages();
|
||||||
|
|
||||||
$this->assertGreaterThanOrEqual(2, $repository->count());
|
$this->assertGreaterThanOrEqual(2, $repository->count());
|
||||||
|
@ -201,4 +160,19 @@ class PathRepositoryTest extends TestCase
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<mixed> $options
|
||||||
|
*/
|
||||||
|
private function createPathRepo(array $options): PathRepository
|
||||||
|
{
|
||||||
|
$io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock();
|
||||||
|
|
||||||
|
$config = new \Composer\Config();
|
||||||
|
$proc = new ProcessExecutor();
|
||||||
|
$loop = new Loop(new HttpDownloader($io, $config), $proc);
|
||||||
|
|
||||||
|
|
||||||
|
return new PathRepository($options, $io, $config, null, null, $proc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue