package json import ( "math/big" "testing" "github.com/go-test/deep" "github.com/hashicorp/hcl/v2" ) func init() { deep.MaxDepth = 999 } func TestParse(t *testing.T) { tests := []struct { Input string Want node DiagCount int }{ // Simple, single-token constructs { `true`, &booleanVal{ Value: true, SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 5, Byte: 4}, }, }, 0, }, { `false`, &booleanVal{ Value: false, SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, }, }, 0, }, { `null`, &nullVal{ SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 5, Byte: 4}, }, }, 0, }, { `undefined`, invalidVal{hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 10, Byte: 9}, }}, 1, }, { `flase`, invalidVal{hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, }}, 1, }, { `"hello"`, &stringVal{ Value: "hello", SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 8, Byte: 7}, }, }, 0, }, { `"hello\nworld"`, &stringVal{ Value: "hello\nworld", SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 15, Byte: 14}, }, }, 0, }, { `"hello \"world\""`, &stringVal{ Value: `hello "world"`, SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 18, Byte: 17}, }, }, 0, }, { `"hello \\"`, &stringVal{ Value: "hello \\", SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 11, Byte: 10}, }, }, 0, }, { `"hello`, invalidVal{hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 7, Byte: 6}, }}, 1, }, { `"he\llo"`, invalidVal{hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 9, Byte: 8}, }}, 1, }, { `1`, &numberVal{ Value: mustBigFloat("1"), SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }, }, 0, }, { `1.2`, &numberVal{ Value: mustBigFloat("1.2"), SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 4, Byte: 3}, }, }, 0, }, { `-1`, &numberVal{ Value: mustBigFloat("-1"), SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 3, Byte: 2}, }, }, 0, }, { `1.2e5`, &numberVal{ Value: mustBigFloat("120000"), SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, }, }, 0, }, { `1.2e+5`, &numberVal{ Value: mustBigFloat("120000"), SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 7, Byte: 6}, }, }, 0, }, { `1.2e-5`, &numberVal{ Value: mustBigFloat("1.2e-5"), SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 7, Byte: 6}, }, }, 0, }, { `.1`, invalidVal{hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 3, Byte: 2}, }}, 1, }, { `+2`, invalidVal{hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 3, Byte: 2}, }}, 1, }, { `1 2`, &numberVal{ Value: mustBigFloat("1"), SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }, }, 1, }, // Objects { `{"hello": true}`, &objectVal{ Attrs: []*objectAttr{ { Name: "hello", Value: &booleanVal{ Value: true, SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 11, Byte: 10}, End: hcl.Pos{Line: 1, Column: 15, Byte: 14}, }, }, NameRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 2, Byte: 1}, End: hcl.Pos{Line: 1, Column: 9, Byte: 8}, }, }, }, SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, }, OpenRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }, CloseRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, }, }, 0, }, { `{"hello": true, "bye": false}`, &objectVal{ Attrs: []*objectAttr{ { Name: "hello", Value: &booleanVal{ Value: true, SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 11, Byte: 10}, End: hcl.Pos{Line: 1, Column: 15, Byte: 14}, }, }, NameRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 2, Byte: 1}, End: hcl.Pos{Line: 1, Column: 9, Byte: 8}, }, }, { Name: "bye", Value: &booleanVal{ Value: false, SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 24, Byte: 23}, End: hcl.Pos{Line: 1, Column: 29, Byte: 28}, }, }, NameRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 17, Byte: 16}, End: hcl.Pos{Line: 1, Column: 22, Byte: 21}, }, }, }, SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 30, Byte: 29}, }, OpenRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }, CloseRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 29, Byte: 28}, End: hcl.Pos{Line: 1, Column: 30, Byte: 29}, }, }, 0, }, { `{}`, &objectVal{ Attrs: []*objectAttr{}, SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 3, Byte: 2}, }, OpenRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }, CloseRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 2, Byte: 1}, End: hcl.Pos{Line: 1, Column: 3, Byte: 2}, }, }, 0, }, { `{"hello":true`, invalidVal{hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }}, 1, }, { `{"hello":true]`, invalidVal{hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }}, 1, }, { `{"hello":true,}`, invalidVal{hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }}, 1, }, { `{true:false}`, invalidVal{hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }}, 1, }, { `{"hello": true, "hello": true}`, &objectVal{ Attrs: []*objectAttr{ { Name: "hello", Value: &booleanVal{ Value: true, SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 11, Byte: 10}, End: hcl.Pos{Line: 1, Column: 15, Byte: 14}, }, }, NameRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 2, Byte: 1}, End: hcl.Pos{Line: 1, Column: 9, Byte: 8}, }, }, { Name: "hello", Value: &booleanVal{ Value: true, SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 26, Byte: 25}, End: hcl.Pos{Line: 1, Column: 30, Byte: 29}, }, }, NameRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 17, Byte: 16}, End: hcl.Pos{Line: 1, Column: 24, Byte: 23}, }, }, }, SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 31, Byte: 30}, }, OpenRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }, CloseRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 30, Byte: 29}, End: hcl.Pos{Line: 1, Column: 31, Byte: 30}, }, }, 0, }, { `{"hello": true, "hello": true, "hello", true}`, invalidVal{hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }}, 1, // comma used where colon is expected }, { `{"hello", "world"}`, invalidVal{hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }}, 1, }, { `[]`, &arrayVal{ Values: []node{}, SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 3, Byte: 2}, }, OpenRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }, }, 0, }, { `[true]`, &arrayVal{ Values: []node{ &booleanVal{ Value: true, SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 2, Byte: 1}, End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, }, }, }, SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 7, Byte: 6}, }, OpenRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }, }, 0, }, { `[true, false]`, &arrayVal{ Values: []node{ &booleanVal{ Value: true, SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 2, Byte: 1}, End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, }, }, &booleanVal{ Value: false, SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, End: hcl.Pos{Line: 1, Column: 13, Byte: 12}, }, }, }, SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 14, Byte: 13}, }, OpenRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }, }, 0, }, { `[[]]`, &arrayVal{ Values: []node{ &arrayVal{ Values: []node{}, SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 2, Byte: 1}, End: hcl.Pos{Line: 1, Column: 4, Byte: 3}, }, OpenRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 2, Byte: 1}, End: hcl.Pos{Line: 1, Column: 3, Byte: 2}, }, }, }, SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 5, Byte: 4}, }, OpenRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }, }, 0, }, { `[`, invalidVal{hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }}, 2, }, { `[true`, invalidVal{hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }}, 1, }, { `]`, invalidVal{hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }}, 1, }, { `[true,]`, invalidVal{hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }}, 1, }, { `[[],]`, invalidVal{hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }}, 1, }, { `["hello":true]`, invalidVal{hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }}, 1, }, { `[true}`, invalidVal{hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }}, 1, }, { `{"wrong"=true}`, invalidVal{hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }}, 1, }, { `{"wrong" = true}`, invalidVal{hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }}, 1, }, { `{"wrong" true}`, invalidVal{hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, }}, 1, }, } for _, test := range tests { t.Run(test.Input, func(t *testing.T) { got, diag := parseFileContent([]byte(test.Input), "", hcl.Pos{Byte: 0, Line: 1, Column: 1}) if len(diag) != test.DiagCount { t.Errorf("got %d diagnostics; want %d", len(diag), test.DiagCount) for _, d := range diag { t.Logf(" - %s", d.Error()) } } if diff := deep.Equal(got, test.Want); diff != nil { for _, problem := range diff { t.Error(problem) } } }) } } func TestParseWithPos(t *testing.T) { tests := []struct { Input string StartPos hcl.Pos Want node DiagCount int }{ // Simple, single-token constructs { `true`, hcl.Pos{Byte: 0, Line: 3, Column: 10}, &booleanVal{ Value: true, SrcRange: hcl.Range{ Start: hcl.Pos{Line: 3, Column: 10, Byte: 0}, End: hcl.Pos{Line: 3, Column: 14, Byte: 4}, }, }, 0, }, } for _, test := range tests { t.Run(test.Input, func(t *testing.T) { got, diag := parseFileContent([]byte(test.Input), "", test.StartPos) if len(diag) != test.DiagCount { t.Errorf("got %d diagnostics; want %d", len(diag), test.DiagCount) for _, d := range diag { t.Logf(" - %s", d.Error()) } } if diff := deep.Equal(got, test.Want); diff != nil { for _, problem := range diff { t.Error(problem) } } }) } } func mustBigFloat(s string) *big.Float { f, _, err := (&big.Float{}).Parse(s, 10) if err != nil { panic(err) } return f }