From 2485b2e9c129e789ec3b2d7db81ca30f87c63911 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 24 Aug 2024 10:02:48 +0200 Subject: [PATCH] Bleeding edge - check nonexistent classes in LocalTypeAliasesCheck --- conf/config.neon | 1 + src/Rules/Classes/LocalTypeAliasesCheck.php | 27 ++++++++++++++++++- src/Rules/Classes/LocalTypeAliasesRule.php | 2 +- .../Classes/LocalTypeTraitAliasesRule.php | 2 +- .../Classes/LocalTypeAliasesRuleTest.php | 23 ++++++++++++++++ .../Classes/LocalTypeTraitAliasesRuleTest.php | 14 ++++++++++ .../Rules/Classes/data/local-type-aliases.php | 10 +++++++ 7 files changed, 76 insertions(+), 3 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 081dc1a4a6..688c2a4f5c 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -914,6 +914,7 @@ services: arguments: globalTypeAliases: %typeAliases% checkMissingTypehints: %checkMissingTypehints% + checkClassCaseSensitivity: %checkClassCaseSensitivity% absentTypeChecks: %featureToggles.absentTypeChecks% - diff --git a/src/Rules/Classes/LocalTypeAliasesCheck.php b/src/Rules/Classes/LocalTypeAliasesCheck.php index 74c7f5507b..a5606a5b20 100644 --- a/src/Rules/Classes/LocalTypeAliasesCheck.php +++ b/src/Rules/Classes/LocalTypeAliasesCheck.php @@ -2,11 +2,14 @@ namespace PHPStan\Rules\Classes; +use PhpParser\Node\Stmt\ClassLike; use PHPStan\Analyser\NameScope; use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Rules\ClassNameCheck; +use PHPStan\Rules\ClassNameNodePair; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\RuleErrorBuilder; @@ -31,7 +34,9 @@ public function __construct( private ReflectionProvider $reflectionProvider, private TypeNodeResolver $typeNodeResolver, private MissingTypehintCheck $missingTypehintCheck, + private ClassNameCheck $classCheck, private bool $checkMissingTypehints, + private bool $checkClassCaseSensitivity, private bool $absentTypeChecks, ) { @@ -40,7 +45,7 @@ public function __construct( /** * @return list */ - public function check(ClassReflection $reflection): array + public function check(ClassReflection $reflection, ClassLike $node): array { $phpDoc = $reflection->getResolvedPhpDoc(); if ($phpDoc === null) { @@ -214,6 +219,26 @@ public function check(ClassReflection $reflection): array ))->identifier('missingType.callable')->build(); } } + + foreach ($resolvedType->getReferencedClasses() as $class) { + if (!$this->reflectionProvider->hasClass($class)) { + $errors[] = RuleErrorBuilder::message(sprintf('Type alias %s contains unknown class %s.', $aliasName, $class)) + ->identifier('class.notFound') + ->discoveringSymbolsTip() + ->build(); + } elseif ($this->reflectionProvider->getClass($class)->isTrait()) { + $errors[] = RuleErrorBuilder::message(sprintf('Type alias %s contains invalid type %s.', $aliasName, $class)) + ->identifier('typeAlias.trait') + ->build(); + } else { + $errors = array_merge( + $errors, + $this->classCheck->checkClassNames([ + new ClassNameNodePair($class, $node), + ], $this->checkClassCaseSensitivity), + ); + } + } } } diff --git a/src/Rules/Classes/LocalTypeAliasesRule.php b/src/Rules/Classes/LocalTypeAliasesRule.php index 7fb999403c..cfb270cadc 100644 --- a/src/Rules/Classes/LocalTypeAliasesRule.php +++ b/src/Rules/Classes/LocalTypeAliasesRule.php @@ -24,7 +24,7 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - return $this->check->check($node->getClassReflection()); + return $this->check->check($node->getClassReflection(), $node->getOriginalNode()); } } diff --git a/src/Rules/Classes/LocalTypeTraitAliasesRule.php b/src/Rules/Classes/LocalTypeTraitAliasesRule.php index 241cef7c6c..58d72696ad 100644 --- a/src/Rules/Classes/LocalTypeTraitAliasesRule.php +++ b/src/Rules/Classes/LocalTypeTraitAliasesRule.php @@ -33,7 +33,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - return $this->check->check($this->reflectionProvider->getClass($traitName->toString())); + return $this->check->check($this->reflectionProvider->getClass($traitName->toString()), $node); } } diff --git a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php index b3b8689b84..7cdffa7704 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php @@ -3,6 +3,9 @@ namespace PHPStan\Rules\Classes; use PHPStan\PhpDoc\TypeNodeResolver; +use PHPStan\Rules\ClassCaseSensitivityCheck; +use PHPStan\Rules\ClassForbiddenNameCheck; +use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; @@ -16,12 +19,19 @@ class LocalTypeAliasesRuleTest extends RuleTestCase protected function getRule(): Rule { + $reflectionProvider = $this->createReflectionProvider(); + return new LocalTypeAliasesRule( new LocalTypeAliasesCheck( ['GlobalTypeAlias' => 'int|string'], $this->createReflectionProvider(), self::getContainer()->getByType(TypeNodeResolver::class), new MissingTypehintCheck(true, true, true, true, []), + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + true, true, true, ), @@ -108,6 +118,19 @@ public function testRule(): void 'Class LocalTypeAliases\MissingTypehints has type alias NoCallable with no signature specified for callable.', 77, ], + [ + 'Type alias A contains unknown class LocalTypeAliases\Nonexistent.', + 87, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Type alias B contains invalid type LocalTypeTraitAliases\Foo.', + 87, + ], + [ + 'Class LocalTypeAliases\Foo referenced with incorrect case: LocalTypeAliases\fOO.', + 87, + ], ]); } diff --git a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php index ba18681762..7106d65997 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php @@ -3,6 +3,10 @@ namespace PHPStan\Rules\Classes; use PHPStan\PhpDoc\TypeNodeResolver; +use PHPStan\Rules\ClassCaseSensitivityCheck; +use PHPStan\Rules\ClassForbiddenNameCheck; +use PHPStan\Rules\ClassNameCheck; +use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; @@ -14,11 +18,21 @@ class LocalTypeTraitAliasesRuleTest extends RuleTestCase protected function getRule(): Rule { + $reflectionProvider = $this->createReflectionProvider(); + return new LocalTypeTraitAliasesRule( new LocalTypeAliasesCheck( ['GlobalTypeAlias' => 'int|string'], $this->createReflectionProvider(), self::getContainer()->getByType(TypeNodeResolver::class), + new MissingTypehintCheck(true, true, true, true, []), + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + true, + true, + true, ), $this->createReflectionProvider(), ); diff --git a/tests/PHPStan/Rules/Classes/data/local-type-aliases.php b/tests/PHPStan/Rules/Classes/data/local-type-aliases.php index 9dde0cdd29..ba4c47f576 100644 --- a/tests/PHPStan/Rules/Classes/data/local-type-aliases.php +++ b/tests/PHPStan/Rules/Classes/data/local-type-aliases.php @@ -78,3 +78,13 @@ class MissingTypehints { } + +/** + * @phpstan-type A = Nonexistent + * @phpstan-type B = \LocalTypeTraitAliases\Foo + * @phpstan-type C = fOO + */ +class NonexistentClasses +{ + +}