add line numbers for parser errors (#7326)

This commit is contained in:
Patrick Devine 2024-11-14 13:59:44 -08:00 committed by GitHub
parent 0679d491fe
commit 4efb98cb4f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 68 additions and 9 deletions

View file

@ -65,9 +65,22 @@ var (
errInvalidCommand = errors.New("command must be one of \"from\", \"license\", \"template\", \"system\", \"adapter\", \"parameter\", or \"message\"") errInvalidCommand = errors.New("command must be one of \"from\", \"license\", \"template\", \"system\", \"adapter\", \"parameter\", or \"message\"")
) )
type ParserError struct {
LineNumber int
Msg string
}
func (e *ParserError) Error() string {
if e.LineNumber > 0 {
return fmt.Sprintf("(line %d): %s", e.LineNumber, e.Msg)
}
return e.Msg
}
func ParseFile(r io.Reader) (*File, error) { func ParseFile(r io.Reader) (*File, error) {
var cmd Command var cmd Command
var curr state var curr state
var currLine int = 1
var b bytes.Buffer var b bytes.Buffer
var role string var role string
@ -84,11 +97,18 @@ func ParseFile(r io.Reader) (*File, error) {
return nil, err return nil, err
} }
if isNewline(r) {
currLine++
}
next, r, err := parseRuneForState(r, curr) next, r, err := parseRuneForState(r, curr)
if errors.Is(err, io.ErrUnexpectedEOF) { if errors.Is(err, io.ErrUnexpectedEOF) {
return nil, fmt.Errorf("%w: %s", err, b.String()) return nil, fmt.Errorf("%w: %s", err, b.String())
} else if err != nil { } else if err != nil {
return nil, err return nil, &ParserError{
LineNumber: currLine,
Msg: err.Error(),
}
} }
// process the state transition, some transitions need to be intercepted and redirected // process the state transition, some transitions need to be intercepted and redirected
@ -96,7 +116,10 @@ func ParseFile(r io.Reader) (*File, error) {
switch curr { switch curr {
case stateName: case stateName:
if !isValidCommand(b.String()) { if !isValidCommand(b.String()) {
return nil, errInvalidCommand return nil, &ParserError{
LineNumber: currLine,
Msg: errInvalidCommand.Error(),
}
} }
// next state sometimes depends on the current buffer value // next state sometimes depends on the current buffer value
@ -117,7 +140,10 @@ func ParseFile(r io.Reader) (*File, error) {
cmd.Name = b.String() cmd.Name = b.String()
case stateMessage: case stateMessage:
if !isValidMessageRole(b.String()) { if !isValidMessageRole(b.String()) {
return nil, errInvalidMessageRole return nil, &ParserError{
LineNumber: currLine,
Msg: errInvalidMessageRole.Error(),
}
} }
role = b.String() role = b.String()

View file

@ -3,6 +3,7 @@ package parser
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"io" "io"
"strings" "strings"
@ -180,8 +181,15 @@ func TestParseFileBadCommand(t *testing.T) {
FROM foo FROM foo
BADCOMMAND param1 value1 BADCOMMAND param1 value1
` `
parserError := &ParserError{
LineNumber: 3,
Msg: errInvalidCommand.Error(),
}
_, err := ParseFile(strings.NewReader(input)) _, err := ParseFile(strings.NewReader(input))
require.ErrorIs(t, err, errInvalidCommand) if !errors.As(err, &parserError) {
t.Errorf("unexpected error: expected: %s, actual: %s", parserError.Error(), err.Error())
}
} }
func TestParseFileMessages(t *testing.T) { func TestParseFileMessages(t *testing.T) {
@ -245,7 +253,10 @@ FROM foo
MESSAGE badguy I'm a bad guy! MESSAGE badguy I'm a bad guy!
`, `,
nil, nil,
errInvalidMessageRole, &ParserError{
LineNumber: 3,
Msg: errInvalidMessageRole.Error(),
},
}, },
{ {
` `
@ -264,13 +275,35 @@ MESSAGE system`,
}, },
} }
for _, c := range cases { for _, tt := range cases {
t.Run("", func(t *testing.T) { t.Run("", func(t *testing.T) {
modelfile, err := ParseFile(strings.NewReader(c.input)) modelfile, err := ParseFile(strings.NewReader(tt.input))
require.ErrorIs(t, err, c.err)
if modelfile != nil { if modelfile != nil {
assert.Equal(t, c.expected, modelfile.Commands) assert.Equal(t, tt.expected, modelfile.Commands)
} }
if tt.err == nil {
if err != nil {
t.Fatalf("expected no error, but got %v", err)
}
return
}
switch tt.err.(type) {
case *ParserError:
var pErr *ParserError
if errors.As(err, &pErr) {
// got the correct type of error
return
}
}
if errors.Is(err, tt.err) {
return
}
t.Fatalf("unexpected error: expected: %v, actual: %v", tt.err, err)
}) })
} }
} }