PhpUnitNamespacedFixer.php 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  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\AbstractFixer;
  13. use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface;
  14. use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
  15. use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
  16. use PhpCsFixer\FixerDefinition\CodeSample;
  17. use PhpCsFixer\FixerDefinition\FixerDefinition;
  18. use PhpCsFixer\Tokenizer\Token;
  19. use PhpCsFixer\Tokenizer\Tokens;
  20. /**
  21. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  22. */
  23. final class PhpUnitNamespacedFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface
  24. {
  25. /**
  26. * @var string
  27. */
  28. private $originalClassRegEx;
  29. /**
  30. * {@inheritdoc}
  31. */
  32. public function getDefinition()
  33. {
  34. return new FixerDefinition(
  35. 'PHPUnit classes MUST be used in namespaced version, eg `\PHPUnit\Framework\TestCase` instead of `\PHPUnit_Framework_TestCase`.',
  36. [
  37. new CodeSample(
  38. '<?php
  39. final class MyTest extends \PHPUnit_Framework_TestCase
  40. {
  41. }
  42. '
  43. ),
  44. ],
  45. "PHPUnit v6 has finally fully switched to namespaces.\n"
  46. ."You could start preparing the upgrade by switching from non-namespaced TestCase to namespaced one.\n"
  47. .'Forward compatibility layer (`\PHPUnit\Framework\TestCase` class) was backported to PHPUnit v4.8.35 and PHPUnit v5.4.0.'."\n"
  48. .'Extended forward compatibility layer (`PHPUnit\Framework\Assert`, `PHPUnit\Framework\BaseTestListener`, `PHPUnit\Framework\TestListener` classes) was introduced in v5.7.0.'."\n",
  49. 'Risky when PHPUnit classes are overridden or not accessible, or when project has PHPUnit incompatibilities.'
  50. );
  51. }
  52. /**
  53. * {@inheritdoc}
  54. */
  55. public function isCandidate(Tokens $tokens)
  56. {
  57. return $tokens->isTokenKindFound(T_CLASS);
  58. }
  59. /**
  60. * {@inheritdoc}
  61. */
  62. public function isRisky()
  63. {
  64. return true;
  65. }
  66. /**
  67. * {@inheritdoc}
  68. */
  69. public function configure(array $configuration = null)
  70. {
  71. parent::configure($configuration);
  72. if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_6_0)) {
  73. $this->originalClassRegEx = '/^PHPUnit_\w+$/i';
  74. } elseif (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_7)) {
  75. $this->originalClassRegEx = '/^PHPUnit_Framework_TestCase|PHPUnit_Framework_Assert|PHPUnit_Framework_BaseTestListener|PHPUnit_Framework_TestListener$/i';
  76. } else {
  77. $this->originalClassRegEx = '/^PHPUnit_Framework_TestCase$/i';
  78. }
  79. }
  80. /**
  81. * {@inheritdoc}
  82. */
  83. protected function applyFix(\SplFileInfo $file, Tokens $tokens)
  84. {
  85. $importedOriginalClassesMap = [];
  86. $currIndex = 0;
  87. while (null !== $currIndex) {
  88. $match = $tokens->findSequence([[T_STRING]], $currIndex);
  89. if (null === $match) {
  90. break;
  91. }
  92. $matchIndexes = array_keys($match);
  93. $currIndex = $matchIndexes[0];
  94. $originalClass = $match[$currIndex]->getContent();
  95. if (1 !== preg_match($this->originalClassRegEx, $originalClass)) {
  96. ++$currIndex;
  97. continue;
  98. }
  99. $substituteTokens = $this->generateReplacement($originalClass);
  100. $tokens->clearAt($currIndex);
  101. $tokens->insertAt(
  102. $currIndex,
  103. isset($importedOriginalClassesMap[$originalClass]) ? $substituteTokens[$substituteTokens->getSize() - 1] : $substituteTokens
  104. );
  105. $prevIndex = $tokens->getPrevMeaningfulToken($currIndex);
  106. if ($tokens[$prevIndex]->isGivenKind(T_USE)) {
  107. $importedOriginalClassesMap[$originalClass] = true;
  108. } elseif ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) {
  109. $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex);
  110. $importedOriginalClassesMap[$originalClass] = $tokens[$prevIndex]->isGivenKind(T_USE);
  111. }
  112. }
  113. }
  114. /**
  115. * {@inheritdoc}
  116. */
  117. protected function createConfigurationDefinition()
  118. {
  119. return new FixerConfigurationResolver([
  120. (new FixerOptionBuilder('target', 'Target version of PHPUnit.'))
  121. ->setAllowedTypes(['string'])
  122. ->setAllowedValues([PhpUnitTargetVersion::VERSION_4_8, PhpUnitTargetVersion::VERSION_5_7, PhpUnitTargetVersion::VERSION_6_0, PhpUnitTargetVersion::VERSION_NEWEST])
  123. ->setDefault(PhpUnitTargetVersion::VERSION_NEWEST)
  124. ->getOption(),
  125. ]);
  126. }
  127. /**
  128. * @param string $originalClassName
  129. *
  130. * @return Tokens
  131. */
  132. private function generateReplacement($originalClassName)
  133. {
  134. $parts = explode('_', $originalClassName);
  135. $tokensArray = [];
  136. while (!empty($parts)) {
  137. $tokensArray[] = new Token([T_STRING, array_shift($parts)]);
  138. if (!empty($parts)) {
  139. $tokensArray[] = new Token([T_NS_SEPARATOR, '\\']);
  140. }
  141. }
  142. return Tokens::fromArray($tokensArray);
  143. }
  144. }