diff --git a/docs/Rules.md b/docs/Rules.md index 01d42f8c..277ecb32 100644 --- a/docs/Rules.md +++ b/docs/Rules.md @@ -183,6 +183,8 @@ Blank lines are added before non-consecutive `return`, `yield` and `yield from` ### `ListSpacing` (call 1: `processDeclarations()`) +Newlines are added between comma-delimited constant declarations and property declarations. When neither `StrictLists` nor `AlignLists` are enabled, they are also added to `use` statements between comma-delimited imports and traits that break over multiple lines. + If a list of property hooks has one or more attributes with a trailing newline, every attribute is placed on its own line, and blank lines are added before and after annotated hooks to improve readability. ### `ListSpacing` (call 2: `processList()`) @@ -290,8 +292,6 @@ In switch case lists: ### `DeclarationSpacing`, unless disabled -Newlines are added between comma-delimited constant or property declarations. - One-line declarations with a collapsed or collapsible DocBlock, or no DocBlock at all, are considered "collapsible". Declarations that break over multiple lines or have a DocBlock that cannot be collapsed to one line are considered "non-collapsible". "Tight" spacing is applied by suppressing blank lines between collapsible declarations of the same type when they appear consecutively and: @@ -450,7 +450,7 @@ Newlines and spaces are added after tokens that would otherwise fail to parse. T | -------------- | ------------------------------------------------------ | | `CASE` | `DeclarationSpacing` | | `CLASS` | `DeclarationSpacing`, `Drupal` | -| `CONST` | `DeclarationSpacing` | +| `CONST` | `DeclarationSpacing`, `ListSpacing` | | `DECLARE` | `DeclarationSpacing` | | `ENUM` | `DeclarationSpacing`, `Drupal` | | `FUNCTION` | `DeclarationSpacing` | @@ -460,7 +460,7 @@ Newlines and spaces are added after tokens that would otherwise fail to parse. T | `PARAM` | `ListSpacing`, `StandardSpacing` | | `PROPERTY` | `DeclarationSpacing`, `ListSpacing`, `StandardSpacing` | | `TRAIT` | `DeclarationSpacing`, `Drupal` | -| `USE` | `DeclarationSpacing` | -| `USE_CONST` | `DeclarationSpacing` | -| `USE_FUNCTION` | `DeclarationSpacing` | -| `USE_TRAIT` | `DeclarationSpacing` | +| `USE` | `DeclarationSpacing`, `ListSpacing` | +| `USE_CONST` | `DeclarationSpacing`, `ListSpacing` | +| `USE_FUNCTION` | `DeclarationSpacing`, `ListSpacing` | +| `USE_TRAIT` | `DeclarationSpacing`, `ListSpacing` | diff --git a/src/Contract/ListRule.php b/src/Contract/ListRule.php index 97933391..49baab81 100644 --- a/src/Contract/ListRule.php +++ b/src/Contract/ListRule.php @@ -15,17 +15,17 @@ interface ListRule extends Rule /** * Apply the rule to a token and the list of items associated with it * - * If `$parent` is a `T_OPEN_PARENTHESIS`, `T_OPEN_BRACKET` or `T_ATTRIBUTE` - * token, `$items` has at least one item. + * If `$parent` is a `T_OPEN_PARENTHESIS`, `T_OPEN_BRACKET`, `T_OPEN_BRACE` + * or `T_ATTRIBUTE` token, `$items` has at least one item. * - * Otherwise, `$parent` is a `T_EXTENDS` or `T_IMPLEMENTS` token, and + * Otherwise, `$parent` is a `T_EXTENDS`, `T_IMPLEMENTS`, `T_CONST`, + * `T_USE`, `T_INSTEADOF`, `T_STATIC`, `T_GLOBAL` or modifier token, and * `$items` has at least two items. * * Each token in `$items` is the first code token after `$parent` or a * delimiter. * - * This method is not called for empty lists or for classes that extend or - * implement fewer than two interfaces. + * This method is not called for empty lists. */ - public function processList(Token $parent, TokenCollection $items): void; + public function processList(Token $parent, TokenCollection $items, Token $lastChild): void; } diff --git a/src/Formatter.php b/src/Formatter.php index 1a1e9225..c72bad3b 100644 --- a/src/Formatter.php +++ b/src/Formatter.php @@ -2,7 +2,7 @@ namespace Lkrms\PrettyPHP; -use Lkrms\PrettyPHP\Catalog\DeclarationType; +use Lkrms\PrettyPHP\Catalog\DeclarationType as Type; use Lkrms\PrettyPHP\Catalog\FormatterFlag; use Lkrms\PrettyPHP\Catalog\HeredocIndent; use Lkrms\PrettyPHP\Catalog\ImportSortOrder; @@ -711,7 +711,7 @@ private function apply(): self private function getAllDeclarationTypes(): array { /** @var int[] */ - $types = Reflect::getConstants(DeclarationType::class); + $types = Reflect::getConstants(Type::class); return array_fill_keys($types, true); } @@ -949,14 +949,18 @@ public function format( } Profile::startTimer(__METHOD__ . '#find-lists'); - $parents = $this->sortTokensByType($this->TokensById, [ + $lists = []; + $parents = $this->getTokensByType($this->TokensById, [ \T_OPEN_PARENTHESIS => true, \T_OPEN_BRACKET => true, + \T_OPEN_BRACE => true, \T_ATTRIBUTE => true, \T_EXTENDS => true, \T_IMPLEMENTS => true, + \T_INSTEADOF => true, + \T_STATIC => true, + \T_GLOBAL => true, ]); - $lists = []; foreach ($parents as $i => $parent) { if ($parent->CloseBracket === $parent->NextCode) { continue; @@ -967,16 +971,33 @@ public function format( && $parent->PrevCode->id === \T_FOR ? \T_SEMICOLON : \T_COMMA; + $last = null; $items = null; $minCount = 1; + /** @var Token */ + $endStatement = $parent->EndStatement; + switch ($parent->id) { + case \T_STATIC: + if ( + $parent->Statement !== $parent + || $parent->Flags & TokenFlag::NAMED_DECLARATION + ) { + continue 2; + } + // No break + case \T_INSTEADOF: + case \T_GLOBAL: + /** @var Token */ + $last = $endStatement->PrevCode; + // No break case \T_EXTENDS: case \T_IMPLEMENTS: /** @var Token */ $first = $parent->NextCode; - /** @var Token */ - $last = $parent->nextSiblingFrom($idx->OpenBraceOrImplements)->PrevCode; + $last ??= $parent->nextSiblingFrom($idx->OpenBraceOrImplements)->PrevCode; + /** @var Token $last */ $items = $first->withNextSiblings($last) ->filter( fn(Token $t, ?Token $next, ?Token $prev) => @@ -1008,8 +1029,24 @@ public function format( continue 2; } break; + + case \T_OPEN_BRACE: + /** @var Token */ + $statement = $parent->Statement; + if (!( + $statement->Flags & TokenFlag::NAMED_DECLARATION + && ($statement->Data[TokenData::NAMED_DECLARATION_TYPE] & ( + Type::_USE + | Type::_TRAIT + )) === Type::_USE + )) { + continue 2; + } + break; } + $last ??= ($parent->CloseBracket ?? $endStatement)->PrevCode; + /** @var Token $last */ $items ??= $parent->children() ->filter( fn(Token $t, ?Token $next, ?Token $prev) => @@ -1034,8 +1071,76 @@ public function format( $token->Flags |= TokenFlag::LIST_ITEM; $token->Data[TokenData::LIST_PARENT] = $parent; } - $lists[$i] = $items; + $lists[$i] = [$parent, $items, $last]; + } + + $parents = $this->getTokensByType($this->DeclarationsByType, [ + Type::_CONST => true, + Type::_USE => true, + Type::PROPERTY => true, + Type::USE_CONST => true, + Type::USE_FUNCTION => true, + Type::USE_TRAIT => true, + ]); + foreach ($parents as $i => $parent) { + $type = $parent->Data[TokenData::NAMED_DECLARATION_TYPE]; + $first = null; + $last = null; + + /** @var Token */ + $endStatement = $parent->EndStatement; + + switch ($type) { + case Type::_USE: + case Type::USE_CONST: + case Type::USE_FUNCTION: + case Type::USE_TRAIT: + /** @var Token */ + $first = $parent->NextCode; + $first = $first->skipNextSiblingFrom($idx->ConstOrFunction); + if ($type === Type::USE_TRAIT) { + $last = $parent->nextSiblingFrom($idx->OpenBraceOrSemicolon) + ->PrevCode; + } + break; + + case Type::PROPERTY: + $first = $parent->nextSiblingOf(\T_VARIABLE); + /** @var Token */ + $last = $parent->nextSiblingFrom($idx->OpenBraceOrSemicolon) + ->PrevCode; + break; + } + + $first ??= $parent->nextSiblingOf(\T_EQUAL)->PrevCode; + /** @var Token $first */ + $parent = $first->PrevCode; + /** @var Token $parent */ + $last ??= $endStatement->PrevCode; + /** @var Token $last */ + $items = $first->withNextSiblings($last) + ->filter( + fn(Token $t, ?Token $next, ?Token $prev) => + !$prev + || !$t->PrevCode + || $t->PrevCode->id === \T_COMMA + ); + + $count = $items->count(); + if ($count < 2) { + continue; + } + $parent->Flags |= TokenFlag::LIST_PARENT; + $parent->Data[TokenData::LIST_DELIMITER] = \T_COMMA; + $parent->Data[TokenData::LIST_ITEMS] = $items; + $parent->Data[TokenData::LIST_ITEM_COUNT] = $count; + foreach ($items as $token) { + $token->Flags |= TokenFlag::LIST_ITEM; + $token->Data[TokenData::LIST_PARENT] = $parent; + } + $lists[$i] = [$parent, $items, $last]; } + ksort($lists, \SORT_NUMERIC); Profile::stopTimer(__METHOD__ . '#find-lists'); $logProgress = $this->LogProgress @@ -1057,9 +1162,9 @@ public function format( } if ($method === ListRule::PROCESS_LIST) { - foreach ($lists as $i => $list) { + foreach ($lists as [$parent, $items, $last]) { /** @var ListRule $rule */ - $rule->processList($parents[$i], clone $list); + $rule->processList($parent, clone $items, $last); } Profile::stopTimer($_rule, 'rule'); $logProgress($_rule, ListRule::PROCESS_LIST); diff --git a/src/Internal/TokenCollection.php b/src/Internal/TokenCollection.php index e8023a1a..f8cd17a4 100644 --- a/src/Internal/TokenCollection.php +++ b/src/Internal/TokenCollection.php @@ -185,6 +185,23 @@ public function tokenHasNewlineAfter(bool $closedBy = false): bool return false; } + /** + * Check if, between a token in the collection and its next code token, + * there's a newline between tokens + */ + public function tokenHasNewlineBeforeNextCode(bool $closedBy = false): bool + { + foreach ($this->Items as $token) { + if ($closedBy && $token->CloseBracket) { + $token = $token->CloseBracket; + } + if ($token->hasNewlineBeforeNextCode()) { + return true; + } + } + return false; + } + /** * Check if any tokens in the collection are separated by one or more line * breaks diff --git a/src/Rule/AlignLists.php b/src/Rule/AlignLists.php index 5de0031a..eb37bdaa 100644 --- a/src/Rule/AlignLists.php +++ b/src/Rule/AlignLists.php @@ -48,7 +48,7 @@ public function reset(): void * after their open brackets, or with the first item in the list if they * have no enclosing brackets. */ - public function processList(Token $parent, TokenCollection $items): void + public function processList(Token $parent, TokenCollection $items, Token $lastChild): void { /** @var Token */ $first = $items->first(); @@ -56,9 +56,7 @@ public function processList(Token $parent, TokenCollection $items): void /** @var Token */ $until = $last->PrevCode; } else { - /** @var Token */ - $last = $parent->nextSiblingFrom($this->Idx->OpenBraceOrImplements) - ->PrevCode; + $last = $lastChild; $until = $last; } diff --git a/src/Rule/DeclarationSpacing.php b/src/Rule/DeclarationSpacing.php index 4d6183bb..a1c95633 100644 --- a/src/Rule/DeclarationSpacing.php +++ b/src/Rule/DeclarationSpacing.php @@ -69,9 +69,6 @@ public function boot(): void /** * Apply the rule to the given declarations * - * Newlines are added between comma-delimited constant or property - * declarations. - * * One-line declarations with a collapsed or collapsible DocBlock, or no * DocBlock at all, are considered "collapsible". Declarations that break * over multiple lines or have a DocBlock that cannot be collapsed to one @@ -131,12 +128,6 @@ public function processDeclarations(array $declarations): void $decl = new Declaration($token, $type, $modifiers); - if ($type === Type::_CONST || $type === Type::PROPERTY) { - foreach ($token->withNextSiblings($decl->End)->getAnyOf(\T_COMMA) as $comma) { - $comma->Whitespace |= Space::LINE_AFTER; - } - } - // Group consecutive declarations by parent $parentIndex = $token->Parent ? $token->Parent->index diff --git a/src/Rule/ListSpacing.php b/src/Rule/ListSpacing.php index 236c63e6..ca23b453 100644 --- a/src/Rule/ListSpacing.php +++ b/src/Rule/ListSpacing.php @@ -2,7 +2,7 @@ namespace Lkrms\PrettyPHP\Rule; -use Lkrms\PrettyPHP\Catalog\DeclarationType; +use Lkrms\PrettyPHP\Catalog\DeclarationType as Type; use Lkrms\PrettyPHP\Catalog\TokenData; use Lkrms\PrettyPHP\Catalog\WhitespaceFlag as Space; use Lkrms\PrettyPHP\Concern\DeclarationRuleTrait; @@ -35,8 +35,13 @@ public static function getPriority(string $method): ?int public static function getDeclarationTypes(array $all): array { return [ - DeclarationType::PROPERTY => true, - DeclarationType::PARAM => true, + Type::_CONST => true, + Type::_USE => true, + Type::PROPERTY => true, + Type::PARAM => true, + Type::USE_CONST => true, + Type::USE_FUNCTION => true, + Type::USE_TRAIT => true, ]; } @@ -64,10 +69,14 @@ public function boot(): void * nor `AlignLists` are enabled, a newline is added before the first * interface. */ - public function processList(Token $parent, TokenCollection $items): void + public function processList(Token $parent, TokenCollection $items, Token $lastChild): void { if (!$parent->CloseBracket) { - if (!$this->ListRuleEnabled && $items->tokenHasNewlineBefore()) { + if ( + !$this->ListRuleEnabled + && ($parent->id === \T_EXTENDS || $parent->id === \T_IMPLEMENTS) + && $items->tokenHasNewlineBefore() + ) { /** @var Token */ $token = $items->first(); $token->applyWhitespace(Space::LINE_BEFORE); @@ -92,6 +101,11 @@ public function processList(Token $parent, TokenCollection $items): void /** * Apply the rule to the given declarations * + * Newlines are added between comma-delimited constant declarations and + * property declarations. When neither `StrictLists` nor `AlignLists` are + * enabled, they are also added to `use` statements between comma-delimited + * imports and traits that break over multiple lines. + * * If a list of property hooks has one or more attributes with a trailing * newline, every attribute is placed on its own line, and blank lines are * added before and after annotated hooks to improve readability. @@ -99,10 +113,29 @@ public function processList(Token $parent, TokenCollection $items): void public function processDeclarations(array $declarations): void { foreach ($declarations as $token) { - /** @var TokenCollection */ - $hooks = $token->Data[TokenData::PROPERTY_HOOKS]; - if ($hooks->count()) { - $this->normaliseDeclarationList($hooks); + $type = $token->Data[TokenData::NAMED_DECLARATION_TYPE]; + + if ( + $type === Type::_CONST + || $type === Type::PROPERTY + || $type & Type::_USE + ) { + $commas = $token->withNextSiblings($token->EndStatement) + ->getAnyOf(\T_COMMA); + if (!($type & Type::_USE) || ( + !$this->ListRuleEnabled + && $commas->tokenHasNewlineBeforeNextCode() + )) { + $commas->applyWhitespace(Space::LINE_AFTER); + } + } + + if ($type & Type::PROPERTY) { + /** @var TokenCollection */ + $hooks = $token->Data[TokenData::PROPERTY_HOOKS]; + if ($hooks->count()) { + $this->normaliseDeclarationList($hooks); + } } } } diff --git a/src/Rule/Preset/Symfony.php b/src/Rule/Preset/Symfony.php index fa1c9116..473fd471 100644 --- a/src/Rule/Preset/Symfony.php +++ b/src/Rule/Preset/Symfony.php @@ -100,7 +100,7 @@ public function processTokens(array $tokens): void * Newlines are suppressed between parameters in function declarations that * have no promoted constructor parameters. */ - public function processList(Token $parent, TokenCollection $items): void + public function processList(Token $parent, TokenCollection $items, Token $lastChild): void { if (!$parent->isParameterList()) { return; diff --git a/src/Rule/StrictLists.php b/src/Rule/StrictLists.php index e75e6e37..5dbc5869 100644 --- a/src/Rule/StrictLists.php +++ b/src/Rule/StrictLists.php @@ -33,7 +33,7 @@ public static function getPriority(string $method): ?int * Items in lists are arranged horizontally or vertically by replicating the * arrangement of the first and second items. */ - public function processList(Token $parent, TokenCollection $items): void + public function processList(Token $parent, TokenCollection $items, Token $lastChild): void { if ($items->count() < 2) { return; @@ -42,6 +42,13 @@ public function processList(Token $parent, TokenCollection $items): void /** @var Token */ $second = $items->nth(2); if ($second->hasNewlineBefore()) { + if ( + !$parent->CloseBracket + && $parent->id !== \T_EXTENDS + && $parent->id !== \T_IMPLEMENTS + ) { + $items = $items->shift(); + } $items->applyWhitespace(Space::LINE_BEFORE); } else { $items->applyWhitespace(Space::NO_BLANK_BEFORE | Space::NO_LINE_BEFORE); diff --git a/src/TokenIndex.php b/src/TokenIndex.php index 8d1bc460..e427e5d4 100644 --- a/src/TokenIndex.php +++ b/src/TokenIndex.php @@ -187,6 +187,17 @@ class TokenIndex implements HasTokenIndex, Immutable ] + self::TOKEN_INDEX; + /** + * T_OPEN_BRACE, T_SEMICOLON + * + * @var array + */ + public array $OpenBraceOrSemicolon = [ + \T_OPEN_BRACE => true, + \T_SEMICOLON => true, + ] + + self::TOKEN_INDEX; + /** * T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO * @@ -368,6 +379,17 @@ class TokenIndex implements HasTokenIndex, Immutable ] + self::TOKEN_INDEX; + /** + * T_CONST, T_FUNCTION + * + * @var array + */ + public array $ConstOrFunction = [ + \T_CONST => true, + \T_FUNCTION => true, + ] + + self::TOKEN_INDEX; + /** * T_COMMA, T_DOUBLE_ARROW * diff --git a/tests/fixtures/Formatter/in/issues/0136-improve-hanging-indentation.php b/tests/fixtures/Formatter/in/issues/0136-improve-hanging-indentation.php new file mode 100644 index 00000000..f9b0d829 --- /dev/null +++ b/tests/fixtures/Formatter/in/issues/0136-improve-hanging-indentation.php @@ -0,0 +1,28 @@ + + $foo + || $bar; // Remove indentation here + +$foo = bar('foo', + 'bar') // Add indentation here + ->baz() + ->qux(); + +if ( + $comments + && ($prev = end($comments)) === $comment->Prev + && $comment->line - $prev->line < 2 + && ($comment->Flags & TokenFlagMask::COMMENT_TYPE) + === ($prev->Flags & TokenFlagMask::COMMENT_TYPE) + // Remove indentation here + && $comment->Flags & TokenFlag::ONELINE_COMMENT + && $comment->column > 1 + && $comment->column > $column + ($comment->Depth - $depth) * $tabSize + && ( + !$nextCode + || !($nextCodeWasFirst ??= $nextCode->wasFirstOnLine()) + || $comment->column > $nextCode->column + ) +) { + $comments[] = $comment; +} diff --git a/tests/fixtures/Formatter/in/standalone/lists-01.php b/tests/fixtures/Formatter/in/standalone/lists-01.php new file mode 100644 index 00000000..dfb94774 --- /dev/null +++ b/tests/fixtures/Formatter/in/standalone/lists-01.php @@ -0,0 +1,60 @@ + + $foo || + $bar; // Remove indentation here + +$foo = bar('foo', + 'bar') // Add indentation here + ->baz() + ->qux(); + +if ( + $comments && + ($prev = end($comments)) === $comment->Prev && + $comment->line - $prev->line < 2 && + ($comment->Flags & TokenFlagMask::COMMENT_TYPE) === + ($prev->Flags & TokenFlagMask::COMMENT_TYPE) && + // Remove indentation here + $comment->Flags & TokenFlag::ONELINE_COMMENT && + $comment->column > 1 && + $comment->column > $column + ($comment->Depth - $depth) * $tabSize && + ( + !$nextCode || + !($nextCodeWasFirst ??= $nextCode->wasFirstOnLine()) || + $comment->column > $nextCode->column + ) +) { + $comments[] = $comment; +} diff --git a/tests/fixtures/Formatter/out/01-default/standalone/lists-01.php b/tests/fixtures/Formatter/out/01-default/standalone/lists-01.php new file mode 100644 index 00000000..5761ab22 --- /dev/null +++ b/tests/fixtures/Formatter/out/01-default/standalone/lists-01.php @@ -0,0 +1,69 @@ + + $foo || + $bar; // Remove indentation here + +$foo = bar('foo', + 'bar') // Add indentation here + ->baz() + ->qux(); + +if ( + $comments && + ($prev = end($comments)) === $comment->Prev && + $comment->line - $prev->line < 2 && + ($comment->Flags & TokenFlagMask::COMMENT_TYPE) === + ($prev->Flags & TokenFlagMask::COMMENT_TYPE) && + // Remove indentation here + $comment->Flags & TokenFlag::ONELINE_COMMENT && + $comment->column > 1 && + $comment->column > $column + ($comment->Depth - $depth) * $tabSize && + ( + !$nextCode || + !($nextCodeWasFirst ??= $nextCode->wasFirstOnLine()) || + $comment->column > $nextCode->column + ) +) { + $comments[] = $comment; +} diff --git a/tests/fixtures/Formatter/out/02-aligned/standalone/lists-01.php b/tests/fixtures/Formatter/out/02-aligned/standalone/lists-01.php new file mode 100644 index 00000000..5d6db3ef --- /dev/null +++ b/tests/fixtures/Formatter/out/02-aligned/standalone/lists-01.php @@ -0,0 +1,66 @@ + + $foo || + $bar; // Remove indentation here + +$foo = bar('foo', + 'bar') // Add indentation here + ->baz() + ->qux(); + +if ( + $comments && + ($prev = end($comments)) === $comment->Prev && + $comment->line - $prev->line < 2 && + ($comment->Flags & TokenFlagMask::COMMENT_TYPE) === + ($prev->Flags & TokenFlagMask::COMMENT_TYPE) && + // Remove indentation here + $comment->Flags & TokenFlag::ONELINE_COMMENT && + $comment->column > 1 && + $comment->column > $column + ($comment->Depth - $depth) * $tabSize && + ( + !$nextCode || + !($nextCodeWasFirst ??= $nextCode->wasFirstOnLine()) || + $comment->column > $nextCode->column + ) +) { + $comments[] = $comment; +} diff --git a/tests/fixtures/Formatter/out/03-tab/standalone/lists-01.php b/tests/fixtures/Formatter/out/03-tab/standalone/lists-01.php new file mode 100644 index 00000000..cb7dc716 --- /dev/null +++ b/tests/fixtures/Formatter/out/03-tab/standalone/lists-01.php @@ -0,0 +1,69 @@ + $foo + || $bar; // Remove indentation here + +$foo = bar( + 'foo', + 'bar' +) // Add indentation here + ->baz() + ->qux(); + +if ( + $comments + && ($prev = end($comments)) === $comment->Prev + && $comment->line - $prev->line < 2 + && ($comment->Flags & TokenFlagMask::COMMENT_TYPE) + === ($prev->Flags & TokenFlagMask::COMMENT_TYPE) + // Remove indentation here + && $comment->Flags & TokenFlag::ONELINE_COMMENT + && $comment->column > 1 + && $comment->column > $column + ($comment->Depth - $depth) * $tabSize + && ( + !$nextCode + || !($nextCodeWasFirst ??= $nextCode->wasFirstOnLine()) + || $comment->column > $nextCode->column + ) +) { + $comments[] = $comment; +} diff --git a/tests/fixtures/Formatter/out/04-psr12/standalone/lists-01.php b/tests/fixtures/Formatter/out/04-psr12/standalone/lists-01.php new file mode 100644 index 00000000..779ea380 --- /dev/null +++ b/tests/fixtures/Formatter/out/04-psr12/standalone/lists-01.php @@ -0,0 +1,74 @@ +