Skip to content

Commit

Permalink
Merge pull request #117 from goccy/feature/fix-print-error-token
Browse files Browse the repository at this point in the history
Fix output format for PrintErrorToken
  • Loading branch information
goccy authored Jun 7, 2020
2 parents 2ff6c9a + 997278a commit 296b97b
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 62 deletions.
9 changes: 4 additions & 5 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1487,10 +1487,9 @@ a: c
`
expected := `
[3:1] duplicate key "a"
2 |
> 3 | a: b
4 | a: c
^
2 | a: b
> 3 | a: c
^
`
var v map[string]string
err := yaml.NewDecoder(strings.NewReader(yml), yaml.DisallowDuplicateKey()).Decode(&v)
Expand Down Expand Up @@ -1587,7 +1586,7 @@ complecated: string
// 1 | ---
// 2 | simple: string
// > 3 | complecated: string
// ^
// ^
}

type unmarshalableStringValue string
Expand Down
152 changes: 103 additions & 49 deletions printer/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,80 +231,134 @@ func (p *Printer) newLineCount(s string) int {
return cnt
}

func (p *Printer) isNewLineChar(c byte) bool {
if c == '\n' {
return true
}
if c == '\r' {
return true
func (p *Printer) isNewLineLastChar(s string) bool {
for i := len(s) - 1; i > 0; i-- {
c := s[i]
switch c {
case ' ':
continue
case '\n', '\r':
return true
}
break
}
return false
}

func (p *Printer) PrintErrorToken(tk *token.Token, isColored bool) string {
errToken := tk
pos := tk.Position
curLine := pos.Line
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))
maxLine := curExtLine + 3
func (p *Printer) printBeforeTokens(tk *token.Token, minLine, extLine int) token.Tokens {
for {
if tk.Position.Line < minLine {
if tk.Prev == nil {
break
}
if tk.Prev == nil {
if tk.Prev.Position.Line < minLine {
break
}
tk = tk.Prev
}
tokens := token.Tokens{}
lastTk := tk
for tk.Position.Line <= curExtLine {
minTk := tk
if minTk.Prev != nil {
// add white spaces to minTk by prev token
prev := minTk.Prev
whiteSpaceLen := len(prev.Origin) - len(strings.TrimRight(prev.Origin, " "))
minTk.Origin = strings.Repeat(" ", whiteSpaceLen) + minTk.Origin
}
minTk.Origin = p.removeLeftSideNewLineChar(minTk.Origin)
tokens := token.Tokens{minTk}
tk = minTk.Next
for tk != nil && tk.Position.Line <= extLine {
tokens.Add(tk)
lastTk = tk
tk = tk.Next
if tk == nil {
break
}
lastTk := tokens[len(tokens)-1]
trimmedOrigin := p.removeRightSideWhiteSpaceChar(lastTk.Origin)
suffix := lastTk.Origin[len(trimmedOrigin):]
lastTk.Origin = trimmedOrigin

if lastTk.Next != nil && len(suffix) > 1 {
// add suffix to header of next token
if suffix[0] == '\n' || suffix[0] == '\r' {
suffix = suffix[1:]
}
lastTk.Next.Origin = suffix + lastTk.Next.Origin
}
return tokens
}

func (p *Printer) addNewLineCharIfDocumentHeader(tk *token.Token) {
if tk.Prev == nil {
return
}
if tk.Type != token.DocumentHeaderType {
return
}
prev := tk.Prev
lineDiff := tk.Position.Line - prev.Position.Line
if p.isNewLineLastChar(prev.Origin) {
lineDiff--
}
tk.Origin = strings.Repeat("\n", lineDiff) + tk.Origin
}

func (p *Printer) printAfterTokens(tk *token.Token, maxLine int) token.Tokens {
tokens := token.Tokens{}
if tk == nil {
return tokens
}
if tk.Position.Line > maxLine {
return tokens
}
org := lastTk.Origin
trimmed := p.removeRightSideWhiteSpaceChar(org)
lastTk.Origin = trimmed
if tk != nil {
tk.Origin = p.removeLeftSideNewLineChar(tk.Origin)
minTk := tk
minTk.Origin = p.removeLeftSideNewLineChar(minTk.Origin)
tokens.Add(minTk)
tk = minTk.Next
for tk != nil && tk.Position.Line <= maxLine {
p.addNewLineCharIfDocumentHeader(tk)
tokens.Add(tk)
tk = tk.Next
}
return tokens
}

func (p *Printer) setupErrorTokenFormat(annotateLine int, isColored bool) {
prefix := func(annotateLine, num int) string {
if annotateLine == num {
return fmt.Sprintf("> %2d | ", num)
}
return fmt.Sprintf(" %2d | ", num)
}
p.LineNumber = true
p.LineNumberFormat = func(num int) string {
if isColored {
fn := color.New(color.Bold, color.FgHiWhite).SprintFunc()
if curLine == num {
return fn(fmt.Sprintf("> %2d | ", num))
}
return fn(fmt.Sprintf(" %2d | ", num))
}
if curLine == num {
return fmt.Sprintf("> %2d | ", num)
return fn(prefix(annotateLine, num))
}
return fmt.Sprintf(" %2d | ", num)
return prefix(annotateLine, num)
}
if isColored {
p.setDefaultColorSet()
}
beforeSource := p.PrintTokens(tokens)
prefixSpaceNum := len(fmt.Sprintf(" %2d | ", 1))
annotateLine := strings.Repeat(" ", prefixSpaceNum+errToken.Position.Column-2) + "^"
tokens = token.Tokens{}
for tk != nil {
if tk.Position.Line > maxLine {
break
}
tokens.Add(tk)
tk = tk.Next
}

func (p *Printer) PrintErrorToken(tk *token.Token, isColored bool) string {
errToken := tk
curLine := tk.Position.Line
curExtLine := curLine + p.newLineCount(p.removeLeftSideNewLineChar(tk.Origin))
if p.isNewLineLastChar(tk.Origin) {
// if last character ( exclude white space ) is new line character, ignore it.
curExtLine--
}
afterSource := p.PrintTokens(tokens)

minLine := int(math.Max(float64(curLine-3), 1))
maxLine := curExtLine + 3
p.setupErrorTokenFormat(curLine, isColored)

beforeTokens := p.printBeforeTokens(tk, minLine, curExtLine)
lastTk := beforeTokens[len(beforeTokens)-1]
afterTokens := p.printAfterTokens(lastTk.Next, maxLine)

beforeSource := p.PrintTokens(beforeTokens)
prefixSpaceNum := len(fmt.Sprintf(" %2d | ", curLine))
annotateLine := strings.Repeat(" ", prefixSpaceNum+errToken.Position.Column-1) + "^"
afterSource := p.PrintTokens(afterTokens)
return fmt.Sprintf("%s\n%s\n%s", beforeSource, annotateLine, afterSource)
}
38 changes: 32 additions & 6 deletions printer/printer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ alias: *x
expect := `
1 | ---
> 2 | text: aaaa
^
^
3 | text2: aaaa
4 | bbbb
5 | cccc
Expand All @@ -55,7 +55,7 @@ alias: *x
5 | cccc
6 | dddd
7 | eeee
^
^
`
if actual != expect {
t.Fatalf("unexpected output: expect:[%s]\n actual:[%s]", expect, actual)
Expand All @@ -73,7 +73,7 @@ alias: *x
5 | cccc
6 | dddd
7 | eeee
^
^
8 | text3: ffff
9 | gggg
10 | hhhh
Expand All @@ -84,21 +84,47 @@ alias: *x
t.Fatalf("unexpected output: expect:[%s]\n actual:[%s]", expect, actual)
}
})
t.Run("print error token with document header", func(t *testing.T) {
tokens := lexer.Tokenize(`---
a:
b:
c:
d: e
f: g
h: i
---
`)
expect := `
3 | b:
4 | c:
5 | d: e
> 6 | f: g
^
7 | h: i
8 |
9 | ---`
var p printer.Printer
actual := "\n" + p.PrintErrorToken(tokens[12], false)
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.Logf("\n%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.Logf("\n%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.Logf("\n%s", p.PrintErrorToken(tokens[12], true))
})
})
t.Run("print error message", func(t *testing.T) {
Expand Down
3 changes: 1 addition & 2 deletions validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,11 @@ func ExampleStructValidator() {
fmt.Printf("%v", err)
// OUTPUT:
// [5:8] Key: 'Person.Age' Error:Field validation for 'Age' failed on the 'gte' tag
// 1 | ---
// 2 | - name: john
// 3 | age: 20
// 4 | - name: tom
// > 5 | age: -1
// ^
// ^
// 6 | - name: ken
// 7 | age: 10
}

0 comments on commit 296b97b

Please sign in to comment.