blob: ead77bfb34dc70b475842fec5ddd55402eec3ebf [file] [log] [blame] [edit]
package hclsyntax
import (
"reflect"
"testing"
"github.com/hashicorp/hcl/v2"
)
func TestBlocksAtPos(t *testing.T) {
tests := map[string]struct {
Src string
Pos hcl.Pos
WantTypes []string
}{
"empty": {
``,
hcl.Pos{Byte: 0},
nil,
},
"spaces": {
` `,
hcl.Pos{Byte: 1},
nil,
},
"single in header": {
`foo {}`,
hcl.Pos{Byte: 1},
[]string{"foo"},
},
"single in body": {
`foo { }`,
hcl.Pos{Byte: 7},
[]string{"foo"},
},
"single in body with unselected nested": {
`
foo {
bar {
}
}
`,
hcl.Pos{Byte: 10},
[]string{"foo"},
},
"single in body with unselected sibling": {
`
foo { }
bar { }
`,
hcl.Pos{Byte: 10},
[]string{"foo"},
},
"selected nested two levels": {
`
foo {
bar {
}
}
`,
hcl.Pos{Byte: 20},
[]string{"foo", "bar"},
},
"selected nested three levels": {
`
foo {
bar {
baz {
}
}
}
`,
hcl.Pos{Byte: 31},
[]string{"foo", "bar", "baz"},
},
"selected nested three levels with unselected sibling after": {
`
foo {
bar {
baz {
}
}
not_wanted {}
}
`,
hcl.Pos{Byte: 31},
[]string{"foo", "bar", "baz"},
},
"selected nested three levels with unselected sibling before": {
`
foo {
not_wanted {}
bar {
baz {
}
}
}
`,
hcl.Pos{Byte: 49},
[]string{"foo", "bar", "baz"},
},
"unterminated": {
`foo { `,
hcl.Pos{Byte: 7},
[]string{"foo"},
},
"unterminated nested": {
`
foo {
bar {
}
`,
hcl.Pos{Byte: 16},
[]string{"foo", "bar"},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
f, diags := ParseConfig([]byte(test.Src), "", hcl.Pos{Line: 1, Column: 1})
for _, diag := range diags {
// We intentionally ignore diagnostics here because we should be
// able to work with the incomplete configuration that results
// when the parser does its recovery behavior. However, we do
// log them in case it's helpful to someone debugging a failing
// test.
t.Logf(diag.Error())
}
blocks := f.BlocksAtPos(test.Pos)
outermost := f.OutermostBlockAtPos(test.Pos)
innermost := f.InnermostBlockAtPos(test.Pos)
gotTypes := make([]string, len(blocks))
for i, block := range blocks {
gotTypes[i] = block.Type
}
if len(test.WantTypes) == 0 {
if len(gotTypes) != 0 {
t.Errorf("wrong block types\ngot: %#v\nwant: (none)", gotTypes)
}
if outermost != nil {
t.Errorf("wrong outermost type\ngot: %#v\nwant: (none)", outermost.Type)
}
if innermost != nil {
t.Errorf("wrong innermost type\ngot: %#v\nwant: (none)", innermost.Type)
}
return
}
if !reflect.DeepEqual(gotTypes, test.WantTypes) {
if len(gotTypes) != 0 {
t.Errorf("wrong block types\ngot: %#v\nwant: %#v", gotTypes, test.WantTypes)
}
}
if got, want := outermost.Type, test.WantTypes[0]; got != want {
t.Errorf("wrong outermost type\ngot: %#v\nwant: %#v", got, want)
}
if got, want := innermost.Type, test.WantTypes[len(test.WantTypes)-1]; got != want {
t.Errorf("wrong innermost type\ngot: %#v\nwant: %#v", got, want)
}
})
}
}
func TestAttributeAtPos(t *testing.T) {
tests := map[string]struct {
Src string
Pos hcl.Pos
WantName string
}{
"empty": {
``,
hcl.Pos{Byte: 0},
"",
},
"top-level": {
`foo = 1`,
hcl.Pos{Byte: 0},
"foo",
},
"top-level with ignored sibling after": {
`
foo = 1
bar = 2
`,
hcl.Pos{Byte: 6},
"foo",
},
"top-level ignored sibling before": {
`
foo = 1
bar = 2
`,
hcl.Pos{Byte: 17},
"bar",
},
"nested": {
`
foo {
bar = 2
}
`,
hcl.Pos{Byte: 17},
"bar",
},
"nested in unterminated block": {
`
foo {
bar = 2
`,
hcl.Pos{Byte: 17},
"bar",
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
f, diags := ParseConfig([]byte(test.Src), "", hcl.Pos{Line: 1, Column: 1})
for _, diag := range diags {
// We intentionally ignore diagnostics here because we should be
// able to work with the incomplete configuration that results
// when the parser does its recovery behavior. However, we do
// log them in case it's helpful to someone debugging a failing
// test.
t.Logf(diag.Error())
}
got := f.AttributeAtPos(test.Pos)
if test.WantName == "" {
if got != nil {
t.Errorf("wrong attribute name\ngot: %#v\nwant: (none)", got.Name)
}
return
}
if got == nil {
t.Fatalf("wrong attribute name\ngot: (none)\nwant: %#v", test.WantName)
}
if got.Name != test.WantName {
t.Errorf("wrong attribute name\ngot: %#v\nwant: %#v", got.Name, test.WantName)
}
})
}
}
func TestOutermostExprAtPos(t *testing.T) {
tests := map[string]struct {
Src string
Pos hcl.Pos
WantSrc string
}{
"empty": {
``,
hcl.Pos{Byte: 0},
``,
},
"simple bool": {
`a = true`,
hcl.Pos{Byte: 6},
`true`,
},
"simple reference": {
`a = blah`,
hcl.Pos{Byte: 6},
`blah`,
},
"attribute reference": {
`a = blah.foo`,
hcl.Pos{Byte: 6},
`blah.foo`,
},
"parens": {
`a = (1 + 1)`,
hcl.Pos{Byte: 6},
`(1 + 1)`,
},
"tuple cons": {
`a = [1, 2, 3]`,
hcl.Pos{Byte: 5},
`[1, 2, 3]`,
},
"function call": {
`a = foom("a")`,
hcl.Pos{Byte: 10},
`foom("a")`,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
inputSrc := []byte(test.Src)
f, diags := ParseConfig(inputSrc, "", hcl.Pos{Line: 1, Column: 1})
for _, diag := range diags {
// We intentionally ignore diagnostics here because we should be
// able to work with the incomplete configuration that results
// when the parser does its recovery behavior. However, we do
// log them in case it's helpful to someone debugging a failing
// test.
t.Logf(diag.Error())
}
gotExpr := f.OutermostExprAtPos(test.Pos)
var gotSrc string
if gotExpr != nil {
rng := gotExpr.Range()
gotSrc = string(rng.SliceBytes(inputSrc))
}
if test.WantSrc == "" {
if gotExpr != nil {
t.Errorf("wrong expression source\ngot: %s\nwant: (none)", gotSrc)
}
return
}
if gotExpr == nil {
t.Fatalf("wrong expression source\ngot: (none)\nwant: %s", test.WantSrc)
}
if gotSrc != test.WantSrc {
t.Errorf("wrong expression source\ngot: %#v\nwant: %#v", gotSrc, test.WantSrc)
}
})
}
}