diff --git a/blink/internal/fixer/rules/array_notation.go b/blink/internal/fixer/rules/array_notation.go index 3c78f5edc0..fcf22786bc 100644 --- a/blink/internal/fixer/rules/array_notation.go +++ b/blink/internal/fixer/rules/array_notation.go @@ -250,6 +250,17 @@ func topBracketIsArray(stack []string) bool { return len(stack) > 0 && stack[len(stack)-1] == "[" } +// isDestructuringAssignOpen reports whether the "[" at open is a short-list +// destructuring target - its matching "]" is directly followed by a single "=". +func isDestructuringAssignOpen(s *tokens.Stream, open int) bool { + c := s.MatchForward(open) + if c < 0 { + return false + } + n := nextSignificantIndex(s, c) + return n >= 0 && s.At(n).Kind == token.Punct && s.At(n).Value == "=" +} + // PHP-CS-Fixer: https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/src/Fixer/ArrayNotation/NoWhitespaceBeforeCommaInArrayFixer.php // // NoWhitespaceBeforeCommaInArray removes single-line whitespace before a comma @@ -340,7 +351,15 @@ func (f WhitespaceAfterCommaInArray) Fix(s *tokens.Stream) bool { continue } switch t.Value { - case "(", "[", "{": + case "[": + // a destructuring target ("[$a,$b] = ...", or nested in one) is not an + // array literal, so php-cs-fixer does not space its commas + if (len(stack) > 0 && stack[len(stack)-1] == "d[") || isDestructuringAssignOpen(s, i) { + stack = append(stack, "d[") + } else { + stack = append(stack, "[") + } + case "(", "{": stack = append(stack, t.Value) case ")", "]", "}": if len(stack) > 0 { diff --git a/blink/internal/fixer/rules/constructs.go b/blink/internal/fixer/rules/constructs.go index d82d08f5d1..dde73cf984 100644 --- a/blink/internal/fixer/rules/constructs.go +++ b/blink/internal/fixer/rules/constructs.go @@ -261,7 +261,8 @@ func (f NoSpacesInsideParenthesis) Fix(s *tokens.Stream) bool { for i := 0; i < s.Len(); i++ { t := s.At(i) if t.Kind == token.Punct && t.Value == "(" && - i+1 < s.Len() && s.At(i+1).Kind == token.Whitespace && !hasNewline(s.At(i+1).Value) { + i+1 < s.Len() && s.At(i+1).Kind == token.Whitespace && !hasNewline(s.At(i+1).Value) && + !commentAt(s, i+2) { // keep the space before a trailing comment s.RemoveAt(i + 1) changed = true } diff --git a/blink/internal/fixer/rules/gen_arrays.go b/blink/internal/fixer/rules/gen_arrays.go index 02d79e89b7..94a007df8e 100644 --- a/blink/internal/fixer/rules/gen_arrays.go +++ b/blink/internal/fixer/rules/gen_arrays.go @@ -145,6 +145,11 @@ func isComment(t token.Token) bool { return t.Kind == token.Comment || t.Kind == token.DocComment } +// commentAt reports whether index i is in range and holds a comment token. +func commentAt(s *tokens.Stream, i int) bool { + return i >= 0 && i < s.Len() && isComment(s.At(i)) +} + func isLineComment(t token.Token) bool { if t.Kind != token.Comment { return false diff --git a/blink/internal/fixer/rules/no_singleline_whitespace_before_semicolons.go b/blink/internal/fixer/rules/no_singleline_whitespace_before_semicolons.go index 48621407af..3558be75d8 100644 --- a/blink/internal/fixer/rules/no_singleline_whitespace_before_semicolons.go +++ b/blink/internal/fixer/rules/no_singleline_whitespace_before_semicolons.go @@ -30,6 +30,11 @@ func (NoSinglelineWhitespaceBeforeSemicolons) Fix(s *tokens.Stream) bool { } prev := s.At(i - 1) if prev.Kind == token.Whitespace && !strings.ContainsAny(prev.Value, "\n\r") { + // keep the space before a ";" that follows another ";" - the empty + // condition of a for() header ("for ($i = 0; ; ++$i)") + if p, ok := prevSignificant(s, i-1); ok && p.Kind == token.Punct && p.Value == ";" { + continue + } s.RemoveAt(i - 1) changed = true } diff --git a/blink/internal/fixer/rules/rules_test.go b/blink/internal/fixer/rules/rules_test.go index e48af016b6..465d1eab0f 100644 --- a/blink/internal/fixer/rules/rules_test.go +++ b/blink/internal/fixer/rules/rules_test.go @@ -33,9 +33,10 @@ func TestSpaceAfterSemicolon(t *testing.T) { if want := " "for (; ; )"); only ")" suppresses it + if next.Kind == token.Punct && next.Value == ")" { continue } s.InsertAt(i+1, token.Token{Kind: token.Whitespace, Value: " "}) diff --git a/blink/internal/fixer/rules/token_batch.go b/blink/internal/fixer/rules/token_batch.go index f4ceb433be..910c37bdc8 100644 --- a/blink/internal/fixer/rules/token_batch.go +++ b/blink/internal/fixer/rules/token_batch.go @@ -118,11 +118,14 @@ func (TrimArraySpaces) Fix(s *tokens.Stream) bool { if closeIdx < 0 { continue } - if closeIdx-1 > i && s.At(closeIdx-1).Kind == token.Whitespace && !hasNewline(s.At(closeIdx-1).Value) { + // a space kept next to a comment is left alone ("[ // note" / "/* note */ ]") + if closeIdx-1 > i && s.At(closeIdx-1).Kind == token.Whitespace && !hasNewline(s.At(closeIdx-1).Value) && + !commentAt(s, closeIdx-2) { s.RemoveAt(closeIdx - 1) changed = true } - if i+1 < s.Len() && s.At(i+1).Kind == token.Whitespace && !hasNewline(s.At(i+1).Value) { + if i+1 < s.Len() && s.At(i+1).Kind == token.Whitespace && !hasNewline(s.At(i+1).Value) && + !commentAt(s, i+2) { s.RemoveAt(i + 1) changed = true } diff --git a/blink/internal/lexer/lexer.go b/blink/internal/lexer/lexer.go index c294b26545..bcc294fa18 100644 --- a/blink/internal/lexer/lexer.go +++ b/blink/internal/lexer/lexer.go @@ -137,7 +137,8 @@ func (l *lexer) lexPHP() { l.lexString(start, '\'') case c == '"': l.lexString(start, '"') - case isDigit(c): + case isDigit(c) || (c == '.' && l.pos+1 < len(l.src) && isDigit(l.src[l.pos+1])): + // a leading-dot float (".1") is one DNUMBER in PHP's context-free scanner for l.pos < len(l.src) && isNumber(l.src[l.pos]) { l.pos++ }