From 0d0de946900adf4eb3c799b1b547567536e23147 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 3 Sep 2024 16:18:34 +0200 Subject: [PATCH] Bleeding edge - check `@mixin` PHPDoc tag above traits --- conf/config.level2.neon | 10 ++++ src/PhpDoc/StubValidator.php | 4 ++ src/Rules/Classes/MixinTraitRule.php | 41 +++++++++++++++ src/Rules/Classes/MixinTraitUseRule.php | 34 ++++++++++++ .../Analyser/NodeScopeResolverTest.php | 1 + .../Rules/Classes/MixinTraitRuleTest.php | 52 +++++++++++++++++++ .../Rules/Classes/MixinTraitUseRuleTest.php | 50 ++++++++++++++++++ .../Rules/Classes/data/mixin-trait-use.php | 36 +++++++++++++ .../Rules/Classes/data/mixin-trait.php | 17 ++++++ 9 files changed, 245 insertions(+) create mode 100644 src/Rules/Classes/MixinTraitRule.php create mode 100644 src/Rules/Classes/MixinTraitUseRule.php create mode 100644 tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php create mode 100644 tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php create mode 100644 tests/PHPStan/Rules/Classes/data/mixin-trait-use.php create mode 100644 tests/PHPStan/Rules/Classes/data/mixin-trait.php diff --git a/conf/config.level2.neon b/conf/config.level2.neon index efbc7b7653..1399394032 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -55,6 +55,10 @@ conditionalTags: phpstan.rules.rule: %featureToggles.absentTypeChecks% PHPStan\Rules\Classes\MethodTagTraitUseRule: phpstan.rules.rule: %featureToggles.absentTypeChecks% + PHPStan\Rules\Classes\MixinTraitRule: + phpstan.rules.rule: %featureToggles.absentTypeChecks% + PHPStan\Rules\Classes\MixinTraitUseRule: + phpstan.rules.rule: %featureToggles.absentTypeChecks% PHPStan\Rules\Classes\PropertyTagRule: phpstan.rules.rule: %featureToggles.absentTypeChecks% PHPStan\Rules\Classes\PropertyTagTraitRule: @@ -86,6 +90,12 @@ services: tags: - phpstan.rules.rule + - + class: PHPStan\Rules\Classes\MixinTraitRule + + - + class: PHPStan\Rules\Classes\MixinTraitUseRule + - class: PHPStan\Rules\Classes\MethodTagRule diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index e639533f64..43ecfdd0a5 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -33,6 +33,8 @@ use PHPStan\Rules\Classes\MethodTagTraitUseRule; use PHPStan\Rules\Classes\MixinCheck; use PHPStan\Rules\Classes\MixinRule; +use PHPStan\Rules\Classes\MixinTraitRule; +use PHPStan\Rules\Classes\MixinTraitUseRule; use PHPStan\Rules\Classes\PropertyTagCheck; use PHPStan\Rules\Classes\PropertyTagRule; use PHPStan\Rules\Classes\PropertyTagTraitRule; @@ -259,6 +261,8 @@ private function getRuleRegistry(Container $container): RuleRegistry $rules[] = new PropertyTagTraitRule($propertyTagCheck, $reflectionProvider); $rules[] = new PropertyTagTraitUseRule($propertyTagCheck); $rules[] = new MixinRule($mixinCheck); + $rules[] = new MixinTraitRule($mixinCheck, $reflectionProvider); + $rules[] = new MixinTraitUseRule($mixinCheck); $rules[] = new LocalTypeTraitUseAliasesRule($localTypeAliasesCheck); $rules[] = new MethodTagTemplateTypeTraitRule($methodTagTemplateTypeCheck, $reflectionProvider); } diff --git a/src/Rules/Classes/MixinTraitRule.php b/src/Rules/Classes/MixinTraitRule.php new file mode 100644 index 0000000000..5cb7c1ecd9 --- /dev/null +++ b/src/Rules/Classes/MixinTraitRule.php @@ -0,0 +1,41 @@ + + */ +final class MixinTraitRule implements Rule +{ + + public function __construct(private MixinCheck $check, private ReflectionProvider $reflectionProvider) + { + } + + public function getNodeType(): string + { + return Node\Stmt\Trait_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $traitName = $node->namespacedName; + if ($traitName === null) { + return []; + } + + if (!$this->reflectionProvider->hasClass($traitName->toString())) { + return []; + } + + return $this->check->checkInTraitDefinitionContext( + $this->reflectionProvider->getClass($traitName->toString()), + ); + } + +} diff --git a/src/Rules/Classes/MixinTraitUseRule.php b/src/Rules/Classes/MixinTraitUseRule.php new file mode 100644 index 0000000000..33a3e80780 --- /dev/null +++ b/src/Rules/Classes/MixinTraitUseRule.php @@ -0,0 +1,34 @@ + + */ +final class MixinTraitUseRule implements Rule +{ + + public function __construct(private MixinCheck $check) + { + } + + public function getNodeType(): string + { + return InTraitNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return $this->check->checkInTraitUseContext( + $node->getTraitReflection(), + $node->getImplementingClassReflection(), + $node->getOriginalNode(), + ); + } + +} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index a5aac09b6e..f3e8929e16 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -184,6 +184,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Classes/data/bug-11591.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Classes/data/bug-11591-method-tag.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Classes/data/bug-11591-property-tag.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Classes/data/mixin-trait-use.php'); } /** diff --git a/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php b/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php new file mode 100644 index 0000000000..f23e120458 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php @@ -0,0 +1,52 @@ + + */ +class MixinTraitRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + $reflectionProvider = $this->createReflectionProvider(); + + return new MixinTraitRule( + new MixinCheck( + $reflectionProvider, + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + new GenericObjectTypeCheck(), + new MissingTypehintCheck(true, true, true, true, []), + new UnresolvableTypeHelper(), + true, + true, + ), + $reflectionProvider, + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/mixin-trait.php'], [ + [ + 'Trait MixinTrait\FooTrait has PHPDoc tag @mixin with no value type specified in iterable type array.', + 14, + MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php new file mode 100644 index 0000000000..dbc7906da5 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php @@ -0,0 +1,50 @@ + + */ +class MixinTraitUseRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + $reflectionProvider = $this->createReflectionProvider(); + + return new MixinTraitUseRule( + new MixinCheck( + $reflectionProvider, + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + new GenericObjectTypeCheck(), + new MissingTypehintCheck(true, true, true, true, []), + new UnresolvableTypeHelper(), + true, + true, + ), + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/mixin-trait-use.php'], [ + [ + 'PHPDoc tag @mixin contains unresolvable type.', + 22, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Classes/data/mixin-trait-use.php b/tests/PHPStan/Rules/Classes/data/mixin-trait-use.php new file mode 100644 index 0000000000..0b67bfb4fb --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/mixin-trait-use.php @@ -0,0 +1,36 @@ + + * @mixin string&int + */ +trait FooTrait +{ + +} + +class Usages +{ + + use FooTrait; + +} + +function (Usages $u): void { + assertType(Usages::class, $u->get()); +}; diff --git a/tests/PHPStan/Rules/Classes/data/mixin-trait.php b/tests/PHPStan/Rules/Classes/data/mixin-trait.php new file mode 100644 index 0000000000..83c0f0b488 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/mixin-trait.php @@ -0,0 +1,17 @@ + + */ +trait FooTrait +{ + +}