2018-07-28 20:17:51 +00:00
|
|
|
package hclsyntax
|
|
|
|
|
|
|
|
import (
|
|
|
|
"reflect"
|
|
|
|
"testing"
|
|
|
|
|
2019-09-09 23:08:19 +00:00
|
|
|
"github.com/hashicorp/hcl/v2"
|
2018-07-28 20:17:51 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|