From 63e2897c12da1bfb68eb9c5771586d3adf1694c7 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Thu, 5 Dec 2019 17:03:57 -0800 Subject: [PATCH] hclsyntax: Source range of IndexExpr must cover whole expression Some HCL callers make the (reasonable) assumption that the overall source range of an expression will be a superset of all of the ranges of its child expressions, for purposes such as extraction of source code snippets, parse tree annotation in hclwrite, text editor analysis functions like "go to reference", etc. The IndexExpr type was not previously honoring that assumption, since its source range was placed around only the bracket portion. That is a good region to use when reporting errors relating to the index operation, but it is not a faithful representation of the full extent of the expression. In order to meet both of these requirements at once, IndexExpr now has both SrcRange covering the entire expression and BracketRange covering the index part delimited by brackets. We can then use BracketRange in our error messages but return SrcRange as the result of the general Range method that is common to all expression types. --- hclsyntax/expression.go | 7 +- hclsyntax/parser.go | 11 +-- hclsyntax/parser_test.go | 98 +++++++++++++++++++++++++- hclwrite/parser_test.go | 144 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 250 insertions(+), 10 deletions(-) diff --git a/hclsyntax/expression.go b/hclsyntax/expression.go index 963ed77..0821ce3 100644 --- a/hclsyntax/expression.go +++ b/hclsyntax/expression.go @@ -615,8 +615,9 @@ type IndexExpr struct { Collection Expression Key Expression - SrcRange hcl.Range - OpenRange hcl.Range + SrcRange hcl.Range + OpenRange hcl.Range + BracketRange hcl.Range } func (e *IndexExpr) walkChildNodes(w internalWalkFunc) { @@ -631,7 +632,7 @@ func (e *IndexExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { diags = append(diags, collDiags...) diags = append(diags, keyDiags...) - val, indexDiags := hcl.Index(coll, key, &e.SrcRange) + val, indexDiags := hcl.Index(coll, key, &e.BracketRange) setDiagEvalContext(indexDiags, e, ctx) diags = append(diags, indexDiags...) return val, diags diff --git a/hclsyntax/parser.go b/hclsyntax/parser.go index 6fb284a..f67d989 100644 --- a/hclsyntax/parser.go +++ b/hclsyntax/parser.go @@ -760,7 +760,7 @@ Traversal: Each: travExpr, Item: itemExpr, - SrcRange: hcl.RangeBetween(dot.Range, lastRange), + SrcRange: hcl.RangeBetween(from.Range(), lastRange), MarkerRange: hcl.RangeBetween(dot.Range, marker.Range), } @@ -819,7 +819,7 @@ Traversal: Each: travExpr, Item: itemExpr, - SrcRange: hcl.RangeBetween(open.Range, travExpr.Range()), + SrcRange: hcl.RangeBetween(from.Range(), travExpr.Range()), MarkerRange: hcl.RangeBetween(open.Range, close.Range), } @@ -867,8 +867,9 @@ Traversal: Collection: ret, Key: keyExpr, - SrcRange: rng, - OpenRange: open.Range, + SrcRange: hcl.RangeBetween(from.Range(), rng), + OpenRange: open.Range, + BracketRange: rng, } } } @@ -899,7 +900,7 @@ func makeRelativeTraversal(expr Expression, next hcl.Traverser, rng hcl.Range) E return &RelativeTraversalExpr{ Source: expr, Traversal: hcl.Traversal{next}, - SrcRange: rng, + SrcRange: hcl.RangeBetween(expr.Range(), rng), } } } diff --git a/hclsyntax/parser_test.go b/hclsyntax/parser_test.go index 3df29b7..45b5645 100644 --- a/hclsyntax/parser_test.go +++ b/hclsyntax/parser_test.go @@ -1834,13 +1834,17 @@ block "valid" {} }, }, SrcRange: hcl.Range{ - Start: hcl.Pos{Line: 1, Column: 26, Byte: 25}, + Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, End: hcl.Pos{Line: 1, Column: 39, Byte: 38}, }, OpenRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 26, Byte: 25}, End: hcl.Pos{Line: 1, Column: 27, Byte: 26}, }, + BracketRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 26, Byte: 25}, + End: hcl.Pos{Line: 1, Column: 39, Byte: 38}, + }, }, SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 5, Byte: 4}, @@ -1872,6 +1876,91 @@ block "valid" {} }, }, }, + { + "a = \"${var.public_subnets[*]}\"\n", + 0, + &Body{ + Attributes: Attributes{ + "a": { + Name: "a", + Expr: &TemplateWrapExpr{ + Wrapped: &SplatExpr{ + Source: &ScopeTraversalExpr{ + Traversal: hcl.Traversal{ + hcl.TraverseRoot{ + Name: "var", + + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + End: hcl.Pos{Line: 1, Column: 11, Byte: 10}, + }, + }, + hcl.TraverseAttr{ + Name: "public_subnets", + + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 11, Byte: 10}, + End: hcl.Pos{Line: 1, Column: 26, Byte: 25}, + }, + }, + }, + + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + End: hcl.Pos{Line: 1, Column: 26, Byte: 25}, + }, + }, + Each: &AnonSymbolExpr{ + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 26, Byte: 25}, + End: hcl.Pos{Line: 1, Column: 29, Byte: 28}, + }, + }, + Item: &AnonSymbolExpr{ + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 26, Byte: 25}, + End: hcl.Pos{Line: 1, Column: 29, Byte: 28}, + }, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + End: hcl.Pos{Line: 1, Column: 29, Byte: 28}, + }, + MarkerRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 26, Byte: 25}, + End: hcl.Pos{Line: 1, Column: 29, Byte: 28}, + }, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 5, Byte: 4}, + End: hcl.Pos{Line: 1, Column: 31, Byte: 30}, + }, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 31, Byte: 30}, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, + }, + EqualsRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 3, Byte: 2}, + End: hcl.Pos{Line: 1, Column: 4, Byte: 3}, + }, + }, + }, + Blocks: Blocks{}, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 2, Column: 1, Byte: 31}, + }, + EndRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 1, Byte: 31}, + End: hcl.Pos{Line: 2, Column: 1, Byte: 31}, + }, + }, + }, { "a = 1 # line comment\n", 0, @@ -2353,7 +2442,7 @@ block "valid" {} }, SrcRange: hcl.Range{ Filename: "", - Start: hcl.Pos{Line: 1, Column: 30, Byte: 29}, + Start: hcl.Pos{Line: 1, Column: 5, Byte: 4}, End: hcl.Pos{Line: 1, Column: 43, Byte: 42}, }, OpenRange: hcl.Range{ @@ -2361,6 +2450,11 @@ block "valid" {} Start: hcl.Pos{Line: 1, Column: 30, Byte: 29}, End: hcl.Pos{Line: 1, Column: 31, Byte: 30}, }, + BracketRange: hcl.Range{ + Filename: "", + Start: hcl.Pos{Line: 1, Column: 30, Byte: 29}, + End: hcl.Pos{Line: 1, Column: 43, Byte: 42}, + }, }, SrcRange: hcl.Range{ Filename: "", diff --git a/hclwrite/parser_test.go b/hclwrite/parser_test.go index 8ac82f5..28b5380 100644 --- a/hclwrite/parser_test.go +++ b/hclwrite/parser_test.go @@ -555,6 +555,150 @@ func TestParse(t *testing.T) { }, }, }, + { + "a = foo[bar]\n", + TestTreeNode{ + Type: "Body", + Children: []TestTreeNode{ + { + Type: "Attribute", + Children: []TestTreeNode{ + { + Type: "comments", + }, + { + Type: "identifier", + Val: "a", + }, + { + Type: "Tokens", + Val: " =", + }, + { + Type: "Expression", + Children: []TestTreeNode{ + { + Type: "Traversal", + Children: []TestTreeNode{ + { + Type: "TraverseName", + Children: []TestTreeNode{ + { + Type: "identifier", + Val: " foo", + }, + }, + }, + }, + }, + { + Type: "Tokens", + Val: "[", + }, + { + Type: "Traversal", + Children: []TestTreeNode{ + { + Type: "TraverseName", + Children: []TestTreeNode{ + { + Type: "identifier", + Val: "bar", + }, + }, + }, + }, + }, + { + Type: "Tokens", + Val: "]", + }, + }, + }, + { + Type: "comments", + }, + { + Type: "Tokens", + Val: "\n", + }, + }, + }, + }, + }, + }, + { + "a = foo[bar].baz\n", + TestTreeNode{ + Type: "Body", + Children: []TestTreeNode{ + { + Type: "Attribute", + Children: []TestTreeNode{ + { + Type: "comments", + }, + { + Type: "identifier", + Val: "a", + }, + { + Type: "Tokens", + Val: " =", + }, + { + Type: "Expression", + Children: []TestTreeNode{ + { + Type: "Traversal", + Children: []TestTreeNode{ + { + Type: "TraverseName", + Children: []TestTreeNode{ + { + Type: "identifier", + Val: " foo", + }, + }, + }, + }, + }, + { + Type: "Tokens", + Val: "[", + }, + { + Type: "Traversal", + Children: []TestTreeNode{ + { + Type: "TraverseName", + Children: []TestTreeNode{ + { + Type: "identifier", + Val: "bar", + }, + }, + }, + }, + }, + { + Type: "Tokens", + Val: "].baz", + }, + }, + }, + { + Type: "comments", + }, + { + Type: "Tokens", + Val: "\n", + }, + }, + }, + }, + }, + }, } for _, test := range tests {