Skip to content

Commit 2b1ae40

Browse files
authored
Fix missing hover for count and for_each expression (#166)
* Add test for missing hover on for_each expression * Add test for missing hover on count expression * Enable reference collection for count and for_each
1 parent e6f0252 commit 2b1ae40

File tree

2 files changed

+210
-9
lines changed

2 files changed

+210
-9
lines changed

decoder/hover_test.go

Lines changed: 196 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -935,7 +935,7 @@ func TestDecoder_HoverAtPos_typeDeclaration(t *testing.T) {
935935
}
936936
}
937937

938-
func TestDecoder_HoverAtPos_extension(t *testing.T) {
938+
func TestDecoder_HoverAtPos_extensions_count(t *testing.T) {
939939
testCases := []struct {
940940
name string
941941
bodySchema *schema.BodySchema
@@ -1106,7 +1106,7 @@ func TestDecoder_HoverAtPos_extension(t *testing.T) {
11061106
}
11071107
}
11081108

1109-
func TestDecoder_HoverAtPos_foreach_extension(t *testing.T) {
1109+
func TestDecoder_HoverAtPos_extension_for_each(t *testing.T) {
11101110
testCases := []struct {
11111111
name string
11121112
bodySchema *schema.BodySchema
@@ -1395,7 +1395,7 @@ func TestDecoder_HoverAtPos_foreach_extension(t *testing.T) {
13951395
}
13961396
}
13971397

1398-
func TestDecoder_HoverAtPos_dynamic_extension(t *testing.T) {
1398+
func TestDecoder_HoverAtPos_extensions_dynamic(t *testing.T) {
13991399
testCases := []struct {
14001400
name string
14011401
bodySchema *schema.BodySchema
@@ -1540,3 +1540,196 @@ func TestDecoder_HoverAtPos_dynamic_extension(t *testing.T) {
15401540
})
15411541
}
15421542
}
1543+
1544+
func TestDecoder_HoverAtPos_extensions_references(t *testing.T) {
1545+
testCases := []struct {
1546+
name string
1547+
bodySchema *schema.BodySchema
1548+
config string
1549+
pos hcl.Pos
1550+
expectedData *lang.HoverData
1551+
}{
1552+
{
1553+
"for_each var reference",
1554+
&schema.BodySchema{
1555+
Blocks: map[string]*schema.BlockSchema{
1556+
"myblock": {
1557+
Labels: []*schema.LabelSchema{
1558+
{Name: "type", IsDepKey: true},
1559+
{Name: "name"},
1560+
},
1561+
Body: &schema.BodySchema{
1562+
Extensions: &schema.BodyExtensions{
1563+
ForEach: true,
1564+
},
1565+
Attributes: map[string]*schema.AttributeSchema{
1566+
"foo": {
1567+
IsOptional: true,
1568+
Expr: schema.ExprConstraints{
1569+
schema.TraversalExpr{
1570+
OfType: cty.String,
1571+
},
1572+
},
1573+
},
1574+
},
1575+
},
1576+
},
1577+
"variable": {
1578+
Address: &schema.BlockAddrSchema{
1579+
Steps: []schema.AddrStep{
1580+
schema.StaticStep{Name: "var"},
1581+
schema.LabelStep{Index: 0},
1582+
},
1583+
ScopeId: lang.ScopeId("variable"),
1584+
AsReference: true,
1585+
AsTypeOf: &schema.BlockAsTypeOf{
1586+
AttributeExpr: "type",
1587+
AttributeValue: "default",
1588+
},
1589+
},
1590+
Labels: []*schema.LabelSchema{
1591+
{Name: "name"},
1592+
},
1593+
Body: &schema.BodySchema{
1594+
Attributes: map[string]*schema.AttributeSchema{
1595+
"type": {
1596+
Expr: schema.ExprConstraints{schema.TypeDeclarationExpr{}},
1597+
IsOptional: true,
1598+
},
1599+
},
1600+
},
1601+
},
1602+
},
1603+
},
1604+
`myblock "foo" "bar" {
1605+
foo = each.value
1606+
for_each = var.name
1607+
}
1608+
variable "name" {
1609+
value = { key = "value" }
1610+
}
1611+
`,
1612+
hcl.Pos{Line: 3, Column: 19, Byte: 59},
1613+
&lang.HoverData{
1614+
Content: lang.Markdown("`var.name`\n_dynamic_"),
1615+
Range: hcl.Range{
1616+
Filename: "test.tf",
1617+
Start: hcl.Pos{Line: 3, Column: 14, Byte: 54},
1618+
End: hcl.Pos{Line: 3, Column: 22, Byte: 62},
1619+
},
1620+
},
1621+
},
1622+
{
1623+
"count var reference",
1624+
&schema.BodySchema{
1625+
Blocks: map[string]*schema.BlockSchema{
1626+
"myblock": {
1627+
Labels: []*schema.LabelSchema{
1628+
{Name: "type", IsDepKey: true},
1629+
{Name: "name"},
1630+
},
1631+
Body: &schema.BodySchema{
1632+
Extensions: &schema.BodyExtensions{
1633+
Count: true,
1634+
},
1635+
Attributes: map[string]*schema.AttributeSchema{
1636+
"foo": {
1637+
IsOptional: true,
1638+
Expr: schema.ExprConstraints{
1639+
schema.TraversalExpr{
1640+
OfType: cty.Number,
1641+
},
1642+
},
1643+
},
1644+
},
1645+
},
1646+
},
1647+
"variable": {
1648+
Address: &schema.BlockAddrSchema{
1649+
Steps: []schema.AddrStep{
1650+
schema.StaticStep{Name: "var"},
1651+
schema.LabelStep{Index: 0},
1652+
},
1653+
ScopeId: lang.ScopeId("variable"),
1654+
AsReference: true,
1655+
AsTypeOf: &schema.BlockAsTypeOf{
1656+
AttributeExpr: "type",
1657+
AttributeValue: "default",
1658+
},
1659+
},
1660+
Labels: []*schema.LabelSchema{
1661+
{Name: "name"},
1662+
},
1663+
Body: &schema.BodySchema{
1664+
Attributes: map[string]*schema.AttributeSchema{
1665+
"type": {
1666+
Expr: schema.ExprConstraints{schema.TypeDeclarationExpr{}},
1667+
IsOptional: true,
1668+
},
1669+
},
1670+
},
1671+
},
1672+
},
1673+
},
1674+
`myblock "foo" "bar" {
1675+
foo = count.index
1676+
count = var.name
1677+
}
1678+
variable "name" {
1679+
value = 4
1680+
}
1681+
`,
1682+
hcl.Pos{Line: 3, Column: 16, Byte: 57},
1683+
&lang.HoverData{
1684+
Content: lang.Markdown("`var.name`\n_dynamic_"),
1685+
Range: hcl.Range{
1686+
Filename: "test.tf",
1687+
Start: hcl.Pos{Line: 3, Column: 11, Byte: 52},
1688+
End: hcl.Pos{Line: 3, Column: 19, Byte: 60},
1689+
},
1690+
},
1691+
},
1692+
}
1693+
1694+
for i, tc := range testCases {
1695+
t.Run(fmt.Sprintf("%d-%s", i, tc.name), func(t *testing.T) {
1696+
ctx := context.Background()
1697+
1698+
f, diags := hclsyntax.ParseConfig([]byte(tc.config), "test.tf", hcl.InitialPos)
1699+
if diags != nil {
1700+
t.Fatal(diags)
1701+
}
1702+
1703+
d := testPathDecoder(t, &PathContext{
1704+
Schema: tc.bodySchema,
1705+
Files: map[string]*hcl.File{
1706+
"test.tf": f,
1707+
},
1708+
})
1709+
targets, err := d.CollectReferenceTargets()
1710+
if err != nil {
1711+
t.Fatal(err)
1712+
}
1713+
origins, err := d.CollectReferenceOrigins()
1714+
if err != nil {
1715+
t.Fatal(err)
1716+
}
1717+
d = testPathDecoder(t, &PathContext{
1718+
Schema: tc.bodySchema,
1719+
Files: map[string]*hcl.File{
1720+
"test.tf": f,
1721+
},
1722+
ReferenceTargets: targets,
1723+
ReferenceOrigins: origins,
1724+
})
1725+
1726+
data, err := d.HoverAtPos(ctx, "test.tf", tc.pos)
1727+
if err != nil {
1728+
t.Fatal(err)
1729+
}
1730+
if diff := cmp.Diff(tc.expectedData, data, ctydebug.CmpOptions); diff != "" {
1731+
t.Fatalf("hover data mismatch: %s", diff)
1732+
}
1733+
})
1734+
}
1735+
}

decoder/reference_origins.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,21 @@ func (d *PathDecoder) referenceOriginsInBody(body hcl.Body, bodySchema *schema.B
122122
content := decodeBody(body, bodySchema)
123123

124124
for _, attr := range content.Attributes {
125-
aSchema, ok := bodySchema.Attributes[attr.Name]
126-
if !ok {
127-
if bodySchema.AnyAttribute == nil {
128-
// skip unknown attribute
129-
continue
125+
var aSchema *schema.AttributeSchema
126+
if bodySchema.Extensions != nil && bodySchema.Extensions.Count && attr.Name == "count" {
127+
aSchema = countAttributeSchema()
128+
} else if bodySchema.Extensions != nil && bodySchema.Extensions.ForEach && attr.Name == "for_each" {
129+
aSchema = forEachAttributeSchema()
130+
} else {
131+
var ok bool
132+
aSchema, ok = bodySchema.Attributes[attr.Name]
133+
if !ok {
134+
if bodySchema.AnyAttribute == nil {
135+
// skip unknown attribute
136+
continue
137+
}
138+
aSchema = bodySchema.AnyAttribute
130139
}
131-
aSchema = bodySchema.AnyAttribute
132140
}
133141

134142
if aSchema.OriginForTarget != nil {

0 commit comments

Comments
 (0)