go-yaml/parser/parser_test.go

1014 lines
13 KiB
Go
Raw Normal View History

2019-10-16 18:21:20 +09:00
package parser_test
import (
"fmt"
2019-12-22 11:50:07 +09:00
"path/filepath"
2021-09-07 17:31:17 +09:00
"reflect"
2019-10-16 18:21:20 +09:00
"strings"
"testing"
2023-09-15 12:11:17 +09:00
"github.com/goccy/go-yaml"
2019-10-16 18:21:20 +09:00
"github.com/goccy/go-yaml/ast"
"github.com/goccy/go-yaml/lexer"
"github.com/goccy/go-yaml/parser"
2021-09-07 17:31:17 +09:00
"github.com/goccy/go-yaml/token"
2019-10-16 18:21:20 +09:00
)
func TestParser(t *testing.T) {
sources := []string{
"null\n",
"{}\n",
"v: hi\n",
"v: \"true\"\n",
"v: \"false\"\n",
"v: true\n",
"v: false\n",
"v: 10\n",
"v: -10\n",
"v: 42\n",
"v: 4294967296\n",
"v: \"10\"\n",
"v: 0.1\n",
"v: 0.99\n",
"v: -0.1\n",
"v: .inf\n",
"v: -.inf\n",
"v: .nan\n",
"v: null\n",
"v: \"\"\n",
"v:\n- A\n- B\n",
"a: '-'\n",
"123\n",
"hello: world\n",
"a: null\n",
"v:\n- A\n- 1\n- B:\n - 2\n - 3\n",
"a:\n b: c\n",
"a: {x: 1}\n",
"t2: 2018-01-09T10:40:47Z\nt4: 2098-01-09T10:40:47Z\n",
"a: [1, 2]\n",
"a: {b: c, d: e}\n",
"a: 3s\n",
"a: <foo>\n",
"a: \"1:1\"\n",
"a: 1.2.3.4\n",
"a: \"2015-02-24T18:19:39Z\"\n",
"a: 'b: c'\n",
"a: 'Hello #comment'\n",
2021-03-01 17:32:11 +09:00
"a: abc <<def>> ghi",
"a: <<abcd",
"a: <<:abcd",
"a: << :abcd",
2019-10-16 18:21:20 +09:00
"a: 100.5\n",
"a: bogus\n",
"a: \"\\0\"\n",
"b: 2\na: 1\nd: 4\nc: 3\nsub:\n e: 5\n",
" a : b \n",
"a: b # comment\nb: c\n",
"---\na: b\n",
"a: b\n...\n",
"%YAML 1.2\n---\n",
"a: !!binary gIGC\n",
"a: !!binary |\n " + strings.Repeat("kJCQ", 17) + "kJ\n CQ\n",
"- !tag\n a: b\n c: d\n",
"v:\n- A\n- |-\n B\n C\n",
"v:\n- A\n- >-\n B\n C\n",
"v: |-\n 0\n",
"v: |-\n 0\nx: 0",
`"a\n1\nb"`,
`{"a":"b"}`,
`!!map {
? !!str "explicit":!!str "entry",
? !!str "implicit" : !!str "entry",
? !!null "" : !!null "",
}`,
2022-12-02 03:56:31 +09:00
"\"a\": a\n\"b\": b",
"'a': a\n'b': b",
"a: \r\n b: 1\r\n",
"a_ok: \r bc: 2\r",
"a_mk: \n bd: 3\n",
2019-10-16 18:21:20 +09:00
}
for _, src := range sources {
2020-07-02 12:49:05 +09:00
if _, err := parser.Parse(lexer.Tokenize(src), 0); err != nil {
t.Fatalf("parse error: source [%s]: %+v", src, err)
2019-10-16 18:21:20 +09:00
}
}
}
func TestParseComplicatedDocument(t *testing.T) {
tests := []struct {
source string
expect string
}{
{
`
american:
- Boston Red Sox
- Detroit Tigers
- New York Yankees
national:
- New York Mets
- Chicago Cubs
- Atlanta Braves
`, `
american:
- Boston Red Sox
- Detroit Tigers
- New York Yankees
national:
- New York Mets
- Chicago Cubs
- Atlanta Braves
`,
},
{
`
a:
b: c
d: e
f: g
h:
i: j
k:
l: m
n: o
p: q
r: s
`, `
a:
b: c
d: e
f: g
h:
i: j
k:
l: m
n: o
p: q
r: s
`,
},
{
`
- a:
- b
- c
- d
`, `
- a:
- b
- c
- d
`,
},
{
`
- a
- b
- c
- d
- e
- f
`, `
- a
- b
- c - d - e
- f
`,
},
{
`
a: 0 - 1
`,
`
a: 0 - 1
2019-10-16 18:21:20 +09:00
`,
},
{`
- a:
b: c
d: e
- f:
g: h
`,
`
- a:
b: c
d: e
2020-06-03 17:57:26 +09:00
- f: null
2019-10-16 18:21:20 +09:00
g: h
`,
},
{
`
a:
b
c
d: e
`, `
a: b c
d: e
`,
},
{
`
a
b
c
`, `
a b c
`,
},
{
`
a:
- b
- c
`, `
a:
- b
- c
`,
},
{
`
- a :
b: c
`, `
2020-06-03 17:57:26 +09:00
- a: null
2019-10-16 18:21:20 +09:00
b: c
`,
},
{
`
- a:
b
c
d
hoge: fuga
`, `
- a: b c d
hoge: fuga
`,
},
{
`
- a # ' " # - : %
- b # " # - : % '
- c # # - : % ' "
- d # - : % ' " #
- e # : % ' " # -
- f # % ' : # - :
`,
`
- a
- b
- c
- d
- e
- f
`,
},
{
`
# comment
a: # comment
# comment
b: c # comment
# comment
d: e # comment
# comment
`,
`
a:
b: c
d: e
`,
},
{
`
a: b#notcomment
`,
`
a: b#notcomment
2019-10-16 18:21:20 +09:00
`,
},
{
`
anchored: &anchor foo
aliased: *anchor
`,
`
anchored: &anchor foo
aliased: *anchor
`,
},
{
`
---
- &CENTER { x: 1, y: 2 }
- &LEFT { x: 0, y: 2 }
- &BIG { r: 10 }
- &SMALL { r: 1 }
# All the following maps are equal:
- # Explicit keys
x: 1
y: 2
r: 10
label: center/big
- # Merge one map
<< : *CENTER
r: 10
label: center/big
- # Merge multiple maps
<< : [ *CENTER, *BIG ]
label: center/big
- # Override
<< : [ *BIG, *LEFT, *SMALL ]
x: 1
label: center/big
`,
`
2019-11-05 17:02:55 +09:00
---
2019-10-16 18:21:20 +09:00
- &CENTER {x: 1, y: 2}
- &LEFT {x: 0, y: 2}
- &BIG {r: 10}
- &SMALL {r: 1}
- x: 1
y: 2
r: 10
label: center/big
- <<: *CENTER
r: 10
label: center/big
- <<: [*CENTER, *BIG]
label: center/big
- <<: [*BIG, *LEFT, *SMALL]
x: 1
label: center/big
`,
},
{
`
a:
- - b
- - c
- d
`,
`
a:
- - b
- - c
- d
`,
},
{
`
a:
b:
c: d
e:
f: g
h: i
j: k
`,
`
a:
b:
c: d
e:
f: g
h: i
j: k
2019-11-05 17:02:55 +09:00
`,
},
{
`
---
a: 1
b: 2
...
---
c: 3
d: 4
...
`,
`
---
a: 1
b: 2
...
---
c: 3
d: 4
...
2019-11-07 23:45:39 +09:00
`,
},
{
`
a:
b: |
{
[ 1, 2 ]
}
c: d
`,
`
a:
b: |
{
[ 1, 2 ]
}
c: d
2019-11-08 16:48:54 +09:00
`,
},
{
`
|
hoge
fuga
piyo`,
`
|
hoge
fuga
piyo
`,
},
{
`
a: |
bbbbbbb
ccccccc
d: eeeeeeeeeeeeeeeee
`,
`
a: |
bbbbbbb
ccccccc
d: eeeeeeeeeeeeeeeee
`,
},
{
`
a: b
c
`,
`
a: b c
`,
},
{
`
a:
b: c
`,
`
a:
b: c
`,
},
{
`
a: b
c: d
`,
`
a: b
c: d
2020-03-07 12:03:06 +09:00
`,
},
{
`
- ab - cd
- ef - gh
`,
`
- ab - cd
- ef - gh
`,
},
{
`
- 0 - 1
- 2 - 3
`,
`
- 0 - 1 - 2 - 3
`,
},
{
`
a - b - c: value
`,
`
a - b - c: value
2020-03-07 20:17:54 +09:00
`,
},
{
`
a:
-
b: c
d: e
-
f: g
h: i
`,
`
a:
- b: c
d: e
- f: g
h: i
`,
},
{
`
a: |-
value
b: c
`,
`
a: |-
value
b: c
`,
},
{
`
a: |+
value
b: c
`,
`
a: |+
value
b: c
`,
},
{
`
- key1: val
key2:
(
foo
+
bar
)
`,
`
- key1: val
key2: ( foo + bar )
2022-12-02 03:56:31 +09:00
`,
},
{
`
"a": b
'c': d
"e": "f"
g: "h"
i: 'j'
`,
`
"a": b
'c': d
"e": "f"
g: "h"
i: 'j'
2019-10-16 18:21:20 +09:00
`,
},
}
2019-10-16 18:21:20 +09:00
for _, test := range tests {
t.Run(test.source, func(t *testing.T) {
tokens := lexer.Tokenize(test.source)
f, err := parser.Parse(tokens, 0)
if err != nil {
t.Fatalf("%+v", err)
}
var v Visitor
for _, doc := range f.Docs {
ast.Walk(&v, doc.Body)
}
expect := fmt.Sprintf("\n%+v", f)
if test.expect != expect {
tokens.Dump()
t.Fatalf("unexpected output: [%s] != [%s]", test.expect, expect)
}
})
2019-10-16 18:21:20 +09:00
}
}
2019-12-22 11:50:07 +09:00
func TestNewLineChar(t *testing.T) {
for _, f := range []string{
"lf.yml",
"cr.yml",
"crlf.yml",
} {
2019-12-22 12:08:45 +09:00
ast, err := parser.ParseFile(filepath.Join("testdata", f), 0)
2019-12-22 11:59:24 +09:00
if err != nil {
2019-12-22 11:50:07 +09:00
t.Fatalf("%+v", err)
}
actual := fmt.Sprintf("%v", ast)
2019-12-22 11:59:24 +09:00
expect := `a: "a"
b: 1
`
if expect != actual {
t.Fatal("unexpected result")
}
2019-12-22 11:50:07 +09:00
}
}
func TestSyntaxError(t *testing.T) {
tests := []struct {
source string
expect string
}{
{
`
a:
- b
c: d
e: f
g: h`,
`
[3:3] unexpected key name
2 | a:
> 3 | - b
4 | c: d
2020-07-02 12:49:05 +09:00
^
5 | e: f
6 | g: h`,
},
{
`
a
- b: c`,
`
[2:1] unexpected key name
> 2 | a
3 | - b: c
^
`,
},
{
`%YAML 1.1 {}`,
`
[1:2] unexpected directive value. document not started
> 1 | %YAML 1.1 {}
^
2021-03-01 17:58:17 +09:00
`,
},
{
`{invalid`,
`
[1:2] unexpected map
> 1 | {invalid
^
`,
},
{
`{ "key": "value" `,
`
[1:1] unterminated flow mapping
> 1 | { "key": "value"
^
`,
},
{
`
a:
- b: c
- `,
`
[4:1] empty sequence entry
2 | a:
3 | - b: c
> 4 | -
^
`,
},
}
for _, test := range tests {
t.Run(test.source, func(t *testing.T) {
_, err := parser.ParseBytes([]byte(test.source), 0)
if err == nil {
t.Fatal("cannot catch syntax error")
}
actual := "\n" + err.Error()
if test.expect != actual {
t.Fatalf("expected: [%s] but got [%s]", test.expect, actual)
}
})
}
}
2020-05-28 21:39:06 +09:00
func TestComment(t *testing.T) {
2020-05-29 11:49:32 +09:00
tests := []struct {
name string
yaml string
}{
{
name: "map with comment",
yaml: `
2020-05-28 21:39:06 +09:00
# commentA
a: #commentB
# commentC
b: c # commentD
# commentE
d: e # commentF
# commentG
f: g # commentH
# commentI
f: g # commentJ
# commentK
2020-05-29 11:49:32 +09:00
`,
},
{
name: "sequence with comment",
yaml: `
# commentA
- a # commentB
# commentC
- b: # commentD
# commentE
- d # commentF
- e # commentG
# commentH
`,
},
{
name: "anchor and alias",
yaml: `
a: &x b # commentA
c: *x # commentB
2021-07-16 21:49:17 +09:00
`,
},
{
name: "multiline",
yaml: `
# foo comment
# foo comment2
foo: # map key comment
# bar above comment
# bar above comment2
bar: 10 # comment for bar
# baz above comment
# baz above comment2
baz: bbbb # comment for baz
piyo: # sequence key comment
# sequence1 above comment 1
# sequence1 above comment 2
- sequence1 # sequence1
# sequence2 above comment 1
# sequence2 above comment 2
- sequence2 # sequence2
# sequence3 above comment 1
# sequence3 above comment 2
- false # sequence3
# foo2 comment
# foo2 comment2
foo2: &anchor text # anchor comment
# foo3 comment
# foo3 comment2
foo3: *anchor # alias comment
2021-07-19 18:48:09 +09:00
`,
},
{
name: "literal",
yaml: `
foo: | # comment
x: 42
`,
},
{
name: "folded",
yaml: `
foo: > # comment
x: 42
2020-05-29 11:49:32 +09:00
`,
},
2020-05-28 21:39:06 +09:00
}
2020-05-29 11:49:32 +09:00
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
f, err := parser.ParseBytes([]byte(test.yaml), parser.ParseComments)
if err != nil {
t.Fatalf("%+v", err)
}
got := "\n" + f.String()
2021-07-16 21:49:17 +09:00
if test.yaml != got {
t.Fatalf("expected:%s\ngot:%s", test.yaml, got)
2020-05-29 11:49:32 +09:00
}
})
2020-05-28 21:39:06 +09:00
}
}
func TestCommentWithNull(t *testing.T) {
t.Run("same line", func(t *testing.T) {
content := `
foo:
bar: # comment
baz: 1
`
expected := `
foo:
bar: null # comment
baz: 1`
f, err := parser.ParseBytes([]byte(content), parser.ParseComments)
if err != nil {
t.Fatal(err)
}
if len(f.Docs) != 1 {
t.Fatal("failed to parse content with same line comment")
}
if f.Docs[0].String() != strings.TrimPrefix(expected, "\n") {
t.Fatal("failed to parse comment")
}
})
t.Run("next line", func(t *testing.T) {
content := `
foo:
bar:
# comment
baz: 1
`
expected := `
foo:
bar: null # comment
baz: 1`
f, err := parser.ParseBytes([]byte(content), parser.ParseComments)
if err != nil {
t.Fatal(err)
}
if len(f.Docs) != 1 {
t.Fatal("failed to parse content with next line comment")
}
if f.Docs[0].String() != strings.TrimPrefix(expected, "\n") {
t.Fatal("failed to parse comment")
}
})
t.Run("next line and different indent", func(t *testing.T) {
content := `
foo:
bar:
# comment
baz: 1`
f, err := parser.ParseBytes([]byte(content), parser.ParseComments)
if err != nil {
t.Fatal(err)
}
if len(f.Docs) != 1 {
t.Fatal("failed to parse content with next line comment")
}
expected := `
foo:
bar: null # comment
baz: 1`
if f.Docs[0].String() != strings.TrimPrefix(expected, "\n") {
t.Fatal("failed to parse comment")
}
})
2023-09-15 12:11:17 +09:00
}
2023-09-15 12:11:17 +09:00
func TestSequenceComment(t *testing.T) {
content := `
foo:
- # comment
bar: 1
baz:
- xxx
`
f, err := parser.ParseBytes([]byte(content), parser.ParseComments)
if err != nil {
t.Fatal(err)
}
if len(f.Docs) != 1 {
t.Fatal("failed to parse content with next line with sequence")
}
expected := `
foo:
# comment
- bar: 1
baz:
- xxx`
if f.Docs[0].String() != strings.TrimPrefix(expected, "\n") {
t.Fatal("failed to parse comment")
}
t.Run("foo[0].bar", func(t *testing.T) {
path, err := yaml.PathString("$.foo[0].bar")
if err != nil {
t.Fatal(err)
}
v, err := path.FilterFile(f)
if err != nil {
t.Fatal(err)
}
if v.String() != "1" {
t.Fatal("failed to get foo[0].bar value")
}
})
t.Run("baz[0]", func(t *testing.T) {
path, err := yaml.PathString("$.baz[0]")
if err != nil {
t.Fatal(err)
}
v, err := path.FilterFile(f)
if err != nil {
t.Fatal(err)
}
if v.String() != "xxx" {
t.Fatal("failed to get baz[0] value")
}
})
}
2021-09-07 17:31:17 +09:00
func TestNodePath(t *testing.T) {
yml := `
a: # commentA
b: # commentB
c: foo # commentC
d: bar # commentD
e: baz # commentE
f: # commentF
g: hoge # commentG
h: # commentH
- list1 # comment list1
- list2 # comment list2
- list3 # comment list3
i: fuga # commentI
j: piyo # commentJ
k.l.m.n: moge # commentKLMN
o#p: hogera # commentOP
q#.r: hogehoge # commentQR
2021-09-07 17:31:17 +09:00
`
f, err := parser.ParseBytes([]byte(yml), parser.ParseComments)
if err != nil {
t.Fatalf("%+v", err)
}
var capturer pathCapturer
for _, doc := range f.Docs {
ast.Walk(&capturer, doc.Body)
}
commentPaths := []string{}
for i := 0; i < capturer.capturedNum; i++ {
if capturer.orderedTypes[i] == ast.CommentType {
commentPaths = append(commentPaths, capturer.orderedPaths[i])
}
}
expectedPaths := []string{
"$.a",
"$.a.b",
"$.a.b.c",
"$.a.b.d",
"$.a.b.e",
"$.a.f",
"$.a.f.g",
"$.a.h",
"$.a.h[0]",
"$.a.h[1]",
"$.a.h[2]",
"$.a.i",
"$.j",
"$.'k.l.m.n'",
"$.o#p",
"$.'q#.r'",
2021-09-07 17:31:17 +09:00
}
if !reflect.DeepEqual(expectedPaths, commentPaths) {
t.Fatalf("failed to get YAMLPath to the comment node:\nexpected[%s]\ngot [%s]", expectedPaths, commentPaths)
}
}
type pathCapturer struct {
capturedNum int
orderedPaths []string
orderedTypes []ast.NodeType
orderedTokens []*token.Token
}
func (c *pathCapturer) Visit(node ast.Node) ast.Visitor {
c.capturedNum++
c.orderedPaths = append(c.orderedPaths, node.GetPath())
c.orderedTypes = append(c.orderedTypes, node.Type())
c.orderedTokens = append(c.orderedTokens, node.GetToken())
return c
}
2019-10-16 18:21:20 +09:00
type Visitor struct {
}
func (v *Visitor) Visit(node ast.Node) ast.Visitor {
tk := node.GetToken()
tk.Prev = nil
tk.Next = nil
2019-10-16 18:21:20 +09:00
return v
}