PhpUnitConstructFixer.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  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\Fixer\PhpUnit;
  12. use PhpCsFixer\Fixer\AbstractPhpUnitFixer;
  13. use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface;
  14. use PhpCsFixer\FixerConfiguration\AllowedValueSubset;
  15. use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverRootless;
  16. use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
  17. use PhpCsFixer\FixerDefinition\CodeSample;
  18. use PhpCsFixer\FixerDefinition\FixerDefinition;
  19. use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
  20. use PhpCsFixer\Tokenizer\Token;
  21. use PhpCsFixer\Tokenizer\Tokens;
  22. /**
  23. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  24. */
  25. final class PhpUnitConstructFixer extends AbstractPhpUnitFixer implements ConfigurationDefinitionFixerInterface
  26. {
  27. private static $assertionFixers = [
  28. 'assertSame' => 'fixAssertPositive',
  29. 'assertEquals' => 'fixAssertPositive',
  30. 'assertNotEquals' => 'fixAssertNegative',
  31. 'assertNotSame' => 'fixAssertNegative',
  32. ];
  33. /**
  34. * {@inheritdoc}
  35. */
  36. public function isRisky()
  37. {
  38. return true;
  39. }
  40. /**
  41. * {@inheritdoc}
  42. */
  43. public function getDefinition()
  44. {
  45. return new FixerDefinition(
  46. 'PHPUnit assertion method calls like `->assertSame(true, $foo)` should be written with dedicated method like `->assertTrue($foo)`.',
  47. [
  48. new CodeSample(
  49. '<?php
  50. final class FooTest extends \PHPUnit_Framework_TestCase {
  51. public function testSomething() {
  52. $this->assertEquals(false, $b);
  53. $this->assertSame(true, $a);
  54. $this->assertNotEquals(null, $c);
  55. $this->assertNotSame(null, $d);
  56. }
  57. }
  58. '
  59. ),
  60. new CodeSample(
  61. '<?php
  62. final class FooTest extends \PHPUnit_Framework_TestCase {
  63. public function testSomething() {
  64. $this->assertEquals(false, $b);
  65. $this->assertSame(true, $a);
  66. $this->assertNotEquals(null, $c);
  67. $this->assertNotSame(null, $d);
  68. }
  69. }
  70. ',
  71. ['assertions' => ['assertSame', 'assertNotSame']]
  72. ),
  73. ],
  74. null,
  75. 'Fixer could be risky if one is overriding PHPUnit\'s native methods.'
  76. );
  77. }
  78. /**
  79. * {@inheritdoc}
  80. *
  81. * Must run before PhpUnitDedicateAssertFixer.
  82. */
  83. public function getPriority()
  84. {
  85. return -10;
  86. }
  87. /**
  88. * {@inheritdoc}
  89. */
  90. protected function applyPhpUnitClassFix(Tokens $tokens, $startIndex, $endIndex)
  91. {
  92. // no assertions to be fixed - fast return
  93. if (empty($this->configuration['assertions'])) {
  94. return;
  95. }
  96. foreach ($this->configuration['assertions'] as $assertionMethod) {
  97. $assertionFixer = self::$assertionFixers[$assertionMethod];
  98. for ($index = $startIndex; $index < $endIndex; ++$index) {
  99. $index = $this->{$assertionFixer}($tokens, $index, $assertionMethod);
  100. if (null === $index) {
  101. break;
  102. }
  103. }
  104. }
  105. }
  106. /**
  107. * {@inheritdoc}
  108. */
  109. protected function createConfigurationDefinition()
  110. {
  111. return new FixerConfigurationResolverRootless('assertions', [
  112. (new FixerOptionBuilder('assertions', 'List of assertion methods to fix.'))
  113. ->setAllowedTypes(['array'])
  114. ->setAllowedValues([new AllowedValueSubset(array_keys(self::$assertionFixers))])
  115. ->setDefault([
  116. 'assertEquals',
  117. 'assertSame',
  118. 'assertNotEquals',
  119. 'assertNotSame',
  120. ])
  121. ->getOption(),
  122. ], $this->getName());
  123. }
  124. /**
  125. * @param int $index
  126. * @param string $method
  127. *
  128. * @return null|int
  129. */
  130. private function fixAssertNegative(Tokens $tokens, $index, $method)
  131. {
  132. static $map = [
  133. 'false' => 'assertNotFalse',
  134. 'null' => 'assertNotNull',
  135. 'true' => 'assertNotTrue',
  136. ];
  137. return $this->fixAssert($map, $tokens, $index, $method);
  138. }
  139. /**
  140. * @param int $index
  141. * @param string $method
  142. *
  143. * @return null|int
  144. */
  145. private function fixAssertPositive(Tokens $tokens, $index, $method)
  146. {
  147. static $map = [
  148. 'false' => 'assertFalse',
  149. 'null' => 'assertNull',
  150. 'true' => 'assertTrue',
  151. ];
  152. return $this->fixAssert($map, $tokens, $index, $method);
  153. }
  154. /**
  155. * @param array<string, string> $map
  156. * @param int $index
  157. * @param string $method
  158. *
  159. * @return null|int
  160. */
  161. private function fixAssert(array $map, Tokens $tokens, $index, $method)
  162. {
  163. $functionsAnalyzer = new FunctionsAnalyzer();
  164. $sequence = $tokens->findSequence(
  165. [
  166. [T_STRING, $method],
  167. '(',
  168. ],
  169. $index
  170. );
  171. if (null === $sequence) {
  172. return null;
  173. }
  174. $sequenceIndexes = array_keys($sequence);
  175. if (!$functionsAnalyzer->isTheSameClassCall($tokens, $sequenceIndexes[0])) {
  176. return null;
  177. }
  178. $sequenceIndexes[2] = $tokens->getNextMeaningfulToken($sequenceIndexes[1]);
  179. $firstParameterToken = $tokens[$sequenceIndexes[2]];
  180. if (!$firstParameterToken->isNativeConstant()) {
  181. return $sequenceIndexes[2];
  182. }
  183. $sequenceIndexes[3] = $tokens->getNextMeaningfulToken($sequenceIndexes[2]);
  184. // return if first method argument is an expression, not value
  185. if (!$tokens[$sequenceIndexes[3]]->equals(',')) {
  186. return $sequenceIndexes[3];
  187. }
  188. $tokens[$sequenceIndexes[0]] = new Token([T_STRING, $map[strtolower($firstParameterToken->getContent())]]);
  189. $tokens->clearRange($sequenceIndexes[2], $tokens->getNextNonWhitespace($sequenceIndexes[3]) - 1);
  190. return $sequenceIndexes[3];
  191. }
  192. }