6c4344623b
The main HCL package is more visible this way, and so it's easier than having to pick it out from dozens of other package directories.
336 lines
6.5 KiB
Go
336 lines
6.5 KiB
Go
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`, // The parser trims the parens off, so they aren't considered as part of the expression :(
|
|
},
|
|
"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)
|
|
}
|
|
})
|
|
}
|
|
}
|