diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
index 8db105a9..62b79725 100644
--- a/.php-cs-fixer.dist.php
+++ b/.php-cs-fixer.dist.php
@@ -67,14 +67,17 @@
'phpdoc_no_useless_inheritdoc' => true,
'phpdoc_order' => ['order' => [
'todo',
- 'method',
'property',
+ 'property-read',
+ 'property-write',
+ 'method',
'api',
'internal',
'requires',
'dataProvider',
'backupGlobals',
'template',
+ 'template-covariant',
'extends',
'implements',
'use',
@@ -83,18 +86,20 @@
'readonly',
'var',
'param',
+ 'param-out',
'return',
'throws',
]],
// 'phpdoc_param_order' => true,
'phpdoc_separation' => ['groups' => [
['see', 'link'],
- ['property', 'phpstan-property', 'property-read', 'phpstan-property-read'],
+ ['property', 'property-read', 'property-write', 'phpstan-property', 'phpstan-property-read', 'phpstan-property-write'],
+ ['method', 'phpstan-method'],
['requires', 'dataProvider', 'backupGlobals'],
['template', 'template-covariant'],
['extends', 'implements', 'use'],
['phpstan-require-extends', 'phpstan-require-implements'],
- ['readonly', 'var', 'phpstan-var', 'param', 'param-out', 'phpstan-param', 'return', 'phpstan-return', 'throws', 'phpstan-assert*', 'phpstan-ignore*', 'disregard'],
+ ['readonly', 'var', 'param', 'param-out', 'return', 'throws', 'phpstan-var', 'phpstan-param', 'phpstan-return', 'phpstan-assert*', 'phpstan-ignore*', 'disregard'],
['phpstan-*'],
]],
'phpdoc_tag_casing' => true,
diff --git a/composer.lock b/composer.lock
index 51444696..e93ff680 100644
--- a/composer.lock
+++ b/composer.lock
@@ -360,16 +360,16 @@
},
{
"name": "salient/cli",
- "version": "v0.99.65",
+ "version": "v0.99.68",
"source": {
"type": "git",
"url": "https://github.com/salient-labs/toolkit-cli.git",
- "reference": "e28058395b61777395347e23ada6c55be363eef0"
+ "reference": "8ed34826739c218c401d7a4a8af3c3c99e660c9a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/salient-labs/toolkit-cli/zipball/e28058395b61777395347e23ada6c55be363eef0",
- "reference": "e28058395b61777395347e23ada6c55be363eef0",
+ "url": "https://api.github.com/repos/salient-labs/toolkit-cli/zipball/8ed34826739c218c401d7a4a8af3c3c99e660c9a",
+ "reference": "8ed34826739c218c401d7a4a8af3c3c99e660c9a",
"shasum": ""
},
"require": {
@@ -401,20 +401,20 @@
"issues": "https://github.com/salient-labs/toolkit/issues",
"source": "https://github.com/salient-labs/toolkit"
},
- "time": "2024-11-29T09:22:45+00:00"
+ "time": "2025-01-13T06:34:58+00:00"
},
{
"name": "salient/collections",
- "version": "v0.99.65",
+ "version": "v0.99.68",
"source": {
"type": "git",
"url": "https://github.com/salient-labs/toolkit-collections.git",
- "reference": "c8739544f12bd5880b65fd840e3cd7af559aac53"
+ "reference": "238bd617c099c5e92043e22682fc0f11c5eadeb1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/salient-labs/toolkit-collections/zipball/c8739544f12bd5880b65fd840e3cd7af559aac53",
- "reference": "c8739544f12bd5880b65fd840e3cd7af559aac53",
+ "url": "https://api.github.com/repos/salient-labs/toolkit-collections/zipball/238bd617c099c5e92043e22682fc0f11c5eadeb1",
+ "reference": "238bd617c099c5e92043e22682fc0f11c5eadeb1",
"shasum": ""
},
"require": {
@@ -443,20 +443,20 @@
"issues": "https://github.com/salient-labs/toolkit/issues",
"source": "https://github.com/salient-labs/toolkit"
},
- "time": "2024-12-09T13:32:56+00:00"
+ "time": "2025-01-13T06:34:58+00:00"
},
{
"name": "salient/console",
- "version": "v0.99.65",
+ "version": "v0.99.68",
"source": {
"type": "git",
"url": "https://github.com/salient-labs/toolkit-console.git",
- "reference": "8c30d874aab3b08a4b539dc02288d52885352764"
+ "reference": "10a8c1d24c08ea69e0eac02b11882f1be13d6878"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/salient-labs/toolkit-console/zipball/8c30d874aab3b08a4b539dc02288d52885352764",
- "reference": "8c30d874aab3b08a4b539dc02288d52885352764",
+ "url": "https://api.github.com/repos/salient-labs/toolkit-console/zipball/10a8c1d24c08ea69e0eac02b11882f1be13d6878",
+ "reference": "10a8c1d24c08ea69e0eac02b11882f1be13d6878",
"shasum": ""
},
"require": {
@@ -493,20 +493,20 @@
"issues": "https://github.com/salient-labs/toolkit/issues",
"source": "https://github.com/salient-labs/toolkit"
},
- "time": "2024-11-29T09:22:45+00:00"
+ "time": "2025-01-13T06:34:58+00:00"
},
{
"name": "salient/container",
- "version": "v0.99.65",
+ "version": "v0.99.68",
"source": {
"type": "git",
"url": "https://github.com/salient-labs/toolkit-container.git",
- "reference": "46cef04555303e899772560ce717858b3fb25fc6"
+ "reference": "718d0f2c4ddcccf5f042fc5f50a5b74aa7ac6c31"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/salient-labs/toolkit-container/zipball/46cef04555303e899772560ce717858b3fb25fc6",
- "reference": "46cef04555303e899772560ce717858b3fb25fc6",
+ "url": "https://api.github.com/repos/salient-labs/toolkit-container/zipball/718d0f2c4ddcccf5f042fc5f50a5b74aa7ac6c31",
+ "reference": "718d0f2c4ddcccf5f042fc5f50a5b74aa7ac6c31",
"shasum": ""
},
"require": {
@@ -547,20 +547,20 @@
"issues": "https://github.com/salient-labs/toolkit/issues",
"source": "https://github.com/salient-labs/toolkit"
},
- "time": "2024-11-14T04:09:41+00:00"
+ "time": "2025-01-13T06:34:58+00:00"
},
{
"name": "salient/contracts",
- "version": "v0.99.65",
+ "version": "v0.99.68",
"source": {
"type": "git",
"url": "https://github.com/salient-labs/toolkit-contracts.git",
- "reference": "0180046156ff3a17e6ad39f7688333e0cbf8e306"
+ "reference": "0a973d13b26db51430e52baf2526f4af77a1c9db"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/salient-labs/toolkit-contracts/zipball/0180046156ff3a17e6ad39f7688333e0cbf8e306",
- "reference": "0180046156ff3a17e6ad39f7688333e0cbf8e306",
+ "url": "https://api.github.com/repos/salient-labs/toolkit-contracts/zipball/0a973d13b26db51430e52baf2526f4af77a1c9db",
+ "reference": "0a973d13b26db51430e52baf2526f4af77a1c9db",
"shasum": ""
},
"require": {
@@ -596,20 +596,20 @@
"issues": "https://github.com/salient-labs/toolkit/issues",
"source": "https://github.com/salient-labs/toolkit"
},
- "time": "2024-12-09T13:32:56+00:00"
+ "time": "2025-01-13T06:34:58+00:00"
},
{
"name": "salient/core",
- "version": "v0.99.65",
+ "version": "v0.99.68",
"source": {
"type": "git",
"url": "https://github.com/salient-labs/toolkit-core.git",
- "reference": "472f6e1983666bf5702b93a79bf40b3a62133a2f"
+ "reference": "d1d91ab432c85c3f9589b2965febb64d7ae6d0bb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/salient-labs/toolkit-core/zipball/472f6e1983666bf5702b93a79bf40b3a62133a2f",
- "reference": "472f6e1983666bf5702b93a79bf40b3a62133a2f",
+ "url": "https://api.github.com/repos/salient-labs/toolkit-core/zipball/d1d91ab432c85c3f9589b2965febb64d7ae6d0bb",
+ "reference": "d1d91ab432c85c3f9589b2965febb64d7ae6d0bb",
"shasum": ""
},
"require": {
@@ -644,20 +644,20 @@
"issues": "https://github.com/salient-labs/toolkit/issues",
"source": "https://github.com/salient-labs/toolkit"
},
- "time": "2024-11-29T09:22:45+00:00"
+ "time": "2025-01-13T06:34:58+00:00"
},
{
"name": "salient/iterators",
- "version": "v0.99.65",
+ "version": "v0.99.68",
"source": {
"type": "git",
"url": "https://github.com/salient-labs/toolkit-iterators.git",
- "reference": "0666eef3dbed163b94a246d533f1cb45f01b2c5d"
+ "reference": "88e67e1bd95bf12e2bf539f77b3f121db1218b46"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/salient-labs/toolkit-iterators/zipball/0666eef3dbed163b94a246d533f1cb45f01b2c5d",
- "reference": "0666eef3dbed163b94a246d533f1cb45f01b2c5d",
+ "url": "https://api.github.com/repos/salient-labs/toolkit-iterators/zipball/88e67e1bd95bf12e2bf539f77b3f121db1218b46",
+ "reference": "88e67e1bd95bf12e2bf539f77b3f121db1218b46",
"shasum": ""
},
"require": {
@@ -686,20 +686,20 @@
"issues": "https://github.com/salient-labs/toolkit/issues",
"source": "https://github.com/salient-labs/toolkit"
},
- "time": "2024-11-29T09:22:45+00:00"
+ "time": "2025-01-13T06:34:58+00:00"
},
{
"name": "salient/phpdoc",
- "version": "v0.99.65",
+ "version": "v0.99.68",
"source": {
"type": "git",
"url": "https://github.com/salient-labs/toolkit-phpdoc.git",
- "reference": "36bb531ced37c54cbadc6e4d98efecb8ff1ea364"
+ "reference": "1ce2025b046791436453ce44d0e4e50eff6a8f53"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/salient-labs/toolkit-phpdoc/zipball/36bb531ced37c54cbadc6e4d98efecb8ff1ea364",
- "reference": "36bb531ced37c54cbadc6e4d98efecb8ff1ea364",
+ "url": "https://api.github.com/repos/salient-labs/toolkit-phpdoc/zipball/1ce2025b046791436453ce44d0e4e50eff6a8f53",
+ "reference": "1ce2025b046791436453ce44d0e4e50eff6a8f53",
"shasum": ""
},
"require": {
@@ -729,20 +729,20 @@
"issues": "https://github.com/salient-labs/toolkit/issues",
"source": "https://github.com/salient-labs/toolkit"
},
- "time": "2024-11-15T09:20:32+00:00"
+ "time": "2025-01-13T06:34:58+00:00"
},
{
"name": "salient/polyfills",
- "version": "v0.99.65",
+ "version": "v0.99.68",
"source": {
"type": "git",
"url": "https://github.com/salient-labs/toolkit-polyfills.git",
- "reference": "7f1cc7cff859b5fbccc5014fa5025e62f4aa50c4"
+ "reference": "c02ad4a8e653029eb0ae8cc91c2733ea7a6f5de1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/salient-labs/toolkit-polyfills/zipball/7f1cc7cff859b5fbccc5014fa5025e62f4aa50c4",
- "reference": "7f1cc7cff859b5fbccc5014fa5025e62f4aa50c4",
+ "url": "https://api.github.com/repos/salient-labs/toolkit-polyfills/zipball/c02ad4a8e653029eb0ae8cc91c2733ea7a6f5de1",
+ "reference": "c02ad4a8e653029eb0ae8cc91c2733ea7a6f5de1",
"shasum": ""
},
"require": {
@@ -773,20 +773,20 @@
"issues": "https://github.com/salient-labs/toolkit/issues",
"source": "https://github.com/salient-labs/toolkit"
},
- "time": "2024-11-14T04:09:41+00:00"
+ "time": "2025-01-13T06:34:58+00:00"
},
{
"name": "salient/utils",
- "version": "v0.99.65",
+ "version": "v0.99.68",
"source": {
"type": "git",
"url": "https://github.com/salient-labs/toolkit-utils.git",
- "reference": "62b87c21a28c4990ce731d019a2f0d8250e42a5c"
+ "reference": "eb08bf882f7d465935df7092326ebd1f995cbe18"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/salient-labs/toolkit-utils/zipball/62b87c21a28c4990ce731d019a2f0d8250e42a5c",
- "reference": "62b87c21a28c4990ce731d019a2f0d8250e42a5c",
+ "url": "https://api.github.com/repos/salient-labs/toolkit-utils/zipball/eb08bf882f7d465935df7092326ebd1f995cbe18",
+ "reference": "eb08bf882f7d465935df7092326ebd1f995cbe18",
"shasum": ""
},
"require": {
@@ -820,7 +820,7 @@
"issues": "https://github.com/salient-labs/toolkit/issues",
"source": "https://github.com/salient-labs/toolkit"
},
- "time": "2024-12-09T13:32:56+00:00"
+ "time": "2025-01-13T06:34:58+00:00"
},
{
"name": "sebastian/diff",
@@ -2587,16 +2587,16 @@
},
{
"name": "salient/phpstan",
- "version": "v0.99.65",
+ "version": "v0.99.68",
"source": {
"type": "git",
"url": "https://github.com/salient-labs/toolkit-phpstan.git",
- "reference": "eaf63f2951f0fa44b967b530ceb8cb58d309c56f"
+ "reference": "2e747960ae45fbc2d4e2e8c8ecda9cd3ff79e64f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/salient-labs/toolkit-phpstan/zipball/eaf63f2951f0fa44b967b530ceb8cb58d309c56f",
- "reference": "eaf63f2951f0fa44b967b530ceb8cb58d309c56f",
+ "url": "https://api.github.com/repos/salient-labs/toolkit-phpstan/zipball/2e747960ae45fbc2d4e2e8c8ecda9cd3ff79e64f",
+ "reference": "2e747960ae45fbc2d4e2e8c8ecda9cd3ff79e64f",
"shasum": ""
},
"require": {
@@ -2632,20 +2632,20 @@
"issues": "https://github.com/salient-labs/toolkit/issues",
"source": "https://github.com/salient-labs/toolkit"
},
- "time": "2024-11-16T06:02:30+00:00"
+ "time": "2025-01-14T04:37:48+00:00"
},
{
"name": "salient/sli",
- "version": "v0.99.65",
+ "version": "v0.99.68",
"source": {
"type": "git",
"url": "https://github.com/salient-labs/toolkit-sli.git",
- "reference": "8264f3b4343eeeb8fdcdc6d1d2ab9c27cd1a92c5"
+ "reference": "c61312211228c1901611c4430c726aaf365ca499"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/salient-labs/toolkit-sli/zipball/8264f3b4343eeeb8fdcdc6d1d2ab9c27cd1a92c5",
- "reference": "8264f3b4343eeeb8fdcdc6d1d2ab9c27cd1a92c5",
+ "url": "https://api.github.com/repos/salient-labs/toolkit-sli/zipball/c61312211228c1901611c4430c726aaf365ca499",
+ "reference": "c61312211228c1901611c4430c726aaf365ca499",
"shasum": ""
},
"require": {
@@ -2688,20 +2688,20 @@
"issues": "https://github.com/salient-labs/toolkit/issues",
"source": "https://github.com/salient-labs/toolkit"
},
- "time": "2024-11-29T09:22:45+00:00"
+ "time": "2025-01-13T11:49:04+00:00"
},
{
"name": "salient/sync",
- "version": "v0.99.65",
+ "version": "v0.99.68",
"source": {
"type": "git",
"url": "https://github.com/salient-labs/toolkit-sync.git",
- "reference": "737523b6dcaf3382ed51644e240c5796ee5e0de0"
+ "reference": "dfd118a56811f660ed7ae1b2c4587fbc3aed8d6f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/salient-labs/toolkit-sync/zipball/737523b6dcaf3382ed51644e240c5796ee5e0de0",
- "reference": "737523b6dcaf3382ed51644e240c5796ee5e0de0",
+ "url": "https://api.github.com/repos/salient-labs/toolkit-sync/zipball/dfd118a56811f660ed7ae1b2c4587fbc3aed8d6f",
+ "reference": "dfd118a56811f660ed7ae1b2c4587fbc3aed8d6f",
"shasum": ""
},
"require": {
@@ -2740,11 +2740,11 @@
"issues": "https://github.com/salient-labs/toolkit/issues",
"source": "https://github.com/salient-labs/toolkit"
},
- "time": "2024-11-29T09:22:45+00:00"
+ "time": "2025-01-14T03:45:20+00:00"
},
{
"name": "salient/testing",
- "version": "v0.99.65",
+ "version": "v0.99.68",
"source": {
"type": "git",
"url": "https://github.com/salient-labs/toolkit-testing.git",
diff --git a/docs/Rules.md b/docs/Rules.md
index 83ffac9c..16cafaf5 100644
--- a/docs/Rules.md
+++ b/docs/Rules.md
@@ -33,13 +33,14 @@ This file is generated by [generate.sh][], so any changes should be made there o
| `PlaceComments` (1) | Y | - | 1 | `processTokens()` | 126 |
| `PreserveNewlines` | - | Y | 1 | `processTokens()` | 200 |
| `PreserveOneLineStatements` | - | - | 1 | `processStatements()` | 202 |
-| `BlankBeforeReturn` | - | - | 1 | `processTokens()` | 220 |
-| `VerticalSpacing` (1) | Y | - | 1 | `processTokens()` | 240 |
-| `VerticalSpacing` (2) | Y | - | 1 | `processList()` | 240 |
-| `VerticalSpacing` (3) | Y | - | 1 | `processDeclarations()` | 240 |
-| `StrictLists` | - | - | 1 | `processList()` | 242 |
-| `StrictExpressions` | - | - | 1 | `processTokens()` | 244 |
-| `SemiStrictExpressions` | - | - | 1 | `processTokens()` | 246 |
+| `BlankBeforeReturn` | - | - | 1 | `processTokens()` | 204 |
+| `VerticalSpacing` (1) | Y | - | 1 | `processList()` | 220 |
+| `VerticalSpacing` (2) | Y | - | 1 | `processDeclarations()` | 220 |
+| `StrictExpressions` | - | - | 1 | `processTokens()` | 222 |
+| `SemiStrictExpressions` | - | - | 1 | `processTokens()` | 224 |
+| `StrictLists` | - | - | 1 | `processList()` | 226 |
+| `PlaceBrackets` | Y | - | 1 | `processTokens()` | 240 |
+| `VerticalSpacing` (3) | Y | - | 1 | `processTokens()` | 242 |
| `AlignChains` (1) | - | - | 1 | `processTokens()` | 280 |
| `DeclarationSpacing` | - | Y | 1 | `processDeclarations()` | 299 |
| `StandardIndentation` | Y | - | 1 | `processTokens()` | 300 |
@@ -165,7 +166,7 @@ Spaces are added before and after operators not mentioned above.
### `StandardSpacing` (1)
-(mandatory, `processTokens()`, priority 104, tokens: `,` | `T_DECLARE` | `T_MATCH` | `T_ATTRIBUTE` | `T_OPEN_TAG` | `T_OPEN_TAG_WITH_ECHO` | `T_CLOSE_TAG` | `T_START_HEREDOC` | `T_ATTRIBUTE_COMMENT`)
+(mandatory, `processTokens()`, priority 104, tokens: `,` | `T_FOR` | `T_DECLARE` | `T_MATCH` | `T_ATTRIBUTE` | `T_OPEN_TAG` | `T_OPEN_TAG_WITH_ECHO` | `T_CLOSE_TAG` | `T_START_HEREDOC` | `T_ATTRIBUTE_COMMENT`)
If the indentation level of an open tag aligns with a tab stop, and a close tag is found in the same scope (or the document has no close tag, and the open tag is in the global scope), a callback is registered to align nested tokens with it. An additional level of indentation is applied if the formatter's `IndentBetweenTags` property is `true`.
@@ -177,6 +178,7 @@ Whitespace is applied to other tokens as follows:
- **Commas:** leading whitespace is suppressed, and trailing spaces are added.
- **`declare` expressions:** whitespace is suppressed between parentheses.
+- **`for` loops:** whitespace in empty expressions is suppressed.
- **`match` expressions:** newlines are added after delimiters between arms.
- **Attributes:** in parameters, property hooks, anonymous functions and arrow functions, spaces are added before and after attributes, and trailing blank lines are suppressed. For other attributes, leading and trailing newlines are added.
- **Heredocs:** leading newlines are suppressed in strict PSR-12 mode.
@@ -268,66 +270,73 @@ Attributes on their own line are excluded from consideration.
### `BlankBeforeReturn`
-(optional, `processTokens()`, priority 220, tokens: `T_YIELD` | `T_YIELD_FROM` | `T_RETURN`)
+(optional, `processTokens()`, priority 204, tokens: `T_YIELD` | `T_YIELD_FROM` | `T_RETURN`)
Blank lines are added before non-consecutive `return`, `yield` and `yield from` statements.
### `VerticalSpacing` (1)
-(mandatory, `processTokens()`, priority 240, tokens: `&` | `?` | `^` | `{` | `|` | `T_LOGICAL_OR` | `T_LOGICAL_XOR` | `T_LOGICAL_AND` | `T_FOR` | `T_BOOLEAN_OR` | `T_BOOLEAN_AND` | `T_OBJECT_OPERATOR` | `T_NULLSAFE_OBJECT_OPERATOR` | `T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG` | `T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG`)
+(mandatory, `processList()`, priority 220)
-In expressions where one or more boolean operators have an adjacent newline, newlines are added to other boolean operators of equal or lower precedence.
-
-In `for` loops:
+If interface lists break over multiple lines and neither `StrictLists` nor `AlignLists` are enabled, a newline is added before the first interface.
-- If an expression with multiple expressions breaks over multiple lines, newlines are added after comma-delimited expressions, and blank lines are added after semicolon-delimited expressions.
-- Otherwise, if an expression breaks over multiple lines, newlines are added after semicolon-delimited expressions.
-- Otherwise, if the second or third expression has a leading newline, a newline is added before the other.
-- Whitespace in empty expressions is suppressed.
+Arrays and argument lists with trailing ("magic") commas are split into one item per line.
-Newlines are added before open braces that belong to top-level declarations and anonymous classes declared over multiple lines.
+If parameter lists have 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 parameters to improve readability.
-Newlines are added before both operators in ternary expressions where one operator has a leading newline.
+### `VerticalSpacing` (2)
-In method chains where an object operator (`->` or `?->`) has a leading newline, newlines are added before every object operator. If the `AlignChains` rule is enabled and strict PSR-12 compliance is not, the first object operator in the chain is excluded from this operation.
+(mandatory, `processDeclarations()`, priority 220, declarations: `CONST` | `USE` | `PROPERTY` | `PARAM` | `USE_CONST` | `USE_FUNCTION` | `USE_TRAIT`)
-### `VerticalSpacing` (2)
+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 trait insertions that break over multiple lines.
-(mandatory, `processList()`, priority 240)
+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.
-If interface lists break over multiple lines and neither `StrictLists` nor `AlignLists` are enabled, a newline is added before the first interface.
+### `StrictExpressions`
-Arrays and argument lists with trailing ("magic") commas are split into one item per line.
+(optional, `processTokens()`, priority 222, tokens: `T_IF` | `T_ELSEIF` | `T_WHILE` | `T_FOR` | `T_FOREACH` | `T_SWITCH`)
-If parameter lists have 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 parameters to improve readability.
+Newlines are added before and after control structure expressions that break over multiple lines. In `for` expressions that break over multiple lines, newlines are also added after semicolons between expressions.
-### `VerticalSpacing` (3)
+### `SemiStrictExpressions`
-(mandatory, `processDeclarations()`, priority 240, declarations: `CONST` | `USE` | `PROPERTY` | `PARAM` | `USE_CONST` | `USE_FUNCTION` | `USE_TRAIT`)
+(optional, `processTokens()`, priority 224, tokens: `T_IF` | `T_ELSEIF` | `T_WHILE` | `T_FOR` | `T_FOREACH` | `T_SWITCH`)
-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 trait insertions that break over multiple lines.
+Newlines are added before and after control structure expressions with newlines between siblings. In `for` expressions that break over multiple lines, newlines are also added after semicolons between expressions.
-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.
+> Unlike `StrictExpressions`, this rule does not apply leading and trailing newlines to expressions that would not break over multiple lines if tokens between brackets were removed.
### `StrictLists`
-(optional, `processList()`, priority 242)
+(optional, `processList()`, priority 226)
Items in lists are arranged horizontally or vertically by replicating the arrangement of the first and second items.
-### `StrictExpressions`
+### `PlaceBrackets`
-(optional, `processTokens()`, priority 244, tokens: `T_IF` | `T_ELSEIF` | `T_WHILE` | `T_FOR` | `T_FOREACH` | `T_SWITCH`)
+(mandatory, `processTokens()`, priority 240, tokens: `(` | `[` | `{` | `T_ATTRIBUTE` | `T_DOLLAR_OPEN_CURLY_BRACES` | `T_CURLY_OPEN`)
-Newlines are added before and after control structure expressions that break over multiple lines.
+Inner whitespace is copied from open brackets to close brackets.
-### `SemiStrictExpressions`
+Structural and `match` expression braces are ignored.
-(optional, `processTokens()`, priority 246, tokens: `T_IF` | `T_ELSEIF` | `T_WHILE` | `T_FOR` | `T_FOREACH` | `T_SWITCH`)
+### `VerticalSpacing` (3)
-Newlines are added before and after control structure expressions with newlines between siblings.
+(mandatory, `processTokens()`, priority 242, tokens: `&` | `?` | `^` | `{` | `|` | `T_LOGICAL_OR` | `T_LOGICAL_XOR` | `T_LOGICAL_AND` | `T_FOR` | `T_BOOLEAN_OR` | `T_BOOLEAN_AND` | `T_OBJECT_OPERATOR` | `T_NULLSAFE_OBJECT_OPERATOR` | `T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG` | `T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG`)
-> Unlike `StrictExpressions`, this rule does not apply leading and trailing newlines to expressions that would not break over multiple lines if tokens between brackets were removed.
+In expressions where one or more boolean operators have an adjacent newline, newlines are added to other boolean operators of equal or lower precedence.
+
+In `for` loops:
+
+- If an expression with multiple expressions breaks over multiple lines, newlines are added after comma-delimited expressions, and blank lines are added after semicolon-delimited expressions.
+- Otherwise, if an expression breaks over multiple lines, newlines are added after semicolon-delimited expressions.
+- Otherwise, if the second or third expression has a leading newline, a newline is added before the other.
+
+Newlines are added before open braces that belong to top-level declarations and anonymous classes declared over multiple lines.
+
+Newlines are added before both operators in ternary expressions where one operator has a leading newline.
+
+In method chains where an object operator (`->` or `?->`) has a leading newline, newlines are added before every object operator. If the `AlignChains` rule is enabled and strict PSR-12 compliance is not, the first object operator in the chain is excluded from this operation.
### `AlignChains` (1)
@@ -362,7 +371,7 @@ Blank lines are also added before and after each group of declarations. They are
(mandatory, `processTokens()`, priority 300, tokens: `*`)
-The `Indent` and inner whitespace of each open bracket is copied to its close bracket, and the `Indent` of tokens between brackets with inner newlines is incremented.
+`Indent` is copied from open brackets to close brackets, and the `Indent` of tokens between brackets with inner newlines is incremented.
### `SwitchIndentation`
@@ -579,64 +588,66 @@ Newlines and spaces are added after tokens that would otherwise fail to parse. T
## `TokenRule` classes, by token
-| Token | Rules |
-| ------------------------------------------- | ------------------------------------------------------------------------------------------ |
-| `*` | `HangingIndentation`, `StandardIndentation` |
-| `* (except virtual)` | `PreserveNewlines` |
-| `T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG` | `VerticalSpacing` |
-| `T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG` | `VerticalSpacing` |
-| `T_AND` | `VerticalSpacing` |
-| `T_ATTRIBUTE` | `StandardSpacing` |
-| `T_ATTRIBUTE_COMMENT` | `StandardSpacing` |
-| `T_BACKTICK` | `ProtectStrings` |
-| `T_BOOLEAN_AND` | `VerticalSpacing` |
-| `T_BOOLEAN_OR` | `VerticalSpacing` |
-| `T_CASE` | `SwitchIndentation` |
-| `T_CATCH` | `Drupal` |
-| `T_CLOSE_TAG` | `StandardSpacing` |
-| `T_COALESCE` | `AlignTernaryOperators` |
-| `T_COLON` | `StatementSpacing`, `WordPress` |
-| `T_COMMA` | `StandardSpacing` |
-| `T_COMMENT` | `NormaliseComments`, `PlaceComments`, `WordPress` |
-| `T_CONCAT` | `Laravel`, `Symfony` |
-| `T_CONSTANT_ENCAPSED_STRING` | `NormaliseStrings` |
-| `T_DECLARE` | `StandardSpacing` |
-| `T_DEFAULT` | `SwitchIndentation` |
-| `T_DNUMBER` | `NormaliseNumbers` |
-| `T_DO` | `ControlStructureSpacing` |
-| `T_DOC_COMMENT` | `Drupal`, `NormaliseComments`, `PlaceComments`, `WordPress` |
-| `T_DOUBLE_QUOTE` | `ProtectStrings` |
-| `T_ELSE` | `ControlStructureSpacing`, `Drupal` |
-| `T_ELSEIF` | `ControlStructureSpacing`, `Drupal`, `SemiStrictExpressions`, `StrictExpressions` |
-| `T_ENCAPSED_AND_WHITESPACE` | `NormaliseStrings` |
-| `T_FINALLY` | `Drupal` |
-| `T_FN` | `AlignArrowFunctions`, `Laravel`, `Symfony` |
-| `T_FOR` | `ControlStructureSpacing`, `SemiStrictExpressions`, `StrictExpressions`, `VerticalSpacing` |
-| `T_FOREACH` | `ControlStructureSpacing`, `SemiStrictExpressions`, `StrictExpressions` |
-| `T_IF` | `ControlStructureSpacing`, `SemiStrictExpressions`, `StrictExpressions` |
-| `T_LNUMBER` | `NormaliseNumbers` |
-| `T_LOGICAL_AND` | `VerticalSpacing` |
-| `T_LOGICAL_NOT` | `Laravel`, `WordPress` |
-| `T_LOGICAL_OR` | `VerticalSpacing` |
-| `T_LOGICAL_XOR` | `VerticalSpacing` |
-| `T_MATCH` | `StandardSpacing` |
-| `T_NULLSAFE_OBJECT_OPERATOR` | `AlignChains`, `VerticalSpacing` |
-| `T_OBJECT_OPERATOR` | `AlignChains`, `VerticalSpacing` |
-| `T_OPEN_BRACE` | `PlaceBraces`, `VerticalSpacing`, `WordPress` |
-| `T_OPEN_BRACKET` | `WordPress` |
-| `T_OPEN_PARENTHESIS` | `WordPress` |
-| `T_OPEN_TAG` | `StandardSpacing` |
-| `T_OPEN_TAG_WITH_ECHO` | `StandardSpacing` |
-| `T_OR` | `VerticalSpacing` |
-| `T_QUESTION` | `AlignTernaryOperators`, `VerticalSpacing` |
-| `T_RETURN` | `BlankBeforeReturn` |
-| `T_SEMICOLON` | `StatementSpacing` |
-| `T_START_HEREDOC` | `FormatHeredocs`, `ProtectStrings`, `StandardSpacing` |
-| `T_SWITCH` | `SemiStrictExpressions`, `StrictExpressions`, `SwitchIndentation` |
-| `T_WHILE` | `ControlStructureSpacing`, `SemiStrictExpressions`, `StrictExpressions` |
-| `T_XOR` | `VerticalSpacing` |
-| `T_YIELD` | `BlankBeforeReturn` |
-| `T_YIELD_FROM` | `BlankBeforeReturn` |
+| Token | Rules |
+| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
+| `*` | `HangingIndentation`, `StandardIndentation` |
+| `* (except virtual)` | `PreserveNewlines` |
+| `T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG` | `VerticalSpacing` |
+| `T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG` | `VerticalSpacing` |
+| `T_AND` | `VerticalSpacing` |
+| `T_ATTRIBUTE` | `PlaceBrackets`, `StandardSpacing` |
+| `T_ATTRIBUTE_COMMENT` | `StandardSpacing` |
+| `T_BACKTICK` | `ProtectStrings` |
+| `T_BOOLEAN_AND` | `VerticalSpacing` |
+| `T_BOOLEAN_OR` | `VerticalSpacing` |
+| `T_CASE` | `SwitchIndentation` |
+| `T_CATCH` | `Drupal` |
+| `T_CLOSE_TAG` | `StandardSpacing` |
+| `T_COALESCE` | `AlignTernaryOperators` |
+| `T_COLON` | `StatementSpacing`, `WordPress` |
+| `T_COMMA` | `StandardSpacing` |
+| `T_COMMENT` | `NormaliseComments`, `PlaceComments`, `WordPress` |
+| `T_CONCAT` | `Laravel`, `Symfony` |
+| `T_CONSTANT_ENCAPSED_STRING` | `NormaliseStrings` |
+| `T_CURLY_OPEN` | `PlaceBrackets` |
+| `T_DECLARE` | `StandardSpacing` |
+| `T_DEFAULT` | `SwitchIndentation` |
+| `T_DNUMBER` | `NormaliseNumbers` |
+| `T_DO` | `ControlStructureSpacing` |
+| `T_DOC_COMMENT` | `Drupal`, `NormaliseComments`, `PlaceComments`, `WordPress` |
+| `T_DOLLAR_OPEN_CURLY_BRACES` | `PlaceBrackets` |
+| `T_DOUBLE_QUOTE` | `ProtectStrings` |
+| `T_ELSE` | `ControlStructureSpacing`, `Drupal` |
+| `T_ELSEIF` | `ControlStructureSpacing`, `Drupal`, `SemiStrictExpressions`, `StrictExpressions` |
+| `T_ENCAPSED_AND_WHITESPACE` | `NormaliseStrings` |
+| `T_FINALLY` | `Drupal` |
+| `T_FN` | `AlignArrowFunctions`, `Laravel`, `Symfony` |
+| `T_FOR` | `ControlStructureSpacing`, `SemiStrictExpressions`, `StandardSpacing`, `StrictExpressions`, `VerticalSpacing` |
+| `T_FOREACH` | `ControlStructureSpacing`, `SemiStrictExpressions`, `StrictExpressions` |
+| `T_IF` | `ControlStructureSpacing`, `SemiStrictExpressions`, `StrictExpressions` |
+| `T_LNUMBER` | `NormaliseNumbers` |
+| `T_LOGICAL_AND` | `VerticalSpacing` |
+| `T_LOGICAL_NOT` | `Laravel`, `WordPress` |
+| `T_LOGICAL_OR` | `VerticalSpacing` |
+| `T_LOGICAL_XOR` | `VerticalSpacing` |
+| `T_MATCH` | `StandardSpacing` |
+| `T_NULLSAFE_OBJECT_OPERATOR` | `AlignChains`, `VerticalSpacing` |
+| `T_OBJECT_OPERATOR` | `AlignChains`, `VerticalSpacing` |
+| `T_OPEN_BRACE` | `PlaceBraces`, `PlaceBrackets`, `VerticalSpacing`, `WordPress` |
+| `T_OPEN_BRACKET` | `PlaceBrackets`, `WordPress` |
+| `T_OPEN_PARENTHESIS` | `PlaceBrackets`, `WordPress` |
+| `T_OPEN_TAG` | `StandardSpacing` |
+| `T_OPEN_TAG_WITH_ECHO` | `StandardSpacing` |
+| `T_OR` | `VerticalSpacing` |
+| `T_QUESTION` | `AlignTernaryOperators`, `VerticalSpacing` |
+| `T_RETURN` | `BlankBeforeReturn` |
+| `T_SEMICOLON` | `StatementSpacing` |
+| `T_START_HEREDOC` | `FormatHeredocs`, `ProtectStrings`, `StandardSpacing` |
+| `T_SWITCH` | `SemiStrictExpressions`, `StrictExpressions`, `SwitchIndentation` |
+| `T_WHILE` | `ControlStructureSpacing`, `SemiStrictExpressions`, `StrictExpressions` |
+| `T_XOR` | `VerticalSpacing` |
+| `T_YIELD` | `BlankBeforeReturn` |
+| `T_YIELD_FROM` | `BlankBeforeReturn` |
## `DeclarationRule` classes, by declaration type
diff --git a/scripts/get-rules.php b/scripts/get-rules.php
index ac7acb9d..352ad0df 100755
--- a/scripts/get-rules.php
+++ b/scripts/get-rules.php
@@ -26,9 +26,9 @@
/**
* @param array,is_mandatory:bool,is_default:bool,pass:int,method:string,priority:int,php_doc:PHPDoc|null,tokens:array|array{string}|null,declarations:array|array{'*'}|null}>> $array
- * @param-out array,is_mandatory:bool,is_default:bool,pass:int,method:string,priority:int,php_doc:PHPDoc|null,tokens:array|array{string}|null,declarations:array|array{'*'}|null}>> $array
* @param class-string $rule
* @param array,array>|null $callbackDocs
+ * @param-out array,is_mandatory:bool,is_default:bool,pass:int,method:string,priority:int,php_doc:PHPDoc|null,tokens:array|array{string}|null,declarations:array|array{'*'}|null}>> $array
*/
function maybeAddRule(
array &$array,
diff --git a/src/App/PrettyPHPCommand.php b/src/App/PrettyPHPCommand.php
index 583dffc1..6ef6e48a 100644
--- a/src/App/PrettyPHPCommand.php
+++ b/src/App/PrettyPHPCommand.php
@@ -1394,8 +1394,8 @@ private function getConfigFile(string $dir): ?string
/**
* @param array $files
- * @param-out array $files
* @param array $dirs
+ * @param-out array $files
* @param-out array $dirs
*/
private function addDir(string $dir, array &$files, array &$dirs): void
@@ -1434,8 +1434,8 @@ private function addDir(string $dir, array &$files, array &$dirs): void
/**
* @param SplFileInfo|string $file
* @param array $files
- * @param-out array $files
* @param array $dirs
+ * @param-out array $files
* @param-out array $dirs
*/
private function addFile($file, array &$files, array &$dirs): void
diff --git a/src/Catalog/TokenData.php b/src/Catalog/TokenData.php
index b88e2766..fbbf7796 100644
--- a/src/Catalog/TokenData.php
+++ b/src/Catalog/TokenData.php
@@ -53,50 +53,56 @@ interface TokenData
*/
public const PROPERTY_HOOKS = 7;
+ /**
+ * Collections of tokens associated with a T_FOR loop: expr1, expr2, expr3,
+ * semicolon delimiters, comma delimiters
+ */
+ public const FOR_PARTS = 8;
+
/**
* The last token of the string opened by the token
*/
- public const END_STRING = 8;
+ public const END_STRING = 9;
/**
* The first object operator in a chain of method calls
*/
- public const CHAIN = 9;
+ public const CHAIN = 10;
/**
* The other T_QUESTION or T_COLON associated with a TERNARY
*/
- public const OTHER_TERNARY = 10;
+ public const OTHER_TERNARY = 11;
/**
* The token ID of the delimiter associated with a LIST_PARENT token
*/
- public const LIST_DELIMITER = 11;
+ public const LIST_DELIMITER = 12;
/**
* A collection of items associated with a LIST_PARENT token
*/
- public const LIST_ITEMS = 12;
+ public const LIST_ITEMS = 13;
/**
* The number of items associated with a LIST_PARENT token
*/
- public const LIST_ITEM_COUNT = 13;
+ public const LIST_ITEM_COUNT = 14;
/**
* The LIST_PARENT of the first token in a LIST_ITEM
*/
- public const LIST_PARENT = 14;
+ public const LIST_PARENT = 15;
/**
* The content of a normalised DocBlock token (T_DOC_COMMENT or T_COMMENT)
* after delimiters and trailing whitespace are removed
*/
- public const COMMENT_CONTENT = 15;
+ public const COMMENT_CONTENT = 16;
/**
* An array of closures that align other tokens with the token when its
* output column changes
*/
- public const ALIGNMENT_CALLBACKS = 16;
+ public const ALIGNMENT_CALLBACKS = 17;
}
diff --git a/src/Concern/RuleTrait.php b/src/Concern/RuleTrait.php
index 547ccaab..a0029e3e 100644
--- a/src/Concern/RuleTrait.php
+++ b/src/Concern/RuleTrait.php
@@ -56,7 +56,7 @@ private function preserveOneLine(
}
$start->collect($end)
- ->applyInnerWhitespace(Space::CRITICAL_NO_BLANK | Space::CRITICAL_NO_LINE);
+ ->setInnerWhitespace(Space::CRITICAL_NO_BLANK | Space::CRITICAL_NO_LINE);
return true;
}
diff --git a/src/Formatter.php b/src/Formatter.php
index b104cb9c..dec02b5c 100644
--- a/src/Formatter.php
+++ b/src/Formatter.php
@@ -58,6 +58,7 @@
use Lkrms\PrettyPHP\Rule\NormaliseStrings;
use Lkrms\PrettyPHP\Rule\OperatorSpacing;
use Lkrms\PrettyPHP\Rule\PlaceBraces;
+use Lkrms\PrettyPHP\Rule\PlaceBrackets;
use Lkrms\PrettyPHP\Rule\PlaceComments;
use Lkrms\PrettyPHP\Rule\PreserveNewlines;
use Lkrms\PrettyPHP\Rule\PreserveOneLineStatements;
@@ -152,6 +153,7 @@ final class Formatter implements Buildable, Immutable
PlaceComments::class,
PreserveNewlines::class,
VerticalSpacing::class,
+ PlaceBrackets::class,
DeclarationSpacing::class,
StandardIndentation::class,
SwitchIndentation::class,
@@ -168,9 +170,9 @@ final class Formatter implements Buildable, Immutable
PreserveNewlines::class,
PreserveOneLineStatements::class,
BlankBeforeReturn::class,
- StrictLists::class,
StrictExpressions::class,
SemiStrictExpressions::class,
+ StrictLists::class,
AlignChains::class,
DeclarationSpacing::class,
AlignArrowFunctions::class,
diff --git a/src/Internal/TokenCollection.php b/src/Internal/TokenCollection.php
index d6ff57b1..c2d1ab93 100644
--- a/src/Internal/TokenCollection.php
+++ b/src/Internal/TokenCollection.php
@@ -324,7 +324,18 @@ public function toString(string $delimiter = ''): string
/**
* @return $this
*/
- public function applyWhitespace(int $whitespace)
+ public function setTokenWhitespace(int $whitespace): self
+ {
+ foreach ($this->Items as $token) {
+ $token->Whitespace |= $whitespace;
+ }
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function applyTokenWhitespace(int $whitespace): self
{
// Shift *_BEFORE and *_AFTER to their NO_* counterparts, then clear
// other bits
@@ -342,7 +353,7 @@ public function applyWhitespace(int $whitespace)
/**
* @return $this
*/
- public function applyInnerWhitespace(int $whitespace)
+ public function setInnerWhitespace(int $whitespace): self
{
$this->assertCollected();
@@ -354,7 +365,6 @@ public function applyInnerWhitespace(int $whitespace)
if ($this->count() < 2) {
return $this;
}
- $remove = $whitespace << 6 & 0b111111000000;
$ignore = true;
foreach ($this->Items as $token) {
if ($ignore) {
@@ -364,9 +374,6 @@ public function applyInnerWhitespace(int $whitespace)
|| $token->Data[Data::BOUND_TO]->index > $token->index
) {
$token->Whitespace |= $whitespace;
- if ($remove) {
- $token->doRemoveWhitespace($remove);
- }
}
}
return $this;
diff --git a/src/Parser.php b/src/Parser.php
index 13888e0b..665a5f25 100644
--- a/src/Parser.php
+++ b/src/Parser.php
@@ -162,10 +162,10 @@ private function linkTokens(array $tokens): void
* - `Data[Data::END_STRING]`
*
* @param non-empty-list $tokens
- * @param-out non-empty-list $tokens
* @param array>|null $tokensById
- * @param-out array> $tokensById
* @param Token[]|null $scopes
+ * @param-out non-empty-list $tokens
+ * @param-out array> $tokensById
* @param-out non-empty-array $scopes
*/
private function buildHierarchy(
@@ -719,20 +719,21 @@ private function parseStatements(array $scopes, ?array &$statements): void
}
/**
- * Pass 4: identify declarations and parse (some) expressions
+ * Pass 4: parse expressions
*
* Token properties set:
*
* - `Data[Data::DECLARATION_PARTS]`
* - `Data[Data::DECLARATION_TYPE]`
* - `Data[Data::PROPERTY_HOOKS]`
+ * - `Data[Data::FOR_PARTS]`
* - `Data[Data::OTHER_TERNARY]`
* - `Data[Data::CHAIN]`
*
* @param Token[] $statements
* @param array|null $declarations
- * @param-out array $declarations
* @param array>|null $declarationsByType
+ * @param-out array $declarations
* @param-out array> $declarationsByType
*/
private function parseExpressions(
@@ -750,7 +751,7 @@ private function parseExpressions(
$end = $statement->EndStatement;
$end = $end->OpenBracket ?? $end;
- // Detect non-anonymous declarations
+ // Parse non-anonymous declarations and `for` expressions
if (
$idx->AttributeOrDeclaration[$statement->id]
&& ($first = $this->skipNextSiblingsFrom($statement, $idx->Attribute))
@@ -835,6 +836,39 @@ private function parseExpressions(
}
}
}
+ } elseif ($statement->id === \T_FOR) {
+ /** @var Token */
+ $open = $statement->NextCode;
+ /** @var Token */
+ $close = $open->CloseBracket;
+ /** @var Token */
+ $first = $open->Next;
+ /** @var Token */
+ $last = $close->Prev;
+
+ $children = $open->children();
+ $semicolons = $children->getAnyOf(\T_SEMICOLON);
+ $commas = $children->getAnyOf(\T_COMMA);
+ /** @var Token */
+ $semi1 = $semicolons->first();
+ /** @var Token */
+ $second = $semi1->Next;
+ /** @var Token */
+ $semi2 = $semicolons->last();
+ /** @var Token */
+ $third = $semi2->Next;
+
+ $expr1 = $first->collect($semi1);
+ $expr2 = $second->collect($semi2);
+ $expr3 = $third->collect($last);
+
+ $statement->Data[Data::FOR_PARTS] = [
+ $expr1,
+ $expr2,
+ $expr3,
+ $semicolons,
+ $commas,
+ ];
}
$token = $statement;
diff --git a/src/Rule/BlankBeforeReturn.php b/src/Rule/BlankBeforeReturn.php
index d0b013b5..eb0f412a 100644
--- a/src/Rule/BlankBeforeReturn.php
+++ b/src/Rule/BlankBeforeReturn.php
@@ -22,7 +22,7 @@ final class BlankBeforeReturn implements TokenRule
public static function getPriority(string $method): ?int
{
return [
- self::PROCESS_TOKENS => 220,
+ self::PROCESS_TOKENS => 204,
][$method] ?? null;
}
diff --git a/src/Rule/DeclarationSpacing.php b/src/Rule/DeclarationSpacing.php
index 97bcd38d..2bd2e9cc 100644
--- a/src/Rule/DeclarationSpacing.php
+++ b/src/Rule/DeclarationSpacing.php
@@ -224,7 +224,7 @@ public function processDeclarations(array $declarations): void
&& !$prev->isMultiLine()
&& !$decl->isMultiLine()
)) {
- $prev->End->collect($token)->applyInnerWhitespace(Space::NO_BLANK);
+ $prev->End->collect($token)->setInnerWhitespace(Space::NO_BLANK);
$noBlankApplied = true;
} elseif (!$decl->isCollapsible()) {
// Apply "loose" spacing to multi-line declarations
diff --git a/src/Rule/OperatorSpacing.php b/src/Rule/OperatorSpacing.php
index e8002501..f4ce5f9c 100644
--- a/src/Rule/OperatorSpacing.php
+++ b/src/Rule/OperatorSpacing.php
@@ -133,7 +133,7 @@ public function processTokens(array $tokens): void
$token->Whitespace |= Space::NONE_BEFORE | Space::NONE_AFTER;
if (!$inTypeContext) {
/** @var Token $parent */
- $parent->outer()->applyInnerWhitespace(Space::NONE);
+ $parent->outer()->setInnerWhitespace(Space::NONE);
if (
$parent->PrevCode
&& $parent->PrevCode->id !== \T_OR
diff --git a/src/Rule/PlaceBrackets.php b/src/Rule/PlaceBrackets.php
new file mode 100644
index 00000000..8050f779
--- /dev/null
+++ b/src/Rule/PlaceBrackets.php
@@ -0,0 +1,76 @@
+ 240,
+ ][$method] ?? null;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public static function getTokens(AbstractTokenIndex $idx): array
+ {
+ return $idx->OpenBracket;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public static function needsSortedTokens(): bool
+ {
+ return false;
+ }
+
+ /**
+ * Apply the rule to the given tokens
+ *
+ * Inner whitespace is copied from open brackets to close brackets.
+ *
+ * Structural and `match` expression braces are ignored.
+ */
+ public function processTokens(array $tokens): void
+ {
+ foreach ($tokens as $token) {
+ if (
+ $token->Flags & Flag::STRUCTURAL_BRACE
+ || ($token->id === \T_OPEN_BRACE && $token->isMatchOpenBrace())
+ ) {
+ continue;
+ }
+
+ /** @var Token */
+ $close = $token->CloseBracket;
+ if (!$token->hasNewlineBeforeNextCode()) {
+ $close->Whitespace |= Space::NO_BLANK_BEFORE | Space::NO_LINE_BEFORE;
+ } else {
+ $close->Whitespace |= Space::LINE_BEFORE;
+ if (!$close->hasNewlineBefore()) {
+ $close->removeWhitespace(Space::NO_LINE_BEFORE);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Rule/Preset/Symfony.php b/src/Rule/Preset/Symfony.php
index 304b538f..7b6c7919 100644
--- a/src/Rule/Preset/Symfony.php
+++ b/src/Rule/Preset/Symfony.php
@@ -116,6 +116,6 @@ public function processList(Token $parent, TokenCollection $items, Token $lastCh
}
}
- $parent->outer()->applyInnerWhitespace(Space::NO_BLANK | Space::NO_LINE);
+ $parent->outer()->setInnerWhitespace(Space::NO_BLANK | Space::NO_LINE);
}
}
diff --git a/src/Rule/SemiStrictExpressions.php b/src/Rule/SemiStrictExpressions.php
index 7de8f571..7e726d9e 100644
--- a/src/Rule/SemiStrictExpressions.php
+++ b/src/Rule/SemiStrictExpressions.php
@@ -2,9 +2,11 @@
namespace Lkrms\PrettyPHP\Rule;
+use Lkrms\PrettyPHP\Catalog\TokenData as Data;
use Lkrms\PrettyPHP\Catalog\WhitespaceFlag as Space;
use Lkrms\PrettyPHP\Concern\TokenRuleTrait;
use Lkrms\PrettyPHP\Contract\TokenRule;
+use Lkrms\PrettyPHP\Internal\TokenCollection;
use Lkrms\PrettyPHP\AbstractTokenIndex;
use Lkrms\PrettyPHP\Token;
@@ -24,7 +26,7 @@ final class SemiStrictExpressions implements TokenRule
public static function getPriority(string $method): ?int
{
return [
- self::PROCESS_TOKENS => 246,
+ self::PROCESS_TOKENS => 224,
][$method] ?? null;
}
@@ -48,7 +50,8 @@ public static function needsSortedTokens(): bool
* Apply the rule to the given tokens
*
* Newlines are added before and after control structure expressions with
- * newlines between siblings.
+ * newlines between siblings. In `for` expressions that break over multiple
+ * lines, newlines are also added after semicolons between expressions.
*
* > Unlike `StrictExpressions`, this rule does not apply leading and
* > trailing newlines to expressions that would not break over multiple
@@ -58,15 +61,20 @@ public function processTokens(array $tokens): void
{
foreach ($tokens as $token) {
/** @var Token */
- $first = $token->NextCode;
- if ($first->hasNewlineAfter()) {
+ $open = $token->NextCode;
+ if ($open->hasNewlineAfter()) {
continue;
}
- if ($first->children()->pop()->tokenHasNewlineAfter(true)) {
+ if ($open->children()->pop()->tokenHasNewlineAfter(true)) {
/** @var Token */
- $last = $first->CloseBracket;
- $first->applyWhitespace(Space::LINE_AFTER);
- $last->applyWhitespace(Space::LINE_BEFORE);
+ $close = $open->CloseBracket;
+ $open->applyWhitespace(Space::LINE_AFTER);
+ $close->applyWhitespace(Space::LINE_BEFORE);
+ if ($token->id === \T_FOR) {
+ /** @var TokenCollection */
+ $semicolons = $token->Data[Data::FOR_PARTS][3];
+ $semicolons->setTokenWhitespace(Space::LINE_AFTER);
+ }
}
}
}
diff --git a/src/Rule/StandardIndentation.php b/src/Rule/StandardIndentation.php
index 5932c347..276c958a 100644
--- a/src/Rule/StandardIndentation.php
+++ b/src/Rule/StandardIndentation.php
@@ -2,7 +2,6 @@
namespace Lkrms\PrettyPHP\Rule;
-use Lkrms\PrettyPHP\Catalog\WhitespaceFlag as Space;
use Lkrms\PrettyPHP\Concern\TokenRuleTrait;
use Lkrms\PrettyPHP\Contract\TokenRule;
@@ -36,9 +35,8 @@ public static function needsSortedTokens(): bool
/**
* Apply the rule to the given tokens
*
- * The `Indent` and inner whitespace of each open bracket is copied to its
- * close bracket, and the `Indent` of tokens between brackets with inner
- * newlines is incremented.
+ * `Indent` is copied from open brackets to close brackets, and the `Indent`
+ * of tokens between brackets with inner newlines is incremented.
*/
public function processTokens(array $tokens): void
{
@@ -55,21 +53,11 @@ public function processTokens(array $tokens): void
$prev = $token->Prev;
$token->Indent = $prev->Indent;
- if ($close = $prev->CloseBracket) {
- if ($hasNewline = $prev->hasNewlineBeforeNextCode()) {
- $token->Indent++;
- }
-
- if (!$this->Idx->Virtual[$prev->id]) {
- if (!$hasNewline) {
- $close->Whitespace |= Space::NO_BLANK_BEFORE | Space::NO_LINE_BEFORE;
- } else {
- $close->Whitespace |= Space::LINE_BEFORE;
- if (!$close->hasNewlineBefore()) {
- $close->removeWhitespace(Space::NO_LINE_BEFORE);
- }
- }
- }
+ if (
+ $prev->CloseBracket
+ && $prev->hasNewlineBeforeNextCode()
+ ) {
+ $token->Indent++;
}
}
}
diff --git a/src/Rule/StandardSpacing.php b/src/Rule/StandardSpacing.php
index dc1f1286..952aeb69 100644
--- a/src/Rule/StandardSpacing.php
+++ b/src/Rule/StandardSpacing.php
@@ -48,6 +48,7 @@ public static function getTokens(AbstractTokenIndex $idx): array
\T_CLOSE_TAG => true,
\T_COMMA => true,
\T_DECLARE => true,
+ \T_FOR => true,
\T_MATCH => true,
\T_START_HEREDOC => true,
],
@@ -110,6 +111,7 @@ public static function needsSortedDeclarations(): bool
* added.
* - **`declare` expressions:** whitespace is suppressed between
* parentheses.
+ * - **`for` loops:** whitespace in empty expressions is suppressed.
* - **`match` expressions:** newlines are added after delimiters between
* arms.
* - **Attributes:** in parameters, property hooks, anonymous functions and
@@ -293,7 +295,23 @@ static function () use ($idx, $innerIndent, $next, $last) {
if ($token->id === \T_DECLARE) {
/** @var Token */
$nextCode = $token->NextCode;
- $nextCode->outer()->applyInnerWhitespace(Space::NONE);
+ $nextCode->outer()->setInnerWhitespace(Space::NONE);
+ continue;
+ }
+
+ if ($token->id === \T_FOR) {
+ [$expr1, $expr2, $expr3, $semicolons] = $token->Data[Data::FOR_PARTS];
+ /** @var TokenCollection $expr */
+ foreach ([$expr1, $expr2, $expr3] as $i => $expr) {
+ $count = $expr->count();
+ if ($i < 2 && $count === 1) {
+ $expr->setTokenWhitespace(Space::NONE_BEFORE);
+ } elseif ($i === 2 && $count === 0) {
+ /** @var Token */
+ $semi2 = $semicolons->last();
+ $semi2->Whitespace |= Space::NONE_AFTER;
+ }
+ }
continue;
}
diff --git a/src/Rule/StrictExpressions.php b/src/Rule/StrictExpressions.php
index db4ad39e..9f3f1699 100644
--- a/src/Rule/StrictExpressions.php
+++ b/src/Rule/StrictExpressions.php
@@ -2,9 +2,11 @@
namespace Lkrms\PrettyPHP\Rule;
+use Lkrms\PrettyPHP\Catalog\TokenData as Data;
use Lkrms\PrettyPHP\Catalog\WhitespaceFlag as Space;
use Lkrms\PrettyPHP\Concern\TokenRuleTrait;
use Lkrms\PrettyPHP\Contract\TokenRule;
+use Lkrms\PrettyPHP\Internal\TokenCollection;
use Lkrms\PrettyPHP\AbstractTokenIndex;
use Lkrms\PrettyPHP\Token;
@@ -24,7 +26,7 @@ final class StrictExpressions implements TokenRule
public static function getPriority(string $method): ?int
{
return [
- self::PROCESS_TOKENS => 244,
+ self::PROCESS_TOKENS => 222,
][$method] ?? null;
}
@@ -48,23 +50,29 @@ public static function needsSortedTokens(): bool
* Apply the rule to the given tokens
*
* Newlines are added before and after control structure expressions that
- * break over multiple lines.
+ * break over multiple lines. In `for` expressions that break over multiple
+ * lines, newlines are also added after semicolons between expressions.
*/
public function processTokens(array $tokens): void
{
foreach ($tokens as $token) {
/** @var Token */
- $first = $token->NextCode;
- if ($first->hasNewlineAfter()) {
+ $open = $token->NextCode;
+ if ($open->hasNewlineAfter()) {
continue;
}
/** @var Token */
- $last = $first->CloseBracket;
+ $close = $open->CloseBracket;
/** @var Token */
- $beforeLast = $last->Prev;
- if ($first->collect($beforeLast)->hasNewline()) {
- $first->applyWhitespace(Space::LINE_AFTER);
- $last->applyWhitespace(Space::LINE_BEFORE);
+ $last = $close->Prev;
+ if ($open->collect($last)->hasNewline()) {
+ $open->applyWhitespace(Space::LINE_AFTER);
+ $close->applyWhitespace(Space::LINE_BEFORE);
+ if ($token->id === \T_FOR) {
+ /** @var TokenCollection */
+ $semicolons = $token->Data[Data::FOR_PARTS][3];
+ $semicolons->setTokenWhitespace(Space::LINE_AFTER);
+ }
}
}
}
diff --git a/src/Rule/StrictLists.php b/src/Rule/StrictLists.php
index 275fbfcb..a9b36fe5 100644
--- a/src/Rule/StrictLists.php
+++ b/src/Rule/StrictLists.php
@@ -23,7 +23,7 @@ final class StrictLists implements ListRule
public static function getPriority(string $method): ?int
{
return [
- self::PROCESS_LIST => 242,
+ self::PROCESS_LIST => 226,
][$method] ?? null;
}
@@ -49,9 +49,9 @@ public function processList(Token $parent, TokenCollection $items, Token $lastCh
) {
$items = $items->shift();
}
- $items->applyWhitespace(Space::LINE_BEFORE);
+ $items->applyTokenWhitespace(Space::LINE_BEFORE);
} else {
- $items->applyWhitespace(Space::NO_BLANK_BEFORE | Space::NO_LINE_BEFORE);
+ $items->setTokenWhitespace(Space::NO_BLANK_BEFORE | Space::NO_LINE_BEFORE);
}
}
}
diff --git a/src/Rule/VerticalSpacing.php b/src/Rule/VerticalSpacing.php
index 71b1450c..d4add9d4 100644
--- a/src/Rule/VerticalSpacing.php
+++ b/src/Rule/VerticalSpacing.php
@@ -44,9 +44,9 @@ final class VerticalSpacing implements TokenRule, ListRule, DeclarationRule
public static function getPriority(string $method): ?int
{
return [
- self::PROCESS_TOKENS => 240,
- self::PROCESS_LIST => 240,
- self::PROCESS_DECLARATIONS => 240,
+ self::PROCESS_TOKENS => 242,
+ self::PROCESS_LIST => 220,
+ self::PROCESS_DECLARATIONS => 220,
][$method] ?? null;
}
@@ -191,7 +191,6 @@ public function reset(): void
* added after semicolon-delimited expressions.
* - Otherwise, if the second or third expression has a leading newline, a
* newline is added before the other.
- * - Whitespace in empty expressions is suppressed.
*
* Newlines are added before open braces that belong to top-level
* declarations and anonymous classes declared over multiple lines.
@@ -255,33 +254,14 @@ public function processTokens(array $tokens): void
}
}
} elseif ($token->id === \T_FOR) {
- /** @var Token */
- $open = $token->NextCode;
- /** @var Token */
- $close = $open->CloseBracket;
- /** @var Token */
- $first = $open->Next;
- /** @var Token */
- $last = $close->Prev;
-
- $children = $open->children();
- $commas = $children->getAnyOf(\T_COMMA);
- $semicolons = $children->getAnyOf(\T_SEMICOLON);
- /** @var Token */
- $semi1 = $semicolons->first();
- /** @var Token */
- $second = $semi1->Next;
- /** @var Token */
- $semi2 = $semicolons->last();
- /** @var Token */
- $third = $semi2->Next;
-
- $expr1 = $first->collect($semi1);
- $expr2 = $second->collect($semi2);
- $expr3 = $third->collect($last);
-
+ /**
+ * @var TokenCollection $semicolons
+ * @var TokenCollection $commas
+ */
+ [$expr1, $expr2, $expr3, $semicolons, $commas] = $token->Data[Data::FOR_PARTS];
$hasNewline = false;
$hasNewlineAndComma = false;
+ /** @var TokenCollection $expr */
foreach ([$expr1, $expr2, $expr3] as $expr) {
if ($expr->hasNewlineBetweenTokens()) {
$hasNewline = true;
@@ -292,20 +272,10 @@ public function processTokens(array $tokens): void
}
}
if ($hasNewlineAndComma) {
- $commas->applyWhitespace(Space::LINE_AFTER);
- $semicolons->applyWhitespace(Space::BLANK_AFTER);
+ $commas->setTokenWhitespace(Space::LINE_AFTER);
+ $semicolons->setTokenWhitespace(Space::BLANK_AFTER);
} elseif ($hasNewline || $semicolons->tokenHasNewlineAfter()) {
- $semicolons->applyWhitespace(Space::LINE_AFTER);
- }
-
- // Suppress whitespace in empty `for` loop expressions
- foreach ([$expr1, $expr2, $expr3] as $i => $expr) {
- $count = $expr->count();
- if ($i < 2 && $count === 1) {
- $expr->applyWhitespace(Space::NONE_BEFORE);
- } elseif ($i === 2 && $count === 0) {
- $semi2->Whitespace |= Space::NONE_AFTER;
- }
+ $semicolons->setTokenWhitespace(Space::LINE_AFTER);
}
} elseif ($token->id === \T_OPEN_BRACE) {
if (
@@ -381,7 +351,7 @@ public function processTokens(array $tokens): void
$chain = $chain->shift();
}
- $chain->applyWhitespace(Space::LINE_BEFORE);
+ $chain->setTokenWhitespace(Space::LINE_BEFORE);
}
}
}
@@ -418,7 +388,7 @@ public function processList(Token $parent, TokenCollection $items, Token $lastCh
if ($lastChild->id === \T_COMMA) {
$parent->Whitespace |= Space::LINE_AFTER;
$items->add($parent->CloseBracket)
- ->applyWhitespace(Space::LINE_BEFORE);
+ ->applyTokenWhitespace(Space::LINE_BEFORE);
}
if ($parent->id === \T_OPEN_PARENTHESIS && $parent->isParameterList()) {
@@ -454,7 +424,7 @@ public function processDeclarations(array $declarations): void
!$this->ListRuleEnabled
&& $commas->tokenHasNewlineBeforeNextCode()
)) {
- $commas->applyWhitespace(Space::LINE_AFTER);
+ $commas->setTokenWhitespace(Space::LINE_AFTER);
}
}
diff --git a/src/Token.php b/src/Token.php
index 33d07666..c6836dbf 100644
--- a/src/Token.php
+++ b/src/Token.php
@@ -57,7 +57,7 @@ final class Token extends GenericToken implements HasTokenNames, JsonSerializabl
/**
* @var array
- * @phpstan-var array{self,self|null,self|null,self,bool,TokenCollection,int,TokenCollection,self,self,self,int,TokenCollection,int,self,string,Closure[]}
+ * @phpstan-var array{self,self|null,self|null,self,bool,TokenCollection,int,TokenCollection,array{TokenCollection,TokenCollection,TokenCollection,TokenCollection,TokenCollection},self,self,self,int,TokenCollection,int,self,string,Closure[]}
*/
public array $Data;
diff --git a/src/TokenUtil.php b/src/TokenUtil.php
index 5cf6c6f8..96ba5cf5 100644
--- a/src/TokenUtil.php
+++ b/src/TokenUtil.php
@@ -506,7 +506,14 @@ public static function serialize(Token $token): array
? self::describe($value)
: ($value instanceof TokenCollection
? $value->toString(' ')
- : (is_array($value) ? count($value) : $value));
+ : (is_array($value)
+ ? (Arr::of($value, TokenCollection::class)
+ ? array_map(
+ fn(TokenCollection $coll) => $coll->toString(' '),
+ $value,
+ )
+ : count($value))
+ : $value));
}
}
diff --git a/tests/fixtures/Formatter/in/issues/0166-anonymous-functions.php b/tests/fixtures/Formatter/in/issues/0166-anonymous-functions.php
new file mode 100644
index 00000000..1dd16af3
--- /dev/null
+++ b/tests/fixtures/Formatter/in/issues/0166-anonymous-functions.php
@@ -0,0 +1,64 @@
+
+ function ($bar,
+ $baz) /* */ use (&$foo,
+ $qux) {},
+];
+
+function ($foo,
+ $bar,
+ $baz) use ($a,
+ $b,
+ $c) {
+ quux();
+};
+
+$foo = function ($bar //
+) use ($baz,
+ $qux) {
+ quux();
+};
+
+$foo = function ($bar
+/* */) use ($baz,
+ $qux) {
+ quux();
+};
+
+$foo = function ($bar,
+ $baz) //
+ use ($baz,
+ $qux) {
+ quux();
+ };
+
+$foo = [
+ 10 =>
+ function ($bar,
+ $baz) /* */ use (&$foo,
+ $qux) {
+ quux();
+ },
+];
diff --git a/tests/fixtures/Formatter/out/01-default/issues/0166-anonymous-functions.php b/tests/fixtures/Formatter/out/01-default/issues/0166-anonymous-functions.php
new file mode 100644
index 00000000..9948b5ab
--- /dev/null
+++ b/tests/fixtures/Formatter/out/01-default/issues/0166-anonymous-functions.php
@@ -0,0 +1,64 @@
+
+ function ($bar,
+ $baz) /* */ use (&$foo,
+ $qux) {},
+];
+
+function ($foo,
+ $bar,
+ $baz) use ($a,
+ $b,
+ $c) {
+ quux();
+};
+
+$foo = function ($bar //
+) use ($baz,
+ $qux) {
+ quux();
+};
+
+$foo = function ($bar
+/* */) use ($baz,
+ $qux) {
+ quux();
+};
+
+$foo = function ($bar,
+ $baz) //
+ use ($baz,
+ $qux) {
+ quux();
+ };
+
+$foo = [
+ 10 =>
+ function ($bar,
+ $baz) /* */ use (&$foo,
+ $qux) {
+ quux();
+ },
+];
diff --git a/tests/fixtures/Formatter/out/02-aligned/issues/0166-anonymous-functions.php b/tests/fixtures/Formatter/out/02-aligned/issues/0166-anonymous-functions.php
new file mode 100644
index 00000000..269545de
--- /dev/null
+++ b/tests/fixtures/Formatter/out/02-aligned/issues/0166-anonymous-functions.php
@@ -0,0 +1,64 @@
+
+ function ($bar,
+ $baz) /* */ use (&$foo,
+ $qux) {},
+];
+
+function ($foo,
+ $bar,
+ $baz) use ($a,
+ $b,
+ $c) {
+ quux();
+};
+
+$foo = function ($bar //
+ ) use ($baz,
+ $qux) {
+ quux();
+};
+
+$foo = function ($bar
+ /* */) use ($baz,
+ $qux) {
+ quux();
+};
+
+$foo = function ($bar,
+ $baz) //
+ use ($baz,
+ $qux) {
+ quux();
+ };
+
+$foo = [
+ 10 =>
+ function ($bar,
+ $baz) /* */ use (&$foo,
+ $qux) {
+ quux();
+ },
+];
diff --git a/tests/fixtures/Formatter/out/03-tab/issues/0166-anonymous-functions.php b/tests/fixtures/Formatter/out/03-tab/issues/0166-anonymous-functions.php
new file mode 100644
index 00000000..460dc669
--- /dev/null
+++ b/tests/fixtures/Formatter/out/03-tab/issues/0166-anonymous-functions.php
@@ -0,0 +1,64 @@
+
+ function ($bar,
+ $baz) /* */ use (&$foo,
+ $qux) {},
+];
+
+function ($foo,
+ $bar,
+ $baz) use ($a,
+ $b,
+ $c) {
+ quux();
+};
+
+$foo = function ($bar //
+) use ($baz,
+ $qux) {
+ quux();
+};
+
+$foo = function ($bar
+/* */) use ($baz,
+ $qux) {
+ quux();
+};
+
+$foo = function ($bar,
+ $baz) //
+ use ($baz,
+ $qux) {
+ quux();
+ };
+
+$foo = [
+ 10 =>
+ function ($bar,
+ $baz) /* */ use (&$foo,
+ $qux) {
+ quux();
+ },
+];
diff --git a/tests/fixtures/Formatter/out/04-psr12/issues/0166-anonymous-functions.php b/tests/fixtures/Formatter/out/04-psr12/issues/0166-anonymous-functions.php
new file mode 100644
index 00000000..e584ee21
--- /dev/null
+++ b/tests/fixtures/Formatter/out/04-psr12/issues/0166-anonymous-functions.php
@@ -0,0 +1,96 @@
+
+ function (
+ $bar,
+ $baz
+ ) /* */ use (
+ &$foo,
+ $qux
+ ) {},
+];
+
+function (
+ $foo,
+ $bar,
+ $baz
+) use (
+ $a,
+ $b,
+ $c
+) {
+ quux();
+};
+
+$foo = function ($bar //
+) use (
+ $baz,
+ $qux
+) {
+ quux();
+};
+
+$foo = function ($bar
+/* */) use (
+ $baz,
+ $qux
+) {
+ quux();
+};
+
+$foo = function (
+ $bar,
+ $baz
+) //
+ use (
+ $baz,
+ $qux
+ ) {
+ quux();
+ };
+
+$foo = [
+ 10 =>
+ function (
+ $bar,
+ $baz
+ ) /* */ use (
+ &$foo,
+ $qux
+ ) {
+ quux();
+ },
+];
diff --git a/tests/unit/Rule/VerticalSpacingTest.php b/tests/unit/Rule/VerticalSpacingTest.php
index 2b2179ab..9487279d 100644
--- a/tests/unit/Rule/VerticalSpacingTest.php
+++ b/tests/unit/Rule/VerticalSpacingTest.php
@@ -264,6 +264,38 @@ function getArray()
->disable([MoveComments::class])
->build(),
],
+ 'idempotent brackets' => [
+ <<<'PHP'
+