44
55use  Countable ;
66use  PhpParser \Node ;
7+ use  PhpParser \Node \Expr \MethodCall ;
78use  PhpParser \NodeAbstract ;
89use  PHPStan \Analyser \Scope ;
910use  PHPStan \Rules \Rule ;
1718class  AssertSameWithCountRule implements  Rule
1819{
1920
21+ 	/** @var bool */ 
22+ 	private  $ bleedingEdge ;
23+ 
24+ 	public  function  __construct (bool  $ bleedingEdge )
25+ 	{
26+ 		$ this  ->bleedingEdge  = $ bleedingEdge ;
27+ 	}
28+ 
2029	public  function  getNodeType (): string 
2130	{
2231		return  NodeAbstract::class;
@@ -37,36 +46,89 @@ public function processNode(Node $node, Scope $scope): array
3746
3847		$ right  = $ node ->getArgs ()[1 ]->value ;
3948
40- 		if  (
41- 			$ right  instanceof  Node \Expr \FuncCall
42- 			&& $ right ->name  instanceof  Node \Name
43- 			&& $ right ->name ->toLowerString () === 'count ' 
44- 		) {
45- 			return  [
46- 				RuleErrorBuilder::message ('You should use assertCount($expectedCount, $variable) instead of assertSame($expectedCount, count($variable)). ' )
47- 					->identifier ('phpunit.assertCount ' )
48- 					->build (),
49- 			];
49+ 		$ rightIsCountFuncCall  = $ this  ->isCountFuncCall ($ right );
50+ 		$ rightIsCountMethodCall  = $ this  ->isCountMethodCall ($ right ) && $ this  ->argIsCountable ($ right , $ scope );
51+ 		if  (!($ rightIsCountFuncCall  || $ rightIsCountMethodCall )) {
52+ 			return  [];
5053		}
5154
52- 		if  (
53- 			$ right  instanceof  Node \Expr \MethodCall
54- 			&& $ right ->name  instanceof  Node \Identifier
55- 			&& $ right ->name ->toLowerString () === 'count ' 
56- 			&& count ($ right ->getArgs ()) === 0 
57- 		) {
58- 			$ type  = $ scope ->getType ($ right ->var );
55+ 		$ leftIsCountFuncCall  = $ leftIsCountMethodCall  = false ;
56+ 		if  ($ this  ->bleedingEdge ) {
57+ 			$ left  = $ node ->getArgs ()[0 ]->value ;
58+ 			$ leftIsCountFuncCall  = $ this  ->isCountFuncCall ($ left );
59+ 			$ leftIsCountMethodCall  = $ this  ->isCountMethodCall ($ left ) && $ this  ->argIsCountable ($ left , $ scope );
60+ 		}
5961
60- 			if  ((new  ObjectType (Countable::class))->isSuperTypeOf ($ type )->yes ()) {
62+ 		if  ($ rightIsCountFuncCall ) {
63+ 			if  ($ leftIsCountFuncCall ) {
6164				return  [
62- 					RuleErrorBuilder::message ('You should use assertCount($expectedCount, $variable) instead of assertSame($expectedCount, $variable->count()). ' )
63- 						->identifier ('phpunit.assertCount ' )
65+ 					RuleErrorBuilder::message ('You should use assertSameSize($expected, $variable) instead of assertSame(count($expected), count($variable)). ' )
66+ 						->identifier ('phpunit.assertSameSize ' )
67+ 						->build (),
68+ 				];
69+ 			} elseif  ($ leftIsCountMethodCall ) {
70+ 				return  [
71+ 					RuleErrorBuilder::message ('You should use assertSameSize($expected, $variable) instead of assertSame($expected->count(), count($variable)). ' )
72+ 						->identifier ('phpunit.assertSameSize ' )
6473						->build (),
6574				];
6675			}
76+ 
77+ 			return  [
78+ 				RuleErrorBuilder::message ('You should use assertCount($expectedCount, $variable) instead of assertSame($expectedCount, count($variable)). ' )
79+ 					->identifier ('phpunit.assertCount ' )
80+ 					->build (),
81+ 			];
82+ 		}
83+ 
84+ 		if  ($ leftIsCountFuncCall ) {
85+ 			return  [
86+ 				RuleErrorBuilder::message ('You should use assertSameSize($expected, $variable) instead of assertSame(count($expected), $variable->count()). ' )
87+ 					->identifier ('phpunit.assertSameSize ' )
88+ 					->build (),
89+ 			];
90+ 		} elseif  ($ leftIsCountMethodCall ) {
91+ 			return  [
92+ 				RuleErrorBuilder::message ('You should use assertSameSize($expected, $variable) instead of assertSame($expected->count(), $variable->count()). ' )
93+ 					->identifier ('phpunit.assertSameSize ' )
94+ 					->build (),
95+ 			];
6796		}
6897
69- 		return  [];
98+ 		return  [
99+ 			RuleErrorBuilder::message ('You should use assertCount($expectedCount, $variable) instead of assertSame($expectedCount, $variable->count()). ' )
100+ 				->identifier ('phpunit.assertCount ' )
101+ 				->build (),
102+ 		];
103+ 	}
104+ 
105+ 	/** 
106+ 	 * @phpstan-assert-if-true Node\Expr\FuncCall $expr 
107+ 	 */ 
108+ 	private  function  isCountFuncCall (Node \Expr   $ expr ): bool 
109+ 	{
110+ 		return  $ expr  instanceof  Node \Expr \FuncCall
111+ 			&& $ expr ->name  instanceof  Node \Name
112+ 			&& $ expr ->name ->toLowerString () === 'count ' ;
113+ 	}
114+ 
115+ 	/** 
116+ 	 * @phpstan-assert-if-true Node\Expr\MethodCall $expr 
117+ 	 */ 
118+ 	private  function  isCountMethodCall (Node \Expr   $ expr ): bool 
119+ 	{
120+ 		return  $ expr  instanceof  Node \Expr \MethodCall
121+ 			&& $ expr ->name  instanceof  Node \Identifier
122+ 			&& $ expr ->name ->toLowerString () === 'count ' 
123+ 			&& count ($ expr ->getArgs ()) === 0 ;
124+ 	}
125+ 
126+ 	private  function  argIsCountable (MethodCall   $ methodCall , Scope   $ scope ): bool 
127+ 	{
128+ 		$ type  = $ scope ->getType ($ methodCall ->var );
129+ 		$ countableType  = new  ObjectType (Countable::class);
130+ 
131+ 		return  $ countableType ->isSuperTypeOf ($ type )->yes ();
70132	}
71133
72134}
0 commit comments