|
8 | 8 | use PHPStan\Analyser\Scope; |
9 | 9 | use PHPStan\Rules\Rule; |
10 | 10 | use PHPStan\Rules\RuleErrorBuilder; |
| 11 | +use PHPStan\TrinaryLogic; |
| 12 | +use PHPStan\Type\Constant\ConstantIntegerType; |
11 | 13 | use PHPStan\Type\ObjectType; |
| 14 | +use PHPStan\Type\Type; |
12 | 15 | use function count; |
| 16 | +use const COUNT_NORMAL; |
13 | 17 |
|
14 | 18 | /** |
15 | 19 | * @implements Rule<CallLike> |
@@ -42,36 +46,67 @@ public function processNode(Node $node, Scope $scope): array |
42 | 46 | } |
43 | 47 |
|
44 | 48 | $right = $node->getArgs()[1]->value; |
45 | | - if ( |
46 | | - $right instanceof Node\Expr\FuncCall |
47 | | - && $right->name instanceof Node\Name |
48 | | - && $right->name->toLowerString() === 'count' |
49 | | - ) { |
| 49 | + if (self::isCountFunctionCall($right, $scope)) { |
50 | 50 | return [ |
51 | 51 | RuleErrorBuilder::message('You should use assertCount($expectedCount, $variable) instead of assertSame($expectedCount, count($variable)).') |
52 | 52 | ->identifier('phpunit.assertCount') |
53 | 53 | ->build(), |
54 | 54 | ]; |
55 | 55 | } |
56 | 56 |
|
| 57 | + if (self::isCountableMethodCall($right, $scope)) { |
| 58 | + return [ |
| 59 | + RuleErrorBuilder::message('You should use assertCount($expectedCount, $variable) instead of assertSame($expectedCount, $variable->count()).') |
| 60 | + ->identifier('phpunit.assertCount') |
| 61 | + ->build(), |
| 62 | + ]; |
| 63 | + } |
| 64 | + |
| 65 | + return []; |
| 66 | + } |
| 67 | + |
| 68 | + /** |
| 69 | + * @phpstan-assert-if-true Node\Expr\FuncCall $expr |
| 70 | + */ |
| 71 | + private static function isCountFunctionCall(Node\Expr $expr, Scope $scope): bool |
| 72 | + { |
| 73 | + return $expr instanceof Node\Expr\FuncCall |
| 74 | + && $expr->name instanceof Node\Name |
| 75 | + && $expr->name->toLowerString() === 'count' |
| 76 | + && count($expr->getArgs()) >= 1 |
| 77 | + && self::isNormalCount($expr, $scope->getType($expr->getArgs()[0]->value), $scope)->yes(); |
| 78 | + } |
| 79 | + |
| 80 | + /** |
| 81 | + * @phpstan-assert-if-true Node\Expr\MethodCall $expr |
| 82 | + */ |
| 83 | + private static function isCountableMethodCall(Node\Expr $expr, Scope $scope): bool |
| 84 | + { |
57 | 85 | if ( |
58 | | - $right instanceof Node\Expr\MethodCall |
59 | | - && $right->name instanceof Node\Identifier |
60 | | - && $right->name->toLowerString() === 'count' |
61 | | - && count($right->getArgs()) === 0 |
| 86 | + $expr instanceof Node\Expr\MethodCall |
| 87 | + && $expr->name instanceof Node\Identifier |
| 88 | + && $expr->name->toLowerString() === 'count' |
| 89 | + && count($expr->getArgs()) === 0 |
62 | 90 | ) { |
63 | | - $type = $scope->getType($right->var); |
| 91 | + $type = $scope->getType($expr->var); |
64 | 92 |
|
65 | 93 | if ((new ObjectType(Countable::class))->isSuperTypeOf($type)->yes()) { |
66 | | - return [ |
67 | | - RuleErrorBuilder::message('You should use assertCount($expectedCount, $variable) instead of assertSame($expectedCount, $variable->count()).') |
68 | | - ->identifier('phpunit.assertCount') |
69 | | - ->build(), |
70 | | - ]; |
| 94 | + return true; |
71 | 95 | } |
72 | 96 | } |
73 | 97 |
|
74 | | - return []; |
| 98 | + return false; |
| 99 | + } |
| 100 | + |
| 101 | + private static function isNormalCount(Node\Expr\FuncCall $countFuncCall, Type $countedType, Scope $scope): TrinaryLogic |
| 102 | + { |
| 103 | + if (count($countFuncCall->getArgs()) === 1) { |
| 104 | + $isNormalCount = TrinaryLogic::createYes(); |
| 105 | + } else { |
| 106 | + $mode = $scope->getType($countFuncCall->getArgs()[1]->value); |
| 107 | + $isNormalCount = (new ConstantIntegerType(COUNT_NORMAL))->isSuperTypeOf($mode)->result->or($countedType->getIterableValueType()->isArray()->negate()); |
| 108 | + } |
| 109 | + return $isNormalCount; |
75 | 110 | } |
76 | 111 |
|
77 | 112 | } |
0 commit comments