From 5b36241fa8638780e43a4ba6d2769c3b6900d07e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Wer=C5=82os?= Date: Sat, 1 May 2021 00:04:50 +0200 Subject: [PATCH] TrailingCommaInMultilineFixer - introduction --- doc/ruleSets/PHP73Migration.rst | 2 +- doc/ruleSets/Symfony.rst | 2 +- .../trailing_comma_in_multiline_array.rst | 30 +- .../trailing_comma_in_multiline.rst | 125 ++++ doc/rules/index.rst | 4 +- ...tilineWhitespaceAroundDoubleArrowFixer.php | 2 +- .../TrailingCommaInMultilineArrayFixer.php | 97 +-- .../TrailingCommaInMultilineFixer.php | 219 ++++++ src/RuleSet/Sets/PHP73MigrationSet.php | 2 +- src/RuleSet/Sets/SymfonySet.php | 2 +- src/Tokenizer/TokensAnalyzer.php | 21 +- tests/AutoReview/FixerFactoryTest.php | 2 +- .../TrailingCommaInMultilineFixerTest.php | 634 ++++++++++++++++++ ...le_arrow,trailing_comma_in_multiline.test} | 4 +- tests/Tokenizer/TokensAnalyzerTest.php | 58 ++ 15 files changed, 1095 insertions(+), 109 deletions(-) create mode 100644 doc/rules/control_structure/trailing_comma_in_multiline.rst create mode 100644 src/Fixer/ControlStructure/TrailingCommaInMultilineFixer.php create mode 100644 tests/Fixer/ControlStructure/TrailingCommaInMultilineFixerTest.php rename tests/Fixtures/Integration/priority/{no_multiline_whitespace_around_double_arrow,trailing_comma_in_multiline_array.test => no_multiline_whitespace_around_double_arrow,trailing_comma_in_multiline.test} (75%) diff --git a/doc/ruleSets/PHP73Migration.rst b/doc/ruleSets/PHP73Migration.rst index da38a526b97..1d4403e2839 100644 --- a/doc/ruleSets/PHP73Migration.rst +++ b/doc/ruleSets/PHP73Migration.rst @@ -15,6 +15,6 @@ Rules - `no_whitespace_before_comma_in_array <./../rules/array_notation/no_whitespace_before_comma_in_array.rst>`_ config: ``['after_heredoc' => true]`` -- `trailing_comma_in_multiline_array <./../rules/array_notation/trailing_comma_in_multiline_array.rst>`_ +- `trailing_comma_in_multiline <./../rules/control_structure/trailing_comma_in_multiline.rst>`_ config: ``['after_heredoc' => true]`` diff --git a/doc/ruleSets/Symfony.rst b/doc/ruleSets/Symfony.rst index 19502a6e948..80d6f7e1401 100644 --- a/doc/ruleSets/Symfony.rst +++ b/doc/ruleSets/Symfony.rst @@ -119,7 +119,7 @@ Rules - `standardize_increment <./../rules/operator/standardize_increment.rst>`_ - `standardize_not_equals <./../rules/operator/standardize_not_equals.rst>`_ - `switch_continue_to_break <./../rules/control_structure/switch_continue_to_break.rst>`_ -- `trailing_comma_in_multiline_array <./../rules/array_notation/trailing_comma_in_multiline_array.rst>`_ +- `trailing_comma_in_multiline <./../rules/control_structure/trailing_comma_in_multiline.rst>`_ - `trim_array_spaces <./../rules/array_notation/trim_array_spaces.rst>`_ - `unary_operator_spaces <./../rules/operator/unary_operator_spaces.rst>`_ - `visibility_required <./../rules/class_notation/visibility_required.rst>`_ diff --git a/doc/rules/array_notation/trailing_comma_in_multiline_array.rst b/doc/rules/array_notation/trailing_comma_in_multiline_array.rst index 4f3bc14eee4..32f802097e3 100644 --- a/doc/rules/array_notation/trailing_comma_in_multiline_array.rst +++ b/doc/rules/array_notation/trailing_comma_in_multiline_array.rst @@ -2,6 +2,10 @@ Rule ``trailing_comma_in_multiline_array`` ========================================== +.. warning:: This rule is deprecated and will be removed on next major version. + + You should use ``trailing_comma_in_multiline`` instead. + PHP multi-line arrays should have a trailing comma. Configuration @@ -52,29 +56,3 @@ With configuration: ``['after_heredoc' => true]``. - EOD + EOD, ]; - -Rule sets ---------- - -The rule is part of the following rule sets: - -@PHP73Migration - Using the `@PHP73Migration <./../../ruleSets/PHP73Migration.rst>`_ rule set will enable the ``trailing_comma_in_multiline_array`` rule with the config below: - - ``['after_heredoc' => true]`` - -@PHP74Migration - Using the `@PHP74Migration <./../../ruleSets/PHP74Migration.rst>`_ rule set will enable the ``trailing_comma_in_multiline_array`` rule with the config below: - - ``['after_heredoc' => true]`` - -@PHP80Migration - Using the `@PHP80Migration <./../../ruleSets/PHP80Migration.rst>`_ rule set will enable the ``trailing_comma_in_multiline_array`` rule with the config below: - - ``['after_heredoc' => true]`` - -@PhpCsFixer - Using the `@PhpCsFixer <./../../ruleSets/PhpCsFixer.rst>`_ rule set will enable the ``trailing_comma_in_multiline_array`` rule with the default config. - -@Symfony - Using the `@Symfony <./../../ruleSets/Symfony.rst>`_ rule set will enable the ``trailing_comma_in_multiline_array`` rule with the default config. diff --git a/doc/rules/control_structure/trailing_comma_in_multiline.rst b/doc/rules/control_structure/trailing_comma_in_multiline.rst new file mode 100644 index 00000000000..ca12d7d94e8 --- /dev/null +++ b/doc/rules/control_structure/trailing_comma_in_multiline.rst @@ -0,0 +1,125 @@ +==================================== +Rule ``trailing_comma_in_multiline`` +==================================== + +Multi-line arrays, arguments list and parameters list must have a trailing +comma. + +Configuration +------------- + +``after_heredoc`` +~~~~~~~~~~~~~~~~~ + +Whether a trailing comma should also be placed after heredoc end. + +Allowed types: ``bool`` + +Default value: ``false`` + +``elements`` +~~~~~~~~~~~~ + +Where to fix multiline trailing comma (PHP >= 7.3 required for ``arguments``, +PHP >= 8.0 for ``parameters``). + +Allowed values: a subset of ``['arrays', 'arguments', 'parameters']`` + +Default value: ``['arrays']`` + +Examples +-------- + +Example #1 +~~~~~~~~~~ + +*Default* configuration. + +.. code-block:: diff + + --- Original + +++ New + true]``. + +.. code-block:: diff + + --- Original + +++ New + ['arguments']]``. + +.. code-block:: diff + + --- Original + +++ New + ['parameters']]``. + +.. code-block:: diff + + --- Original + +++ New + `_ rule set will enable the ``trailing_comma_in_multiline`` rule with the config below: + + ``['after_heredoc' => true]`` + +@PHP74Migration + Using the `@PHP74Migration <./../../ruleSets/PHP74Migration.rst>`_ rule set will enable the ``trailing_comma_in_multiline`` rule with the config below: + + ``['after_heredoc' => true]`` + +@PHP80Migration + Using the `@PHP80Migration <./../../ruleSets/PHP80Migration.rst>`_ rule set will enable the ``trailing_comma_in_multiline`` rule with the config below: + + ``['after_heredoc' => true]`` + +@PhpCsFixer + Using the `@PhpCsFixer <./../../ruleSets/PhpCsFixer.rst>`_ rule set will enable the ``trailing_comma_in_multiline`` rule with the default config. + +@Symfony + Using the `@Symfony <./../../ruleSets/Symfony.rst>`_ rule set will enable the ``trailing_comma_in_multiline`` rule with the default config. diff --git a/doc/rules/index.rst b/doc/rules/index.rst index 2f4f74c8421..b4dd904671d 100644 --- a/doc/rules/index.rst +++ b/doc/rules/index.rst @@ -39,7 +39,7 @@ Array Notation In array declaration, there MUST NOT be a whitespace before each comma. - `normalize_index_brace <./array_notation/normalize_index_brace.rst>`_ Array index should always be written by using square braces. -- `trailing_comma_in_multiline_array <./array_notation/trailing_comma_in_multiline_array.rst>`_ +- `trailing_comma_in_multiline_array <./array_notation/trailing_comma_in_multiline_array.rst>`_ *(deprecated)* PHP multi-line arrays should have a trailing comma. - `trim_array_spaces <./array_notation/trim_array_spaces.rst>`_ Arrays should be formatted like function/method arguments, without leading or trailing single line space. @@ -201,6 +201,8 @@ Control Structure Removes extra spaces between colon and case value. - `switch_continue_to_break <./control_structure/switch_continue_to_break.rst>`_ Switch case must not be ended with ``continue`` but with ``break``. +- `trailing_comma_in_multiline <./control_structure/trailing_comma_in_multiline.rst>`_ + Multi-line arrays, arguments list and parameters list must have a trailing comma. - `yoda_style <./control_structure/yoda_style.rst>`_ Write conditions in Yoda style (``true``), non-Yoda style (``['equal' => false, 'identical' => false, 'less_and_greater' => false]``) or ignore those conditions (``null``) based on configuration. diff --git a/src/Fixer/ArrayNotation/NoMultilineWhitespaceAroundDoubleArrowFixer.php b/src/Fixer/ArrayNotation/NoMultilineWhitespaceAroundDoubleArrowFixer.php index cd24bf8ee4a..ba0a4f0cf0c 100644 --- a/src/Fixer/ArrayNotation/NoMultilineWhitespaceAroundDoubleArrowFixer.php +++ b/src/Fixer/ArrayNotation/NoMultilineWhitespaceAroundDoubleArrowFixer.php @@ -39,7 +39,7 @@ public function getDefinition() /** * {@inheritdoc} * - * Must run before BinaryOperatorSpacesFixer, TrailingCommaInMultilineArrayFixer. + * Must run before BinaryOperatorSpacesFixer, TrailingCommaInMultilineFixer. */ public function getPriority() { diff --git a/src/Fixer/ArrayNotation/TrailingCommaInMultilineArrayFixer.php b/src/Fixer/ArrayNotation/TrailingCommaInMultilineArrayFixer.php index 41c14099372..9b2fa4dc800 100644 --- a/src/Fixer/ArrayNotation/TrailingCommaInMultilineArrayFixer.php +++ b/src/Fixer/ArrayNotation/TrailingCommaInMultilineArrayFixer.php @@ -12,27 +12,29 @@ namespace PhpCsFixer\Fixer\ArrayNotation; -use PhpCsFixer\AbstractFixer; +use PhpCsFixer\AbstractProxyFixer; use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\ControlStructure\TrailingCommaInMultilineFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; -use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; -use PhpCsFixer\FixerConfiguration\InvalidOptionsForEnvException; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\VersionSpecification; use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; -use PhpCsFixer\Tokenizer\CT; -use PhpCsFixer\Tokenizer\Token; -use PhpCsFixer\Tokenizer\Tokens; -use PhpCsFixer\Tokenizer\TokensAnalyzer; -use Symfony\Component\OptionsResolver\Options; /** * @author Sebastiaan Stok * @author Dariusz Rumiński + * + * @deprecated */ -final class TrailingCommaInMultilineArrayFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +final class TrailingCommaInMultilineArrayFixer extends AbstractProxyFixer implements ConfigurationDefinitionFixerInterface, DeprecatedFixerInterface { + /** + * @var TrailingCommaInMultilineFixer + */ + private $fixer; + /** * {@inheritdoc} */ @@ -61,87 +63,42 @@ public function getDefinition() ); } - /** - * {@inheritdoc} - * - * Must run after NoMultilineWhitespaceAroundDoubleArrowFixer. - */ - public function getPriority() + public function configure(array $configuration = null) { - return 0; + $configuration['elements'] = [TrailingCommaInMultilineFixer::ELEMENTS_ARRAYS]; + $this->getFixer()->configure($configuration); + $this->configuration = $configuration; } - /** - * {@inheritdoc} - */ - public function isCandidate(Tokens $tokens) + public function getConfigurationDefinition() { - return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); + return new FixerConfigurationResolver([ + $this->getFixer()->getConfigurationDefinition()->getOptions()[0], + ]); } /** * {@inheritdoc} */ - protected function applyFix(\SplFileInfo $file, Tokens $tokens) + public function getSuccessorsNames() { - $tokensAnalyzer = new TokensAnalyzer($tokens); - - for ($index = $tokens->count() - 1; $index >= 0; --$index) { - if ($tokensAnalyzer->isArray($index) && $tokensAnalyzer->isArrayMultiLine($index)) { - $this->fixArray($tokens, $index); - } - } + return array_keys($this->proxyFixers); } /** * {@inheritdoc} */ - protected function createConfigurationDefinition() + protected function createProxyFixers() { - return new FixerConfigurationResolver([ - (new FixerOptionBuilder('after_heredoc', 'Whether a trailing comma should also be placed after heredoc end.')) - ->setAllowedTypes(['bool']) - ->setDefault(false) - ->setNormalizer(static function (Options $options, $value) { - if (\PHP_VERSION_ID < 70300 && $value) { - throw new InvalidOptionsForEnvException('"after_heredoc" option can only be enabled with PHP 7.3+.'); - } - - return $value; - }) - ->getOption(), - ]); + return [$this->getFixer()]; } - /** - * @param int $index - */ - private function fixArray(Tokens $tokens, $index) + private function getFixer() { - $startIndex = $index; - - if ($tokens[$startIndex]->isGivenKind(T_ARRAY)) { - $startIndex = $tokens->getNextTokenOfKind($startIndex, ['(']); - $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex); - } else { - $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $startIndex); + if (null === $this->fixer) { + $this->fixer = new TrailingCommaInMultilineFixer(); } - $beforeEndIndex = $tokens->getPrevMeaningfulToken($endIndex); - $beforeEndToken = $tokens[$beforeEndIndex]; - - // if there is some item between braces then add `,` after it - if ( - $startIndex !== $beforeEndIndex && !$beforeEndToken->equals(',') - && ($this->configuration['after_heredoc'] || !$beforeEndToken->isGivenKind(T_END_HEREDOC)) - ) { - $tokens->insertAt($beforeEndIndex + 1, new Token(',')); - - $endToken = $tokens[$endIndex]; - - if (!$endToken->isComment() && !$endToken->isWhitespace()) { - $tokens->ensureWhitespaceAtIndex($endIndex, 1, ' '); - } - } + return $this->fixer; } } diff --git a/src/Fixer/ControlStructure/TrailingCommaInMultilineFixer.php b/src/Fixer/ControlStructure/TrailingCommaInMultilineFixer.php new file mode 100644 index 00000000000..75db85cfaaa --- /dev/null +++ b/src/Fixer/ControlStructure/TrailingCommaInMultilineFixer.php @@ -0,0 +1,219 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerConfiguration\InvalidOptionsForEnvException; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; +use Symfony\Component\OptionsResolver\Options; + +/** + * @author Sebastiaan Stok + * @author Dariusz Rumiński + * @author Kuba Werłos + */ +final class TrailingCommaInMultilineFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * @internal + */ + const ELEMENTS_ARRAYS = 'arrays'; + + /** + * @internal + */ + const ELEMENTS_ARGUMENTS = 'arguments'; + + /** + * @internal + */ + const ELEMENTS_PARAMETERS = 'parameters'; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Multi-line arrays, arguments list and parameters list must have a trailing comma.', + [ + new CodeSample(" true] + ), + new VersionSpecificCodeSample(" [self::ELEMENTS_ARGUMENTS]]), + new VersionSpecificCodeSample(" [self::ELEMENTS_PARAMETERS]]), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after NoMultilineWhitespaceAroundDoubleArrowFixer. + */ + public function getPriority() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN, '(']); + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('after_heredoc', 'Whether a trailing comma should also be placed after heredoc end.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->setNormalizer(static function (Options $options, $value) { + if (\PHP_VERSION_ID < 70300 && $value) { + throw new InvalidOptionsForEnvException('"after_heredoc" option can only be enabled with PHP 7.3+.'); + } + + return $value; + }) + ->getOption(), + (new FixerOptionBuilder('elements', sprintf('Where to fix multiline trailing comma (PHP >= 7.3 required for `%s`, PHP >= 8.0 for `%s`).', self::ELEMENTS_ARGUMENTS, self::ELEMENTS_PARAMETERS))) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset([self::ELEMENTS_ARRAYS, self::ELEMENTS_ARGUMENTS, self::ELEMENTS_PARAMETERS])]) + ->setDefault([self::ELEMENTS_ARRAYS]) + ->setNormalizer(static function (Options $options, $value) { + if (\PHP_VERSION_ID < 70300 && \in_array(self::ELEMENTS_ARGUMENTS, $value, true)) { + throw new InvalidOptionsForEnvException(sprintf('"%s" option can only be enabled with PHP 7.3+.', self::ELEMENTS_ARGUMENTS)); + } + if (\PHP_VERSION_ID < 80000 && \in_array(self::ELEMENTS_PARAMETERS, $value, true)) { + throw new InvalidOptionsForEnvException(sprintf('"%s" option can only be enabled with PHP 8.0+.', self::ELEMENTS_PARAMETERS)); + } + + return $value; + }) + ->getOption(), + ]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $fixArrays = \in_array(self::ELEMENTS_ARRAYS, $this->configuration['elements'], true); + $fixArguments = \in_array(self::ELEMENTS_ARGUMENTS, $this->configuration['elements'], true); + $fixParameters = \in_array(self::ELEMENTS_PARAMETERS, $this->configuration['elements'], true); + + $tokensAnalyzer = new TokensAnalyzer($tokens); + + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + if ( + $fixArrays + && ( + $tokens[$index]->equals('(') && $tokens[$prevIndex]->isGivenKind(T_ARRAY) // long syntax + || $tokens[$index]->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN) // short syntax + ) + ) { + $this->fixBlock($tokens, $index); + + continue; + } + + if (!$tokens[$index]->equals('(')) { + continue; + } + + $prevPrevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + + if ($fixArguments + && $tokens[$prevIndex]->equalsAny([']', [T_CLASS], [T_STRING], [T_VARIABLE]]) + && !$tokens[$prevPrevIndex]->isGivenKind(T_FUNCTION) + ) { + $this->fixBlock($tokens, $index); + + continue; + } + + if ( + $fixParameters + && ( + $tokens[$prevIndex]->isGivenKind(T_STRING) && $tokens[$prevPrevIndex]->isGivenKind(T_FUNCTION) + || $tokens[$prevIndex]->isGivenKind([T_FN, T_FUNCTION]) + ) + ) { + $this->fixBlock($tokens, $index); + } + } + } + + /** + * @param int $startIndex + */ + private function fixBlock(Tokens $tokens, $startIndex) + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + if (!$tokensAnalyzer->isBlockMultiline($tokens, $startIndex)) { + return; + } + + $blockType = Tokens::detectBlockType($tokens[$startIndex]); + $endIndex = $tokens->findBlockEnd($blockType['type'], $startIndex); + + $beforeEndIndex = $tokens->getPrevMeaningfulToken($endIndex); + $beforeEndToken = $tokens[$beforeEndIndex]; + + // if there is some item between braces then add `,` after it + if ( + $startIndex !== $beforeEndIndex && !$beforeEndToken->equals(',') + && ($this->configuration['after_heredoc'] || !$beforeEndToken->isGivenKind(T_END_HEREDOC)) + ) { + $tokens->insertAt($beforeEndIndex + 1, new Token(',')); + + $endToken = $tokens[$endIndex]; + + if (!$endToken->isComment() && !$endToken->isWhitespace()) { + $tokens->ensureWhitespaceAtIndex($endIndex, 1, ' '); + } + } + } +} diff --git a/src/RuleSet/Sets/PHP73MigrationSet.php b/src/RuleSet/Sets/PHP73MigrationSet.php index 8915330f22f..05b2e9bed51 100644 --- a/src/RuleSet/Sets/PHP73MigrationSet.php +++ b/src/RuleSet/Sets/PHP73MigrationSet.php @@ -26,7 +26,7 @@ public function getRules() 'heredoc_indentation' => true, 'method_argument_space' => ['after_heredoc' => true], 'no_whitespace_before_comma_in_array' => ['after_heredoc' => true], - 'trailing_comma_in_multiline_array' => ['after_heredoc' => true], + 'trailing_comma_in_multiline' => ['after_heredoc' => true], ]; } diff --git a/src/RuleSet/Sets/SymfonySet.php b/src/RuleSet/Sets/SymfonySet.php index 37914a640d2..a9534dab357 100644 --- a/src/RuleSet/Sets/SymfonySet.php +++ b/src/RuleSet/Sets/SymfonySet.php @@ -175,7 +175,7 @@ public function getRules() 'standardize_increment' => true, 'standardize_not_equals' => true, 'switch_continue_to_break' => true, - 'trailing_comma_in_multiline_array' => true, + 'trailing_comma_in_multiline' => true, 'trim_array_spaces' => true, 'unary_operator_spaces' => true, 'visibility_required' => true, diff --git a/src/Tokenizer/TokensAnalyzer.php b/src/Tokenizer/TokensAnalyzer.php index 423cbf3a4a0..f8116077fac 100644 --- a/src/Tokenizer/TokensAnalyzer.php +++ b/src/Tokenizer/TokensAnalyzer.php @@ -146,10 +146,23 @@ public function isArrayMultiLine($index) $index = $tokens->getNextMeaningfulToken($index); } - $endIndex = $tokens[$index]->equals('(') - ? $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index) - : $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index) - ; + return $this->isBlockMultiline($tokens, $index); + } + + /** + * @param int $index + * + * @return bool + */ + public function isBlockMultiline(Tokens $tokens, $index) + { + $blockType = Tokens::detectBlockType($tokens[$index]); + + if (null === $blockType || !$blockType['isStart']) { + throw new \InvalidArgumentException(sprintf('Not an block start at given index %d.', $index)); + } + + $endIndex = $tokens->findBlockEnd($blockType['type'], $index); for (++$index; $index < $endIndex; ++$index) { $token = $tokens[$index]; diff --git a/tests/AutoReview/FixerFactoryTest.php b/tests/AutoReview/FixerFactoryTest.php index 4b74491b789..4a51fbb210d 100644 --- a/tests/AutoReview/FixerFactoryTest.php +++ b/tests/AutoReview/FixerFactoryTest.php @@ -162,7 +162,7 @@ public function provideFixersPriorityCases() [$fixers['no_extra_blank_lines'], $fixers['blank_line_before_statement']], [$fixers['no_leading_import_slash'], $fixers['ordered_imports']], [$fixers['no_multiline_whitespace_around_double_arrow'], $fixers['binary_operator_spaces']], - [$fixers['no_multiline_whitespace_around_double_arrow'], $fixers['trailing_comma_in_multiline_array']], + [$fixers['no_multiline_whitespace_around_double_arrow'], $fixers['trailing_comma_in_multiline']], [$fixers['no_php4_constructor'], $fixers['ordered_class_elements']], [$fixers['no_short_bool_cast'], $fixers['cast_spaces']], [$fixers['no_short_echo_tag'], $fixers['no_mixed_echo_print']], diff --git a/tests/Fixer/ControlStructure/TrailingCommaInMultilineFixerTest.php b/tests/Fixer/ControlStructure/TrailingCommaInMultilineFixerTest.php new file mode 100644 index 00000000000..e6f7f789a34 --- /dev/null +++ b/tests/Fixer/ControlStructure/TrailingCommaInMultilineFixerTest.php @@ -0,0 +1,634 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tests\Fixer\ControlStructure; + +use PhpCsFixer\Fixer\ControlStructure\TrailingCommaInMultilineFixer; +use PhpCsFixer\Tests\Test\AbstractFixerTestCase; + +/** + * @author Sebastiaan Stok + * @author Kuba Werłos + * + * @internal + * + * @covers \PhpCsFixer\Fixer\ControlStructure\TrailingCommaInMultilineFixer + */ +final class TrailingCommaInMultilineFixerTest extends AbstractFixerTestCase +{ + /** + * @requires PHP <8.0 + * + * @dataProvider provideInvalidConfigurationCases + * + * @param mixed $exceptionMessega + * @param mixed $configuration + */ + public function testInvalidConfiguration($exceptionMessega, $configuration) + { + $this->expectException(\PhpCsFixer\ConfigurationException\InvalidForEnvFixerConfigurationException::class); + $this->expectExceptionMessage($exceptionMessega); + + $this->fixer->configure($configuration); + } + + public static function provideInvalidConfigurationCases() + { + if (\PHP_VERSION_ID < 70300) { + yield [ + '[trailing_comma_in_multiline] Invalid configuration for env: "after_heredoc" option can only be enabled with PHP 7.3+.', + ['after_heredoc' => true], + ]; + + yield [ + '[trailing_comma_in_multiline] Invalid configuration for env: "arguments" option can only be enabled with PHP 7.3+.', + ['elements' => [TrailingCommaInMultilineFixer::ELEMENTS_ARGUMENTS]], + ]; + } + + yield [ + '[trailing_comma_in_multiline] Invalid configuration for env: "parameters" option can only be enabled with PHP 8.0+.', + ['elements' => [TrailingCommaInMultilineFixer::ELEMENTS_PARAMETERS]], + ]; + } + + /** + * @param string $expected + * @param null|string $input + * + * @dataProvider provideFixCases + */ + public function testFix($expected, $input = null) + { + $this->doTest($expected, $input); + } + + public static function provideFixCases() + { + return [ + // long syntax tests + [' array( + 2 => 3, + ), + );', + ' array( + 2 => 3 + ) + );', + ], + [ + " function ($b) { + return "bar".$b; + });', + ], + [ + ' 1, + "b" => 2, + );', + ' 1, + "b" => 2 + );', + ], + [ + ' function ($b) { + return "bar".$b; + }];', + ], + [ + ' 1, + "b" => 2, + ];', + ' 1, + "b" => 2 + ];', + ], + [ + ' 1, + "b" => 2, + ); +}', + ' 1, + "b" => 2 + ); +}', + ], + [ + ' 1, + "b" => 2, + ]; +}', + ' 1, + "b" => 2 + ]; +}', + ], + ['fixer->configure($config); + $this->doTest($expected, $input); + } + + public static function provideFix73Cases() + { + return [ + [ + " [TrailingCommaInMultilineFixer::ELEMENTS_ARGUMENTS]], + ], + [ + " [TrailingCommaInMultilineFixer::ELEMENTS_ARGUMENTS]], + ], + [ + ' [TrailingCommaInMultilineFixer::ELEMENTS_ARGUMENTS]], + ], + [ + " [TrailingCommaInMultilineFixer::ELEMENTS_ARGUMENTS]], + ], + [ + " [TrailingCommaInMultilineFixer::ELEMENTS_ARGUMENTS]], + ], + [ + " [TrailingCommaInMultilineFixer::ELEMENTS_ARGUMENTS]], + ], + [ + " [TrailingCommaInMultilineFixer::ELEMENTS_ARRAYS]], + ], + [ + " [TrailingCommaInMultilineFixer::ELEMENTS_ARGUMENTS]], + ], + [ + " [TrailingCommaInMultilineFixer::ELEMENTS_ARGUMENTS]], + ], + [ + " [TrailingCommaInMultilineFixer::ELEMENTS_ARGUMENTS]], + ], + [ + " [TrailingCommaInMultilineFixer::ELEMENTS_ARGUMENTS]], + ], + [ + " [TrailingCommaInMultilineFixer::ELEMENTS_ARGUMENTS]], + ], + [ + " [TrailingCommaInMultilineFixer::ELEMENTS_ARGUMENTS]], + ], + [ + 'method( + 1, + 2, + ); + ', + 'method( + 1, + 2 + ); + ', + ['elements' => [TrailingCommaInMultilineFixer::ELEMENTS_ARGUMENTS]], + ], + [ + ' [TrailingCommaInMultilineFixer::ELEMENTS_ARRAYS, TrailingCommaInMultilineFixer::ELEMENTS_ARGUMENTS]], + ], + [ + ' [TrailingCommaInMultilineFixer::ELEMENTS_ARRAYS, TrailingCommaInMultilineFixer::ELEMENTS_ARGUMENTS]], + ], + [ + <<<'EXPECTED' + true], + ], + ]; + } + + /** + * @param string $expected + * @param null|string $input + * + * @dataProvider provideFix80Cases + * @requires PHP 8.0 + */ + public function testFix80($expected, $input = null, array $config = []) + { + $this->fixer->configure($config); + $this->doTest($expected, $input); + } + + public static function provideFix80Cases() + { + return [ + [ + ' [TrailingCommaInMultilineFixer::ELEMENTS_PARAMETERS]], + ], + [ + ' [TrailingCommaInMultilineFixer::ELEMENTS_ARRAYS, TrailingCommaInMultilineFixer::ELEMENTS_ARGUMENTS]], + ], + [ + ' [TrailingCommaInMultilineFixer::ELEMENTS_PARAMETERS]], + ], + [ + ' [TrailingCommaInMultilineFixer::ELEMENTS_PARAMETERS]], + ], + [ + ' $x + $y;', + ' $x + $y;', + ['elements' => [TrailingCommaInMultilineFixer::ELEMENTS_PARAMETERS]], + ], + ]; + } +} diff --git a/tests/Fixtures/Integration/priority/no_multiline_whitespace_around_double_arrow,trailing_comma_in_multiline_array.test b/tests/Fixtures/Integration/priority/no_multiline_whitespace_around_double_arrow,trailing_comma_in_multiline.test similarity index 75% rename from tests/Fixtures/Integration/priority/no_multiline_whitespace_around_double_arrow,trailing_comma_in_multiline_array.test rename to tests/Fixtures/Integration/priority/no_multiline_whitespace_around_double_arrow,trailing_comma_in_multiline.test index 91299239ed4..91e13f02f31 100644 --- a/tests/Fixtures/Integration/priority/no_multiline_whitespace_around_double_arrow,trailing_comma_in_multiline_array.test +++ b/tests/Fixtures/Integration/priority/no_multiline_whitespace_around_double_arrow,trailing_comma_in_multiline.test @@ -1,7 +1,7 @@ --TEST-- -Integration of fixers: no_multiline_whitespace_around_double_arrow,trailing_comma_in_multiline_array. +Integration of fixers: no_multiline_whitespace_around_double_arrow,trailing_comma_in_multiline. --RULESET-- -{"no_multiline_whitespace_around_double_arrow": true, "trailing_comma_in_multiline_array": true} +{"no_multiline_whitespace_around_double_arrow": true, "trailing_comma_in_multiline": true} --EXPECT-- 'a'); diff --git a/tests/Tokenizer/TokensAnalyzerTest.php b/tests/Tokenizer/TokensAnalyzerTest.php index edb31163cfb..617b7e8b2c9 100644 --- a/tests/Tokenizer/TokensAnalyzerTest.php +++ b/tests/Tokenizer/TokensAnalyzerTest.php @@ -1582,6 +1582,64 @@ public function provideArrayExceptionsCases() ]; } + public function testIsBlockMultilineException() + { + $this->expectException(\LogicException::class); + + $tokens = Tokens::fromCode('isBlockMultiline($tokens, 1); + } + + /** + * @param bool $isBlockMultiline + * @param string $source + * @param int $tokenIndex + * + * @dataProvider provideIsBlockMultilineCases + */ + public function testIsBlockMultiline($isBlockMultiline, $source, $tokenIndex) + { + $tokens = Tokens::fromCode($source); + $tokensAnalyzer = new TokensAnalyzer($tokens); + + static::assertSame($isBlockMultiline, $tokensAnalyzer->isBlockMultiline($tokens, $tokenIndex)); + } + + public static function provideIsBlockMultilineCases() + { + yield [ + false, + '