Skip to content

Commit 892b319

Browse files
committed
Bleeding edge - MissingMethodSelfOutTypeRule
1 parent 9ebc315 commit 892b319

File tree

5 files changed

+171
-0
lines changed

5 files changed

+171
-0
lines changed

conf/config.level6.neon

+7
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ rules:
1313
- PHPStan\Rules\Methods\MissingMethodReturnTypehintRule
1414
- PHPStan\Rules\Properties\MissingPropertyTypehintRule
1515

16+
conditionalTags:
17+
PHPStan\Rules\Methods\MissingMethodSelfOutTypeRule:
18+
phpstan.rules.rule: %featureToggles.absentTypeChecks%
19+
1620
services:
1721
-
1822
class: PHPStan\Rules\Functions\MissingFunctionParameterTypehintRule
@@ -27,3 +31,6 @@ services:
2731
paramOut: %featureToggles.paramOutType%
2832
tags:
2933
- phpstan.rules.rule
34+
35+
-
36+
class: PHPStan\Rules\Methods\MissingMethodSelfOutTypeRule

src/PhpDoc/StubValidator.php

+5
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
use PHPStan\Rules\Methods\MethodSignatureRule;
5252
use PHPStan\Rules\Methods\MissingMethodParameterTypehintRule;
5353
use PHPStan\Rules\Methods\MissingMethodReturnTypehintRule;
54+
use PHPStan\Rules\Methods\MissingMethodSelfOutTypeRule;
5455
use PHPStan\Rules\Methods\OverridingMethodRule;
5556
use PHPStan\Rules\MissingTypehintCheck;
5657
use PHPStan\Rules\PhpDoc\GenericCallableRuleHelper;
@@ -228,6 +229,10 @@ private function getRuleRegistry(Container $container): RuleRegistry
228229
);
229230
}
230231

232+
if ((bool) $container->getParameter('featureToggles')['absentTypeChecks']) {
233+
$rules[] = new MissingMethodSelfOutTypeRule($missingTypehintCheck);
234+
}
235+
231236
return new DirectRuleRegistry($rules);
232237
}
233238

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Methods;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Node\InClassMethodNode;
8+
use PHPStan\Rules\MissingTypehintCheck;
9+
use PHPStan\Rules\Rule;
10+
use PHPStan\Rules\RuleErrorBuilder;
11+
use PHPStan\Type\VerbosityLevel;
12+
use function implode;
13+
use function sprintf;
14+
15+
/**
16+
* @implements Rule<InClassMethodNode>
17+
*/
18+
final class MissingMethodSelfOutTypeRule implements Rule
19+
{
20+
21+
public function __construct(
22+
private MissingTypehintCheck $missingTypehintCheck,
23+
)
24+
{
25+
}
26+
27+
public function getNodeType(): string
28+
{
29+
return InClassMethodNode::class;
30+
}
31+
32+
public function processNode(Node $node, Scope $scope): array
33+
{
34+
$methodReflection = $node->getMethodReflection();
35+
$selfOutType = $methodReflection->getSelfOutType();
36+
37+
if ($selfOutType === null) {
38+
return [];
39+
}
40+
41+
$classReflection = $methodReflection->getDeclaringClass();
42+
$phpDocTagMessage = 'PHPDoc tag @phpstan-self-out';
43+
44+
$messages = [];
45+
foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($selfOutType) as $iterableType) {
46+
$iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly());
47+
$messages[] = RuleErrorBuilder::message(sprintf(
48+
'Method %s::%s() has %s with no value type specified in iterable type %s.',
49+
$classReflection->getDisplayName(),
50+
$methodReflection->getName(),
51+
$phpDocTagMessage,
52+
$iterableTypeDescription,
53+
))
54+
->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP)
55+
->identifier('missingType.iterableValue')
56+
->build();
57+
}
58+
59+
foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($selfOutType) as [$name, $genericTypeNames]) {
60+
$messages[] = RuleErrorBuilder::message(sprintf(
61+
'Method %s::%s() has %s with generic %s but does not specify its types: %s',
62+
$classReflection->getDisplayName(),
63+
$methodReflection->getName(),
64+
$phpDocTagMessage,
65+
$name,
66+
implode(', ', $genericTypeNames),
67+
))
68+
->identifier('missingType.generics')
69+
->build();
70+
}
71+
72+
foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($selfOutType) as $callableType) {
73+
$messages[] = RuleErrorBuilder::message(sprintf(
74+
'Method %s::%s() has %s with no signature specified for %s.',
75+
$classReflection->getDisplayName(),
76+
$methodReflection->getName(),
77+
$phpDocTagMessage,
78+
$callableType->describe(VerbosityLevel::typeOnly()),
79+
))->identifier('missingType.callable')->build();
80+
}
81+
82+
return $messages;
83+
}
84+
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Methods;
4+
5+
use PHPStan\Rules\MissingTypehintCheck;
6+
use PHPStan\Rules\Rule as TRule;
7+
use PHPStan\Testing\RuleTestCase;
8+
9+
/**
10+
* @extends RuleTestCase<MissingMethodSelfOutTypeRule>
11+
*/
12+
class MissingMethodSelfOutTypeRuleTest extends RuleTestCase
13+
{
14+
15+
protected function getRule(): TRule
16+
{
17+
return new MissingMethodSelfOutTypeRule(new MissingTypehintCheck(true, true, true, true, []));
18+
}
19+
20+
public function testRule(): void
21+
{
22+
$this->analyse([__DIR__ . '/data/missing-method-self-out-type.php'], [
23+
[
24+
'Method MissingMethodSelfOutType\Foo::doFoo() has PHPDoc tag @phpstan-self-out with no value type specified in iterable type array.',
25+
14,
26+
'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type',
27+
],
28+
[
29+
'Method MissingMethodSelfOutType\Foo::doFoo2() has PHPDoc tag @phpstan-self-out with generic class MissingMethodSelfOutType\Foo but does not specify its types: T',
30+
22,
31+
],
32+
[
33+
'Method MissingMethodSelfOutType\Foo::doFoo3() has PHPDoc tag @phpstan-self-out with no signature specified for callable.',
34+
30,
35+
],
36+
]);
37+
}
38+
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace MissingMethodSelfOutType;
4+
5+
/**
6+
* @template T
7+
*/
8+
class Foo
9+
{
10+
11+
/**
12+
* @phpstan-self-out self<array>
13+
*/
14+
public function doFoo(): void
15+
{
16+
17+
}
18+
19+
/**
20+
* @phpstan-self-out self
21+
*/
22+
public function doFoo2(): void
23+
{
24+
25+
}
26+
27+
/**
28+
* @phpstan-self-out Foo<int>&callable
29+
*/
30+
public function doFoo3(): void
31+
{
32+
33+
}
34+
35+
}

0 commit comments

Comments
 (0)