From 2ba6d3ec7840ea01d5f2d083e5e67c62b0a700ad Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Fri, 30 Oct 2026 17:25:52 +0100 Subject: [PATCH] Space compound assignment operators by default in blink BinaryOperatorSpaces Compound assignment operators (.=, +=, -=, *=, etc.) are always binary and never unary, so PHP-CS-Fixer's BinaryOperatorSpaces spaces them under the default mode like plain =. blink treated them as extra operators that only got spaced when explicitly listed in the operators option, so code like $x .='c' kept its missing space and diverged from ECS. Move the compound assignment family into the default target set; arithmetic operators that can be unary stay guarded. Resolves the .= laravel parity divergences (ServeFileTest, BladeCompiler). --- blink/internal/fixer/rules/binary_operator_spaces.go | 12 +++++++++--- .../rules/binary_operator_spaces_config_test.go | 3 +++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/blink/internal/fixer/rules/binary_operator_spaces.go b/blink/internal/fixer/rules/binary_operator_spaces.go index 0cc122f916..f0da086189 100644 --- a/blink/internal/fixer/rules/binary_operator_spaces.go +++ b/blink/internal/fixer/rules/binary_operator_spaces.go @@ -19,6 +19,13 @@ var binaryOperators = map[string]bool{ "&&": true, "||": true, "??": true, "=>": true, } +// compoundAssignmentOperators are always binary (never unary), so they follow the +// default spacing like "=" without needing an explicit operators override. +var compoundAssignmentOperators = map[string]bool{ + "+=": true, "-=": true, "*=": true, "/=": true, ".=": true, "%=": true, + "**=": true, "??=": true, "&=": true, "|=": true, "^=": true, "<<=": true, ">>=": true, +} + // BinaryOperatorSpaces normalizes spacing around binary operators (assignment // "=", arrow "=>", comparison and logical operators). The zero value applies a // single space to the default operator set. Whitespace spanning a newline is left @@ -203,7 +210,7 @@ func binaryOperatorSpacesDefaultTarget(s *tokens.Stream, i int) bool { // keeps the variable); only declare(...) headers are left alone return !insideDeclareArgs(s, i) } - return binaryOperators[t.Value] + return binaryOperators[t.Value] || compoundAssignmentOperators[t.Value] } // binaryOperatorSpacesExtraTarget covers operators outside the default set that @@ -211,8 +218,7 @@ func binaryOperatorSpacesDefaultTarget(s *tokens.Stream, i int) bool { func binaryOperatorSpacesExtraTarget(s *tokens.Stream, i int) bool { t := s.At(i) switch t.Value { - case "+=", "-=", "*=", "/=", ".=", "%=", "**=", "??=", "&=", "|=", "^=", "<<=", ">>=", - "**", "/", "%", "^", "<<", ">>": + case "**", "/", "%", "^", "<<", ">>": return true case "+", "-", "*": prev, ok := prevSignificant(s, i) diff --git a/blink/internal/fixer/rules/binary_operator_spaces_config_test.go b/blink/internal/fixer/rules/binary_operator_spaces_config_test.go index 6f2229b321..f2017c45a8 100644 --- a/blink/internal/fixer/rules/binary_operator_spaces_config_test.go +++ b/blink/internal/fixer/rules/binary_operator_spaces_config_test.go @@ -16,6 +16,9 @@ func TestBinaryOperatorSpacesConfigCases(t *testing.T) { {"align unsupported is a no-op", BinaryOperatorSpaces{}.WithConfig(map[string]any{"default": "align"}).(fixerRule), "2];", "2];"}, {"extra operator via map", BinaryOperatorSpaces{}.WithConfig(map[string]any{"operators": map[string]any{"+=": "single_space"}}).(fixerRule), " 1,\n 'bb' => 2,\n])]\nclass C {}", " 1,\n 'bb' => 2,\n])]\nclass C {}"}, {"arrow inside attribute string left alone", BinaryOperatorSpaces{}, " b')]\nclass C {}", " b')]\nclass C {}"}, {"reference assignment is spaced around =", BinaryOperatorSpaces{}, "