Repository: goccy/go-yaml
Pull request #67 (2019-12-22 08:20:53)

Fix printing logic for token
resolve https://github.com/goccy/go-yaml/issues/64



Patch:
diff --git a/printer/printer.go b/printer/printer.go
index cced497..f3e6333 100644
--- a/printer/printer.go
+++ b/printer/printer.go
@@ -35,19 +35,6 @@ func defaultLineNumberFormat(num int) string {
 	return fmt.Sprintf("%2d | ", num)
 }
 
-func (p *Printer) lineTexts(tk *token.Token, prop *Property) []string {
-	texts := []string{}
-	for idx, src := range strings.Split(tk.Origin, "\n") {
-		header := ""
-		if p.LineNumber {
-			header = p.LineNumberFormat(tk.Position.Line + idx)
-		}
-		lineText := prop.Prefix + src + prop.Suffix
-		texts = append(texts, fmt.Sprintf("%s%s", header, lineText))
-	}
-	return texts
-}
-
 func (p *Printer) property(tk *token.Token) *Property {
 	prop := &Property{}
 	switch tk.PreviousType() {
@@ -213,13 +200,54 @@ func (p *Printer) PrintErrorMessage(msg string, isColored bool) string {
 	return msg
 }
 
+func (p *Printer) removeLeftSideNewLineChar(src string) string {
+	return strings.TrimLeft(strings.TrimLeft(src, "\r"), "\n")
+}
+
+func (p *Printer) removeRightSideNewLineChar(src string) string {
+	return strings.TrimRight(strings.TrimRight(src, "\r"), "\n")
+}
+
+func (p *Printer) removeRightSideWhiteSpaceChar(src string) string {
+	return p.removeRightSideNewLineChar(strings.TrimRight(src, " "))
+}
+
+func (p *Printer) newLineCount(s string) int {
+	src := []rune(s)
+	size := len(src)
+	cnt := 0
+	for i := 0; i < size; i++ {
+		c := src[i]
+		switch c {
+		case '\r':
+			if i+1 < size && src[i+1] == '\n' {
+				i++
+			}
+			cnt++
+		case '\n':
+			cnt++
+		}
+	}
+	return cnt
+}
+
+func (p *Printer) isNewLineChar(c byte) bool {
+	if c == '\n' {
+		return true
+	}
+	if c == '\r' {
+		return true
+	}
+	return false
+}
+
 func (p *Printer) PrintErrorToken(tk *token.Token, isColored bool) string {
 	errToken := tk
 	pos := tk.Position
 	curLine := pos.Line
-	curExtLine := curLine + len(strings.Split(strings.TrimLeft(tk.Origin, "\n"), "\n")) - 1
-	if tk.Origin[len(tk.Origin)-1] == '\n' {
-		// if last character is '\n', ignore it.
+	curExtLine := curLine + p.newLineCount(p.removeLeftSideNewLineChar(tk.Origin))
+	if p.isNewLineChar(tk.Origin[len(tk.Origin)-1]) {
+		// if last character is new line character, ignore it.
 		curExtLine--
 	}
 	minLine := int(math.Max(float64(curLine-3), 1))
@@ -244,10 +272,10 @@ func (p *Printer) PrintErrorToken(tk *token.Token, isColored bool) string {
 		}
 	}
 	org := lastTk.Origin
-	trimmed := strings.TrimRight(strings.TrimRight(lastTk.Origin, " "), "\n")
+	trimmed := p.removeRightSideWhiteSpaceChar(org)
 	lastTk.Origin = trimmed
 	if tk != nil {
-		tk.Origin = org[len(trimmed)+1:] + tk.Origin
+		tk.Origin = p.removeLeftSideNewLineChar(tk.Origin)
 	}
 	p.LineNumber = true
 	p.LineNumberFormat = func(num int) string {
diff --git a/scanner/scanner.go b/scanner/scanner.go
index 33d47e6..b8b6a27 100644
--- a/scanner/scanner.go
+++ b/scanner/scanner.go
@@ -111,6 +111,24 @@ func (s *Scanner) isNewLineChar(c rune) bool {
 	return false
 }
 
+func (s *Scanner) newLineCount(src []rune) int {
+	size := len(src)
+	cnt := 0
+	for i := 0; i < size; i++ {
+		c := src[i]
+		switch c {
+		case '\r':
+			if i+1 < size && src[i+1] == '\n' {
+				i++
+			}
+			cnt++
+		case '\n':
+			cnt++
+		}
+	}
+	return cnt
+}
+
 func (s *Scanner) updateIndent(ctx *Context, c rune) {
 	if s.isFirstCharAtLine && s.isNewLineChar(c) && ctx.isDocument() {
 		return
@@ -348,7 +366,7 @@ func (s *Scanner) scan(ctx *Context) (pos int) {
 			s.addBufferedTokenIfExists(ctx)
 		} else if s.isChangedToIndentStateEqual() {
 			// if first character is new line character, buffer expect to raw folded literal
-			if len(ctx.obuf) > 0 && !s.isNewLineChar(ctx.obuf[0]) {
+			if len(ctx.obuf) > 0 && s.newLineCount(ctx.obuf) <= 1 {
 				// doesn't raw folded literal
 				s.addBufferedTokenIfExists(ctx)
 			}


Test patch:
diff --git a/printer/printer_test.go b/printer/printer_test.go
new file mode 100644
index 0000000..dc8d4bd
--- /dev/null
+++ b/printer/printer_test.go
@@ -0,0 +1,113 @@
+package printer_test
+
+import (
+	"testing"
+
+	"github.com/goccy/go-yaml/lexer"
+	"github.com/goccy/go-yaml/printer"
+)
+
+func Test_Printer(t *testing.T) {
+	yml := `---
+text: aaaa
+text2: aaaa
+ bbbb
+ cccc
+ dddd
+ eeee
+text3: ffff
+ gggg
+ hhhh
+ iiii
+ jjjj
+bool: true
+number: 10
+anchor: &x 1
+alias: *x
+`
+	t.Run("print starting from tokens[3]", func(t *testing.T) {
+		tokens := lexer.Tokenize(yml)
+		var p printer.Printer
+		actual := "\n" + p.PrintErrorToken(tokens[3], false)
+		expect := `
+   1 | ---
+>  2 | text: aaaa
+            ^
+   3 | text2: aaaa
+   4 |  bbbb
+   5 |  cccc
+   6 |  dddd
+   7 |  eeee
+   8 | `
+		if actual != expect {
+			t.Fatalf("unexpected output: expect:[%s]\n actual:[%s]", expect, actual)
+		}
+	})
+	t.Run("print stargin from tokens[4]", func(t *testing.T) {
+		tokens := lexer.Tokenize(yml)
+		var p printer.Printer
+		actual := "\n" + p.PrintErrorToken(tokens[4], false)
+		expect := `
+   1 | ---
+   2 | text: aaaa
+>  3 | text2: aaaa
+   4 |  bbbb
+   5 |  cccc
+   6 |  dddd
+   7 |  eeee
+      ^
+`
+		if actual != expect {
+			t.Fatalf("unexpected output: expect:[%s]\n actual:[%s]", expect, actual)
+		}
+	})
+	t.Run("print starting from tokens[6]", func(t *testing.T) {
+		tokens := lexer.Tokenize(yml)
+		var p printer.Printer
+		actual := "\n" + p.PrintErrorToken(tokens[6], false)
+		expect := `
+   1 | ---
+   2 | text: aaaa
+>  3 | text2: aaaa
+   4 |  bbbb
+   5 |  cccc
+   6 |  dddd
+   7 |  eeee
+             ^
+   8 | text3: ffff
+   9 |  gggg
+  10 |  hhhh
+  11 |  iiii
+  12 |  jjjj
+  13 | `
+		if actual != expect {
+			t.Fatalf("unexpected output: expect:[%s]\n actual:[%s]", expect, actual)
+		}
+	})
+	t.Run("output with color", func(t *testing.T) {
+		t.Run("token6", func(t *testing.T) {
+			tokens := lexer.Tokenize(yml)
+			var p printer.Printer
+			t.Logf("%s", p.PrintErrorToken(tokens[6], true))
+		})
+		t.Run("token9", func(t *testing.T) {
+			tokens := lexer.Tokenize(yml)
+			var p printer.Printer
+			t.Logf("%s", p.PrintErrorToken(tokens[9], true))
+		})
+		t.Run("token12", func(t *testing.T) {
+			tokens := lexer.Tokenize(yml)
+			var p printer.Printer
+			t.Logf("%s", p.PrintErrorToken(tokens[12], true))
+		})
+	})
+	t.Run("print error message", func(t *testing.T) {
+		var p printer.Printer
+		src := "message"
+		msg := p.PrintErrorMessage(src, false)
+		if msg != src {
+			t.Fatal("unexpected result")
+		}
+		p.PrintErrorMessage(src, true)
+	})
+}