Repository: goccy/go-yaml (go)

Issue:
Trailing new lines are added to literal strings
Even if they are not part of a literal string, trailing newlines are systematically added to the string

```yaml
foo: |
  bar


# ↑↑↑ Note the two newlines right after the literal string
```

In this siituation, the `bar` value will be `"bar\n\n"` instead of `"bar\n"`.

Note that this also applies with strip sign `|-`

Here is a simple reproduction case:

```golang
package main

import (
	"fmt"
	goYaml "github.com/goccy/go-yaml"
)

func main() {
	yml := `
foo: |-
  bar


`
	var vmap[string]any
	goYaml.Unmarshal([]byte(yml), &v)

	fmt.Println(v["foo"])
	// -> "bar\n\n"
}
```

And a related playground: https://go.dev/play/p/XG6oCnbwwiz

 - Go version: 1.21.2
 - go-yaml's Version: v1.11.2


Pull request:
Fix trailing new line chars handling
fix #406


Patch:
diff --git a/scanner/context.go b/scanner/context.go
index 415cf07..5263f3e 100644
--- a/scanner/context.go
+++ b/scanner/context.go
@@ -273,27 +273,39 @@ func (c *Context) existsBuffer() bool {
 
 func (c *Context) bufferedSrc() []rune {
 	src := c.buf[:c.notSpaceCharPos]
-	if c.isDocument() && (strings.HasPrefix(c.docOpt, "-") || strings.HasSuffix(c.docOpt, "-")) {
-		// remove end '\n' character and trailing empty lines
+	if c.isDocument() {
+		// remove end '\n' character and trailing empty lines.
 		// https://yaml.org/spec/1.2.2/#8112-block-chomping-indicator
-		for {
-			if len(src) > 0 && src[len(src)-1] == '\n' {
-				src = src[:len(src)-1]
-				continue
+		if c.hasTrimAllEndNewlineOpt() {
+			// If the '-' flag is specified, all trailing newline characters will be removed.
+			src = []rune(strings.TrimRight(string(src), "\n"))
+		} else {
+			// Normally, all but one of the trailing newline characters are removed.
+			var newLineCharCount int
+			for i := len(src) - 1; i >= 0; i-- {
+				if src[i] == '\n' {
+					newLineCharCount++
+					continue
+				}
+				break
 			}
-			break
-		}
-		for {
-			if len(src) > 0 && src[len(src)-1] == ' ' {
-				src = src[:len(src)-1]
-				continue
+			removedNewLineCharCount := newLineCharCount - 1
+			for removedNewLineCharCount > 0 {
+				src = []rune(strings.TrimSuffix(string(src), "\n"))
+				removedNewLineCharCount--
 			}
-			break
 		}
+
+		// If the text ends with a space character, remove all of them.
+		src = []rune(strings.TrimRight(string(src), " "))
 	}
 	return src
 }
 
+func (c *Context) hasTrimAllEndNewlineOpt() bool {
+	return strings.HasPrefix(c.docOpt, "-") || strings.HasSuffix(c.docOpt, "-")
+}
+
 func (c *Context) bufferedToken(pos *token.Position) *token.Token {
 	if c.idx == 0 {
 		return nil


Test patch:
diff --git a/decode_test.go b/decode_test.go
index 718ca2e..cac2b81 100644
--- a/decode_test.go
+++ b/decode_test.go
@@ -2763,18 +2763,12 @@ func TestDecoder_LiteralWithNewLine(t *testing.T) {
 		{
 			Node: "hello\nworld\n",
 		},
-		{
-			Node: "hello\nworld\n\n",
-		},
 		{
 			LastNode: "hello\nworld",
 		},
 		{
 			LastNode: "hello\nworld\n",
 		},
-		{
-			LastNode: "hello\nworld\n\n",
-		},
 	}
 	// struct(want) -> Marshal -> Unmarchal -> struct(got)
 	for _, want := range tests {
diff --git a/lexer/lexer_test.go b/lexer/lexer_test.go
index 81b8be9..810d2d2 100644
--- a/lexer/lexer_test.go
+++ b/lexer/lexer_test.go
@@ -2172,6 +2172,31 @@ s: >-3
 				},
 			},
 		},
+		{
+			YAML: `
+|
+  a
+
+
+
+`,
+			Tokens: token.Tokens{
+				{
+					Type:          token.LiteralType,
+					CharacterType: token.CharacterTypeIndicator,
+					Indicator:     token.BlockScalarIndicator,
+					Value:         "|",
+					Origin:        "\n|\n",
+				},
+				{
+					Type:          token.StringType,
+					CharacterType: token.CharacterTypeMiscellaneous,
+					Indicator:     token.NotIndicator,
+					Value:         "a\n",
+					Origin:        "  a\n\n\n\n",
+				},
+			},
+		},
 	}
 	for _, test := range tests {
 		t.Run(test.YAML, func(t *testing.T) {