AbstractTransformerTestCase.php 7.1 KB

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