Repository: google/yamlfmt
Pull request #188 (2024-06-24 15:08:58)

Add trim_trailing_whitespace option
This PR adds a feature to trim all trailing whitespace from every line of yaml content.


Patch:
diff --git a/docs/config-file.md b/docs/config-file.md
index 26b12aa..54b6d19 100644
--- a/docs/config-file.md
+++ b/docs/config-file.md
@@ -82,6 +82,7 @@ The basic formatter is a barebones formatter that simply takes the data provided
 | `indentless_arrays`         | bool           | false   | Render `-` array items (block sequence items) without an increased indent. |
 | `drop_merge_tag`            | bool           | false   | Assume that any well formed merge using just a `<<` token will be a merge, and drop the `!!merge` tag from the formatted result. |
 | `pad_line_comments`         | int            | 1       | The number of padding spaces to insert before line comments. |
+| `trim_trailing_whitespace`  | bool           | false   | Whether to trim trailing whitespace from lines. |
 
 ### Note on `max_line_length`
 
diff --git a/formatters/basic/config.go b/formatters/basic/config.go
index 6aea472..a9e4959 100644
--- a/formatters/basic/config.go
+++ b/formatters/basic/config.go
@@ -32,6 +32,7 @@ type Config struct {
 	IndentlessArrays       bool                   `mapstructure:"indentless_arrays"`
 	DropMergeTag           bool                   `mapstructure:"drop_merge_tag"`
 	PadLineComments        int                    `mapstructure:"pad_line_comments"`
+	TrimTrailingWhitespace bool                   `mapstructure:"trim_trailing_whitespace"`
 }
 
 func DefaultConfig() *Config {
diff --git a/formatters/basic/features.go b/formatters/basic/features.go
index 976c2d6..0f0f382 100644
--- a/formatters/basic/features.go
+++ b/formatters/basic/features.go
@@ -19,17 +19,26 @@ import (
 	"github.com/google/yamlfmt"
 	"github.com/google/yamlfmt/formatters/basic/anchors"
 	"github.com/google/yamlfmt/internal/hotfix"
+	"github.com/google/yamlfmt/internal/trim"
 )
 
 func ConfigureFeaturesFromConfig(config *Config) yamlfmt.FeatureList {
+	lineSep, err := config.LineEnding.Separator()
+	if err != nil {
+		lineSep = "\n"
+	}
 	features := []yamlfmt.Feature{}
 	if config.RetainLineBreaks || config.RetainLineBreaksSingle {
-		lineSep, err := config.LineEnding.Separator()
-		if err != nil {
-			lineSep = "\n"
-		}
-		featLineBreak := hotfix.MakeFeatureRetainLineBreak(lineSep, config.RetainLineBreaksSingle)
-		features = append(features, featLineBreak)
+		features = append(
+			features,
+			hotfix.MakeFeatureRetainLineBreak(lineSep, config.RetainLineBreaksSingle),
+		)
+	}
+	if config.TrimTrailingWhitespace {
+		features = append(
+			features,
+			trim.MakeFeatureTrimTrailingWhitespace(lineSep),
+		)
 	}
 	return features
 }
diff --git a/internal/trim/trim_whitespace.go b/internal/trim/trim_whitespace.go
new file mode 100644
index 0000000..1bcbcf9
--- /dev/null
+++ b/internal/trim/trim_whitespace.go
@@ -0,0 +1,42 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package trim
+
+import (
+	"bufio"
+	"bytes"
+	"strings"
+
+	"github.com/google/yamlfmt"
+)
+
+func MakeFeatureTrimTrailingWhitespace(linebreakStr string) yamlfmt.Feature {
+	return yamlfmt.Feature{
+		Name:         "Trim Trailing Whitespace",
+		BeforeAction: trimTrailingWhitespaceFeature(linebreakStr),
+	}
+}
+
+func trimTrailingWhitespaceFeature(linebreakStr string) yamlfmt.FeatureFunc {
+	return func(content []byte) ([]byte, error) {
+		buf := bytes.NewBuffer(content)
+		s := bufio.NewScanner(buf)
+		newLines := []string{}
+		for s.Scan() {
+			newLines = append(newLines, strings.TrimRight(s.Text(), " "))
+		}
+		return []byte(strings.Join(newLines, linebreakStr)), nil
+	}
+}


Test patch:
diff --git a/formatters/basic/formatter_test.go b/formatters/basic/formatter_test.go
index 39d666e..7ae9ba1 100644
--- a/formatters/basic/formatter_test.go
+++ b/formatters/basic/formatter_test.go
@@ -309,3 +309,23 @@ func TestPadLineComments(t *testing.T) {
 		t.Fatalf("expected: '%s', got: '%s'", expectedStr, resultStr)
 	}
 }
+
+func TestTrimTrailingWhitespace(t *testing.T) {
+	config := basic.DefaultConfig()
+	config.TrimTrailingWhitespace = true
+	f := newFormatter(config)
+
+	yml := `a: 1
+b: 2    `
+	expectedYml := `a: 1
+b: 2`
+
+	result, err := f.Format([]byte(yml))
+	if err != nil {
+		t.Fatalf("expected formatting to pass, returned error: %v", err)
+	}
+	resultStr := strings.TrimSuffix(string(result), "\n")
+	if resultStr != expectedYml {
+		t.Fatalf("expected: '%s', got: '%s'", expectedYml, resultStr)
+	}
+}
diff --git a/integrationtest/command/command_test.go b/integrationtest/command/command_test.go
index 836c4d1..2b899e4 100644
--- a/integrationtest/command/command_test.go
+++ b/integrationtest/command/command_test.go
@@ -123,3 +123,11 @@ func TestPrintConfFlagsAndFile(t *testing.T) {
 		Update:  *updateFlag,
 	}.Run(t)
 }
+
+func TestMultilineStringBug(t *testing.T) {
+	TestCase{
+		Dir:     "multiline_string_bug",
+		Command: yamlfmtWithArgs("-formatter trim_trailing_whitespace=true ."),
+		Update:  *updateFlag,
+	}.Run(t)
+}
diff --git a/integrationtest/command/testdata/multiline_string_bug/after/a.yaml b/integrationtest/command/testdata/multiline_string_bug/after/a.yaml
new file mode 100755
index 0000000..e719648
--- /dev/null
+++ b/integrationtest/command/testdata/multiline_string_bug/after/a.yaml
@@ -0,0 +1,4 @@
+# There is a trailing whitespace within the string
+a: |-
+  hello
+  hi
diff --git a/integrationtest/command/testdata/multiline_string_bug/before/a.yaml b/integrationtest/command/testdata/multiline_string_bug/before/a.yaml
new file mode 100644
index 0000000..e719648
--- /dev/null
+++ b/integrationtest/command/testdata/multiline_string_bug/before/a.yaml
@@ -0,0 +1,4 @@
+# There is a trailing whitespace within the string
+a: |-
+  hello
+  hi
diff --git a/integrationtest/command/testdata/multiline_string_bug/stdout/stderr.txt b/integrationtest/command/testdata/multiline_string_bug/stdout/stderr.txt
new file mode 100755
index 0000000..e69de29
diff --git a/integrationtest/command/testdata/multiline_string_bug/stdout/stdout.txt b/integrationtest/command/testdata/multiline_string_bug/stdout/stdout.txt
new file mode 100755
index 0000000..e69de29
diff --git a/integrationtest/command/testdata/print_conf_file/after/.yamlfmt b/integrationtest/command/testdata/print_conf_file/after/.yamlfmt
old mode 100644
new mode 100755
diff --git a/integrationtest/command/testdata/print_conf_file/stdout/stdout.txt b/integrationtest/command/testdata/print_conf_file/stdout/stdout.txt
index 10e2412..d8598e4 100644
--- a/integrationtest/command/testdata/print_conf_file/stdout/stdout.txt
+++ b/integrationtest/command/testdata/print_conf_file/stdout/stdout.txt
@@ -23,4 +23,5 @@ formatter:
     retain_line_breaks: false
     retain_line_breaks_single: true
     scan_folded_as_literal: false
+    trim_trailing_whitespace: false
     type: basic
diff --git a/integrationtest/command/testdata/print_conf_flags/after/.gitkeep b/integrationtest/command/testdata/print_conf_flags/after/.gitkeep
old mode 100644
new mode 100755
diff --git a/integrationtest/command/testdata/print_conf_flags/stdout/stdout.txt b/integrationtest/command/testdata/print_conf_flags/stdout/stdout.txt
index 19fdf6c..fff8566 100644
--- a/integrationtest/command/testdata/print_conf_flags/stdout/stdout.txt
+++ b/integrationtest/command/testdata/print_conf_flags/stdout/stdout.txt
@@ -22,4 +22,5 @@ formatter:
     retain_line_breaks: true
     retain_line_breaks_single: false
     scan_folded_as_literal: false
+    trim_trailing_whitespace: false
     type: basic
diff --git a/integrationtest/command/testdata/print_conf_flags_and_file/after/.yamlfmt b/integrationtest/command/testdata/print_conf_flags_and_file/after/.yamlfmt
old mode 100644
new mode 100755
diff --git a/integrationtest/command/testdata/print_conf_flags_and_file/stdout/stdout.txt b/integrationtest/command/testdata/print_conf_flags_and_file/stdout/stdout.txt
index 777d2ac..ce14d09 100644
--- a/integrationtest/command/testdata/print_conf_flags_and_file/stdout/stdout.txt
+++ b/integrationtest/command/testdata/print_conf_flags_and_file/stdout/stdout.txt
@@ -23,4 +23,5 @@ formatter:
     retain_line_breaks: true
     retain_line_breaks_single: true
     scan_folded_as_literal: false
+    trim_trailing_whitespace: false
     type: basic