|
5 | 5 | use PhpParser\Node;
|
6 | 6 | use PhpParser\Node\Expr\MethodCall;
|
7 | 7 | use PHPStan\Analyser\Scope;
|
| 8 | +use PHPStan\Rules\IdentifierRuleError; |
8 | 9 | use PHPStan\Rules\Rule;
|
9 | 10 | use PHPStan\Rules\RuleErrorBuilder;
|
10 |
| -use PHPUnit\Framework\MockObject\Builder\InvocationMocker; |
| 11 | +use PHPStan\Type\Type; |
11 | 12 | use PHPUnit\Framework\MockObject\MockObject;
|
12 | 13 | use PHPUnit\Framework\MockObject\Stub;
|
13 | 14 | use function array_filter;
|
@@ -47,44 +48,58 @@ public function processNode(Node $node, Scope $scope): array
|
47 | 48 | $method = $constantString->getValue();
|
48 | 49 | $type = $scope->getType($node->var);
|
49 | 50 |
|
50 |
| -if ( |
51 |
| -( |
52 |
| -in_array(MockObject::class, $type->getObjectClassNames(), true) |
53 |
| -|| in_array(Stub::class, $type->getObjectClassNames(), true) |
54 |
| -) |
55 |
| -&& !$type->hasMethod($method)->yes() |
56 |
| -) { |
57 |
| -$mockClasses = array_filter($type->getObjectClassNames(), static fn (string $class): bool => $class !== MockObject::class && $class !== Stub::class); |
58 |
| -if (count($mockClasses) === 0) { |
59 |
| -continue; |
60 |
| -} |
61 |
| - |
62 |
| -$errors[] = RuleErrorBuilder::message(sprintf( |
63 |
| -'Trying to mock an undefined method %s() on class %s.', |
64 |
| -$method, |
65 |
| -implode('&', $mockClasses), |
66 |
| -))->identifier('phpunit.mockMethod')->build(); |
| 51 | +$error = $this->checkCallOnType($type, $method); |
| 52 | +if ($error !== null) { |
| 53 | +$errors[] = $error; |
67 | 54 | continue;
|
68 | 55 | }
|
69 | 56 |
|
70 |
| -$mockedClassObject = $type->getTemplateType(InvocationMocker::class, 'TMockedClass'); |
71 |
| -if ($mockedClassObject->hasMethod($method)->yes()) { |
| 57 | +if (!$node->var instanceof MethodCall) { |
72 | 58 | continue;
|
73 | 59 | }
|
74 | 60 |
|
75 |
| -$classNames = $mockedClassObject->getObjectClassNames(); |
76 |
| -if (count($classNames) === 0) { |
| 61 | +if (!$node->var->name instanceof Node\Identifier) { |
77 | 62 | continue;
|
78 | 63 | }
|
79 | 64 |
|
80 |
| -$errors[] = RuleErrorBuilder::message(sprintf( |
| 65 | +if ($node->var->name->toLowerString() !== 'expects') { |
| 66 | +continue; |
| 67 | +} |
| 68 | + |
| 69 | +$varType = $scope->getType($node->var->var); |
| 70 | +$error = $this->checkCallOnType($varType, $method); |
| 71 | +if ($error === null) { |
| 72 | +continue; |
| 73 | +} |
| 74 | + |
| 75 | +$errors[] = $error; |
| 76 | +} |
| 77 | + |
| 78 | +return $errors; |
| 79 | +} |
| 80 | + |
| 81 | +private function checkCallOnType(Type $type, string $method): ?IdentifierRuleError |
| 82 | +{ |
| 83 | +if ( |
| 84 | +( |
| 85 | +in_array(MockObject::class, $type->getObjectClassNames(), true) |
| 86 | +|| in_array(Stub::class, $type->getObjectClassNames(), true) |
| 87 | +) |
| 88 | +&& !$type->hasMethod($method)->yes() |
| 89 | +) { |
| 90 | +$mockClasses = array_filter($type->getObjectClassNames(), static fn (string $class): bool => $class !== MockObject::class && $class !== Stub::class); |
| 91 | +if (count($mockClasses) === 0) { |
| 92 | +return null; |
| 93 | +} |
| 94 | + |
| 95 | +return RuleErrorBuilder::message(sprintf( |
81 | 96 | 'Trying to mock an undefined method %s() on class %s.',
|
82 | 97 | $method,
|
83 |
| -implode('|', $classNames), |
| 98 | +implode('&', $mockClasses), |
84 | 99 | ))->identifier('phpunit.mockMethod')->build();
|
85 | 100 | }
|
86 | 101 |
|
87 |
| -return $errors; |
| 102 | +return null; |
88 | 103 | }
|
89 | 104 |
|
90 | 105 | }
|
0 commit comments