From 042c0bce97276025f7fb6120d89af5e70483e86f Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 4 Feb 2024 12:55:30 +0100 Subject: [PATCH 01/19] First iteration --- .../control_structure/fully_multiline.rst | 132 ++++++++ doc/rules/index.rst | 3 + .../ControlStructure/FullyMultilineFixer.php | 288 ++++++++++++++++++ .../FullyMultilineFixerTest.php | 283 +++++++++++++++++ 4 files changed, 706 insertions(+) create mode 100644 doc/rules/control_structure/fully_multiline.rst create mode 100644 src/Fixer/ControlStructure/FullyMultilineFixer.php create mode 100644 tests/Fixer/ControlStructure/FullyMultilineFixerTest.php diff --git a/doc/rules/control_structure/fully_multiline.rst b/doc/rules/control_structure/fully_multiline.rst new file mode 100644 index 00000000000..6080c80e3af --- /dev/null +++ b/doc/rules/control_structure/fully_multiline.rst @@ -0,0 +1,132 @@ +======================== +Rule ``fully_multiline`` +======================== + +Multi-line arrays, arguments list, parameters list, control structures, +``switch`` cases and ``match`` expressions should have one element by line. + +Configuration +------------- + +``elements`` +~~~~~~~~~~~~ + +Which expression must have one element by line (PHP >= 8.0 for ``parameters`` +and ``match``). + +Allowed values: a subset of ``['arguments', 'arrays', 'case', 'control_structures', 'match', 'parameters']`` + +Default value: ``['arrays']`` + +Examples +-------- + +Example #1 +~~~~~~~~~~ + +*Default* configuration. + +.. code-block:: diff + + --- Original + +++ New + ['arguments']]``. + +.. code-block:: diff + + --- Original + +++ New + ['control_structures']]``. + +.. code-block:: diff + + --- Original + +++ New + ['case']]``. + +.. code-block:: diff + + --- Original + +++ New + ['parameters']]``. + +.. code-block:: diff + + --- Original + +++ New + ['match']]``. + +.. code-block:: diff + + --- Original + +++ New + 1, 2 => 2 + + 1 => 1, + +2 => 2 + }; +References +---------- + +- Fixer class: `PhpCsFixer\\Fixer\\ControlStructure\\FullyMultilineFixer <./../../../src/Fixer/ControlStructure/FullyMultilineFixer.php>`_ +- Test class: `PhpCsFixer\\Tests\\Fixer\\ControlStructure\\FullyMultilineFixerTest <./../../../tests/Fixer/ControlStructure/FullyMultilineFixerTest.php>`_ + +The test class defines officially supported behaviour. Each test case is a part of our backward compatibility promise. diff --git a/doc/rules/index.rst b/doc/rules/index.rst index 62c7cb522fd..df63f0ca8c6 100644 --- a/doc/rules/index.rst +++ b/doc/rules/index.rst @@ -291,6 +291,9 @@ Control Structure - `empty_loop_condition <./control_structure/empty_loop_condition.rst>`_ Empty loop-condition must be in configured style. +- `fully_multiline <./control_structure/fully_multiline.rst>`_ + + Multi-line arrays, arguments list, parameters list, control structures, ``switch`` cases and ``match`` expressions should have one element by line. - `include <./control_structure/include.rst>`_ Include/Require and file path should be divided with a single space. File path should not be placed within parentheses. diff --git a/src/Fixer/ControlStructure/FullyMultilineFixer.php b/src/Fixer/ControlStructure/FullyMultilineFixer.php new file mode 100644 index 00000000000..8d171ffdcd8 --- /dev/null +++ b/src/Fixer/ControlStructure/FullyMultilineFixer.php @@ -0,0 +1,288 @@ + + * 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\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Vincent Langlet + */ +final class FullyMultilineFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @internal + */ + public const ELEMENTS_ARRAYS = 'arrays'; + + /** + * @internal + */ + public const ELEMENTS_ARGUMENTS = 'arguments'; + + /** + * @internal + */ + public const ELEMENTS_PARAMETERS = 'parameters'; + + /** + * @internal + */ + public const ELEMENTS_CONTROL_STRUCTURES = 'control_structures'; + + /** + * @internal + */ + public const SWITCH_CASES = 'case'; + + /** + * @internal + */ + public const MATCH_EXPRESSIONS = 'match'; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Multi-line arrays, arguments list, parameters list, control structures, `switch` cases and `match` expressions should have one element by line.', + [ + new CodeSample(" [self::ELEMENTS_ARGUMENTS]]), + new CodeSample(" [self::ELEMENTS_CONTROL_STRUCTURES]]), + new CodeSample(" [self::SWITCH_CASES]]), + new CodeSample(" [self::ELEMENTS_PARAMETERS]]), + new VersionSpecificCodeSample(" 1, 2 => 2\n};\n", new VersionSpecification(8_00_00), ['elements' => [self::MATCH_EXPRESSIONS]]), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN, '(']); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('elements', sprintf('Which expression must have one element by line (PHP >= 8.0 for `%s` and `%s`).', self::ELEMENTS_PARAMETERS, self::MATCH_EXPRESSIONS))) // @TODO: remove text when PHP 8.0+ is required + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset([ + self::ELEMENTS_ARRAYS, + self::ELEMENTS_ARGUMENTS, + self::ELEMENTS_PARAMETERS, + self::ELEMENTS_CONTROL_STRUCTURES, + self::SWITCH_CASES, + self::MATCH_EXPRESSIONS, + ])]) + ->setDefault([self::ELEMENTS_ARRAYS]) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $this->processBlock($tokens, 0, $tokens->count() - 1, false); + } + + /** + * Process tokens from $tokens[$begin] to $tokens[$end] and add missing new lines: + * - If the code is not multiline, there is nothing to add + * - If the code is multiline, it looks for newline right after the first token, + * after every comma, and right before the last token and add them when they are missing. + * + * Since recursion is used to handle things like array inside array or functions we need + * a parameter $shouldAddNewLine to knows if we are currently fixing the structure, which + * requires to add newlines, or if we are just looking for the next one to fix. + */ + private function processBlock(Tokens $tokens, int $begin, int $end, bool $shouldAddNewLine, ?string $lineDelimiter = null): int + { + $tokenAddedCount = 0; + if (!$tokens->isPartialCodeMultiline($begin, $end)) { + return $tokenAddedCount; + } + + if ($shouldAddNewLine) { + $tokenAddedCount += $this->addNewLineAfterIfNecessary($tokens, $begin); + } + + $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); + $fixControlStructures = \in_array(self::ELEMENTS_CONTROL_STRUCTURES, $this->configuration['elements'], true); + $fixSwitchCases = \in_array(self::SWITCH_CASES, $this->configuration['elements'], true); + $fixMatch = \in_array(self::MATCH_EXPRESSIONS, $this->configuration['elements'], true); + + for ($index = $begin + 1; $index < $end + $tokenAddedCount; ++$index) { + /** @var Token $token */ + $token = $tokens[$index]; + + if (null !== $lineDelimiter && $token->equals($lineDelimiter)) { + if ($shouldAddNewLine) { + $tokenAddedCount += $this->addNewLineAfterIfNecessary($tokens, $index); + } + + continue; + } + + if ($tokens[$index]->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index); + $added = $this->processBlock($tokens, $index, $until, $fixArrays, ','); + + $index = $until + $added; + $tokenAddedCount += $added; + + continue; + } + + if (!$tokens[$index]->equals('(')) { + continue; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$prevIndex]->isGivenKind(T_ARRAY)) { + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $added = $this->processBlock($tokens, $index, $until, $fixArrays, ','); + + $index = $until + $added; + $tokenAddedCount += $added; + + continue; + } + + if ($tokens[$prevIndex]->isGivenKind([T_IF, T_ELSEIF, T_WHILE, T_FOREACH, T_CATCH])) { + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $added = $this->processBlock($tokens, $index, $until, $fixControlStructures); + + $index = $until + $added; + $tokenAddedCount += $added; + + continue; + } + + if ($tokens[$prevIndex]->isGivenKind(T_FOR)) { + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $added = $this->processBlock($tokens, $index, $until, $fixControlStructures, ';'); + + $index = $until + $added; + $tokenAddedCount += $added; + + continue; + } + + $prevPrevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + if ($tokens[$prevIndex]->equalsAny([']', [T_CLASS], [T_STRING], [T_VARIABLE], [T_STATIC]]) + && !$tokens[$prevPrevIndex]->isGivenKind(T_FUNCTION) + ) { + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $added = $this->processBlock($tokens, $index, $until, $fixArguments, ','); + + $index = $until + $added; + $tokenAddedCount += $added; + + continue; + } + + if ( + $tokens[$prevIndex]->isGivenKind([T_FN, T_FUNCTION]) + || $tokens[$prevIndex]->isGivenKind(T_STRING) && $tokens[$prevPrevIndex]->isGivenKind(T_FUNCTION) + ) { + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $added = $this->processBlock($tokens, $index, $until, $fixParameters, ','); + + $index = $until + $added; + $tokenAddedCount += $added; + + continue; + } + + if ($tokens[$prevIndex]->isGivenKind(T_SWITCH)) { + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $tokenAddedCount += $this->processBlock($tokens, $index, $until, $fixControlStructures); + + $index = $tokens->getNextTokenOfKind($index, ['{']); + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + $added = $this->processBlock($tokens, $index, $until, $fixSwitchCases, ':'); + + $index = $until + $added; + $tokenAddedCount += $added; + + continue; + } + + // @TODO: remove defined condition when PHP 8.0+ is required + if (\defined('T_MATCH') && $tokens[$prevIndex]->isGivenKind(T_MATCH)) { + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $tokenAddedCount += $this->processBlock($tokens, $index, $until, $fixControlStructures); + + $index = $tokens->getNextTokenOfKind($index, ['{']); + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + $added = $this->processBlock($tokens, $index, $until, $fixMatch, ','); + + $index = $until + $added; + $tokenAddedCount += $added; + } + } + + if ($shouldAddNewLine) { + $tokenAddedCount += $this->addNewLineBeforeIfNecessary($tokens, $end + $tokenAddedCount); + } + + return $tokenAddedCount; + } + + private function addNewLineAfterIfNecessary(Tokens $tokens, int $index): int + { + $next = $tokens->getNextMeaningfulToken($index); + if ($tokens->isPartialCodeMultiline($index, $next)) { + return 0; + } + + return $this->addNewLineAfter($tokens, $index); + } + + private function addNewLineBeforeIfNecessary(Tokens $tokens, int $index): int + { + $previous = $tokens->getPrevMeaningfulToken($index); + if ($tokens->isPartialCodeMultiline($previous, $index)) { + return 0; + } + + return $this->addNewLineAfter($tokens, $previous); + } + + private function addNewLineAfter(Tokens $tokens, int $index): int + { + if ($tokens[$index + 1]->isWhitespace()) { + $tokens[$index + 1] = new Token([T_WHITESPACE, "\n"]); + + return 0; + } + + $tokens->insertSlices([$index + 1 => new Token([T_WHITESPACE, "\n"])]); + + return 1; + } +} diff --git a/tests/Fixer/ControlStructure/FullyMultilineFixerTest.php b/tests/Fixer/ControlStructure/FullyMultilineFixerTest.php new file mode 100644 index 00000000000..1016c893d81 --- /dev/null +++ b/tests/Fixer/ControlStructure/FullyMultilineFixerTest.php @@ -0,0 +1,283 @@ + + * 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\FullyMultilineFixer; +use PhpCsFixer\Tests\Test\AbstractFixerTestCase; + +/** + * @author Vincent Langlet + * + * @internal + * + * @covers \PhpCsFixer\Fixer\ControlStructure\FullyMultilineFixer + */ +final class FullyMultilineFixerTest extends AbstractFixerTestCase +{ + /** + * @param array $config + * + * @dataProvider provideFixCases + */ + public function testFix(string $expected, ?string $input = null, array $config = []): void + { + $this->fixer->configure($config); + + $this->doTest($expected, $input); + } + + /** + * @return iterable}> + */ + public static function provideFixCases(): iterable + { + yield [' [FullyMultilineFixer::ELEMENTS_ARGUMENTS]], + ]; + + yield [ + ' [FullyMultilineFixer::ELEMENTS_PARAMETERS]], + ]; + + yield [ + ' $value +) { + };', + ' $value) { + };', + ['elements' => [FullyMultilineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ]; + + yield [ + ' [FullyMultilineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ]; + + yield [ + ' [FullyMultilineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ]; + + yield [ + ' [FullyMultilineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ]; + + yield [ + ' [FullyMultilineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ]; + } + + /** + * @requires PHP 8.0 + * + * @param array $config + * + * @dataProvider provideFix80Cases + */ + public function testFix80(string $expected, ?string $input = null, array $config = []): void + { + $this->fixer->configure($config); + + $this->doTest($expected, $input); + } + + /** + * @return iterable}> + */ + public static function provideFix80Cases(): iterable + { + yield [ + ' 1, 2 => 2, + 3, 4 => 4, + };', + ' 1, 2 => 2, + 3, 4 => 4, + };', + ['elements' => [FullyMultilineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ]; + + yield [ + ' 1, +2 => 2, + 3, +4 => 4, + };', + ' 1, 2 => 2, + 3, 4 => 4, + };', + ['elements' => [FullyMultilineFixer::MATCH_EXPRESSIONS]], + ]; + + yield [ + ' 1, 2 => 2, + 3, 4 => 4, + };', + ' 1, 2 => 2, + 3, 4 => 4, + };', + ['elements' => [FullyMultilineFixer::ELEMENTS_ARGUMENTS, FullyMultilineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ]; + } +} From 739ac5eb9d6ab6d707a8e88801fece6378cc2411 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 18 Feb 2024 17:32:58 +0100 Subject: [PATCH 02/19] Rename --- ...ine.rst => single_expression_per_line.rst} | 10 +++---- doc/rules/index.rst | 6 ++-- ...r.php => SingleExpressionPerLineFixer.php} | 2 +- ...p => SingleExpressionPerLineFixerTest.php} | 28 +++++++++---------- 4 files changed, 22 insertions(+), 24 deletions(-) rename doc/rules/control_structure/{fully_multiline.rst => single_expression_per_line.rst} (80%) rename src/Fixer/ControlStructure/{FullyMultilineFixer.php => SingleExpressionPerLineFixer.php} (99%) rename tests/Fixer/ControlStructure/{FullyMultilineFixerTest.php => SingleExpressionPerLineFixerTest.php} (83%) diff --git a/doc/rules/control_structure/fully_multiline.rst b/doc/rules/control_structure/single_expression_per_line.rst similarity index 80% rename from doc/rules/control_structure/fully_multiline.rst rename to doc/rules/control_structure/single_expression_per_line.rst index 6080c80e3af..049eb9f202b 100644 --- a/doc/rules/control_structure/fully_multiline.rst +++ b/doc/rules/control_structure/single_expression_per_line.rst @@ -1,6 +1,6 @@ -======================== -Rule ``fully_multiline`` -======================== +=================================== +Rule ``single_expression_per_line`` +=================================== Multi-line arrays, arguments list, parameters list, control structures, ``switch`` cases and ``match`` expressions should have one element by line. @@ -126,7 +126,7 @@ With configuration: ``['elements' => ['match']]``. References ---------- -- Fixer class: `PhpCsFixer\\Fixer\\ControlStructure\\FullyMultilineFixer <./../../../src/Fixer/ControlStructure/FullyMultilineFixer.php>`_ -- Test class: `PhpCsFixer\\Tests\\Fixer\\ControlStructure\\FullyMultilineFixerTest <./../../../tests/Fixer/ControlStructure/FullyMultilineFixerTest.php>`_ +- Fixer class: `PhpCsFixer\\Fixer\\ControlStructure\\SingleExpressionPerLineFixer <./../../../src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php>`_ +- Test class: `PhpCsFixer\\Tests\\Fixer\\ControlStructure\\SingleExpressionPerLineFixerTest <./../../../tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php>`_ The test class defines officially supported behaviour. Each test case is a part of our backward compatibility promise. diff --git a/doc/rules/index.rst b/doc/rules/index.rst index df63f0ca8c6..46dc1f07e2f 100644 --- a/doc/rules/index.rst +++ b/doc/rules/index.rst @@ -291,9 +291,6 @@ Control Structure - `empty_loop_condition <./control_structure/empty_loop_condition.rst>`_ Empty loop-condition must be in configured style. -- `fully_multiline <./control_structure/fully_multiline.rst>`_ - - Multi-line arrays, arguments list, parameters list, control structures, ``switch`` cases and ``match`` expressions should have one element by line. - `include <./control_structure/include.rst>`_ Include/Require and file path should be divided with a single space. File path should not be placed within parentheses. @@ -324,6 +321,9 @@ Control Structure - `simplified_if_return <./control_structure/simplified_if_return.rst>`_ Simplify ``if`` control structures that return the boolean result of their condition. +- `single_expression_per_line <./control_structure/single_expression_per_line.rst>`_ + + Multi-line arrays, arguments list, parameters list, control structures, ``switch`` cases and ``match`` expressions should have one element by line. - `switch_case_semicolon_to_colon <./control_structure/switch_case_semicolon_to_colon.rst>`_ A case should be followed by a colon and not a semicolon. diff --git a/src/Fixer/ControlStructure/FullyMultilineFixer.php b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php similarity index 99% rename from src/Fixer/ControlStructure/FullyMultilineFixer.php rename to src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php index 8d171ffdcd8..24500a9a36c 100644 --- a/src/Fixer/ControlStructure/FullyMultilineFixer.php +++ b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php @@ -32,7 +32,7 @@ /** * @author Vincent Langlet */ -final class FullyMultilineFixer extends AbstractFixer implements ConfigurableFixerInterface +final class SingleExpressionPerLineFixer extends AbstractFixer implements ConfigurableFixerInterface { /** * @internal diff --git a/tests/Fixer/ControlStructure/FullyMultilineFixerTest.php b/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php similarity index 83% rename from tests/Fixer/ControlStructure/FullyMultilineFixerTest.php rename to tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php index 1016c893d81..3b4de5f098a 100644 --- a/tests/Fixer/ControlStructure/FullyMultilineFixerTest.php +++ b/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php @@ -14,17 +14,15 @@ namespace PhpCsFixer\Tests\Fixer\ControlStructure; -use PhpCsFixer\Fixer\ControlStructure\FullyMultilineFixer; +use PhpCsFixer\Fixer\ControlStructure\SingleExpressionPerLineFixer; use PhpCsFixer\Tests\Test\AbstractFixerTestCase; /** * @author Vincent Langlet - * * @internal - * - * @covers \PhpCsFixer\Fixer\ControlStructure\FullyMultilineFixer + * @covers \PhpCsFixer\Fixer\ControlStructure\SingleExpressionPerLineFixer */ -final class FullyMultilineFixerTest extends AbstractFixerTestCase +final class SingleExpressionPerLineFixerTest extends AbstractFixerTestCase { /** * @param array $config @@ -106,7 +104,7 @@ public static function provideFixCases(): iterable $a = foo(1, 2, 3 );', - ['elements' => [FullyMultilineFixer::ELEMENTS_ARGUMENTS]], + ['elements' => [SingleExpressionPerLineFixer::ELEMENTS_ARGUMENTS]], ]; yield [ @@ -120,7 +118,7 @@ function( function($a, $b, $c ) {};', - ['elements' => [FullyMultilineFixer::ELEMENTS_PARAMETERS]], + ['elements' => [SingleExpressionPerLineFixer::ELEMENTS_PARAMETERS]], ]; yield [ @@ -134,7 +132,7 @@ function($a, $b, foreach ($foo as $key => $value) { };', - ['elements' => [FullyMultilineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ['elements' => [SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], ]; yield [ @@ -149,7 +147,7 @@ function($a, $b, for ($i = 0; $i < 2; $i++) { };', - ['elements' => [FullyMultilineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ['elements' => [SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], ]; yield [ @@ -171,7 +169,7 @@ function($a, $b, && $d) { } else { }', - ['elements' => [FullyMultilineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ['elements' => [SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], ]; yield [ @@ -185,7 +183,7 @@ function($a, $b, while ($a && $b) { }', - ['elements' => [FullyMultilineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ['elements' => [SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], ]; yield [ @@ -201,7 +199,7 @@ function($a, $b, catch (\LogicException | \RuntimeException $e) { };', - ['elements' => [FullyMultilineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ['elements' => [SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], ]; } @@ -239,7 +237,7 @@ public static function provideFix80Cases(): iterable 1 => 1, 2 => 2, 3, 4 => 4, };', - ['elements' => [FullyMultilineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ['elements' => [SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], ]; yield [ @@ -257,7 +255,7 @@ public static function provideFix80Cases(): iterable 1 => 1, 2 => 2, 3, 4 => 4, };', - ['elements' => [FullyMultilineFixer::MATCH_EXPRESSIONS]], + ['elements' => [SingleExpressionPerLineFixer::MATCH_EXPRESSIONS]], ]; yield [ @@ -277,7 +275,7 @@ public static function provideFix80Cases(): iterable 1 => 1, 2 => 2, 3, 4 => 4, };', - ['elements' => [FullyMultilineFixer::ELEMENTS_ARGUMENTS, FullyMultilineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ['elements' => [SingleExpressionPerLineFixer::ELEMENTS_ARGUMENTS, SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], ]; } } From 6c2322864907978c5e2f6cd4d230de2404067c6e Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 18 Feb 2024 19:57:53 +0100 Subject: [PATCH 03/19] Fix some bugs --- .../SingleExpressionPerLineFixer.php | 76 ++++++++++++++++--- .../SingleExpressionPerLineFixerTest.php | 48 ++++++++++++ 2 files changed, 112 insertions(+), 12 deletions(-) diff --git a/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php index 24500a9a36c..669a8b97acf 100644 --- a/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php +++ b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php @@ -25,6 +25,8 @@ use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\FixerDefinition\VersionSpecification; use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\SwitchAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\ControlCaseStructuresAnalyzer; use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; @@ -87,7 +89,7 @@ public function isCandidate(Tokens $tokens): bool protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ - (new FixerOptionBuilder('elements', sprintf('Which expression must have one element by line (PHP >= 8.0 for `%s` and `%s`).', self::ELEMENTS_PARAMETERS, self::MATCH_EXPRESSIONS))) // @TODO: remove text when PHP 8.0+ is required + (new FixerOptionBuilder('elements', 'Which expression must have one element by line.')) ->setAllowedTypes(['array']) ->setAllowedValues([new AllowedValueSubset([ self::ELEMENTS_ARRAYS, @@ -116,9 +118,18 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void * Since recursion is used to handle things like array inside array or functions we need * a parameter $shouldAddNewLine to knows if we are currently fixing the structure, which * requires to add newlines, or if we are just looking for the next one to fix. + * + * @param null|array $lineDelimiters + * @param null|array $lineDelimitersIndexes */ - private function processBlock(Tokens $tokens, int $begin, int $end, bool $shouldAddNewLine, ?string $lineDelimiter = null): int - { + private function processBlock( + Tokens $tokens, + int $begin, + int $end, + bool $shouldAddNewLine, + ?array $lineDelimiters = null, + ?array $lineDelimitersIndexes = null + ): int { $tokenAddedCount = 0; if (!$tokens->isPartialCodeMultiline($begin, $end)) { return $tokenAddedCount; @@ -139,7 +150,11 @@ private function processBlock(Tokens $tokens, int $begin, int $end, bool $should /** @var Token $token */ $token = $tokens[$index]; - if (null !== $lineDelimiter && $token->equals($lineDelimiter)) { + if ( + null !== $lineDelimiters + && $token->equalsAny($lineDelimiters) + && (null === $lineDelimitersIndexes || \in_array($index, $lineDelimitersIndexes, true)) + ) { if ($shouldAddNewLine) { $tokenAddedCount += $this->addNewLineAfterIfNecessary($tokens, $index); } @@ -149,7 +164,7 @@ private function processBlock(Tokens $tokens, int $begin, int $end, bool $should if ($tokens[$index]->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index); - $added = $this->processBlock($tokens, $index, $until, $fixArrays, ','); + $added = $this->processBlock($tokens, $index, $until, $fixArrays, [',']); $index = $until + $added; $tokenAddedCount += $added; @@ -164,7 +179,7 @@ private function processBlock(Tokens $tokens, int $begin, int $end, bool $should $prevIndex = $tokens->getPrevMeaningfulToken($index); if ($tokens[$prevIndex]->isGivenKind(T_ARRAY)) { $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); - $added = $this->processBlock($tokens, $index, $until, $fixArrays, ','); + $added = $this->processBlock($tokens, $index, $until, $fixArrays, [',']); $index = $until + $added; $tokenAddedCount += $added; @@ -184,7 +199,7 @@ private function processBlock(Tokens $tokens, int $begin, int $end, bool $should if ($tokens[$prevIndex]->isGivenKind(T_FOR)) { $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); - $added = $this->processBlock($tokens, $index, $until, $fixControlStructures, ';'); + $added = $this->processBlock($tokens, $index, $until, $fixControlStructures, [';']); $index = $until + $added; $tokenAddedCount += $added; @@ -197,7 +212,7 @@ private function processBlock(Tokens $tokens, int $begin, int $end, bool $should && !$tokens[$prevPrevIndex]->isGivenKind(T_FUNCTION) ) { $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); - $added = $this->processBlock($tokens, $index, $until, $fixArguments, ','); + $added = $this->processBlock($tokens, $index, $until, $fixArguments, [',']); $index = $until + $added; $tokenAddedCount += $added; @@ -210,7 +225,7 @@ private function processBlock(Tokens $tokens, int $begin, int $end, bool $should || $tokens[$prevIndex]->isGivenKind(T_STRING) && $tokens[$prevPrevIndex]->isGivenKind(T_FUNCTION) ) { $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); - $added = $this->processBlock($tokens, $index, $until, $fixParameters, ','); + $added = $this->processBlock($tokens, $index, $until, $fixParameters, [',']); $index = $until + $added; $tokenAddedCount += $added; @@ -224,7 +239,14 @@ private function processBlock(Tokens $tokens, int $begin, int $end, bool $should $index = $tokens->getNextTokenOfKind($index, ['{']); $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); - $added = $this->processBlock($tokens, $index, $until, $fixSwitchCases, ':'); + $added = $this->processBlock( + $tokens, + $index, + $until, + $fixSwitchCases, + [':', ';'], + $this->computeSwitchColonIndexes($tokens) + ); $index = $until + $added; $tokenAddedCount += $added; @@ -239,7 +261,7 @@ private function processBlock(Tokens $tokens, int $begin, int $end, bool $should $index = $tokens->getNextTokenOfKind($index, ['{']); $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); - $added = $this->processBlock($tokens, $index, $until, $fixMatch, ','); + $added = $this->processBlock($tokens, $index, $until, $fixMatch, [',']); $index = $until + $added; $tokenAddedCount += $added; @@ -253,10 +275,40 @@ private function processBlock(Tokens $tokens, int $begin, int $end, bool $should return $tokenAddedCount; } + /** + * @return array + */ + private function computeSwitchColonIndexes(Tokens $tokens): array + { + static $colonIndexesByHash = []; + $hash = $tokens->getCodeHash(); + + if (!isset($colonIndexesByHash[$hash])) { + $colonIndexes = []; + + /** @var SwitchAnalysis $analysis */ + foreach (ControlCaseStructuresAnalyzer::findControlStructures($tokens, [T_SWITCH]) as $analysis) { + $default = $analysis->getDefaultAnalysis(); + + if (null !== $default) { + $colonIndexes[] = $default->getColonIndex(); + } + + foreach ($analysis->getCases() as $caseAnalysis) { + $colonIndexes[] = $caseAnalysis->getColonIndex(); + } + } + + $colonIndexesByHash[$hash] = $colonIndexes; + } + + return $colonIndexesByHash[$hash]; + } + private function addNewLineAfterIfNecessary(Tokens $tokens, int $index): int { $next = $tokens->getNextMeaningfulToken($index); - if ($tokens->isPartialCodeMultiline($index, $next)) { + if ($tokens->isPartialCodeMultiline($index, $next - 1)) { return 0; } diff --git a/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php b/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php index 3b4de5f098a..f018939dcc9 100644 --- a/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php +++ b/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php @@ -19,7 +19,9 @@ /** * @author Vincent Langlet + * * @internal + * * @covers \PhpCsFixer\Fixer\ControlStructure\SingleExpressionPerLineFixer */ final class SingleExpressionPerLineFixerTest extends AbstractFixerTestCase @@ -201,6 +203,52 @@ function($a, $b, };', ['elements' => [SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], ]; + + yield [ + ' [SingleExpressionPerLineFixer::SWITCH_CASES]], + ]; + + yield [ + ' [SingleExpressionPerLineFixer::SWITCH_CASES]], + ]; } /** From 0a257a89d8ab68cbbdfe02092724c1fa30323609 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 18 Feb 2024 19:58:01 +0100 Subject: [PATCH 04/19] Update rulesets --- doc/ruleSets/PER-CS2.0.rst | 4 +++ doc/ruleSets/PSR12.rst | 4 +++ doc/ruleSets/PhpCsFixer.rst | 4 +++ doc/ruleSets/Symfony.rst | 1 + .../single_expression_per_line.rst | 34 +++++++++++++++++-- src/RuleSet/Sets/PERCS2x0Set.php | 10 ++++++ src/RuleSet/Sets/PSR12Set.php | 9 +++++ src/RuleSet/Sets/PhpCsFixerSet.php | 7 ++++ src/RuleSet/Sets/SymfonySet.php | 1 + 9 files changed, 72 insertions(+), 2 deletions(-) diff --git a/doc/ruleSets/PER-CS2.0.rst b/doc/ruleSets/PER-CS2.0.rst index 65901090299..a48030416fd 100644 --- a/doc/ruleSets/PER-CS2.0.rst +++ b/doc/ruleSets/PER-CS2.0.rst @@ -19,4 +19,8 @@ Rules ``['closure_fn_spacing' => 'none']`` - `method_argument_space <./../rules/function_notation/method_argument_space.rst>`_ +- `single_expression_per_line <./../rules/control_structure/single_expression_per_line.rst>`_ with config: + + ``['elements' => ['arrays', 'arguments', 'parameters', 'control_structures', 'case', 'match']]`` + - `single_line_empty_body <./../rules/basic/single_line_empty_body.rst>`_ diff --git a/doc/ruleSets/PSR12.rst b/doc/ruleSets/PSR12.rst index 2ac28567326..159ab4a9aad 100644 --- a/doc/ruleSets/PSR12.rst +++ b/doc/ruleSets/PSR12.rst @@ -41,6 +41,10 @@ Rules - `return_type_declaration <./../rules/function_notation/return_type_declaration.rst>`_ - `short_scalar_cast <./../rules/cast_notation/short_scalar_cast.rst>`_ +- `single_expression_per_line <./../rules/control_structure/single_expression_per_line.rst>`_ with config: + + ``['elements' => ['arguments', 'parameters', 'control_structures', 'case', 'match']]`` + - `single_import_per_statement <./../rules/import/single_import_per_statement.rst>`_ with config: ``['group_to_single_imports' => false]`` diff --git a/doc/ruleSets/PhpCsFixer.rst b/doc/ruleSets/PhpCsFixer.rst index c9bb1a3254d..706c65bd090 100644 --- a/doc/ruleSets/PhpCsFixer.rst +++ b/doc/ruleSets/PhpCsFixer.rst @@ -64,6 +64,10 @@ Rules - `protected_to_private <./../rules/class_notation/protected_to_private.rst>`_ - `return_assignment <./../rules/return_notation/return_assignment.rst>`_ - `self_static_accessor <./../rules/class_notation/self_static_accessor.rst>`_ +- `single_expression_per_line <./../rules/control_structure/single_expression_per_line.rst>`_ with config: + + ``['elements' => ['parameters', 'case', 'match']]`` + - `single_line_comment_style <./../rules/comment/single_line_comment_style.rst>`_ - `single_line_empty_body <./../rules/basic/single_line_empty_body.rst>`_ - `string_implicit_backslashes <./../rules/string_notation/string_implicit_backslashes.rst>`_ with config: diff --git a/doc/ruleSets/Symfony.rst b/doc/ruleSets/Symfony.rst index 072008a555e..1918795e0bc 100644 --- a/doc/ruleSets/Symfony.rst +++ b/doc/ruleSets/Symfony.rst @@ -183,4 +183,5 @@ Rules Disabled rules -------------- +- `single_expression_per_line <./../rules/control_structure/single_expression_per_line.rst>`_ - `single_line_empty_body <./../rules/basic/single_line_empty_body.rst>`_ diff --git a/doc/rules/control_structure/single_expression_per_line.rst b/doc/rules/control_structure/single_expression_per_line.rst index 049eb9f202b..08ddb603248 100644 --- a/doc/rules/control_structure/single_expression_per_line.rst +++ b/doc/rules/control_structure/single_expression_per_line.rst @@ -11,8 +11,7 @@ Configuration ``elements`` ~~~~~~~~~~~~ -Which expression must have one element by line (PHP >= 8.0 for ``parameters`` -and ``match``). +Which expression must have one element by line. Allowed values: a subset of ``['arguments', 'arrays', 'case', 'control_structures', 'match', 'parameters']`` @@ -123,6 +122,37 @@ With configuration: ``['elements' => ['match']]``. + 1 => 1, +2 => 2 }; + +Rule sets +--------- + +The rule is part of the following rule sets: + +- `@PER <./../../ruleSets/PER.rst>`_ with config: + + ``['elements' => ['arrays', 'arguments', 'parameters', 'control_structures', 'case', 'match']]`` + +- `@PER-CS <./../../ruleSets/PER-CS.rst>`_ with config: + + ``['elements' => ['arrays', 'arguments', 'parameters', 'control_structures', 'case', 'match']]`` + +- `@PER-CS1.0 <./../../ruleSets/PER-CS1.0.rst>`_ with config: + + ``['elements' => ['arguments', 'parameters', 'control_structures', 'case', 'match']]`` + +- `@PER-CS2.0 <./../../ruleSets/PER-CS2.0.rst>`_ with config: + + ``['elements' => ['arrays', 'arguments', 'parameters', 'control_structures', 'case', 'match']]`` + +- `@PSR12 <./../../ruleSets/PSR12.rst>`_ with config: + + ``['elements' => ['arguments', 'parameters', 'control_structures', 'case', 'match']]`` + +- `@PhpCsFixer <./../../ruleSets/PhpCsFixer.rst>`_ with config: + + ``['elements' => ['parameters', 'case', 'match']]`` + + References ---------- diff --git a/src/RuleSet/Sets/PERCS2x0Set.php b/src/RuleSet/Sets/PERCS2x0Set.php index a5c28b19929..51157b10857 100644 --- a/src/RuleSet/Sets/PERCS2x0Set.php +++ b/src/RuleSet/Sets/PERCS2x0Set.php @@ -41,6 +41,16 @@ public function getRules(): array 'closure_fn_spacing' => 'none', ], 'method_argument_space' => true, + 'single_expression_per_line' => [ + 'elements' => [ + 'arguments', + 'arrays', + 'case', + 'control_structures', + 'match', + 'parameters', + ], + ], 'single_line_empty_body' => true, ]; } diff --git a/src/RuleSet/Sets/PSR12Set.php b/src/RuleSet/Sets/PSR12Set.php index 47719b10732..be9c60d3c7b 100644 --- a/src/RuleSet/Sets/PSR12Set.php +++ b/src/RuleSet/Sets/PSR12Set.php @@ -61,6 +61,15 @@ public function getRules(): array ], 'return_type_declaration' => true, 'short_scalar_cast' => true, + 'single_expression_per_line' => [ + 'elements' => [ + 'arguments', + 'case', + 'control_structures', + 'match', + 'parameters', + ], + ], 'single_import_per_statement' => ['group_to_single_imports' => false], 'single_trait_insert_per_statement' => true, 'ternary_operator_spaces' => true, diff --git a/src/RuleSet/Sets/PhpCsFixerSet.php b/src/RuleSet/Sets/PhpCsFixerSet.php index e90a02dd807..aeb7a8b819a 100644 --- a/src/RuleSet/Sets/PhpCsFixerSet.php +++ b/src/RuleSet/Sets/PhpCsFixerSet.php @@ -116,6 +116,13 @@ public function getRules(): array 'protected_to_private' => true, 'return_assignment' => true, 'self_static_accessor' => true, + 'single_expression_per_line' => [ + 'elements' => [ + 'case', + 'match', + 'parameters', + ], + ], 'single_line_comment_style' => true, 'single_line_empty_body' => true, 'single_line_throw' => false, diff --git a/src/RuleSet/Sets/SymfonySet.php b/src/RuleSet/Sets/SymfonySet.php index 1b4f38cdd9f..01ba031ee9e 100644 --- a/src/RuleSet/Sets/SymfonySet.php +++ b/src/RuleSet/Sets/SymfonySet.php @@ -197,6 +197,7 @@ public function getRules(): array 'semicolon_after_instruction' => true, 'simple_to_complex_string_variable' => true, 'single_class_element_per_statement' => true, + 'single_expression_per_line' => false, // overrides @PER-CS2.0 'single_import_per_statement' => true, 'single_line_comment_spacing' => true, 'single_line_comment_style' => [ From 8665d8aa1b29103947f312e1b70707e13237eb55 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 18 Feb 2024 20:25:50 +0100 Subject: [PATCH 05/19] Refacto --- .../SingleExpressionPerLineFixer.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php index 669a8b97acf..b59868d67d3 100644 --- a/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php +++ b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php @@ -66,6 +66,8 @@ final class SingleExpressionPerLineFixer extends AbstractFixer implements Config */ public const MATCH_EXPRESSIONS = 'match'; + private ?array $switchColonIndexes = null; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -245,7 +247,7 @@ private function processBlock( $until, $fixSwitchCases, [':', ';'], - $this->computeSwitchColonIndexes($tokens) + $this->getSwitchColonIndexes($tokens) ); $index = $until + $added; @@ -278,12 +280,9 @@ private function processBlock( /** * @return array */ - private function computeSwitchColonIndexes(Tokens $tokens): array + private function getSwitchColonIndexes(Tokens $tokens): array { - static $colonIndexesByHash = []; - $hash = $tokens->getCodeHash(); - - if (!isset($colonIndexesByHash[$hash])) { + if (null === $this->switchColonIndexes) { $colonIndexes = []; /** @var SwitchAnalysis $analysis */ @@ -299,10 +298,10 @@ private function computeSwitchColonIndexes(Tokens $tokens): array } } - $colonIndexesByHash[$hash] = $colonIndexes; + $this->switchColonIndexes = $colonIndexes; } - return $colonIndexesByHash[$hash]; + return $this->switchColonIndexes; } private function addNewLineAfterIfNecessary(Tokens $tokens, int $index): int From 44ecdc48388acf0671ca844a8d4b26c612aa74c6 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 10 Mar 2024 17:16:08 +0100 Subject: [PATCH 06/19] Fix --- doc/ruleSets/PER-CS2.0.rst | 2 +- doc/ruleSets/PSR12.rst | 2 +- doc/ruleSets/PhpCsFixer.rst | 2 +- .../control_structure/single_expression_per_line.rst | 12 ++++++------ 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/ruleSets/PER-CS2.0.rst b/doc/ruleSets/PER-CS2.0.rst index a48030416fd..dba65aad3ce 100644 --- a/doc/ruleSets/PER-CS2.0.rst +++ b/doc/ruleSets/PER-CS2.0.rst @@ -21,6 +21,6 @@ Rules - `method_argument_space <./../rules/function_notation/method_argument_space.rst>`_ - `single_expression_per_line <./../rules/control_structure/single_expression_per_line.rst>`_ with config: - ``['elements' => ['arrays', 'arguments', 'parameters', 'control_structures', 'case', 'match']]`` + ``['elements' => ['arguments', 'arrays', 'case', 'control_structures', 'match', 'parameters']]`` - `single_line_empty_body <./../rules/basic/single_line_empty_body.rst>`_ diff --git a/doc/ruleSets/PSR12.rst b/doc/ruleSets/PSR12.rst index 159ab4a9aad..194a57f0571 100644 --- a/doc/ruleSets/PSR12.rst +++ b/doc/ruleSets/PSR12.rst @@ -43,7 +43,7 @@ Rules - `short_scalar_cast <./../rules/cast_notation/short_scalar_cast.rst>`_ - `single_expression_per_line <./../rules/control_structure/single_expression_per_line.rst>`_ with config: - ``['elements' => ['arguments', 'parameters', 'control_structures', 'case', 'match']]`` + ``['elements' => ['arguments', 'case', 'control_structures', 'match', 'parameters']]`` - `single_import_per_statement <./../rules/import/single_import_per_statement.rst>`_ with config: diff --git a/doc/ruleSets/PhpCsFixer.rst b/doc/ruleSets/PhpCsFixer.rst index 706c65bd090..c75c4c01a2c 100644 --- a/doc/ruleSets/PhpCsFixer.rst +++ b/doc/ruleSets/PhpCsFixer.rst @@ -66,7 +66,7 @@ Rules - `self_static_accessor <./../rules/class_notation/self_static_accessor.rst>`_ - `single_expression_per_line <./../rules/control_structure/single_expression_per_line.rst>`_ with config: - ``['elements' => ['parameters', 'case', 'match']]`` + ``['elements' => ['case', 'match', 'parameters']]`` - `single_line_comment_style <./../rules/comment/single_line_comment_style.rst>`_ - `single_line_empty_body <./../rules/basic/single_line_empty_body.rst>`_ diff --git a/doc/rules/control_structure/single_expression_per_line.rst b/doc/rules/control_structure/single_expression_per_line.rst index 08ddb603248..3dba476cf27 100644 --- a/doc/rules/control_structure/single_expression_per_line.rst +++ b/doc/rules/control_structure/single_expression_per_line.rst @@ -130,27 +130,27 @@ The rule is part of the following rule sets: - `@PER <./../../ruleSets/PER.rst>`_ with config: - ``['elements' => ['arrays', 'arguments', 'parameters', 'control_structures', 'case', 'match']]`` + ``['elements' => ['arguments', 'arrays', 'case', 'control_structures', 'match', 'parameters']]`` - `@PER-CS <./../../ruleSets/PER-CS.rst>`_ with config: - ``['elements' => ['arrays', 'arguments', 'parameters', 'control_structures', 'case', 'match']]`` + ``['elements' => ['arguments', 'arrays', 'case', 'control_structures', 'match', 'parameters']]`` - `@PER-CS1.0 <./../../ruleSets/PER-CS1.0.rst>`_ with config: - ``['elements' => ['arguments', 'parameters', 'control_structures', 'case', 'match']]`` + ``['elements' => ['arguments', 'case', 'control_structures', 'match', 'parameters']]`` - `@PER-CS2.0 <./../../ruleSets/PER-CS2.0.rst>`_ with config: - ``['elements' => ['arrays', 'arguments', 'parameters', 'control_structures', 'case', 'match']]`` + ``['elements' => ['arguments', 'arrays', 'case', 'control_structures', 'match', 'parameters']]`` - `@PSR12 <./../../ruleSets/PSR12.rst>`_ with config: - ``['elements' => ['arguments', 'parameters', 'control_structures', 'case', 'match']]`` + ``['elements' => ['arguments', 'case', 'control_structures', 'match', 'parameters']]`` - `@PhpCsFixer <./../../ruleSets/PhpCsFixer.rst>`_ with config: - ``['elements' => ['parameters', 'case', 'match']]`` + ``['elements' => ['case', 'match', 'parameters']]`` References From d9f7a0956fde0bc8b62359dd3b6e66a7bff6fd70 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 14 Mar 2024 08:37:01 +0100 Subject: [PATCH 07/19] Simplify --- doc/ruleSets/PhpCsFixer.rst | 4 ---- doc/rules/control_structure/single_expression_per_line.rst | 4 ---- src/RuleSet/Sets/PhpCsFixerSet.php | 7 ------- 3 files changed, 15 deletions(-) diff --git a/doc/ruleSets/PhpCsFixer.rst b/doc/ruleSets/PhpCsFixer.rst index c75c4c01a2c..c9bb1a3254d 100644 --- a/doc/ruleSets/PhpCsFixer.rst +++ b/doc/ruleSets/PhpCsFixer.rst @@ -64,10 +64,6 @@ Rules - `protected_to_private <./../rules/class_notation/protected_to_private.rst>`_ - `return_assignment <./../rules/return_notation/return_assignment.rst>`_ - `self_static_accessor <./../rules/class_notation/self_static_accessor.rst>`_ -- `single_expression_per_line <./../rules/control_structure/single_expression_per_line.rst>`_ with config: - - ``['elements' => ['case', 'match', 'parameters']]`` - - `single_line_comment_style <./../rules/comment/single_line_comment_style.rst>`_ - `single_line_empty_body <./../rules/basic/single_line_empty_body.rst>`_ - `string_implicit_backslashes <./../rules/string_notation/string_implicit_backslashes.rst>`_ with config: diff --git a/doc/rules/control_structure/single_expression_per_line.rst b/doc/rules/control_structure/single_expression_per_line.rst index 3dba476cf27..75541f7fc05 100644 --- a/doc/rules/control_structure/single_expression_per_line.rst +++ b/doc/rules/control_structure/single_expression_per_line.rst @@ -148,10 +148,6 @@ The rule is part of the following rule sets: ``['elements' => ['arguments', 'case', 'control_structures', 'match', 'parameters']]`` -- `@PhpCsFixer <./../../ruleSets/PhpCsFixer.rst>`_ with config: - - ``['elements' => ['case', 'match', 'parameters']]`` - References ---------- diff --git a/src/RuleSet/Sets/PhpCsFixerSet.php b/src/RuleSet/Sets/PhpCsFixerSet.php index aeb7a8b819a..e90a02dd807 100644 --- a/src/RuleSet/Sets/PhpCsFixerSet.php +++ b/src/RuleSet/Sets/PhpCsFixerSet.php @@ -116,13 +116,6 @@ public function getRules(): array 'protected_to_private' => true, 'return_assignment' => true, 'self_static_accessor' => true, - 'single_expression_per_line' => [ - 'elements' => [ - 'case', - 'match', - 'parameters', - ], - ], 'single_line_comment_style' => true, 'single_line_empty_body' => true, 'single_line_throw' => false, From 8b36f538809433ae203f07cf26b6878450a287de Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 15 Mar 2024 19:39:29 +0100 Subject: [PATCH 08/19] Add priority --- .../ControlStructure/SingleExpressionPerLineFixer.php | 11 +++++++++++ src/Fixer/Whitespace/ArrayIndentationFixer.php | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php index b59868d67d3..f55ec53494d 100644 --- a/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php +++ b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php @@ -83,6 +83,17 @@ public function getDefinition(): FixerDefinitionInterface ); } + /** + * {@inheritdoc} + * + * Must run before ArrayIndentationFixer, StatementIndentationFixer. + * Must run after MethodArgumentSpaceFixer. + */ + public function getPriority(): int + { + return 29; + } + public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN, '(']); diff --git a/src/Fixer/Whitespace/ArrayIndentationFixer.php b/src/Fixer/Whitespace/ArrayIndentationFixer.php index 80ef9ef9095..3d02facf564 100644 --- a/src/Fixer/Whitespace/ArrayIndentationFixer.php +++ b/src/Fixer/Whitespace/ArrayIndentationFixer.php @@ -52,7 +52,7 @@ public function isCandidate(Tokens $tokens): bool */ public function getPriority(): int { - return 29; + return 28; } protected function applyFix(\SplFileInfo $file, Tokens $tokens): void From 0e315f783555b92ec9a066273f9a96bdfe391b66 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 17 Mar 2024 19:49:16 +0100 Subject: [PATCH 09/19] Change default --- .../control_structure/single_expression_per_line.rst | 2 +- .../ControlStructure/SingleExpressionPerLineFixer.php | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/doc/rules/control_structure/single_expression_per_line.rst b/doc/rules/control_structure/single_expression_per_line.rst index 75541f7fc05..a7db33303fb 100644 --- a/doc/rules/control_structure/single_expression_per_line.rst +++ b/doc/rules/control_structure/single_expression_per_line.rst @@ -15,7 +15,7 @@ Which expression must have one element by line. Allowed values: a subset of ``['arguments', 'arrays', 'case', 'control_structures', 'match', 'parameters']`` -Default value: ``['arrays']`` +Default value: ``['arrays', 'arguments', 'parameters', 'control_structures', 'case', 'match']`` Examples -------- diff --git a/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php index f55ec53494d..b9f470a4eea 100644 --- a/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php +++ b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php @@ -112,7 +112,14 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn self::SWITCH_CASES, self::MATCH_EXPRESSIONS, ])]) - ->setDefault([self::ELEMENTS_ARRAYS]) + ->setDefault([ + self::ELEMENTS_ARRAYS, + self::ELEMENTS_ARGUMENTS, + self::ELEMENTS_PARAMETERS, + self::ELEMENTS_CONTROL_STRUCTURES, + self::SWITCH_CASES, + self::MATCH_EXPRESSIONS, + ]) ->getOption(), ]); } From 02282aebd09fba7edddb328b72531ef9d1eda755 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 17 Mar 2024 20:34:21 +0100 Subject: [PATCH 10/19] Indent correctly --- .../SingleExpressionPerLineFixer.php | 61 +++++++-- .../SingleExpressionPerLineFixerTest.php | 125 +++++++++++------- 2 files changed, 128 insertions(+), 58 deletions(-) diff --git a/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php index b9f470a4eea..d3279cfba1b 100644 --- a/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php +++ b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -34,7 +35,7 @@ /** * @author Vincent Langlet */ -final class SingleExpressionPerLineFixer extends AbstractFixer implements ConfigurableFixerInterface +final class SingleExpressionPerLineFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { /** * @internal @@ -156,7 +157,7 @@ private function processBlock( } if ($shouldAddNewLine) { - $tokenAddedCount += $this->addNewLineAfterIfNecessary($tokens, $begin); + $tokenAddedCount += $this->addNewLineAfterIfNecessary($tokens, $begin, 1); } $fixArrays = \in_array(self::ELEMENTS_ARRAYS, $this->configuration['elements'], true); @@ -289,7 +290,7 @@ private function processBlock( } if ($shouldAddNewLine) { - $tokenAddedCount += $this->addNewLineBeforeIfNecessary($tokens, $end + $tokenAddedCount); + $tokenAddedCount += $this->addNewLineBeforeIfNecessary($tokens, $end + $tokenAddedCount, -1); } return $tokenAddedCount; @@ -322,36 +323,76 @@ private function getSwitchColonIndexes(Tokens $tokens): array return $this->switchColonIndexes; } - private function addNewLineAfterIfNecessary(Tokens $tokens, int $index): int + private function addNewLineAfterIfNecessary(Tokens $tokens, int $index, int $extraIndentation = 0): int { $next = $tokens->getNextMeaningfulToken($index); if ($tokens->isPartialCodeMultiline($index, $next - 1)) { return 0; } - return $this->addNewLineAfter($tokens, $index); + return $this->addNewLineAfter($tokens, $index, $extraIndentation); } - private function addNewLineBeforeIfNecessary(Tokens $tokens, int $index): int + private function addNewLineBeforeIfNecessary(Tokens $tokens, int $index, int $extraIndentation = 0): int { $previous = $tokens->getPrevMeaningfulToken($index); if ($tokens->isPartialCodeMultiline($previous, $index)) { return 0; } - return $this->addNewLineAfter($tokens, $previous); + return $this->addNewLineAfter($tokens, $previous, $extraIndentation); } - private function addNewLineAfter(Tokens $tokens, int $index): int + private function addNewLineAfter(Tokens $tokens, int $index, int $extraIndentation = 0): int { + $indent = $this->getIndentation($tokens, $index, $extraIndentation); if ($tokens[$index + 1]->isWhitespace()) { - $tokens[$index + 1] = new Token([T_WHITESPACE, "\n"]); + $tokens[$index + 1] = new Token([T_WHITESPACE, "\n".$indent]); return 0; } - $tokens->insertSlices([$index + 1 => new Token([T_WHITESPACE, "\n"])]); + $tokens->insertSlices([$index + 1 => new Token([T_WHITESPACE, "\n".$indent])]); return 1; } + + private function getIndentation(Tokens $tokens, int $index, int $extraIndentation = 0): string + { + // find out what the indentation is + $searchIndex = $index; + do { + $prevWhitespaceTokenIndex = $tokens->getPrevTokenOfKind( + $searchIndex, + [[T_ENCAPSED_AND_WHITESPACE], [T_WHITESPACE]], + ); + + $searchIndex = $prevWhitespaceTokenIndex; + } while ( + null !== $prevWhitespaceTokenIndex + && !str_contains($tokens[$prevWhitespaceTokenIndex]->getContent(), "\n") + && $prevWhitespaceTokenIndex > 1 + ); + + if (null === $prevWhitespaceTokenIndex) { + $existingIndentation = ''; + } elseif (!$tokens[$prevWhitespaceTokenIndex]->isGivenKind(T_WHITESPACE)) { + return ''; + } else { + $existingIndentation = $tokens[$prevWhitespaceTokenIndex]->getContent(); + $lastLineIndex = strrpos($existingIndentation, "\n"); + $existingIndentation = false === $lastLineIndex + ? $existingIndentation + : substr($existingIndentation, $lastLineIndex + 1); + } + + if ($extraIndentation > 0) { + return $existingIndentation.str_repeat($this->whitespacesConfig->getIndent(), $extraIndentation); + } + if ($extraIndentation < 0) { + return substr($existingIndentation, 0, $extraIndentation * strlen($this->whitespacesConfig->getIndent())); + } + + return $existingIndentation; + } } diff --git a/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php b/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php index f018939dcc9..50b030d8d4a 100644 --- a/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php +++ b/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php @@ -16,6 +16,7 @@ use PhpCsFixer\Fixer\ControlStructure\SingleExpressionPerLineFixer; use PhpCsFixer\Tests\Test\AbstractFixerTestCase; +use PhpCsFixer\WhitespacesFixerConfig; /** * @author Vincent Langlet @@ -33,7 +34,24 @@ final class SingleExpressionPerLineFixerTest extends AbstractFixerTestCase */ public function testFix(string $expected, ?string $input = null, array $config = []): void { + $indent = ' '; + $lineEnding = "\n"; + + if (str_contains($expected, "\t")) { + $indent = "\t"; + } elseif (preg_match('/\n \S/', $expected)) { + $indent = ' '; + } + + if (str_contains($expected, "\r")) { + $lineEnding = "\r\n"; + } + $this->fixer->configure($config); + $this->fixer->setWhitespacesConfig(new WhitespacesFixerConfig( + $indent, + $lineEnding + )); $this->doTest($expected, $input); } @@ -48,9 +66,9 @@ public static function provideFixCases(): iterable yield [ ' $value -) { + $foo + as $key => $value + ) { };', ' $value) { + as $key => $value) { };', ['elements' => [SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], ]; @@ -140,14 +151,14 @@ function($a, $b, yield [ ' [SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], ]; @@ -155,13 +166,13 @@ function($a, $b, yield [ ' [SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], ]; @@ -208,9 +219,9 @@ function($a, $b, 'fixer->configure($config); + $this->fixer->setWhitespacesConfig(new WhitespacesFixerConfig( + $indent, + $lineEnding + )); + $this->doTest($expected, $input); } @@ -273,9 +302,9 @@ public static function provideFix80Cases(): iterable yield [ ' 1, 2 => 2, 3, 4 => 4, };', @@ -293,9 +322,9 @@ public static function provideFix80Cases(): iterable match ($a + $b) { 1 => 1, -2 => 2, + 2 => 2, 3, -4 => 4, + 4 => 4, };', ' 1, 2 => 2, 3, 4 => 4, };', ' 1, 2 => 2, 3, 4 => 4, };', From a773dee3881090ac3ad014e21e2b6db5816ef221 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 17 Mar 2024 20:36:42 +0100 Subject: [PATCH 11/19] Update doc --- .../control_structure/single_expression_per_line.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/rules/control_structure/single_expression_per_line.rst b/doc/rules/control_structure/single_expression_per_line.rst index a7db33303fb..c591a0bba13 100644 --- a/doc/rules/control_structure/single_expression_per_line.rst +++ b/doc/rules/control_structure/single_expression_per_line.rst @@ -33,7 +33,7 @@ Example #1 -array(1, - 2); +array( - +1, + + 1, + 2 +); @@ -50,7 +50,7 @@ With configuration: ``['elements' => ['arguments']]``. -foo(1, - 2); +foo( - +1, + + 1, + 2 +); @@ -67,7 +67,7 @@ With configuration: ``['elements' => ['control_structures']]``. -if ($a - && $b) {}; +if ( - +$a + + $a + && $b +) {}; @@ -84,7 +84,7 @@ With configuration: ``['elements' => ['case']]``. switch ($foo) { - case 0: case 1: + case 0: - +case 1: + + case 1: return null; }; @@ -101,7 +101,7 @@ With configuration: ``['elements' => ['parameters']]``. -function foo($x, - $y) +function foo( - +$x, + + $x, + $y +) { @@ -120,7 +120,7 @@ With configuration: ``['elements' => ['match']]``. match($x) { - 1 => 1, 2 => 2 + 1 => 1, - +2 => 2 + + 2 => 2 }; Rule sets From c1055a5184991d7bdfd0a36bbe5f689837b959cd Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 17 Mar 2024 20:44:28 +0100 Subject: [PATCH 12/19] Fix --- src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php | 2 +- tests/AutoReview/FixerFactoryTest.php | 4 ++++ .../ControlStructure/SingleExpressionPerLineFixerTest.php | 1 - 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php index d3279cfba1b..85db4cf7bde 100644 --- a/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php +++ b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php @@ -390,7 +390,7 @@ private function getIndentation(Tokens $tokens, int $index, int $extraIndentatio return $existingIndentation.str_repeat($this->whitespacesConfig->getIndent(), $extraIndentation); } if ($extraIndentation < 0) { - return substr($existingIndentation, 0, $extraIndentation * strlen($this->whitespacesConfig->getIndent())); + return substr($existingIndentation, 0, $extraIndentation * \strlen($this->whitespacesConfig->getIndent())); } return $existingIndentation; diff --git a/tests/AutoReview/FixerFactoryTest.php b/tests/AutoReview/FixerFactoryTest.php index f9188f33758..4dc73213299 100644 --- a/tests/AutoReview/FixerFactoryTest.php +++ b/tests/AutoReview/FixerFactoryTest.php @@ -880,6 +880,10 @@ private static function getFixersPriorityGraph(): array 'single_class_element_per_statement' => [ 'class_attributes_separation', ], + 'single_expression_per_line' => [ + 'array_indentation', + 'statement_indentation', + ], 'single_import_per_statement' => [ 'multiline_whitespace_before_semicolons', 'no_leading_import_slash', diff --git a/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php b/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php index 50b030d8d4a..3fc2864c870 100644 --- a/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php +++ b/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php @@ -290,7 +290,6 @@ public function testFix80(string $expected, ?string $input = null, array $config $lineEnding )); - $this->doTest($expected, $input); } From 514a9ef395dcf8d5b044f351ea61f13db7554aaf Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 17 Mar 2024 21:08:09 +0100 Subject: [PATCH 13/19] Revert "Indent correctly" This reverts commit 02282aebd09fba7edddb328b72531ef9d1eda755. --- .../SingleExpressionPerLineFixer.php | 61 ++------- .../SingleExpressionPerLineFixerTest.php | 124 +++++++----------- 2 files changed, 58 insertions(+), 127 deletions(-) diff --git a/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php index 85db4cf7bde..b9f470a4eea 100644 --- a/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php +++ b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php @@ -16,7 +16,6 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; -use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -35,7 +34,7 @@ /** * @author Vincent Langlet */ -final class SingleExpressionPerLineFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +final class SingleExpressionPerLineFixer extends AbstractFixer implements ConfigurableFixerInterface { /** * @internal @@ -157,7 +156,7 @@ private function processBlock( } if ($shouldAddNewLine) { - $tokenAddedCount += $this->addNewLineAfterIfNecessary($tokens, $begin, 1); + $tokenAddedCount += $this->addNewLineAfterIfNecessary($tokens, $begin); } $fixArrays = \in_array(self::ELEMENTS_ARRAYS, $this->configuration['elements'], true); @@ -290,7 +289,7 @@ private function processBlock( } if ($shouldAddNewLine) { - $tokenAddedCount += $this->addNewLineBeforeIfNecessary($tokens, $end + $tokenAddedCount, -1); + $tokenAddedCount += $this->addNewLineBeforeIfNecessary($tokens, $end + $tokenAddedCount); } return $tokenAddedCount; @@ -323,76 +322,36 @@ private function getSwitchColonIndexes(Tokens $tokens): array return $this->switchColonIndexes; } - private function addNewLineAfterIfNecessary(Tokens $tokens, int $index, int $extraIndentation = 0): int + private function addNewLineAfterIfNecessary(Tokens $tokens, int $index): int { $next = $tokens->getNextMeaningfulToken($index); if ($tokens->isPartialCodeMultiline($index, $next - 1)) { return 0; } - return $this->addNewLineAfter($tokens, $index, $extraIndentation); + return $this->addNewLineAfter($tokens, $index); } - private function addNewLineBeforeIfNecessary(Tokens $tokens, int $index, int $extraIndentation = 0): int + private function addNewLineBeforeIfNecessary(Tokens $tokens, int $index): int { $previous = $tokens->getPrevMeaningfulToken($index); if ($tokens->isPartialCodeMultiline($previous, $index)) { return 0; } - return $this->addNewLineAfter($tokens, $previous, $extraIndentation); + return $this->addNewLineAfter($tokens, $previous); } - private function addNewLineAfter(Tokens $tokens, int $index, int $extraIndentation = 0): int + private function addNewLineAfter(Tokens $tokens, int $index): int { - $indent = $this->getIndentation($tokens, $index, $extraIndentation); if ($tokens[$index + 1]->isWhitespace()) { - $tokens[$index + 1] = new Token([T_WHITESPACE, "\n".$indent]); + $tokens[$index + 1] = new Token([T_WHITESPACE, "\n"]); return 0; } - $tokens->insertSlices([$index + 1 => new Token([T_WHITESPACE, "\n".$indent])]); + $tokens->insertSlices([$index + 1 => new Token([T_WHITESPACE, "\n"])]); return 1; } - - private function getIndentation(Tokens $tokens, int $index, int $extraIndentation = 0): string - { - // find out what the indentation is - $searchIndex = $index; - do { - $prevWhitespaceTokenIndex = $tokens->getPrevTokenOfKind( - $searchIndex, - [[T_ENCAPSED_AND_WHITESPACE], [T_WHITESPACE]], - ); - - $searchIndex = $prevWhitespaceTokenIndex; - } while ( - null !== $prevWhitespaceTokenIndex - && !str_contains($tokens[$prevWhitespaceTokenIndex]->getContent(), "\n") - && $prevWhitespaceTokenIndex > 1 - ); - - if (null === $prevWhitespaceTokenIndex) { - $existingIndentation = ''; - } elseif (!$tokens[$prevWhitespaceTokenIndex]->isGivenKind(T_WHITESPACE)) { - return ''; - } else { - $existingIndentation = $tokens[$prevWhitespaceTokenIndex]->getContent(); - $lastLineIndex = strrpos($existingIndentation, "\n"); - $existingIndentation = false === $lastLineIndex - ? $existingIndentation - : substr($existingIndentation, $lastLineIndex + 1); - } - - if ($extraIndentation > 0) { - return $existingIndentation.str_repeat($this->whitespacesConfig->getIndent(), $extraIndentation); - } - if ($extraIndentation < 0) { - return substr($existingIndentation, 0, $extraIndentation * \strlen($this->whitespacesConfig->getIndent())); - } - - return $existingIndentation; - } } diff --git a/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php b/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php index 3fc2864c870..f018939dcc9 100644 --- a/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php +++ b/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php @@ -16,7 +16,6 @@ use PhpCsFixer\Fixer\ControlStructure\SingleExpressionPerLineFixer; use PhpCsFixer\Tests\Test\AbstractFixerTestCase; -use PhpCsFixer\WhitespacesFixerConfig; /** * @author Vincent Langlet @@ -34,24 +33,7 @@ final class SingleExpressionPerLineFixerTest extends AbstractFixerTestCase */ public function testFix(string $expected, ?string $input = null, array $config = []): void { - $indent = ' '; - $lineEnding = "\n"; - - if (str_contains($expected, "\t")) { - $indent = "\t"; - } elseif (preg_match('/\n \S/', $expected)) { - $indent = ' '; - } - - if (str_contains($expected, "\r")) { - $lineEnding = "\r\n"; - } - $this->fixer->configure($config); - $this->fixer->setWhitespacesConfig(new WhitespacesFixerConfig( - $indent, - $lineEnding - )); $this->doTest($expected, $input); } @@ -66,9 +48,9 @@ public static function provideFixCases(): iterable yield [ ' $value - ) { +$foo + as $key => $value +) { };', ' $value) { + as $key => $value) { };', ['elements' => [SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], ]; @@ -151,14 +140,14 @@ function($a, $b, yield [ ' [SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], ]; @@ -166,13 +155,13 @@ function($a, $b, yield [ ' [SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], ]; @@ -219,9 +208,9 @@ function($a, $b, 'fixer->configure($config); - $this->fixer->setWhitespacesConfig(new WhitespacesFixerConfig( - $indent, - $lineEnding - )); $this->doTest($expected, $input); } @@ -301,9 +273,9 @@ public static function provideFix80Cases(): iterable yield [ ' 1, 2 => 2, 3, 4 => 4, };', @@ -321,9 +293,9 @@ public static function provideFix80Cases(): iterable match ($a + $b) { 1 => 1, - 2 => 2, +2 => 2, 3, - 4 => 4, +4 => 4, };', ' 1, 2 => 2, 3, 4 => 4, };', ' 1, 2 => 2, 3, 4 => 4, };', From e8f5b2450428bc31b263923efb3991217166ec4f Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 17 Mar 2024 21:16:16 +0100 Subject: [PATCH 14/19] Wip --- .../single_expression_per_line.rst | 4 +- .../SingleExpressionPerLineFixer.php | 53 +++++++++++-------- .../SingleExpressionPerLineFixerTest.php | 24 +++++++++ 3 files changed, 56 insertions(+), 25 deletions(-) diff --git a/doc/rules/control_structure/single_expression_per_line.rst b/doc/rules/control_structure/single_expression_per_line.rst index c591a0bba13..926d99208b4 100644 --- a/doc/rules/control_structure/single_expression_per_line.rst +++ b/doc/rules/control_structure/single_expression_per_line.rst @@ -3,7 +3,7 @@ Rule ``single_expression_per_line`` =================================== Multi-line arrays, arguments list, parameters list, control structures, -``switch`` cases and ``match`` expressions should have one element by line. +``switch`` cases and ``match`` expressions should have one element per line. Configuration ------------- @@ -11,7 +11,7 @@ Configuration ``elements`` ~~~~~~~~~~~~ -Which expression must have one element by line. +Which expression must have one element per line. Allowed values: a subset of ``['arguments', 'arrays', 'case', 'control_structures', 'match', 'parameters']`` diff --git a/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php index b9f470a4eea..2d333956ecb 100644 --- a/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php +++ b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php @@ -68,6 +68,13 @@ final class SingleExpressionPerLineFixer extends AbstractFixer implements Config private ?array $switchColonIndexes = null; + private bool $fixArrays = false; + private bool $fixArguments = false; + private bool $fixParameters = false; + private bool $fixControlStructures = false; + private bool $fixSwitchCases = false; + private bool $fixMatch = false; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -126,6 +133,13 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { + $this->fixArrays = \in_array(self::ELEMENTS_ARRAYS, $this->configuration['elements'], true); + $this->fixArguments = \in_array(self::ELEMENTS_ARGUMENTS, $this->configuration['elements'], true); + $this->fixParameters = \in_array(self::ELEMENTS_PARAMETERS, $this->configuration['elements'], true); + $this->fixControlStructures = \in_array(self::ELEMENTS_CONTROL_STRUCTURES, $this->configuration['elements'], true); + $this->fixSwitchCases = \in_array(self::SWITCH_CASES, $this->configuration['elements'], true); + $this->fixMatch = \in_array(self::MATCH_EXPRESSIONS, $this->configuration['elements'], true); + $this->processBlock($tokens, 0, $tokens->count() - 1, false); } @@ -139,8 +153,8 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void * a parameter $shouldAddNewLine to knows if we are currently fixing the structure, which * requires to add newlines, or if we are just looking for the next one to fix. * - * @param null|array $lineDelimiters - * @param null|array $lineDelimitersIndexes + * @param null|non-empty-array $lineDelimiters + * @param null|non-empty-array $lineDelimitersIndexes */ private function processBlock( Tokens $tokens, @@ -159,15 +173,7 @@ private function processBlock( $tokenAddedCount += $this->addNewLineAfterIfNecessary($tokens, $begin); } - $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); - $fixControlStructures = \in_array(self::ELEMENTS_CONTROL_STRUCTURES, $this->configuration['elements'], true); - $fixSwitchCases = \in_array(self::SWITCH_CASES, $this->configuration['elements'], true); - $fixMatch = \in_array(self::MATCH_EXPRESSIONS, $this->configuration['elements'], true); - for ($index = $begin + 1; $index < $end + $tokenAddedCount; ++$index) { - /** @var Token $token */ $token = $tokens[$index]; if ( @@ -182,9 +188,9 @@ private function processBlock( continue; } - if ($tokens[$index]->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { + if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index); - $added = $this->processBlock($tokens, $index, $until, $fixArrays, [',']); + $added = $this->processBlock($tokens, $index, $until, $this->fixArrays, [',']); $index = $until + $added; $tokenAddedCount += $added; @@ -192,14 +198,14 @@ private function processBlock( continue; } - if (!$tokens[$index]->equals('(')) { + if (!$token->equals('(')) { continue; } $prevIndex = $tokens->getPrevMeaningfulToken($index); if ($tokens[$prevIndex]->isGivenKind(T_ARRAY)) { $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); - $added = $this->processBlock($tokens, $index, $until, $fixArrays, [',']); + $added = $this->processBlock($tokens, $index, $until, $this->fixArrays, [',']); $index = $until + $added; $tokenAddedCount += $added; @@ -209,7 +215,7 @@ private function processBlock( if ($tokens[$prevIndex]->isGivenKind([T_IF, T_ELSEIF, T_WHILE, T_FOREACH, T_CATCH])) { $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); - $added = $this->processBlock($tokens, $index, $until, $fixControlStructures); + $added = $this->processBlock($tokens, $index, $until, $this->fixControlStructures); $index = $until + $added; $tokenAddedCount += $added; @@ -219,7 +225,7 @@ private function processBlock( if ($tokens[$prevIndex]->isGivenKind(T_FOR)) { $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); - $added = $this->processBlock($tokens, $index, $until, $fixControlStructures, [';']); + $added = $this->processBlock($tokens, $index, $until, $this->fixControlStructures, [';']); $index = $until + $added; $tokenAddedCount += $added; @@ -228,11 +234,12 @@ private function processBlock( } $prevPrevIndex = $tokens->getPrevMeaningfulToken($prevIndex); - if ($tokens[$prevIndex]->equalsAny([']', [T_CLASS], [T_STRING], [T_VARIABLE], [T_STATIC]]) + if ( + $tokens[$prevIndex]->equalsAny([']', [T_CLASS], [T_STRING], [T_VARIABLE], [T_STATIC]]) && !$tokens[$prevPrevIndex]->isGivenKind(T_FUNCTION) ) { $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); - $added = $this->processBlock($tokens, $index, $until, $fixArguments, [',']); + $added = $this->processBlock($tokens, $index, $until, $this->fixArguments, [',']); $index = $until + $added; $tokenAddedCount += $added; @@ -245,7 +252,7 @@ private function processBlock( || $tokens[$prevIndex]->isGivenKind(T_STRING) && $tokens[$prevPrevIndex]->isGivenKind(T_FUNCTION) ) { $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); - $added = $this->processBlock($tokens, $index, $until, $fixParameters, [',']); + $added = $this->processBlock($tokens, $index, $until, $this->fixParameters, [',']); $index = $until + $added; $tokenAddedCount += $added; @@ -255,7 +262,7 @@ private function processBlock( if ($tokens[$prevIndex]->isGivenKind(T_SWITCH)) { $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); - $tokenAddedCount += $this->processBlock($tokens, $index, $until, $fixControlStructures); + $tokenAddedCount += $this->processBlock($tokens, $index, $until, $this->fixControlStructures); $index = $tokens->getNextTokenOfKind($index, ['{']); $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); @@ -263,7 +270,7 @@ private function processBlock( $tokens, $index, $until, - $fixSwitchCases, + $this->fixSwitchCases, [':', ';'], $this->getSwitchColonIndexes($tokens) ); @@ -277,11 +284,11 @@ private function processBlock( // @TODO: remove defined condition when PHP 8.0+ is required if (\defined('T_MATCH') && $tokens[$prevIndex]->isGivenKind(T_MATCH)) { $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); - $tokenAddedCount += $this->processBlock($tokens, $index, $until, $fixControlStructures); + $tokenAddedCount += $this->processBlock($tokens, $index, $until, $this->fixControlStructures); $index = $tokens->getNextTokenOfKind($index, ['{']); $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); - $added = $this->processBlock($tokens, $index, $until, $fixMatch, [',']); + $added = $this->processBlock($tokens, $index, $until, $this->fixMatch, [',']); $index = $until + $added; $tokenAddedCount += $added; diff --git a/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php b/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php index f018939dcc9..4d013d13a92 100644 --- a/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php +++ b/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php @@ -70,6 +70,30 @@ public static function provideFixCases(): iterable ];', ]; + yield [ + ' "foo", +2 => "bar", + ];', + ' "foo", 2 => "bar", + ];', + ]; + + yield [ + ' Date: Sun, 17 Mar 2024 22:26:22 +0100 Subject: [PATCH 15/19] Fix --- .../single_expression_per_line.rst | 16 ++++++++-------- .../Whitespace/ArrayIndentationFixer.php | 2 +- .../Whitespace/StatementIndentationFixer.php | 2 +- src/RuleSet/Sets/PERCS2x0Set.php | 11 +---------- .../SingleExpressionPerLineFixerTest.php | 7 ------- ...expression_per_line,array_indentation.test | 15 +++++++++++++++ ...ession_per_line,statement_indentation.test | 19 +++++++++++++++++++ 7 files changed, 45 insertions(+), 27 deletions(-) create mode 100644 tests/Fixtures/Integration/priority/single_expression_per_line,array_indentation.test create mode 100644 tests/Fixtures/Integration/priority/single_expression_per_line,statement_indentation.test diff --git a/doc/rules/control_structure/single_expression_per_line.rst b/doc/rules/control_structure/single_expression_per_line.rst index 926d99208b4..a7db33303fb 100644 --- a/doc/rules/control_structure/single_expression_per_line.rst +++ b/doc/rules/control_structure/single_expression_per_line.rst @@ -3,7 +3,7 @@ Rule ``single_expression_per_line`` =================================== Multi-line arrays, arguments list, parameters list, control structures, -``switch`` cases and ``match`` expressions should have one element per line. +``switch`` cases and ``match`` expressions should have one element by line. Configuration ------------- @@ -11,7 +11,7 @@ Configuration ``elements`` ~~~~~~~~~~~~ -Which expression must have one element per line. +Which expression must have one element by line. Allowed values: a subset of ``['arguments', 'arrays', 'case', 'control_structures', 'match', 'parameters']`` @@ -33,7 +33,7 @@ Example #1 -array(1, - 2); +array( - + 1, + +1, + 2 +); @@ -50,7 +50,7 @@ With configuration: ``['elements' => ['arguments']]``. -foo(1, - 2); +foo( - + 1, + +1, + 2 +); @@ -67,7 +67,7 @@ With configuration: ``['elements' => ['control_structures']]``. -if ($a - && $b) {}; +if ( - + $a + +$a + && $b +) {}; @@ -84,7 +84,7 @@ With configuration: ``['elements' => ['case']]``. switch ($foo) { - case 0: case 1: + case 0: - + case 1: + +case 1: return null; }; @@ -101,7 +101,7 @@ With configuration: ``['elements' => ['parameters']]``. -function foo($x, - $y) +function foo( - + $x, + +$x, + $y +) { @@ -120,7 +120,7 @@ With configuration: ``['elements' => ['match']]``. match($x) { - 1 => 1, 2 => 2 + 1 => 1, - + 2 => 2 + +2 => 2 }; Rule sets diff --git a/src/Fixer/Whitespace/ArrayIndentationFixer.php b/src/Fixer/Whitespace/ArrayIndentationFixer.php index 3d02facf564..fd42055a72d 100644 --- a/src/Fixer/Whitespace/ArrayIndentationFixer.php +++ b/src/Fixer/Whitespace/ArrayIndentationFixer.php @@ -48,7 +48,7 @@ public function isCandidate(Tokens $tokens): bool * {@inheritdoc} * * Must run before AlignMultilineCommentFixer, BinaryOperatorSpacesFixer. - * Must run after MethodArgumentSpaceFixer. + * Must run after MethodArgumentSpaceFixer, SingleExpressionPerLineFixer. */ public function getPriority(): int { diff --git a/src/Fixer/Whitespace/StatementIndentationFixer.php b/src/Fixer/Whitespace/StatementIndentationFixer.php index 6f8785e7c86..f4cf9e6e821 100644 --- a/src/Fixer/Whitespace/StatementIndentationFixer.php +++ b/src/Fixer/Whitespace/StatementIndentationFixer.php @@ -97,7 +97,7 @@ public function getDefinition(): FixerDefinitionInterface * {@inheritdoc} * * Must run before HeredocIndentationFixer. - * Must run after BracesPositionFixer, ClassAttributesSeparationFixer, CurlyBracesPositionFixer, FullyQualifiedStrictTypesFixer, GlobalNamespaceImportFixer, MethodArgumentSpaceFixer, NoUselessElseFixer, YieldFromArrayToYieldsFixer. + * Must run after BracesPositionFixer, ClassAttributesSeparationFixer, CurlyBracesPositionFixer, FullyQualifiedStrictTypesFixer, GlobalNamespaceImportFixer, MethodArgumentSpaceFixer, NoUselessElseFixer, SingleExpressionPerLineFixer, YieldFromArrayToYieldsFixer. */ public function getPriority(): int { diff --git a/src/RuleSet/Sets/PERCS2x0Set.php b/src/RuleSet/Sets/PERCS2x0Set.php index 51157b10857..e9125cef983 100644 --- a/src/RuleSet/Sets/PERCS2x0Set.php +++ b/src/RuleSet/Sets/PERCS2x0Set.php @@ -41,16 +41,7 @@ public function getRules(): array 'closure_fn_spacing' => 'none', ], 'method_argument_space' => true, - 'single_expression_per_line' => [ - 'elements' => [ - 'arguments', - 'arrays', - 'case', - 'control_structures', - 'match', - 'parameters', - ], - ], + 'single_expression_per_line' => true, 'single_line_empty_body' => true, ]; } diff --git a/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php b/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php index 4d013d13a92..11c2520d784 100644 --- a/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php +++ b/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php @@ -112,13 +112,6 @@ public static function provideFixCases(): iterable yield [' Date: Thu, 21 Mar 2024 09:59:35 +0100 Subject: [PATCH 16/19] Rework about what is multiline --- .../SingleExpressionPerLineFixer.php | 103 +++++++++++++----- .../SingleExpressionPerLineFixerTest.php | 39 ++++++- 2 files changed, 108 insertions(+), 34 deletions(-) diff --git a/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php index 2d333956ecb..8cb79f02b0e 100644 --- a/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php +++ b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php @@ -153,36 +153,46 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void * a parameter $shouldAddNewLine to knows if we are currently fixing the structure, which * requires to add newlines, or if we are just looking for the next one to fix. * - * @param null|non-empty-array $lineDelimiters - * @param null|non-empty-array $lineDelimitersIndexes + * @param null|non-empty-array $expressionDelimiters + * @param null|non-empty-array $expressionDelimitersIndexes */ private function processBlock( Tokens $tokens, int $begin, int $end, bool $shouldAddNewLine, - ?array $lineDelimiters = null, - ?array $lineDelimitersIndexes = null + bool $enforceOneExpressionPerLine = false, + ?array $expressionDelimiters = null, + ?array $expressionDelimitersIndexes = null + ): int { - $tokenAddedCount = 0; if (!$tokens->isPartialCodeMultiline($begin, $end)) { - return $tokenAddedCount; + return 0; } + $isMultiline = false; + $tokenAddedCount = 0; + $indexWithNewLineNeeded = []; if ($shouldAddNewLine) { - $tokenAddedCount += $this->addNewLineAfterIfNecessary($tokens, $begin); + $indexWithNewLineNeeded += $this->shouldAddNewLineAfter($tokens, $begin); } for ($index = $begin + 1; $index < $end + $tokenAddedCount; ++$index) { $token = $tokens[$index]; if ( - null !== $lineDelimiters - && $token->equalsAny($lineDelimiters) - && (null === $lineDelimitersIndexes || \in_array($index, $lineDelimitersIndexes, true)) + null !== $expressionDelimiters + && \in_array($token->getContent(), $expressionDelimiters, true) + && (null === $expressionDelimitersIndexes || \in_array($index, $expressionDelimitersIndexes, true)) ) { if ($shouldAddNewLine) { - $tokenAddedCount += $this->addNewLineAfterIfNecessary($tokens, $index); + if ($enforceOneExpressionPerLine) { + $indexWithNewLineNeeded += $this->shouldAddNewLineAfter($tokens, $index); + } else { + $isMultiline = $isMultiline + || !array_values($this->shouldAddNewLineAfter($tokens, $index))[0] + || !array_values($this->shouldAddNewLineBefore($tokens, $index))[0]; + } } continue; @@ -190,7 +200,7 @@ private function processBlock( if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index); - $added = $this->processBlock($tokens, $index, $until, $this->fixArrays, [',']); + $added = $this->processBlock($tokens, $index, $until, $this->fixArrays, true, [',']); $index = $until + $added; $tokenAddedCount += $added; @@ -205,7 +215,27 @@ private function processBlock( $prevIndex = $tokens->getPrevMeaningfulToken($index); if ($tokens[$prevIndex]->isGivenKind(T_ARRAY)) { $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); - $added = $this->processBlock($tokens, $index, $until, $this->fixArrays, [',']); + $added = $this->processBlock($tokens, $index, $until, $this->fixArrays, true, [',']); + + $index = $until + $added; + $tokenAddedCount += $added; + + continue; + } + + if ($tokens[$prevIndex]->isGivenKind([T_IF, T_ELSEIF, T_WHILE])) { + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $added = $this->processBlock($tokens, $index, $until, $this->fixControlStructures, false, ['&&', '||']); + + $index = $until + $added; + $tokenAddedCount += $added; + + continue; + } + + if ($tokens[$prevIndex]->isGivenKind(T_FOREACH)) { + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $added = $this->processBlock($tokens, $index, $until, $this->fixControlStructures, false, ['as']); $index = $until + $added; $tokenAddedCount += $added; @@ -213,9 +243,9 @@ private function processBlock( continue; } - if ($tokens[$prevIndex]->isGivenKind([T_IF, T_ELSEIF, T_WHILE, T_FOREACH, T_CATCH])) { + if ($tokens[$prevIndex]->isGivenKind(T_CATCH)) { $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); - $added = $this->processBlock($tokens, $index, $until, $this->fixControlStructures); + $added = $this->processBlock($tokens, $index, $until, $this->fixControlStructures, false, ['|']); $index = $until + $added; $tokenAddedCount += $added; @@ -225,7 +255,7 @@ private function processBlock( if ($tokens[$prevIndex]->isGivenKind(T_FOR)) { $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); - $added = $this->processBlock($tokens, $index, $until, $this->fixControlStructures, [';']); + $added = $this->processBlock($tokens, $index, $until, $this->fixControlStructures, true, [';']); $index = $until + $added; $tokenAddedCount += $added; @@ -239,7 +269,7 @@ private function processBlock( && !$tokens[$prevPrevIndex]->isGivenKind(T_FUNCTION) ) { $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); - $added = $this->processBlock($tokens, $index, $until, $this->fixArguments, [',']); + $added = $this->processBlock($tokens, $index, $until, $this->fixArguments, true, [',']); $index = $until + $added; $tokenAddedCount += $added; @@ -252,7 +282,7 @@ private function processBlock( || $tokens[$prevIndex]->isGivenKind(T_STRING) && $tokens[$prevPrevIndex]->isGivenKind(T_FUNCTION) ) { $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); - $added = $this->processBlock($tokens, $index, $until, $this->fixParameters, [',']); + $added = $this->processBlock($tokens, $index, $until, $this->fixParameters, true, [',']); $index = $until + $added; $tokenAddedCount += $added; @@ -262,7 +292,7 @@ private function processBlock( if ($tokens[$prevIndex]->isGivenKind(T_SWITCH)) { $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); - $tokenAddedCount += $this->processBlock($tokens, $index, $until, $this->fixControlStructures); + $tokenAddedCount += $this->processBlock($tokens, $index, $until, $this->fixControlStructures, false, ['&&', '||']); $index = $tokens->getNextTokenOfKind($index, ['{']); $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); @@ -271,6 +301,7 @@ private function processBlock( $index, $until, $this->fixSwitchCases, + true, [':', ';'], $this->getSwitchColonIndexes($tokens) ); @@ -284,11 +315,11 @@ private function processBlock( // @TODO: remove defined condition when PHP 8.0+ is required if (\defined('T_MATCH') && $tokens[$prevIndex]->isGivenKind(T_MATCH)) { $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); - $tokenAddedCount += $this->processBlock($tokens, $index, $until, $this->fixControlStructures); + $tokenAddedCount += $this->processBlock($tokens, $index, $until, $this->fixControlStructures, false, ['&&', '||']); $index = $tokens->getNextTokenOfKind($index, ['{']); $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); - $added = $this->processBlock($tokens, $index, $until, $this->fixMatch, [',']); + $added = $this->processBlock($tokens, $index, $until, $this->fixMatch, true, [',']); $index = $until + $added; $tokenAddedCount += $added; @@ -296,7 +327,17 @@ private function processBlock( } if ($shouldAddNewLine) { - $tokenAddedCount += $this->addNewLineBeforeIfNecessary($tokens, $end + $tokenAddedCount); + $indexWithNewLineNeeded += $this->shouldAddNewLineBefore($tokens, $end + $tokenAddedCount); + } + + if ($isMultiline || \in_array(false, $indexWithNewLineNeeded, true)) { + $increment = 0; + foreach ($indexWithNewLineNeeded as $index => $shouldAddNewLineAfter) { + if ($shouldAddNewLineAfter) { + $increment += $this->addNewLineAfter($tokens, $index + $increment); + } + } + $tokenAddedCount += $increment; } return $tokenAddedCount; @@ -329,24 +370,30 @@ private function getSwitchColonIndexes(Tokens $tokens): array return $this->switchColonIndexes; } - private function addNewLineAfterIfNecessary(Tokens $tokens, int $index): int + /** + * @return non-empty-array + */ + private function shouldAddNewLineAfter(Tokens $tokens, int $index): array { $next = $tokens->getNextMeaningfulToken($index); if ($tokens->isPartialCodeMultiline($index, $next - 1)) { - return 0; + return [$index => false]; } - return $this->addNewLineAfter($tokens, $index); + return [$index => true]; } - private function addNewLineBeforeIfNecessary(Tokens $tokens, int $index): int + /** + * @return non-empty-array + */ + private function shouldAddNewLineBefore(Tokens $tokens, int $index): array { $previous = $tokens->getPrevMeaningfulToken($index); if ($tokens->isPartialCodeMultiline($previous, $index)) { - return 0; + return [$previous => false]; } - return $this->addNewLineAfter($tokens, $previous); + return [$previous => true]; } private function addNewLineAfter(Tokens $tokens, int $index): int diff --git a/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php b/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php index 11c2520d784..d18de34e1d9 100644 --- a/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php +++ b/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php @@ -266,6 +266,24 @@ function($a, $b, null, ['elements' => [SingleExpressionPerLineFixer::SWITCH_CASES]], ]; + + yield [ + 'get("/hello/{name}", function ($name) use ($app) { + return "Hello " . $app->escape($name); + }); + ', + ]; + + yield [ + ' 1, 2 => 2, + 3, 4 => 4, + };', + null, + ['elements' => [SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ]; + yield [ ' 1, 2 => 2, 3, 4 => 4, };', ' 1, 2 => 2, 3, 4 => 4, };', @@ -325,12 +354,10 @@ public static function provideFix80Cases(): iterable yield [ ' 1, 2 => 2, 3, 4 => 4, };', From 8e35b841e3ef98675e5a5481058306265c727b6e Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 21 Mar 2024 10:05:35 +0100 Subject: [PATCH 17/19] docs --- doc/ruleSets/PER-CS2.0.rst | 5 +---- .../single_expression_per_line.rst | 15 +++------------ 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/doc/ruleSets/PER-CS2.0.rst b/doc/ruleSets/PER-CS2.0.rst index dba65aad3ce..401051e2d3d 100644 --- a/doc/ruleSets/PER-CS2.0.rst +++ b/doc/ruleSets/PER-CS2.0.rst @@ -19,8 +19,5 @@ Rules ``['closure_fn_spacing' => 'none']`` - `method_argument_space <./../rules/function_notation/method_argument_space.rst>`_ -- `single_expression_per_line <./../rules/control_structure/single_expression_per_line.rst>`_ with config: - - ``['elements' => ['arguments', 'arrays', 'case', 'control_structures', 'match', 'parameters']]`` - +- `single_expression_per_line <./../rules/control_structure/single_expression_per_line.rst>`_ - `single_line_empty_body <./../rules/basic/single_line_empty_body.rst>`_ diff --git a/doc/rules/control_structure/single_expression_per_line.rst b/doc/rules/control_structure/single_expression_per_line.rst index a7db33303fb..220b453d213 100644 --- a/doc/rules/control_structure/single_expression_per_line.rst +++ b/doc/rules/control_structure/single_expression_per_line.rst @@ -128,22 +128,13 @@ Rule sets The rule is part of the following rule sets: -- `@PER <./../../ruleSets/PER.rst>`_ with config: - - ``['elements' => ['arguments', 'arrays', 'case', 'control_structures', 'match', 'parameters']]`` - -- `@PER-CS <./../../ruleSets/PER-CS.rst>`_ with config: - - ``['elements' => ['arguments', 'arrays', 'case', 'control_structures', 'match', 'parameters']]`` - +- `@PER <./../../ruleSets/PER.rst>`_ +- `@PER-CS <./../../ruleSets/PER-CS.rst>`_ - `@PER-CS1.0 <./../../ruleSets/PER-CS1.0.rst>`_ with config: ``['elements' => ['arguments', 'case', 'control_structures', 'match', 'parameters']]`` -- `@PER-CS2.0 <./../../ruleSets/PER-CS2.0.rst>`_ with config: - - ``['elements' => ['arguments', 'arrays', 'case', 'control_structures', 'match', 'parameters']]`` - +- `@PER-CS2.0 <./../../ruleSets/PER-CS2.0.rst>`_ - `@PSR12 <./../../ruleSets/PSR12.rst>`_ with config: ``['elements' => ['arguments', 'case', 'control_structures', 'match', 'parameters']]`` From 31c8f19edadd504f14ab96fc2347e0dbc5366806 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 21 Mar 2024 10:25:08 +0100 Subject: [PATCH 18/19] Fix --- src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php index 8cb79f02b0e..dce6d9c206b 100644 --- a/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php +++ b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php @@ -164,7 +164,6 @@ private function processBlock( bool $enforceOneExpressionPerLine = false, ?array $expressionDelimiters = null, ?array $expressionDelimitersIndexes = null - ): int { if (!$tokens->isPartialCodeMultiline($begin, $end)) { return 0; From 4b1a1815cd12f55bb39f23cc3651eb2ce480855d Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 22 Mar 2024 12:18:09 +0100 Subject: [PATCH 19/19] Fix --- src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php index dce6d9c206b..cb9087aba6c 100644 --- a/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php +++ b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php @@ -94,7 +94,6 @@ public function getDefinition(): FixerDefinitionInterface * {@inheritdoc} * * Must run before ArrayIndentationFixer, StatementIndentationFixer. - * Must run after MethodArgumentSpaceFixer. */ public function getPriority(): int {