feat: parse into a Document that keeps order and comments
Test / test (push) Successful in 1m34s

Assisted-by: DeepSeek V4.1 Flash
This commit is contained in:
2026-09-20 10:40:57 +02:00
parent a6e3e3fe31
commit 72f8b21ac4
16 changed files with 939 additions and 119 deletions
+37 -37
View File
@@ -12,7 +12,7 @@ import (
)
func TestParseScalars(t *testing.T) {
tree, err := Parse([]byte(`
tree, err := ParseMap([]byte(`
title = "interpres"
count = 42
ratio = 3.14
@@ -50,7 +50,7 @@ expv = 1e3
}
func TestParseInfNan(t *testing.T) {
tree, err := Parse([]byte("pos = inf\nneg = -inf\nbad = nan\n"))
tree, err := ParseMap([]byte("pos = inf\nneg = -inf\nbad = nan\n"))
if err != nil {
t.Fatalf("parse: %v", err)
}
@@ -66,7 +66,7 @@ func TestParseInfNan(t *testing.T) {
}
func TestParseStrings(t *testing.T) {
tree, err := Parse([]byte(`
tree, err := ParseMap([]byte(`
basic = "a\tb\nc"
literal = 'C:\path\no\escape'
quote = "say \"hi\""
@@ -90,7 +90,7 @@ unicode = "\u00e9"
}
func TestParseMultilineString(t *testing.T) {
tree, err := Parse([]byte("text = \"\"\"\nfirst\nsecond\"\"\"\n"))
tree, err := ParseMap([]byte("text = \"\"\"\nfirst\nsecond\"\"\"\n"))
if err != nil {
t.Fatalf("parse: %v", err)
}
@@ -100,7 +100,7 @@ func TestParseMultilineString(t *testing.T) {
}
func TestParseMultilineLineEndingBackslash(t *testing.T) {
tree, err := Parse([]byte("text = \"\"\"\\\n one \\\n two\"\"\"\n"))
tree, err := ParseMap([]byte("text = \"\"\"\\\n one \\\n two\"\"\"\n"))
if err != nil {
t.Fatalf("parse: %v", err)
}
@@ -110,7 +110,7 @@ func TestParseMultilineLineEndingBackslash(t *testing.T) {
}
func TestParseTablesAndDottedKeys(t *testing.T) {
tree, err := Parse([]byte(`
tree, err := ParseMap([]byte(`
owner.name = "Petr"
[server]
@@ -138,7 +138,7 @@ enabled = true
}
func TestParseArrayOfTables(t *testing.T) {
tree, err := Parse([]byte(`
tree, err := ParseMap([]byte(`
[[forms]]
name = "contact"
@@ -158,7 +158,7 @@ name = "feedback"
}
func TestParseArraysAndInlineTables(t *testing.T) {
tree, err := Parse([]byte(`
tree, err := ParseMap([]byte(`
ports = [80, 443]
mixed = [
"a",
@@ -184,7 +184,7 @@ point = { x = 1, y = 2 }
}
func TestParseDateTime(t *testing.T) {
tree, err := Parse([]byte(`
tree, err := ParseMap([]byte(`
offset = 1979-05-27T07:32:00Z
local = 1979-05-27T07:32:00
day = 1979-05-27
@@ -208,7 +208,7 @@ clock = 07:32:00
}
func TestDateTimeFormats(t *testing.T) {
tree, err := Parse([]byte("a = 1987-07-05 17:45:00Z\nb = 1987-07-05t17:45:00z\nc = 1977-12-21T10:32:00.555\n"))
tree, err := ParseMap([]byte("a = 1987-07-05 17:45:00Z\nb = 1987-07-05t17:45:00z\nc = 1977-12-21T10:32:00.555\n"))
if err != nil {
t.Fatalf("parse: %v", err)
}
@@ -353,7 +353,7 @@ func TestSkippedFieldTag(t *testing.T) {
}
func TestSyntaxErrorReportsLine(t *testing.T) {
_, err := Parse([]byte("a = 1\nb = \nc = 3\n"))
_, err := ParseMap([]byte("a = 1\nb = \nc = 3\n"))
if err == nil {
t.Fatal("expected a syntax error")
}
@@ -367,7 +367,7 @@ func TestSyntaxErrorReportsLine(t *testing.T) {
}
func TestComments(t *testing.T) {
tree, err := Parse([]byte(`
tree, err := ParseMap([]byte(`
# a leading comment
key = "value" # trailing comment
# another
@@ -381,7 +381,7 @@ key = "value" # trailing comment
}
func TestDuplicateKeyRejected(t *testing.T) {
_, err := Parse([]byte("a = 1\na = 2\n"))
_, err := ParseMap([]byte("a = 1\na = 2\n"))
if err == nil {
t.Fatal("expected duplicate key error")
}
@@ -396,7 +396,7 @@ func TestRejectsInvalidNumbers(t *testing.T) {
"0x", "0o", "0b", "0b2", "0o8", "0xG",
"+0x1",
} {
if _, err := Parse([]byte("v = " + tok + "\n")); err == nil {
if _, err := ParseMap([]byte("v = " + tok + "\n")); err == nil {
t.Errorf("%q: expected an error, got none", tok)
}
}
@@ -409,14 +409,14 @@ func TestParseRejectsOffsetOutOfRange(t *testing.T) {
"1979-05-27T07:32:00+24:00",
"1979-05-27T07:32:00+99:99",
} {
if _, err := Parse([]byte("v = " + tok + "\n")); err == nil {
if _, err := ParseMap([]byte("v = " + tok + "\n")); err == nil {
t.Errorf("%q: expected an error, got none", tok)
}
}
}
func TestParseAcceptsOffsetBounds(t *testing.T) {
tree, err := Parse([]byte("a = 1979-05-27T07:32:00+23:59\nb = 1979-05-27T07:32:00-23:59\n"))
tree, err := ParseMap([]byte("a = 1979-05-27T07:32:00+23:59\nb = 1979-05-27T07:32:00-23:59\n"))
if err != nil {
t.Fatalf("parse: %v", err)
}
@@ -450,7 +450,7 @@ func TestAcceptsNumberEdgeCases(t *testing.T) {
"-2.5E-3": -2.5e-3,
}
for tok, want := range cases {
tree, err := Parse([]byte("v = " + tok + "\n"))
tree, err := ParseMap([]byte("v = " + tok + "\n"))
if err != nil {
t.Errorf("%q: %v", tok, err)
continue
@@ -462,14 +462,14 @@ func TestAcceptsNumberEdgeCases(t *testing.T) {
}
func TestRejectsTableRedefinition(t *testing.T) {
_, err := Parse([]byte("[a]\nx = 1\n\n[a]\ny = 2\n"))
_, err := ParseMap([]byte("[a]\nx = 1\n\n[a]\ny = 2\n"))
if err == nil {
t.Fatal("expected a table-redefinition error")
}
}
func TestAllowsImplicitThenExplicitTable(t *testing.T) {
tree, err := Parse([]byte("[a.b]\nx = 1\n\n[a]\ny = 2\n"))
tree, err := ParseMap([]byte("[a.b]\nx = 1\n\n[a]\ny = 2\n"))
if err != nil {
t.Fatalf("parse: %v", err)
}
@@ -483,13 +483,13 @@ func TestAllowsImplicitThenExplicitTable(t *testing.T) {
}
func TestRejectsControlCharInString(t *testing.T) {
if _, err := Parse([]byte("v = \"a\x01b\"\n")); err == nil {
if _, err := ParseMap([]byte("v = \"a\x01b\"\n")); err == nil {
t.Fatal("expected a control-character error")
}
}
func TestAllowsEscapedControlChar(t *testing.T) {
tree, err := Parse([]byte(`v = "\u0000"`))
tree, err := ParseMap([]byte(`v = "\u0000"`))
if err != nil {
t.Fatalf("parse: %v", err)
}
@@ -499,7 +499,7 @@ func TestAllowsEscapedControlChar(t *testing.T) {
}
func TestMultilineQuotesAtDelimiter(t *testing.T) {
tree, err := Parse([]byte("a = '''''two quotes'''''\n"))
tree, err := ParseMap([]byte("a = '''''two quotes'''''\n"))
if err != nil {
t.Fatalf("parse: %v", err)
}
@@ -518,7 +518,7 @@ func TestRejectsInlineTableExtension(t *testing.T) {
"by nested array header": "a = { b = {} }\n[[a.b.c]]\nx = 2\n",
}
for name, doc := range cases {
if _, err := Parse([]byte(doc)); err == nil {
if _, err := ParseMap([]byte(doc)); err == nil {
t.Errorf("%s: expected an inline-table extension error", name)
}
}
@@ -533,7 +533,7 @@ func TestArrayOfTablesFreshScopePerElement(t *testing.T) {
"dotted key": "[[a]]\nb.c = 1\n[[a]]\n[a.b]\nd = 2\n",
}
for name, doc := range cases {
tree, err := Parse([]byte(doc))
tree, err := ParseMap([]byte(doc))
if err != nil {
t.Errorf("%s: %v", name, err)
continue
@@ -548,7 +548,7 @@ func TestArrayOfTablesFreshScopePerElement(t *testing.T) {
"header over dotted in one element": "[[a]]\nb.c = 1\n[a.b]\nd = 2\n",
"table over nested array": "[[a]]\n[[a.b]]\n[a.b]\nx = 1\n",
} {
if _, err := Parse([]byte(doc)); err == nil {
if _, err := ParseMap([]byte(doc)); err == nil {
t.Errorf("%s: expected an error, got none", name)
}
}
@@ -566,14 +566,14 @@ func TestRejectsSpecInvalid(t *testing.T) {
// makes the seconds optional.
}
for name, doc := range cases {
if _, err := Parse([]byte(doc)); err == nil {
if _, err := ParseMap([]byte(doc)); err == nil {
t.Errorf("%s: expected an error", name)
}
}
}
func TestArrayOfTablesPerElementSubtable(t *testing.T) {
tree, err := Parse([]byte(`
tree, err := ParseMap([]byte(`
[[forms]]
name = "a"
@@ -604,7 +604,7 @@ host = "h2"
// --- TOML 1.1 --------------------------------------------------------------
func TestParseAcceptsNoSecondsDatetimes(t *testing.T) {
tree, err := Parse([]byte(`t = 13:37
tree, err := ParseMap([]byte(`t = 13:37
dt = 1979-05-27T07:32
odt1 = 1979-05-27 07:32Z
odt2 = 1979-05-27 07:32-07:00
@@ -627,13 +627,13 @@ odt2 = 1979-05-27 07:32-07:00
t.Errorf("odt2 = %q", got)
}
// The fraction still requires the seconds it belongs to.
if _, err := Parse([]byte("a = 07:32.5\n")); err == nil {
if _, err := ParseMap([]byte("a = 07:32.5\n")); err == nil {
t.Error("07:32.5: expected an error, got none")
}
}
func TestParseAcceptsEscapeAndHexEscapes(t *testing.T) {
tree, err := Parse([]byte(`esc = "\e"
tree, err := ParseMap([]byte(`esc = "\e"
hex = "\x20\x7f\xf8"
nul = "\x00"
multi = """\x68\x65"""
@@ -660,14 +660,14 @@ lit = '\x20'
}
// Two digits exactly; a short or non-hex escape is an error.
for _, doc := range []string{`a = "\x4"`, `a = "\x"`, `a = "\xgg"`} {
if _, err := Parse([]byte(doc)); err == nil {
if _, err := ParseMap([]byte(doc)); err == nil {
t.Errorf("%s: expected an error, got none", doc)
}
}
}
func TestParseAcceptsMultilineInlineTables(t *testing.T) {
tree, err := Parse([]byte("tbl = {\n\thello = \"world\",\n\tarr = [1,\n\t\t2,\n\t],\n\tsub = {\n\t\tk = 1,\n\t},\n\tbare = 2}\n"))
tree, err := ParseMap([]byte("tbl = {\n\thello = \"world\",\n\tarr = [1,\n\t\t2,\n\t],\n\tsub = {\n\t\tk = 1,\n\t},\n\tbare = 2}\n"))
if err != nil {
t.Fatalf("parse: %v", err)
}
@@ -682,7 +682,7 @@ func TestParseAcceptsMultilineInlineTables(t *testing.T) {
t.Errorf("sub = %#v", tbl["sub"])
}
// Comments inside the table, and a trailing comma at both depths.
tree, err = Parse([]byte("m = { # one\n\t# two\n\ta = 1, # three\n\t# four\n}\n"))
tree, err = ParseMap([]byte("m = { # one\n\t# two\n\ta = 1, # three\n\t# four\n}\n"))
if err != nil {
t.Fatalf("parse with comments: %v", err)
}
@@ -690,7 +690,7 @@ func TestParseAcceptsMultilineInlineTables(t *testing.T) {
t.Errorf("m = %#v", m)
}
// The old single-line shapes keep working, with and without the comma.
if _, err := Parse([]byte("a = { b = 1, c = 2 }\n")); err != nil {
if _, err := ParseMap([]byte("a = { b = 1, c = 2 }\n")); err != nil {
t.Errorf("single line: %v", err)
}
// Still rejected: two commas, a missing value, and an unclosed table.
@@ -699,7 +699,7 @@ func TestParseAcceptsMultilineInlineTables(t *testing.T) {
"missing value": "a = {\n\tb =\n}\n",
"unterminated": "a = { b = 1,\n",
} {
if _, err := Parse([]byte(doc)); err == nil {
if _, err := ParseMap([]byte(doc)); err == nil {
t.Errorf("%s: expected an error, got none", name)
}
}
@@ -711,10 +711,10 @@ func TestParseNestingLimit(t *testing.T) {
deep := func(n int) []byte {
return []byte("v = " + strings.Repeat("[", n) + strings.Repeat("]", n) + "\n")
}
if _, err := Parse(deep(100)); err != nil {
if _, err := ParseMap(deep(100)); err != nil {
t.Fatalf("a document well inside the limit: %v", err)
}
_, err := Parse(deep(maxNestingDepth + 1))
_, err := ParseMap(deep(maxNestingDepth + 1))
if err == nil {
t.Fatal("expected a nesting error")
}