Implement config type parsing
parent
532166a892
commit
6fdd9494ef
|
@ -326,7 +326,7 @@
|
|||
},
|
||||
"github-protocols": {
|
||||
"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": {
|
||||
"type": "string"
|
||||
}
|
||||
|
|
|
@ -3,20 +3,39 @@
|
|||
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\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 {
|
||||
final class ConfigReturnTypeExtension implements DynamicMethodReturnTypeExtension
|
||||
{
|
||||
/** @var array<string, \PHPStan\Type\Type> */
|
||||
private $properties = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$schema = JsonFile::parseJson(file_get_contents(__DIR__.'/../../../res/composer-schema.json'));
|
||||
foreach ($schema['properties']['config']['properties'] as $prop => $conf) {
|
||||
$type = $this->parseType($conf, $prop);
|
||||
|
||||
$this->properties[$prop] = $type;
|
||||
}
|
||||
}
|
||||
|
||||
public function getClass(): string
|
||||
{
|
||||
|
@ -38,13 +57,115 @@ final class ConfigReturnTypeExtension implements DynamicMethodReturnTypeExtensio
|
|||
|
||||
$keyType = $scope->getType($args[0]->value);
|
||||
if ($keyType instanceof ConstantStringType) {
|
||||
if ($keyType->getValue() == 'allow-plugins') {
|
||||
return TypeCombinator::addNull(
|
||||
new ArrayType(new StringType(), new BooleanType())
|
||||
);
|
||||
if (isset($this->properties[$keyType->getValue()])) {
|
||||
return $this->properties[$keyType->getValue()];
|
||||
}
|
||||
}
|
||||
|
||||
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $types
|
||||
*/
|
||||
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'], true)) {
|
||||
$types[] = IntegerRangeType::createAllGreaterThan(0);
|
||||
} else {
|
||||
$types[] = new IntegerType();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'string':
|
||||
if ($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':
|
||||
$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'])) {
|
||||
$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();
|
||||
}
|
||||
}
|
||||
} elseif (isset($def['enum'])) {
|
||||
$types[] = TypeCombinator::union(...array_map(function (string $value): ConstantStringType {
|
||||
return new ConstantStringType($value);
|
||||
}, $def['enum']));
|
||||
} else {
|
||||
$types = [new MixedType()];
|
||||
}
|
||||
|
||||
$type = \count($types) === 1 ? $types[0] : TypeCombinator::union(...$types);
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue