From 1905bd7b6af5c74f1cbc141d9c27a55ef571a2d6 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Sun, 1 Oct 2023 10:48:50 +0100 Subject: [PATCH] Correct error position with tab indentation Previously the ^^^s would be misplaced, e.g.: 2 | k1 = "asd" 3 | k2 = "ok" 4 | k3 = "invalid" ^^^^^^^ --- error.go | 44 ++++++++++++++++++++++++++++++++++++++---- error_test.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 4 deletions(-) diff --git a/error.go b/error.go index 7eead78a..b45a3f45 100644 --- a/error.go +++ b/error.go @@ -114,13 +114,22 @@ func (pe ParseError) ErrorWithPosition() string { msg, pe.Position.Line, col, col+pe.Position.Len) } if pe.Position.Line > 2 { - fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-2, lines[pe.Position.Line-3]) + fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-2, expandTab(lines[pe.Position.Line-3])) } if pe.Position.Line > 1 { - fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-1, lines[pe.Position.Line-2]) + fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-1, expandTab(lines[pe.Position.Line-2])) } - fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line, lines[pe.Position.Line-1]) - fmt.Fprintf(b, "% 10s%s%s\n", "", strings.Repeat(" ", col), strings.Repeat("^", pe.Position.Len)) + + /// Expand tabs, so that the ^^^s are at the correct position, but leave + /// "column 10-13" intact. Adjusting this to the visual column would be + /// better, but we don't know the tabsize of the user in their editor, which + /// can be 8, 4, 2, or something else. We can't know. So leaving it as the + /// character index is probably the "most correct". + expanded := expandTab(lines[pe.Position.Line-1]) + diff := len(expanded) - len(lines[pe.Position.Line-1]) + + fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line, expanded) + fmt.Fprintf(b, "% 10s%s%s\n", "", strings.Repeat(" ", col+diff), strings.Repeat("^", pe.Position.Len)) return b.String() } @@ -159,6 +168,33 @@ func (pe ParseError) column(lines []string) int { return col } +func expandTab(s string) string { + var ( + b strings.Builder + l int + fill = func(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = ' ' + } + return string(b) + } + ) + b.Grow(len(s)) + for _, r := range s { + switch r { + case '\t': + tw := 8 - l%8 + b.WriteString(fill(tw)) + l += tw + default: + b.WriteRune(r) + l += 1 + } + } + return b.String() +} + type ( errLexControl struct{ r rune } errLexEscape struct{ r rune } diff --git a/error_test.go b/error_test.go index a7de72c0..5f4010d6 100644 --- a/error_test.go +++ b/error_test.go @@ -351,3 +351,56 @@ At line 3, column 6-13: t.Errorf("\nwant:\n%s\nhave:\n%s", want, have) } } + +func TestErrorIndent(t *testing.T) { + getErr := func(t *testing.T, tml string) toml.ParseError { + var m map[string]any + _, err := toml.Decode(tml, &m) + if err == nil { + t.Fatal(err) + } + var pErr toml.ParseError + if !errors.As(err, &pErr) { + t.Fatalf("not a ParseError: %#v", err) + } + return pErr + } + + err := getErr(t, "\tspaces = xxx") + want := `toml: error: expected value but found "xxx" instead + +At line 1, column 10-13: + + 1 | spaces = xxx + ^^^ +` + + if have := err.ErrorWithUsage(); have != want { + t.Errorf("\nwant:\n%s\nhave:\n%s", want, have) + } + + err = getErr(t, "\tspaces\t=\txxx") + want = `toml: error: expected value but found "xxx" instead + +At line 1, column 10-13: + + 1 | spaces = xxx + ^^^ +` + if have := err.ErrorWithUsage(); have != want { + t.Errorf("\nwant:\n%s\nhave:\n%s", want, have) + } + + err = getErr(t, "\txxx \t = \t 1\n\tspaces\t=\txxx") + want = `toml: error: expected value but found "xxx" instead + +At line 2, column 10-13: + + 1 | xxx = 1 + 2 | spaces = xxx + ^^^ +` + if have := err.ErrorWithUsage(); have != want { + t.Errorf("\nwant:\n%s\nhave:\n%s", want, have) + } +}