AbstractFixerTestCase.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. <?php
  2. /*
  3. * This file is part of PHP CS Fixer.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. * Dariusz Rumiński <dariusz.ruminski@gmail.com>
  7. *
  8. * This source file is subject to the MIT license that is bundled
  9. * with this source code in the file LICENSE.
  10. */
  11. namespace PhpCsFixer\Tests\Test;
  12. use GeckoPackages\PHPUnit\Constraints\SameStringsConstraint;
  13. use PhpCsFixer\Fixer\FixerInterface;
  14. use PhpCsFixer\FixerFactory;
  15. use PhpCsFixer\Linter\Linter;
  16. use PhpCsFixer\Linter\LinterInterface;
  17. use PhpCsFixer\RuleSet;
  18. use PhpCsFixer\Tests\Test\Assert\AssertTokensTrait;
  19. use PhpCsFixer\Tokenizer\Token;
  20. use PhpCsFixer\Tokenizer\Tokens;
  21. use PhpCsFixer\Utils;
  22. use PHPUnit\Framework\TestCase;
  23. use Prophecy\Argument;
  24. /**
  25. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  26. *
  27. * @internal
  28. */
  29. abstract class AbstractFixerTestCase extends TestCase
  30. {
  31. use AssertTokensTrait;
  32. /**
  33. * @var LinterInterface
  34. */
  35. protected $linter;
  36. /**
  37. * @var null|FixerInterface
  38. */
  39. protected $fixer;
  40. /**
  41. * @var null|string
  42. */
  43. private $fixerClassName;
  44. protected function setUp()
  45. {
  46. parent::setUp();
  47. $this->linter = $this->getLinter();
  48. $this->fixer = $this->createFixer();
  49. }
  50. /**
  51. * @return FixerInterface
  52. */
  53. protected function createFixer()
  54. {
  55. $fixerClassName = $this->getFixerClassName();
  56. return new $fixerClassName();
  57. }
  58. /**
  59. * Create fixer factory with all needed fixers registered.
  60. *
  61. * @return FixerFactory
  62. */
  63. protected function createFixerFactory()
  64. {
  65. return FixerFactory::create()->registerBuiltInFixers();
  66. }
  67. /**
  68. * @return string
  69. */
  70. protected function getFixerName()
  71. {
  72. $reflection = new \ReflectionClass($this);
  73. $name = preg_replace('/FixerTest$/', '', $reflection->getShortName());
  74. return Utils::camelCaseToUnderscore($name);
  75. }
  76. /**
  77. * @param string $filename
  78. *
  79. * @return \SplFileInfo
  80. */
  81. protected function getTestFile($filename = __FILE__)
  82. {
  83. static $files = [];
  84. if (!isset($files[$filename])) {
  85. $files[$filename] = new \SplFileInfo($filename);
  86. }
  87. return $files[$filename];
  88. }
  89. /**
  90. * Tests if a fixer fixes a given string to match the expected result.
  91. *
  92. * It is used both if you want to test if something is fixed or if it is not touched by the fixer.
  93. * It also makes sure that the expected output does not change when run through the fixer. That means that you
  94. * do not need two test cases like [$expected] and [$expected, $input] (where $expected is the same in both cases)
  95. * as the latter covers both of them.
  96. * This method throws an exception if $expected and $input are equal to prevent test cases that accidentally do
  97. * not test anything.
  98. *
  99. * @param string $expected The expected fixer output
  100. * @param null|string $input The fixer input, or null if it should intentionally be equal to the output
  101. * @param null|\SplFileInfo $file The file to fix, or null if unneeded
  102. */
  103. protected function doTest($expected, $input = null, \SplFileInfo $file = null)
  104. {
  105. if ($expected === $input) {
  106. throw new \InvalidArgumentException('Input parameter must not be equal to expected parameter.');
  107. }
  108. $file = $file ?: $this->getTestFile();
  109. $fileIsSupported = $this->fixer->supports($file);
  110. if (null !== $input) {
  111. $this->assertNull($this->lintSource($input));
  112. Tokens::clearCache();
  113. $tokens = Tokens::fromCode($input);
  114. if ($fileIsSupported) {
  115. $this->assertTrue($this->fixer->isCandidate($tokens), 'Fixer must be a candidate for input code.');
  116. $this->assertFalse($tokens->isChanged(), 'Fixer must not touch Tokens on candidate check.');
  117. $fixResult = $this->fixer->fix($file, $tokens);
  118. $this->assertNull($fixResult, '->fix method must return null.');
  119. }
  120. $this->assertThat(
  121. $tokens->generateCode(),
  122. new SameStringsConstraint($expected),
  123. 'Code build on input code must match expected code.'
  124. );
  125. $this->assertTrue($tokens->isChanged(), 'Tokens collection built on input code must be marked as changed after fixing.');
  126. $tokens->clearEmptyTokens();
  127. $this->assertSame(
  128. count($tokens),
  129. count(array_unique(array_map(static function (Token $token) {
  130. return spl_object_hash($token);
  131. }, $tokens->toArray()))),
  132. 'Token items inside Tokens collection must be unique.'
  133. );
  134. Tokens::clearCache();
  135. $expectedTokens = Tokens::fromCode($expected);
  136. $this->assertTokens($expectedTokens, $tokens);
  137. }
  138. $this->assertNull($this->lintSource($expected));
  139. Tokens::clearCache();
  140. $tokens = Tokens::fromCode($expected);
  141. if ($fileIsSupported) {
  142. $fixResult = $this->fixer->fix($file, $tokens);
  143. $this->assertNull($fixResult, '->fix method must return null.');
  144. }
  145. $this->assertThat(
  146. $tokens->generateCode(),
  147. new SameStringsConstraint($expected),
  148. 'Code build on expected code must not change.'
  149. );
  150. $this->assertFalse($tokens->isChanged(), 'Tokens collection built on expected code must not be marked as changed after fixing.');
  151. }
  152. /**
  153. * @param string $source
  154. *
  155. * @return null|string
  156. */
  157. protected function lintSource($source)
  158. {
  159. try {
  160. $this->linter->lintSource($source)->check();
  161. } catch (\Exception $e) {
  162. return $e->getMessage()."\n\nSource:\n${source}";
  163. }
  164. }
  165. private function assertTokens(Tokens $expectedTokens, Tokens $inputTokens)
  166. {
  167. foreach ($expectedTokens as $index => $expectedToken) {
  168. $option = ['JSON_PRETTY_PRINT'];
  169. $inputToken = $inputTokens[$index];
  170. $this->assertTrue(
  171. $expectedToken->equals($inputToken),
  172. sprintf("The token at index %d must be:\n%s,\ngot:\n%s.", $index, $expectedToken->toJson($option), $inputToken->toJson($option))
  173. );
  174. $expectedTokenKind = $expectedToken->isArray() ? $expectedToken->getId() : $expectedToken->getContent();
  175. $this->assertTrue(
  176. $inputTokens->isTokenKindFound($expectedTokenKind),
  177. sprintf('The token kind %s must be found in fixed tokens collection.', $expectedTokenKind)
  178. );
  179. }
  180. $this->assertSame($expectedTokens->count(), $inputTokens->count(), 'Both collections must have the same length.');
  181. }
  182. /**
  183. * @return LinterInterface
  184. */
  185. private function getLinter()
  186. {
  187. static $linter = null;
  188. if (null === $linter) {
  189. if (getenv('SKIP_LINT_TEST_CASES')) {
  190. $linterProphecy = $this->prophesize(\PhpCsFixer\Linter\LinterInterface::class);
  191. $linterProphecy
  192. ->lintSource(Argument::type('string'))
  193. ->willReturn($this->prophesize(\PhpCsFixer\Linter\LintingResultInterface::class)->reveal());
  194. $linter = $linterProphecy->reveal();
  195. } else {
  196. $linter = new Linter();
  197. }
  198. }
  199. return $linter;
  200. }
  201. /**
  202. * @return string
  203. */
  204. private function getFixerClassName()
  205. {
  206. if (null !== $this->fixerClassName) {
  207. return $this->fixerClassName;
  208. }
  209. try {
  210. $fixers = $this->createFixerFactory()
  211. ->useRuleSet(new RuleSet([$this->getFixerName() => true]))
  212. ->getFixers()
  213. ;
  214. } catch (\UnexpectedValueException $e) {
  215. throw new \UnexpectedValueException('Cannot determine fixer class, perhaps you forget to override `getFixerName` or `createFixerFactory` method?', 0, $e);
  216. }
  217. if (1 !== count($fixers)) {
  218. throw new \UnexpectedValueException(sprintf('Determine fixer class should result in one fixer, got "%d". Perhaps you configured the fixer to "false" ?', count($fixers)));
  219. }
  220. $this->fixerClassName = get_class($fixers[0]);
  221. return $this->fixerClassName;
  222. }
  223. }