diff options
| author | Martin Atkins <mart@degeneration.co.uk> | 2019-09-09 16:08:19 -0700 |
|---|---|---|
| committer | Martin Atkins <mart@degeneration.co.uk> | 2019-09-09 16:08:19 -0700 |
| commit | 6c4344623b6ac528a57f9b80e4622acfab2fde40 (patch) | |
| tree | 83031084d3ab54abbe40e3fd749e439c8b28e9d1 /hclsyntax/structure_at_pos_test.go | |
| parent | 0f5ab3bd563c111077917020c0cbc8c211e1bff3 (diff) | |
Unfold the "hcl" directory up into the root
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.
Diffstat (limited to 'hclsyntax/structure_at_pos_test.go')
| -rw-r--r-- | hclsyntax/structure_at_pos_test.go | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/hclsyntax/structure_at_pos_test.go b/hclsyntax/structure_at_pos_test.go new file mode 100644 index 0000000..fb87274 --- /dev/null +++ b/hclsyntax/structure_at_pos_test.go @@ -0,0 +1,335 @@ +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) + } + }) + } +} |
