diff --git a/printer/printer.go b/printer/printer.go index cced4979..f3e63330 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/printer/printer_test.go b/printer/printer_test.go new file mode 100644 index 00000000..dc8d4bdb --- /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) + }) +} diff --git a/scanner/scanner.go b/scanner/scanner.go index 33d47e62..b8b6a276 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) }