diff --git a/hclsyntax/expression_test.go b/hclsyntax/expression_test.go index 8a15d115..05db38d4 100644 --- a/hclsyntax/expression_test.go +++ b/hclsyntax/expression_test.go @@ -1,8 +1,10 @@ package hclsyntax import ( + "fmt" "testing" + "github.com/google/go-cmp/cmp" "github.com/hashicorp/hcl/v2" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/function" @@ -2269,3 +2271,77 @@ func TestStaticExpressionList(t *testing.T) { t.Fatalf("wrong first value %#v; want cty.Zero", first.Val) } } + +// Check that function call w/ incomplete argument still reports correct range +func TestParseExpression_incompleteFunctionCall(t *testing.T) { + tests := []struct { + cfg string + expectedRange hcl.Range + }{ + { + `object({ foo = })`, + hcl.Range{ + Filename: "test.hcl", + Start: hcl.InitialPos, + End: hcl.Pos{Line: 1, Column: 18, Byte: 17}, + }, + }, + { + `object({ + foo = +})`, + hcl.Range{ + Filename: "test.hcl", + Start: hcl.InitialPos, + End: hcl.Pos{Line: 3, Column: 3, Byte: 19}, + }, + }, + { + `object({ foo = }`, + hcl.Range{ + Filename: "test.hcl", + Start: hcl.InitialPos, + End: hcl.Pos{Line: 0, Column: 0, Byte: 0}, + }, + }, + { + `object({ + foo = +}`, + hcl.Range{ + Filename: "test.hcl", + Start: hcl.InitialPos, + End: hcl.Pos{Line: 0, Column: 0, Byte: 0}, + }, + }, + { + `object({ + foo = +`, + hcl.Range{ + Filename: "test.hcl", + Start: hcl.InitialPos, + End: hcl.Pos{Line: 0, Column: 0, Byte: 0}, + }, + }, + { + `object({ + foo = +)`, + hcl.Range{ + Filename: "test.hcl", + Start: hcl.InitialPos, + End: hcl.Pos{Line: 0, Column: 0, Byte: 0}, + }, + }, + } + + for i, tc := range tests { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + expr, _ := ParseExpression([]byte(tc.cfg), "test.hcl", hcl.InitialPos) + if diff := cmp.Diff(tc.expectedRange, expr.Range()); diff != "" { + t.Fatalf("range mismatch: %s", diff) + } + }) + } +} diff --git a/hclsyntax/parser.go b/hclsyntax/parser.go index ec83d3dc..109c1c81 100644 --- a/hclsyntax/parser.go +++ b/hclsyntax/parser.go @@ -1174,7 +1174,12 @@ Token: // if there was a parse error in the argument then we've // probably been left in a weird place in the token stream, // so we'll bail out with a partial argument list. - p.recover(TokenCParen) + recoveredTok := p.recover(TokenCParen) + + // record the recovered token, if one was found + if recoveredTok.Type == TokenCParen { + closeTok = recoveredTok + } break Token } diff --git a/hclsyntax/parser_test.go b/hclsyntax/parser_test.go index 2215d0d5..7e2484a1 100644 --- a/hclsyntax/parser_test.go +++ b/hclsyntax/parser_test.go @@ -1,9 +1,12 @@ package hclsyntax import ( + "fmt" "testing" "github.com/go-test/deep" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/hashicorp/hcl/v2" "github.com/zclconf/go-cty/cty" ) @@ -2480,6 +2483,1055 @@ block "valid" {} } } +func TestParseConfig_incompleteFunctionCall(t *testing.T) { + tests := []struct { + input string + want *Body + }{ + { + `attr = object({ foo = }) +attr2 = "foo" +`, + &Body{ + Attributes: Attributes{ + "attr": { + Name: "attr", + Expr: &FunctionCallExpr{ + Name: "object", + Args: []Expression{ + &ObjectConsExpr{ + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 1, Column: 24, Byte: 23}, + }, + OpenRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, + }, + }, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + End: hcl.Pos{Line: 1, Column: 14, Byte: 13}, + }, + OpenParenRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 14, Byte: 13}, + End: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + }, + CloseParenRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 24, Byte: 23}, + End: hcl.Pos{Line: 1, Column: 25, Byte: 24}, + }, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 25, Byte: 24}, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 5, Byte: 4}, + }, + EqualsRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, + End: hcl.Pos{Line: 1, Column: 7, Byte: 6}, + }, + }, + "attr2": { + Name: "attr2", + Expr: &TemplateExpr{ + Parts: []Expression{ + &LiteralValueExpr{ + Val: cty.StringVal("foo"), + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 10, Byte: 34}, + End: hcl.Pos{Line: 2, Column: 13, Byte: 37}, + }, + }, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 9, Byte: 33}, + End: hcl.Pos{Line: 2, Column: 14, Byte: 38}, + }, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 1, Byte: 25}, + End: hcl.Pos{Line: 2, Column: 14, Byte: 38}, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 1, Byte: 25}, + End: hcl.Pos{Line: 2, Column: 6, Byte: 30}, + }, + EqualsRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 7, Byte: 31}, + End: hcl.Pos{Line: 2, Column: 8, Byte: 32}, + }, + }, + }, + Blocks: Blocks{}, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 39}, + }, + EndRange: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 1, Byte: 39}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 39}, + }, + }, + }, + { + `block "label" { + attr = object( +`, + &Body{ + Attributes: Attributes{}, + Blocks: Blocks{ + { + Type: "block", + Labels: []string{"label"}, + Body: &Body{ + Attributes: Attributes{ + "attr": { + Name: "attr", + Expr: &FunctionCallExpr{ + Name: "object", + Args: []Expression{ + &LiteralValueExpr{ + Val: cty.DynamicVal, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 1, Byte: 33}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 33}, + }, + }, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 10, Byte: 25}, + End: hcl.Pos{Line: 2, Column: 16, Byte: 31}, + }, + OpenParenRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 16, Byte: 31}, + End: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + }, + CloseParenRange: hcl.Range{}, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 3, Byte: 18}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 33}, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 3, Byte: 18}, + End: hcl.Pos{Line: 2, Column: 7, Byte: 22}, + }, + EqualsRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 8, Byte: 23}, + End: hcl.Pos{Line: 2, Column: 9, Byte: 24}, + }, + }, + }, + Blocks: Blocks{}, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 33}, + }, + EndRange: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 1, Byte: 33}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 33}, + }, + }, + TypeRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, + }, + LabelRanges: []hcl.Range{ + { + Start: hcl.Pos{Line: 1, Column: 7, Byte: 6}, + End: hcl.Pos{Line: 1, Column: 14, Byte: 13}, + }, + }, + OpenBraceRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, + }, + CloseBraceRange: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 1, Byte: 33}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 33}, + }, + }, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 33}, + }, + EndRange: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 1, Byte: 33}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 33}, + }, + }, + }, + { + `block "label" { + attr = object({ +`, + &Body{ + Attributes: Attributes{}, + Blocks: Blocks{ + { + Type: "block", + Labels: []string{"label"}, + Body: &Body{ + Attributes: Attributes{ + "attr": { + Name: "attr", + Expr: &FunctionCallExpr{ + Name: "object", + Args: []Expression{ + &ObjectConsExpr{ + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 19, Byte: 34}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 36}, + }, + OpenRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 19, Byte: 34}, + End: hcl.Pos{Line: 2, Column: 20, Byte: 35}, + }, + }, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 12, Byte: 27}, + End: hcl.Pos{Line: 2, Column: 18, Byte: 33}, + }, + OpenParenRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 18, Byte: 33}, + End: hcl.Pos{Line: 2, Column: 19, Byte: 34}, + }, + CloseParenRange: hcl.Range{}, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 5, Byte: 20}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 36}, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 5, Byte: 20}, + End: hcl.Pos{Line: 2, Column: 9, Byte: 24}, + }, + EqualsRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 10, Byte: 25}, + End: hcl.Pos{Line: 2, Column: 11, Byte: 26}, + }, + }, + }, + Blocks: Blocks{}, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 36}, + }, + EndRange: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 1, Byte: 36}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 36}, + }, + }, + TypeRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, + }, + LabelRanges: []hcl.Range{ + { + Start: hcl.Pos{Line: 1, Column: 7, Byte: 6}, + End: hcl.Pos{Line: 1, Column: 14, Byte: 13}, + }, + }, + OpenBraceRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, + }, + CloseBraceRange: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 1, Byte: 36}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 36}, + }, + }, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 36}, + }, + EndRange: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 1, Byte: 36}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 36}, + }, + }, + }, + { + `block "label" { + attr = object({ foo +`, + &Body{ + Attributes: Attributes{}, + Blocks: Blocks{ + { + Type: "block", + Labels: []string{"label"}, + Body: &Body{ + Attributes: Attributes{ + "attr": { + Name: "attr", + Expr: &FunctionCallExpr{ + Name: "object", + Args: []Expression{ + &ObjectConsExpr{ + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 38}, + }, + OpenRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + End: hcl.Pos{Line: 2, Column: 18, Byte: 33}, + }, + }, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 10, Byte: 25}, + End: hcl.Pos{Line: 2, Column: 16, Byte: 31}, + }, + OpenParenRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 16, Byte: 31}, + End: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + }, + CloseParenRange: hcl.Range{}, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 3, Byte: 18}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 38}, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 3, Byte: 18}, + End: hcl.Pos{Line: 2, Column: 7, Byte: 22}, + }, + EqualsRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 8, Byte: 23}, + End: hcl.Pos{Line: 2, Column: 9, Byte: 24}, + }, + }, + }, + Blocks: Blocks{}, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 38}, + }, + EndRange: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 1, Byte: 38}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 38}, + }, + }, + TypeRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, + }, + LabelRanges: []hcl.Range{ + { + Start: hcl.Pos{Line: 1, Column: 7, Byte: 6}, + End: hcl.Pos{Line: 1, Column: 14, Byte: 13}, + }, + }, + OpenBraceRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, + }, + CloseBraceRange: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 1, Byte: 38}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 38}, + }, + }, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 38}, + }, + EndRange: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 1, Byte: 38}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 38}, + }, + }, + }, + { + `block "label" { + attr = object({ foo = +`, + &Body{ + Attributes: Attributes{}, + Blocks: Blocks{ + { + Type: "block", + Labels: []string{"label"}, + Body: &Body{ + Attributes: Attributes{ + "attr": { + Name: "attr", + Expr: &FunctionCallExpr{ + Name: "object", + Args: []Expression{ + &ObjectConsExpr{ + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 40}, + }, + OpenRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + End: hcl.Pos{Line: 2, Column: 18, Byte: 33}, + }, + }, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 10, Byte: 25}, + End: hcl.Pos{Line: 2, Column: 16, Byte: 31}, + }, + OpenParenRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 16, Byte: 31}, + End: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + }, + CloseParenRange: hcl.Range{}, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 3, Byte: 18}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 40}, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 3, Byte: 18}, + End: hcl.Pos{Line: 2, Column: 7, Byte: 22}, + }, + EqualsRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 8, Byte: 23}, + End: hcl.Pos{Line: 2, Column: 9, Byte: 24}, + }, + }, + }, + Blocks: Blocks{}, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 40}, + }, + EndRange: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 1, Byte: 40}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 40}, + }, + }, + TypeRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, + }, + LabelRanges: []hcl.Range{ + { + Start: hcl.Pos{Line: 1, Column: 7, Byte: 6}, + End: hcl.Pos{Line: 1, Column: 14, Byte: 13}, + }, + }, + OpenBraceRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, + }, + CloseBraceRange: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 1, Byte: 40}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 40}, + }, + }, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 40}, + }, + EndRange: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 1, Byte: 40}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 40}, + }, + }, + }, + { + `block "label" { + attr = object({ foo = } +`, + &Body{ + Attributes: Attributes{}, + Blocks: Blocks{ + { + Type: "block", + Labels: []string{"label"}, + Body: &Body{ + Attributes: Attributes{ + "attr": { + Name: "attr", + Expr: &FunctionCallExpr{ + Name: "object", + Args: []Expression{ + &ObjectConsExpr{ + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + End: hcl.Pos{Line: 2, Column: 26, Byte: 41}, + }, + OpenRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + End: hcl.Pos{Line: 2, Column: 18, Byte: 33}, + }, + }, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 10, Byte: 25}, + End: hcl.Pos{Line: 2, Column: 16, Byte: 31}, + }, + OpenParenRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 16, Byte: 31}, + End: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + }, + CloseParenRange: hcl.Range{}, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 3, Byte: 18}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 42}, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 3, Byte: 18}, + End: hcl.Pos{Line: 2, Column: 7, Byte: 22}, + }, + EqualsRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 8, Byte: 23}, + End: hcl.Pos{Line: 2, Column: 9, Byte: 24}, + }, + }, + }, + Blocks: Blocks{}, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 42}, + }, + EndRange: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 1, Byte: 42}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 42}, + }, + }, + TypeRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, + }, + LabelRanges: []hcl.Range{ + { + Start: hcl.Pos{Line: 1, Column: 7, Byte: 6}, + End: hcl.Pos{Line: 1, Column: 14, Byte: 13}, + }, + }, + OpenBraceRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, + }, + CloseBraceRange: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 1, Byte: 42}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 42}, + }, + }, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 42}, + }, + EndRange: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 1, Byte: 42}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 42}, + }, + }, + }, + { + `block "label" { + attr = object({ foo = }) +`, + &Body{ + Attributes: Attributes{}, + Blocks: Blocks{ + { + Type: "block", + Labels: []string{"label"}, + Body: &Body{ + Attributes: Attributes{ + "attr": { + Name: "attr", + Expr: &FunctionCallExpr{ + Name: "object", + Args: []Expression{ + &ObjectConsExpr{ + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + End: hcl.Pos{Line: 2, Column: 26, Byte: 41}, + }, + OpenRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + End: hcl.Pos{Line: 2, Column: 18, Byte: 33}, + }, + }, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 10, Byte: 25}, + End: hcl.Pos{Line: 2, Column: 16, Byte: 31}, + }, + OpenParenRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 16, Byte: 31}, + End: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + }, + CloseParenRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 26, Byte: 41}, + End: hcl.Pos{Line: 2, Column: 27, Byte: 42}, + }, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 3, Byte: 18}, + End: hcl.Pos{Line: 2, Column: 27, Byte: 42}, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 3, Byte: 18}, + End: hcl.Pos{Line: 2, Column: 7, Byte: 22}, + }, + EqualsRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 8, Byte: 23}, + End: hcl.Pos{Line: 2, Column: 9, Byte: 24}, + }, + }, + }, + Blocks: Blocks{}, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 43}, + }, + EndRange: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 1, Byte: 43}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 43}, + }, + }, + TypeRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, + }, + LabelRanges: []hcl.Range{ + { + Start: hcl.Pos{Line: 1, Column: 7, Byte: 6}, + End: hcl.Pos{Line: 1, Column: 14, Byte: 13}, + }, + }, + OpenBraceRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, + }, + CloseBraceRange: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 1, Byte: 43}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 43}, + }, + }, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 43}, + }, + EndRange: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 1, Byte: 43}, + End: hcl.Pos{Line: 3, Column: 1, Byte: 43}, + }, + }, + }, + { + `block "label" { + attr = object({ + foo = + +`, + &Body{ + Attributes: Attributes{}, + Blocks: Blocks{ + { + Type: "block", + Labels: []string{"label"}, + Body: &Body{ + Attributes: Attributes{ + "attr": { + Name: "attr", + Expr: &FunctionCallExpr{ + Name: "object", + Args: []Expression{ + &ObjectConsExpr{ + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + End: hcl.Pos{Line: 5, Column: 1, Byte: 45}, + }, + OpenRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + End: hcl.Pos{Line: 2, Column: 18, Byte: 33}, + }, + }, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 10, Byte: 25}, + End: hcl.Pos{Line: 2, Column: 16, Byte: 31}, + }, + OpenParenRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 16, Byte: 31}, + End: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + }, + CloseParenRange: hcl.Range{}, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 3, Byte: 18}, + End: hcl.Pos{Line: 5, Column: 1, Byte: 45}, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 3, Byte: 18}, + End: hcl.Pos{Line: 2, Column: 7, Byte: 22}, + }, + EqualsRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 8, Byte: 23}, + End: hcl.Pos{Line: 2, Column: 9, Byte: 24}, + }, + }, + }, + Blocks: Blocks{}, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 5, Column: 1, Byte: 45}, + }, + EndRange: hcl.Range{ + Start: hcl.Pos{Line: 5, Column: 1, Byte: 45}, + End: hcl.Pos{Line: 5, Column: 1, Byte: 45}, + }, + }, + TypeRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, + }, + LabelRanges: []hcl.Range{ + { + Start: hcl.Pos{Line: 1, Column: 7, Byte: 6}, + End: hcl.Pos{Line: 1, Column: 14, Byte: 13}, + }, + }, + OpenBraceRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, + }, + CloseBraceRange: hcl.Range{ + Start: hcl.Pos{Line: 5, Column: 1, Byte: 45}, + End: hcl.Pos{Line: 5, Column: 1, Byte: 45}, + }, + }, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 5, Column: 1, Byte: 45}, + }, + EndRange: hcl.Range{ + Start: hcl.Pos{Line: 5, Column: 1, Byte: 45}, + End: hcl.Pos{Line: 5, Column: 1, Byte: 45}, + }, + }, + }, + { + `block "label" { + attr = object({ + foo = + +another_block { + +} +`, + &Body{ + Attributes: Attributes{}, + Blocks: Blocks{ + { + Type: "block", + Labels: []string{"label"}, + Body: &Body{ + Attributes: Attributes{ + "attr": { + Name: "attr", + Expr: &FunctionCallExpr{ + Name: "object", + Args: []Expression{ + &ObjectConsExpr{ + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + End: hcl.Pos{Line: 8, Column: 1, Byte: 64}, + }, + OpenRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + End: hcl.Pos{Line: 2, Column: 18, Byte: 33}, + }, + }, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 10, Byte: 25}, + End: hcl.Pos{Line: 2, Column: 16, Byte: 31}, + }, + OpenParenRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 16, Byte: 31}, + End: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + }, + CloseParenRange: hcl.Range{}, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 3, Byte: 18}, + End: hcl.Pos{Line: 8, Column: 1, Byte: 64}, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 3, Byte: 18}, + End: hcl.Pos{Line: 2, Column: 7, Byte: 22}, + }, + EqualsRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 8, Byte: 23}, + End: hcl.Pos{Line: 2, Column: 9, Byte: 24}, + }, + }, + }, + Blocks: Blocks{}, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 8, Column: 1, Byte: 64}, + }, + EndRange: hcl.Range{ + Start: hcl.Pos{Line: 8, Column: 1, Byte: 64}, + End: hcl.Pos{Line: 8, Column: 1, Byte: 64}, + }, + }, + TypeRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, + }, + LabelRanges: []hcl.Range{ + { + Start: hcl.Pos{Line: 1, Column: 7, Byte: 6}, + End: hcl.Pos{Line: 1, Column: 14, Byte: 13}, + }, + }, + OpenBraceRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, + }, + CloseBraceRange: hcl.Range{ + Start: hcl.Pos{Line: 8, Column: 1, Byte: 64}, + End: hcl.Pos{Line: 8, Column: 1, Byte: 64}, + }, + }, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 8, Column: 1, Byte: 64}, + }, + EndRange: hcl.Range{ + Start: hcl.Pos{Line: 8, Column: 1, Byte: 64}, + End: hcl.Pos{Line: 8, Column: 1, Byte: 64}, + }, + }, + }, + { + `block "label" { + attr = object({ + foo = + } +`, + &Body{ + Attributes: Attributes{}, + Blocks: Blocks{ + { + Type: "block", + Labels: []string{"label"}, + Body: &Body{ + Attributes: Attributes{ + "attr": { + Name: "attr", + Expr: &FunctionCallExpr{ + Name: "object", + Args: []Expression{ + &ObjectConsExpr{ + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + End: hcl.Pos{Line: 4, Column: 4, Byte: 47}, + }, + OpenRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + End: hcl.Pos{Line: 2, Column: 18, Byte: 33}, + }, + }, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 10, Byte: 25}, + End: hcl.Pos{Line: 2, Column: 16, Byte: 31}, + }, + OpenParenRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 16, Byte: 31}, + End: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + }, + CloseParenRange: hcl.Range{}, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 3, Byte: 18}, + End: hcl.Pos{Line: 5, Column: 1, Byte: 48}, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 3, Byte: 18}, + End: hcl.Pos{Line: 2, Column: 7, Byte: 22}, + }, + EqualsRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 8, Byte: 23}, + End: hcl.Pos{Line: 2, Column: 9, Byte: 24}, + }, + }, + }, + Blocks: Blocks{}, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 5, Column: 1, Byte: 48}, + }, + EndRange: hcl.Range{ + Start: hcl.Pos{Line: 5, Column: 1, Byte: 48}, + End: hcl.Pos{Line: 5, Column: 1, Byte: 48}, + }, + }, + TypeRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, + }, + LabelRanges: []hcl.Range{ + { + Start: hcl.Pos{Line: 1, Column: 7, Byte: 6}, + End: hcl.Pos{Line: 1, Column: 14, Byte: 13}, + }, + }, + OpenBraceRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, + }, + CloseBraceRange: hcl.Range{ + Start: hcl.Pos{Line: 5, Column: 1, Byte: 48}, + End: hcl.Pos{Line: 5, Column: 1, Byte: 48}, + }, + }, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 5, Column: 1, Byte: 48}, + }, + EndRange: hcl.Range{ + Start: hcl.Pos{Line: 5, Column: 1, Byte: 48}, + End: hcl.Pos{Line: 5, Column: 1, Byte: 48}, + }, + }, + }, + { + `block "label" { + attr = object({ + foo = + }) +`, + &Body{ + Attributes: Attributes{}, + Blocks: Blocks{ + { + Type: "block", + Labels: []string{"label"}, + Body: &Body{ + Attributes: Attributes{ + "attr": { + Name: "attr", + Expr: &FunctionCallExpr{ + Name: "object", + Args: []Expression{ + &ObjectConsExpr{ + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + End: hcl.Pos{Line: 4, Column: 4, Byte: 47}, + }, + OpenRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + End: hcl.Pos{Line: 2, Column: 18, Byte: 33}, + }, + }, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 10, Byte: 25}, + End: hcl.Pos{Line: 2, Column: 16, Byte: 31}, + }, + OpenParenRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 16, Byte: 31}, + End: hcl.Pos{Line: 2, Column: 17, Byte: 32}, + }, + CloseParenRange: hcl.Range{ + Start: hcl.Pos{Line: 4, Column: 4, Byte: 47}, + End: hcl.Pos{Line: 4, Column: 5, Byte: 48}, + }, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 3, Byte: 18}, + End: hcl.Pos{Line: 4, Column: 5, Byte: 48}, + }, + NameRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 3, Byte: 18}, + End: hcl.Pos{Line: 2, Column: 7, Byte: 22}, + }, + EqualsRange: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 8, Byte: 23}, + End: hcl.Pos{Line: 2, Column: 9, Byte: 24}, + }, + }, + }, + Blocks: Blocks{}, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 5, Column: 1, Byte: 49}, + }, + EndRange: hcl.Range{ + Start: hcl.Pos{Line: 5, Column: 1, Byte: 49}, + End: hcl.Pos{Line: 5, Column: 1, Byte: 49}, + }, + }, + TypeRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, + }, + LabelRanges: []hcl.Range{ + { + Start: hcl.Pos{Line: 1, Column: 7, Byte: 6}, + End: hcl.Pos{Line: 1, Column: 14, Byte: 13}, + }, + }, + OpenBraceRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, + }, + CloseBraceRange: hcl.Range{ + Start: hcl.Pos{Line: 5, Column: 1, Byte: 49}, + End: hcl.Pos{Line: 5, Column: 1, Byte: 49}, + }, + }, + }, + SrcRange: hcl.Range{ + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 5, Column: 1, Byte: 49}, + }, + EndRange: hcl.Range{ + Start: hcl.Pos{Line: 5, Column: 1, Byte: 49}, + End: hcl.Pos{Line: 5, Column: 1, Byte: 49}, + }, + }, + }, + } + opts := cmp.Options{ + cmpopts.IgnoreUnexported(FunctionCallExpr{}), + cmpopts.IgnoreUnexported(Body{}), + cmpopts.IgnoreUnexported(cty.Value{}), + } + for i, test := range tests { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + t.Logf("\n%s", test.input) + file, _ := ParseConfig([]byte(test.input), "", hcl.InitialPos) + + got := file.Body + + if diff := cmp.Diff(got, test.want, opts); diff != "" { + t.Errorf(diff) + } + }) + } +} + func TestParseConfigDiagnostics(t *testing.T) { // This test function is a variant of TestParseConfig which tests for // specific error messages for certain kinds of invalid input where we