diff --git a/doc/ruleSets/PER-CS2.0.rst b/doc/ruleSets/PER-CS2.0.rst index 65901090299..401051e2d3d 100644 --- a/doc/ruleSets/PER-CS2.0.rst +++ b/doc/ruleSets/PER-CS2.0.rst @@ -19,4 +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>`_ - `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..194a57f0571 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', 'case', 'control_structures', 'match', 'parameters']]`` + - `single_import_per_statement <./../rules/import/single_import_per_statement.rst>`_ with config: ``['group_to_single_imports' => false]`` 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 new file mode 100644 index 00000000000..220b453d213 --- /dev/null +++ b/doc/rules/control_structure/single_expression_per_line.rst @@ -0,0 +1,149 @@ +=================================== +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. + +Configuration +------------- + +``elements`` +~~~~~~~~~~~~ + +Which expression must have one element by line. + +Allowed values: a subset of ``['arguments', 'arrays', 'case', 'control_structures', 'match', 'parameters']`` + +Default value: ``['arrays', 'arguments', 'parameters', 'control_structures', 'case', 'match']`` + +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 + }; + +Rule sets +--------- + +The rule is part of the following rule sets: + +- `@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>`_ +- `@PSR12 <./../../ruleSets/PSR12.rst>`_ with config: + + ``['elements' => ['arguments', 'case', 'control_structures', 'match', 'parameters']]`` + + +References +---------- + +- 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 62c7cb522fd..46dc1f07e2f 100644 --- a/doc/rules/index.rst +++ b/doc/rules/index.rst @@ -321,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/SingleExpressionPerLineFixer.php b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php new file mode 100644 index 00000000000..cb9087aba6c --- /dev/null +++ b/src/Fixer/ControlStructure/SingleExpressionPerLineFixer.php @@ -0,0 +1,409 @@ + + * 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\Analyzer\Analysis\SwitchAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\ControlCaseStructuresAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Vincent Langlet + */ +final class SingleExpressionPerLineFixer 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'; + + 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( + '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]]), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before ArrayIndentationFixer, StatementIndentationFixer. + */ + public function getPriority(): int + { + return 29; + } + + 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', 'Which expression must have one element by line.')) + ->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, + self::ELEMENTS_ARGUMENTS, + self::ELEMENTS_PARAMETERS, + self::ELEMENTS_CONTROL_STRUCTURES, + self::SWITCH_CASES, + self::MATCH_EXPRESSIONS, + ]) + ->getOption(), + ]); + } + + 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); + } + + /** + * 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. + * + * @param null|non-empty-array $expressionDelimiters + * @param null|non-empty-array $expressionDelimitersIndexes + */ + private function processBlock( + Tokens $tokens, + int $begin, + int $end, + bool $shouldAddNewLine, + bool $enforceOneExpressionPerLine = false, + ?array $expressionDelimiters = null, + ?array $expressionDelimitersIndexes = null + ): int { + if (!$tokens->isPartialCodeMultiline($begin, $end)) { + return 0; + } + + $isMultiline = false; + $tokenAddedCount = 0; + $indexWithNewLineNeeded = []; + if ($shouldAddNewLine) { + $indexWithNewLineNeeded += $this->shouldAddNewLineAfter($tokens, $begin); + } + + for ($index = $begin + 1; $index < $end + $tokenAddedCount; ++$index) { + $token = $tokens[$index]; + + if ( + null !== $expressionDelimiters + && \in_array($token->getContent(), $expressionDelimiters, true) + && (null === $expressionDelimitersIndexes || \in_array($index, $expressionDelimitersIndexes, true)) + ) { + if ($shouldAddNewLine) { + 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; + } + + 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, true, [',']); + + $index = $until + $added; + $tokenAddedCount += $added; + + continue; + } + + 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, $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; + + continue; + } + + if ($tokens[$prevIndex]->isGivenKind(T_CATCH)) { + $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_FOR)) { + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $added = $this->processBlock($tokens, $index, $until, $this->fixControlStructures, true, [';']); + + $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, $this->fixArguments, true, [',']); + + $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, $this->fixParameters, true, [',']); + + $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, $this->fixControlStructures, false, ['&&', '||']); + + $index = $tokens->getNextTokenOfKind($index, ['{']); + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + $added = $this->processBlock( + $tokens, + $index, + $until, + $this->fixSwitchCases, + true, + [':', ';'], + $this->getSwitchColonIndexes($tokens) + ); + + $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, $this->fixControlStructures, false, ['&&', '||']); + + $index = $tokens->getNextTokenOfKind($index, ['{']); + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + $added = $this->processBlock($tokens, $index, $until, $this->fixMatch, true, [',']); + + $index = $until + $added; + $tokenAddedCount += $added; + } + } + + if ($shouldAddNewLine) { + $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; + } + + /** + * @return array + */ + private function getSwitchColonIndexes(Tokens $tokens): array + { + if (null === $this->switchColonIndexes) { + $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(); + } + } + + $this->switchColonIndexes = $colonIndexes; + } + + return $this->switchColonIndexes; + } + + /** + * @return non-empty-array + */ + private function shouldAddNewLineAfter(Tokens $tokens, int $index): array + { + $next = $tokens->getNextMeaningfulToken($index); + if ($tokens->isPartialCodeMultiline($index, $next - 1)) { + return [$index => false]; + } + + return [$index => true]; + } + + /** + * @return non-empty-array + */ + private function shouldAddNewLineBefore(Tokens $tokens, int $index): array + { + $previous = $tokens->getPrevMeaningfulToken($index); + if ($tokens->isPartialCodeMultiline($previous, $index)) { + return [$previous => false]; + } + + return [$previous => true]; + } + + 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/src/Fixer/Whitespace/ArrayIndentationFixer.php b/src/Fixer/Whitespace/ArrayIndentationFixer.php index 80ef9ef9095..fd42055a72d 100644 --- a/src/Fixer/Whitespace/ArrayIndentationFixer.php +++ b/src/Fixer/Whitespace/ArrayIndentationFixer.php @@ -48,11 +48,11 @@ 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 { - return 29; + return 28; } protected function applyFix(\SplFileInfo $file, Tokens $tokens): void 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 a5c28b19929..e9125cef983 100644 --- a/src/RuleSet/Sets/PERCS2x0Set.php +++ b/src/RuleSet/Sets/PERCS2x0Set.php @@ -41,6 +41,7 @@ public function getRules(): array 'closure_fn_spacing' => 'none', ], 'method_argument_space' => true, + 'single_expression_per_line' => true, '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/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' => [ 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 new file mode 100644 index 00000000000..d18de34e1d9 --- /dev/null +++ b/tests/Fixer/ControlStructure/SingleExpressionPerLineFixerTest.php @@ -0,0 +1,373 @@ + + * 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\SingleExpressionPerLineFixer; +use PhpCsFixer\Tests\Test\AbstractFixerTestCase; + +/** + * @author Vincent Langlet + * + * @internal + * + * @covers \PhpCsFixer\Fixer\ControlStructure\SingleExpressionPerLineFixer + */ +final class SingleExpressionPerLineFixerTest 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 [' "foo", +2 => "bar", + ];', + ' "foo", 2 => "bar", + ];', + ]; + + yield [ + ' [SingleExpressionPerLineFixer::ELEMENTS_ARGUMENTS]], + ]; + + yield [ + ' [SingleExpressionPerLineFixer::ELEMENTS_PARAMETERS]], + ]; + + yield [ + ' $value +) { + };', + ' $value) { + };', + ['elements' => [SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ]; + + yield [ + ' [SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ]; + + yield [ + ' [SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ]; + + yield [ + ' [SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ]; + + yield [ + ' [SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ]; + + yield [ + ' [SingleExpressionPerLineFixer::SWITCH_CASES]], + ]; + + yield [ + ' [SingleExpressionPerLineFixer::SWITCH_CASES]], + ]; + + yield [ + 'get("/hello/{name}", function ($name) use ($app) { + return "Hello " . $app->escape($name); + }); + ', + ]; + + yield [ + ' $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, + };', + null, + ['elements' => [SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ]; + + yield [ + ' 1, 2 => 2, + 3, 4 => 4, + };', + ' 1, 2 => 2, + 3, 4 => 4, + };', + ['elements' => [SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ]; + + yield [ + ' 1, +2 => 2, + 3, +4 => 4, + };', + ' 1, 2 => 2, + 3, 4 => 4, + };', + ['elements' => [SingleExpressionPerLineFixer::MATCH_EXPRESSIONS]], + ]; + + yield [ + ' 1, 2 => 2, + 3, 4 => 4, + };', + ' 1, 2 => 2, + 3, 4 => 4, + };', + ['elements' => [SingleExpressionPerLineFixer::ELEMENTS_ARGUMENTS, SingleExpressionPerLineFixer::ELEMENTS_CONTROL_STRUCTURES]], + ]; + } +} diff --git a/tests/Fixtures/Integration/priority/single_expression_per_line,array_indentation.test b/tests/Fixtures/Integration/priority/single_expression_per_line,array_indentation.test new file mode 100644 index 00000000000..759bfcbd7fb --- /dev/null +++ b/tests/Fixtures/Integration/priority/single_expression_per_line,array_indentation.test @@ -0,0 +1,15 @@ +--TEST-- +Integration of fixers: single_expression_per_line,array_indentation. +--RULESET-- +{ "single_expression_per_line": true, "array_indentation": true} +--EXPECT-- +