Skip to content

Commit 57ccd8c

Browse files
committed
Refactoring - extract MixinCheck
1 parent c47730f commit 57ccd8c

File tree

6 files changed

+150
-120
lines changed

6 files changed

+150
-120
lines changed

conf/config.level2.neon

-3
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,6 @@ conditionalTags:
8383
services:
8484
-
8585
class: PHPStan\Rules\Classes\MixinRule
86-
arguments:
87-
checkClassCaseSensitivity: %checkClassCaseSensitivity%
88-
absentTypeChecks: %featureToggles.absentTypeChecks%
8986
tags:
9087
- phpstan.rules.rule
9188

conf/config.neon

+6
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,12 @@ services:
923923
arguments:
924924
checkClassCaseSensitivity: %checkClassCaseSensitivity%
925925

926+
-
927+
class: PHPStan\Rules\Classes\MixinCheck
928+
arguments:
929+
checkClassCaseSensitivity: %checkClassCaseSensitivity%
930+
absentTypeChecks: %featureToggles.absentTypeChecks%
931+
926932
-
927933
class: PHPStan\Rules\Classes\PropertyTagCheck
928934
arguments:

src/PhpDoc/StubValidator.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use PHPStan\Rules\Classes\MethodTagRule;
3232
use PHPStan\Rules\Classes\MethodTagTraitRule;
3333
use PHPStan\Rules\Classes\MethodTagTraitUseRule;
34+
use PHPStan\Rules\Classes\MixinCheck;
3435
use PHPStan\Rules\Classes\MixinRule;
3536
use PHPStan\Rules\Classes\PropertyTagCheck;
3637
use PHPStan\Rules\Classes\PropertyTagRule;
@@ -182,6 +183,7 @@ private function getRuleRegistry(Container $container): RuleRegistry
182183
$phpClassReflectionExtension = $container->getByType(PhpClassReflectionExtension::class);
183184
$genericCallableRuleHelper = $container->getByType(GenericCallableRuleHelper::class);
184185
$methodTagTemplateTypeCheck = $container->getByType(MethodTagTemplateTypeCheck::class);
186+
$mixinCheck = $container->getByType(MixinCheck::class);
185187

186188
$rules = [
187189
// level 0
@@ -256,7 +258,7 @@ private function getRuleRegistry(Container $container): RuleRegistry
256258
$rules[] = new PropertyTagRule($propertyTagCheck);
257259
$rules[] = new PropertyTagTraitRule($propertyTagCheck, $reflectionProvider);
258260
$rules[] = new PropertyTagTraitUseRule($propertyTagCheck);
259-
$rules[] = new MixinRule($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true, true);
261+
$rules[] = new MixinRule($mixinCheck);
260262
$rules[] = new LocalTypeTraitUseAliasesRule($localTypeAliasesCheck);
261263
$rules[] = new MethodTagTemplateTypeTraitRule($methodTagTemplateTypeCheck, $reflectionProvider);
262264
}

src/Rules/Classes/MixinCheck.php

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Classes;
4+
5+
use PhpParser\Node\Stmt\ClassLike;
6+
use PHPStan\Reflection\ClassReflection;
7+
use PHPStan\Reflection\ReflectionProvider;
8+
use PHPStan\Rules\ClassNameCheck;
9+
use PHPStan\Rules\ClassNameNodePair;
10+
use PHPStan\Rules\Generics\GenericObjectTypeCheck;
11+
use PHPStan\Rules\IdentifierRuleError;
12+
use PHPStan\Rules\MissingTypehintCheck;
13+
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
14+
use PHPStan\Rules\RuleErrorBuilder;
15+
use PHPStan\Type\VerbosityLevel;
16+
use function array_merge;
17+
use function implode;
18+
use function sprintf;
19+
20+
final class MixinCheck
21+
{
22+
23+
public function __construct(
24+
private ReflectionProvider $reflectionProvider,
25+
private ClassNameCheck $classCheck,
26+
private GenericObjectTypeCheck $genericObjectTypeCheck,
27+
private MissingTypehintCheck $missingTypehintCheck,
28+
private UnresolvableTypeHelper $unresolvableTypeHelper,
29+
private bool $checkClassCaseSensitivity,
30+
private bool $absentTypeChecks,
31+
)
32+
{
33+
}
34+
35+
/**
36+
* @return list<IdentifierRuleError>
37+
*/
38+
public function check(ClassReflection $classReflection, ClassLike $node): array
39+
{
40+
$mixinTags = $classReflection->getMixinTags();
41+
$errors = [];
42+
foreach ($mixinTags as $mixinTag) {
43+
$type = $mixinTag->getType();
44+
if (!$type->canCallMethods()->yes() || !$type->canAccessProperties()->yes()) {
45+
$errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains non-object type %s.', $type->describe(VerbosityLevel::typeOnly())))
46+
->identifier('mixin.nonObject')
47+
->build();
48+
continue;
49+
}
50+
51+
if (
52+
$this->unresolvableTypeHelper->containsUnresolvableType($type)
53+
) {
54+
$errors[] = RuleErrorBuilder::message('PHPDoc tag @mixin contains unresolvable type.')
55+
->identifier('mixin.unresolvableType')
56+
->build();
57+
continue;
58+
}
59+
60+
$errors = array_merge($errors, $this->genericObjectTypeCheck->check(
61+
$type,
62+
'PHPDoc tag @mixin contains generic type %s but %s %s is not generic.',
63+
'Generic type %s in PHPDoc tag @mixin does not specify all template types of %s %s: %s',
64+
'Generic type %s in PHPDoc tag @mixin specifies %d template types, but %s %s supports only %d: %s',
65+
'Type %s in generic type %s in PHPDoc tag @mixin is not subtype of template type %s of %s %s.',
66+
'Call-site variance of %s in generic type %s in PHPDoc tag @mixin is in conflict with %s template type %s of %s %s.',
67+
'Call-site variance of %s in generic type %s in PHPDoc tag @mixin is redundant, template type %s of %s %s has the same variance.',
68+
));
69+
70+
foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($type) as [$innerName, $genericTypeNames]) {
71+
$errors[] = RuleErrorBuilder::message(sprintf(
72+
'PHPDoc tag @mixin contains generic %s but does not specify its types: %s',
73+
$innerName,
74+
implode(', ', $genericTypeNames),
75+
))
76+
->identifier('missingType.generics')
77+
->build();
78+
}
79+
80+
if ($this->absentTypeChecks) {
81+
foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($type) as $iterableType) {
82+
$iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly());
83+
$errors[] = RuleErrorBuilder::message(sprintf(
84+
'%s %s has PHPDoc tag @mixin with no value type specified in iterable type %s.',
85+
$classReflection->getClassTypeDescription(),
86+
$classReflection->getDisplayName(),
87+
$iterableTypeDescription,
88+
))
89+
->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP)
90+
->identifier('missingType.iterableValue')
91+
->build();
92+
}
93+
94+
foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($type) as $callableType) {
95+
$errors[] = RuleErrorBuilder::message(sprintf(
96+
'%s %s has PHPDoc tag @mixin with no signature specified for %s.',
97+
$classReflection->getClassTypeDescription(),
98+
$classReflection->getDisplayName(),
99+
$callableType->describe(VerbosityLevel::typeOnly()),
100+
))->identifier('missingType.callable')->build();
101+
}
102+
}
103+
104+
foreach ($type->getReferencedClasses() as $class) {
105+
if (!$this->reflectionProvider->hasClass($class)) {
106+
$errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains unknown class %s.', $class))
107+
->identifier('class.notFound')
108+
->discoveringSymbolsTip()
109+
->build();
110+
} elseif ($this->reflectionProvider->getClass($class)->isTrait()) {
111+
$errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains invalid type %s.', $class))
112+
->identifier('mixin.trait')
113+
->build();
114+
} else {
115+
$errors = array_merge(
116+
$errors,
117+
$this->classCheck->checkClassNames([
118+
new ClassNameNodePair($class, $node),
119+
], $this->checkClassCaseSensitivity),
120+
);
121+
}
122+
}
123+
}
124+
125+
return $errors;
126+
}
127+
128+
}

src/Rules/Classes/MixinRule.php

+2-107
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,15 @@
55
use PhpParser\Node;
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Node\InClassNode;
8-
use PHPStan\Reflection\ReflectionProvider;
9-
use PHPStan\Rules\ClassNameCheck;
10-
use PHPStan\Rules\ClassNameNodePair;
11-
use PHPStan\Rules\Generics\GenericObjectTypeCheck;
12-
use PHPStan\Rules\MissingTypehintCheck;
13-
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
148
use PHPStan\Rules\Rule;
15-
use PHPStan\Rules\RuleErrorBuilder;
16-
use PHPStan\Type\VerbosityLevel;
17-
use function array_merge;
18-
use function implode;
19-
use function sprintf;
209

2110
/**
2211
* @implements Rule<InClassNode>
2312
*/
2413
final class MixinRule implements Rule
2514
{
2615

27-
public function __construct(
28-
private ReflectionProvider $reflectionProvider,
29-
private ClassNameCheck $classCheck,
30-
private GenericObjectTypeCheck $genericObjectTypeCheck,
31-
private MissingTypehintCheck $missingTypehintCheck,
32-
private UnresolvableTypeHelper $unresolvableTypeHelper,
33-
private bool $checkClassCaseSensitivity,
34-
private bool $absentTypeChecks,
35-
)
16+
public function __construct(private MixinCheck $check)
3617
{
3718
}
3819

@@ -43,93 +24,7 @@ public function getNodeType(): string
4324

4425
public function processNode(Node $node, Scope $scope): array
4526
{
46-
$classReflection = $node->getClassReflection();
47-
$mixinTags = $classReflection->getMixinTags();
48-
$errors = [];
49-
foreach ($mixinTags as $mixinTag) {
50-
$type = $mixinTag->getType();
51-
if (!$type->canCallMethods()->yes() || !$type->canAccessProperties()->yes()) {
52-
$errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains non-object type %s.', $type->describe(VerbosityLevel::typeOnly())))
53-
->identifier('mixin.nonObject')
54-
->build();
55-
continue;
56-
}
57-
58-
if (
59-
$this->unresolvableTypeHelper->containsUnresolvableType($type)
60-
) {
61-
$errors[] = RuleErrorBuilder::message('PHPDoc tag @mixin contains unresolvable type.')
62-
->identifier('mixin.unresolvableType')
63-
->build();
64-
continue;
65-
}
66-
67-
$errors = array_merge($errors, $this->genericObjectTypeCheck->check(
68-
$type,
69-
'PHPDoc tag @mixin contains generic type %s but %s %s is not generic.',
70-
'Generic type %s in PHPDoc tag @mixin does not specify all template types of %s %s: %s',
71-
'Generic type %s in PHPDoc tag @mixin specifies %d template types, but %s %s supports only %d: %s',
72-
'Type %s in generic type %s in PHPDoc tag @mixin is not subtype of template type %s of %s %s.',
73-
'Call-site variance of %s in generic type %s in PHPDoc tag @mixin is in conflict with %s template type %s of %s %s.',
74-
'Call-site variance of %s in generic type %s in PHPDoc tag @mixin is redundant, template type %s of %s %s has the same variance.',
75-
));
76-
77-
foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($type) as [$innerName, $genericTypeNames]) {
78-
$errors[] = RuleErrorBuilder::message(sprintf(
79-
'PHPDoc tag @mixin contains generic %s but does not specify its types: %s',
80-
$innerName,
81-
implode(', ', $genericTypeNames),
82-
))
83-
->identifier('missingType.generics')
84-
->build();
85-
}
86-
87-
if ($this->absentTypeChecks) {
88-
foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($type) as $iterableType) {
89-
$iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly());
90-
$errors[] = RuleErrorBuilder::message(sprintf(
91-
'%s %s has PHPDoc tag @mixin with no value type specified in iterable type %s.',
92-
$classReflection->getClassTypeDescription(),
93-
$classReflection->getDisplayName(),
94-
$iterableTypeDescription,
95-
))
96-
->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP)
97-
->identifier('missingType.iterableValue')
98-
->build();
99-
}
100-
101-
foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($type) as $callableType) {
102-
$errors[] = RuleErrorBuilder::message(sprintf(
103-
'%s %s has PHPDoc tag @mixin with no signature specified for %s.',
104-
$classReflection->getClassTypeDescription(),
105-
$classReflection->getDisplayName(),
106-
$callableType->describe(VerbosityLevel::typeOnly()),
107-
))->identifier('missingType.callable')->build();
108-
}
109-
}
110-
111-
foreach ($type->getReferencedClasses() as $class) {
112-
if (!$this->reflectionProvider->hasClass($class)) {
113-
$errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains unknown class %s.', $class))
114-
->identifier('class.notFound')
115-
->discoveringSymbolsTip()
116-
->build();
117-
} elseif ($this->reflectionProvider->getClass($class)->isTrait()) {
118-
$errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains invalid type %s.', $class))
119-
->identifier('mixin.trait')
120-
->build();
121-
} else {
122-
$errors = array_merge(
123-
$errors,
124-
$this->classCheck->checkClassNames([
125-
new ClassNameNodePair($class, $node),
126-
], $this->checkClassCaseSensitivity),
127-
);
128-
}
129-
}
130-
}
131-
132-
return $errors;
27+
return $this->check->check($node->getClassReflection(), $node->getOriginalNode());
13328
}
13429

13530
}

tests/PHPStan/Rules/Classes/MixinRuleTest.php

+11-9
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,18 @@ protected function getRule(): Rule
2323
$reflectionProvider = $this->createReflectionProvider();
2424

2525
return new MixinRule(
26-
$reflectionProvider,
27-
new ClassNameCheck(
28-
new ClassCaseSensitivityCheck($reflectionProvider, true),
29-
new ClassForbiddenNameCheck(self::getContainer()),
26+
new MixinCheck(
27+
$reflectionProvider,
28+
new ClassNameCheck(
29+
new ClassCaseSensitivityCheck($reflectionProvider, true),
30+
new ClassForbiddenNameCheck(self::getContainer()),
31+
),
32+
new GenericObjectTypeCheck(),
33+
new MissingTypehintCheck(true, true, true, true, []),
34+
new UnresolvableTypeHelper(),
35+
true,
36+
true,
3037
),
31-
new GenericObjectTypeCheck(),
32-
new MissingTypehintCheck(true, true, true, true, []),
33-
new UnresolvableTypeHelper(),
34-
true,
35-
true,
3638
);
3739
}
3840

0 commit comments

Comments
 (0)