|
12 | 12 | use PHPStan\Rules\Rule; |
13 | 13 | use PHPStan\Rules\RuleErrorBuilder; |
14 | 14 | use PHPStan\ShouldNotHappenException; |
| 15 | +use PHPStan\Type\ClosureType; |
15 | 16 | use PHPStan\Type\FileTypeMapper; |
16 | 17 | use PHPStan\Type\Generic\TemplateType; |
17 | 18 | use PHPStan\Type\Type; |
18 | 19 | use PHPStan\Type\VerbosityLevel; |
19 | 20 | use function array_merge; |
| 21 | +use function in_array; |
20 | 22 | use function is_string; |
21 | 23 | use function sprintf; |
22 | 24 | use function trim; |
@@ -68,10 +70,9 @@ public function processNode(Node $node, Scope $scope): array |
68 | 70 |
|
69 | 71 | $errors = []; |
70 | 72 |
|
71 | | - foreach ([$resolvedPhpDoc->getParamTags(), $resolvedPhpDoc->getParamOutTags()] as $parameters) { |
| 73 | + foreach (['@param' => $resolvedPhpDoc->getParamTags(), '@param-out' => $resolvedPhpDoc->getParamOutTags(), '@param-closure-this' => $resolvedPhpDoc->getParamClosureThisTags()] as $tagName => $parameters) { |
72 | 74 | foreach ($parameters as $parameterName => $phpDocParamTag) { |
73 | 75 | $phpDocParamType = $phpDocParamTag->getType(); |
74 | | - $tagName = $phpDocParamTag instanceof ParamTag ? '@param' : '@param-out'; |
75 | 76 |
|
76 | 77 | if (!isset($nativeParameterTypes[$parameterName])) { |
77 | 78 | $errors[] = RuleErrorBuilder::message(sprintf( |
@@ -99,7 +100,6 @@ public function processNode(Node $node, Scope $scope): array |
99 | 100 | ) { |
100 | 101 | $phpDocParamType = $phpDocParamType->getIterableValueType(); |
101 | 102 | } |
102 | | - $isParamSuperType = $nativeParamType->isSuperTypeOf($phpDocParamType); |
103 | 103 |
|
104 | 104 | $escapedParameterName = SprintfHelper::escapeFormatString($parameterName); |
105 | 105 | $escapedTagName = SprintfHelper::escapeFormatString($tagName); |
@@ -160,28 +160,43 @@ public function processNode(Node $node, Scope $scope): array |
160 | 160 | continue; |
161 | 161 | } |
162 | 162 |
|
163 | | - if ($isParamSuperType->no()) { |
164 | | - $errors[] = RuleErrorBuilder::message(sprintf( |
165 | | - 'PHPDoc tag %s for parameter $%s with type %s is incompatible with native type %s.', |
166 | | - $tagName, |
167 | | - $parameterName, |
168 | | - $phpDocParamType->describe(VerbosityLevel::typeOnly()), |
169 | | - $nativeParamType->describe(VerbosityLevel::typeOnly()), |
170 | | - ))->identifier('parameter.phpDocType')->build(); |
171 | | - |
172 | | - } elseif ($isParamSuperType->maybe()) { |
173 | | - $errorBuilder = RuleErrorBuilder::message(sprintf( |
174 | | - 'PHPDoc tag %s for parameter $%s with type %s is not subtype of native type %s.', |
175 | | - $tagName, |
176 | | - $parameterName, |
177 | | - $phpDocParamType->describe(VerbosityLevel::typeOnly()), |
178 | | - $nativeParamType->describe(VerbosityLevel::typeOnly()), |
179 | | - ))->identifier('parameter.phpDocType'); |
180 | | - if ($phpDocParamType instanceof TemplateType) { |
181 | | - $errorBuilder->tip(sprintf('Write @template %s of %s to fix this.', $phpDocParamType->getName(), $nativeParamType->describe(VerbosityLevel::typeOnly()))); |
| 163 | + if (in_array($tagName, ['@param', '@param-out'], true)) { |
| 164 | + $isParamSuperType = $nativeParamType->isSuperTypeOf($phpDocParamType); |
| 165 | + if ($isParamSuperType->no()) { |
| 166 | + $errors[] = RuleErrorBuilder::message(sprintf( |
| 167 | + 'PHPDoc tag %s for parameter $%s with type %s is incompatible with native type %s.', |
| 168 | + $tagName, |
| 169 | + $parameterName, |
| 170 | + $phpDocParamType->describe(VerbosityLevel::typeOnly()), |
| 171 | + $nativeParamType->describe(VerbosityLevel::typeOnly()), |
| 172 | + ))->identifier('parameter.phpDocType')->build(); |
| 173 | + |
| 174 | + } elseif ($isParamSuperType->maybe()) { |
| 175 | + $errorBuilder = RuleErrorBuilder::message(sprintf( |
| 176 | + 'PHPDoc tag %s for parameter $%s with type %s is not subtype of native type %s.', |
| 177 | + $tagName, |
| 178 | + $parameterName, |
| 179 | + $phpDocParamType->describe(VerbosityLevel::typeOnly()), |
| 180 | + $nativeParamType->describe(VerbosityLevel::typeOnly()), |
| 181 | + ))->identifier('parameter.phpDocType'); |
| 182 | + if ($phpDocParamType instanceof TemplateType) { |
| 183 | + $errorBuilder->tip(sprintf('Write @template %s of %s to fix this.', $phpDocParamType->getName(), $nativeParamType->describe(VerbosityLevel::typeOnly()))); |
| 184 | + } |
| 185 | + |
| 186 | + $errors[] = $errorBuilder->build(); |
182 | 187 | } |
| 188 | + } |
183 | 189 |
|
184 | | - $errors[] = $errorBuilder->build(); |
| 190 | + if ($tagName === '@param-closure-this') { |
| 191 | + $isNonClosure = (new ClosureType())->isSuperTypeOf($nativeParamType)->no(); |
| 192 | + if ($isNonClosure) { |
| 193 | + $errors[] = RuleErrorBuilder::message(sprintf( |
| 194 | + 'PHPDoc tag %s is for parameter $%s with non-Closure type %s.', |
| 195 | + $tagName, |
| 196 | + $parameterName, |
| 197 | + $nativeParamType->describe(VerbosityLevel::typeOnly()), |
| 198 | + ))->identifier('paramClosureThis.nonClosure')->build(); |
| 199 | + } |
185 | 200 | } |
186 | 201 | } |
187 | 202 | } |
|
0 commit comments