SingleImportPerStatementFixer.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  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\Import;
  12. use PhpCsFixer\AbstractFixer;
  13. use PhpCsFixer\Tokenizer\Token;
  14. use PhpCsFixer\Tokenizer\Tokens;
  15. use PhpCsFixer\Tokenizer\TokensAnalyzer;
  16. /**
  17. * Fixer for rules defined in PSR2 ¶3.
  18. *
  19. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  20. * @author SpacePossum
  21. */
  22. final class SingleImportPerStatementFixer extends AbstractFixer
  23. {
  24. /**
  25. * {@inheritdoc}
  26. */
  27. public function isCandidate(Tokens $tokens)
  28. {
  29. return $tokens->isTokenKindFound(T_USE);
  30. }
  31. /**
  32. * {@inheritdoc}
  33. */
  34. public function fix(\SplFileInfo $file, Tokens $tokens)
  35. {
  36. $tokensAnalyzer = new TokensAnalyzer($tokens);
  37. $uses = array_reverse($tokensAnalyzer->getImportUseIndexes());
  38. foreach ($uses as $index) {
  39. $endIndex = $tokens->getNextTokenOfKind($index, array(';', array(T_CLOSE_TAG)));
  40. $groupClose = $tokens->getPrevMeaningfulToken($endIndex);
  41. if ($tokens[$groupClose]->isGivenKind(CT_GROUP_IMPORT_BRACE_CLOSE)) {
  42. $this->fixGroupUse($tokens, $index, $endIndex);
  43. } else {
  44. $this->fixMultipleUse($tokens, $index, $endIndex);
  45. }
  46. }
  47. }
  48. /**
  49. * {@inheritdoc}
  50. */
  51. public function getDescription()
  52. {
  53. return 'There MUST be one use keyword per declaration.';
  54. }
  55. public function getPriority()
  56. {
  57. // must be run before NoLeadingImportSlashFixer, NoSinglelineWhitespaceBeforeSemicolonsFixer, SpaceAfterSemicolonFixer, NoMultilineWhitespaceBeforeSemicolonsFixer, NoLeadingImportSlashFixer.
  58. return 1;
  59. }
  60. /**
  61. * @param Tokens $tokens
  62. * @param int $index
  63. *
  64. * @return string
  65. */
  66. private function detectIndent(Tokens $tokens, $index)
  67. {
  68. if (!$tokens[$index - 1]->isWhitespace()) {
  69. return ''; // cannot detect indent
  70. }
  71. $explodedContent = explode("\n", $tokens[$index - 1]->getContent());
  72. return end($explodedContent);
  73. }
  74. /**
  75. * @param Tokens $tokens
  76. * @param int $index
  77. *
  78. * @return array
  79. */
  80. private function getGroupDeclaration(Tokens $tokens, $index)
  81. {
  82. $groupPrefix = 'use';
  83. $comment = '';
  84. for ($i = $index + 1; ; ++$i) {
  85. if ($tokens[$i]->isGivenKind(CT_GROUP_IMPORT_BRACE_OPEN)) {
  86. $groupOpenIndex = $i;
  87. break;
  88. }
  89. if ($tokens[$i]->isComment()) {
  90. $comment .= $tokens[$i]->getContent();
  91. if (!$tokens[$i - 1]->isWhitespace() && !$tokens[$i + 1]->isWhitespace()) {
  92. $groupPrefix .= ' ';
  93. }
  94. continue;
  95. }
  96. if ($tokens[$i]->isWhitespace()) {
  97. $groupPrefix .= ' ';
  98. continue;
  99. }
  100. $groupPrefix .= $tokens[$i]->getContent();
  101. }
  102. return array(
  103. $groupPrefix,
  104. $groupOpenIndex,
  105. $tokens->findBlockEnd(Tokens::BLOCK_TYPE_GROUP_IMPORT_BRACE, $groupOpenIndex),
  106. $comment,
  107. );
  108. }
  109. /**
  110. * @param Tokens $tokens
  111. * @param string $groupPrefix
  112. * @param int $groupOpenIndex
  113. * @param int $groupCloseIndex
  114. * @param string $comment
  115. *
  116. * @return string[]
  117. */
  118. private function getGroupStatements(Tokens $tokens, $groupPrefix, $groupOpenIndex, $groupCloseIndex, $comment)
  119. {
  120. $statements = array();
  121. $statement = $groupPrefix;
  122. for ($i = $groupOpenIndex + 1; $i <= $groupCloseIndex; ++$i) {
  123. $token = $tokens[$i];
  124. if ($token->equalsAny(array(',', array(CT_GROUP_IMPORT_BRACE_CLOSE)))) {
  125. $statements[] = $statement.';';
  126. $statement = $groupPrefix;
  127. continue;
  128. }
  129. if ($token->isWhitespace()) {
  130. $j = $tokens->getNextMeaningfulToken($i);
  131. if ($tokens[$j]->equals(array(T_AS))) {
  132. $statement .= ' as ';
  133. $i += 2;
  134. }
  135. if ($token->isWhitespace(" \t") || '//' !== substr($tokens[$i - 1]->getContent(), 0, 2)) {
  136. continue;
  137. }
  138. }
  139. $statement .= $token->getContent();
  140. }
  141. if ('' !== $comment) {
  142. $statements[0] .= ' '.$comment;
  143. }
  144. return $statements;
  145. }
  146. private function fixGroupUse(Tokens $tokens, $index, $endIndex)
  147. {
  148. list($groupPrefix, $groupOpenIndex, $groupCloseIndex, $comment) = $this->getGroupDeclaration($tokens, $index);
  149. $statements = $this->getGroupStatements($tokens, $groupPrefix, $groupOpenIndex, $groupCloseIndex, $comment);
  150. if (count($statements) < 2) {
  151. return;
  152. }
  153. $tokens->clearRange($index, $groupCloseIndex);
  154. if ($tokens[$endIndex]->equals(';')) {
  155. $tokens[$endIndex]->clear();
  156. }
  157. $importTokens = Tokens::fromCode('<?php '.implode("\n", $statements));
  158. $importTokens[0]->clear();
  159. $importTokens->clearEmptyTokens();
  160. $tokens->insertAt($index, $importTokens);
  161. }
  162. private function fixMultipleUse(Tokens $tokens, $index, $endIndex)
  163. {
  164. for ($i = $endIndex - 1; $i > $index; --$i) {
  165. if (!$tokens[$i]->equals(',')) {
  166. continue;
  167. }
  168. $tokens->overrideAt($i, new Token(';'));
  169. $i = $tokens->getNextMeaningfulToken($i);
  170. $tokens->insertAt($i, new Token(array(T_USE, 'use')));
  171. $tokens->insertAt($i + 1, new Token(array(T_WHITESPACE, ' ')));
  172. $indent = $this->detectIndent($tokens, $index);
  173. if ($tokens[$i - 1]->isWhitespace()) {
  174. $tokens[$i - 1]->setContent("\n".$indent);
  175. continue;
  176. }
  177. if (false === strpos($tokens[$i - 1]->getContent(), "\n")) {
  178. $tokens->insertAt($i, new Token(array(T_WHITESPACE, "\n".$indent)));
  179. }
  180. }
  181. }
  182. }