Skip to content

Commit e7d6c3a

Browse files
committed
DRY for @param-out related rules
1 parent 879ecaa commit e7d6c3a

File tree

6 files changed

+147
-185
lines changed

6 files changed

+147
-185
lines changed

conf/config.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,6 +1027,9 @@ services:
10271027
-
10281028
class: PHPStan\Rules\UnusedFunctionParametersCheck
10291029

1030+
-
1031+
class: PHPStan\Rules\TooWideTypehints\TooWideParameterOutTypeCheck
1032+
10301033
-
10311034
class: PHPStan\Type\FileTypeMapper
10321035
arguments:

src/Rules/TooWideTypehints/TooWideFunctionParameterOutTypeRule.php

Lines changed: 12 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,8 @@
55
use PhpParser\Node;
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Node\FunctionReturnStatementsNode;
8-
use PHPStan\Reflection\FunctionReflection;
9-
use PHPStan\Reflection\ParameterReflectionWithPhpDocs;
108
use PHPStan\Reflection\ParametersAcceptorSelector;
119
use PHPStan\Rules\Rule;
12-
use PHPStan\Rules\RuleError;
13-
use PHPStan\Rules\RuleErrorBuilder;
14-
use PHPStan\Type\TypeUtils;
15-
use PHPStan\Type\UnionType;
16-
use PHPStan\Type\VerbosityLevel;
1710
use function sprintf;
1811

1912
/**
@@ -22,6 +15,12 @@
2215
class TooWideFunctionParameterOutTypeRule implements Rule
2316
{
2417

18+
public function __construct(
19+
private TooWideParameterOutTypeCheck $check,
20+
)
21+
{
22+
}
23+
2524
public function getNodeType(): string
2625
{
2726
return FunctionReturnStatementsNode::class;
@@ -30,91 +29,13 @@ public function getNodeType(): string
3029
public function processNode(Node $node, Scope $scope): array
3130
{
3231
$inFunction = $node->getFunctionReflection();
33-
$finalScope = null;
34-
foreach ($node->getExecutionEnds() as $executionEnd) {
35-
$endScope = $executionEnd->getStatementResult()->getScope();
36-
if ($finalScope === null) {
37-
$finalScope = $endScope;
38-
continue;
39-
}
40-
41-
$finalScope = $finalScope->mergeWith($endScope);
42-
}
43-
44-
foreach ($node->getReturnStatements() as $statement) {
45-
if ($finalScope === null) {
46-
$finalScope = $statement->getScope();
47-
continue;
48-
}
49-
50-
$finalScope = $finalScope->mergeWith($statement->getScope());
51-
}
52-
53-
if ($finalScope === null) {
54-
return [];
55-
}
56-
57-
$variant = ParametersAcceptorSelector::selectSingle($inFunction->getVariants());
58-
$parameters = $variant->getParameters();
59-
$errors = [];
60-
foreach ($parameters as $parameter) {
61-
if (!$parameter->passedByReference()->createsNewVariable()) {
62-
continue;
63-
}
64-
65-
foreach ($this->processSingleParameter($finalScope, $inFunction, $parameter) as $error) {
66-
$errors[] = $error;
67-
}
68-
}
69-
70-
return $errors;
71-
}
72-
73-
/**
74-
* @return array<RuleError>
75-
*/
76-
private function processSingleParameter(
77-
Scope $scope,
78-
FunctionReflection $inFunction,
79-
ParameterReflectionWithPhpDocs $parameter,
80-
): array
81-
{
82-
$isParamOutType = true;
83-
$outType = $parameter->getOutType();
84-
if ($outType === null) {
85-
$isParamOutType = false;
86-
$outType = $parameter->getType();
87-
}
88-
89-
$outType = TypeUtils::resolveLateResolvableTypes($outType);
90-
if (!$outType instanceof UnionType) {
91-
return [];
92-
}
93-
94-
$variableExpr = new Node\Expr\Variable($parameter->getName());
95-
$variableType = $scope->getType($variableExpr);
96-
97-
$messages = [];
98-
foreach ($outType->getTypes() as $type) {
99-
if (!$type->isSuperTypeOf($variableType)->no()) {
100-
continue;
101-
}
102-
103-
$errorBuilder = RuleErrorBuilder::message(sprintf(
104-
'Function %s() never assigns %s to &$%s so it can be removed from the %s.',
105-
$inFunction->getName(),
106-
$type->describe(VerbosityLevel::getRecommendedLevelByType($type)),
107-
$parameter->getName(),
108-
$isParamOutType ? '@param-out type' : 'by-ref type',
109-
));
110-
if (!$isParamOutType) {
111-
$errorBuilder->tip('You can narrow the parameter out type with @param-out PHPDoc tag.');
112-
}
113-
114-
$messages[] = $errorBuilder->build();
115-
}
11632

117-
return $messages;
33+
return $this->check->check(
34+
$node->getExecutionEnds(),
35+
$node->getReturnStatements(),
36+
ParametersAcceptorSelector::selectSingle($inFunction->getVariants())->getParameters(),
37+
sprintf('Function %s()', $inFunction->getName()),
38+
);
11839
}
11940

12041
}

src/Rules/TooWideTypehints/TooWideMethodParameterOutTypeRule.php

Lines changed: 12 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,8 @@
55
use PhpParser\Node;
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Node\MethodReturnStatementsNode;
8-
use PHPStan\Reflection\ExtendedMethodReflection;
9-
use PHPStan\Reflection\ParameterReflectionWithPhpDocs;
108
use PHPStan\Reflection\ParametersAcceptorSelector;
119
use PHPStan\Rules\Rule;
12-
use PHPStan\Rules\RuleError;
13-
use PHPStan\Rules\RuleErrorBuilder;
14-
use PHPStan\Type\TypeUtils;
15-
use PHPStan\Type\UnionType;
16-
use PHPStan\Type\VerbosityLevel;
1710
use function sprintf;
1811

1912
/**
@@ -22,6 +15,12 @@
2215
class TooWideMethodParameterOutTypeRule implements Rule
2316
{
2417

18+
public function __construct(
19+
private TooWideParameterOutTypeCheck $check,
20+
)
21+
{
22+
}
23+
2524
public function getNodeType(): string
2625
{
2726
return MethodReturnStatementsNode::class;
@@ -30,92 +29,13 @@ public function getNodeType(): string
3029
public function processNode(Node $node, Scope $scope): array
3130
{
3231
$inMethod = $node->getMethodReflection();
33-
$finalScope = null;
34-
foreach ($node->getExecutionEnds() as $executionEnd) {
35-
$endScope = $executionEnd->getStatementResult()->getScope();
36-
if ($finalScope === null) {
37-
$finalScope = $endScope;
38-
continue;
39-
}
40-
41-
$finalScope = $finalScope->mergeWith($endScope);
42-
}
43-
44-
foreach ($node->getReturnStatements() as $statement) {
45-
if ($finalScope === null) {
46-
$finalScope = $statement->getScope();
47-
continue;
48-
}
49-
50-
$finalScope = $finalScope->mergeWith($statement->getScope());
51-
}
52-
53-
if ($finalScope === null) {
54-
return [];
55-
}
56-
57-
$variant = ParametersAcceptorSelector::selectSingle($inMethod->getVariants());
58-
$parameters = $variant->getParameters();
59-
$errors = [];
60-
foreach ($parameters as $parameter) {
61-
if (!$parameter->passedByReference()->createsNewVariable()) {
62-
continue;
63-
}
64-
65-
foreach ($this->processSingleParameter($finalScope, $inMethod, $parameter) as $error) {
66-
$errors[] = $error;
67-
}
68-
}
69-
70-
return $errors;
71-
}
72-
73-
/**
74-
* @return array<RuleError>
75-
*/
76-
private function processSingleParameter(
77-
Scope $scope,
78-
ExtendedMethodReflection $inMethod,
79-
ParameterReflectionWithPhpDocs $parameter,
80-
): array
81-
{
82-
$isParamOutType = true;
83-
$outType = $parameter->getOutType();
84-
if ($outType === null) {
85-
$isParamOutType = false;
86-
$outType = $parameter->getType();
87-
}
88-
89-
$outType = TypeUtils::resolveLateResolvableTypes($outType);
90-
if (!$outType instanceof UnionType) {
91-
return [];
92-
}
93-
94-
$variableExpr = new Node\Expr\Variable($parameter->getName());
95-
$variableType = $scope->getType($variableExpr);
96-
97-
$messages = [];
98-
foreach ($outType->getTypes() as $type) {
99-
if (!$type->isSuperTypeOf($variableType)->no()) {
100-
continue;
101-
}
102-
103-
$errorBuilder = RuleErrorBuilder::message(sprintf(
104-
'Method %s::%s() never assigns %s to &$%s so it can be removed from the %s.',
105-
$inMethod->getDeclaringClass()->getDisplayName(),
106-
$inMethod->getName(),
107-
$type->describe(VerbosityLevel::getRecommendedLevelByType($type)),
108-
$parameter->getName(),
109-
$isParamOutType ? '@param-out type' : 'by-ref type',
110-
));
111-
if (!$isParamOutType) {
112-
$errorBuilder->tip('You can narrow the parameter out type with @param-out PHPDoc tag.');
113-
}
114-
115-
$messages[] = $errorBuilder->build();
116-
}
11732

118-
return $messages;
33+
return $this->check->check(
34+
$node->getExecutionEnds(),
35+
$node->getReturnStatements(),
36+
ParametersAcceptorSelector::selectSingle($inMethod->getVariants())->getParameters(),
37+
sprintf('Method %s::%s()', $inMethod->getDeclaringClass()->getDisplayName(), $inMethod->getName()),
38+
);
11939
}
12040

12141
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\TooWideTypehints;
4+
5+
use PhpParser\Node\Expr\Variable;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Node\ExecutionEndNode;
8+
use PHPStan\Node\ReturnStatement;
9+
use PHPStan\Reflection\ParameterReflectionWithPhpDocs;
10+
use PHPStan\Rules\RuleError;
11+
use PHPStan\Rules\RuleErrorBuilder;
12+
use PHPStan\Type\TypeUtils;
13+
use PHPStan\Type\UnionType;
14+
use PHPStan\Type\VerbosityLevel;
15+
use function sprintf;
16+
17+
class TooWideParameterOutTypeCheck
18+
{
19+
20+
/**
21+
* @param list<ExecutionEndNode> $executionEnds
22+
* @param list<ReturnStatement> $returnStatements
23+
* @param ParameterReflectionWithPhpDocs[] $parameters
24+
* @return list<RuleError>
25+
*/
26+
public function check(
27+
array $executionEnds,
28+
array $returnStatements,
29+
array $parameters,
30+
string $functionDescription,
31+
): array
32+
{
33+
$finalScope = null;
34+
foreach ($executionEnds as $executionEnd) {
35+
$endScope = $executionEnd->getStatementResult()->getScope();
36+
if ($finalScope === null) {
37+
$finalScope = $endScope;
38+
continue;
39+
}
40+
41+
$finalScope = $finalScope->mergeWith($endScope);
42+
}
43+
44+
foreach ($returnStatements as $statement) {
45+
if ($finalScope === null) {
46+
$finalScope = $statement->getScope();
47+
continue;
48+
}
49+
50+
$finalScope = $finalScope->mergeWith($statement->getScope());
51+
}
52+
53+
if ($finalScope === null) {
54+
return [];
55+
}
56+
57+
$errors = [];
58+
foreach ($parameters as $parameter) {
59+
if (!$parameter->passedByReference()->createsNewVariable()) {
60+
continue;
61+
}
62+
63+
foreach ($this->processSingleParameter($finalScope, $functionDescription, $parameter) as $error) {
64+
$errors[] = $error;
65+
}
66+
}
67+
68+
return $errors;
69+
}
70+
71+
/**
72+
* @return array<RuleError>
73+
*/
74+
private function processSingleParameter(
75+
Scope $scope,
76+
string $functionDescription,
77+
ParameterReflectionWithPhpDocs $parameter,
78+
): array
79+
{
80+
$isParamOutType = true;
81+
$outType = $parameter->getOutType();
82+
if ($outType === null) {
83+
$isParamOutType = false;
84+
$outType = $parameter->getType();
85+
}
86+
87+
$outType = TypeUtils::resolveLateResolvableTypes($outType);
88+
if (!$outType instanceof UnionType) {
89+
return [];
90+
}
91+
92+
$variableExpr = new Variable($parameter->getName());
93+
$variableType = $scope->getType($variableExpr);
94+
95+
$messages = [];
96+
foreach ($outType->getTypes() as $type) {
97+
if (!$type->isSuperTypeOf($variableType)->no()) {
98+
continue;
99+
}
100+
101+
$errorBuilder = RuleErrorBuilder::message(sprintf(
102+
'%s never assigns %s to &$%s so it can be removed from the %s.',
103+
$functionDescription,
104+
$type->describe(VerbosityLevel::getRecommendedLevelByType($type)),
105+
$parameter->getName(),
106+
$isParamOutType ? '@param-out type' : 'by-ref type',
107+
));
108+
if (!$isParamOutType) {
109+
$errorBuilder->tip('You can narrow the parameter out type with @param-out PHPDoc tag.');
110+
}
111+
112+
$messages[] = $errorBuilder->build();
113+
}
114+
115+
return $messages;
116+
}
117+
118+
}

tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionParameterOutTypeRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class TooWideFunctionParameterOutTypeRuleTest extends RuleTestCase
1313

1414
protected function getRule(): TRule
1515
{
16-
return new TooWideFunctionParameterOutTypeRule();
16+
return new TooWideFunctionParameterOutTypeRule(new TooWideParameterOutTypeCheck());
1717
}
1818

1919
public function testRule(): void

0 commit comments

Comments
 (0)