Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Require PHPStan 1.10.x #84

Merged
merged 5 commits into from
Mar 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"require": {
"php": "^7.4 || ^8.0",
"nikic/php-parser": "^4.14.0",
"phpstan/phpstan": "^1.9.1"
"phpstan/phpstan": "^1.10.0"
},
"require-dev": {
"editorconfig-checker/editorconfig-checker": "^10.3.0",
Expand Down
2 changes: 0 additions & 2 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -303,13 +303,11 @@
<rule ref="SlevomatCodingStandard.Exceptions.DeadCatch"/>
<rule ref="SlevomatCodingStandard.Exceptions.RequireNonCapturingCatch"/>
<rule ref="SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly"/>
<rule ref="SlevomatCodingStandard.Functions.DisallowArrowFunction"/>
<rule ref="SlevomatCodingStandard.Functions.DisallowNamedArguments"/>
<rule ref="SlevomatCodingStandard.Functions.RequireTrailingCommaInCall"/>
<rule ref="SlevomatCodingStandard.Functions.StaticClosure"/>
<rule ref="SlevomatCodingStandard.Functions.UselessParameterDefaultValue"/>
<rule ref="SlevomatCodingStandard.Functions.UnusedInheritedVariablePassedToClosure"/>
<rule ref="SlevomatCodingStandard.Functions.DisallowArrowFunction"/>
<rule ref="SlevomatCodingStandard.Namespaces.UseFromSameNamespace"/>
<rule ref="SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses">
<properties>
Expand Down
48 changes: 0 additions & 48 deletions src/Helper/EnumTypeHelper.php

This file was deleted.

4 changes: 1 addition & 3 deletions src/Rule/EnforceListReturnRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Rules\Rule;
use PHPStan\Type\Accessory\AccessoryArrayListType;
use PHPStan\Type\ArrayType;
use PHPStan\Type\NeverType;
use PHPStan\Type\VerbosityLevel;
use function count;

Expand Down Expand Up @@ -79,7 +77,7 @@ private function alwaysReturnList(MethodReturnStatementsNode $node): bool
return false;
}

if ($returnStatementsCount === 1 && $returnType instanceof ArrayType && $returnType->getItemType() instanceof NeverType) {
if ($returnStatementsCount === 1 && $returnType->isArray()->yes() && $returnType->isIterableAtLeastOnce()->no()) {
return false; // do not consider empty array as list when it is the only return statement
janedbal marked this conversation as resolved.
Show resolved Hide resolved
}
}
Expand Down
23 changes: 11 additions & 12 deletions src/Rule/EnforceNativeReturnTypehintRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,22 @@
use PHPStan\Type\ArrayType;
use PHPStan\Type\BooleanType;
use PHPStan\Type\CallableType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\FileTypeMapper;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\IterableType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\StaticType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\UnionType;
use PHPStan\Type\VerbosityLevel;
use PHPStan\Type\VoidType;
use function count;
use function implode;
use function in_array;
use function sprintf;
Expand Down Expand Up @@ -114,7 +111,7 @@ private function getTypehintByType(
return $this->phpVersion->getVersionId() >= 80_000 ? 'mixed' : null;
}

if ($type instanceof VoidType) {
if ($type->isVoid()->yes()) {
return 'void';
}

Expand All @@ -126,7 +123,7 @@ private function getTypehintByType(
return 'void';
}

if ($type instanceof NullType) {
if ($type->isNull()->yes()) {
if (!$topLevel || $this->phpVersion->getVersionId() >= 80_200) {
return 'null';
}
Expand All @@ -138,7 +135,7 @@ private function getTypehintByType(
$typeHint = null;

if ((new BooleanType())->accepts($typeWithoutNull, $scope->isDeclareStrictTypes())->yes()) {
if ($typeWithoutNull instanceof ConstantBooleanType && $this->phpVersion->getVersionId() >= 80_200) {
if (($typeWithoutNull->isTrue()->yes() || $typeWithoutNull->isFalse()->yes()) && $this->phpVersion->getVersionId() >= 80_200) {
$typeHint = $typeWithoutNull->describe(VerbosityLevel::typeOnly());
} else {
$typeHint = 'bool';
Expand All @@ -157,11 +154,13 @@ private function getTypehintByType(
} else {
$typeHint = 'static';
}
} elseif ($typeWithoutNull instanceof TypeWithClassName) {
if ($typeWithoutNull->getClassName() === $this->getClassName($scope)) {
} elseif (count($typeWithoutNull->getObjectClassNames()) === 1) {
$className = $typeWithoutNull->getObjectClassNames()[0];

if ($className === $this->getClassName($scope)) {
$typeHint = 'self';
} else {
$typeHint = '\\' . $typeWithoutNull->getClassName();
$typeHint = '\\' . $className;
}
} elseif ((new CallableType())->accepts($typeWithoutNull, $scope->isDeclareStrictTypes())->yes()) {
$typeHint = 'callable';
Expand Down Expand Up @@ -270,7 +269,7 @@ private function getUnionTypehint(Type $type, Scope $scope, bool $typeFromPhpDoc
foreach ($type->getTypes() as $subtype) {
$wrap = false;

if ($subtype instanceof IntersectionType) {
if ($subtype instanceof IntersectionType) { // @phpstan-ignore-line ignore instanceof intersection
if ($this->phpVersion->getVersionId() < 80_200) { // DNF
return null;
}
Expand Down Expand Up @@ -301,7 +300,7 @@ private function getIntersectionTypehint(
bool $alwaysTerminating
): ?string
{
if (!$type instanceof IntersectionType) {
if (!$type instanceof IntersectionType) { // @phpstan-ignore-line ignore instanceof intersection
return null;
}

Expand Down
112 changes: 54 additions & 58 deletions src/Rule/ForbidCustomFunctionsRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\Generic\GenericClassStringType;
use PHPStan\Type\TypeUtils;
use function array_map;
use function array_merge;
use function count;
use function explode;
use function gettype;
Expand Down Expand Up @@ -84,46 +84,33 @@ public function getNodeType(): string
public function processNode(Node $node, Scope $scope): array
{
if ($node instanceof MethodCall) {
$methodName = $this->getMethodName($node->name, $scope);
$methodNames = $this->getMethodNames($node->name, $scope);

if ($methodName === null) {
return [];
}

return $this->validateCallOverExpr($methodName, $node->var, $scope);
return $this->validateCallOverExpr($methodNames, $node->var, $scope);
}

if ($node instanceof StaticCall) {
$methodName = $this->getMethodName($node->name, $scope);

if ($methodName === null) {
return [];
}
$methodNames = $this->getMethodNames($node->name, $scope);

$classNode = $node->class;

if ($classNode instanceof Name) {
return $this->validateMethod($methodName, $scope->resolveName($classNode));
return $this->validateMethod($methodNames, $scope->resolveName($classNode));
}

return $this->validateCallOverExpr($methodName, $classNode, $scope);
return $this->validateCallOverExpr($methodNames, $classNode, $scope);
}

if ($node instanceof FuncCall) {
$methodName = $this->getFunctionName($node->name, $scope);

if ($methodName === null) {
return [];
}

return $this->validateFunction($methodName);
$methodNames = $this->getFunctionNames($node->name, $scope);
return $this->validateFunction($methodNames);
}

if ($node instanceof New_) {
$classNode = $node->class;

if ($classNode instanceof Name) {
return $this->validateMethod('__construct', $scope->resolveName($classNode));
return $this->validateMethod(['__construct'], $scope->resolveName($classNode));
}

if ($classNode instanceof Expr) {
Expand All @@ -143,107 +130,116 @@ private function validateConstructorWithDynamicString(Expr $expr, Scope $scope):
{
$type = $scope->getType($expr);

if ($type instanceof ConstantStringType) {
return $this->validateMethod('__construct', $type->getValue());
$errors = [];

foreach ($type->getConstantStrings() as $constantStringType) {
$errors = array_merge($errors, $this->validateMethod(['__construct'], $constantStringType->getValue()));
}

return [];
return $errors;
}

/**
* @param list<string> $methodNames
* @return list<string>
*/
private function validateCallOverExpr(string $methodName, Expr $expr, Scope $scope): array
private function validateCallOverExpr(array $methodNames, Expr $expr, Scope $scope): array
{
$classType = $scope->getType($expr);

if ($classType instanceof GenericClassStringType) {
$classType = $classType->getGenericType();
}

$classNames = TypeUtils::getDirectClassNames($classType);
$classNames = $classType->getObjectTypeOrClassStringObjectType()->getObjectClassNames();
$errors = [];

foreach ($classNames as $className) {
$errors = [
...$errors,
...$this->validateMethod($methodName, $className),
...$this->validateMethod($methodNames, $className),
];
}

return $errors;
}

/**
* @param list<string> $methodNames
* @return list<string>
*/
private function validateMethod(string $methodName, string $className): array
private function validateMethod(array $methodNames, string $className): array
{
$errors = [];

foreach ($this->reflectionProvider->getClass($className)->getAncestors() as $ancestor) {
$ancestorClassName = $ancestor->getName();

if (isset($this->forbiddenFunctions[$ancestorClassName][self::ANY_METHOD])) {
return [sprintf('Class %s is forbidden. %s', $ancestorClassName, $this->forbiddenFunctions[$ancestorClassName][self::ANY_METHOD])];
$errors[] = sprintf('Class %s is forbidden. %s', $ancestorClassName, $this->forbiddenFunctions[$ancestorClassName][self::ANY_METHOD]);
}

if (isset($this->forbiddenFunctions[$ancestorClassName][$methodName])) {
return [sprintf('Method %s::%s() is forbidden. %s', $ancestorClassName, $methodName, $this->forbiddenFunctions[$ancestorClassName][$methodName])];
foreach ($methodNames as $methodName) {
if (isset($this->forbiddenFunctions[$ancestorClassName][$methodName])) {
$errors[] = sprintf('Method %s::%s() is forbidden. %s', $ancestorClassName, $methodName, $this->forbiddenFunctions[$ancestorClassName][$methodName]);
}
}
}

return [];
return $errors;
}

/**
* @param list<string> $functionNames
* @return list<string>
*/
private function validateFunction(string $functionName): array
private function validateFunction(array $functionNames): array
{
if (isset($this->forbiddenFunctions[self::FUNCTION][$functionName])) {
return [sprintf('Function %s() is forbidden. %s', $functionName, $this->forbiddenFunctions[self::FUNCTION][$functionName])];
$errors = [];

foreach ($functionNames as $functionName) {
if (isset($this->forbiddenFunctions[self::FUNCTION][$functionName])) {
$errors[] = sprintf('Function %s() is forbidden. %s', $functionName, $this->forbiddenFunctions[self::FUNCTION][$functionName]);
}
}

return [];
return $errors;
}

/**
* @param Name|Expr $name
* @return list<string>
*/
private function getFunctionName(Node $name, Scope $scope): ?string
private function getFunctionNames(Node $name, Scope $scope): array
{
if ($name instanceof Name) {
return $this->reflectionProvider->resolveFunctionName($name, $scope);
$functionName = $this->reflectionProvider->resolveFunctionName($name, $scope);
return $functionName === null ? [] : [$functionName];
}

$nameType = $scope->getType($name);

if ($nameType instanceof ConstantStringType) {
return $nameType->getValue();
}

return null;
return array_map(
static fn (ConstantStringType $type) => $type->getValue(),
$nameType->getConstantStrings(),
);
}

/**
* @param Name|Expr|Identifier $name
* @return list<string>
*/
private function getMethodName(Node $name, Scope $scope): ?string
private function getMethodNames(Node $name, Scope $scope): array
{
if ($name instanceof Name) {
return $name->toString();
return [$name->toString()];
}

if ($name instanceof Identifier) {
return $name->toString();
return [$name->toString()];
}

$nameType = $scope->getType($name);

if ($nameType instanceof ConstantStringType) {
return $nameType->getValue();
}

return null;
return array_map(
static fn (ConstantStringType $type) => $type->getValue(),
$nameType->getConstantStrings(),
);
}

}
Loading