AbstractTransformerTestCase.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  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\Test;
  13. use PhpCsFixer\Tests\TestCase;
  14. use PhpCsFixer\Tokenizer\CT;
  15. use PhpCsFixer\Tokenizer\Token;
  16. use PhpCsFixer\Tokenizer\Tokens;
  17. use PhpCsFixer\Tokenizer\TransformerInterface;
  18. /**
  19. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  20. *
  21. * @internal
  22. */
  23. abstract class AbstractTransformerTestCase extends TestCase
  24. {
  25. /**
  26. * @var null|TransformerInterface
  27. */
  28. protected $transformer;
  29. protected function setUp(): void
  30. {
  31. parent::setUp();
  32. $this->transformer = $this->createTransformer();
  33. }
  34. protected function tearDown(): void
  35. {
  36. parent::tearDown();
  37. $this->transformer = null;
  38. }
  39. public function testGetPriority(): void
  40. {
  41. static::assertIsInt($this->transformer->getPriority(), $this->transformer->getName());
  42. }
  43. public function testGetName(): void
  44. {
  45. $name = $this->transformer->getName();
  46. static::assertMatchesRegularExpression('/^[a-z]+[a-z_]*[a-z]$/', $name);
  47. }
  48. public function testGetCustomTokens(): void
  49. {
  50. $name = $this->transformer->getName();
  51. $customTokens = $this->transformer->getCustomTokens();
  52. static::assertIsArray($customTokens, $name);
  53. foreach ($customTokens as $customToken) {
  54. static::assertIsInt($customToken, $name);
  55. }
  56. }
  57. public function testGetRequiredPhpVersionId(): void
  58. {
  59. $name = $this->transformer->getName();
  60. $requiredPhpVersionId = $this->transformer->getRequiredPhpVersionId();
  61. static::assertIsInt($requiredPhpVersionId, $name);
  62. static::assertGreaterThanOrEqual(5_00_00, $requiredPhpVersionId, $name);
  63. }
  64. public function testTransformersIsFinal(): void
  65. {
  66. $transformerRef = new \ReflectionClass($this->transformer);
  67. static::assertTrue(
  68. $transformerRef->isFinal(),
  69. sprintf('Transformer "%s" must be declared "final."', $this->transformer->getName())
  70. );
  71. }
  72. public function testTransformDoesNotChangeSimpleCode(): void
  73. {
  74. if (\PHP_VERSION_ID < $this->transformer->getRequiredPhpVersionId()) {
  75. $this->expectNotToPerformAssertions();
  76. return;
  77. }
  78. Tokens::clearCache();
  79. $tokens = Tokens::fromCode('<?php ');
  80. foreach ($tokens as $index => $token) {
  81. $this->transformer->process($tokens, $token, $index);
  82. }
  83. static::assertFalse($tokens->isChanged());
  84. }
  85. /**
  86. * @param array<int, int|string> $expectedTokens
  87. * @param list<int> $observedKindsOrPrototypes
  88. */
  89. protected function doTest(string $source, array $expectedTokens, array $observedKindsOrPrototypes = []): void
  90. {
  91. Tokens::clearCache();
  92. $tokens = new TokensWithObservedTransformers();
  93. $tokens->setCode($source);
  94. static::assertSame(
  95. \count($expectedTokens),
  96. $this->countTokenPrototypes(
  97. $tokens,
  98. array_map(
  99. static function ($kindOrPrototype) {
  100. return \is_int($kindOrPrototype) ? [$kindOrPrototype] : $kindOrPrototype;
  101. },
  102. array_unique(array_merge($observedKindsOrPrototypes, $expectedTokens))
  103. )
  104. ),
  105. 'Number of expected tokens does not match actual token count.'
  106. );
  107. $transformerName = $this->transformer->getName();
  108. $customTokensOfTransformer = $this->transformer->getCustomTokens();
  109. foreach ($customTokensOfTransformer as $customTokenOfTransformer) {
  110. static::assertTrue(CT::has($customTokenOfTransformer), sprintf('Unknown custom token id "%d" in "%s".', $transformerName, $customTokenOfTransformer));
  111. static::assertStringStartsWith('CT::', CT::getName($customTokenOfTransformer));
  112. }
  113. $customTokensOfTransformerList = implode(', ', array_map(
  114. static fn (int $ct): string => CT::getName($ct),
  115. $customTokensOfTransformer,
  116. ));
  117. foreach ($tokens->observedModificationsPerTransformer as $appliedTransformerName => $modificationsOfTransformer) {
  118. foreach ($modificationsOfTransformer as $modification) {
  119. $customTokenName = Token::getNameForId($modification);
  120. if ($appliedTransformerName === $transformerName) {
  121. static::assertContains(
  122. $modification,
  123. $customTokensOfTransformer,
  124. sprintf(
  125. 'Transformation into "%s" must be allowed in self-documentation of the Transformer, currently allowed custom tokens are: %s',
  126. $customTokenName,
  127. $customTokensOfTransformerList
  128. )
  129. );
  130. } else {
  131. static::assertNotContains(
  132. $modification,
  133. $customTokensOfTransformer,
  134. sprintf(
  135. 'Transformation into "%s" must NOT be applied by other Transformer than "%s".',
  136. $customTokenName,
  137. $transformerName
  138. )
  139. );
  140. }
  141. }
  142. }
  143. foreach ($expectedTokens as $index => $tokenIdOrContent) {
  144. if (\is_string($tokenIdOrContent)) {
  145. static::assertTrue($tokens[$index]->equals($tokenIdOrContent), sprintf('The token at index %d should be %s, got %s', $index, json_encode($tokenIdOrContent), $tokens[$index]->toJson()));
  146. continue;
  147. }
  148. static::assertSame(
  149. CT::has($tokenIdOrContent) ? CT::getName($tokenIdOrContent) : token_name($tokenIdOrContent),
  150. $tokens[$index]->getName(),
  151. sprintf('Token name should be the same. Got token "%s" at index %d.', $tokens[$index]->toJson(), $index)
  152. );
  153. static::assertSame(
  154. $tokenIdOrContent,
  155. $tokens[$index]->getId(),
  156. sprintf('Token id should be the same. Got token "%s" at index %d.', $tokens[$index]->toJson(), $index)
  157. );
  158. }
  159. }
  160. /**
  161. * @param list<array{0: int, 1?: string}> $prototypes
  162. */
  163. private function countTokenPrototypes(Tokens $tokens, array $prototypes): int
  164. {
  165. $count = 0;
  166. foreach ($tokens as $token) {
  167. if ($token->equalsAny($prototypes)) {
  168. ++$count;
  169. }
  170. }
  171. return $count;
  172. }
  173. private function createTransformer(): TransformerInterface
  174. {
  175. $transformerClassName = preg_replace('/^(PhpCsFixer)\\\\Tests(\\\\.+)Test$/', '$1$2', static::class);
  176. return new $transformerClassName();
  177. }
  178. }