Remove optional offset and fallback, guarantee offset by construction
ParserError.Offset is now a plain exported int field, always set: - The parser sets it via setErrOffset() when capturing parse errors - strict.go sets it from the key's Raw range at construction - wrapDecodeError computes it inline from cap(document) - cap(highlight) This eliminates: - The SetOffset/Offset() accessor methods and offsetValid flag - The subsliceOffset fallback function in errors.go - Any conditional logic around whether the offset is present The offset is guaranteed by construction at every path that creates or consumes a ParserError. Co-authored-by: Thomas Pelletier <thomas@pelletier.dev>
This commit is contained in:
@@ -99,10 +99,7 @@ func (e *DecodeError) Key() Key {
|
|||||||
//
|
//
|
||||||
//nolint:funlen
|
//nolint:funlen
|
||||||
func wrapDecodeError(document []byte, de *unstable.ParserError) *DecodeError {
|
func wrapDecodeError(document []byte, de *unstable.ParserError) *DecodeError {
|
||||||
offset, ok := de.Offset()
|
offset := cap(document) - cap(de.Highlight)
|
||||||
if !ok {
|
|
||||||
offset = subsliceOffset(document, de.Highlight)
|
|
||||||
}
|
|
||||||
|
|
||||||
errMessage := de.Error()
|
errMessage := de.Error()
|
||||||
errLine, errColumn := positionAtEnd(document[:offset])
|
errLine, errColumn := positionAtEnd(document[:offset])
|
||||||
@@ -264,9 +261,3 @@ func positionAtEnd(b []byte) (row int, column int) {
|
|||||||
|
|
||||||
return row, column
|
return row, column
|
||||||
}
|
}
|
||||||
|
|
||||||
// subsliceOffset returns the byte offset of subslice within data.
|
|
||||||
// subslice must share the same backing array as data.
|
|
||||||
func subsliceOffset(data []byte, subslice []byte) int {
|
|
||||||
return cap(data) - cap(subslice)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ line 5`,
|
|||||||
err := wrapDecodeError(doc, &unstable.ParserError{
|
err := wrapDecodeError(doc, &unstable.ParserError{
|
||||||
Highlight: hl,
|
Highlight: hl,
|
||||||
Message: e.msg,
|
Message: e.msg,
|
||||||
|
Offset: start,
|
||||||
})
|
})
|
||||||
|
|
||||||
var derr *DecodeError
|
var derr *DecodeError
|
||||||
|
|||||||
@@ -55,13 +55,12 @@ func (s *strict) MissingTable(node *unstable.Node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
highlight, offset := s.keyLocation(node)
|
highlight, offset := s.keyLocation(node)
|
||||||
pe := unstable.ParserError{
|
s.missing = append(s.missing, unstable.ParserError{
|
||||||
Highlight: highlight,
|
Highlight: highlight,
|
||||||
Message: "missing table",
|
Message: "missing table",
|
||||||
Key: s.key.Key(),
|
Key: s.key.Key(),
|
||||||
}
|
Offset: offset,
|
||||||
pe.SetOffset(offset)
|
})
|
||||||
s.missing = append(s.missing, pe)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *strict) MissingField(node *unstable.Node) {
|
func (s *strict) MissingField(node *unstable.Node) {
|
||||||
@@ -70,13 +69,12 @@ func (s *strict) MissingField(node *unstable.Node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
highlight, offset := s.keyLocation(node)
|
highlight, offset := s.keyLocation(node)
|
||||||
pe := unstable.ParserError{
|
s.missing = append(s.missing, unstable.ParserError{
|
||||||
Highlight: highlight,
|
Highlight: highlight,
|
||||||
Message: "missing field",
|
Message: "missing field",
|
||||||
Key: s.key.Key(),
|
Key: s.key.Key(),
|
||||||
}
|
Offset: offset,
|
||||||
pe.SetOffset(offset)
|
})
|
||||||
s.missing = append(s.missing, pe)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *strict) Error(doc []byte) error {
|
func (s *strict) Error(doc []byte) error {
|
||||||
|
|||||||
+11
-25
@@ -18,8 +18,9 @@ type ParserError struct {
|
|||||||
Message string
|
Message string
|
||||||
Key []string // optional
|
Key []string // optional
|
||||||
|
|
||||||
offset int
|
// Offset is the byte offset of Highlight within the document.
|
||||||
offsetValid bool
|
// Set by the parser when the error is captured.
|
||||||
|
Offset int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error is the implementation of the error interface.
|
// Error is the implementation of the error interface.
|
||||||
@@ -27,21 +28,6 @@ func (e *ParserError) Error() string {
|
|||||||
return e.Message
|
return e.Message
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetOffset records the byte offset of the error highlight within the
|
|
||||||
// document. Used by the parser to cache position information so
|
|
||||||
// downstream consumers don't need to re-derive it from pointers.
|
|
||||||
func (e *ParserError) SetOffset(offset int) {
|
|
||||||
e.offset = offset
|
|
||||||
e.offsetValid = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Offset returns the byte offset of the error highlight within the
|
|
||||||
// document, if it was previously set by the parser. The boolean
|
|
||||||
// indicates whether the offset is valid.
|
|
||||||
func (e *ParserError) Offset() (int, bool) {
|
|
||||||
return e.offset, e.offsetValid
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParserError is a convenience function to create a ParserError
|
// NewParserError is a convenience function to create a ParserError
|
||||||
//
|
//
|
||||||
// Warning: Highlight needs to be a subslice of Parser.data, so only slices
|
// Warning: Highlight needs to be a subslice of Parser.data, so only slices
|
||||||
@@ -145,11 +131,15 @@ func (p *Parser) NextExpression() bool {
|
|||||||
p.left, p.err = p.parseNewline(p.left)
|
p.left, p.err = p.parseNewline(p.left)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(p.left) == 0 || p.err != nil {
|
if p.err != nil {
|
||||||
p.setErrOffset()
|
p.setErrOffset()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(p.left) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
p.ref, p.left, p.err = p.parseExpression(p.left)
|
p.ref, p.left, p.err = p.parseExpression(p.left)
|
||||||
|
|
||||||
if p.err != nil {
|
if p.err != nil {
|
||||||
@@ -176,9 +166,8 @@ func (p *Parser) Error() error {
|
|||||||
return p.err
|
return p.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// setErrOffset computes and caches the byte offset of the error's
|
// setErrOffset sets the byte offset on the parser error from the
|
||||||
// highlight within p.data, so downstream consumers can use it
|
// highlight's position within p.data.
|
||||||
// without pointer arithmetic.
|
|
||||||
func (p *Parser) setErrOffset() {
|
func (p *Parser) setErrOffset() {
|
||||||
if p.err == nil {
|
if p.err == nil {
|
||||||
return
|
return
|
||||||
@@ -187,10 +176,7 @@ func (p *Parser) setErrOffset() {
|
|||||||
if !errors.As(p.err, &perr) {
|
if !errors.As(p.err, &perr) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if perr.offsetValid || len(perr.Highlight) == 0 {
|
perr.Offset = p.subsliceOffset(perr.Highlight)
|
||||||
return
|
|
||||||
}
|
|
||||||
perr.SetOffset(p.subsliceOffset(perr.Highlight))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Position describes a position in the input.
|
// Position describes a position in the input.
|
||||||
|
|||||||
@@ -766,7 +766,7 @@ func TestErrorHighlightPositions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParserError_CachedOffset(t *testing.T) {
|
func TestParserError_Offset(t *testing.T) {
|
||||||
examples := []struct {
|
examples := []struct {
|
||||||
desc string
|
desc string
|
||||||
input string
|
input string
|
||||||
@@ -803,12 +803,8 @@ func TestParserError_CachedOffset(t *testing.T) {
|
|||||||
if !errors.As(err, &perr) {
|
if !errors.As(err, &perr) {
|
||||||
t.Fatalf("expected ParserError, got %T", err)
|
t.Fatalf("expected ParserError, got %T", err)
|
||||||
}
|
}
|
||||||
offset, ok := perr.Offset()
|
if perr.Offset != e.wantOffset {
|
||||||
if !ok {
|
t.Errorf("offset: got %d, want %d", perr.Offset, e.wantOffset)
|
||||||
t.Fatal("expected offset to be set")
|
|
||||||
}
|
|
||||||
if offset != e.wantOffset {
|
|
||||||
t.Errorf("cached offset: got %d, want %d", offset, e.wantOffset)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user