-
-
Notifications
You must be signed in to change notification settings - Fork 864
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d2bb725
commit a8d9628
Showing
12 changed files
with
592 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\PhpDoc; | ||
|
||
use PHPStan\Reflection\ParametersAcceptor; | ||
use PHPStan\Rules\RuleError; | ||
use PHPStan\Rules\RuleErrorBuilder; | ||
use PHPStan\Type\ConditionalType; | ||
use PHPStan\Type\ConditionalTypeForParameter; | ||
use PHPStan\Type\Generic\TemplateType; | ||
use PHPStan\Type\Type; | ||
use PHPStan\Type\TypeTraverser; | ||
use PHPStan\Type\VerbosityLevel; | ||
use function array_key_exists; | ||
use function sprintf; | ||
use function substr; | ||
|
||
class ConditionalReturnTypeRuleHelper | ||
{ | ||
|
||
/** | ||
* @return RuleError[] | ||
*/ | ||
public function check(ParametersAcceptor $acceptor): array | ||
{ | ||
$templateTypeMap = $acceptor->getTemplateTypeMap(); | ||
$parametersByName = []; | ||
foreach ($acceptor->getParameters() as $parameter) { | ||
$parametersByName[$parameter->getName()] = $parameter; | ||
} | ||
|
||
$conditionalTypes = []; | ||
TypeTraverser::map($acceptor->getReturnType(), static function (Type $type, callable $traverse) use (&$conditionalTypes): Type { | ||
if ($type instanceof ConditionalType || $type instanceof ConditionalTypeForParameter) { | ||
$conditionalTypes[] = $type; | ||
} | ||
|
||
return $traverse($type); | ||
}); | ||
|
||
$errors = []; | ||
foreach ($conditionalTypes as $conditionalType) { | ||
if ($conditionalType instanceof ConditionalType) { | ||
$subjectType = $conditionalType->getSubject(); | ||
if (!$subjectType instanceof TemplateType || $templateTypeMap->getType($subjectType->getName()) === null) { | ||
$errors[] = RuleErrorBuilder::message(sprintf('Conditional return type uses subject type %s which is not part of PHPDoc @template tags.', $subjectType->describe(VerbosityLevel::typeOnly())))->build(); | ||
continue; | ||
} | ||
} else { | ||
$parameterName = substr($conditionalType->getParameterName(), 1); | ||
if (!array_key_exists($parameterName, $parametersByName)) { | ||
$errors[] = RuleErrorBuilder::message(sprintf('Conditional return type references unknown parameter $%s.', $parameterName))->build(); | ||
continue; | ||
} | ||
$subjectType = $parametersByName[$parameterName]->getType(); | ||
} | ||
|
||
$targetType = $conditionalType->getTarget(); | ||
$isTargetSuperType = $targetType->isSuperTypeOf($subjectType); | ||
if ($isTargetSuperType->maybe()) { | ||
continue; | ||
} | ||
|
||
$errors[] = RuleErrorBuilder::message(sprintf( | ||
'Condition "%s" in conditional return type is always %s.', | ||
sprintf('%s %s %s', $subjectType->describe(VerbosityLevel::typeOnly()), $conditionalType->isNegated() ? 'is not' : 'is', $targetType->describe(VerbosityLevel::typeOnly())), | ||
$conditionalType->isNegated() | ||
? ($isTargetSuperType->yes() ? 'false' : 'true') | ||
: ($isTargetSuperType->yes() ? 'true' : 'false'), | ||
))->build(); | ||
} | ||
|
||
return $errors; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\PhpDoc; | ||
|
||
use PhpParser\Node; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Node\InFunctionNode; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\ShouldNotHappenException; | ||
use function count; | ||
|
||
/** | ||
* @implements Rule<InFunctionNode> | ||
*/ | ||
class FunctionConditionalReturnTypeRule implements Rule | ||
{ | ||
|
||
public function __construct(private ConditionalReturnTypeRuleHelper $helper) | ||
{ | ||
} | ||
|
||
public function getNodeType(): string | ||
{ | ||
return InFunctionNode::class; | ||
} | ||
|
||
public function processNode(Node $node, Scope $scope): array | ||
{ | ||
$method = $scope->getFunction(); | ||
if ($method === null) { | ||
throw new ShouldNotHappenException(); | ||
} | ||
|
||
$variants = $method->getVariants(); | ||
if (count($variants) !== 1) { | ||
return []; | ||
} | ||
|
||
return $this->helper->check($variants[0]); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\PhpDoc; | ||
|
||
use PhpParser\Node; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Node\InClassMethodNode; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\ShouldNotHappenException; | ||
use function count; | ||
|
||
/** | ||
* @implements Rule<InClassMethodNode> | ||
*/ | ||
class MethodConditionalReturnTypeRule implements Rule | ||
{ | ||
|
||
public function __construct(private ConditionalReturnTypeRuleHelper $helper) | ||
{ | ||
} | ||
|
||
public function getNodeType(): string | ||
{ | ||
return InClassMethodNode::class; | ||
} | ||
|
||
public function processNode(Node $node, Scope $scope): array | ||
{ | ||
$method = $scope->getFunction(); | ||
if ($method === null) { | ||
throw new ShouldNotHappenException(); | ||
} | ||
|
||
$variants = $method->getVariants(); | ||
if (count($variants) !== 1) { | ||
return []; | ||
} | ||
|
||
return $this->helper->check($variants[0]); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
66 changes: 66 additions & 0 deletions
66
tests/PHPStan/Rules/PhpDoc/FunctionConditionalReturnTypeRuleTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\PhpDoc; | ||
|
||
use PHPStan\Rules\Rule; | ||
use PHPStan\Testing\RuleTestCase; | ||
|
||
/** | ||
* @extends RuleTestCase<FunctionConditionalReturnTypeRule> | ||
*/ | ||
class FunctionConditionalReturnTypeRuleTest extends RuleTestCase | ||
{ | ||
|
||
protected function getRule(): Rule | ||
{ | ||
return new FunctionConditionalReturnTypeRule(new ConditionalReturnTypeRuleHelper()); | ||
} | ||
|
||
public function testRule(): void | ||
{ | ||
require_once __DIR__ . '/data/function-conditional-return-type.php'; | ||
$this->analyse([__DIR__ . '/data/function-conditional-return-type.php'], [ | ||
[ | ||
'Conditional return type uses subject type stdClass which is not part of PHPDoc @template tags.', | ||
37, | ||
], | ||
[ | ||
'Conditional return type references unknown parameter $j.', | ||
45, | ||
], | ||
[ | ||
'Condition "int is int" in conditional return type is always true.', | ||
53, | ||
], | ||
[ | ||
'Condition "T of int is int" in conditional return type is always true.', | ||
63, | ||
], | ||
[ | ||
'Condition "T of int is int" in conditional return type is always true.', | ||
73, | ||
], | ||
[ | ||
'Condition "int is not int" in conditional return type is always false.', | ||
81, | ||
], | ||
[ | ||
'Condition "int is string" in conditional return type is always false.', | ||
89, | ||
], | ||
[ | ||
'Condition "T of int is string" in conditional return type is always false.', | ||
99, | ||
], | ||
[ | ||
'Condition "T of int is string" in conditional return type is always false.', | ||
109, | ||
], | ||
[ | ||
'Condition "int is not string" in conditional return type is always true.', | ||
117, | ||
], | ||
]); | ||
} | ||
|
||
} |
69 changes: 69 additions & 0 deletions
69
tests/PHPStan/Rules/PhpDoc/MethodConditionalReturnTypeRuleTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\PhpDoc; | ||
|
||
use PHPStan\Rules\Rule; | ||
use PHPStan\Testing\RuleTestCase; | ||
|
||
/** | ||
* @extends RuleTestCase<MethodConditionalReturnTypeRule> | ||
*/ | ||
class MethodConditionalReturnTypeRuleTest extends RuleTestCase | ||
{ | ||
|
||
protected function getRule(): Rule | ||
{ | ||
return new MethodConditionalReturnTypeRule(new ConditionalReturnTypeRuleHelper()); | ||
} | ||
|
||
public function testRule(): void | ||
{ | ||
$this->analyse([__DIR__ . '/data/method-conditional-return-type.php'], [ | ||
[ | ||
'Conditional return type uses subject type stdClass which is not part of PHPDoc @template tags.', | ||
48, | ||
], | ||
[ | ||
'Conditional return type uses subject type TAboveClass which is not part of PHPDoc @template tags.', | ||
57, | ||
], | ||
[ | ||
'Conditional return type references unknown parameter $j.', | ||
65, | ||
], | ||
[ | ||
'Condition "int is int" in conditional return type is always true.', | ||
73, | ||
], | ||
[ | ||
'Condition "T of int is int" in conditional return type is always true.', | ||
83, | ||
], | ||
[ | ||
'Condition "T of int is int" in conditional return type is always true.', | ||
93, | ||
], | ||
[ | ||
'Condition "int is not int" in conditional return type is always false.', | ||
101, | ||
], | ||
[ | ||
'Condition "int is string" in conditional return type is always false.', | ||
114, | ||
], | ||
[ | ||
'Condition "T of int is string" in conditional return type is always false.', | ||
124, | ||
], | ||
[ | ||
'Condition "T of int is string" in conditional return type is always false.', | ||
134, | ||
], | ||
[ | ||
'Condition "int is not string" in conditional return type is always true.', | ||
142, | ||
], | ||
]); | ||
} | ||
|
||
} |
Oops, something went wrong.