From 03a13d6330843ad01eb66047e28700b82834d4ae Mon Sep 17 00:00:00 2001 From: Owen Voke Date: Mon, 9 Nov 2026 11:09:50 +0000 Subject: [PATCH] Blink: keep whitespace-only lines in `NoTrailingWhitespace` PHP-CS-Fixer trims a whitespace token only up to its first line break: the segments after it are pure whitespace, and it only rewrites a segment when trimming leaves something behind. A line holding nothing but spaces is therefore left alone, as is a trailing single-line run unless it ends the file. The Go port stripped every run before a newline, so it emptied those lines and reported files the PHP engine passes. Trim the same span PHP does, including its open-tag exception, and treat `\r\n` and a lone `\r` as line breaks as `\R` does. --- .../fixer/rules/no_trailing_whitespace.go | 78 +++++-------------- blink/internal/fixer/rules/rules_test.go | 17 ++++ 2 files changed, 36 insertions(+), 59 deletions(-) diff --git a/blink/internal/fixer/rules/no_trailing_whitespace.go b/blink/internal/fixer/rules/no_trailing_whitespace.go index 5d0995142a..49c3ba8d6e 100644 --- a/blink/internal/fixer/rules/no_trailing_whitespace.go +++ b/blink/internal/fixer/rules/no_trailing_whitespace.go @@ -1,13 +1,18 @@ package rules import ( + "strings" + "github.com/ecsphp/ecs-src/blink/internal/token" "github.com/ecsphp/ecs-src/blink/internal/tokens" ) // PHP-CS-Fixer: https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php // -// NoTrailingWhitespace removes spaces and tabs at the end of a line. +// NoTrailingWhitespace removes spaces and tabs at the end of a line. Like +// PHP-CS-Fixer, it trims a whitespace token only up to its first line break, +// so a line holding nothing but whitespace is left alone, and the same goes for +// a single-line token unless it is the file's last. type NoTrailingWhitespace struct{} func (NoTrailingWhitespace) Name() string { @@ -26,66 +31,21 @@ func (NoTrailingWhitespace) Fix(s *tokens.Stream) bool { if t.Kind != token.Whitespace { continue } - v := stripTrailingWS(t.Value, i == last) - if v != t.Value { - s.SetValue(i, v) - changed = true + head, rest := t.Value, "" + if nl := strings.IndexAny(t.Value, "\r\n"); nl >= 0 { + head, rest = t.Value[:nl], t.Value[nl:] + } else if i != last { + continue } - } - return changed -} - -// stripTrailingWS drops a run of spaces/tabs that sits immediately before a -// (\r?\n) newline, and - only in the file's final token (isLast) - a run at the -// very end. It returns the input unchanged, with no allocation, when there is -// nothing to strip, matching the previous regexes `[ \t]+(\r?\n)` and `[ \t]+$`. -func stripTrailingWS(val string, isLast bool) string { - for i := 0; i < len(val); { - if val[i] == ' ' || val[i] == '\t' { - start := i - for i < len(val) && (val[i] == ' ' || val[i] == '\t') { - i++ - } - if trailing(val, i, isLast) { - return stripTrailingBuild(val, start, isLast) - } - } else { - i++ + // an open tag that already ends the line leaves this token's head as + // the next line's indentation + if i > 0 && s.At(i-1).Kind == token.OpenTag && strings.HasSuffix(s.At(i-1).Value, "\n") { + continue } - } - return val -} - -// stripTrailingBuild builds the stripped string once a first run to drop is -// found at firstDrop, copying the untouched prefix and continuing the scan. -func stripTrailingBuild(val string, firstDrop int, isLast bool) string { - out := make([]byte, 0, len(val)) - out = append(out, val[:firstDrop]...) - for i := firstDrop; i < len(val); { - if val[i] == ' ' || val[i] == '\t' { - start := i - for i < len(val) && (val[i] == ' ' || val[i] == '\t') { - i++ - } - if !trailing(val, i, isLast) { - out = append(out, val[start:i]...) - } - } else { - out = append(out, val[i]) - i++ + if trimmed := strings.TrimRight(head, " \t"); trimmed != head { + s.SetValue(i, trimmed+rest) + changed = true } } - return string(out) -} - -// trailing reports whether a space/tab run ending at end should be dropped: it -// is directly before a newline, or at the file's end in the final token. -func trailing(val string, end int, isLast bool) bool { - if end < len(val) && val[end] == '\n' { - return true - } - if end+1 < len(val) && val[end] == '\r' && val[end+1] == '\n' { - return true - } - return end == len(val) && isLast + return changed } diff --git a/blink/internal/fixer/rules/rules_test.go b/blink/internal/fixer/rules/rules_test.go index 47f132e5bf..bcf00a0f5c 100644 --- a/blink/internal/fixer/rules/rules_test.go +++ b/blink/internal/fixer/rules/rules_test.go @@ -535,6 +535,23 @@ func TestNoTrailingWhitespace(t *testing.T) { if _, changed := apply(t, NoTrailingWhitespace{}, "