From 67424e43b1841759fe3f4f83013c58707d7027f0 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Mon, 26 Nov 2018 23:35:46 +0000 Subject: [PATCH] hcled: Introduce ContextDefRange (#58) This can be used for looking up range of block definition so that we can render it alongside attribute-agnostic errors --- hcl/hclsyntax/navigation.go | 18 +++++ hcl/hclsyntax/navigation_test.go | 133 +++++++++++++++++++++++++++++++ hcl/hclsyntax/structure.go | 4 + hcled/navigation.go | 14 ++++ 4 files changed, 169 insertions(+) create mode 100644 hcl/hclsyntax/navigation_test.go diff --git a/hcl/hclsyntax/navigation.go b/hcl/hclsyntax/navigation.go index 4d41b6b..c8c97f3 100644 --- a/hcl/hclsyntax/navigation.go +++ b/hcl/hclsyntax/navigation.go @@ -3,6 +3,8 @@ package hclsyntax import ( "bytes" "fmt" + + "github.com/hashicorp/hcl2/hcl" ) type navigation struct { @@ -39,3 +41,19 @@ func (n navigation) ContextString(offset int) string { } return buf.String() } + +func (n navigation) ContextDefRange(offset int) hcl.Range { + var block *Block + for _, candidate := range n.root.Blocks { + if candidate.Range().ContainsOffset(offset) { + block = candidate + break + } + } + + if block == nil { + return hcl.Range{} + } + + return block.DefRange() +} diff --git a/hcl/hclsyntax/navigation_test.go b/hcl/hclsyntax/navigation_test.go new file mode 100644 index 0000000..5d300b4 --- /dev/null +++ b/hcl/hclsyntax/navigation_test.go @@ -0,0 +1,133 @@ +package hclsyntax + +import ( + "fmt" + "strconv" + "testing" + + "github.com/hashicorp/hcl2/hcl" +) + +func TestNavigationContextString(t *testing.T) { + cfg := ` + + +resource { +} + +resource "random_type" { +} + +resource "null_resource" "baz" { + name = "foo" + boz = { + one = "111" + two = "22222" + } +} + +data "another" "baz" { + name = "foo" + boz = { + one = "111" + two = "22222" + } +} +` + file, diags := ParseConfig([]byte(cfg), "", hcl.Pos{Byte: 0, Line: 1, Column: 1}) + if len(diags) != 0 { + fmt.Printf("offset %d\n", diags[0].Subject.Start.Byte) + t.Errorf("Unexpected diagnostics: %s", diags) + } + if file == nil { + t.Fatalf("Got nil file") + } + nav := file.Nav.(navigation) + + testCases := []struct { + Offset int + Want string + }{ + {0, ``}, + {2, ``}, + {4, `resource`}, + {17, `resource "random_type"`}, + {25, `resource "random_type"`}, + {45, `resource "null_resource" "baz"`}, + {142, `data "another" "baz"`}, + {180, `data "another" "baz"`}, + {99999, ``}, + } + + for _, tc := range testCases { + t.Run(strconv.Itoa(tc.Offset), func(t *testing.T) { + got := nav.ContextString(tc.Offset) + + if got != tc.Want { + t.Errorf("wrong result\ngot: %s\nwant: %s", got, tc.Want) + } + }) + } +} + +func TestNavigationContextDefRange(t *testing.T) { + cfg := ` + + +resource { +} + +resource "random_type" { +} + +resource "null_resource" "baz" { + name = "foo" + boz = { + one = "111" + two = "22222" + } +} + +data "another" "baz" { + name = "foo" + boz = { + one = "111" + two = "22222" + } +} +` + file, diags := ParseConfig([]byte(cfg), "", hcl.Pos{Byte: 0, Line: 1, Column: 1}) + if len(diags) != 0 { + fmt.Printf("offset %d\n", diags[0].Subject.Start.Byte) + t.Errorf("Unexpected diagnostics: %s", diags) + } + if file == nil { + t.Fatalf("Got nil file") + } + nav := file.Nav.(navigation) + + testCases := []struct { + Offset int + WantRange hcl.Range + }{ + {0, hcl.Range{}}, + {2, hcl.Range{}}, + {4, hcl.Range{Filename: "", Start: hcl.Pos{Line: 4, Column: 1, Byte: 3}, End: hcl.Pos{Line: 4, Column: 11, Byte: 13}}}, + {17, hcl.Range{Filename: "", Start: hcl.Pos{Line: 7, Column: 1, Byte: 17}, End: hcl.Pos{Line: 7, Column: 25, Byte: 41}}}, + {25, hcl.Range{Filename: "", Start: hcl.Pos{Line: 7, Column: 1, Byte: 17}, End: hcl.Pos{Line: 7, Column: 25, Byte: 41}}}, + {45, hcl.Range{Filename: "", Start: hcl.Pos{Line: 10, Column: 1, Byte: 45}, End: hcl.Pos{Line: 10, Column: 33, Byte: 77}}}, + {142, hcl.Range{Filename: "", Start: hcl.Pos{Line: 18, Column: 1, Byte: 142}, End: hcl.Pos{Line: 18, Column: 23, Byte: 164}}}, + {180, hcl.Range{Filename: "", Start: hcl.Pos{Line: 18, Column: 1, Byte: 142}, End: hcl.Pos{Line: 18, Column: 23, Byte: 164}}}, + {99999, hcl.Range{}}, + } + + for _, tc := range testCases { + t.Run(strconv.Itoa(tc.Offset), func(t *testing.T) { + got := nav.ContextDefRange(tc.Offset) + + if got != tc.WantRange { + t.Errorf("wrong range\ngot: %#v\nwant: %#v", got, tc.WantRange) + } + }) + } +} diff --git a/hcl/hclsyntax/structure.go b/hcl/hclsyntax/structure.go index 3b941c9..ebafcb4 100644 --- a/hcl/hclsyntax/structure.go +++ b/hcl/hclsyntax/structure.go @@ -384,3 +384,7 @@ func (b *Block) walkChildNodes(w internalWalkFunc) { func (b *Block) Range() hcl.Range { return hcl.RangeBetween(b.TypeRange, b.CloseBraceRange) } + +func (b *Block) DefRange() hcl.Range { + return hcl.RangeBetween(b.TypeRange, b.OpenBraceRange) +} diff --git a/hcled/navigation.go b/hcled/navigation.go index b63a3f4..5d10cd8 100644 --- a/hcled/navigation.go +++ b/hcled/navigation.go @@ -18,3 +18,17 @@ func ContextString(file *hcl.File, offset int) string { } return "" } + +type contextDefRanger interface { + ContextDefRange(offset int) hcl.Range +} + +func ContextDefRange(file *hcl.File, offset int) hcl.Range { + if cser, ok := file.Nav.(contextDefRanger); ok { + defRange := cser.ContextDefRange(offset) + if !defRange.Empty() { + return defRange + } + } + return file.Body.MissingItemRange() +}