From 4ad2560fada5cd01d73fff2a3f4d52a1b81e4bcd Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Wed, 23 Nov 2022 08:07:15 +0000 Subject: [PATCH] Use local ref. targets for each.* references (#162) --- decoder/body_extensions_test.go | 86 +++++++++++++++++++++++++++++--- decoder/decoder.go | 56 +++++++-------------- decoder/expression_candidates.go | 4 -- decoder/hover.go | 14 ------ decoder/hover_test.go | 4 +- decoder/reference_targets.go | 4 ++ 6 files changed, 104 insertions(+), 64 deletions(-) diff --git a/decoder/body_extensions_test.go b/decoder/body_extensions_test.go index d3cf6b74..c517ad7e 100644 --- a/decoder/body_extensions_test.go +++ b/decoder/body_extensions_test.go @@ -693,7 +693,44 @@ variable "test" { }, }, }, - reference.Targets{}, + reference.Targets{ + { + LocalAddr: lang.Address{ + lang.RootStep{Name: "each"}, + lang.AttrStep{Name: "key"}, + }, + Type: cty.String, + Description: lang.Markdown("The map key (or set member) corresponding to this instance"), + RangePtr: &hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 1, Byte: 32}, + End: hcl.Pos{Line: 5, Column: 2, Byte: 93}, + }, + DefRangePtr: &hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 1, Byte: 32}, + End: hcl.Pos{Line: 2, Column: 9, Byte: 40}, + }, + }, + { + LocalAddr: lang.Address{ + lang.RootStep{Name: "each"}, + lang.AttrStep{Name: "value"}, + }, + Type: cty.DynamicPseudoType, + Description: lang.Markdown("The map value corresponding to this instance. (If a set was provided, this is the same as `each.key`.)"), + RangePtr: &hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 1, Byte: 32}, + End: hcl.Pos{Line: 5, Column: 2, Byte: 93}, + }, + DefRangePtr: &hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 1, Byte: 32}, + End: hcl.Pos{Line: 2, Column: 9, Byte: 40}, + }, + }, + }, `resource "aws_instance" "foo" { for_each = { a_group = "eastus" @@ -701,7 +738,7 @@ for_each = { } thing = }`, - hcl.Pos{Line: 6, Column: 8, Byte: 101}, + hcl.Pos{Line: 6, Column: 9, Byte: 102}, lang.CompleteCandidates([]lang.Candidate{ { Label: "each.key", @@ -723,7 +760,7 @@ thing = }, { Label: "each.value", - Detail: "any type", + Detail: "dynamic", Kind: lang.TraversalCandidateKind, Description: lang.MarkupContent{ Value: "The map value corresponding to this instance. (If a set was provided, this is the same as `each.key`.)", @@ -880,7 +917,7 @@ for_each = { IsOptional: true, Expr: schema.ExprConstraints{ schema.TraversalExpr{ - OfType: cty.Number, + OfType: cty.DynamicPseudoType, }, }, }, @@ -892,7 +929,44 @@ for_each = { }, }, }, - reference.Targets{}, + reference.Targets{ + { + LocalAddr: lang.Address{ + lang.RootStep{Name: "each"}, + lang.AttrStep{Name: "key"}, + }, + Type: cty.String, + Description: lang.Markdown("The map key (or set member) corresponding to this instance"), + RangePtr: &hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 1, Byte: 32}, + End: hcl.Pos{Line: 5, Column: 2, Byte: 93}, + }, + DefRangePtr: &hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 1, Byte: 32}, + End: hcl.Pos{Line: 2, Column: 9, Byte: 40}, + }, + }, + { + LocalAddr: lang.Address{ + lang.RootStep{Name: "each"}, + lang.AttrStep{Name: "value"}, + }, + Type: cty.DynamicPseudoType, + Description: lang.Markdown("The map value corresponding to this instance. (If a set was provided, this is the same as `each.key`.)"), + RangePtr: &hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 1, Byte: 32}, + End: hcl.Pos{Line: 5, Column: 2, Byte: 93}, + }, + DefRangePtr: &hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 1, Byte: 32}, + End: hcl.Pos{Line: 2, Column: 9, Byte: 40}, + }, + }, + }, `resource "aws_instance" "foo" { for_each = { a_group = "eastus" @@ -924,7 +998,7 @@ foo { }, { Label: "each.value", - Detail: "any type", + Detail: "dynamic", Kind: lang.TraversalCandidateKind, Description: lang.MarkupContent{ Value: "The map value corresponding to this instance. (If a set was provided, this is the same as `each.key`.)", diff --git a/decoder/decoder.go b/decoder/decoder.go index b6a7c4e8..5c931054 100644 --- a/decoder/decoder.go +++ b/decoder/decoder.go @@ -264,49 +264,29 @@ func countIndexReferenceTarget(attr *hcl.Attribute, bodyRange hcl.Range) referen } } -func foreachEachCandidate(editRng hcl.Range) []lang.Candidate { - return []lang.Candidate{ +func forEachReferenceTargets(attr *hcl.Attribute, bodyRange hcl.Range) reference.Targets { + return reference.Targets{ { - Label: "each.key", - Detail: "string", - Description: lang.MarkupContent{ - Value: "The map key (or set member) corresponding to this instance", - Kind: lang.MarkdownKind, - }, - Kind: lang.TraversalCandidateKind, - TextEdit: lang.TextEdit{ - NewText: "each.key", - Snippet: "each.key", - Range: editRng, + LocalAddr: lang.Address{ + lang.RootStep{Name: "each"}, + lang.AttrStep{Name: "key"}, }, + TargetableFromRangePtr: bodyRange.Ptr(), + Type: cty.String, + Description: lang.Markdown("The map key (or set member) corresponding to this instance"), + RangePtr: attr.Range.Ptr(), + DefRangePtr: attr.NameRange.Ptr(), }, { - Label: "each.value", - Detail: "any type", - Description: lang.MarkupContent{ - Value: "The map value corresponding to this instance. (If a set was provided, this is the same as `each.key`.)", - Kind: lang.MarkdownKind, - }, - Kind: lang.TraversalCandidateKind, - TextEdit: lang.TextEdit{ - NewText: "each.value", - Snippet: "each.value", - Range: editRng, + LocalAddr: lang.Address{ + lang.RootStep{Name: "each"}, + lang.AttrStep{Name: "value"}, }, + TargetableFromRangePtr: bodyRange.Ptr(), + Type: cty.DynamicPseudoType, + Description: lang.Markdown("The map value corresponding to this instance. (If a set was provided, this is the same as `each.key`.)"), + RangePtr: attr.Range.Ptr(), + DefRangePtr: attr.NameRange.Ptr(), }, } } - -func eachKeyHoverData(rng hcl.Range) *lang.HoverData { - return &lang.HoverData{ - Content: lang.Markdown("`each.key` _string_\n\nThe map key (or set member) corresponding to this instance"), - Range: rng, - } -} - -func eachValueHoverData(rng hcl.Range) *lang.HoverData { - return &lang.HoverData{ - Content: lang.Markdown("`each.value` _any type_\n\nThe map value corresponding to this instance. (If a set was provided, this is the same as `each.key`.)"), - Range: rng, - } -} diff --git a/decoder/expression_candidates.go b/decoder/expression_candidates.go index e452251a..83426ab7 100644 --- a/decoder/expression_candidates.go +++ b/decoder/expression_candidates.go @@ -328,10 +328,6 @@ func (d *PathDecoder) constraintToCandidates(ctx context.Context, constraint sch }, }) case schema.TraversalExpr: - if schema.ActiveForEachFromContext(ctx) && attr.Name != "for_each" { - candidates = append(candidates, foreachEachCandidate(editRng)...) - } - candidates = append(candidates, d.candidatesForTraversalConstraint(ctx, c, outerBodyRng, prefixRng, editRng)...) case schema.TupleConsExpr: candidates = append(candidates, lang.Candidate{ diff --git a/decoder/hover.go b/decoder/hover.go index ac133199..2d12f286 100644 --- a/decoder/hover.go +++ b/decoder/hover.go @@ -264,20 +264,6 @@ func (d *PathDecoder) hoverDataForExpr(ctx context.Context, expr hcl.Expression, }, nil } - address, err := lang.TraversalToAddress(e.AsTraversal()) - if err != nil { - return nil, err - } - - eachKeyAddr := lang.Address{lang.RootStep{Name: "each"}, lang.AttrStep{Name: "key"}} - eachValueAddr := lang.Address{lang.RootStep{Name: "each"}, lang.AttrStep{Name: "value"}} - - if address.Equals(eachKeyAddr) && schema.ActiveForEachFromContext(ctx) { - return eachKeyHoverData(expr.Range()), nil - } else if address.Equals(eachValueAddr) && schema.ActiveForEachFromContext(ctx) { - return eachValueHoverData(expr.Range()), nil - } - tes, ok := constraints.TraversalExprs() if ok { content, err := d.hoverContentForTraversalExpr(ctx, e.AsTraversal(), tes, pos) diff --git a/decoder/hover_test.go b/decoder/hover_test.go index abf5720b..fd7d7173 100644 --- a/decoder/hover_test.go +++ b/decoder/hover_test.go @@ -1252,7 +1252,7 @@ func TestDecoder_HoverAtPos_foreach_extension(t *testing.T) { `, hcl.Pos{Line: 2, Column: 8, Byte: 30}, &lang.HoverData{ - Content: lang.Markdown("`each.key` _string_\n\nThe map key (or set member) corresponding to this instance"), + Content: lang.Markdown("`each.key`\n_string_\n\nThe map key (or set member) corresponding to this instance"), Range: hcl.Range{ Filename: "test.tf", Start: hcl.Pos{Line: 2, Column: 8, Byte: 29}, @@ -1356,7 +1356,7 @@ func TestDecoder_HoverAtPos_foreach_extension(t *testing.T) { `, hcl.Pos{Line: 2, Column: 8, Byte: 30}, &lang.HoverData{ - Content: lang.Markdown("`each.value` _any type_\n\nThe map value corresponding to this instance. (If a set was provided, this is the same as `each.key`.)"), + Content: lang.Markdown("`each.value`\n_dynamic_\n\nThe map value corresponding to this instance. (If a set was provided, this is the same as `each.key`.)"), Range: hcl.Range{ Filename: "test.tf", Start: hcl.Pos{Line: 2, Column: 8, Byte: 29}, diff --git a/decoder/reference_targets.go b/decoder/reference_targets.go index 57b88413..7d7b3223 100644 --- a/decoder/reference_targets.go +++ b/decoder/reference_targets.go @@ -112,6 +112,10 @@ func (d *PathDecoder) decodeReferenceTargetsForBody(body hcl.Body, parentBlock * refs = append(refs, countIndexReferenceTarget(attr, *content.RangePtr)) continue } + if bodySchema.Extensions.ForEach && attr.Name == "for_each" && content.RangePtr != nil { + refs = append(refs, forEachReferenceTargets(attr, *content.RangePtr)...) + continue + } } attrSchema, ok := bodySchema.Attributes[attr.Name] if !ok {