NoUnneededBracesFixer.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  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\Fixer\ControlStructure;
  13. use PhpCsFixer\AbstractFixer;
  14. use PhpCsFixer\Fixer\ConfigurableFixerInterface;
  15. use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
  16. use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
  17. use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
  18. use PhpCsFixer\FixerDefinition\CodeSample;
  19. use PhpCsFixer\FixerDefinition\FixerDefinition;
  20. use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
  21. use PhpCsFixer\Tokenizer\Token;
  22. use PhpCsFixer\Tokenizer\Tokens;
  23. final class NoUnneededBracesFixer extends AbstractFixer implements ConfigurableFixerInterface
  24. {
  25. public function getDefinition(): FixerDefinitionInterface
  26. {
  27. return new FixerDefinition(
  28. 'Removes unneeded braces that are superfluous and aren\'t part of a control structure\'s body.',
  29. [
  30. new CodeSample(
  31. '<?php {
  32. echo 1;
  33. }
  34. switch ($b) {
  35. case 1: {
  36. break;
  37. }
  38. }
  39. '
  40. ),
  41. new CodeSample(
  42. '<?php
  43. namespace Foo {
  44. function Bar(){}
  45. }
  46. ',
  47. ['namespaces' => true]
  48. ),
  49. ]
  50. );
  51. }
  52. /**
  53. * {@inheritdoc}
  54. *
  55. * Must run before NoUselessElseFixer, NoUselessReturnFixer, ReturnAssignmentFixer, SimplifiedIfReturnFixer.
  56. */
  57. public function getPriority(): int
  58. {
  59. return 40;
  60. }
  61. public function isCandidate(Tokens $tokens): bool
  62. {
  63. return $tokens->isTokenKindFound('}');
  64. }
  65. protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
  66. {
  67. foreach ($this->findBraceOpen($tokens) as $index) {
  68. if ($this->isOverComplete($tokens, $index)) {
  69. $this->clearOverCompleteBraces($tokens, $index, $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index));
  70. }
  71. }
  72. if (true === $this->configuration['namespaces']) {
  73. $this->clearIfIsOverCompleteNamespaceBlock($tokens);
  74. }
  75. }
  76. protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
  77. {
  78. return new FixerConfigurationResolver([
  79. (new FixerOptionBuilder('namespaces', 'Remove unneeded braces from bracketed namespaces.'))
  80. ->setAllowedTypes(['bool'])
  81. ->setDefault(false)
  82. ->getOption(),
  83. ]);
  84. }
  85. /**
  86. * @param int $openIndex index of `{` token
  87. * @param int $closeIndex index of `}` token
  88. */
  89. private function clearOverCompleteBraces(Tokens $tokens, int $openIndex, int $closeIndex): void
  90. {
  91. $tokens->clearTokenAndMergeSurroundingWhitespace($closeIndex);
  92. $tokens->clearTokenAndMergeSurroundingWhitespace($openIndex);
  93. }
  94. /**
  95. * @return iterable<int>
  96. */
  97. private function findBraceOpen(Tokens $tokens): iterable
  98. {
  99. for ($i = \count($tokens) - 1; $i > 0; --$i) {
  100. if ($tokens[$i]->equals('{')) {
  101. yield $i;
  102. }
  103. }
  104. }
  105. /**
  106. * @param int $index index of `{` token
  107. */
  108. private function isOverComplete(Tokens $tokens, int $index): bool
  109. {
  110. static $include = ['{', '}', [T_OPEN_TAG], ':', ';'];
  111. return $tokens[$tokens->getPrevMeaningfulToken($index)]->equalsAny($include);
  112. }
  113. private function clearIfIsOverCompleteNamespaceBlock(Tokens $tokens): void
  114. {
  115. if (1 !== $tokens->countTokenKind(T_NAMESPACE)) {
  116. return; // fast check, we never fix if multiple namespaces are defined
  117. }
  118. $index = $tokens->getNextTokenOfKind(0, [[T_NAMESPACE]]);
  119. $namespaceIsNamed = false;
  120. $index = $tokens->getNextMeaningfulToken($index);
  121. while ($tokens[$index]->isGivenKind([T_STRING, T_NS_SEPARATOR])) {
  122. $index = $tokens->getNextMeaningfulToken($index);
  123. $namespaceIsNamed = true;
  124. }
  125. if (!$namespaceIsNamed) {
  126. return;
  127. }
  128. if (!$tokens[$index]->equals('{')) {
  129. return; // `;`
  130. }
  131. $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
  132. $afterCloseIndex = $tokens->getNextMeaningfulToken($closeIndex);
  133. if (null !== $afterCloseIndex && (!$tokens[$afterCloseIndex]->isGivenKind(T_CLOSE_TAG) || null !== $tokens->getNextMeaningfulToken($afterCloseIndex))) {
  134. return;
  135. }
  136. // clear up
  137. $tokens->clearTokenAndMergeSurroundingWhitespace($closeIndex);
  138. $tokens[$index] = new Token(';');
  139. if ($tokens[$index - 1]->isWhitespace(" \t") && !$tokens[$index - 2]->isComment()) {
  140. $tokens->clearTokenAndMergeSurroundingWhitespace($index - 1);
  141. }
  142. }
  143. }