From da678f7079c2ca519887e379850722b7027a9fb3 Mon Sep 17 00:00:00 2001 From: Dawid Parafinski Date: Fri, 10 Oct 2025 15:54:33 +0200 Subject: [PATCH 1/6] Added rule for splitting multiple methods arguments into multiline if there is more than one --- .../Rule/MultilineParametersFixer.php | 178 ++++++++++++++++++ .../PhpCsFixer/Sets/AbstractIbexaRuleSet.php | 4 +- .../PhpCsFixer/InternalConfigFactoryTest.php | 6 +- .../Rule/MultilineParametersFixerTest.php | 112 +++++++++++ .../4_6_rule_set/local_rules.php | 1 + .../4_6_rule_set/php_cs_fixer_rules.php | 1 + .../5_0_rule_set/local_rules.php | 1 + .../5_0_rule_set/php_cs_fixer_rules.php | 1 + 8 files changed, 301 insertions(+), 3 deletions(-) create mode 100644 src/lib/PhpCsFixer/Rule/MultilineParametersFixer.php create mode 100644 tests/lib/PhpCsFixer/Rule/MultilineParametersFixerTest.php diff --git a/src/lib/PhpCsFixer/Rule/MultilineParametersFixer.php b/src/lib/PhpCsFixer/Rule/MultilineParametersFixer.php new file mode 100644 index 0000000..617765c --- /dev/null +++ b/src/lib/PhpCsFixer/Rule/MultilineParametersFixer.php @@ -0,0 +1,178 @@ +isTokenKindFound(T_FUNCTION); + } + + protected function applyFix( + \SplFileInfo $file, + Tokens $tokens + ): void { + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { + continue; + } + + $openParenIndex = $tokens->getNextTokenOfKind($index, ['(']); + $closeParenIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenIndex); + + // Count commas to determine parameter count + $paramCount = $this->countParameters($tokens, $openParenIndex, $closeParenIndex); + + // Only process if 2+ parameters + if ($paramCount < 2) { + continue; + } + + // Check if already multiline + if ($this->isMultiline($tokens, $openParenIndex, $closeParenIndex)) { + continue; + } + + // Apply multiline formatting + $this->makeMultiline($tokens, $openParenIndex, $closeParenIndex); + } + } + + private function countParameters( + Tokens $tokens, + int $start, + int $end + ): int { + $count = 0; + $depth = 0; + + for ($i = $start + 1; $i < $end; ++$i) { + if ($tokens[$i]->equals('(') || $tokens[$i]->equals('[')) { + ++$depth; + } elseif ($tokens[$i]->equals(')') || $tokens[$i]->equals(']')) { + --$depth; + } elseif ($depth === 0 && $tokens[$i]->equals(',')) { + ++$count; + } + } + + // If we found any commas, param count is commas + 1 + // If no commas but there's content, it's 1 param + if ($count > 0) { + return $count + 1; + } + + // Check if there's any non-whitespace content + for ($i = $start + 1; $i < $end; ++$i) { + if (!$tokens[$i]->isWhitespace()) { + return 1; + } + } + + return 0; + } + + private function isMultiline( + Tokens $tokens, + int $start, + int $end + ): bool { + for ($i = $start; $i <= $end; ++$i) { + if ($tokens[$i]->isGivenKind(T_WHITESPACE) && str_contains($tokens[$i]->getContent(), "\n")) { + return true; + } + } + + return false; + } + + private function makeMultiline( + Tokens $tokens, + int $openParenIndex, + int $closeParenIndex + ): void { + $indent = $this->detectIndent($tokens, $openParenIndex); + $lineIndent = str_repeat(' ', 4); // 4 spaces for parameters + + // Add newline after opening parenthesis + $tokens->insertAt($openParenIndex + 1, new Token([T_WHITESPACE, "\n" . $indent . $lineIndent])); + ++$closeParenIndex; + + // Find all commas and add newlines after them + $depth = 0; + for ($i = $openParenIndex + 1; $i < $closeParenIndex; ++$i) { + if ($tokens[$i]->equals('(') || $tokens[$i]->equals('[')) { + ++$depth; + } elseif ($tokens[$i]->equals(')') || $tokens[$i]->equals(']')) { + --$depth; + } elseif ($depth === 0 && $tokens[$i]->equals(',')) { + // Remove any whitespace after comma + $nextIndex = $i + 1; + while ($nextIndex < $closeParenIndex && $tokens[$nextIndex]->isWhitespace()) { + $tokens->clearAt($nextIndex); + ++$nextIndex; + } + + // Insert newline with proper indentation + $tokens->insertAt($i + 1, new Token([T_WHITESPACE, "\n" . $indent . $lineIndent])); + $closeParenIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenIndex); + } + } + + // Add newline before closing parenthesis + $tokens->insertAt($closeParenIndex, new Token([T_WHITESPACE, "\n" . $indent])); + + // Handle the opening brace + $nextNonWhitespace = $tokens->getNextNonWhitespace($closeParenIndex); + if ($nextNonWhitespace !== null && $tokens[$nextNonWhitespace]->equals(T_CURLY_OPEN)) { + $tokens->ensureWhitespaceAtIndex($nextNonWhitespace - 1, 1, ' '); + } + } + + private function detectIndent( + Tokens $tokens, + int $index + ): string { + // Look backwards to find the indentation of the current line + for ($i = $index - 1; $i >= 0; --$i) { + if ($tokens[$i]->isGivenKind(T_WHITESPACE) && str_contains($tokens[$i]->getContent(), "\n")) { + $lines = explode("\n", $tokens[$i]->getContent()); + + return end($lines); + } + } + + return ''; + } + + public function getName(): string + { + return 'Ibexa/multiline_parameters'; + } +} diff --git a/src/lib/PhpCsFixer/Sets/AbstractIbexaRuleSet.php b/src/lib/PhpCsFixer/Sets/AbstractIbexaRuleSet.php index 4ccf3f6..079b8fb 100644 --- a/src/lib/PhpCsFixer/Sets/AbstractIbexaRuleSet.php +++ b/src/lib/PhpCsFixer/Sets/AbstractIbexaRuleSet.php @@ -9,6 +9,7 @@ namespace Ibexa\CodeStyle\PhpCsFixer\Sets; use AdamWojs\PhpCsFixerPhpdocForceFQCN\Fixer\Phpdoc\ForceFQCNFixer; +use Ibexa\CodeStyle\PhpCsFixer\Rule\MultilineParametersFixer; use PhpCsFixer\Config; abstract class AbstractIbexaRuleSet implements RuleSetInterface @@ -26,6 +27,7 @@ public function getRules(): array return [ '@PSR12' => false, 'AdamWojs/phpdoc_force_fqcn_fixer' => true, + 'Ibexa/multiline_parameters' => true, 'array_syntax' => [ 'syntax' => 'short', ], @@ -204,7 +206,7 @@ public function getRules(): array public function buildConfig(): Config { $config = new Config(); - $config->registerCustomFixers([new ForceFQCNFixer()]); + $config->registerCustomFixers([new ForceFQCNFixer(), new MultilineParametersFixer()]); $config->setRules(array_merge( $config->getRules(), diff --git a/tests/lib/PhpCsFixer/InternalConfigFactoryTest.php b/tests/lib/PhpCsFixer/InternalConfigFactoryTest.php index 71ef115..22f28a7 100644 --- a/tests/lib/PhpCsFixer/InternalConfigFactoryTest.php +++ b/tests/lib/PhpCsFixer/InternalConfigFactoryTest.php @@ -38,8 +38,10 @@ protected function setUp(): void /** * @dataProvider provideRuleSetTestCases */ - public function testVersionBasedRuleSetSelection(array $package, string $expectedRuleSetClass): void - { + public function testVersionBasedRuleSetSelection( + array $package, + string $expectedRuleSetClass + ): void { $ruleSet = $this->createRuleSetFromPackage->invoke($this->factory, $package); self::assertInstanceOf($expectedRuleSetClass, $ruleSet); diff --git a/tests/lib/PhpCsFixer/Rule/MultilineParametersFixerTest.php b/tests/lib/PhpCsFixer/Rule/MultilineParametersFixerTest.php new file mode 100644 index 0000000..d396fec --- /dev/null +++ b/tests/lib/PhpCsFixer/Rule/MultilineParametersFixerTest.php @@ -0,0 +1,112 @@ +fixer = new MultilineParametersFixer(); + } + + /** + * @dataProvider provideFixCases + */ + public function testFix( + string $input, + string $expected + ): void { + $tokens = Tokens::fromCode($input); + $this->fixer->fix(new \SplFileInfo(__FILE__), $tokens); + + self::assertSame($expected, $tokens->generateCode()); + } + + public static function provideFixCases(): iterable + { + yield 'single parameter should not be modified' => [ + ' [ + ' [ + ' [ + ' [ + ' [ + ' true, 'whitespace_after_comma_in_array' => true, 'yoda_style' => false, + 'Ibexa/multiline_parameters' => true, ]; diff --git a/tests/lib/PhpCsFixer/Sets/expected_rules/4_6_rule_set/php_cs_fixer_rules.php b/tests/lib/PhpCsFixer/Sets/expected_rules/4_6_rule_set/php_cs_fixer_rules.php index ca860a8..700bc04 100644 --- a/tests/lib/PhpCsFixer/Sets/expected_rules/4_6_rule_set/php_cs_fixer_rules.php +++ b/tests/lib/PhpCsFixer/Sets/expected_rules/4_6_rule_set/php_cs_fixer_rules.php @@ -179,4 +179,5 @@ 'no_trailing_comma_in_singleline_array' => true, 'no_unneeded_curly_braces' => true, 'single_blank_line_before_namespace' => true, + 'Ibexa/multiline_parameters' => true, ]; diff --git a/tests/lib/PhpCsFixer/Sets/expected_rules/5_0_rule_set/local_rules.php b/tests/lib/PhpCsFixer/Sets/expected_rules/5_0_rule_set/local_rules.php index 37e51fb..f0685d5 100644 --- a/tests/lib/PhpCsFixer/Sets/expected_rules/5_0_rule_set/local_rules.php +++ b/tests/lib/PhpCsFixer/Sets/expected_rules/5_0_rule_set/local_rules.php @@ -218,4 +218,5 @@ 'visibility_required' => true, 'whitespace_after_comma_in_array' => true, 'yoda_style' => false, + 'Ibexa/multiline_parameters' => true, ]; diff --git a/tests/lib/PhpCsFixer/Sets/expected_rules/5_0_rule_set/php_cs_fixer_rules.php b/tests/lib/PhpCsFixer/Sets/expected_rules/5_0_rule_set/php_cs_fixer_rules.php index a50d32f..e4b9f51 100644 --- a/tests/lib/PhpCsFixer/Sets/expected_rules/5_0_rule_set/php_cs_fixer_rules.php +++ b/tests/lib/PhpCsFixer/Sets/expected_rules/5_0_rule_set/php_cs_fixer_rules.php @@ -204,4 +204,5 @@ 'types_spaces' => [ 'space' => 'single', ], + 'Ibexa/multiline_parameters' => true, ]; From 5c33b15037458dd01e2bfa7615a8d762d4b0ace3 Mon Sep 17 00:00:00 2001 From: Dawid Parafinski Date: Fri, 10 Oct 2025 15:58:54 +0200 Subject: [PATCH 2/6] Fixed phpstan issues --- src/lib/PhpCsFixer/Rule/MultilineParametersFixer.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/PhpCsFixer/Rule/MultilineParametersFixer.php b/src/lib/PhpCsFixer/Rule/MultilineParametersFixer.php index 617765c..057cfd4 100644 --- a/src/lib/PhpCsFixer/Rule/MultilineParametersFixer.php +++ b/src/lib/PhpCsFixer/Rule/MultilineParametersFixer.php @@ -44,6 +44,10 @@ protected function applyFix( } $openParenIndex = $tokens->getNextTokenOfKind($index, ['(']); + if ($openParenIndex === null) { + continue; + } + $closeParenIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenIndex); // Count commas to determine parameter count @@ -150,7 +154,7 @@ private function makeMultiline( // Handle the opening brace $nextNonWhitespace = $tokens->getNextNonWhitespace($closeParenIndex); - if ($nextNonWhitespace !== null && $tokens[$nextNonWhitespace]->equals(T_CURLY_OPEN)) { + if ($nextNonWhitespace !== null && $tokens[$nextNonWhitespace]->equals('{')) { $tokens->ensureWhitespaceAtIndex($nextNonWhitespace - 1, 1, ' '); } } From 99d38779e1fc88515e6c30b8d16aa94fb31d8076 Mon Sep 17 00:00:00 2001 From: Dawid Parafinski Date: Fri, 10 Oct 2025 16:00:36 +0200 Subject: [PATCH 3/6] make test php7.4 compliant --- tests/lib/PhpCsFixer/Rule/MultilineParametersFixerTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/lib/PhpCsFixer/Rule/MultilineParametersFixerTest.php b/tests/lib/PhpCsFixer/Rule/MultilineParametersFixerTest.php index d396fec..38d8e47 100644 --- a/tests/lib/PhpCsFixer/Rule/MultilineParametersFixerTest.php +++ b/tests/lib/PhpCsFixer/Rule/MultilineParametersFixerTest.php @@ -81,14 +81,14 @@ function test( yield 'constructor with properties should be split' => [ ' Date: Mon, 13 Oct 2025 08:19:16 +0200 Subject: [PATCH 4/6] Added tests folder to phpstan --- phpstan.neon | 1 + tests/lib/PhpCsFixer/InternalConfigFactoryTest.php | 6 ++++++ tests/lib/PhpCsFixer/Rule/MultilineParametersFixerTest.php | 3 +++ 3 files changed, 10 insertions(+) diff --git a/phpstan.neon b/phpstan.neon index f5da971..8a310a5 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,3 +4,4 @@ parameters: checkMissingCallableSignature: true paths: - src + - tests diff --git a/tests/lib/PhpCsFixer/InternalConfigFactoryTest.php b/tests/lib/PhpCsFixer/InternalConfigFactoryTest.php index 22f28a7..e85f80c 100644 --- a/tests/lib/PhpCsFixer/InternalConfigFactoryTest.php +++ b/tests/lib/PhpCsFixer/InternalConfigFactoryTest.php @@ -37,6 +37,9 @@ protected function setUp(): void /** * @dataProvider provideRuleSetTestCases + * + * @param array{name: string, version: string, pretty_version?: string} $package + * @param class-string $expectedRuleSetClass */ public function testVersionBasedRuleSetSelection( array $package, @@ -47,6 +50,9 @@ public function testVersionBasedRuleSetSelection( self::assertInstanceOf($expectedRuleSetClass, $ruleSet); } + /** + * @return array + */ public function provideRuleSetTestCases(): array { return [ diff --git a/tests/lib/PhpCsFixer/Rule/MultilineParametersFixerTest.php b/tests/lib/PhpCsFixer/Rule/MultilineParametersFixerTest.php index 38d8e47..679a2f1 100644 --- a/tests/lib/PhpCsFixer/Rule/MultilineParametersFixerTest.php +++ b/tests/lib/PhpCsFixer/Rule/MultilineParametersFixerTest.php @@ -34,6 +34,9 @@ public function testFix( self::assertSame($expected, $tokens->generateCode()); } + /** + * @return iterable + */ public static function provideFixCases(): iterable { yield 'single parameter should not be modified' => [ From a55b44ced228a00c2657483dcf43e9f7f223fbcb Mon Sep 17 00:00:00 2001 From: Dawid Parafinski Date: Mon, 13 Oct 2025 08:29:12 +0200 Subject: [PATCH 5/6] minor cr fixes --- .../Rule/MultilineParametersFixer.php | 22 +++++++++---------- .../Rule/MultilineParametersFixerTest.php | 3 ++- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/lib/PhpCsFixer/Rule/MultilineParametersFixer.php b/src/lib/PhpCsFixer/Rule/MultilineParametersFixer.php index 057cfd4..1b885d2 100644 --- a/src/lib/PhpCsFixer/Rule/MultilineParametersFixer.php +++ b/src/lib/PhpCsFixer/Rule/MultilineParametersFixer.php @@ -43,15 +43,15 @@ protected function applyFix( continue; } - $openParenIndex = $tokens->getNextTokenOfKind($index, ['(']); - if ($openParenIndex === null) { + $openParentIndex = $tokens->getNextTokenOfKind($index, ['(']); + if ($openParentIndex === null) { continue; } - $closeParenIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenIndex); + $closeParenIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParentIndex); // Count commas to determine parameter count - $paramCount = $this->countParameters($tokens, $openParenIndex, $closeParenIndex); + $paramCount = $this->countParameters($tokens, $openParentIndex, $closeParenIndex); // Only process if 2+ parameters if ($paramCount < 2) { @@ -59,12 +59,12 @@ protected function applyFix( } // Check if already multiline - if ($this->isMultiline($tokens, $openParenIndex, $closeParenIndex)) { + if ($this->isMultiline($tokens, $openParentIndex, $closeParenIndex)) { continue; } // Apply multiline formatting - $this->makeMultiline($tokens, $openParenIndex, $closeParenIndex); + $this->makeMultiline($tokens, $openParentIndex, $closeParenIndex); } } @@ -118,19 +118,19 @@ private function isMultiline( private function makeMultiline( Tokens $tokens, - int $openParenIndex, + int $openParentIndex, int $closeParenIndex ): void { - $indent = $this->detectIndent($tokens, $openParenIndex); + $indent = $this->detectIndent($tokens, $openParentIndex); $lineIndent = str_repeat(' ', 4); // 4 spaces for parameters // Add newline after opening parenthesis - $tokens->insertAt($openParenIndex + 1, new Token([T_WHITESPACE, "\n" . $indent . $lineIndent])); + $tokens->insertAt($openParentIndex + 1, new Token([T_WHITESPACE, "\n" . $indent . $lineIndent])); ++$closeParenIndex; // Find all commas and add newlines after them $depth = 0; - for ($i = $openParenIndex + 1; $i < $closeParenIndex; ++$i) { + for ($i = $openParentIndex + 1; $i < $closeParenIndex; ++$i) { if ($tokens[$i]->equals('(') || $tokens[$i]->equals('[')) { ++$depth; } elseif ($tokens[$i]->equals(')') || $tokens[$i]->equals(']')) { @@ -145,7 +145,7 @@ private function makeMultiline( // Insert newline with proper indentation $tokens->insertAt($i + 1, new Token([T_WHITESPACE, "\n" . $indent . $lineIndent])); - $closeParenIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenIndex); + $closeParenIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParentIndex); } } diff --git a/tests/lib/PhpCsFixer/Rule/MultilineParametersFixerTest.php b/tests/lib/PhpCsFixer/Rule/MultilineParametersFixerTest.php index 679a2f1..61f536c 100644 --- a/tests/lib/PhpCsFixer/Rule/MultilineParametersFixerTest.php +++ b/tests/lib/PhpCsFixer/Rule/MultilineParametersFixerTest.php @@ -11,6 +11,7 @@ use Ibexa\CodeStyle\PhpCsFixer\Rule\MultilineParametersFixer; use PhpCsFixer\Tokenizer\Tokens; use PHPUnit\Framework\TestCase; +use SplFileInfo; final class MultilineParametersFixerTest extends TestCase { @@ -29,7 +30,7 @@ public function testFix( string $expected ): void { $tokens = Tokens::fromCode($input); - $this->fixer->fix(new \SplFileInfo(__FILE__), $tokens); + $this->fixer->fix(new SplFileInfo(__FILE__), $tokens); self::assertSame($expected, $tokens->generateCode()); } From f143ecea11f2d443ed1dae23f0d63e655c4b8deb Mon Sep 17 00:00:00 2001 From: Dawid Parafinski Date: Mon, 13 Oct 2025 08:33:44 +0200 Subject: [PATCH 6/6] fixed closeParentIndex variable name --- .../Rule/MultilineParametersFixer.php | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/lib/PhpCsFixer/Rule/MultilineParametersFixer.php b/src/lib/PhpCsFixer/Rule/MultilineParametersFixer.php index 1b885d2..cbe75ba 100644 --- a/src/lib/PhpCsFixer/Rule/MultilineParametersFixer.php +++ b/src/lib/PhpCsFixer/Rule/MultilineParametersFixer.php @@ -48,10 +48,10 @@ protected function applyFix( continue; } - $closeParenIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParentIndex); + $closeParentIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParentIndex); // Count commas to determine parameter count - $paramCount = $this->countParameters($tokens, $openParentIndex, $closeParenIndex); + $paramCount = $this->countParameters($tokens, $openParentIndex, $closeParentIndex); // Only process if 2+ parameters if ($paramCount < 2) { @@ -59,12 +59,12 @@ protected function applyFix( } // Check if already multiline - if ($this->isMultiline($tokens, $openParentIndex, $closeParenIndex)) { + if ($this->isMultiline($tokens, $openParentIndex, $closeParentIndex)) { continue; } // Apply multiline formatting - $this->makeMultiline($tokens, $openParentIndex, $closeParenIndex); + $this->makeMultiline($tokens, $openParentIndex, $closeParentIndex); } } @@ -119,18 +119,18 @@ private function isMultiline( private function makeMultiline( Tokens $tokens, int $openParentIndex, - int $closeParenIndex + int $closeParentIndex ): void { $indent = $this->detectIndent($tokens, $openParentIndex); $lineIndent = str_repeat(' ', 4); // 4 spaces for parameters // Add newline after opening parenthesis $tokens->insertAt($openParentIndex + 1, new Token([T_WHITESPACE, "\n" . $indent . $lineIndent])); - ++$closeParenIndex; + ++$closeParentIndex; // Find all commas and add newlines after them $depth = 0; - for ($i = $openParentIndex + 1; $i < $closeParenIndex; ++$i) { + for ($i = $openParentIndex + 1; $i < $closeParentIndex; ++$i) { if ($tokens[$i]->equals('(') || $tokens[$i]->equals('[')) { ++$depth; } elseif ($tokens[$i]->equals(')') || $tokens[$i]->equals(']')) { @@ -138,22 +138,22 @@ private function makeMultiline( } elseif ($depth === 0 && $tokens[$i]->equals(',')) { // Remove any whitespace after comma $nextIndex = $i + 1; - while ($nextIndex < $closeParenIndex && $tokens[$nextIndex]->isWhitespace()) { + while ($nextIndex < $closeParentIndex && $tokens[$nextIndex]->isWhitespace()) { $tokens->clearAt($nextIndex); ++$nextIndex; } // Insert newline with proper indentation $tokens->insertAt($i + 1, new Token([T_WHITESPACE, "\n" . $indent . $lineIndent])); - $closeParenIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParentIndex); + $closeParentIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParentIndex); } } // Add newline before closing parenthesis - $tokens->insertAt($closeParenIndex, new Token([T_WHITESPACE, "\n" . $indent])); + $tokens->insertAt($closeParentIndex, new Token([T_WHITESPACE, "\n" . $indent])); // Handle the opening brace - $nextNonWhitespace = $tokens->getNextNonWhitespace($closeParenIndex); + $nextNonWhitespace = $tokens->getNextNonWhitespace($closeParentIndex); if ($nextNonWhitespace !== null && $tokens[$nextNonWhitespace]->equals('{')) { $tokens->ensureWhitespaceAtIndex($nextNonWhitespace - 1, 1, ' '); }