NoTrailingCommaInSinglelineFixer.php 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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\Basic;
  13. use PhpCsFixer\AbstractFixer;
  14. use PhpCsFixer\Fixer\ConfigurableFixerInterface;
  15. use PhpCsFixer\Fixer\ConfigurableFixerTrait;
  16. use PhpCsFixer\FixerConfiguration\AllowedValueSubset;
  17. use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
  18. use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
  19. use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
  20. use PhpCsFixer\FixerDefinition\CodeSample;
  21. use PhpCsFixer\FixerDefinition\FixerDefinition;
  22. use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
  23. use PhpCsFixer\Tokenizer\Analyzer\AttributeAnalyzer;
  24. use PhpCsFixer\Tokenizer\CT;
  25. use PhpCsFixer\Tokenizer\Tokens;
  26. /**
  27. * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
  28. *
  29. * @phpstan-type _AutogeneratedInputConfiguration array{
  30. * elements?: list<'arguments'|'array'|'array_destructuring'|'group_import'>
  31. * }
  32. * @phpstan-type _AutogeneratedComputedConfiguration array{
  33. * elements: list<'arguments'|'array'|'array_destructuring'|'group_import'>
  34. * }
  35. */
  36. final class NoTrailingCommaInSinglelineFixer extends AbstractFixer implements ConfigurableFixerInterface
  37. {
  38. /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
  39. use ConfigurableFixerTrait;
  40. public function getDefinition(): FixerDefinitionInterface
  41. {
  42. return new FixerDefinition(
  43. 'If a list of values separated by a comma is contained on a single line, then the last item MUST NOT have a trailing comma.',
  44. [
  45. new CodeSample("<?php\nfoo(\$a,);\n\$foo = array(1,);\n[\$foo, \$bar,] = \$array;\nuse a\\{ClassA, ClassB,};\n"),
  46. new CodeSample("<?php\nfoo(\$a,);\n[\$foo, \$bar,] = \$array;\n", ['elements' => ['array_destructuring']]),
  47. ]
  48. );
  49. }
  50. public function isCandidate(Tokens $tokens): bool
  51. {
  52. return
  53. $tokens->isTokenKindFound(',')
  54. && $tokens->isAnyTokenKindsFound([')', CT::T_ARRAY_SQUARE_BRACE_CLOSE, CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, CT::T_GROUP_IMPORT_BRACE_CLOSE]);
  55. }
  56. protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
  57. {
  58. $elements = ['arguments', 'array_destructuring', 'array', 'group_import'];
  59. return new FixerConfigurationResolver([
  60. (new FixerOptionBuilder('elements', 'Which elements to fix.'))
  61. ->setAllowedTypes(['string[]'])
  62. ->setAllowedValues([new AllowedValueSubset($elements)])
  63. ->setDefault($elements)
  64. ->getOption(),
  65. ]);
  66. }
  67. protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
  68. {
  69. for ($index = $tokens->count() - 1; $index >= 0; --$index) {
  70. if (!$tokens[$index]->equals(')') && !$tokens[$index]->isGivenKind([CT::T_ARRAY_SQUARE_BRACE_CLOSE, CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, CT::T_GROUP_IMPORT_BRACE_CLOSE])) {
  71. continue;
  72. }
  73. $commaIndex = $tokens->getPrevMeaningfulToken($index);
  74. if (!$tokens[$commaIndex]->equals(',')) {
  75. continue;
  76. }
  77. $block = Tokens::detectBlockType($tokens[$index]);
  78. $blockOpenIndex = $tokens->findBlockStart($block['type'], $index);
  79. if ($tokens->isPartialCodeMultiline($blockOpenIndex, $index)) {
  80. continue;
  81. }
  82. if (!$this->shouldBeCleared($tokens, $blockOpenIndex)) {
  83. continue;
  84. }
  85. do {
  86. $tokens->clearTokenAndMergeSurroundingWhitespace($commaIndex);
  87. $commaIndex = $tokens->getPrevMeaningfulToken($commaIndex);
  88. } while ($tokens[$commaIndex]->equals(','));
  89. $tokens->removeTrailingWhitespace($commaIndex);
  90. }
  91. }
  92. private function shouldBeCleared(Tokens $tokens, int $openIndex): bool
  93. {
  94. $elements = $this->configuration['elements'];
  95. if ($tokens[$openIndex]->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) {
  96. return \in_array('array', $elements, true);
  97. }
  98. if ($tokens[$openIndex]->isGivenKind(CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN)) {
  99. return \in_array('array_destructuring', $elements, true);
  100. }
  101. if ($tokens[$openIndex]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_OPEN)) {
  102. return \in_array('group_import', $elements, true);
  103. }
  104. if (!$tokens[$openIndex]->equals('(')) {
  105. return false;
  106. }
  107. $beforeOpen = $tokens->getPrevMeaningfulToken($openIndex);
  108. if ($tokens[$beforeOpen]->isGivenKind(T_ARRAY)) {
  109. return \in_array('array', $elements, true);
  110. }
  111. if ($tokens[$beforeOpen]->isGivenKind(T_LIST)) {
  112. return \in_array('array_destructuring', $elements, true);
  113. }
  114. if ($tokens[$beforeOpen]->isGivenKind([T_UNSET, T_ISSET, T_VARIABLE, T_CLASS])) {
  115. return \in_array('arguments', $elements, true);
  116. }
  117. if ($tokens[$beforeOpen]->isGivenKind(T_STRING)) {
  118. return !AttributeAnalyzer::isAttribute($tokens, $beforeOpen) && \in_array('arguments', $elements, true);
  119. }
  120. if ($tokens[$beforeOpen]->equalsAny([')', ']', [CT::T_DYNAMIC_VAR_BRACE_CLOSE], [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE]])) {
  121. $block = Tokens::detectBlockType($tokens[$beforeOpen]);
  122. return
  123. (
  124. Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE === $block['type']
  125. || Tokens::BLOCK_TYPE_DYNAMIC_VAR_BRACE === $block['type']
  126. || Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE === $block['type']
  127. || Tokens::BLOCK_TYPE_PARENTHESIS_BRACE === $block['type']
  128. ) && \in_array('arguments', $elements, true);
  129. }
  130. return false;
  131. }
  132. }