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"""`,
|
||||
gen: func() test {
|
||||
type doc struct {
|
||||
@@ -934,7 +934,7 @@ huey = 'dewey'
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "multiline basic string hex escape",
|
||||
desc: "multiline basic string hex escape",
|
||||
input: `A = """\x61"""`,
|
||||
gen: func() test {
|
||||
type doc struct {
|
||||
@@ -3727,6 +3727,30 @@ world'`,
|
||||
desc: `backspace in comment`,
|
||||
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 {
|
||||
@@ -4666,3 +4690,176 @@ func TestIssue1028(t *testing.T) {
|
||||
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
|
||||
}
|
||||
|
||||
//nolint:funlen,cyclop
|
||||
//nolint:funlen,cyclop,dupl
|
||||
func (p *Parser) parseInlineTable(b []byte) (reference, []byte, error) {
|
||||
// inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close
|
||||
// inline-table-open = %x7B ws ; {
|
||||
@@ -555,7 +555,7 @@ func (p *Parser) parseInlineTable(b []byte) (reference, []byte, error) {
|
||||
return parent, rest, err
|
||||
}
|
||||
|
||||
//nolint:funlen,cyclop
|
||||
//nolint:funlen,cyclop,dupl
|
||||
func (p *Parser) parseValArray(b []byte) (reference, []byte, error) {
|
||||
// array = array-open [ array-values ] ws-comment-newline array-close
|
||||
// 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) {
|
||||
p := &Parser{}
|
||||
b.Run("4", func(b *testing.B) {
|
||||
|
||||
Reference in New Issue
Block a user