AbstractTransformerTestCase.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  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. self::assertIsInt($this->transformer->getPriority(), $this->transformer->getName());
  42. }
  43. public function testGetName(): void
  44. {
  45. $name = $this->transformer->getName();
  46. self::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. self::assertIsArray($customTokens, $name);
  53. foreach ($customTokens as $customToken) {
  54. self::assertIsInt($customToken, $name);
  55. }
  56. }
  57. public function testGetRequiredPhpVersionId(): void
  58. {
  59. $name = $this->transformer->getName();
  60. $requiredPhpVersionId = $this->transformer->getRequiredPhpVersionId();
  61. self::assertIsInt($requiredPhpVersionId, $name);
  62. self::assertGreaterThanOrEqual(5_00_00, $requiredPhpVersionId, $name);
  63. }
  64. public function testTransformersIsFinal(): void
  65. {
  66. $transformerRef = new \ReflectionClass($this->transformer);
  67. self::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. self::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. self::assertSame(
  95. \count($expectedTokens),
  96. $this->countTokenPrototypes(
  97. $tokens,
  98. array_map(
  99. static fn ($kindOrPrototype) => \is_int($kindOrPrototype) ? [$kindOrPrototype] : $kindOrPrototype,
  100. array_unique([...$observedKindsOrPrototypes, ...$expectedTokens])
  101. )
  102. ),
  103. 'Number of expected tokens does not match actual token count.'
  104. );
  105. $transformerName = $this->transformer->getName();
  106. $customTokensOfTransformer = $this->transformer->getCustomTokens();
  107. foreach ($customTokensOfTransformer as $customTokenOfTransformer) {
  108. self::assertTrue(CT::has($customTokenOfTransformer), sprintf('Unknown custom token id "%d" in "%s".', $transformerName, $customTokenOfTransformer));
  109. self::assertStringStartsWith('CT::', CT::getName($customTokenOfTransformer));
  110. }
  111. $customTokensOfTransformerList = implode(', ', array_map(
  112. static fn (int $ct): string => CT::getName($ct),
  113. $customTokensOfTransformer,
  114. ));
  115. foreach ($tokens->observedModificationsPerTransformer as $appliedTransformerName => $modificationsOfTransformer) {
  116. foreach ($modificationsOfTransformer as $modification) {
  117. $customTokenName = Token::getNameForId($modification);
  118. if ($appliedTransformerName === $transformerName) {
  119. self::assertContains(
  120. $modification,
  121. $customTokensOfTransformer,
  122. sprintf(
  123. 'Transformation into "%s" must be allowed in self-documentation of the Transformer, currently allowed custom tokens are: %s',
  124. $customTokenName,
  125. $customTokensOfTransformerList
  126. )
  127. );
  128. } else {
  129. self::assertNotContains(
  130. $modification,
  131. $customTokensOfTransformer,
  132. sprintf(
  133. 'Transformation into "%s" must NOT be applied by other Transformer than "%s".',
  134. $customTokenName,
  135. $transformerName
  136. )
  137. );
  138. }
  139. }
  140. }
  141. foreach ($expectedTokens as $index => $tokenIdOrContent) {
  142. if (\is_string($tokenIdOrContent)) {
  143. self::assertTrue($tokens[$index]->equals($tokenIdOrContent), sprintf('The token at index %d should be %s, got %s', $index, json_encode($tokenIdOrContent, JSON_THROW_ON_ERROR), $tokens[$index]->toJson()));
  144. continue;
  145. }
  146. self::assertSame(
  147. CT::has($tokenIdOrContent) ? CT::getName($tokenIdOrContent) : token_name($tokenIdOrContent),
  148. $tokens[$index]->getName(),
  149. sprintf('Token name should be the same. Got token "%s" at index %d.', $tokens[$index]->toJson(), $index)
  150. );
  151. self::assertSame(
  152. $tokenIdOrContent,
  153. $tokens[$index]->getId(),
  154. sprintf('Token id should be the same. Got token "%s" at index %d.', $tokens[$index]->toJson(), $index)
  155. );
  156. }
  157. }
  158. /**
  159. * @param list<array{0: int, 1?: string}> $prototypes
  160. */
  161. private function countTokenPrototypes(Tokens $tokens, array $prototypes): int
  162. {
  163. $count = 0;
  164. foreach ($tokens as $token) {
  165. if ($token->equalsAny($prototypes)) {
  166. ++$count;
  167. }
  168. }
  169. return $count;
  170. }
  171. private function createTransformer(): TransformerInterface
  172. {
  173. $transformerClassName = preg_replace('/^(PhpCsFixer)\\\\Tests(\\\\.+)Test$/', '$1$2', static::class);
  174. return new $transformerClassName();
  175. }
  176. }