| 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) |
| } |
| }) |
| } |
| } |