Skip to content

Commit

Permalink
Bleeding edge - check @mixin PHPDoc tag above traits
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Sep 3, 2024
1 parent ba59142 commit 0d0de94
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 0 deletions.
10 changes: 10 additions & 0 deletions conf/config.level2.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -86,6 +90,12 @@ services:
tags:
- phpstan.rules.rule

-
class: PHPStan\Rules\Classes\MixinTraitRule

-
class: PHPStan\Rules\Classes\MixinTraitUseRule

-
class: PHPStan\Rules\Classes\MethodTagRule

Expand Down
4 changes: 4 additions & 0 deletions src/PhpDoc/StubValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down
41 changes: 41 additions & 0 deletions src/Rules/Classes/MixinTraitRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Classes;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;

/**
* @implements Rule<Node\Stmt\Trait_>
*/
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()),
);
}

}
34 changes: 34 additions & 0 deletions src/Rules/Classes/MixinTraitUseRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Classes;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Node\InTraitNode;
use PHPStan\Rules\Rule;

/**
* @implements Rule<InTraitNode>
*/
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(),
);
}

}
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}

/**
Expand Down
52 changes: 52 additions & 0 deletions tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Classes;

use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\ClassForbiddenNameCheck;
use PHPStan\Rules\ClassNameCheck;
use PHPStan\Rules\Generics\GenericObjectTypeCheck;
use PHPStan\Rules\MissingTypehintCheck;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<MixinTraitRule>
*/
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,
],
]);
}

}
50 changes: 50 additions & 0 deletions tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Classes;

use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\ClassForbiddenNameCheck;
use PHPStan\Rules\ClassNameCheck;
use PHPStan\Rules\Generics\GenericObjectTypeCheck;
use PHPStan\Rules\MissingTypehintCheck;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<MixinTraitUseRule>
*/
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,
],
]);
}

}
36 changes: 36 additions & 0 deletions tests/PHPStan/Rules/Classes/data/mixin-trait-use.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace MixinTraitUse;

use function PHPStan\Testing\assertType;

/**
* @template T
*/
interface Foo
{

/** @return T */
public function get();

}

/**
* @mixin Foo<static>
* @mixin string&int
*/
trait FooTrait
{

}

class Usages
{

use FooTrait;

}

function (Usages $u): void {
assertType(Usages::class, $u->get());
};
17 changes: 17 additions & 0 deletions tests/PHPStan/Rules/Classes/data/mixin-trait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace MixinTrait;

/** @template T */
class Foo
{

}

/**
* @mixin Foo<array>
*/
trait FooTrait
{

}

0 comments on commit 0d0de94

Please sign in to comment.