fix: resolve lint issues and improve test coverage for TOML v1.1.0
- Fix dupl lint: add nolint:dupl to parseInlineTable and parseValArray which have intentionally similar loop structures - Fix gci lint: correct alignment in multiline basic string test entries - Add unmarshaler tests for new TOML v1.1.0 features exercising public APIs: multiline inline tables with comments, trailing commas, leading commas, error cases (comma at start, missing separator, double comma, incomplete table), escape sequences, and type mismatch errors - Add parser test for inline table comment handling with KeepComments - Coverage increases from 97.37% to 97.44% vs v2 base https://claude.ai/code/session_01RdiWykFQdmwkQ2nbLwJwwP
This commit is contained in:
+199
-2
@@ -864,7 +864,7 @@ huey = 'dewey'
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "multiline basic string escape character",
|
desc: "multiline basic string escape character",
|
||||||
input: `A = """\e"""`,
|
input: `A = """\e"""`,
|
||||||
gen: func() test {
|
gen: func() test {
|
||||||
type doc struct {
|
type doc struct {
|
||||||
@@ -934,7 +934,7 @@ huey = 'dewey'
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "multiline basic string hex escape",
|
desc: "multiline basic string hex escape",
|
||||||
input: `A = """\x61"""`,
|
input: `A = """\x61"""`,
|
||||||
gen: func() test {
|
gen: func() test {
|
||||||
type doc struct {
|
type doc struct {
|
||||||
@@ -3727,6 +3727,30 @@ world'`,
|
|||||||
desc: `backspace in comment`,
|
desc: `backspace in comment`,
|
||||||
data: "# this is a test\ba=1",
|
data: "# this is a test\ba=1",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: `inline table comma at start`,
|
||||||
|
data: `a = { , b = 1 }`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: `inline table missing separator`,
|
||||||
|
data: `a = { b = 1 c = 2 }`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: `inline table double comma across newline`,
|
||||||
|
data: "a = { b = 1,\n, c = 2 }",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: `incomplete inline table`,
|
||||||
|
data: "a = { b = 1,\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: `incomplete hex escape in multiline basic string`,
|
||||||
|
data: `A = """\x6"""`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: `invalid escape char in basic string`,
|
||||||
|
data: `A = "\z"`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, e := range examples {
|
for _, e := range examples {
|
||||||
@@ -4666,3 +4690,176 @@ func TestIssue1028(t *testing.T) {
|
|||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// customFieldUnmarshaler implements unstable.Unmarshaler and captures all
|
||||||
|
// key-value pairs directed to it, including unknown fields.
|
||||||
|
type customFieldUnmarshaler struct {
|
||||||
|
Values map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *customFieldUnmarshaler) UnmarshalTOML(value *unstable.Node) error {
|
||||||
|
c.Values = map[string]string{
|
||||||
|
"kind": value.Kind.String(),
|
||||||
|
"data": string(value.Data),
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalerInterface_StructFieldFallback(t *testing.T) {
|
||||||
|
// When EnableUnmarshalerInterface is active and a struct field is not found,
|
||||||
|
// the decoder should fall back to the Unmarshaler interface on the struct.
|
||||||
|
type Config struct {
|
||||||
|
Name string `toml:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("unknown field with unmarshaler", func(t *testing.T) {
|
||||||
|
doc := `name = "hello"
|
||||||
|
unknown = "world"`
|
||||||
|
var cfg Config
|
||||||
|
decoder := toml.NewDecoder(bytes.NewReader([]byte(doc)))
|
||||||
|
decoder.EnableUnmarshalerInterface()
|
||||||
|
err := decoder.Decode(&cfg)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "hello", cfg.Name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalerInterface_Value(t *testing.T) {
|
||||||
|
// Test that EnableUnmarshalerInterface delegates value decoding
|
||||||
|
// to the UnmarshalTOML method.
|
||||||
|
type Config struct {
|
||||||
|
Field customFieldUnmarshaler `toml:"field"`
|
||||||
|
}
|
||||||
|
|
||||||
|
doc := `field = "test-value"`
|
||||||
|
var cfg Config
|
||||||
|
decoder := toml.NewDecoder(bytes.NewReader([]byte(doc)))
|
||||||
|
decoder.EnableUnmarshalerInterface()
|
||||||
|
err := decoder.Decode(&cfg)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "test-value", cfg.Field.Values["data"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypeMismatchString_StructFieldContext(t *testing.T) {
|
||||||
|
// Exercise the typeMismatchString code path that includes struct field info
|
||||||
|
// in the error message.
|
||||||
|
type Inner struct {
|
||||||
|
Value int `toml:"value"`
|
||||||
|
}
|
||||||
|
type Config struct {
|
||||||
|
Inner Inner `toml:"inner"`
|
||||||
|
}
|
||||||
|
|
||||||
|
doc := `inner = "not-a-table"`
|
||||||
|
var cfg Config
|
||||||
|
err := toml.Unmarshal([]byte(doc), &cfg)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalInlineTable_IncompatibleType(t *testing.T) {
|
||||||
|
// Exercise the default branch of unmarshalInlineTable when the target
|
||||||
|
// is not a map, struct, or interface.
|
||||||
|
type doc struct {
|
||||||
|
A int `toml:"a"`
|
||||||
|
}
|
||||||
|
var v doc
|
||||||
|
err := toml.Unmarshal([]byte(`a = {b = 1}`), &v)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypeMismatchString_NoStructContext(t *testing.T) {
|
||||||
|
// Exercise the typeMismatchString code path without struct field context (line 186).
|
||||||
|
// Decoding a string into a bare int triggers this path.
|
||||||
|
var v map[string]int
|
||||||
|
err := toml.Unmarshal([]byte(`a = "hello"`), &v)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultilineInlineTable_EmptyWithNewlines(t *testing.T) {
|
||||||
|
doc := "a = {\n\n}"
|
||||||
|
var v map[string]interface{}
|
||||||
|
err := toml.Unmarshal([]byte(doc), &v)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
inner := v["a"]
|
||||||
|
if inner == nil {
|
||||||
|
t.Fatal("expected key 'a' to be present")
|
||||||
|
}
|
||||||
|
m, ok := inner.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected map, got %T", inner)
|
||||||
|
}
|
||||||
|
if len(m) != 0 {
|
||||||
|
t.Fatalf("expected empty map, got %v", m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultilineInlineTable_CommentsOnly(t *testing.T) {
|
||||||
|
doc := "a = {\n # just a comment\n}"
|
||||||
|
var v map[string]interface{}
|
||||||
|
err := toml.Unmarshal([]byte(doc), &v)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
inner := v["a"]
|
||||||
|
if inner == nil {
|
||||||
|
t.Fatal("expected key 'a' to be present")
|
||||||
|
}
|
||||||
|
m, ok := inner.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected map, got %T", inner)
|
||||||
|
}
|
||||||
|
if len(m) != 0 {
|
||||||
|
t.Fatalf("expected empty map, got %v", m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultilineInlineTable_CommentAfterComma(t *testing.T) {
|
||||||
|
// Exercises comment handling after comma in inline table (parser lines 518-524).
|
||||||
|
doc := "a = { b = 1, # comment\nc = 2 }"
|
||||||
|
var v map[string]interface{}
|
||||||
|
err := toml.Unmarshal([]byte(doc), &v)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
m, ok := v["a"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected a map")
|
||||||
|
}
|
||||||
|
if m["b"] != int64(1) {
|
||||||
|
t.Fatalf("expected b=1, got %v", m["b"])
|
||||||
|
}
|
||||||
|
if m["c"] != int64(2) {
|
||||||
|
t.Fatalf("expected c=2, got %v", m["c"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultilineInlineTable_CommentAfterValue(t *testing.T) {
|
||||||
|
// Exercises comment handling after keyval in inline table (parser lines 542-548).
|
||||||
|
doc := "a = { b = 1 # comment\n, c = 2 }"
|
||||||
|
var v map[string]interface{}
|
||||||
|
err := toml.Unmarshal([]byte(doc), &v)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
m, ok := v["a"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected a map")
|
||||||
|
}
|
||||||
|
if m["b"] != int64(1) {
|
||||||
|
t.Fatalf("expected b=1, got %v", m["b"])
|
||||||
|
}
|
||||||
|
if m["c"] != int64(2) {
|
||||||
|
t.Fatalf("expected c=2, got %v", m["c"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultilineInlineTable_LeadingComma(t *testing.T) {
|
||||||
|
doc := "a = { b = 1\n, c = 2 }"
|
||||||
|
var v map[string]interface{}
|
||||||
|
err := toml.Unmarshal([]byte(doc), &v)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
m, ok := v["a"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected a map")
|
||||||
|
}
|
||||||
|
if m["b"] != int64(1) {
|
||||||
|
t.Fatalf("expected b=1, got %v", m["b"])
|
||||||
|
}
|
||||||
|
if m["c"] != int64(2) {
|
||||||
|
t.Fatalf("expected c=2, got %v", m["c"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+2
-2
@@ -460,7 +460,7 @@ func (p *Parser) parseLiteralString(b []byte) ([]byte, []byte, []byte, error) {
|
|||||||
return v, v[1 : len(v)-1], rest, nil
|
return v, v[1 : len(v)-1], rest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:funlen,cyclop
|
//nolint:funlen,cyclop,dupl
|
||||||
func (p *Parser) parseInlineTable(b []byte) (reference, []byte, error) {
|
func (p *Parser) parseInlineTable(b []byte) (reference, []byte, error) {
|
||||||
// inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close
|
// inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close
|
||||||
// inline-table-open = %x7B ws ; {
|
// inline-table-open = %x7B ws ; {
|
||||||
@@ -555,7 +555,7 @@ func (p *Parser) parseInlineTable(b []byte) (reference, []byte, error) {
|
|||||||
return parent, rest, err
|
return parent, rest, err
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:funlen,cyclop
|
//nolint:funlen,cyclop,dupl
|
||||||
func (p *Parser) parseValArray(b []byte) (reference, []byte, error) {
|
func (p *Parser) parseValArray(b []byte) (reference, []byte, error) {
|
||||||
// array = array-open [ array-values ] ws-comment-newline array-close
|
// array = array-open [ array-values ] ws-comment-newline array-close
|
||||||
// array-open = %x5B ; [
|
// array-open = %x5B ; [
|
||||||
|
|||||||
@@ -498,6 +498,44 @@ func TestParser_AST(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseInlineTable_CommentsWithKeepComments(t *testing.T) {
|
||||||
|
// Exercise comment reference handling inside parseInlineTable when
|
||||||
|
// KeepComments is true. This covers the addChild(cref) branches
|
||||||
|
// at the start of the loop, after comma, and after keyval.
|
||||||
|
examples := []struct {
|
||||||
|
desc string
|
||||||
|
input string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "comment at start of inline table",
|
||||||
|
input: "a = {\n# comment\nb = 1\n}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "comment after comma",
|
||||||
|
input: "a = {b = 1,\n# comment\nc = 2\n}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "comment after keyval",
|
||||||
|
input: "a = {b = 1 # comment\n, c = 2}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "comment only in inline table",
|
||||||
|
input: "a = {\n# just a comment\n}",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range examples {
|
||||||
|
e := e
|
||||||
|
t.Run(e.desc, func(t *testing.T) {
|
||||||
|
p := Parser{KeepComments: true}
|
||||||
|
p.Reset([]byte(e.input))
|
||||||
|
p.NextExpression()
|
||||||
|
err := p.Error()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkParseBasicStringWithUnicode(b *testing.B) {
|
func BenchmarkParseBasicStringWithUnicode(b *testing.B) {
|
||||||
p := &Parser{}
|
p := &Parser{}
|
||||||
b.Run("4", func(b *testing.B) {
|
b.Run("4", func(b *testing.B) {
|
||||||
|
|||||||
Reference in New Issue
Block a user