Skip to content

Commit

Permalink
hclsyntax: Source range of IndexExpr must cover whole expression
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
apparentlymart committed Dec 6, 2019
1 parent af72151 commit 63e2897
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 10 deletions.
7 changes: 4 additions & 3 deletions hclsyntax/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
Expand Down
11 changes: 6 additions & 5 deletions hclsyntax/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}

Expand Down Expand Up @@ -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),
}

Expand Down Expand Up @@ -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,
}
}
}
Expand Down Expand Up @@ -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),
}
}
}
Expand Down
98 changes: 96 additions & 2 deletions hclsyntax/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -2353,14 +2442,19 @@ 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{
Filename: "",
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: "",
Expand Down
144 changes: 144 additions & 0 deletions hclwrite/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 63e2897

Please sign in to comment.