ClassyAnalyzerTest.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4. * This file is part of PHP CS Fixer.
  5. *
  6. * (c) Fabien Potencier <fabien@symfony.com>
  7. * Dariusz Rumiński <dariusz.ruminski@gmail.com>
  8. *
  9. * This source file is subject to the MIT license that is bundled
  10. * with this source code in the file LICENSE.
  11. */
  12. namespace PhpCsFixer\Tests\Tokenizer\Analyzer;
  13. use PhpCsFixer\Tests\TestCase;
  14. use PhpCsFixer\Tokenizer\Analyzer\ClassyAnalyzer;
  15. use PhpCsFixer\Tokenizer\Tokens;
  16. /**
  17. * @internal
  18. *
  19. * @covers \PhpCsFixer\Tokenizer\Analyzer\ClassyAnalyzer
  20. */
  21. final class ClassyAnalyzerTest extends TestCase
  22. {
  23. /**
  24. * @param array<int, bool> $expected
  25. *
  26. * @dataProvider provideIsClassyInvocationCases
  27. */
  28. public function testIsClassyInvocation(string $source, array $expected): void
  29. {
  30. self::assertClassyInvocation($source, $expected);
  31. }
  32. public static function provideIsClassyInvocationCases(): iterable
  33. {
  34. yield [
  35. '<?php new Foo;',
  36. [3 => true],
  37. ];
  38. yield [
  39. '<?php new \Foo;',
  40. [4 => true],
  41. ];
  42. yield [
  43. '<?php new Bar\Foo;',
  44. [3 => false, 5 => true],
  45. ];
  46. yield [
  47. '<?php new namespace\Foo;',
  48. [5 => true],
  49. ];
  50. yield [
  51. '<?php Foo::bar();',
  52. [1 => true, 3 => false],
  53. ];
  54. yield [
  55. '<?php \Foo::bar();',
  56. [2 => true, 4 => false],
  57. ];
  58. yield [
  59. '<?php Bar\Foo::bar();',
  60. [1 => false, 3 => true, 5 => false],
  61. ];
  62. yield [
  63. '<?php $foo instanceof Foo;',
  64. [5 => true],
  65. ];
  66. yield [
  67. '<?php class Foo extends \A {}',
  68. [3 => false, 8 => true],
  69. ];
  70. yield [
  71. '<?php class Foo implements A, B\C, \D, E {}',
  72. [3 => false, 7 => true, 10 => false, 12 => true, 16 => true, 19 => true],
  73. ];
  74. yield [
  75. '<?php class Foo { use A, B\C, \D, E { A::bar insteadof \E; } }',
  76. [3 => false, 9 => true, 12 => false, 14 => true, 18 => true, 21 => true, 25 => true, 32 => true],
  77. ];
  78. yield 'with reference' => [
  79. '<?php function foo(Foo $foo, Bar &$bar, \Baz ...$baz, Foo\Bar $fooBar) {}',
  80. [3 => false, 5 => true, 10 => true, 17 => true, 23 => false, 25 => true],
  81. ];
  82. yield [
  83. '<?php class Foo { function bar() { parent::bar(); self::baz(); $a instanceof self; } }',
  84. [3 => false, 9 => false, 15 => false, 17 => false, 22 => false, 24 => false, 33 => false],
  85. ];
  86. yield [
  87. '<?php echo FOO, \BAR;',
  88. [3 => false, 7 => false],
  89. ];
  90. yield [
  91. '<?php FOO & $bar;',
  92. [1 => false],
  93. ];
  94. yield [
  95. '<?php foo(); \bar();',
  96. [1 => false, 7 => false],
  97. ];
  98. yield [
  99. '<?php function foo(): \Foo {}',
  100. [3 => false, 9 => true],
  101. ];
  102. yield [
  103. '<?php function foo(?Foo $foo, ?Foo\Bar $fooBar): ?\Foo {}',
  104. [3 => false, 6 => true, 12 => false, 14 => true, 22 => true],
  105. ];
  106. yield [
  107. '<?php function foo(iterable $foo): string {}',
  108. [3 => false, 5 => false, 11 => false],
  109. ];
  110. yield [
  111. '<?php function foo(?int $foo): ?string {}',
  112. [3 => false, 6 => false, 13 => false],
  113. ];
  114. yield [
  115. '<?php function foo(int $foo, string &$bar): self {}',
  116. [3 => false, 5 => false, 10 => false, 17 => false],
  117. ];
  118. yield [
  119. '<?php function foo(): Foo {}',
  120. [3 => false, 8 => true],
  121. ];
  122. foreach (['bool', 'float', 'int', 'parent', 'self', 'string', 'void'] as $returnType) {
  123. yield [
  124. \sprintf('<?php function foo(): %s {}', $returnType),
  125. [3 => false, 8 => false],
  126. ];
  127. }
  128. yield [
  129. '<?php try {} catch (Foo $e) {}',
  130. [9 => true],
  131. ];
  132. yield [
  133. '<?php try {} catch (\Foo $e) {}',
  134. [10 => true],
  135. ];
  136. yield [
  137. '<?php try {} catch (/* ... */ Foo $e /* ... */) {}',
  138. [11 => true],
  139. ];
  140. yield [
  141. '<?php try {} catch (/* ... */ \Foo $e /* ... */) {}',
  142. [12 => true],
  143. ];
  144. }
  145. /**
  146. * @param array<int, bool> $expected
  147. *
  148. * @dataProvider provideIsClassyInvocation80Cases
  149. *
  150. * @requires PHP 8.0
  151. */
  152. public function testIsClassyInvocation80(string $source, array $expected): void
  153. {
  154. self::assertClassyInvocation($source, $expected);
  155. }
  156. public static function provideIsClassyInvocation80Cases(): iterable
  157. {
  158. yield [
  159. '<?php function foo(): \Foo|int {}',
  160. [3 => false, 9 => true, 11 => false],
  161. ];
  162. yield [
  163. '<?php function foo(): \Foo|A|int {}',
  164. [3 => false, 9 => true, 11 => true, 13 => false],
  165. ];
  166. yield [
  167. '<?php function foo(): int|A|NULL {}',
  168. [3 => false, 8 => false, 10 => true, 12 => false],
  169. ];
  170. yield [
  171. '<?php function foo(): int|A|false {}',
  172. [3 => false, 8 => false, 10 => true, 12 => false],
  173. ];
  174. yield [
  175. '<?php #[AnAttribute] class Foo {}',
  176. [2 => true],
  177. ];
  178. yield [
  179. '<?php try {} catch (Foo) {}',
  180. [9 => true],
  181. ];
  182. yield [
  183. '<?php try {} catch (\Foo) {}',
  184. [10 => true],
  185. ];
  186. yield [
  187. '<?php try {} catch (/* non-capturing catch */ Foo /* just because! */) {}',
  188. [11 => true],
  189. ];
  190. yield [
  191. '<?php try {} catch (/* non-capturing catch */ \Foo /* just because! */) {}',
  192. [12 => true],
  193. ];
  194. }
  195. /**
  196. * @param array<int, bool> $expected
  197. *
  198. * @dataProvider provideIsClassyInvocation81Cases
  199. *
  200. * @requires PHP 8.1
  201. */
  202. public function testIsClassyInvocation81(string $source, array $expected): void
  203. {
  204. self::assertClassyInvocation($source, $expected);
  205. }
  206. public static function provideIsClassyInvocation81Cases(): iterable
  207. {
  208. yield 'never' => [
  209. '<?php function foo(): never {}',
  210. [3 => false, 8 => false],
  211. ];
  212. yield 'intersection' => [
  213. '<?php function foo(): \Foo&Bar {}',
  214. [3 => false, 9 => true, 11 => true],
  215. ];
  216. }
  217. /**
  218. * @param array<int, bool> $expected
  219. */
  220. private static function assertClassyInvocation(string $source, array $expected): void
  221. {
  222. $tokens = Tokens::fromCode($source);
  223. $analyzer = new ClassyAnalyzer();
  224. foreach ($expected as $index => $isClassy) {
  225. self::assertSame($isClassy, $analyzer->isClassyInvocation($tokens, $index), \sprintf('Token at index %d should match the expected value "%s".', $index, true === $isClassy ? 'true' : 'false'));
  226. }
  227. }
  228. }