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\"")
)
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) {
var cmd Command
var curr state
var currLine int = 1
var b bytes.Buffer
var role string
@ -84,11 +97,18 @@ func ParseFile(r io.Reader) (*File, error) {
return nil, err
}
if isNewline(r) {
currLine++
}
next, r, err := parseRuneForState(r, curr)
if errors.Is(err, io.ErrUnexpectedEOF) {
return nil, fmt.Errorf("%w: %s", err, b.String())
} 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
@ -96,7 +116,10 @@ func ParseFile(r io.Reader) (*File, error) {
switch curr {
case stateName:
if !isValidCommand(b.String()) {
return nil, errInvalidCommand
return nil, &ParserError{
LineNumber: currLine,
Msg: errInvalidCommand.Error(),
}
}
// next state sometimes depends on the current buffer value
@ -117,7 +140,10 @@ func ParseFile(r io.Reader) (*File, error) {
cmd.Name = b.String()
case stateMessage:
if !isValidMessageRole(b.String()) {
return nil, errInvalidMessageRole
return nil, &ParserError{
LineNumber: currLine,
Msg: errInvalidMessageRole.Error(),
}
}
role = b.String()

View file

@ -3,6 +3,7 @@ package parser
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"strings"
@ -180,8 +181,15 @@ func TestParseFileBadCommand(t *testing.T) {
FROM foo
BADCOMMAND param1 value1
`
parserError := &ParserError{
LineNumber: 3,
Msg: errInvalidCommand.Error(),
}
_, 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) {
@ -245,7 +253,10 @@ FROM foo
MESSAGE badguy I'm a bad guy!
`,
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) {
modelfile, err := ParseFile(strings.NewReader(c.input))
require.ErrorIs(t, err, c.err)
modelfile, err := ParseFile(strings.NewReader(tt.input))
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)
})
}
}