Skip to content

Commit 2485b2e

Browse files
committed
Bleeding edge - check nonexistent classes in LocalTypeAliasesCheck
1 parent ce7ffaf commit 2485b2e

7 files changed

+76
-3
lines changed

conf/config.neon

+1
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,7 @@ services:
914914
arguments:
915915
globalTypeAliases: %typeAliases%
916916
checkMissingTypehints: %checkMissingTypehints%
917+
checkClassCaseSensitivity: %checkClassCaseSensitivity%
917918
absentTypeChecks: %featureToggles.absentTypeChecks%
918919

919920
-

src/Rules/Classes/LocalTypeAliasesCheck.php

+26-1
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22

33
namespace PHPStan\Rules\Classes;
44

5+
use PhpParser\Node\Stmt\ClassLike;
56
use PHPStan\Analyser\NameScope;
67
use PHPStan\PhpDoc\TypeNodeResolver;
78
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
89
use PHPStan\Reflection\ClassReflection;
910
use PHPStan\Reflection\ReflectionProvider;
11+
use PHPStan\Rules\ClassNameCheck;
12+
use PHPStan\Rules\ClassNameNodePair;
1013
use PHPStan\Rules\IdentifierRuleError;
1114
use PHPStan\Rules\MissingTypehintCheck;
1215
use PHPStan\Rules\RuleErrorBuilder;
@@ -31,7 +34,9 @@ public function __construct(
3134
private ReflectionProvider $reflectionProvider,
3235
private TypeNodeResolver $typeNodeResolver,
3336
private MissingTypehintCheck $missingTypehintCheck,
37+
private ClassNameCheck $classCheck,
3438
private bool $checkMissingTypehints,
39+
private bool $checkClassCaseSensitivity,
3540
private bool $absentTypeChecks,
3641
)
3742
{
@@ -40,7 +45,7 @@ public function __construct(
4045
/**
4146
* @return list<IdentifierRuleError>
4247
*/
43-
public function check(ClassReflection $reflection): array
48+
public function check(ClassReflection $reflection, ClassLike $node): array
4449
{
4550
$phpDoc = $reflection->getResolvedPhpDoc();
4651
if ($phpDoc === null) {
@@ -214,6 +219,26 @@ public function check(ClassReflection $reflection): array
214219
))->identifier('missingType.callable')->build();
215220
}
216221
}
222+
223+
foreach ($resolvedType->getReferencedClasses() as $class) {
224+
if (!$this->reflectionProvider->hasClass($class)) {
225+
$errors[] = RuleErrorBuilder::message(sprintf('Type alias %s contains unknown class %s.', $aliasName, $class))
226+
->identifier('class.notFound')
227+
->discoveringSymbolsTip()
228+
->build();
229+
} elseif ($this->reflectionProvider->getClass($class)->isTrait()) {
230+
$errors[] = RuleErrorBuilder::message(sprintf('Type alias %s contains invalid type %s.', $aliasName, $class))
231+
->identifier('typeAlias.trait')
232+
->build();
233+
} else {
234+
$errors = array_merge(
235+
$errors,
236+
$this->classCheck->checkClassNames([
237+
new ClassNameNodePair($class, $node),
238+
], $this->checkClassCaseSensitivity),
239+
);
240+
}
241+
}
217242
}
218243
}
219244

src/Rules/Classes/LocalTypeAliasesRule.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public function getNodeType(): string
2424

2525
public function processNode(Node $node, Scope $scope): array
2626
{
27-
return $this->check->check($node->getClassReflection());
27+
return $this->check->check($node->getClassReflection(), $node->getOriginalNode());
2828
}
2929

3030
}

src/Rules/Classes/LocalTypeTraitAliasesRule.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public function processNode(Node $node, Scope $scope): array
3333
return [];
3434
}
3535

36-
return $this->check->check($this->reflectionProvider->getClass($traitName->toString()));
36+
return $this->check->check($this->reflectionProvider->getClass($traitName->toString()), $node);
3737
}
3838

3939
}

tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php

+23
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
namespace PHPStan\Rules\Classes;
44

55
use PHPStan\PhpDoc\TypeNodeResolver;
6+
use PHPStan\Rules\ClassCaseSensitivityCheck;
7+
use PHPStan\Rules\ClassForbiddenNameCheck;
8+
use PHPStan\Rules\ClassNameCheck;
69
use PHPStan\Rules\MissingTypehintCheck;
710
use PHPStan\Rules\Rule;
811
use PHPStan\Testing\RuleTestCase;
@@ -16,12 +19,19 @@ class LocalTypeAliasesRuleTest extends RuleTestCase
1619

1720
protected function getRule(): Rule
1821
{
22+
$reflectionProvider = $this->createReflectionProvider();
23+
1924
return new LocalTypeAliasesRule(
2025
new LocalTypeAliasesCheck(
2126
['GlobalTypeAlias' => 'int|string'],
2227
$this->createReflectionProvider(),
2328
self::getContainer()->getByType(TypeNodeResolver::class),
2429
new MissingTypehintCheck(true, true, true, true, []),
30+
new ClassNameCheck(
31+
new ClassCaseSensitivityCheck($reflectionProvider, true),
32+
new ClassForbiddenNameCheck(self::getContainer()),
33+
),
34+
true,
2535
true,
2636
true,
2737
),
@@ -108,6 +118,19 @@ public function testRule(): void
108118
'Class LocalTypeAliases\MissingTypehints has type alias NoCallable with no signature specified for callable.',
109119
77,
110120
],
121+
[
122+
'Type alias A contains unknown class LocalTypeAliases\Nonexistent.',
123+
87,
124+
'Learn more at https://phpstan.org/user-guide/discovering-symbols',
125+
],
126+
[
127+
'Type alias B contains invalid type LocalTypeTraitAliases\Foo.',
128+
87,
129+
],
130+
[
131+
'Class LocalTypeAliases\Foo referenced with incorrect case: LocalTypeAliases\fOO.',
132+
87,
133+
],
111134
]);
112135
}
113136

tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php

+14
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
namespace PHPStan\Rules\Classes;
44

55
use PHPStan\PhpDoc\TypeNodeResolver;
6+
use PHPStan\Rules\ClassCaseSensitivityCheck;
7+
use PHPStan\Rules\ClassForbiddenNameCheck;
8+
use PHPStan\Rules\ClassNameCheck;
9+
use PHPStan\Rules\MissingTypehintCheck;
610
use PHPStan\Rules\Rule;
711
use PHPStan\Testing\RuleTestCase;
812

@@ -14,11 +18,21 @@ class LocalTypeTraitAliasesRuleTest extends RuleTestCase
1418

1519
protected function getRule(): Rule
1620
{
21+
$reflectionProvider = $this->createReflectionProvider();
22+
1723
return new LocalTypeTraitAliasesRule(
1824
new LocalTypeAliasesCheck(
1925
['GlobalTypeAlias' => 'int|string'],
2026
$this->createReflectionProvider(),
2127
self::getContainer()->getByType(TypeNodeResolver::class),
28+
new MissingTypehintCheck(true, true, true, true, []),
29+
new ClassNameCheck(
30+
new ClassCaseSensitivityCheck($reflectionProvider, true),
31+
new ClassForbiddenNameCheck(self::getContainer()),
32+
),
33+
true,
34+
true,
35+
true,
2236
),
2337
$this->createReflectionProvider(),
2438
);

tests/PHPStan/Rules/Classes/data/local-type-aliases.php

+10
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,13 @@ class MissingTypehints
7878
{
7979

8080
}
81+
82+
/**
83+
* @phpstan-type A = Nonexistent
84+
* @phpstan-type B = \LocalTypeTraitAliases\Foo
85+
* @phpstan-type C = fOO
86+
*/
87+
class NonexistentClasses
88+
{
89+
90+
}

0 commit comments

Comments
 (0)