FixerTest.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  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\AutoReview;
  12. use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface;
  13. use PhpCsFixer\Fixer\DefinedFixerInterface;
  14. use PhpCsFixer\Fixer\DeprecatedFixerInterface;
  15. use PhpCsFixer\Fixer\FixerInterface;
  16. use PhpCsFixer\Fixer\Whitespace\SingleBlankLineAtEofFixer;
  17. use PhpCsFixer\FixerDefinition\CodeSampleInterface;
  18. use PhpCsFixer\FixerDefinition\FileSpecificCodeSampleInterface;
  19. use PhpCsFixer\FixerDefinition\VersionSpecificCodeSampleInterface;
  20. use PhpCsFixer\FixerFactory;
  21. use PhpCsFixer\StdinFileInfo;
  22. use PhpCsFixer\Tests\TestCase;
  23. use PhpCsFixer\Tokenizer\Tokens;
  24. /**
  25. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  26. *
  27. * @internal
  28. *
  29. * @coversNothing
  30. * @group auto-review
  31. */
  32. final class FixerTest extends TestCase
  33. {
  34. // do not modify this structure without prior discussion
  35. private $allowedRequiredOptions = [
  36. 'header_comment' => ['header' => true],
  37. ];
  38. // do not modify this structure without prior discussion
  39. private $allowedFixersWithoutDefaultCodeSample = [
  40. 'general_phpdoc_annotation_remove' => true,
  41. ];
  42. /**
  43. * @param FixerInterface $fixer
  44. *
  45. * @dataProvider provideFixerDefinitionsCases
  46. */
  47. public function testFixerDefinitions(FixerInterface $fixer)
  48. {
  49. $this->assertInstanceOf(\PhpCsFixer\Fixer\DefinedFixerInterface::class, $fixer);
  50. /** @var DefinedFixerInterface $fixer */
  51. $fixerName = $fixer->getName();
  52. $definition = $fixer->getDefinition();
  53. $fixerIsConfigurable = $fixer instanceof ConfigurationDefinitionFixerInterface;
  54. $this->assertRegExp('/^[A-Z@].*\.$/', $definition->getSummary(), sprintf('[%s] Description must start with capital letter or an @ and end with dot.', $fixerName));
  55. $samples = $definition->getCodeSamples();
  56. $this->assertNotEmpty($samples, sprintf('[%s] Code samples are required.', $fixerName));
  57. $configSamplesProvided = [];
  58. $dummyFileInfo = new StdinFileInfo();
  59. foreach ($samples as $sampleCounter => $sample) {
  60. $this->assertInstanceOf(CodeSampleInterface::class, $sample, sprintf('[%s] Sample #%d', $fixerName, $sampleCounter));
  61. $this->assertInternalType('int', $sampleCounter);
  62. $code = $sample->getCode();
  63. $this->assertStringIsNotEmpty($code, sprintf('[%s] Sample #%d', $fixerName, $sampleCounter));
  64. if (!($fixer instanceof SingleBlankLineAtEofFixer)) {
  65. $this->assertSame("\n", substr($code, -1), sprintf('[%s] Sample #%d must end with linebreak', $fixerName, $sampleCounter));
  66. }
  67. $config = $sample->getConfiguration();
  68. if (null !== $config) {
  69. $this->assertTrue($fixerIsConfigurable, sprintf('[%s] Sample #%d has configuration, but the fixer is not configurable.', $fixerName, $sampleCounter));
  70. $this->assertInternalType('array', $config, sprintf('[%s] Sample #%d configuration must be an array or null.', $fixerName, $sampleCounter));
  71. $configSamplesProvided[$sampleCounter] = $config;
  72. } elseif ($fixerIsConfigurable) {
  73. if (!$sample instanceof VersionSpecificCodeSampleInterface) {
  74. $this->assertArrayNotHasKey('default', $configSamplesProvided, sprintf('[%s] Multiple non-versioned samples with default configuration.', $fixerName));
  75. }
  76. $configSamplesProvided['default'] = true;
  77. }
  78. if ($sample instanceof VersionSpecificCodeSampleInterface && !$sample->isSuitableFor(PHP_VERSION_ID)) {
  79. continue;
  80. }
  81. if ($fixerIsConfigurable) {
  82. // always re-configure as the fixer might have been configured with diff. configuration form previous sample
  83. $fixer->configure(null === $config ? [] : $config);
  84. }
  85. Tokens::clearCache();
  86. $tokens = Tokens::fromCode($code);
  87. $fixer->fix(
  88. $sample instanceof FileSpecificCodeSampleInterface ? $sample->getSplFileInfo() : $dummyFileInfo,
  89. $tokens
  90. );
  91. $this->assertTrue($tokens->isChanged(), sprintf('[%s] Sample #%d is not changed during fixing.', $fixerName, $sampleCounter));
  92. $duplicatedCodeSample = array_search(
  93. $sample,
  94. array_slice($samples, 0, $sampleCounter),
  95. false
  96. );
  97. $this->assertFalse(
  98. $duplicatedCodeSample,
  99. sprintf('[%s] Sample #%d duplicates #%d.', $fixerName, $sampleCounter, $duplicatedCodeSample)
  100. );
  101. }
  102. if ($fixerIsConfigurable) {
  103. if (isset($configSamplesProvided['default'])) {
  104. reset($configSamplesProvided);
  105. $this->assertSame('default', key($configSamplesProvided), sprintf('[%s] First sample must be for the default configuration.', $fixerName));
  106. } elseif (!isset($this->allowedFixersWithoutDefaultCodeSample[$fixerName])) {
  107. $this->assertArrayHasKey($fixerName, $this->allowedRequiredOptions, sprintf('[%s] Has no sample for default configuration.', $fixerName));
  108. }
  109. }
  110. if ($fixer->isRisky()) {
  111. $this->assertStringIsNotEmpty($definition->getRiskyDescription(), sprintf('[%s] Risky reasoning is required.', $fixerName));
  112. } else {
  113. $this->assertNull($definition->getRiskyDescription(), sprintf('[%s] Fixer is not risky so no description of it expected.', $fixerName));
  114. }
  115. }
  116. /**
  117. * @param FixerInterface $fixer
  118. *
  119. * @group legacy
  120. * @dataProvider provideFixerDefinitionsCases
  121. * @expectedDeprecation PhpCsFixer\FixerDefinition\FixerDefinition::getConfigurationDescription is deprecated and will be removed in 3.0.
  122. * @expectedDeprecation PhpCsFixer\FixerDefinition\FixerDefinition::getDefaultConfiguration is deprecated and will be removed in 3.0.
  123. */
  124. public function testLegacyFixerDefinitions(FixerInterface $fixer)
  125. {
  126. $definition = $fixer->getDefinition();
  127. $this->assertNull($definition->getConfigurationDescription(), sprintf('[%s] No configuration description expected.', $fixer->getName()));
  128. $this->assertNull($definition->getDefaultConfiguration(), sprintf('[%s] No default configuration expected.', $fixer->getName()));
  129. }
  130. /**
  131. * @dataProvider provideFixerDefinitionsCases
  132. */
  133. public function testFixersAreFinal(FixerInterface $fixer)
  134. {
  135. $reflection = new \ReflectionClass($fixer);
  136. $this->assertTrue(
  137. $reflection->isFinal(),
  138. sprintf('Fixer "%s" must be declared "final".', $fixer->getName())
  139. );
  140. }
  141. /**
  142. * @dataProvider provideFixerDefinitionsCases
  143. */
  144. public function testFixersAreDefined(FixerInterface $fixer)
  145. {
  146. $this->assertInstanceOf(\PhpCsFixer\Fixer\DefinedFixerInterface::class, $fixer);
  147. }
  148. /**
  149. * @dataProvider provideFixerDefinitionsCases
  150. */
  151. public function testDeprecatedFixersHaveCorrectSummary(FixerInterface $fixer)
  152. {
  153. $reflection = new \ReflectionClass($fixer);
  154. $comment = $reflection->getDocComment();
  155. $this->assertNotContains(
  156. 'DEPRECATED',
  157. $fixer->getDefinition()->getSummary(),
  158. 'Fixer cannot contain word "DEPRECATED" in summary'
  159. );
  160. if ($fixer instanceof DeprecatedFixerInterface) {
  161. $this->assertContains('@deprecated', $comment);
  162. } elseif (is_string($comment)) {
  163. $this->assertNotContains('@deprecated', $comment);
  164. }
  165. }
  166. public function provideFixerDefinitionsCases()
  167. {
  168. return array_map(static function (FixerInterface $fixer) {
  169. return [$fixer];
  170. }, $this->getAllFixers());
  171. }
  172. /**
  173. * @param ConfigurationDefinitionFixerInterface $fixer
  174. *
  175. * @dataProvider provideFixerConfigurationDefinitionsCases
  176. */
  177. public function testFixerConfigurationDefinitions(ConfigurationDefinitionFixerInterface $fixer)
  178. {
  179. $configurationDefinition = $fixer->getConfigurationDefinition();
  180. $this->assertInstanceOf(\PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface::class, $configurationDefinition);
  181. foreach ($configurationDefinition->getOptions() as $option) {
  182. $this->assertInstanceOf(\PhpCsFixer\FixerConfiguration\FixerOption::class, $option);
  183. $this->assertNotEmpty($option->getDescription());
  184. $this->assertSame(
  185. !isset($this->allowedRequiredOptions[$fixer->getName()][$option->getName()]),
  186. $option->hasDefault(),
  187. sprintf(
  188. $option->hasDefault()
  189. ? 'Option `%s` of fixer `%s` is wrongly listed in `$allowedRequiredOptions` structure, as it is not required. If you just changed that option to not be required anymore, please adjust mentioned structure.'
  190. : 'Option `%s` of fixer `%s` shall not be required. If you want to introduce new required option please adjust `$allowedRequiredOptions` structure.',
  191. $option->getName(),
  192. $fixer->getName()
  193. )
  194. );
  195. }
  196. }
  197. public function provideFixerConfigurationDefinitionsCases()
  198. {
  199. $fixers = array_filter($this->getAllFixers(), static function (FixerInterface $fixer) {
  200. return $fixer instanceof ConfigurationDefinitionFixerInterface;
  201. });
  202. return array_map(static function (FixerInterface $fixer) {
  203. return [$fixer];
  204. }, $fixers);
  205. }
  206. private function getAllFixers()
  207. {
  208. $factory = new FixerFactory();
  209. return $factory->registerBuiltInFixers()->getFixers();
  210. }
  211. /**
  212. * copy paste from GeckoPackages/GeckoPHPUnit StringsAssertTrait, to replace with Trait when possible.
  213. *
  214. * @param mixed $actual
  215. * @param string $message
  216. */
  217. private static function assertStringIsNotEmpty($actual, $message = '')
  218. {
  219. self::assertInternalType('string', $actual, $message);
  220. self::assertNotEmpty($actual, $message);
  221. }
  222. }