Skip to content

Commit 1f03be9

Browse files
committed
semantic tokens
1 parent f235183 commit 1f03be9

File tree

3 files changed

+310
-35
lines changed

3 files changed

+310
-35
lines changed

decoder/semantic_tokens.go

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
package decoder
22

33
import (
4+
"context"
45
"sort"
56

7+
"github.com/zclconf/go-cty/cty"
8+
9+
icontext "github.com/hashicorp/hcl-lang/context"
610
"github.com/hashicorp/hcl-lang/lang"
711
"github.com/hashicorp/hcl-lang/reference"
812
"github.com/hashicorp/hcl-lang/schema"
913
"github.com/hashicorp/hcl/v2"
1014
"github.com/hashicorp/hcl/v2/hclsyntax"
11-
"github.com/zclconf/go-cty/cty"
1215
)
1316

1417
// SemanticTokensInFile returns a sequence of semantic tokens
1518
// within the config file.
16-
func (d *PathDecoder) SemanticTokensInFile(filename string) ([]lang.SemanticToken, error) {
19+
func (d *PathDecoder) SemanticTokensInFile(ctx context.Context, filename string) ([]lang.SemanticToken, error) {
1720
f, err := d.fileByName(filename)
1821
if err != nil {
1922
return nil, err
@@ -28,7 +31,7 @@ func (d *PathDecoder) SemanticTokensInFile(filename string) ([]lang.SemanticToke
2831
return []lang.SemanticToken{}, nil
2932
}
3033

31-
tokens := d.tokensForBody(body, d.pathCtx.Schema, []lang.SemanticTokenModifier{})
34+
tokens := d.tokensForBody(ctx, body, d.pathCtx.Schema, []lang.SemanticTokenModifier{})
3235

3336
sort.Slice(tokens, func(i, j int) bool {
3437
return tokens[i].Range.Start.Byte < tokens[j].Range.Start.Byte
@@ -37,21 +40,43 @@ func (d *PathDecoder) SemanticTokensInFile(filename string) ([]lang.SemanticToke
3740
return tokens, nil
3841
}
3942

40-
func (d *PathDecoder) tokensForBody(body *hclsyntax.Body, bodySchema *schema.BodySchema, parentModifiers []lang.SemanticTokenModifier) []lang.SemanticToken {
43+
func (d *PathDecoder) tokensForBody(ctx context.Context, body *hclsyntax.Body, bodySchema *schema.BodySchema, parentModifiers []lang.SemanticTokenModifier) []lang.SemanticToken {
4144
tokens := make([]lang.SemanticToken, 0)
4245

4346
if bodySchema == nil {
4447
return tokens
4548
}
4649

50+
if bodySchema.Extensions != nil {
51+
ctx = icontext.WithExtensions(ctx, bodySchema.Extensions)
52+
if bodySchema.Extensions.Count {
53+
if _, ok := body.Attributes["count"]; ok {
54+
// append to context we need count provided
55+
ctx = icontext.WithActiveCount(ctx)
56+
}
57+
}
58+
}
59+
4760
for name, attr := range body.Attributes {
61+
4862
attrSchema, ok := bodySchema.Attributes[name]
4963
if !ok {
50-
if bodySchema.AnyAttribute == nil {
51-
// unknown attribute
52-
continue
64+
if bodySchema.Extensions != nil && name == "count" && bodySchema.Extensions.Count {
65+
attrSchema = &schema.AttributeSchema{
66+
Description: lang.MarkupContent{
67+
Kind: lang.MarkdownKind,
68+
Value: "**count** _optional, number_\n\nThe distinct index number (starting with 0) corresponding to the instance",
69+
},
70+
IsOptional: true,
71+
Expr: schema.LiteralTypeOnly(cty.Number),
72+
}
73+
} else {
74+
if bodySchema.AnyAttribute == nil {
75+
// unknown attribute
76+
continue
77+
}
78+
attrSchema = bodySchema.AnyAttribute
5379
}
54-
attrSchema = bodySchema.AnyAttribute
5580
}
5681

5782
attrModifiers := make([]lang.SemanticTokenModifier, 0)
@@ -65,7 +90,7 @@ func (d *PathDecoder) tokensForBody(body *hclsyntax.Body, bodySchema *schema.Bod
6590
})
6691

6792
ec := ExprConstraints(attrSchema.Expr)
68-
tokens = append(tokens, d.tokensForExpression(attr.Expr, ec)...)
93+
tokens = append(tokens, d.tokensForExpression(ctx, attr.Expr, ec)...)
6994
}
7095

7196
for _, block := range body.Blocks {
@@ -106,19 +131,19 @@ func (d *PathDecoder) tokensForBody(body *hclsyntax.Body, bodySchema *schema.Bod
106131
}
107132

108133
if block.Body != nil {
109-
tokens = append(tokens, d.tokensForBody(block.Body, blockSchema.Body, blockModifiers)...)
134+
tokens = append(tokens, d.tokensForBody(ctx, block.Body, blockSchema.Body, blockModifiers)...)
110135
}
111136

112137
depSchema, _, ok := NewBlockSchema(blockSchema).DependentBodySchema(block.AsHCLBlock())
113138
if ok {
114-
tokens = append(tokens, d.tokensForBody(block.Body, depSchema, []lang.SemanticTokenModifier{})...)
139+
tokens = append(tokens, d.tokensForBody(ctx, block.Body, depSchema, []lang.SemanticTokenModifier{})...)
115140
}
116141
}
117142

118143
return tokens
119144
}
120145

121-
func (d *PathDecoder) tokensForExpression(expr hclsyntax.Expression, constraints ExprConstraints) []lang.SemanticToken {
146+
func (d *PathDecoder) tokensForExpression(ctx context.Context, expr hclsyntax.Expression, constraints ExprConstraints) []lang.SemanticToken {
122147
tokens := make([]lang.SemanticToken, 0)
123148

124149
switch eType := expr.(type) {
@@ -135,6 +160,31 @@ func (d *PathDecoder) tokensForExpression(expr hclsyntax.Expression, constraints
135160
}
136161
}
137162

163+
address, _ := lang.TraversalToAddress(eType.AsTraversal())
164+
countIndexAttr := lang.Address{
165+
lang.RootStep{
166+
Name: "count",
167+
},
168+
lang.AttrStep{
169+
Name: "index",
170+
},
171+
}
172+
countAvailable := icontext.ActiveCountFromContext(ctx)
173+
if address.Equals(countIndexAttr) && countAvailable {
174+
traversal := eType.AsTraversal()
175+
tokens = append(tokens, lang.SemanticToken{
176+
Type: lang.TokenTraversalStep,
177+
Modifiers: []lang.SemanticTokenModifier{},
178+
Range: traversal[0].SourceRange(),
179+
})
180+
tokens = append(tokens, lang.SemanticToken{
181+
Type: lang.TokenTraversalStep,
182+
Modifiers: []lang.SemanticTokenModifier{},
183+
Range: traversal[1].SourceRange(),
184+
})
185+
return tokens
186+
}
187+
138188
tes, ok := constraints.TraversalExprs()
139189
if ok && d.pathCtx.ReferenceTargets != nil {
140190
traversal := eType.AsTraversal()
@@ -231,7 +281,7 @@ func (d *PathDecoder) tokensForExpression(expr hclsyntax.Expression, constraints
231281
Range: eType.NameRange,
232282
})
233283
for _, arg := range eType.Args {
234-
tokens = append(tokens, d.tokensForExpression(arg, constraints)...)
284+
tokens = append(tokens, d.tokensForExpression(ctx, arg, constraints)...)
235285
}
236286
return tokens
237287
}
@@ -249,29 +299,29 @@ func (d *PathDecoder) tokensForExpression(expr hclsyntax.Expression, constraints
249299
return tokenForTypedExpression(eType, cty.String)
250300
}
251301
case *hclsyntax.TemplateWrapExpr:
252-
return d.tokensForExpression(eType.Wrapped, constraints)
302+
return d.tokensForExpression(ctx, eType.Wrapped, constraints)
253303
case *hclsyntax.TupleConsExpr:
254304
tc, ok := constraints.TupleConsExpr()
255305
if ok {
256306
ec := ExprConstraints(tc.AnyElem)
257307
for _, expr := range eType.Exprs {
258-
tokens = append(tokens, d.tokensForExpression(expr, ec)...)
308+
tokens = append(tokens, d.tokensForExpression(ctx, expr, ec)...)
259309
}
260310
return tokens
261311
}
262312
se, ok := constraints.SetExpr()
263313
if ok {
264314
ec := ExprConstraints(se.Elem)
265315
for _, expr := range eType.Exprs {
266-
tokens = append(tokens, d.tokensForExpression(expr, ec)...)
316+
tokens = append(tokens, d.tokensForExpression(ctx, expr, ec)...)
267317
}
268318
return tokens
269319
}
270320
le, ok := constraints.ListExpr()
271321
if ok {
272322
ec := ExprConstraints(le.Elem)
273323
for _, expr := range eType.Exprs {
274-
tokens = append(tokens, d.tokensForExpression(expr, ec)...)
324+
tokens = append(tokens, d.tokensForExpression(ctx, expr, ec)...)
275325
}
276326
return tokens
277327
}
@@ -282,7 +332,7 @@ func (d *PathDecoder) tokensForExpression(expr hclsyntax.Expression, constraints
282332
break
283333
}
284334
ec := ExprConstraints(te.Elems[i])
285-
tokens = append(tokens, d.tokensForExpression(expr, ec)...)
335+
tokens = append(tokens, d.tokensForExpression(ctx, expr, ec)...)
286336
}
287337
return tokens
288338
}
@@ -316,7 +366,7 @@ func (d *PathDecoder) tokensForExpression(expr hclsyntax.Expression, constraints
316366
})
317367

318368
ec := ExprConstraints(attr.Expr)
319-
tokens = append(tokens, d.tokensForExpression(item.ValueExpr, ec)...)
369+
tokens = append(tokens, d.tokensForExpression(ctx, item.ValueExpr, ec)...)
320370
}
321371
return tokens
322372
}
@@ -329,7 +379,7 @@ func (d *PathDecoder) tokensForExpression(expr hclsyntax.Expression, constraints
329379
Range: item.KeyExpr.Range(),
330380
})
331381
ec := ExprConstraints(me.Elem)
332-
tokens = append(tokens, d.tokensForExpression(item.ValueExpr, ec)...)
382+
tokens = append(tokens, d.tokensForExpression(ctx, item.ValueExpr, ec)...)
333383
}
334384
return tokens
335385
}
@@ -343,7 +393,7 @@ func (d *PathDecoder) tokensForExpression(expr hclsyntax.Expression, constraints
343393
}
344394
_, ok = constraints.TypeDeclarationExpr()
345395
if ok {
346-
return d.tokensForObjectConsTypeDeclarationExpr(eType, constraints)
396+
return d.tokensForObjectConsTypeDeclarationExpr(ctx, eType, constraints)
347397
}
348398
case *hclsyntax.LiteralValueExpr:
349399
valType := eType.Val.Type()
@@ -389,7 +439,7 @@ func isComplexTypeDeclaration(funcName string) bool {
389439
return false
390440
}
391441

392-
func (d *PathDecoder) tokensForObjectConsTypeDeclarationExpr(expr *hclsyntax.ObjectConsExpr, constraints ExprConstraints) []lang.SemanticToken {
442+
func (d *PathDecoder) tokensForObjectConsTypeDeclarationExpr(ctx context.Context, expr *hclsyntax.ObjectConsExpr, constraints ExprConstraints) []lang.SemanticToken {
393443
tokens := make([]lang.SemanticToken, 0)
394444
for _, item := range expr.Items {
395445
key, _ := item.KeyExpr.Value(nil)
@@ -405,7 +455,7 @@ func (d *PathDecoder) tokensForObjectConsTypeDeclarationExpr(expr *hclsyntax.Obj
405455
Range: item.KeyExpr.Range(),
406456
})
407457

408-
tokens = append(tokens, d.tokensForExpression(item.ValueExpr, constraints)...)
458+
tokens = append(tokens, d.tokensForExpression(ctx, item.ValueExpr, constraints)...)
409459
}
410460
return tokens
411461
}

decoder/semantic_tokens_expr_test.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
package decoder
22

33
import (
4+
"context"
45
"fmt"
56
"testing"
67

78
"github.com/google/go-cmp/cmp"
9+
"github.com/zclconf/go-cty/cty"
10+
811
"github.com/hashicorp/hcl-lang/lang"
912
"github.com/hashicorp/hcl-lang/reference"
1013
"github.com/hashicorp/hcl-lang/schema"
1114
"github.com/hashicorp/hcl/v2"
1215
"github.com/hashicorp/hcl/v2/hclsyntax"
13-
"github.com/zclconf/go-cty/cty"
1416
)
1517

1618
func TestDecoder_SemanticTokensInFile_expressions(t *testing.T) {
@@ -1433,6 +1435,8 @@ EOT
14331435
},
14341436
}
14351437

1438+
ctx := context.Background()
1439+
14361440
for i, tc := range testCases {
14371441
t.Run(fmt.Sprintf("%d-%s", i, tc.name), func(t *testing.T) {
14381442
bodySchema := &schema.BodySchema{
@@ -1451,7 +1455,7 @@ EOT
14511455
},
14521456
})
14531457

1454-
tokens, err := d.SemanticTokensInFile("test.tf")
1458+
tokens, err := d.SemanticTokensInFile(ctx, "test.tf")
14551459
if err != nil {
14561460
t.Fatal(err)
14571461
}
@@ -1912,6 +1916,8 @@ func TestDecoder_SemanticTokensInFile_traversalExpression(t *testing.T) {
19121916
},
19131917
}
19141918

1919+
ctx := context.Background()
1920+
19151921
for i, tc := range testCases {
19161922
t.Run(fmt.Sprintf("%d-%s", i, tc.name), func(t *testing.T) {
19171923
bodySchema := &schema.BodySchema{
@@ -1931,7 +1937,7 @@ func TestDecoder_SemanticTokensInFile_traversalExpression(t *testing.T) {
19311937
},
19321938
})
19331939

1934-
tokens, err := d.SemanticTokensInFile("test.tf")
1940+
tokens, err := d.SemanticTokensInFile(ctx, "test.tf")
19351941
if err != nil {
19361942
t.Fatal(err)
19371943
}
@@ -2227,6 +2233,8 @@ func TestDecoder_SemanticTokensInFile_typeDeclaration(t *testing.T) {
22272233
},
22282234
}
22292235

2236+
ctx := context.Background()
2237+
22302238
for i, tc := range testCases {
22312239
t.Run(fmt.Sprintf("%d-%s", i, tc.name), func(t *testing.T) {
22322240
bodySchema := &schema.BodySchema{
@@ -2245,7 +2253,7 @@ func TestDecoder_SemanticTokensInFile_typeDeclaration(t *testing.T) {
22452253
},
22462254
})
22472255

2248-
tokens, err := d.SemanticTokensInFile("test.tf")
2256+
tokens, err := d.SemanticTokensInFile(ctx, "test.tf")
22492257
if err != nil {
22502258
t.Fatal(err)
22512259
}

0 commit comments

Comments
 (0)