Cache error offset in ParserError for safer position tracking

Instead of requiring downstream consumers to re-derive the byte offset
from pointer arithmetic on the Highlight slice, compute and cache the
offset inside the parser at error-capture time via setErrOffset().

This is safer because:
- The parser is the one place where the backing-array guarantee is known
  to hold (Highlight is always a subslice of the parse buffer)
- Downstream consumers (wrapDecodeError) can use the cached offset
  directly, avoiding the need for pointer comparison
- Errors created outside the parser (strict.go) set the offset from
  existing Raw ranges, which are already correct by construction

Add ParserError.SetOffset/Offset methods for setting and retrieving the
cached offset. Update wrapDecodeError to prefer the cached offset when
available, falling back to subsliceOffset for backward compatibility.

Co-authored-by: Thomas Pelletier <thomas@pelletier.dev>
This commit is contained in:
Cursor Agent
2026-04-12 13:00:38 +00:00
parent d528d3c6b4
commit 154d80392f
4 changed files with 106 additions and 11 deletions
+48
View File
@@ -766,6 +766,54 @@ func TestErrorHighlightPositions(t *testing.T) {
}
}
func TestParserError_CachedOffset(t *testing.T) {
examples := []struct {
desc string
input string
wantOffset int
}{
{
desc: "error after comment",
input: "# comment\n= \"value\"",
wantOffset: 10,
},
{
desc: "error on first line",
input: "= \"value\"",
wantOffset: 0,
},
{
desc: "error after two lines",
input: "a = 1\n= \"value\"",
wantOffset: 6,
},
}
for _, e := range examples {
t.Run(e.desc, func(t *testing.T) {
p := Parser{}
p.Reset([]byte(e.input))
for p.NextExpression() {
}
err := p.Error()
if err == nil {
t.Fatal("expected an error")
}
var perr *ParserError
if !errors.As(err, &perr) {
t.Fatalf("expected ParserError, got %T", err)
}
offset, ok := perr.Offset()
if !ok {
t.Fatal("expected offset to be set")
}
if offset != e.wantOffset {
t.Errorf("cached offset: got %d, want %d", offset, e.wantOffset)
}
})
}
}
func ExampleParser() {
doc := `
hello = "world"