Skip to content

Commit 92aaf3f

Browse files
committed
MissingClassConstantTypehintRule
1 parent 5be8e54 commit 92aaf3f

File tree

4 files changed

+157
-0
lines changed

4 files changed

+157
-0
lines changed

conf/config.level6.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ parameters:
88
checkMissingTypehints: true
99

1010
rules:
11+
- PHPStan\Rules\Constants\MissingClassConstantTypehintRule
1112
- PHPStan\Rules\Functions\MissingFunctionParameterTypehintRule
1213
- PHPStan\Rules\Functions\MissingFunctionReturnTypehintRule
1314
- PHPStan\Rules\Methods\MissingMethodParameterTypehintRule
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Constants;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Reflection\ClassReflection;
8+
use PHPStan\Rules\MissingTypehintCheck;
9+
use PHPStan\Rules\RuleError;
10+
use PHPStan\Rules\RuleErrorBuilder;
11+
use PHPStan\Type\VerbosityLevel;
12+
13+
/**
14+
* @implements \PHPStan\Rules\Rule<Node\Stmt\ClassConst>
15+
*/
16+
final class MissingClassConstantTypehintRule implements \PHPStan\Rules\Rule
17+
{
18+
19+
private \PHPStan\Rules\MissingTypehintCheck $missingTypehintCheck;
20+
21+
public function __construct(MissingTypehintCheck $missingTypehintCheck)
22+
{
23+
$this->missingTypehintCheck = $missingTypehintCheck;
24+
}
25+
26+
public function getNodeType(): string
27+
{
28+
return Node\Stmt\ClassConst::class;
29+
}
30+
31+
public function processNode(Node $node, Scope $scope): array
32+
{
33+
if (!$scope->isInClass()) {
34+
throw new \PHPStan\ShouldNotHappenException();
35+
}
36+
37+
$errors = [];
38+
foreach ($node->consts as $const) {
39+
$constantName = $const->name->toString();
40+
$errors = array_merge($errors, $this->processSingleConstant($scope->getClassReflection(), $constantName));
41+
}
42+
43+
return $errors;
44+
}
45+
46+
/**
47+
* @param string $constantName
48+
* @return RuleError[]
49+
*/
50+
private function processSingleConstant(ClassReflection $classReflection, string $constantName): array
51+
{
52+
$constantReflection = $classReflection->getConstant($constantName);
53+
$constantType = $constantReflection->getValueType();
54+
55+
$errors = [];
56+
foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($constantType) as $iterableType) {
57+
$iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly());
58+
$errors[] = RuleErrorBuilder::message(sprintf(
59+
'Constant %s::%s type has no value type specified in iterable type %s.',
60+
$constantReflection->getDeclaringClass()->getDisplayName(),
61+
$constantName,
62+
$iterableTypeDescription
63+
))->tip(MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP)->build();
64+
}
65+
66+
foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($constantType) as [$name, $genericTypeNames]) {
67+
$errors[] = RuleErrorBuilder::message(sprintf(
68+
'Constant %s::%s with generic %s does not specify its types: %s',
69+
$constantReflection->getDeclaringClass()->getDisplayName(),
70+
$constantName,
71+
$name,
72+
implode(', ', $genericTypeNames)
73+
))->tip(MissingTypehintCheck::TURN_OFF_NON_GENERIC_CHECK_TIP)->build();
74+
}
75+
76+
foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($constantType) as $callableType) {
77+
$errors[] = RuleErrorBuilder::message(sprintf(
78+
'Constant %s::%s type has no signature specified for %s.',
79+
$constantReflection->getDeclaringClass()->getDisplayName(),
80+
$constantName,
81+
$callableType->describe(VerbosityLevel::typeOnly())
82+
))->build();
83+
}
84+
85+
return $errors;
86+
}
87+
88+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Constants;
4+
5+
use PHPStan\Rules\MissingTypehintCheck;
6+
use PHPStan\Testing\RuleTestCase;
7+
8+
/**
9+
* @extends RuleTestCase<MissingClassConstantTypehintRule>
10+
*/
11+
class MissingClassConstantTypehintRuleTest extends RuleTestCase
12+
{
13+
14+
protected function getRule(): \PHPStan\Rules\Rule
15+
{
16+
$reflectionProvider = $this->createReflectionProvider();
17+
return new MissingClassConstantTypehintRule(new MissingTypehintCheck($reflectionProvider, true, true, true));
18+
}
19+
20+
public function testRule(): void
21+
{
22+
$this->analyse([__DIR__ . '/data/missing-class-constant-typehint.php'], [
23+
[
24+
'Constant MissingClassConstantTypehint\Foo::BAR type has no value type specified in iterable type array.',
25+
11,
26+
'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type',
27+
],
28+
[
29+
'Constant MissingClassConstantTypehint\Foo::BAZ with generic class MissingClassConstantTypehint\Bar does not specify its types: T',
30+
17,
31+
'You can turn this off by setting <fg=cyan>checkGenericClassInNonGenericObjectType: false</> in your <fg=cyan>%configurationFile%</>.',
32+
],
33+
[
34+
'Constant MissingClassConstantTypehint\Foo::LOREM type has no signature specified for callable.',
35+
20,
36+
],
37+
]);
38+
}
39+
40+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace MissingClassConstantTypehint;
4+
5+
class Foo
6+
{
7+
8+
private const FOO = '1';
9+
10+
/** @var array */
11+
private const BAR = [];
12+
13+
/** @var mixed[] */
14+
private const BARR = [];
15+
16+
/** @var Bar */
17+
private const BAZ = 1;
18+
19+
/** @var callable */
20+
private const LOREM = 1;
21+
22+
}
23+
24+
/** @template T */
25+
class Bar
26+
{
27+
28+
}

0 commit comments

Comments
 (0)