From d15c0bd110ee3d31bc5e2f6d280eea6587b9eab0 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Thu, 18 Jan 2024 14:46:28 +0000 Subject: [PATCH 01/13] decoder: Fix crash with missing PathContext in LiteralValue --- decoder/expression.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/decoder/expression.go b/decoder/expression.go index 4c9b908e..8dafbe7b 100644 --- a/decoder/expression.go +++ b/decoder/expression.go @@ -127,8 +127,9 @@ func newExpression(pathContext *PathContext, expr hcl.Expression, cons schema.Co } case schema.LiteralValue: return LiteralValue{ - expr: expr, - cons: c, + expr: expr, + cons: c, + pathCtx: pathContext, } case schema.TypeDeclaration: return TypeDeclaration{ From 529112dea4a4f5f3afb35902b925b102c5acca23 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Thu, 18 Jan 2024 15:12:39 +0000 Subject: [PATCH 02/13] schema: Introduce AllowInterpolatedKeys for Map --- schema/constraint_map.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/schema/constraint_map.go b/schema/constraint_map.go index 1373cc0d..e0c9c202 100644 --- a/schema/constraint_map.go +++ b/schema/constraint_map.go @@ -30,6 +30,10 @@ type Map struct { // MaxItems defines maximum number of items (affects completion) MaxItems uint64 + + // AllowInterpolatedKeys determines whether the key names can be + // interpolated (true) or static (literal strings only). + AllowInterpolatedKeys bool } func (Map) isConstraintImpl() constraintSigil { @@ -52,11 +56,12 @@ func (m Map) Copy() Constraint { elem = m.Elem.Copy() } return Map{ - Elem: elem, - Name: m.Name, - Description: m.Description, - MinItems: m.MinItems, - MaxItems: m.MaxItems, + Elem: elem, + Name: m.Name, + Description: m.Description, + MinItems: m.MinItems, + MaxItems: m.MaxItems, + AllowInterpolatedKeys: m.AllowInterpolatedKeys, } } From 8974b67f8d6e219bb9e4afc0d8dcfb8af637161f Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Thu, 18 Jan 2024 15:14:01 +0000 Subject: [PATCH 03/13] schema: Introduce AllowInterpolatedAttrName to Object --- schema/constraint_object.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/schema/constraint_object.go b/schema/constraint_object.go index a887272f..bd0f7db6 100644 --- a/schema/constraint_object.go +++ b/schema/constraint_object.go @@ -25,6 +25,10 @@ type Object struct { // Description defines description of the whole object (affects hover) Description lang.MarkupContent + + // AllowInterpolatedAttrName determines whether the attribute names can be + // interpolated (true) or static (literal strings only). + AllowInterpolatedAttrName bool } type ObjectAttributes map[string]*AttributeSchema @@ -42,9 +46,10 @@ func (o Object) FriendlyName() string { func (o Object) Copy() Constraint { return Object{ - Attributes: o.Attributes.Copy(), - Name: o.Name, - Description: o.Description, + Attributes: o.Attributes.Copy(), + Name: o.Name, + Description: o.Description, + AllowInterpolatedAttrName: o.AllowInterpolatedAttrName, } } From 3c36b48c99cab548ec0575695c99e6e4964f1e30 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Thu, 18 Jan 2024 15:12:57 +0000 Subject: [PATCH 04/13] decoder: Enable completion within parenthesis in map keys --- decoder/expr_any_completion.go | 2 + decoder/expr_any_completion_test.go | 130 ++++++++++++++++++++++++++++ decoder/expr_map_completion.go | 23 +++++ 3 files changed, 155 insertions(+) diff --git a/decoder/expr_any_completion.go b/decoder/expr_any_completion.go index 7e42f97c..7fd2e06a 100644 --- a/decoder/expr_any_completion.go +++ b/decoder/expr_any_completion.go @@ -74,7 +74,9 @@ func (a Any) CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Candidate Elem: schema.AnyExpression{ OfType: typ.ElementType(), }, + AllowInterpolatedKeys: true, } + return newExpression(a.pathCtx, expr, cons).CompletionAtPos(ctx, pos) } diff --git a/decoder/expr_any_completion_test.go b/decoder/expr_any_completion_test.go index 1e00e558..d6d04e7e 100644 --- a/decoder/expr_any_completion_test.go +++ b/decoder/expr_any_completion_test.go @@ -3609,6 +3609,136 @@ func TestCompletionAtPos_exprAny_parentheses(t *testing.T) { }, }), }, + { + "empty parentheses as map key", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.AnyExpression{ + OfType: cty.Map(cty.String), + }, + }, + }, + reference.Targets{ + { + Addr: lang.Address{ + lang.RootStep{Name: "var"}, + lang.AttrStep{Name: "foo"}, + }, + Type: cty.String, + }, + }, + `attr = { + () = "foo" +} +`, + hcl.Pos{Line: 2, Column: 4, Byte: 12}, + lang.CompleteCandidates([]lang.Candidate{ + { + Label: "var.foo", + Detail: "string", + Kind: lang.ReferenceCandidateKind, + TextEdit: lang.TextEdit{ + NewText: "var.foo", + Snippet: "var.foo", + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 4, Byte: 12}, + End: hcl.Pos{Line: 2, Column: 4, Byte: 12}, + }, + }, + }, + }), + }, + { + "parentheses with prefix as map key", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.AnyExpression{ + OfType: cty.Map(cty.String), + }, + }, + }, + reference.Targets{ + { + Addr: lang.Address{ + lang.RootStep{Name: "var"}, + lang.AttrStep{Name: "foo"}, + }, + Type: cty.String, + }, + }, + `attr = { + (var) = "foo" +} +`, + hcl.Pos{Line: 2, Column: 7, Byte: 15}, + lang.CompleteCandidates([]lang.Candidate{ + { + Label: "var.foo", + Detail: "string", + Kind: lang.ReferenceCandidateKind, + TextEdit: lang.TextEdit{ + NewText: "var.foo", + Snippet: "var.foo", + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 4, Byte: 12}, + End: hcl.Pos{Line: 2, Column: 7, Byte: 15}, + }, + }, + }, + }), + }, + { + "empty parentheses as map key in static map", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Map{ + Elem: schema.LiteralType{Type: cty.String}, + }, + }, + }, + reference.Targets{ + { + Addr: lang.Address{ + lang.RootStep{Name: "var"}, + lang.AttrStep{Name: "foo"}, + }, + Type: cty.String, + }, + }, + `attr = { + () = "foo" +} +`, + hcl.Pos{Line: 2, Column: 4, Byte: 12}, + lang.CompleteCandidates([]lang.Candidate{}), + }, + { + "parentheses with prefix as map key in static map", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Map{ + Elem: schema.LiteralType{Type: cty.String}, + }, + }, + }, + reference.Targets{ + { + Addr: lang.Address{ + lang.RootStep{Name: "var"}, + lang.AttrStep{Name: "foo"}, + }, + Type: cty.String, + }, + }, + `attr = { + (var) = "foo" +} +`, + hcl.Pos{Line: 2, Column: 7, Byte: 15}, + lang.CompleteCandidates([]lang.Candidate{}), + }, } for i, tc := range testCases { diff --git a/decoder/expr_map_completion.go b/decoder/expr_map_completion.go index 4b637956..6b6dd8b9 100644 --- a/decoder/expr_map_completion.go +++ b/decoder/expr_map_completion.go @@ -9,8 +9,10 @@ import ( "fmt" "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/hcl-lang/schema" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/zclconf/go-cty/cty" ) func (m Map) CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Candidate { @@ -135,6 +137,18 @@ func (m Map) CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Candidate recoveryPos = item.ValueExpr.Range().End if item.KeyExpr.Range().ContainsPos(pos) { + // handle any interpolation if it is allowed + keyExpr, ok := item.KeyExpr.(*hclsyntax.ObjectConsKeyExpr) + if ok && m.cons.AllowInterpolatedKeys { + parensExpr, ok := keyExpr.Wrapped.(*hclsyntax.ParenthesesExpr) + if ok { + keyCons := schema.AnyExpression{ + OfType: cty.String, + } + return newExpression(m.pathCtx, parensExpr, keyCons).CompletionAtPos(ctx, pos) + } + } + return []lang.Candidate{} } if item.ValueExpr.Range().ContainsPos(pos) || item.ValueExpr.Range().End.Byte == pos.Byte { @@ -162,6 +176,15 @@ func (m Map) CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Candidate } } + // parenthesis implies interpolated map key + if trimmedBytes[len(trimmedBytes)-1] == '(' && m.cons.AllowInterpolatedKeys { + emptyExpr := newEmptyExpressionAtPos(eType.Range().Filename, pos) + keyCons := schema.AnyExpression{ + OfType: cty.String, + } + return newExpression(m.pathCtx, emptyExpr, keyCons).CompletionAtPos(ctx, pos) + } + // if last byte is =, then it's incomplete attribute if trimmedBytes[len(trimmedBytes)-1] == '=' { emptyExpr := newEmptyExpressionAtPos(eType.Range().Filename, pos) From 4c8f96108b6ca81e7f13b30883d6a629e226fa30 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Thu, 18 Jan 2024 15:14:12 +0000 Subject: [PATCH 05/13] decoder: Enable completion within parenthesis in object attribute names --- decoder/expr_any_completion.go | 3 +- decoder/expr_any_completion_test.go | 142 ++++++++++++++++++++++++++++ decoder/expr_object_completion.go | 53 ++++++++--- 3 files changed, 182 insertions(+), 16 deletions(-) diff --git a/decoder/expr_any_completion.go b/decoder/expr_any_completion.go index 7fd2e06a..505c3aa2 100644 --- a/decoder/expr_any_completion.go +++ b/decoder/expr_any_completion.go @@ -87,7 +87,8 @@ func (a Any) CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Candidate } cons := schema.Object{ - Attributes: ctyObjectToObjectAttributes(typ), + Attributes: ctyObjectToObjectAttributes(typ), + AllowInterpolatedAttrName: true, } return newExpression(a.pathCtx, expr, cons).CompletionAtPos(ctx, pos) } diff --git a/decoder/expr_any_completion_test.go b/decoder/expr_any_completion_test.go index d6d04e7e..363ffb3b 100644 --- a/decoder/expr_any_completion_test.go +++ b/decoder/expr_any_completion_test.go @@ -3735,6 +3735,148 @@ func TestCompletionAtPos_exprAny_parentheses(t *testing.T) { `attr = { (var) = "foo" } +`, + hcl.Pos{Line: 2, Column: 7, Byte: 15}, + lang.CompleteCandidates([]lang.Candidate{}), + }, + { + "empty parentheses as object attribute name", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.AnyExpression{ + OfType: cty.Object(map[string]cty.Type{ + "bar": cty.String, + }), + }, + }, + }, + reference.Targets{ + { + Addr: lang.Address{ + lang.RootStep{Name: "var"}, + lang.AttrStep{Name: "foo"}, + }, + Type: cty.String, + }, + }, + `attr = { + () = "foo" +} +`, + hcl.Pos{Line: 2, Column: 4, Byte: 12}, + lang.CompleteCandidates([]lang.Candidate{ + { + Label: "var.foo", + Detail: "string", + Kind: lang.ReferenceCandidateKind, + TextEdit: lang.TextEdit{ + NewText: "var.foo", + Snippet: "var.foo", + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 4, Byte: 12}, + End: hcl.Pos{Line: 2, Column: 4, Byte: 12}, + }, + }, + }, + }), + }, + { + "parentheses with prefix as object attribute name", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.AnyExpression{ + OfType: cty.Object(map[string]cty.Type{ + "bar": cty.String, + }), + }, + }, + }, + reference.Targets{ + { + Addr: lang.Address{ + lang.RootStep{Name: "var"}, + lang.AttrStep{Name: "foo"}, + }, + Type: cty.String, + }, + }, + `attr = { + (var) = "foo" +} +`, + hcl.Pos{Line: 2, Column: 7, Byte: 15}, + lang.CompleteCandidates([]lang.Candidate{ + { + Label: "var.foo", + Detail: "string", + Kind: lang.ReferenceCandidateKind, + TextEdit: lang.TextEdit{ + NewText: "var.foo", + Snippet: "var.foo", + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 4, Byte: 12}, + End: hcl.Pos{Line: 2, Column: 7, Byte: 15}, + }, + }, + }, + }), + }, + { + "empty parentheses as object attribute name in static object", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Object{ + Attributes: schema.ObjectAttributes{ + "foo": &schema.AttributeSchema{ + Constraint: schema.LiteralType{Type: cty.String}, + }, + }, + }, + }, + }, + reference.Targets{ + { + Addr: lang.Address{ + lang.RootStep{Name: "var"}, + lang.AttrStep{Name: "foo"}, + }, + Type: cty.String, + }, + }, + `attr = { + () = "foo" +} +`, + hcl.Pos{Line: 2, Column: 4, Byte: 12}, + lang.CompleteCandidates([]lang.Candidate{}), + }, + { + "parentheses with prefix as map key in static map", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Object{ + Attributes: schema.ObjectAttributes{ + "foo": &schema.AttributeSchema{ + Constraint: schema.LiteralType{Type: cty.String}, + }, + }, + }, + }, + }, + reference.Targets{ + { + Addr: lang.Address{ + lang.RootStep{Name: "var"}, + lang.AttrStep{Name: "foo"}, + }, + Type: cty.String, + }, + }, + `attr = { + (var) = "foo" +} `, hcl.Pos{Line: 2, Column: 7, Byte: 15}, lang.CompleteCandidates([]lang.Candidate{}), diff --git a/decoder/expr_object_completion.go b/decoder/expr_object_completion.go index cd7c03a8..a1077e97 100644 --- a/decoder/expr_object_completion.go +++ b/decoder/expr_object_completion.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/hcl-lang/schema" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/zclconf/go-cty/cty" ) type declaredAttributes map[string]hcl.Range @@ -81,14 +82,12 @@ func (obj Object) CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Candi return []lang.Candidate{} } - attrName, attrRange, ok := rawObjectKey(item.KeyExpr) - if !ok { - continue + attrName, attrRange, isRawName := rawObjectKey(item.KeyExpr) + if isRawName { + // collect all declared attributes + declared[attrName] = hcl.RangeBetween(item.KeyExpr.Range(), item.ValueExpr.Range()) } - // collect all declared attributes - declared[attrName] = hcl.RangeBetween(item.KeyExpr.Range(), item.ValueExpr.Range()) - if nextItemRange != nil { continue } @@ -106,18 +105,33 @@ func (obj Object) CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Candi recoveryPos = item.ValueExpr.Range().End if item.KeyExpr.Range().ContainsPos(pos) { - prefix := "" - - // if we're before start of the attribute - // it means the attribute is likely quoted - if pos.Byte >= attrRange.Start.Byte { - prefixLen := pos.Byte - attrRange.Start.Byte - prefix = attrName[0:prefixLen] + // handle any interpolation if it is allowed + keyExpr, ok := item.KeyExpr.(*hclsyntax.ObjectConsKeyExpr) + if ok && obj.cons.AllowInterpolatedAttrName { + parensExpr, ok := keyExpr.Wrapped.(*hclsyntax.ParenthesesExpr) + if ok { + keyCons := schema.AnyExpression{ + OfType: cty.String, + } + return newExpression(obj.pathCtx, parensExpr, keyCons).CompletionAtPos(ctx, pos) + } } - editRange := hcl.RangeBetween(item.KeyExpr.Range(), item.ValueExpr.Range()) + if isRawName { + prefix := "" + // if we're before start of the attribute + // it means the attribute is likely quoted + if pos.Byte >= attrRange.Start.Byte { + prefixLen := pos.Byte - attrRange.Start.Byte + prefix = attrName[0:prefixLen] + } + + editRange := hcl.RangeBetween(item.KeyExpr.Range(), item.ValueExpr.Range()) + + return objectAttributesToCandidates(ctx, prefix, obj.cons.Attributes, declared, editRange) + } - return objectAttributesToCandidates(ctx, prefix, obj.cons.Attributes, declared, editRange) + return []lang.Candidate{} } if item.ValueExpr.Range().ContainsPos(pos) || item.ValueExpr.Range().End.Byte == pos.Byte { aSchema, ok := obj.cons.Attributes[attrName] @@ -169,6 +183,15 @@ func (obj Object) CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Candi return isObjectItemTerminatingRune(r) || unicode.IsSpace(r) }) + // parenthesis implies interpolated attribute name + if trimmedBytes[len(trimmedBytes)-1] == '(' && obj.cons.AllowInterpolatedAttrName { + emptyExpr := newEmptyExpressionAtPos(eType.Range().Filename, pos) + attrNameCons := schema.AnyExpression{ + OfType: cty.String, + } + return newExpression(obj.pathCtx, emptyExpr, attrNameCons).CompletionAtPos(ctx, pos) + } + // if last byte is =, then it's incomplete attribute if len(trimmedBytes) > 0 && trimmedBytes[len(trimmedBytes)-1] == '=' { emptyExpr := newEmptyExpressionAtPos(eType.Range().Filename, pos) From 7553742e5d27cb93e1aef3a50bc1a023541f7f74 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Thu, 18 Jan 2024 15:10:40 +0000 Subject: [PATCH 06/13] decoder: Enable hover within parenthesis in map keys --- decoder/expr_any_hover.go | 1 + decoder/expr_any_hover_test.go | 60 +++++++++++++++++++++++++++++++++- decoder/expr_map_hover.go | 14 +++++++- 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/decoder/expr_any_hover.go b/decoder/expr_any_hover.go index 53e0e1d9..efaaed87 100644 --- a/decoder/expr_any_hover.go +++ b/decoder/expr_any_hover.go @@ -74,6 +74,7 @@ func (a Any) HoverAtPos(ctx context.Context, pos hcl.Pos) *lang.HoverData { Elem: schema.AnyExpression{ OfType: typ.ElementType(), }, + AllowInterpolatedKeys: true, } return newExpression(a.pathCtx, expr, cons).HoverAtPos(ctx, pos) } diff --git a/decoder/expr_any_hover_test.go b/decoder/expr_any_hover_test.go index 224c0f5f..0df1c15d 100644 --- a/decoder/expr_any_hover_test.go +++ b/decoder/expr_any_hover_test.go @@ -1617,6 +1617,8 @@ func TestHoverAtPos_exprAny_parenthesis(t *testing.T) { testCases := []struct { testName string attrSchema map[string]*schema.AttributeSchema + refTargets reference.Targets + refOrigins reference.Origins cfg string pos hcl.Pos expectedHoverData *lang.HoverData @@ -1630,6 +1632,8 @@ func TestHoverAtPos_exprAny_parenthesis(t *testing.T) { }, }, }, + reference.Targets{}, + reference.Origins{}, `attr = (42+3)*2 `, hcl.Pos{Line: 1, Column: 10, Byte: 9}, @@ -1651,6 +1655,8 @@ func TestHoverAtPos_exprAny_parenthesis(t *testing.T) { }, }, }, + reference.Targets{}, + reference.Origins{}, `attr = (true || false) && true `, hcl.Pos{Line: 1, Column: 11, Byte: 10}, @@ -1672,11 +1678,61 @@ func TestHoverAtPos_exprAny_parenthesis(t *testing.T) { }, }, }, + reference.Targets{}, + reference.Origins{}, `attr = (true || false) && true `, hcl.Pos{Line: 1, Column: 11, Byte: 10}, nil, }, + { + "reference as map key", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.AnyExpression{ + OfType: cty.Map(cty.String), + }, + }, + }, + reference.Targets{ + { + Addr: lang.Address{ + lang.RootStep{Name: "var"}, + lang.AttrStep{Name: "foo"}, + }, + Type: cty.String, + }, + }, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "var"}, + lang.AttrStep{Name: "foo"}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.String}, + }, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 4, Byte: 12}, + End: hcl.Pos{Line: 2, Column: 11, Byte: 19}, + }, + }, + }, + `attr = { + (var.foo) = "foo" +} +`, + hcl.Pos{Line: 2, Column: 7, Byte: 15}, + &lang.HoverData{ + Content: lang.Markdown("`var.foo`\n_string_"), + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 4, Byte: 12}, + End: hcl.Pos{Line: 2, Column: 11, Byte: 19}, + }, + }, + }, } for i, tc := range testCases { t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) { @@ -1686,7 +1742,9 @@ func TestHoverAtPos_exprAny_parenthesis(t *testing.T) { f, _ := hclsyntax.ParseConfig([]byte(tc.cfg), "test.tf", hcl.InitialPos) d := testPathDecoder(t, &PathContext{ - Schema: bodySchema, + Schema: bodySchema, + ReferenceTargets: tc.refTargets, + ReferenceOrigins: tc.refOrigins, Files: map[string]*hcl.File{ "test.tf": f, }, diff --git a/decoder/expr_map_hover.go b/decoder/expr_map_hover.go index 00ee4873..4759aaec 100644 --- a/decoder/expr_map_hover.go +++ b/decoder/expr_map_hover.go @@ -8,8 +8,10 @@ import ( "fmt" "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/hcl-lang/schema" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/zclconf/go-cty/cty" ) func (m Map) HoverAtPos(ctx context.Context, pos hcl.Pos) *lang.HoverData { @@ -20,7 +22,17 @@ func (m Map) HoverAtPos(ctx context.Context, pos hcl.Pos) *lang.HoverData { for _, item := range eType.Items { if item.KeyExpr.Range().ContainsPos(pos) { - // no hover for map keys + keyExpr, ok := item.KeyExpr.(*hclsyntax.ObjectConsKeyExpr) + if ok && m.cons.AllowInterpolatedKeys { + parensExpr, ok := keyExpr.Wrapped.(*hclsyntax.ParenthesesExpr) + if ok { + keyCons := schema.AnyExpression{ + OfType: cty.String, + } + expr := newExpression(m.pathCtx, parensExpr, keyCons) + return expr.HoverAtPos(ctx, pos) + } + } return nil } From 52b23e02e6cdf91ce6279d07425b76dfd7aa88ab Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Thu, 18 Jan 2024 15:10:55 +0000 Subject: [PATCH 07/13] decoder: Enable hover within parenthesis in object attribute names --- decoder/expr_any_hover.go | 3 +- decoder/expr_any_hover_test.go | 50 ++++++++++++++++++++++++++++++++++ decoder/expr_object_hover.go | 40 +++++++++++++++++---------- 3 files changed, 78 insertions(+), 15 deletions(-) diff --git a/decoder/expr_any_hover.go b/decoder/expr_any_hover.go index efaaed87..ecab22f2 100644 --- a/decoder/expr_any_hover.go +++ b/decoder/expr_any_hover.go @@ -86,7 +86,8 @@ func (a Any) HoverAtPos(ctx context.Context, pos hcl.Pos) *lang.HoverData { } cons := schema.Object{ - Attributes: ctyObjectToObjectAttributes(typ), + Attributes: ctyObjectToObjectAttributes(typ), + AllowInterpolatedAttrName: true, } return newExpression(a.pathCtx, expr, cons).HoverAtPos(ctx, pos) } diff --git a/decoder/expr_any_hover_test.go b/decoder/expr_any_hover_test.go index 0df1c15d..e57f98eb 100644 --- a/decoder/expr_any_hover_test.go +++ b/decoder/expr_any_hover_test.go @@ -1722,6 +1722,56 @@ func TestHoverAtPos_exprAny_parenthesis(t *testing.T) { `attr = { (var.foo) = "foo" } +`, + hcl.Pos{Line: 2, Column: 7, Byte: 15}, + &lang.HoverData{ + Content: lang.Markdown("`var.foo`\n_string_"), + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 4, Byte: 12}, + End: hcl.Pos{Line: 2, Column: 11, Byte: 19}, + }, + }, + }, + { + "reference as object attribute name", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.AnyExpression{ + OfType: cty.Object(map[string]cty.Type{ + "bar": cty.String, + }), + }, + }, + }, + reference.Targets{ + { + Addr: lang.Address{ + lang.RootStep{Name: "var"}, + lang.AttrStep{Name: "foo"}, + }, + Type: cty.String, + }, + }, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "var"}, + lang.AttrStep{Name: "foo"}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.String}, + }, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 4, Byte: 12}, + End: hcl.Pos{Line: 2, Column: 11, Byte: 19}, + }, + }, + }, + `attr = { + (var.foo) = "foo" +} `, hcl.Pos{Line: 2, Column: 7, Byte: 15}, &lang.HoverData{ diff --git a/decoder/expr_object_hover.go b/decoder/expr_object_hover.go index 889d1084..599733ab 100644 --- a/decoder/expr_object_hover.go +++ b/decoder/expr_object_hover.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/hcl-lang/schema" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/zclconf/go-cty/cty" ) func (obj Object) HoverAtPos(ctx context.Context, pos hcl.Pos) *lang.HoverData { @@ -20,28 +21,39 @@ func (obj Object) HoverAtPos(ctx context.Context, pos hcl.Pos) *lang.HoverData { } for _, item := range eType.Items { - attrName, _, ok := rawObjectKey(item.KeyExpr) - if !ok { - continue - } + attrName, _, isRawKey := rawObjectKey(item.KeyExpr) - aSchema, ok := obj.cons.Attributes[attrName] - if !ok { - // unknown attribute - continue + var aSchema *schema.AttributeSchema + var isKnownAttr bool + if isRawKey { + aSchema, isKnownAttr = obj.cons.Attributes[attrName] } if item.KeyExpr.Range().ContainsPos(pos) { - itemRng := hcl.RangeBetween(item.KeyExpr.Range(), item.ValueExpr.Range()) - content := hoverContentForAttribute(attrName, aSchema) + // handle any interpolation if it is allowed + keyExpr, ok := item.KeyExpr.(*hclsyntax.ObjectConsKeyExpr) + if ok && obj.cons.AllowInterpolatedAttrName { + parensExpr, ok := keyExpr.Wrapped.(*hclsyntax.ParenthesesExpr) + if ok { + keyCons := schema.AnyExpression{ + OfType: cty.String, + } + return newExpression(obj.pathCtx, parensExpr, keyCons).HoverAtPos(ctx, pos) + } + } + + if isKnownAttr { + itemRng := hcl.RangeBetween(item.KeyExpr.Range(), item.ValueExpr.Range()) + content := hoverContentForAttribute(attrName, aSchema) - return &lang.HoverData{ - Content: content, - Range: itemRng, + return &lang.HoverData{ + Content: content, + Range: itemRng, + } } } - if item.ValueExpr.Range().ContainsPos(pos) { + if isKnownAttr && item.ValueExpr.Range().ContainsPos(pos) { expr := newExpression(obj.pathCtx, item.ValueExpr, aSchema.Constraint) return expr.HoverAtPos(ctx, pos) } From edbbb1a093696aa2719ddb54602b86288da9008e Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Thu, 18 Jan 2024 18:45:46 +0000 Subject: [PATCH 08/13] decoder: Enable semantic tokens within parenthesis in map keys --- decoder/expr_any_semtok.go | 1 + decoder/expr_any_semtok_test.go | 92 +++++++++++++++++++++++++++++++++ decoder/expr_map_semtok.go | 37 +++++++++---- 3 files changed, 120 insertions(+), 10 deletions(-) diff --git a/decoder/expr_any_semtok.go b/decoder/expr_any_semtok.go index f530796c..0fa41582 100644 --- a/decoder/expr_any_semtok.go +++ b/decoder/expr_any_semtok.go @@ -73,6 +73,7 @@ func (a Any) SemanticTokens(ctx context.Context) []lang.SemanticToken { Elem: schema.AnyExpression{ OfType: typ.ElementType(), }, + AllowInterpolatedKeys: true, } return newExpression(a.pathCtx, expr, cons).SemanticTokens(ctx) } diff --git a/decoder/expr_any_semtok_test.go b/decoder/expr_any_semtok_test.go index 0831ab5e..8046b30a 100644 --- a/decoder/expr_any_semtok_test.go +++ b/decoder/expr_any_semtok_test.go @@ -2662,6 +2662,8 @@ func TestSemanticTokens_exprAny_parenthesis(t *testing.T) { testCases := []struct { testName string attrSchema map[string]*schema.AttributeSchema + refOrigins reference.Origins + refTargets reference.Targets cfg string expectedSemanticTokens []lang.SemanticToken }{ @@ -2674,6 +2676,8 @@ func TestSemanticTokens_exprAny_parenthesis(t *testing.T) { }, }, }, + reference.Origins{}, + reference.Targets{}, `attr = (42 + 43)*2 `, []lang.SemanticToken{ @@ -2724,6 +2728,8 @@ func TestSemanticTokens_exprAny_parenthesis(t *testing.T) { }, }, }, + reference.Origins{}, + reference.Targets{}, `attr = (true || false) && true `, []lang.SemanticToken{ @@ -2774,6 +2780,8 @@ func TestSemanticTokens_exprAny_parenthesis(t *testing.T) { }, }, }, + reference.Origins{}, + reference.Targets{}, `attr = (true || false) && true `, []lang.SemanticToken{ @@ -2788,6 +2796,88 @@ func TestSemanticTokens_exprAny_parenthesis(t *testing.T) { }, }, }, + { + "reference as map key", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.AnyExpression{ + OfType: cty.Map(cty.String), + }, + }, + }, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "var"}, + lang.AttrStep{Name: "foo"}, + }, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 4, Byte: 12}, + End: hcl.Pos{Line: 2, Column: 11, Byte: 19}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.String}, + }, + }, + }, + reference.Targets{ + { + Addr: lang.Address{ + lang.RootStep{Name: "var"}, + lang.AttrStep{Name: "foo"}, + }, + Type: cty.String, + RangePtr: &hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 3, Column: 1, Byte: 31}, + End: hcl.Pos{Line: 3, Column: 2, Byte: 32}, + }, + }, + }, + `attr = { + (var.foo) = "foo" +} +`, + []lang.SemanticToken{ + { + Type: lang.TokenAttrName, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 5, Byte: 4}, + }, + }, + { + Type: lang.TokenReferenceStep, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 4, Byte: 12}, + End: hcl.Pos{Line: 2, Column: 7, Byte: 15}, + }, + }, + { + Type: lang.TokenReferenceStep, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 8, Byte: 16}, + End: hcl.Pos{Line: 2, Column: 11, Byte: 19}, + }, + }, + { + Type: lang.TokenString, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 15, Byte: 23}, + End: hcl.Pos{Line: 2, Column: 20, Byte: 28}, + }, + }, + }, + }, } for i, tc := range testCases { t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) { @@ -2801,6 +2891,8 @@ func TestSemanticTokens_exprAny_parenthesis(t *testing.T) { Files: map[string]*hcl.File{ "test.tf": f, }, + ReferenceOrigins: tc.refOrigins, + ReferenceTargets: tc.refTargets, }) ctx := context.Background() diff --git a/decoder/expr_map_semtok.go b/decoder/expr_map_semtok.go index ea156e67..b867d1d5 100644 --- a/decoder/expr_map_semtok.go +++ b/decoder/expr_map_semtok.go @@ -7,7 +7,9 @@ import ( "context" "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/hcl-lang/schema" "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/zclconf/go-cty/cty" ) func (m Map) SemanticTokens(ctx context.Context) []lang.SemanticToken { @@ -23,18 +25,33 @@ func (m Map) SemanticTokens(ctx context.Context) []lang.SemanticToken { tokens := make([]lang.SemanticToken, 0) for _, item := range eType.Items { - _, _, ok := rawObjectKey(item.KeyExpr) - if !ok { + _, _, isRawKey := rawObjectKey(item.KeyExpr) + if isRawKey { + tokens = append(tokens, lang.SemanticToken{ + Type: lang.TokenMapKey, + Modifiers: lang.SemanticTokenModifiers{}, + Range: item.KeyExpr.Range(), + }) + + vExpr := newExpression(m.pathCtx, item.ValueExpr, m.cons.Elem) + tokens = append(tokens, vExpr.SemanticTokens(ctx)...) continue } - tokens = append(tokens, lang.SemanticToken{ - Type: lang.TokenMapKey, - Modifiers: lang.SemanticTokenModifiers{}, - Range: item.KeyExpr.Range(), - }) - - expr := newExpression(m.pathCtx, item.ValueExpr, m.cons.Elem) - tokens = append(tokens, expr.SemanticTokens(ctx)...) + + keyExpr, ok := item.KeyExpr.(*hclsyntax.ObjectConsKeyExpr) + if ok && m.cons.AllowInterpolatedKeys { + parensExpr, ok := keyExpr.Wrapped.(*hclsyntax.ParenthesesExpr) + if ok { + keyCons := schema.AnyExpression{ + OfType: cty.String, + } + kExpr := newExpression(m.pathCtx, parensExpr, keyCons) + tokens = append(tokens, kExpr.SemanticTokens(ctx)...) + + vExpr := newExpression(m.pathCtx, item.ValueExpr, m.cons.Elem) + tokens = append(tokens, vExpr.SemanticTokens(ctx)...) + } + } } return tokens From e5ab500b964e29b7dcc07b83870e613b86f7dc0a Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Thu, 18 Jan 2024 20:02:42 +0000 Subject: [PATCH 09/13] decoder: Enable semantic tokens within parenthesis in object attribute names --- decoder/expr_any_semtok.go | 3 +- decoder/expr_any_semtok_test.go | 75 +++++++++++++++++++++++++++++++++ decoder/expr_object_semtok.go | 47 ++++++++++++++------- 3 files changed, 108 insertions(+), 17 deletions(-) diff --git a/decoder/expr_any_semtok.go b/decoder/expr_any_semtok.go index 0fa41582..dc58d8e5 100644 --- a/decoder/expr_any_semtok.go +++ b/decoder/expr_any_semtok.go @@ -85,7 +85,8 @@ func (a Any) SemanticTokens(ctx context.Context) []lang.SemanticToken { } cons := schema.Object{ - Attributes: ctyObjectToObjectAttributes(typ), + Attributes: ctyObjectToObjectAttributes(typ), + AllowInterpolatedAttrName: true, } return newExpression(a.pathCtx, expr, cons).SemanticTokens(ctx) } diff --git a/decoder/expr_any_semtok_test.go b/decoder/expr_any_semtok_test.go index 8046b30a..113b593f 100644 --- a/decoder/expr_any_semtok_test.go +++ b/decoder/expr_any_semtok_test.go @@ -2878,6 +2878,81 @@ func TestSemanticTokens_exprAny_parenthesis(t *testing.T) { }, }, }, + { + "reference as object attribute name", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.AnyExpression{ + OfType: cty.Object(map[string]cty.Type{ + "bar": cty.String, + }), + }, + }, + }, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "var"}, + lang.AttrStep{Name: "foo"}, + }, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 4, Byte: 12}, + End: hcl.Pos{Line: 2, Column: 11, Byte: 19}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.String}, + }, + }, + }, + reference.Targets{ + { + Addr: lang.Address{ + lang.RootStep{Name: "var"}, + lang.AttrStep{Name: "foo"}, + }, + Type: cty.String, + RangePtr: &hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 3, Column: 1, Byte: 31}, + End: hcl.Pos{Line: 3, Column: 2, Byte: 32}, + }, + }, + }, + `attr = { + (var.foo) = "foo" +} +`, + []lang.SemanticToken{ + { + Type: lang.TokenAttrName, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 5, Byte: 4}, + }, + }, + { + Type: lang.TokenReferenceStep, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 4, Byte: 12}, + End: hcl.Pos{Line: 2, Column: 7, Byte: 15}, + }, + }, + { + Type: lang.TokenReferenceStep, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 8, Byte: 16}, + End: hcl.Pos{Line: 2, Column: 11, Byte: 19}, + }, + }, + }, + }, } for i, tc := range testCases { t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) { diff --git a/decoder/expr_object_semtok.go b/decoder/expr_object_semtok.go index 39d46450..a0b2ea90 100644 --- a/decoder/expr_object_semtok.go +++ b/decoder/expr_object_semtok.go @@ -7,7 +7,9 @@ import ( "context" "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/hcl-lang/schema" "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/zclconf/go-cty/cty" ) func (obj Object) SemanticTokens(ctx context.Context) []lang.SemanticToken { @@ -23,27 +25,40 @@ func (obj Object) SemanticTokens(ctx context.Context) []lang.SemanticToken { tokens := make([]lang.SemanticToken, 0) for _, item := range eType.Items { - attrName, _, ok := rawObjectKey(item.KeyExpr) - if !ok { - // invalid expression - continue + attrName, _, isRawKey := rawObjectKey(item.KeyExpr) + + var aSchema *schema.AttributeSchema + var isKnownAttr bool + if isRawKey { + aSchema, isKnownAttr = obj.cons.Attributes[attrName] } - aSchema, ok := obj.cons.Attributes[attrName] - if !ok { - // skip unknown attribute - continue + keyExpr, ok := item.KeyExpr.(*hclsyntax.ObjectConsKeyExpr) + if ok && obj.cons.AllowInterpolatedAttrName { + parensExpr, ok := keyExpr.Wrapped.(*hclsyntax.ParenthesesExpr) + if ok { + keyCons := schema.AnyExpression{ + OfType: cty.String, + } + kExpr := newExpression(obj.pathCtx, parensExpr, keyCons) + tokens = append(tokens, kExpr.SemanticTokens(ctx)...) + } } - tokens = append(tokens, lang.SemanticToken{ - Type: lang.TokenObjectKey, - Modifiers: lang.SemanticTokenModifiers{}, - // TODO: Consider not reporting the quotes? - Range: item.KeyExpr.Range(), - }) + if isKnownAttr { + tokens = append(tokens, lang.SemanticToken{ + Type: lang.TokenObjectKey, + Modifiers: lang.SemanticTokenModifiers{}, + // TODO: Consider not reporting the quotes? + Range: item.KeyExpr.Range(), + }) + } - expr := newExpression(obj.pathCtx, item.ValueExpr, aSchema.Constraint) - tokens = append(tokens, expr.SemanticTokens(ctx)...) + if isKnownAttr { + expr := newExpression(obj.pathCtx, item.ValueExpr, aSchema.Constraint) + tokens = append(tokens, expr.SemanticTokens(ctx)...) + continue + } } return tokens From 46f22d967b5cbd6ace9f8bc368191e25e47d323d Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Thu, 18 Jan 2024 20:19:36 +0000 Subject: [PATCH 10/13] decoder: Enable reference origins in map keys --- decoder/expr_any_ref_origins.go | 1 + decoder/expr_any_ref_origins_test.go | 32 ++++++++++++++++++++++++++++ decoder/expr_map_ref_origins.go | 22 ++++++++++++++++--- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/decoder/expr_any_ref_origins.go b/decoder/expr_any_ref_origins.go index 6ce35576..2dc117af 100644 --- a/decoder/expr_any_ref_origins.go +++ b/decoder/expr_any_ref_origins.go @@ -89,6 +89,7 @@ func (a Any) ReferenceOrigins(ctx context.Context, allowSelfRefs bool) reference Elem: schema.AnyExpression{ OfType: typ.ElementType(), }, + AllowInterpolatedKeys: true, }, } return m.ReferenceOrigins(ctx, allowSelfRefs) diff --git a/decoder/expr_any_ref_origins_test.go b/decoder/expr_any_ref_origins_test.go index 77ae8847..b9967fc7 100644 --- a/decoder/expr_any_ref_origins_test.go +++ b/decoder/expr_any_ref_origins_test.go @@ -922,6 +922,38 @@ func TestCollectRefOrigins_exprAny_parenthesis_hcl(t *testing.T) { }, }, }, + { + "reference as map key", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.AnyExpression{ + OfType: cty.Map(cty.String), + }, + }, + }, + `attr = { + (var.foo) = "foo" +} +`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "var"}, + lang.AttrStep{Name: "foo"}, + }, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 4, Byte: 12}, + End: hcl.Pos{Line: 2, Column: 11, Byte: 19}, + }, + Constraints: reference.OriginConstraints{ + { + OfType: cty.String, + }, + }, + }, + }, + }, } for i, tc := range testCases { diff --git a/decoder/expr_map_ref_origins.go b/decoder/expr_map_ref_origins.go index 05475ed4..d8437c84 100644 --- a/decoder/expr_map_ref_origins.go +++ b/decoder/expr_map_ref_origins.go @@ -7,7 +7,10 @@ import ( "context" "github.com/hashicorp/hcl-lang/reference" + "github.com/hashicorp/hcl-lang/schema" "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/zclconf/go-cty/cty" ) func (m Map) ReferenceOrigins(ctx context.Context, allowSelfRefs bool) reference.Origins { @@ -23,10 +26,23 @@ func (m Map) ReferenceOrigins(ctx context.Context, allowSelfRefs bool) reference origins := make(reference.Origins, 0) for _, item := range items { - expr := newExpression(m.pathCtx, item.Value, m.cons.Elem) + keyExpr, ok := item.Key.(*hclsyntax.ObjectConsKeyExpr) + if ok { + parensExpr, ok := keyExpr.Wrapped.(*hclsyntax.ParenthesesExpr) + if ok { + keyCons := schema.AnyExpression{ + OfType: cty.String, + } + kExpr := newExpression(m.pathCtx, parensExpr, keyCons) + if expr, ok := kExpr.(ReferenceOriginsExpression); ok { + origins = append(origins, expr.ReferenceOrigins(ctx, allowSelfRefs)...) + } + } + } - if elemExpr, ok := expr.(ReferenceOriginsExpression); ok { - origins = append(origins, elemExpr.ReferenceOrigins(ctx, allowSelfRefs)...) + valExpr := newExpression(m.pathCtx, item.Value, m.cons.Elem) + if expr, ok := valExpr.(ReferenceOriginsExpression); ok { + origins = append(origins, expr.ReferenceOrigins(ctx, allowSelfRefs)...) } } From c073161341b5b5c46f8d8d83ac2e545c25b79bcd Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Thu, 18 Jan 2024 20:24:35 +0000 Subject: [PATCH 11/13] decoder: Enable reference origins in object attribute names --- decoder/expr_any_ref_origins.go | 3 ++- decoder/expr_any_ref_origins_test.go | 34 +++++++++++++++++++++++++ decoder/expr_object_ref_origins.go | 37 +++++++++++++++++++--------- 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/decoder/expr_any_ref_origins.go b/decoder/expr_any_ref_origins.go index 2dc117af..29ca55e9 100644 --- a/decoder/expr_any_ref_origins.go +++ b/decoder/expr_any_ref_origins.go @@ -105,7 +105,8 @@ func (a Any) ReferenceOrigins(ctx context.Context, allowSelfRefs bool) reference expr: a.expr, pathCtx: a.pathCtx, cons: schema.Object{ - Attributes: ctyObjectToObjectAttributes(typ), + Attributes: ctyObjectToObjectAttributes(typ), + AllowInterpolatedAttrName: true, }, } return obj.ReferenceOrigins(ctx, allowSelfRefs) diff --git a/decoder/expr_any_ref_origins_test.go b/decoder/expr_any_ref_origins_test.go index b9967fc7..bd666c38 100644 --- a/decoder/expr_any_ref_origins_test.go +++ b/decoder/expr_any_ref_origins_test.go @@ -934,6 +934,40 @@ func TestCollectRefOrigins_exprAny_parenthesis_hcl(t *testing.T) { `attr = { (var.foo) = "foo" } +`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "var"}, + lang.AttrStep{Name: "foo"}, + }, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 4, Byte: 12}, + End: hcl.Pos{Line: 2, Column: 11, Byte: 19}, + }, + Constraints: reference.OriginConstraints{ + { + OfType: cty.String, + }, + }, + }, + }, + }, + { + "reference as object attribute name", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.AnyExpression{ + OfType: cty.Object(map[string]cty.Type{ + "bar": cty.String, + }), + }, + }, + }, + `attr = { + (var.foo) = "foo" +} `, reference.Origins{ reference.LocalOrigin{ diff --git a/decoder/expr_object_ref_origins.go b/decoder/expr_object_ref_origins.go index ada4f1c7..71bab1fd 100644 --- a/decoder/expr_object_ref_origins.go +++ b/decoder/expr_object_ref_origins.go @@ -7,7 +7,10 @@ import ( "context" "github.com/hashicorp/hcl-lang/reference" + "github.com/hashicorp/hcl-lang/schema" "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/zclconf/go-cty/cty" ) func (obj Object) ReferenceOrigins(ctx context.Context, allowSelfRefs bool) reference.Origins { @@ -23,21 +26,33 @@ func (obj Object) ReferenceOrigins(ctx context.Context, allowSelfRefs bool) refe origins := make(reference.Origins, 0) for _, item := range items { - attrName, _, ok := rawObjectKey(item.Key) - if !ok { - continue - } + attrName, _, isRawKey := rawObjectKey(item.Key) - aSchema, ok := obj.cons.Attributes[attrName] - if !ok { - // skip unknown attribute - continue + var aSchema *schema.AttributeSchema + var isKnownAttr bool + if isRawKey { + aSchema, isKnownAttr = obj.cons.Attributes[attrName] } - expr := newExpression(obj.pathCtx, item.Value, aSchema.Constraint) + keyExpr, ok := item.Key.(*hclsyntax.ObjectConsKeyExpr) + if ok { + parensExpr, ok := keyExpr.Wrapped.(*hclsyntax.ParenthesesExpr) + if ok { + keyCons := schema.AnyExpression{ + OfType: cty.String, + } + kExpr := newExpression(obj.pathCtx, parensExpr, keyCons) + if expr, ok := kExpr.(ReferenceOriginsExpression); ok { + origins = append(origins, expr.ReferenceOrigins(ctx, allowSelfRefs)...) + } + } + } - if elemExpr, ok := expr.(ReferenceOriginsExpression); ok { - origins = append(origins, elemExpr.ReferenceOrigins(ctx, allowSelfRefs)...) + if isKnownAttr { + expr := newExpression(obj.pathCtx, item.Value, aSchema.Constraint) + if elemExpr, ok := expr.(ReferenceOriginsExpression); ok { + origins = append(origins, elemExpr.ReferenceOrigins(ctx, allowSelfRefs)...) + } } } From 95d132dabfe0daad3153749e8862cc043b6c9082 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Fri, 19 Jan 2024 13:11:24 +0000 Subject: [PATCH 12/13] Object: Rename AllowInterpolatedAttrName to AllowInterpolatedKeys --- decoder/expr_any_completion.go | 4 ++-- decoder/expr_any_hover.go | 4 ++-- decoder/expr_any_ref_origins.go | 4 ++-- decoder/expr_any_semtok.go | 4 ++-- decoder/expr_object_completion.go | 4 ++-- decoder/expr_object_hover.go | 2 +- decoder/expr_object_semtok.go | 2 +- schema/constraint_object.go | 12 ++++++------ 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/decoder/expr_any_completion.go b/decoder/expr_any_completion.go index 505c3aa2..c65986c5 100644 --- a/decoder/expr_any_completion.go +++ b/decoder/expr_any_completion.go @@ -87,8 +87,8 @@ func (a Any) CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Candidate } cons := schema.Object{ - Attributes: ctyObjectToObjectAttributes(typ), - AllowInterpolatedAttrName: true, + Attributes: ctyObjectToObjectAttributes(typ), + AllowInterpolatedKeys: true, } return newExpression(a.pathCtx, expr, cons).CompletionAtPos(ctx, pos) } diff --git a/decoder/expr_any_hover.go b/decoder/expr_any_hover.go index ecab22f2..5ba23423 100644 --- a/decoder/expr_any_hover.go +++ b/decoder/expr_any_hover.go @@ -86,8 +86,8 @@ func (a Any) HoverAtPos(ctx context.Context, pos hcl.Pos) *lang.HoverData { } cons := schema.Object{ - Attributes: ctyObjectToObjectAttributes(typ), - AllowInterpolatedAttrName: true, + Attributes: ctyObjectToObjectAttributes(typ), + AllowInterpolatedKeys: true, } return newExpression(a.pathCtx, expr, cons).HoverAtPos(ctx, pos) } diff --git a/decoder/expr_any_ref_origins.go b/decoder/expr_any_ref_origins.go index 29ca55e9..e6ab42f4 100644 --- a/decoder/expr_any_ref_origins.go +++ b/decoder/expr_any_ref_origins.go @@ -105,8 +105,8 @@ func (a Any) ReferenceOrigins(ctx context.Context, allowSelfRefs bool) reference expr: a.expr, pathCtx: a.pathCtx, cons: schema.Object{ - Attributes: ctyObjectToObjectAttributes(typ), - AllowInterpolatedAttrName: true, + Attributes: ctyObjectToObjectAttributes(typ), + AllowInterpolatedKeys: true, }, } return obj.ReferenceOrigins(ctx, allowSelfRefs) diff --git a/decoder/expr_any_semtok.go b/decoder/expr_any_semtok.go index dc58d8e5..cd48fca7 100644 --- a/decoder/expr_any_semtok.go +++ b/decoder/expr_any_semtok.go @@ -85,8 +85,8 @@ func (a Any) SemanticTokens(ctx context.Context) []lang.SemanticToken { } cons := schema.Object{ - Attributes: ctyObjectToObjectAttributes(typ), - AllowInterpolatedAttrName: true, + Attributes: ctyObjectToObjectAttributes(typ), + AllowInterpolatedKeys: true, } return newExpression(a.pathCtx, expr, cons).SemanticTokens(ctx) } diff --git a/decoder/expr_object_completion.go b/decoder/expr_object_completion.go index a1077e97..2a808170 100644 --- a/decoder/expr_object_completion.go +++ b/decoder/expr_object_completion.go @@ -107,7 +107,7 @@ func (obj Object) CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Candi if item.KeyExpr.Range().ContainsPos(pos) { // handle any interpolation if it is allowed keyExpr, ok := item.KeyExpr.(*hclsyntax.ObjectConsKeyExpr) - if ok && obj.cons.AllowInterpolatedAttrName { + if ok && obj.cons.AllowInterpolatedKeys { parensExpr, ok := keyExpr.Wrapped.(*hclsyntax.ParenthesesExpr) if ok { keyCons := schema.AnyExpression{ @@ -184,7 +184,7 @@ func (obj Object) CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Candi }) // parenthesis implies interpolated attribute name - if trimmedBytes[len(trimmedBytes)-1] == '(' && obj.cons.AllowInterpolatedAttrName { + if trimmedBytes[len(trimmedBytes)-1] == '(' && obj.cons.AllowInterpolatedKeys { emptyExpr := newEmptyExpressionAtPos(eType.Range().Filename, pos) attrNameCons := schema.AnyExpression{ OfType: cty.String, diff --git a/decoder/expr_object_hover.go b/decoder/expr_object_hover.go index 599733ab..0c1dfaf9 100644 --- a/decoder/expr_object_hover.go +++ b/decoder/expr_object_hover.go @@ -32,7 +32,7 @@ func (obj Object) HoverAtPos(ctx context.Context, pos hcl.Pos) *lang.HoverData { if item.KeyExpr.Range().ContainsPos(pos) { // handle any interpolation if it is allowed keyExpr, ok := item.KeyExpr.(*hclsyntax.ObjectConsKeyExpr) - if ok && obj.cons.AllowInterpolatedAttrName { + if ok && obj.cons.AllowInterpolatedKeys { parensExpr, ok := keyExpr.Wrapped.(*hclsyntax.ParenthesesExpr) if ok { keyCons := schema.AnyExpression{ diff --git a/decoder/expr_object_semtok.go b/decoder/expr_object_semtok.go index a0b2ea90..596812c9 100644 --- a/decoder/expr_object_semtok.go +++ b/decoder/expr_object_semtok.go @@ -34,7 +34,7 @@ func (obj Object) SemanticTokens(ctx context.Context) []lang.SemanticToken { } keyExpr, ok := item.KeyExpr.(*hclsyntax.ObjectConsKeyExpr) - if ok && obj.cons.AllowInterpolatedAttrName { + if ok && obj.cons.AllowInterpolatedKeys { parensExpr, ok := keyExpr.Wrapped.(*hclsyntax.ParenthesesExpr) if ok { keyCons := schema.AnyExpression{ diff --git a/schema/constraint_object.go b/schema/constraint_object.go index bd0f7db6..21f5ac2e 100644 --- a/schema/constraint_object.go +++ b/schema/constraint_object.go @@ -26,9 +26,9 @@ type Object struct { // Description defines description of the whole object (affects hover) Description lang.MarkupContent - // AllowInterpolatedAttrName determines whether the attribute names can be + // AllowInterpolatedKeys determines whether the attribute names can be // interpolated (true) or static (literal strings only). - AllowInterpolatedAttrName bool + AllowInterpolatedKeys bool } type ObjectAttributes map[string]*AttributeSchema @@ -46,10 +46,10 @@ func (o Object) FriendlyName() string { func (o Object) Copy() Constraint { return Object{ - Attributes: o.Attributes.Copy(), - Name: o.Name, - Description: o.Description, - AllowInterpolatedAttrName: o.AllowInterpolatedAttrName, + Attributes: o.Attributes.Copy(), + Name: o.Name, + Description: o.Description, + AllowInterpolatedKeys: o.AllowInterpolatedKeys, } } From f1861a5209d5b44dab949fcd84cb45ae9390f666 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Mon, 22 Jan 2024 10:08:23 +0000 Subject: [PATCH 13/13] decoder: simplify code --- decoder/expr_object_semtok.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/decoder/expr_object_semtok.go b/decoder/expr_object_semtok.go index 596812c9..3732a39d 100644 --- a/decoder/expr_object_semtok.go +++ b/decoder/expr_object_semtok.go @@ -52,12 +52,9 @@ func (obj Object) SemanticTokens(ctx context.Context) []lang.SemanticToken { // TODO: Consider not reporting the quotes? Range: item.KeyExpr.Range(), }) - } - if isKnownAttr { expr := newExpression(obj.pathCtx, item.ValueExpr, aSchema.Constraint) tokens = append(tokens, expr.SemanticTokens(ctx)...) - continue } }