hcl/json: Detect variable references in property names

When a JSON object is representing an expression, template sequences are
permitted in the property names as well as the values. We must detect
the references here so that applications that do dynamic scope
construction or dependency analysis will get the right result.
This commit is contained in:
Martin Atkins 2018-12-12 16:49:01 -08:00
parent 47d7fce5a6
commit ebe27107e1
2 changed files with 95 additions and 1 deletions

View File

@ -424,7 +424,7 @@ func (e *expression) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
known := true
for _, jsonAttr := range v.Attrs {
// In this one context we allow keys to contain interpolation
// experessions too, assuming we're evaluating in interpolation
// expressions too, assuming we're evaluating in interpolation
// mode. This achieves parity with the native syntax where
// object expressions can have dynamic keys, while block contents
// may not.
@ -533,6 +533,11 @@ func (e *expression) Variables() []hcl.Traversal {
}
case *objectVal:
for _, jsonAttr := range v.Attrs {
keyExpr := &stringVal{ // we're going to treat key as an expression in this context
Value: jsonAttr.Name,
SrcRange: jsonAttr.NameRange,
}
vars = append(vars, (&expression{src: keyExpr}).Variables()...)
vars = append(vars, (&expression{src: jsonAttr.Value}).Variables()...)
}
}

View File

@ -1190,6 +1190,95 @@ func TestJustAttributes(t *testing.T) {
}
}
func TestExpressionVariables(t *testing.T) {
tests := []struct {
Src string
Want []hcl.Traversal
}{
{
`{"a":true}`,
nil,
},
{
`{"a":"${foo}"}`,
[]hcl.Traversal{
{
hcl.TraverseRoot{
Name: "foo",
SrcRange: hcl.Range{
Filename: "test.json",
Start: hcl.Pos{Line: 1, Column: 9, Byte: 8},
End: hcl.Pos{Line: 1, Column: 12, Byte: 11},
},
},
},
},
},
{
`{"a":["${foo}"]}`,
[]hcl.Traversal{
{
hcl.TraverseRoot{
Name: "foo",
SrcRange: hcl.Range{
Filename: "test.json",
Start: hcl.Pos{Line: 1, Column: 10, Byte: 9},
End: hcl.Pos{Line: 1, Column: 13, Byte: 12},
},
},
},
},
},
{
`{"a":{"b":"${foo}"}}`,
[]hcl.Traversal{
{
hcl.TraverseRoot{
Name: "foo",
SrcRange: hcl.Range{
Filename: "test.json",
Start: hcl.Pos{Line: 1, Column: 14, Byte: 13},
End: hcl.Pos{Line: 1, Column: 17, Byte: 16},
},
},
},
},
},
{
`{"a":{"${foo}":"b"}}`,
[]hcl.Traversal{
{
hcl.TraverseRoot{
Name: "foo",
SrcRange: hcl.Range{
Filename: "test.json",
Start: hcl.Pos{Line: 1, Column: 10, Byte: 9},
End: hcl.Pos{Line: 1, Column: 13, Byte: 12},
},
},
},
},
},
}
for _, test := range tests {
t.Run(test.Src, func(t *testing.T) {
file, diags := Parse([]byte(test.Src), "test.json")
if len(diags) != 0 {
t.Fatalf("Parse produced diagnostics: %s", diags)
}
attrs, diags := file.Body.JustAttributes()
if len(diags) != 0 {
t.Fatalf("JustAttributes produced diagnostics: %s", diags)
}
got := attrs["a"].Expr.Variables()
if !reflect.DeepEqual(got, test.Want) {
t.Errorf("wrong result\ngot: %s\nwant: %s", spew.Sdump(got), spew.Sdump(test.Want))
}
})
}
}
func TestExpressionAsTraversal(t *testing.T) {
e := &expression{
src: &stringVal{