From 17d582c4ccc32341c7a1e2bc16640c7535f2c472 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Fri, 23 Sep 2022 09:56:43 -0400 Subject: [PATCH 01/23] Support count and count.index completion in blocks This provides completion hints inside blocks for `count.index` references within resource, data and module blocks anywhere the `count` meta-argument is supported. It detects if count is used already and does not suggest duplicates. --- context/context.go | 28 +++ decoder/body_candidates.go | 25 ++ decoder/body_extensions_test.go | 387 +++++++++++++++++++++++++++++++ decoder/candidates.go | 11 + decoder/candidates_test.go | 16 +- decoder/expression_candidates.go | 27 ++- schema/body_schema.go | 24 ++ 7 files changed, 508 insertions(+), 10 deletions(-) create mode 100644 context/context.go create mode 100644 decoder/body_extensions_test.go diff --git a/context/context.go b/context/context.go new file mode 100644 index 00000000..4478852c --- /dev/null +++ b/context/context.go @@ -0,0 +1,28 @@ +package context + +import ( + "context" + + "github.com/hashicorp/hcl-lang/schema" +) + +type bodyExtCtxKey struct{} + +type bodyActiveCountCtxKey struct{} + +func WithExtensions(ctx context.Context, ext *schema.BodyExtensions) context.Context { + return context.WithValue(ctx, bodyExtCtxKey{}, ext) +} + +func WithActiveCount(ctx context.Context) context.Context { + return context.WithValue(ctx, bodyActiveCountCtxKey{}, true) +} + +func ExtensionsFromContext(ctx context.Context) (*schema.BodyExtensions, bool) { + ext, ok := ctx.Value(bodyExtCtxKey{}).(*schema.BodyExtensions) + return ext, ok +} + +func ActiveCountFromContext(ctx context.Context) bool { + return ctx.Value(bodyActiveCountCtxKey{}) != nil +} diff --git a/decoder/body_candidates.go b/decoder/body_candidates.go index 6f388f96..22c811c7 100644 --- a/decoder/body_candidates.go +++ b/decoder/body_candidates.go @@ -17,6 +17,17 @@ func (d *PathDecoder) bodySchemaCandidates(body *hclsyntax.Body, schema *schema. candidates := lang.NewCandidates() count := 0 + if schema.Extensions != nil { + // check if this schema supports Count attribute + if schema.Extensions.Count { + // check if Count is already used inside this body, so we don't + // suggest a duplicate + if _, ok := body.Attributes["count"]; !ok { + candidates.List = append(candidates.List, countAttributeCandidate(editRng)) + } + } + } + if len(schema.Attributes) > 0 { attrNames := sortedAttributeNames(schema.Attributes) for _, name := range attrNames { @@ -135,3 +146,17 @@ func isBlockDeclarable(body *hclsyntax.Body, blockType string, bSchema *schema.B } return true } + +func countAttributeCandidate(editRng hcl.Range) lang.Candidate { + return lang.Candidate{ + Label: "count", + Detail: "optional, number", + Description: lang.PlainText("The distinct index number (starting with 0) corresponding to the instance"), + Kind: lang.AttributeCandidateKind, + TextEdit: lang.TextEdit{ + NewText: "count", + Snippet: "count = ${1:1}", + Range: editRng, + }, + } +} diff --git a/decoder/body_extensions_test.go b/decoder/body_extensions_test.go new file mode 100644 index 00000000..8879c748 --- /dev/null +++ b/decoder/body_extensions_test.go @@ -0,0 +1,387 @@ +package decoder + +import ( + "context" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/zclconf/go-cty/cty" + + "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/hcl-lang/schema" + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" +) + +func TestCompletionAtPos_BodySchema_Extensions(t *testing.T) { + ctx := context.Background() + + testCases := []struct { + testName string + bodySchema *schema.BodySchema + cfg string + pos hcl.Pos + expectedCandidates lang.Candidates + }{ + { + "count attribute completion", + &schema.BodySchema{ + Blocks: map[string]*schema.BlockSchema{ + "resource": { + Labels: []*schema.LabelSchema{ + { + Name: "aws_instance", + }, + { + Name: "foo", + }, + }, + Body: &schema.BodySchema{ + Extensions: &schema.BodyExtensions{ + Count: true, + }, + }, + }, + }, + }, + `resource "aws_instance" "foo" { + +}`, + hcl.Pos{ + Line: 2, + Column: 1, + Byte: 32, + }, + lang.CompleteCandidates([]lang.Candidate{ + { + Label: "count", + Description: lang.MarkupContent{ + Value: "The distinct index number (starting with 0) corresponding to the instance", + Kind: lang.PlainTextKind, + }, + Detail: "optional, number", + TextEdit: lang.TextEdit{Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 2, + Column: 1, + Byte: 32, + }, + End: hcl.Pos{ + Line: 2, + Column: 1, + Byte: 32, + }, + }, NewText: "count", Snippet: "count = ${1:1}"}, + Kind: lang.AttributeCandidateKind, + }, + }), + }, + { + "count attribute completion does not complete count when extensions not enabled", + &schema.BodySchema{ + Blocks: map[string]*schema.BlockSchema{ + "resource": { + Labels: []*schema.LabelSchema{ + { + Name: "aws_instance", + }, + { + Name: "foo", + }, + }, + Body: &schema.BodySchema{ + Extensions: &schema.BodyExtensions{ + Count: false, + }, + }, + }, + }, + }, + `resource "aws_instance" "foo" { + +}`, + hcl.Pos{ + Line: 2, + Column: 1, + Byte: 32, + }, + lang.CompleteCandidates([]lang.Candidate{}), + }, + { + "count.index does not complete when not needed", + &schema.BodySchema{ + Blocks: map[string]*schema.BlockSchema{ + "resource": { + Labels: []*schema.LabelSchema{ + { + Name: "aws_instance", + }, + { + Name: "foo", + }, + }, + Body: &schema.BodySchema{ + Attributes: map[string]*schema.AttributeSchema{ + "cpu_count": { + IsOptional: true, + Expr: schema.ExprConstraints{ + schema.TraversalExpr{ + OfType: cty.Number, + }, + }, + }, + }, + Extensions: &schema.BodyExtensions{ + Count: true, + }, + }, + }, + }, + }, + `resource "aws_instance" "foo" { + cpu_count = +}`, + hcl.Pos{ + Line: 2, + Column: 8, + Byte: 44, + }, + lang.CompleteCandidates([]lang.Candidate{}), + }, + { + "count.index value completion", + &schema.BodySchema{ + Blocks: map[string]*schema.BlockSchema{ + "resource": { + Labels: []*schema.LabelSchema{ + { + Name: "aws_instance", + }, + { + Name: "foo", + }, + }, + Body: &schema.BodySchema{ + Attributes: map[string]*schema.AttributeSchema{ + "cpu_count": { + IsOptional: true, + Expr: schema.ExprConstraints{ + schema.TraversalExpr{ + OfType: cty.Number, + }, + }, + }, + }, + Extensions: &schema.BodyExtensions{ + Count: true, + }, + }, + }, + }, + }, + `resource "aws_instance" "foo" { + count = 4 + cpu_count = +}`, + hcl.Pos{ + Line: 3, + Column: 14, + Byte: 55, + }, + lang.CompleteCandidates([]lang.Candidate{ + { + Label: "count.index", + Description: lang.MarkupContent{ + Value: "The distinct index number (starting with 0) corresponding to the instance", + Kind: lang.PlainTextKind, + }, + Detail: "number", + TextEdit: lang.TextEdit{Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 3, + Column: 13, + Byte: 55, + }, + End: hcl.Pos{ + Line: 3, + Column: 13, + Byte: 55, + }, + }, NewText: "count.index", Snippet: "count.index"}, + Kind: lang.TraversalCandidateKind, + }, + }), + }, + { + "count does not complete more than once", + &schema.BodySchema{ + Blocks: map[string]*schema.BlockSchema{ + "resource": { + Labels: []*schema.LabelSchema{ + { + Name: "aws_instance", + }, + { + Name: "foo", + }, + }, + Body: &schema.BodySchema{ + Extensions: &schema.BodyExtensions{ + Count: true, + }, + }, + }, + }, + }, + `resource "aws_instance" "foo" { + count = 4 + +}`, + hcl.Pos{ + Line: 3, + Column: 1, + Byte: 43, + }, + lang.CompleteCandidates([]lang.Candidate{}), + }, + { + "count.index does not complete when extension not enabled", + &schema.BodySchema{ + Blocks: map[string]*schema.BlockSchema{ + "resource": { + Labels: []*schema.LabelSchema{ + { + Name: "aws_instance", + }, + { + Name: "foo", + }, + }, + Body: &schema.BodySchema{ + Attributes: map[string]*schema.AttributeSchema{ + "cpu_count": { + IsOptional: true, + Expr: schema.ExprConstraints{ + schema.TraversalExpr{ + OfType: cty.Number, + }, + }, + }, + }, + Extensions: &schema.BodyExtensions{ + Count: false, + }, + }, + }, + }, + }, + `resource "aws_instance" "foo" { + cpu_count = +}`, + hcl.Pos{ + Line: 2, + Column: 8, + Byte: 44, + }, + lang.CompleteCandidates([]lang.Candidate{}), + }, + { + "count.index completes when inside nested blocks", + &schema.BodySchema{ + Blocks: map[string]*schema.BlockSchema{ + "resource": { + Labels: []*schema.LabelSchema{ + { + Name: "aws_instance", + }, + { + Name: "foo", + }, + }, + Body: &schema.BodySchema{ + Blocks: map[string]*schema.BlockSchema{ + "foo": { + Body: &schema.BodySchema{ + Attributes: map[string]*schema.AttributeSchema{ + "cpu_count": { + IsOptional: true, + Expr: schema.ExprConstraints{ + schema.TraversalExpr{ + OfType: cty.Number, + }, + }, + }, + }, + }, + }, + }, + Extensions: &schema.BodyExtensions{ + Count: true, + }, + }, + }, + }, + }, + `resource "aws_instance" "foo" { + count = 4 + foo { + cpu_count = + } +}`, + hcl.Pos{ + Line: 4, + Column: 17, + Byte: 67, + }, + lang.CompleteCandidates([]lang.Candidate{ + { + Label: "count.index", + Description: lang.MarkupContent{ + Value: "The distinct index number (starting with 0) corresponding to the instance", + Kind: lang.PlainTextKind, + }, + Detail: "number", + TextEdit: lang.TextEdit{Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 4, + Column: 16, + Byte: 67, + }, + End: hcl.Pos{ + Line: 4, + Column: 16, + Byte: 67, + }, + }, NewText: "count.index", Snippet: "count.index"}, + Kind: lang.TraversalCandidateKind, + }, + }), + }, + } + + for i, tc := range testCases { + t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) { + f, _ := hclsyntax.ParseConfig([]byte(tc.cfg), "test.tf", hcl.InitialPos) + + d := testPathDecoder(t, &PathContext{ + Schema: tc.bodySchema, + Files: map[string]*hcl.File{ + "test.tf": f, + }, + }) + + candidates, err := d.CandidatesAtPos(ctx, "test.tf", tc.pos) + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(tc.expectedCandidates, candidates); diff != "" { + t.Fatalf("unexpected candidates: %s", diff) + } + }) + } +} diff --git a/decoder/candidates.go b/decoder/candidates.go index da68458b..13b7d07e 100644 --- a/decoder/candidates.go +++ b/decoder/candidates.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + icontext "github.com/hashicorp/hcl-lang/context" "github.com/hashicorp/hcl-lang/lang" "github.com/hashicorp/hcl-lang/schema" "github.com/hashicorp/hcl/v2" @@ -48,6 +49,16 @@ func (d *PathDecoder) candidatesAtPos(ctx context.Context, body *hclsyntax.Body, filename := body.Range().Filename + if bodySchema.Extensions != nil { + ctx = icontext.WithExtensions(ctx, bodySchema.Extensions) + if bodySchema.Extensions.Count { + if _, ok := body.Attributes["count"]; ok { + // append to context we need count completed + ctx = icontext.WithActiveCount(ctx) + } + } + } + for _, attr := range body.Attributes { if d.isPosInsideAttrExpr(attr, pos) { if aSchema, ok := bodySchema.Attributes[attr.Name]; ok { diff --git a/decoder/candidates_test.go b/decoder/candidates_test.go index 5e5ffa2a..95f7a1ba 100644 --- a/decoder/candidates_test.go +++ b/decoder/candidates_test.go @@ -8,13 +8,14 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/zclconf/go-cty-debug/ctydebug" + "github.com/zclconf/go-cty/cty" + "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/hashicorp/hcl/v2/json" - "github.com/zclconf/go-cty-debug/ctydebug" - "github.com/zclconf/go-cty/cty" ) func TestDecoder_CandidatesAtPos_noSchema(t *testing.T) { @@ -1575,9 +1576,10 @@ func TestDecoder_CandidatesAtPos_incompleteLabel(t *testing.T) { resourceSchema := &schema.BlockSchema{ Labels: resourceLabelSchema, Body: &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "count": {Expr: schema.LiteralTypeOnly(cty.Number), IsOptional: true}, + Extensions: &schema.BodyExtensions{ + Count: true, }, + Attributes: map[string]*schema.AttributeSchema{}, }, DependentBody: map[schema.SchemaKey]*schema.BodySchema{ schema.NewSchemaKey(schema.DependencyKeys{ @@ -1638,7 +1640,11 @@ resource "any" "ref" { hcl.Pos{Line: 3, Column: 5, Byte: 28}, lang.CompleteCandidates([]lang.Candidate{ { - Label: "count", + Label: "count", + Description: lang.MarkupContent{ + Value: "The distinct index number (starting with 0) corresponding to the instance", + Kind: lang.PlainTextKind, + }, Detail: "optional, number", TextEdit: lang.TextEdit{ Range: hcl.Range{ diff --git a/decoder/expression_candidates.go b/decoder/expression_candidates.go index b75faf28..947302f7 100644 --- a/decoder/expression_candidates.go +++ b/decoder/expression_candidates.go @@ -7,12 +7,14 @@ import ( "strconv" "strings" + "github.com/zclconf/go-cty/cty" + + icontext "github.com/hashicorp/hcl-lang/context" "github.com/hashicorp/hcl-lang/lang" "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 (d *PathDecoder) attrValueCandidatesAtPos(ctx context.Context, attr *hclsyntax.Attribute, schema *schema.AttributeSchema, outerBodyRng hcl.Range, pos hcl.Pos) (lang.Candidates, error) { @@ -35,7 +37,7 @@ func (d *PathDecoder) attrValueCandidatesAtPos(ctx context.Context, attr *hclsyn return candidates, nil } - candidates.List = append(candidates.List, d.constraintToCandidates(c, outerBodyRng, prefixRng, editRng)...) + candidates.List = append(candidates.List, d.constraintToCandidates(ctx, c, outerBodyRng, prefixRng, editRng)...) count++ } } @@ -305,7 +307,7 @@ func (d *PathDecoder) candidatesFromHooks(ctx context.Context, attr *hclsyntax.A return candidates } -func (d *PathDecoder) constraintToCandidates(constraint schema.ExprConstraint, outerBodyRng, prefixRng, editRng hcl.Range) []lang.Candidate { +func (d *PathDecoder) constraintToCandidates(ctx context.Context, constraint schema.ExprConstraint, outerBodyRng, prefixRng, editRng hcl.Range) []lang.Candidate { candidates := make([]lang.Candidate, 0) switch c := constraint.(type) { @@ -328,7 +330,7 @@ func (d *PathDecoder) constraintToCandidates(constraint schema.ExprConstraint, o }, }) case schema.TraversalExpr: - candidates = append(candidates, d.candidatesForTraversalConstraint(c, outerBodyRng, prefixRng, editRng)...) + candidates = append(candidates, d.candidatesForTraversalConstraint(ctx, c, outerBodyRng, prefixRng, editRng)...) case schema.TupleConsExpr: candidates = append(candidates, lang.Candidate{ Label: fmt.Sprintf(`[%s]`, labelForConstraints(c.AnyElem)), @@ -457,9 +459,24 @@ func (d *PathDecoder) constraintToCandidates(constraint schema.ExprConstraint, o return candidates } -func (d *PathDecoder) candidatesForTraversalConstraint(tc schema.TraversalExpr, outerBodyRng, prefixRng, editRng hcl.Range) []lang.Candidate { +func (d *PathDecoder) candidatesForTraversalConstraint(ctx context.Context, tc schema.TraversalExpr, outerBodyRng, prefixRng, editRng hcl.Range) []lang.Candidate { candidates := make([]lang.Candidate, 0) + ext, ok := icontext.ExtensionsFromContext(ctx) + if ok && ext.Count && icontext.ActiveCountFromContext(ctx) { + candidates = append(candidates, lang.Candidate{ + Label: "count.index", + Detail: "number", + Description: lang.PlainText("The distinct index number (starting with 0) corresponding to the instance"), + Kind: lang.TraversalCandidateKind, + TextEdit: lang.TextEdit{ + NewText: "count.index", + Snippet: "count.index", + Range: editRng, + }, + }) + } + if d.pathCtx.ReferenceTargets == nil { return candidates } diff --git a/schema/body_schema.go b/schema/body_schema.go index cd79cc08..cf0fb402 100644 --- a/schema/body_schema.go +++ b/schema/body_schema.go @@ -45,6 +45,29 @@ type BodySchema struct { // referenced from still unknown locations during the build of the module // schema. ImpliedOrigins ImpliedOrigins + + // Extensions represents any HCL extensions supported in this body + Extensions *BodyExtensions +} + +type BodyExtensions struct { + Count bool // count attribute + count.index refs + ForEach bool // for_each attribute + each.* refs + DynamicBlocks bool // dynamic "block-name" w/ content & for_each inside + SelfRefs bool // self.* refs which refer to outermost parent block body +} + +func (be *BodyExtensions) Copy() *BodyExtensions { + if be == nil { + return nil + } + + return &BodyExtensions{ + Count: be.Count, + ForEach: be.ForEach, + DynamicBlocks: be.DynamicBlocks, + SelfRefs: be.SelfRefs, + } } type ImpliedOrigins []ImpliedOrigin @@ -176,6 +199,7 @@ func (bs *BodySchema) Copy() *BodySchema { HoverURL: bs.HoverURL, DocsLink: bs.DocsLink.Copy(), Targets: bs.Targets.Copy(), + Extensions: bs.Extensions.Copy(), } if bs.TargetableAs != nil { From f2351833d706eda7bfb29eca48a2483a2af52c9a Mon Sep 17 00:00:00 2001 From: James Pogran Date: Mon, 26 Sep 2022 11:45:50 -0400 Subject: [PATCH 02/23] Hover Expresisons --- decoder/hover.go | 73 ++++++++++++++---- decoder/hover_expressions_test.go | 12 ++- decoder/hover_test.go | 123 ++++++++++++++++++++++++++---- 3 files changed, 174 insertions(+), 34 deletions(-) diff --git a/decoder/hover.go b/decoder/hover.go index 7cbc5ad6..c1b706e7 100644 --- a/decoder/hover.go +++ b/decoder/hover.go @@ -1,19 +1,22 @@ package decoder import ( + "context" "fmt" "sort" "strings" + "github.com/zclconf/go-cty/cty" + + icontext "github.com/hashicorp/hcl-lang/context" "github.com/hashicorp/hcl-lang/lang" "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 (d *PathDecoder) HoverAtPos(filename string, pos hcl.Pos) (*lang.HoverData, error) { +func (d *PathDecoder) HoverAtPos(ctx context.Context, filename string, pos hcl.Pos) (*lang.HoverData, error) { f, err := d.fileByName(filename) if err != nil { return nil, err @@ -28,7 +31,7 @@ func (d *PathDecoder) HoverAtPos(filename string, pos hcl.Pos) (*lang.HoverData, return nil, &NoSchemaError{} } - data, err := d.hoverAtPos(rootBody, d.pathCtx.Schema, pos) + data, err := d.hoverAtPos(ctx, rootBody, d.pathCtx.Schema, pos) if err != nil { return nil, err } @@ -36,15 +39,38 @@ func (d *PathDecoder) HoverAtPos(filename string, pos hcl.Pos) (*lang.HoverData, return data, nil } -func (d *PathDecoder) hoverAtPos(body *hclsyntax.Body, bodySchema *schema.BodySchema, pos hcl.Pos) (*lang.HoverData, error) { +func (d *PathDecoder) hoverAtPos(ctx context.Context, body *hclsyntax.Body, bodySchema *schema.BodySchema, pos hcl.Pos) (*lang.HoverData, error) { if bodySchema == nil { return nil, nil } filename := body.Range().Filename + if bodySchema.Extensions != nil { + ctx = icontext.WithExtensions(ctx, bodySchema.Extensions) + if bodySchema.Extensions.Count { + if _, ok := body.Attributes["count"]; ok { + // append to context we need count provided + ctx = icontext.WithActiveCount(ctx) + } + } + } + for name, attr := range body.Attributes { if attr.Range().ContainsPos(pos) { + + if bodySchema.Extensions != nil { + if name == "count" && bodySchema.Extensions.Count { + return &lang.HoverData{ + Content: lang.MarkupContent{ + Kind: lang.MarkdownKind, + Value: "**count** _optional, number_\n\nThe distinct index number (starting with 0) corresponding to the instance", + }, + Range: attr.Range(), + }, nil + } + } + aSchema, ok := bodySchema.Attributes[attr.Name] if !ok { if bodySchema.AnyAttribute == nil { @@ -66,7 +92,7 @@ func (d *PathDecoder) hoverAtPos(body *hclsyntax.Body, bodySchema *schema.BodySc if attr.Expr.Range().ContainsPos(pos) { exprCons := ExprConstraints(aSchema.Expr) - data, err := d.hoverDataForExpr(attr.Expr, exprCons, 0, pos) + data, err := d.hoverDataForExpr(ctx, attr.Expr, exprCons, 0, pos) if err != nil { return nil, &PositionalError{ Filename: filename, @@ -128,7 +154,7 @@ func (d *PathDecoder) hoverAtPos(body *hclsyntax.Body, bodySchema *schema.BodySc return nil, err } - return d.hoverAtPos(block.Body, mergedSchema, pos) + return d.hoverAtPos(ctx, block.Body, mergedSchema, pos) } } } @@ -215,7 +241,7 @@ func (d *PathDecoder) hoverContentForBlock(bType string, schema *schema.BlockSch } } -func (d *PathDecoder) hoverDataForExpr(expr hcl.Expression, constraints ExprConstraints, nestingLvl int, pos hcl.Pos) (*lang.HoverData, error) { +func (d *PathDecoder) hoverDataForExpr(ctx context.Context, expr hcl.Expression, constraints ExprConstraints, nestingLvl int, pos hcl.Pos) (*lang.HoverData, error) { switch e := expr.(type) { case *hclsyntax.ScopeTraversalExpr: kw, ok := constraints.KeywordExpr() @@ -232,6 +258,21 @@ func (d *PathDecoder) hoverDataForExpr(expr hcl.Expression, constraints ExprCons }, nil } + address, _ := lang.TraversalToAddress(e.AsTraversal()) + if address.Equals(lang.Address{ + lang.RootStep{ + Name: "count", + }, + lang.AttrStep{ + Name: "index", + }, + }) && icontext.ActiveCountFromContext(ctx) { + return &lang.HoverData{ + Content: lang.Markdown("**count.index** fooooo"), + Range: expr.Range(), + }, nil + } + tes, ok := constraints.TraversalExprs() if ok { content, err := d.hoverContentForTraversalExpr(e.AsTraversal(), tes) @@ -261,7 +302,7 @@ func (d *PathDecoder) hoverDataForExpr(expr hcl.Expression, constraints ExprCons } case *hclsyntax.TemplateExpr: if e.IsStringLiteral() { - data, err := d.hoverDataForExpr(e.Parts[0], constraints, nestingLvl, pos) + data, err := d.hoverDataForExpr(ctx, e.Parts[0], constraints, nestingLvl, pos) if err != nil { return nil, err } @@ -295,7 +336,7 @@ func (d *PathDecoder) hoverDataForExpr(expr hcl.Expression, constraints ExprCons } } case *hclsyntax.TemplateWrapExpr: - data, err := d.hoverDataForExpr(e.Wrapped, constraints, nestingLvl, pos) + data, err := d.hoverDataForExpr(ctx, e.Wrapped, constraints, nestingLvl, pos) if err != nil { return nil, err } @@ -320,7 +361,7 @@ func (d *PathDecoder) hoverDataForExpr(expr hcl.Expression, constraints ExprCons if ok { for _, elemExpr := range e.Exprs { if elemExpr.Range().ContainsPos(pos) { - return d.hoverDataForExpr(elemExpr, ExprConstraints(se.Elem), nestingLvl, pos) + return d.hoverDataForExpr(ctx, elemExpr, ExprConstraints(se.Elem), nestingLvl, pos) } } content := fmt.Sprintf("_%s_", se.FriendlyName()) @@ -336,7 +377,7 @@ func (d *PathDecoder) hoverDataForExpr(expr hcl.Expression, constraints ExprCons if ok { for _, elemExpr := range e.Exprs { if elemExpr.Range().ContainsPos(pos) { - return d.hoverDataForExpr(elemExpr, ExprConstraints(le.Elem), nestingLvl, pos) + return d.hoverDataForExpr(ctx, elemExpr, ExprConstraints(le.Elem), nestingLvl, pos) } } content := fmt.Sprintf("_%s_", le.FriendlyName()) @@ -356,7 +397,7 @@ func (d *PathDecoder) hoverDataForExpr(expr hcl.Expression, constraints ExprCons return nil, &ConstraintMismatch{elemExpr} } ec := ExprConstraints(te.Elems[i]) - return d.hoverDataForExpr(elemExpr, ec, nestingLvl, pos) + return d.hoverDataForExpr(ctx, elemExpr, ec, nestingLvl, pos) } } content := fmt.Sprintf("_%s_", te.FriendlyName()) @@ -393,7 +434,7 @@ func (d *PathDecoder) hoverDataForExpr(expr hcl.Expression, constraints ExprCons case *hclsyntax.ObjectConsExpr: objExpr, ok := constraints.ObjectExpr() if ok { - return d.hoverDataForObjectExpr(e, objExpr, nestingLvl, pos) + return d.hoverDataForObjectExpr(ctx, e, objExpr, nestingLvl, pos) } mapExpr, ok := constraints.MapExpr() if ok { @@ -469,7 +510,7 @@ func (d *PathDecoder) hoverDataForExpr(expr hcl.Expression, constraints ExprCons return nil, fmt.Errorf("unsupported expression (%T)", expr) } -func (d *PathDecoder) hoverDataForObjectExpr(objExpr *hclsyntax.ObjectConsExpr, oe schema.ObjectExpr, nestingLvl int, pos hcl.Pos) (*lang.HoverData, error) { +func (d *PathDecoder) hoverDataForObjectExpr(ctx context.Context, objExpr *hclsyntax.ObjectConsExpr, oe schema.ObjectExpr, nestingLvl int, pos hcl.Pos) (*lang.HoverData, error) { declaredAttributes := make(map[string]hclsyntax.Expression, 0) for _, item := range objExpr.Items { key, _ := item.KeyExpr.Value(nil) @@ -485,7 +526,7 @@ func (d *PathDecoder) hoverDataForObjectExpr(objExpr *hclsyntax.ObjectConsExpr, } if item.ValueExpr.Range().ContainsPos(pos) { - return d.hoverDataForExpr(item.ValueExpr, ExprConstraints(attr.Expr), nestingLvl+1, pos) + return d.hoverDataForExpr(ctx, item.ValueExpr, ExprConstraints(attr.Expr), nestingLvl+1, pos) } itemRng := hcl.RangeBetween(item.KeyExpr.Range(), item.ValueExpr.Range()) @@ -526,7 +567,7 @@ func (d *PathDecoder) hoverDataForObjectExpr(objExpr *hclsyntax.ObjectConsExpr, attrData := ec.FriendlyName() if attrExpr, ok := declaredAttributes[name]; ok { - data, err := d.hoverDataForExpr(attrExpr, ExprConstraints(ec), nestingLvl+1, pos) + data, err := d.hoverDataForExpr(ctx, attrExpr, ExprConstraints(ec), nestingLvl+1, pos) if err == nil && data.Content.Value != "" { attrData = data.Content.Value } diff --git a/decoder/hover_expressions_test.go b/decoder/hover_expressions_test.go index c05c6c44..1ff634b6 100644 --- a/decoder/hover_expressions_test.go +++ b/decoder/hover_expressions_test.go @@ -1,18 +1,20 @@ package decoder import ( + "context" "errors" "fmt" "testing" "github.com/google/go-cmp/cmp" + "github.com/zclconf/go-cty-debug/ctydebug" + "github.com/zclconf/go-cty/cty" + "github.com/hashicorp/hcl-lang/lang" "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-debug/ctydebug" - "github.com/zclconf/go-cty/cty" ) func TestDecoder_HoverAtPos_expressions(t *testing.T) { @@ -1282,7 +1284,8 @@ _object_`), }, }) - data, err := d.HoverAtPos("test.tf", tc.pos) + ctx := context.Background() + data, err := d.HoverAtPos(ctx, "test.tf", tc.pos) if err != nil { if tc.expectedErr != nil && !errors.As(err, &tc.expectedErr) { @@ -1483,7 +1486,8 @@ func TestDecoder_HoverAtPos_traversalExpressions(t *testing.T) { }, }) - data, err := d.HoverAtPos("test.tf", tc.pos) + ctx := context.Background() + data, err := d.HoverAtPos(ctx, "test.tf", tc.pos) if err != nil { if tc.expectedErr != nil && !errors.As(err, &tc.expectedErr) { t.Fatalf("unexpected error: %s\nexpected: %s\n", diff --git a/decoder/hover_test.go b/decoder/hover_test.go index cd03d914..5dd43adc 100644 --- a/decoder/hover_test.go +++ b/decoder/hover_test.go @@ -1,19 +1,21 @@ package decoder import ( + "context" "errors" "fmt" "strings" "testing" "github.com/google/go-cmp/cmp" + "github.com/zclconf/go-cty-debug/ctydebug" + "github.com/zclconf/go-cty/cty" + "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/hashicorp/hcl/v2/json" - "github.com/zclconf/go-cty-debug/ctydebug" - "github.com/zclconf/go-cty/cty" ) func TestDecoder_HoverAtPos_noSchema(t *testing.T) { @@ -28,7 +30,8 @@ func TestDecoder_HoverAtPos_noSchema(t *testing.T) { }, }) - _, err := d.HoverAtPos("test.tf", hcl.InitialPos) + ctx := context.Background() + _, err := d.HoverAtPos(ctx, "test.tf", hcl.InitialPos) noSchemaErr := &NoSchemaError{} if !errors.As(err, &noSchemaErr) { t.Fatal("expected NoSchemaError for no schema") @@ -46,7 +49,8 @@ func TestDecoder_HoverAtPos_emptyBody(t *testing.T) { }, }) - _, err := d.HoverAtPos("test.tf", hcl.InitialPos) + ctx := context.Background() + _, err := d.HoverAtPos(ctx, "test.tf", hcl.InitialPos) unknownFormatErr := &UnknownFileFormatError{} if !errors.As(err, &unknownFormatErr) { t.Fatal("expected UnknownFileFormatError for empty body") @@ -69,7 +73,8 @@ func TestDecoder_HoverAtPos_json(t *testing.T) { }, }) - _, err := d.HoverAtPos("test.tf.json", hcl.InitialPos) + ctx := context.Background() + _, err := d.HoverAtPos(ctx, "test.tf.json", hcl.InitialPos) unknownFormatErr := &UnknownFileFormatError{} if !errors.As(err, &unknownFormatErr) { t.Fatal("expected UnknownFileFormatError for JSON body") @@ -232,6 +237,8 @@ func TestDecoder_HoverAtPos_nilBodySchema(t *testing.T) { }, } + ctx := context.Background() + for i, tc := range testCases { t.Run(fmt.Sprintf("%d-%s", i, tc.name), func(t *testing.T) { f, pDiags := hclsyntax.ParseConfig([]byte(tc.config), "test.tf", hcl.InitialPos) @@ -245,8 +252,7 @@ func TestDecoder_HoverAtPos_nilBodySchema(t *testing.T) { "test.tf": f, }, }) - - data, err := d.HoverAtPos("test.tf", tc.pos) + data, err := d.HoverAtPos(ctx, "test.tf", tc.pos) if err != nil { t.Fatal(err) } @@ -292,7 +298,8 @@ func TestDecoder_HoverAtPos_unknownAttribute(t *testing.T) { }, }) - _, err := d.HoverAtPos("test.tf", hcl.Pos{ + ctx := context.Background() + _, err := d.HoverAtPos(ctx, "test.tf", hcl.Pos{ Line: 2, Column: 6, Byte: 32, @@ -339,7 +346,8 @@ func TestDecoder_HoverAtPos_unknownBlock(t *testing.T) { }, }) - _, err := d.HoverAtPos("test.tf", hcl.Pos{ + ctx := context.Background() + _, err := d.HoverAtPos(ctx, "test.tf", hcl.Pos{ Line: 2, Column: 1, Byte: 23, @@ -417,7 +425,8 @@ func TestDecoder_HoverAtPos_invalidBlockPositions(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("%d-%s", i, tc.name), func(t *testing.T) { - _, err := d.HoverAtPos("test.tf", tc.pos) + ctx := context.Background() + _, err := d.HoverAtPos(ctx, "test.tf", tc.pos) if err == nil { t.Fatal("expected error") } @@ -460,7 +469,8 @@ func TestDecoder_HoverAtPos_rightHandSide(t *testing.T) { }, }) - data, err := d.HoverAtPos("test.tf", hcl.Pos{ + ctx := context.Background() + data, err := d.HoverAtPos(ctx, "test.tf", hcl.Pos{ Line: 2, Column: 17, Byte: 32, @@ -636,7 +646,8 @@ func TestDecoder_HoverAtPos_basic(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("%d-%s", i, tc.name), func(t *testing.T) { - data, err := d.HoverAtPos("test.tf", tc.pos) + ctx := context.Background() + data, err := d.HoverAtPos(ctx, "test.tf", tc.pos) if err != nil { t.Fatal(err) } @@ -810,7 +821,8 @@ My food block }, }) - data, err := d.HoverAtPos("test.tf", tc.pos) + ctx := context.Background() + data, err := d.HoverAtPos(ctx, "test.tf", tc.pos) if err != nil { t.Fatal(err) } @@ -910,8 +922,91 @@ func TestDecoder_HoverAtPos_typeDeclaration(t *testing.T) { }, }) + ctx := context.Background() pos := hcl.Pos{Line: 2, Column: 6, Byte: 32} - data, err := d.HoverAtPos("test.tf", pos) + data, err := d.HoverAtPos(ctx, "test.tf", pos) + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(tc.expectedData, data, ctydebug.CmpOptions); diff != "" { + t.Fatalf("hover data mismatch: %s", diff) + } + }) + } +} + +func TestDecoder_HoverAtPos_expression_extension(t *testing.T) { + resourceLabelSchema := []*schema.LabelSchema{ + {Name: "type", IsDepKey: true}, + {Name: "name"}, + } + blockSchema := &schema.BlockSchema{ + Labels: resourceLabelSchema, + Description: lang.Markdown("My special block"), + Body: &schema.BodySchema{ + Extensions: &schema.BodyExtensions{ + Count: true, + }, + Attributes: map[string]*schema.AttributeSchema{ + "num_attr": {Expr: schema.LiteralTypeOnly(cty.Number)}, + "str_attr": { + Expr: schema.LiteralTypeOnly(cty.String), + IsOptional: true, + Description: lang.PlainText("Special attribute"), + }, + "bool_attr": { + Expr: schema.LiteralTypeOnly(cty.Bool), + IsSensitive: true, + Description: lang.PlainText("Flag attribute"), + }, + }, + }, + DependentBody: map[schema.SchemaKey]*schema.BodySchema{}, + } + bodySchema := &schema.BodySchema{ + Blocks: map[string]*schema.BlockSchema{ + "myblock": blockSchema, + }, + } + testConfig := []byte(`myblock "foo" "bar" { + count = 1 + num_attr = 4 + bool_attr = true +} +`) + + f, _ := hclsyntax.ParseConfig(testConfig, "test.tf", hcl.InitialPos) + + d := testPathDecoder(t, &PathContext{ + Schema: bodySchema, + Files: map[string]*hcl.File{ + "test.tf": f, + }, + }) + + testCases := []struct { + name string + pos hcl.Pos + expectedData *lang.HoverData + }{ + { + "optional attribute name", + hcl.Pos{Line: 2, Column: 5, Byte: 24}, + &lang.HoverData{ + Content: lang.Markdown("**count** _optional, number_\n\nThe distinct index number (starting with 0) corresponding to the instance"), + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 3, Byte: 24}, + End: hcl.Pos{Line: 2, Column: 12, Byte: 33}, + }, + }, + }, + } + + for i, tc := range testCases { + t.Run(fmt.Sprintf("%d-%s", i, tc.name), func(t *testing.T) { + ctx := context.Background() + data, err := d.HoverAtPos(ctx, "test.tf", tc.pos) if err != nil { t.Fatal(err) } From 1f03be9a5435df0a6c3e25861abcc5e19dd42c97 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Tue, 27 Sep 2022 12:20:50 -0400 Subject: [PATCH 03/23] semantic tokens --- decoder/semantic_tokens.go | 96 ++++++++--- decoder/semantic_tokens_expr_test.go | 16 +- decoder/semantic_tokens_test.go | 233 ++++++++++++++++++++++++++- 3 files changed, 310 insertions(+), 35 deletions(-) diff --git a/decoder/semantic_tokens.go b/decoder/semantic_tokens.go index c1364810..6a557494 100644 --- a/decoder/semantic_tokens.go +++ b/decoder/semantic_tokens.go @@ -1,19 +1,22 @@ package decoder import ( + "context" "sort" + "github.com/zclconf/go-cty/cty" + + icontext "github.com/hashicorp/hcl-lang/context" "github.com/hashicorp/hcl-lang/lang" "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" ) // SemanticTokensInFile returns a sequence of semantic tokens // within the config file. -func (d *PathDecoder) SemanticTokensInFile(filename string) ([]lang.SemanticToken, error) { +func (d *PathDecoder) SemanticTokensInFile(ctx context.Context, filename string) ([]lang.SemanticToken, error) { f, err := d.fileByName(filename) if err != nil { return nil, err @@ -28,7 +31,7 @@ func (d *PathDecoder) SemanticTokensInFile(filename string) ([]lang.SemanticToke return []lang.SemanticToken{}, nil } - tokens := d.tokensForBody(body, d.pathCtx.Schema, []lang.SemanticTokenModifier{}) + tokens := d.tokensForBody(ctx, body, d.pathCtx.Schema, []lang.SemanticTokenModifier{}) sort.Slice(tokens, func(i, j int) bool { return tokens[i].Range.Start.Byte < tokens[j].Range.Start.Byte @@ -37,21 +40,43 @@ func (d *PathDecoder) SemanticTokensInFile(filename string) ([]lang.SemanticToke return tokens, nil } -func (d *PathDecoder) tokensForBody(body *hclsyntax.Body, bodySchema *schema.BodySchema, parentModifiers []lang.SemanticTokenModifier) []lang.SemanticToken { +func (d *PathDecoder) tokensForBody(ctx context.Context, body *hclsyntax.Body, bodySchema *schema.BodySchema, parentModifiers []lang.SemanticTokenModifier) []lang.SemanticToken { tokens := make([]lang.SemanticToken, 0) if bodySchema == nil { return tokens } + if bodySchema.Extensions != nil { + ctx = icontext.WithExtensions(ctx, bodySchema.Extensions) + if bodySchema.Extensions.Count { + if _, ok := body.Attributes["count"]; ok { + // append to context we need count provided + ctx = icontext.WithActiveCount(ctx) + } + } + } + for name, attr := range body.Attributes { + attrSchema, ok := bodySchema.Attributes[name] if !ok { - if bodySchema.AnyAttribute == nil { - // unknown attribute - continue + if bodySchema.Extensions != nil && name == "count" && bodySchema.Extensions.Count { + attrSchema = &schema.AttributeSchema{ + Description: lang.MarkupContent{ + Kind: lang.MarkdownKind, + Value: "**count** _optional, number_\n\nThe distinct index number (starting with 0) corresponding to the instance", + }, + IsOptional: true, + Expr: schema.LiteralTypeOnly(cty.Number), + } + } else { + if bodySchema.AnyAttribute == nil { + // unknown attribute + continue + } + attrSchema = bodySchema.AnyAttribute } - attrSchema = bodySchema.AnyAttribute } attrModifiers := make([]lang.SemanticTokenModifier, 0) @@ -65,7 +90,7 @@ func (d *PathDecoder) tokensForBody(body *hclsyntax.Body, bodySchema *schema.Bod }) ec := ExprConstraints(attrSchema.Expr) - tokens = append(tokens, d.tokensForExpression(attr.Expr, ec)...) + tokens = append(tokens, d.tokensForExpression(ctx, attr.Expr, ec)...) } for _, block := range body.Blocks { @@ -106,19 +131,19 @@ func (d *PathDecoder) tokensForBody(body *hclsyntax.Body, bodySchema *schema.Bod } if block.Body != nil { - tokens = append(tokens, d.tokensForBody(block.Body, blockSchema.Body, blockModifiers)...) + tokens = append(tokens, d.tokensForBody(ctx, block.Body, blockSchema.Body, blockModifiers)...) } depSchema, _, ok := NewBlockSchema(blockSchema).DependentBodySchema(block.AsHCLBlock()) if ok { - tokens = append(tokens, d.tokensForBody(block.Body, depSchema, []lang.SemanticTokenModifier{})...) + tokens = append(tokens, d.tokensForBody(ctx, block.Body, depSchema, []lang.SemanticTokenModifier{})...) } } return tokens } -func (d *PathDecoder) tokensForExpression(expr hclsyntax.Expression, constraints ExprConstraints) []lang.SemanticToken { +func (d *PathDecoder) tokensForExpression(ctx context.Context, expr hclsyntax.Expression, constraints ExprConstraints) []lang.SemanticToken { tokens := make([]lang.SemanticToken, 0) switch eType := expr.(type) { @@ -135,6 +160,31 @@ func (d *PathDecoder) tokensForExpression(expr hclsyntax.Expression, constraints } } + address, _ := lang.TraversalToAddress(eType.AsTraversal()) + countIndexAttr := lang.Address{ + lang.RootStep{ + Name: "count", + }, + lang.AttrStep{ + Name: "index", + }, + } + countAvailable := icontext.ActiveCountFromContext(ctx) + if address.Equals(countIndexAttr) && countAvailable { + traversal := eType.AsTraversal() + tokens = append(tokens, lang.SemanticToken{ + Type: lang.TokenTraversalStep, + Modifiers: []lang.SemanticTokenModifier{}, + Range: traversal[0].SourceRange(), + }) + tokens = append(tokens, lang.SemanticToken{ + Type: lang.TokenTraversalStep, + Modifiers: []lang.SemanticTokenModifier{}, + Range: traversal[1].SourceRange(), + }) + return tokens + } + tes, ok := constraints.TraversalExprs() if ok && d.pathCtx.ReferenceTargets != nil { traversal := eType.AsTraversal() @@ -231,7 +281,7 @@ func (d *PathDecoder) tokensForExpression(expr hclsyntax.Expression, constraints Range: eType.NameRange, }) for _, arg := range eType.Args { - tokens = append(tokens, d.tokensForExpression(arg, constraints)...) + tokens = append(tokens, d.tokensForExpression(ctx, arg, constraints)...) } return tokens } @@ -249,13 +299,13 @@ func (d *PathDecoder) tokensForExpression(expr hclsyntax.Expression, constraints return tokenForTypedExpression(eType, cty.String) } case *hclsyntax.TemplateWrapExpr: - return d.tokensForExpression(eType.Wrapped, constraints) + return d.tokensForExpression(ctx, eType.Wrapped, constraints) case *hclsyntax.TupleConsExpr: tc, ok := constraints.TupleConsExpr() if ok { ec := ExprConstraints(tc.AnyElem) for _, expr := range eType.Exprs { - tokens = append(tokens, d.tokensForExpression(expr, ec)...) + tokens = append(tokens, d.tokensForExpression(ctx, expr, ec)...) } return tokens } @@ -263,7 +313,7 @@ func (d *PathDecoder) tokensForExpression(expr hclsyntax.Expression, constraints if ok { ec := ExprConstraints(se.Elem) for _, expr := range eType.Exprs { - tokens = append(tokens, d.tokensForExpression(expr, ec)...) + tokens = append(tokens, d.tokensForExpression(ctx, expr, ec)...) } return tokens } @@ -271,7 +321,7 @@ func (d *PathDecoder) tokensForExpression(expr hclsyntax.Expression, constraints if ok { ec := ExprConstraints(le.Elem) for _, expr := range eType.Exprs { - tokens = append(tokens, d.tokensForExpression(expr, ec)...) + tokens = append(tokens, d.tokensForExpression(ctx, expr, ec)...) } return tokens } @@ -282,7 +332,7 @@ func (d *PathDecoder) tokensForExpression(expr hclsyntax.Expression, constraints break } ec := ExprConstraints(te.Elems[i]) - tokens = append(tokens, d.tokensForExpression(expr, ec)...) + tokens = append(tokens, d.tokensForExpression(ctx, expr, ec)...) } return tokens } @@ -316,7 +366,7 @@ func (d *PathDecoder) tokensForExpression(expr hclsyntax.Expression, constraints }) ec := ExprConstraints(attr.Expr) - tokens = append(tokens, d.tokensForExpression(item.ValueExpr, ec)...) + tokens = append(tokens, d.tokensForExpression(ctx, item.ValueExpr, ec)...) } return tokens } @@ -329,7 +379,7 @@ func (d *PathDecoder) tokensForExpression(expr hclsyntax.Expression, constraints Range: item.KeyExpr.Range(), }) ec := ExprConstraints(me.Elem) - tokens = append(tokens, d.tokensForExpression(item.ValueExpr, ec)...) + tokens = append(tokens, d.tokensForExpression(ctx, item.ValueExpr, ec)...) } return tokens } @@ -343,7 +393,7 @@ func (d *PathDecoder) tokensForExpression(expr hclsyntax.Expression, constraints } _, ok = constraints.TypeDeclarationExpr() if ok { - return d.tokensForObjectConsTypeDeclarationExpr(eType, constraints) + return d.tokensForObjectConsTypeDeclarationExpr(ctx, eType, constraints) } case *hclsyntax.LiteralValueExpr: valType := eType.Val.Type() @@ -389,7 +439,7 @@ func isComplexTypeDeclaration(funcName string) bool { return false } -func (d *PathDecoder) tokensForObjectConsTypeDeclarationExpr(expr *hclsyntax.ObjectConsExpr, constraints ExprConstraints) []lang.SemanticToken { +func (d *PathDecoder) tokensForObjectConsTypeDeclarationExpr(ctx context.Context, expr *hclsyntax.ObjectConsExpr, constraints ExprConstraints) []lang.SemanticToken { tokens := make([]lang.SemanticToken, 0) for _, item := range expr.Items { key, _ := item.KeyExpr.Value(nil) @@ -405,7 +455,7 @@ func (d *PathDecoder) tokensForObjectConsTypeDeclarationExpr(expr *hclsyntax.Obj Range: item.KeyExpr.Range(), }) - tokens = append(tokens, d.tokensForExpression(item.ValueExpr, constraints)...) + tokens = append(tokens, d.tokensForExpression(ctx, item.ValueExpr, constraints)...) } return tokens } diff --git a/decoder/semantic_tokens_expr_test.go b/decoder/semantic_tokens_expr_test.go index 3c7f09da..66bcb67d 100644 --- a/decoder/semantic_tokens_expr_test.go +++ b/decoder/semantic_tokens_expr_test.go @@ -1,16 +1,18 @@ package decoder import ( + "context" "fmt" "testing" "github.com/google/go-cmp/cmp" + "github.com/zclconf/go-cty/cty" + "github.com/hashicorp/hcl-lang/lang" "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 TestDecoder_SemanticTokensInFile_expressions(t *testing.T) { @@ -1433,6 +1435,8 @@ EOT }, } + ctx := context.Background() + for i, tc := range testCases { t.Run(fmt.Sprintf("%d-%s", i, tc.name), func(t *testing.T) { bodySchema := &schema.BodySchema{ @@ -1451,7 +1455,7 @@ EOT }, }) - tokens, err := d.SemanticTokensInFile("test.tf") + tokens, err := d.SemanticTokensInFile(ctx, "test.tf") if err != nil { t.Fatal(err) } @@ -1912,6 +1916,8 @@ func TestDecoder_SemanticTokensInFile_traversalExpression(t *testing.T) { }, } + ctx := context.Background() + for i, tc := range testCases { t.Run(fmt.Sprintf("%d-%s", i, tc.name), func(t *testing.T) { bodySchema := &schema.BodySchema{ @@ -1931,7 +1937,7 @@ func TestDecoder_SemanticTokensInFile_traversalExpression(t *testing.T) { }, }) - tokens, err := d.SemanticTokensInFile("test.tf") + tokens, err := d.SemanticTokensInFile(ctx, "test.tf") if err != nil { t.Fatal(err) } @@ -2227,6 +2233,8 @@ func TestDecoder_SemanticTokensInFile_typeDeclaration(t *testing.T) { }, } + ctx := context.Background() + for i, tc := range testCases { t.Run(fmt.Sprintf("%d-%s", i, tc.name), func(t *testing.T) { bodySchema := &schema.BodySchema{ @@ -2245,7 +2253,7 @@ func TestDecoder_SemanticTokensInFile_typeDeclaration(t *testing.T) { }, }) - tokens, err := d.SemanticTokensInFile("test.tf") + tokens, err := d.SemanticTokensInFile(ctx, "test.tf") if err != nil { t.Fatal(err) } diff --git a/decoder/semantic_tokens_test.go b/decoder/semantic_tokens_test.go index aa308858..3b098bd0 100644 --- a/decoder/semantic_tokens_test.go +++ b/decoder/semantic_tokens_test.go @@ -1,16 +1,18 @@ package decoder import ( + "context" "errors" "testing" "github.com/google/go-cmp/cmp" + "github.com/zclconf/go-cty/cty" + "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/hashicorp/hcl/v2/json" - "github.com/zclconf/go-cty/cty" ) func TestDecoder_SemanticTokensInFile_emptyBody(t *testing.T) { @@ -23,7 +25,9 @@ func TestDecoder_SemanticTokensInFile_emptyBody(t *testing.T) { }, }) - _, err := d.SemanticTokensInFile("test.tf") + ctx := context.Background() + + _, err := d.SemanticTokensInFile(ctx, "test.tf") unknownFormatErr := &UnknownFileFormatError{} if !errors.As(err, &unknownFormatErr) { t.Fatal("expected UnknownFileFormatError for empty body") @@ -46,7 +50,9 @@ func TestDecoder_SemanticTokensInFile_json(t *testing.T) { }, }) - _, err := d.SemanticTokensInFile("test.tf.json") + ctx := context.Background() + + _, err := d.SemanticTokensInFile(ctx, "test.tf.json") unknownFormatErr := &UnknownFileFormatError{} if !errors.As(err, &unknownFormatErr) { t.Fatal("expected UnknownFileFormatError for JSON body") @@ -65,7 +71,9 @@ func TestDecoder_SemanticTokensInFile_zeroByteContent(t *testing.T) { }, }) - tokens, err := d.SemanticTokensInFile("test.tf") + ctx := context.Background() + + tokens, err := d.SemanticTokensInFile(ctx, "test.tf") if err != nil { t.Fatal(err) } @@ -87,7 +95,9 @@ func TestDecoder_SemanticTokensInFile_fileNotFound(t *testing.T) { }, }) - _, err := d.SemanticTokensInFile("foobar.tf") + ctx := context.Background() + + _, err := d.SemanticTokensInFile(ctx, "foobar.tf") notFoundErr := &FileNotFoundError{} if !errors.As(err, ¬FoundErr) { t.Fatal("expected FileNotFoundError for non-existent file") @@ -149,7 +159,9 @@ resource "vault_auth_backend" "blah" { }, }) - tokens, err := d.SemanticTokensInFile("test.tf") + ctx := context.Background() + + tokens, err := d.SemanticTokensInFile(ctx, "test.tf") if err != nil { t.Fatal(err) } @@ -361,7 +373,9 @@ resource "aws_instance" "beta" { }, }) - tokens, err := d.SemanticTokensInFile("test.tf") + ctx := context.Background() + + tokens, err := d.SemanticTokensInFile(ctx, "test.tf") if err != nil { t.Fatal(err) } @@ -626,7 +640,9 @@ resource "vault_auth_backend" "blah" { }, }) - tokens, err := d.SemanticTokensInFile("test.tf") + ctx := context.Background() + + tokens, err := d.SemanticTokensInFile(ctx, "test.tf") if err != nil { t.Fatal(err) } @@ -836,3 +852,204 @@ resource "vault_auth_backend" "blah" { t.Fatalf("unexpected tokens: %s", diff) } } + +func TestDecoder_SemanticTokensInFile_expression_extensions(t *testing.T) { + bodySchema := &schema.BodySchema{ + Blocks: map[string]*schema.BlockSchema{ + "resource": { + Body: &schema.BodySchema{ + Extensions: &schema.BodyExtensions{ + Count: true, + }, + Attributes: map[string]*schema.AttributeSchema{ + "cpu_count": { + Expr: schema.LiteralTypeOnly(cty.Number), + }, + }, + }, + Labels: []*schema.LabelSchema{ + { + Name: "type", + IsDepKey: true, + SemanticTokenModifiers: lang.SemanticTokenModifiers{ + lang.TokenModifierDependent, + }, + }, + {Name: "name"}, + }, + }, + }, + } + + testCfg := []byte(` +resource "vault_auth_backend" "blah" { + count = 1 + cpu_count = count.index +} +`) + + f, pDiags := hclsyntax.ParseConfig(testCfg, "test.tf", hcl.InitialPos) + if len(pDiags) > 0 { + t.Fatal(pDiags) + } + + d := testPathDecoder(t, &PathContext{ + Schema: bodySchema, + Files: map[string]*hcl.File{ + "test.tf": f, + }, + }) + + ctx := context.Background() + + tokens, err := d.SemanticTokensInFile(ctx, "test.tf") + if err != nil { + t.Fatal(err) + } + + expectedTokens := []lang.SemanticToken{ + { // resource + Type: lang.TokenBlockType, + Modifiers: []lang.SemanticTokenModifier{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 2, + Column: 1, + Byte: 1, + }, + End: hcl.Pos{ + Line: 2, + Column: 9, + Byte: 9, + }, + }, + }, + { // vault_auth_backend + Type: lang.TokenBlockLabel, + Modifiers: []lang.SemanticTokenModifier{ + lang.TokenModifierDependent, + }, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 2, + Column: 10, + Byte: 10, + }, + End: hcl.Pos{ + Line: 2, + Column: 30, + Byte: 30, + }, + }, + }, + { // blah + Type: lang.TokenBlockLabel, + Modifiers: []lang.SemanticTokenModifier{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 2, + Column: 31, + Byte: 31, + }, + End: hcl.Pos{ + Line: 2, + Column: 37, + Byte: 37, + }, + }, + }, + { // count + Type: lang.TokenAttrName, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 3, + Column: 2, + Byte: 41, + }, + End: hcl.Pos{ + Line: 3, + Column: 7, + Byte: 46, + }, + }, + }, + { // 1 + Type: lang.TokenNumber, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 3, + Column: 10, + Byte: 49, + }, + End: hcl.Pos{ + Line: 3, + Column: 11, + Byte: 50, + }, + }, + }, + { // cpu_count + Type: lang.TokenAttrName, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 4, + Column: 3, + Byte: 53, + }, + End: hcl.Pos{ + Line: 4, + Column: 12, + Byte: 62, + }, + }, + }, + { // count. + Type: lang.TokenTraversalStep, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 4, + Column: 15, + Byte: 65, + }, + End: hcl.Pos{ + Line: 4, + Column: 20, + Byte: 70, + }, + }, + }, + { // .index + Type: lang.TokenTraversalStep, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 4, + Column: 20, + Byte: 70, + }, + End: hcl.Pos{ + Line: 4, + Column: 26, + Byte: 76, + }, + }, + }, + } + + diff := cmp.Diff(expectedTokens, tokens) + if diff != "" { + t.Fatalf("unexpected tokens: %s", diff) + } +} From 998e3b9e34d1bb8a6e51fb845ab8ca88c0d029bc Mon Sep 17 00:00:00 2001 From: James Pogran Date: Thu, 6 Oct 2022 12:36:49 -0400 Subject: [PATCH 04/23] wip --- decoder/semantic_tokens.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/decoder/semantic_tokens.go b/decoder/semantic_tokens.go index 6a557494..aa48f1b8 100644 --- a/decoder/semantic_tokens.go +++ b/decoder/semantic_tokens.go @@ -2,6 +2,7 @@ package decoder import ( "context" + "log" "sort" "github.com/zclconf/go-cty/cty" @@ -53,12 +54,14 @@ func (d *PathDecoder) tokensForBody(ctx context.Context, body *hclsyntax.Body, b if _, ok := body.Attributes["count"]; ok { // append to context we need count provided ctx = icontext.WithActiveCount(ctx) + log.Printf("Found Expression: ") } } } for name, attr := range body.Attributes { + log.Printf("Found: %q / %v+", name, bodySchema.Extensions) attrSchema, ok := bodySchema.Attributes[name] if !ok { if bodySchema.Extensions != nil && name == "count" && bodySchema.Extensions.Count { @@ -90,6 +93,8 @@ func (d *PathDecoder) tokensForBody(ctx context.Context, body *hclsyntax.Body, b }) ec := ExprConstraints(attrSchema.Expr) + countAvailable := icontext.ActiveCountFromContext(ctx) + log.Printf("Found Expression: countAvailable %q ", countAvailable) tokens = append(tokens, d.tokensForExpression(ctx, attr.Expr, ec)...) } @@ -170,7 +175,10 @@ func (d *PathDecoder) tokensForExpression(ctx context.Context, expr hclsyntax.Ex }, } countAvailable := icontext.ActiveCountFromContext(ctx) - if address.Equals(countIndexAttr) && countAvailable { + // TODO why is countAvailable not true here? + log.Printf("Found Expression: %q / %q - %v+", countAvailable, address.Equals(countIndexAttr), address) + // if address.Equals(countIndexAttr) && countAvailable { + if address.Equals(countIndexAttr) { traversal := eType.AsTraversal() tokens = append(tokens, lang.SemanticToken{ Type: lang.TokenTraversalStep, From bab293aff396d0481272c5080068b07c2091a35b Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Mon, 10 Oct 2022 15:51:06 +0100 Subject: [PATCH 05/23] decoder: add test for semtok edge case & make it pass --- decoder/semantic_tokens.go | 6 +- decoder/semantic_tokens_test.go | 132 ++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 3 deletions(-) diff --git a/decoder/semantic_tokens.go b/decoder/semantic_tokens.go index aa48f1b8..0affe330 100644 --- a/decoder/semantic_tokens.go +++ b/decoder/semantic_tokens.go @@ -94,7 +94,7 @@ func (d *PathDecoder) tokensForBody(ctx context.Context, body *hclsyntax.Body, b ec := ExprConstraints(attrSchema.Expr) countAvailable := icontext.ActiveCountFromContext(ctx) - log.Printf("Found Expression: countAvailable %q ", countAvailable) + log.Printf("Found Expression: countAvailable %t", countAvailable) tokens = append(tokens, d.tokensForExpression(ctx, attr.Expr, ec)...) } @@ -176,9 +176,9 @@ func (d *PathDecoder) tokensForExpression(ctx context.Context, expr hclsyntax.Ex } countAvailable := icontext.ActiveCountFromContext(ctx) // TODO why is countAvailable not true here? - log.Printf("Found Expression: %q / %q - %v+", countAvailable, address.Equals(countIndexAttr), address) + log.Printf("Found Expression: %t / %t - %v+", countAvailable, address.Equals(countIndexAttr), address) // if address.Equals(countIndexAttr) && countAvailable { - if address.Equals(countIndexAttr) { + if address.Equals(countIndexAttr) && countAvailable { traversal := eType.AsTraversal() tokens = append(tokens, lang.SemanticToken{ Type: lang.TokenTraversalStep, diff --git a/decoder/semantic_tokens_test.go b/decoder/semantic_tokens_test.go index 3b098bd0..0b0e6ac1 100644 --- a/decoder/semantic_tokens_test.go +++ b/decoder/semantic_tokens_test.go @@ -1053,3 +1053,135 @@ resource "vault_auth_backend" "blah" { t.Fatalf("unexpected tokens: %s", diff) } } + +func TestDecoder_SemanticTokensInFile_extensions_countUndeclared(t *testing.T) { + bodySchema := &schema.BodySchema{ + Blocks: map[string]*schema.BlockSchema{ + "resource": { + Body: &schema.BodySchema{ + Extensions: &schema.BodyExtensions{ + Count: true, + }, + Attributes: map[string]*schema.AttributeSchema{ + "cpu_count": { + Expr: schema.LiteralTypeOnly(cty.Number), + }, + }, + }, + Labels: []*schema.LabelSchema{ + { + Name: "type", + IsDepKey: true, + SemanticTokenModifiers: lang.SemanticTokenModifiers{ + lang.TokenModifierDependent, + }, + }, + {Name: "name"}, + }, + }, + }, + } + + testCfg := []byte(` +resource "vault_auth_backend" "blah" { + cpu_count = count.index +} +`) + + f, pDiags := hclsyntax.ParseConfig(testCfg, "test.tf", hcl.InitialPos) + if len(pDiags) > 0 { + t.Fatal(pDiags) + } + + d := testPathDecoder(t, &PathContext{ + Schema: bodySchema, + Files: map[string]*hcl.File{ + "test.tf": f, + }, + }) + + ctx := context.Background() + + tokens, err := d.SemanticTokensInFile(ctx, "test.tf") + if err != nil { + t.Fatal(err) + } + + expectedTokens := []lang.SemanticToken{ + { // resource + Type: lang.TokenBlockType, + Modifiers: []lang.SemanticTokenModifier{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 2, + Column: 1, + Byte: 1, + }, + End: hcl.Pos{ + Line: 2, + Column: 9, + Byte: 9, + }, + }, + }, + { // vault_auth_backend + Type: lang.TokenBlockLabel, + Modifiers: []lang.SemanticTokenModifier{ + lang.TokenModifierDependent, + }, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 2, + Column: 10, + Byte: 10, + }, + End: hcl.Pos{ + Line: 2, + Column: 30, + Byte: 30, + }, + }, + }, + { // blah + Type: lang.TokenBlockLabel, + Modifiers: []lang.SemanticTokenModifier{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 2, + Column: 31, + Byte: 31, + }, + End: hcl.Pos{ + Line: 2, + Column: 37, + Byte: 37, + }, + }, + }, + { // cpu_count + Type: lang.TokenAttrName, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 3, + Column: 3, + Byte: 42, + }, + End: hcl.Pos{ + Line: 3, + Column: 12, + Byte: 51, + }, + }, + }, + } + + diff := cmp.Diff(expectedTokens, tokens) + if diff != "" { + t.Fatalf("unexpected tokens: %s", diff) + } +} From 64a478c9aec4c687264339c1ee95c4b6c0480439 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Mon, 10 Oct 2022 16:57:10 +0100 Subject: [PATCH 06/23] decoder: add test for dep schema & make it pass --- decoder/semantic_tokens.go | 18 +- decoder/semantic_tokens_test.go | 282 ++++++++++++++++++++++++++++---- 2 files changed, 261 insertions(+), 39 deletions(-) diff --git a/decoder/semantic_tokens.go b/decoder/semantic_tokens.go index 0affe330..d8565a39 100644 --- a/decoder/semantic_tokens.go +++ b/decoder/semantic_tokens.go @@ -2,7 +2,6 @@ package decoder import ( "context" - "log" "sort" "github.com/zclconf/go-cty/cty" @@ -48,20 +47,17 @@ func (d *PathDecoder) tokensForBody(ctx context.Context, body *hclsyntax.Body, b return tokens } + // TODO: test for count extension used in root BodySchema if bodySchema.Extensions != nil { - ctx = icontext.WithExtensions(ctx, bodySchema.Extensions) if bodySchema.Extensions.Count { if _, ok := body.Attributes["count"]; ok { // append to context we need count provided ctx = icontext.WithActiveCount(ctx) - log.Printf("Found Expression: ") } } } for name, attr := range body.Attributes { - - log.Printf("Found: %q / %v+", name, bodySchema.Extensions) attrSchema, ok := bodySchema.Attributes[name] if !ok { if bodySchema.Extensions != nil && name == "count" && bodySchema.Extensions.Count { @@ -93,8 +89,6 @@ func (d *PathDecoder) tokensForBody(ctx context.Context, body *hclsyntax.Body, b }) ec := ExprConstraints(attrSchema.Expr) - countAvailable := icontext.ActiveCountFromContext(ctx) - log.Printf("Found Expression: countAvailable %t", countAvailable) tokens = append(tokens, d.tokensForExpression(ctx, attr.Expr, ec)...) } @@ -136,6 +130,15 @@ func (d *PathDecoder) tokensForBody(ctx context.Context, body *hclsyntax.Body, b } if block.Body != nil { + // TODO: Test for count.index in a sub-block + if blockSchema.Body != nil && blockSchema.Body.Extensions != nil { + if blockSchema.Body.Extensions.Count { + if _, ok := block.Body.Attributes["count"]; ok { + // append to context we need count provided + ctx = icontext.WithActiveCount(ctx) + } + } + } tokens = append(tokens, d.tokensForBody(ctx, block.Body, blockSchema.Body, blockModifiers)...) } @@ -176,7 +179,6 @@ func (d *PathDecoder) tokensForExpression(ctx context.Context, expr hclsyntax.Ex } countAvailable := icontext.ActiveCountFromContext(ctx) // TODO why is countAvailable not true here? - log.Printf("Found Expression: %t / %t - %v+", countAvailable, address.Equals(countIndexAttr), address) // if address.Equals(countIndexAttr) && countAvailable { if address.Equals(countIndexAttr) && countAvailable { traversal := eType.AsTraversal() diff --git a/decoder/semantic_tokens_test.go b/decoder/semantic_tokens_test.go index 0b0e6ac1..7834cab3 100644 --- a/decoder/semantic_tokens_test.go +++ b/decoder/semantic_tokens_test.go @@ -862,8 +862,12 @@ func TestDecoder_SemanticTokensInFile_expression_extensions(t *testing.T) { Count: true, }, Attributes: map[string]*schema.AttributeSchema{ - "cpu_count": { - Expr: schema.LiteralTypeOnly(cty.Number), + "cpu_core_count": { + Expr: schema.ExprConstraints{ + schema.TraversalExpr{OfType: cty.Number}, + schema.LiteralTypeExpr{Type: cty.Number}, + }, + IsOptional: true, }, }, }, @@ -882,9 +886,9 @@ func TestDecoder_SemanticTokensInFile_expression_extensions(t *testing.T) { } testCfg := []byte(` -resource "vault_auth_backend" "blah" { - count = 1 - cpu_count = count.index +resource "aws_instance" "app_server" { + count = 1 + cpu_core_count = count.index } `) @@ -925,7 +929,7 @@ resource "vault_auth_backend" "blah" { }, }, }, - { // vault_auth_backend + { // aws_instance Type: lang.TokenBlockLabel, Modifiers: []lang.SemanticTokenModifier{ lang.TokenModifierDependent, @@ -939,20 +943,20 @@ resource "vault_auth_backend" "blah" { }, End: hcl.Pos{ Line: 2, - Column: 30, - Byte: 30, + Column: 24, + Byte: 24, }, }, }, - { // blah + { // app_server Type: lang.TokenBlockLabel, Modifiers: []lang.SemanticTokenModifier{}, Range: hcl.Range{ Filename: "test.tf", Start: hcl.Pos{ Line: 2, - Column: 31, - Byte: 31, + Column: 25, + Byte: 25, }, End: hcl.Pos{ Line: 2, @@ -968,13 +972,13 @@ resource "vault_auth_backend" "blah" { Filename: "test.tf", Start: hcl.Pos{ Line: 3, - Column: 2, - Byte: 41, + Column: 3, + Byte: 42, }, End: hcl.Pos{ Line: 3, - Column: 7, - Byte: 46, + Column: 8, + Byte: 47, }, }, }, @@ -985,17 +989,17 @@ resource "vault_auth_backend" "blah" { Filename: "test.tf", Start: hcl.Pos{ Line: 3, - Column: 10, - Byte: 49, + Column: 20, + Byte: 59, }, End: hcl.Pos{ Line: 3, - Column: 11, - Byte: 50, + Column: 21, + Byte: 60, }, }, }, - { // cpu_count + { // cpu_core_count Type: lang.TokenAttrName, Modifiers: lang.SemanticTokenModifiers{}, Range: hcl.Range{ @@ -1003,33 +1007,232 @@ resource "vault_auth_backend" "blah" { Start: hcl.Pos{ Line: 4, Column: 3, - Byte: 53, + Byte: 63, }, End: hcl.Pos{ Line: 4, - Column: 12, - Byte: 62, + Column: 17, + Byte: 77, }, }, }, - { // count. + { // count Type: lang.TokenTraversalStep, Modifiers: lang.SemanticTokenModifiers{}, Range: hcl.Range{ Filename: "test.tf", Start: hcl.Pos{ Line: 4, - Column: 15, - Byte: 65, + Column: 20, + Byte: 80, }, End: hcl.Pos{ Line: 4, - Column: 20, - Byte: 70, + Column: 25, + Byte: 85, }, }, }, { // .index + Type: lang.TokenTraversalStep, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 4, + Column: 25, + Byte: 85, + }, + End: hcl.Pos{ + Line: 4, + Column: 31, + Byte: 91, + }, + }, + }, + } + + diff := cmp.Diff(expectedTokens, tokens) + if diff != "" { + t.Fatalf("unexpected tokens: %s", diff) + } +} + +func TestDecoder_SemanticTokensInFile_expression_extensions_depSchema(t *testing.T) { + bodySchema := &schema.BodySchema{ + Blocks: map[string]*schema.BlockSchema{ + "resource": { + Body: &schema.BodySchema{ + Extensions: &schema.BodyExtensions{ + Count: true, + }, + }, + DependentBody: map[schema.SchemaKey]*schema.BodySchema{ + schema.NewSchemaKey(schema.DependencyKeys{ + Labels: []schema.LabelDependent{ + { + Index: 0, + Value: "aws_instance", + }, + }, + }): { + Attributes: map[string]*schema.AttributeSchema{ + "cpu_core_count": { + Expr: schema.ExprConstraints{ + schema.TraversalExpr{OfType: cty.Number}, + schema.LiteralTypeExpr{Type: cty.Number}, + }, + IsOptional: true, + }, + }, + }, + }, + Labels: []*schema.LabelSchema{ + { + Name: "type", + IsDepKey: true, + SemanticTokenModifiers: lang.SemanticTokenModifiers{ + lang.TokenModifierDependent, + }, + }, + {Name: "name"}, + }, + }, + }, + } + + testCfg := []byte(` +resource "aws_instance" "app_server" { + count = 1 + cpu_core_count = count.index +} +`) + + f, pDiags := hclsyntax.ParseConfig(testCfg, "test.tf", hcl.InitialPos) + if len(pDiags) > 0 { + t.Fatal(pDiags) + } + + d := testPathDecoder(t, &PathContext{ + Schema: bodySchema, + Files: map[string]*hcl.File{ + "test.tf": f, + }, + }) + + ctx := context.Background() + + tokens, err := d.SemanticTokensInFile(ctx, "test.tf") + if err != nil { + t.Fatal(err) + } + + expectedTokens := []lang.SemanticToken{ + { // resource + Type: lang.TokenBlockType, + Modifiers: []lang.SemanticTokenModifier{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 2, + Column: 1, + Byte: 1, + }, + End: hcl.Pos{ + Line: 2, + Column: 9, + Byte: 9, + }, + }, + }, + { // aws_instance + Type: lang.TokenBlockLabel, + Modifiers: []lang.SemanticTokenModifier{ + lang.TokenModifierDependent, + }, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 2, + Column: 10, + Byte: 10, + }, + End: hcl.Pos{ + Line: 2, + Column: 24, + Byte: 24, + }, + }, + }, + { // app_server + Type: lang.TokenBlockLabel, + Modifiers: []lang.SemanticTokenModifier{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 2, + Column: 25, + Byte: 25, + }, + End: hcl.Pos{ + Line: 2, + Column: 37, + Byte: 37, + }, + }, + }, + { // count + Type: lang.TokenAttrName, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 3, + Column: 3, + Byte: 42, + }, + End: hcl.Pos{ + Line: 3, + Column: 8, + Byte: 47, + }, + }, + }, + { // 1 + Type: lang.TokenNumber, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 3, + Column: 20, + Byte: 59, + }, + End: hcl.Pos{ + Line: 3, + Column: 21, + Byte: 60, + }, + }, + }, + { // cpu_core_count + Type: lang.TokenAttrName, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 4, + Column: 3, + Byte: 63, + }, + End: hcl.Pos{ + Line: 4, + Column: 17, + Byte: 77, + }, + }, + }, + { // count Type: lang.TokenTraversalStep, Modifiers: lang.SemanticTokenModifiers{}, Range: hcl.Range{ @@ -1037,12 +1240,29 @@ resource "vault_auth_backend" "blah" { Start: hcl.Pos{ Line: 4, Column: 20, - Byte: 70, + Byte: 80, + }, + End: hcl.Pos{ + Line: 4, + Column: 25, + Byte: 85, + }, + }, + }, + { // .index + Type: lang.TokenTraversalStep, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 4, + Column: 25, + Byte: 85, }, End: hcl.Pos{ Line: 4, - Column: 26, - Byte: 76, + Column: 31, + Byte: 91, }, }, }, From 518098e01bcfb4a981e55bcb6338ddad0dd7590b Mon Sep 17 00:00:00 2001 From: James Pogran Date: Mon, 10 Oct 2022 12:14:52 -0400 Subject: [PATCH 07/23] Apply suggestions from code review Co-authored-by: Radek Simko --- decoder/body_candidates.go | 4 ++-- decoder/hover_test.go | 3 +-- decoder/semantic_tokens.go | 5 ----- decoder/semantic_tokens_expr_test.go | 1 - schema/body_schema.go | 3 --- 5 files changed, 3 insertions(+), 13 deletions(-) diff --git a/decoder/body_candidates.go b/decoder/body_candidates.go index 22c811c7..41795c99 100644 --- a/decoder/body_candidates.go +++ b/decoder/body_candidates.go @@ -18,9 +18,9 @@ func (d *PathDecoder) bodySchemaCandidates(body *hclsyntax.Body, schema *schema. count := 0 if schema.Extensions != nil { - // check if this schema supports Count attribute + // check if count attribute "extension" is enabled here if schema.Extensions.Count { - // check if Count is already used inside this body, so we don't + // check if count attribute is already declared, so we don't // suggest a duplicate if _, ok := body.Attributes["count"]; !ok { candidates.List = append(candidates.List, countAttributeCandidate(editRng)) diff --git a/decoder/hover_test.go b/decoder/hover_test.go index 5dd43adc..d85a30c1 100644 --- a/decoder/hover_test.go +++ b/decoder/hover_test.go @@ -10,7 +10,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/zclconf/go-cty-debug/ctydebug" "github.com/zclconf/go-cty/cty" - "github.com/hashicorp/hcl-lang/lang" "github.com/hashicorp/hcl-lang/schema" "github.com/hashicorp/hcl/v2" @@ -935,7 +934,7 @@ func TestDecoder_HoverAtPos_typeDeclaration(t *testing.T) { } } -func TestDecoder_HoverAtPos_expression_extension(t *testing.T) { +func TestDecoder_HoverAtPos_extension(t *testing.T) { resourceLabelSchema := []*schema.LabelSchema{ {Name: "type", IsDepKey: true}, {Name: "name"}, diff --git a/decoder/semantic_tokens.go b/decoder/semantic_tokens.go index d8565a39..4db090cc 100644 --- a/decoder/semantic_tokens.go +++ b/decoder/semantic_tokens.go @@ -5,7 +5,6 @@ import ( "sort" "github.com/zclconf/go-cty/cty" - icontext "github.com/hashicorp/hcl-lang/context" "github.com/hashicorp/hcl-lang/lang" "github.com/hashicorp/hcl-lang/reference" @@ -62,10 +61,6 @@ func (d *PathDecoder) tokensForBody(ctx context.Context, body *hclsyntax.Body, b if !ok { if bodySchema.Extensions != nil && name == "count" && bodySchema.Extensions.Count { attrSchema = &schema.AttributeSchema{ - Description: lang.MarkupContent{ - Kind: lang.MarkdownKind, - Value: "**count** _optional, number_\n\nThe distinct index number (starting with 0) corresponding to the instance", - }, IsOptional: true, Expr: schema.LiteralTypeOnly(cty.Number), } diff --git a/decoder/semantic_tokens_expr_test.go b/decoder/semantic_tokens_expr_test.go index 66bcb67d..536603a6 100644 --- a/decoder/semantic_tokens_expr_test.go +++ b/decoder/semantic_tokens_expr_test.go @@ -7,7 +7,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/zclconf/go-cty/cty" - "github.com/hashicorp/hcl-lang/lang" "github.com/hashicorp/hcl-lang/reference" "github.com/hashicorp/hcl-lang/schema" diff --git a/schema/body_schema.go b/schema/body_schema.go index cf0fb402..74ca73b2 100644 --- a/schema/body_schema.go +++ b/schema/body_schema.go @@ -52,9 +52,6 @@ type BodySchema struct { type BodyExtensions struct { Count bool // count attribute + count.index refs - ForEach bool // for_each attribute + each.* refs - DynamicBlocks bool // dynamic "block-name" w/ content & for_each inside - SelfRefs bool // self.* refs which refer to outermost parent block body } func (be *BodyExtensions) Copy() *BodyExtensions { From 04e622de5107425de60f647466e453fd4efe1d49 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Mon, 10 Oct 2022 12:16:00 -0400 Subject: [PATCH 08/23] feedback --- schema/body_schema.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/schema/body_schema.go b/schema/body_schema.go index 74ca73b2..04f46577 100644 --- a/schema/body_schema.go +++ b/schema/body_schema.go @@ -61,9 +61,6 @@ func (be *BodyExtensions) Copy() *BodyExtensions { return &BodyExtensions{ Count: be.Count, - ForEach: be.ForEach, - DynamicBlocks: be.DynamicBlocks, - SelfRefs: be.SelfRefs, } } From 3f345cdecf1c4a560129a529881f0e27df7c9e95 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Mon, 10 Oct 2022 12:20:57 -0400 Subject: [PATCH 09/23] feedback --- decoder/hover_test.go | 4 ++-- decoder/semantic_tokens.go | 2 +- decoder/semantic_tokens_expr_test.go | 2 +- schema/body_schema.go | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/decoder/hover_test.go b/decoder/hover_test.go index d85a30c1..a62c423a 100644 --- a/decoder/hover_test.go +++ b/decoder/hover_test.go @@ -8,13 +8,13 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/zclconf/go-cty-debug/ctydebug" - "github.com/zclconf/go-cty/cty" "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/hashicorp/hcl/v2/json" + "github.com/zclconf/go-cty-debug/ctydebug" + "github.com/zclconf/go-cty/cty" ) func TestDecoder_HoverAtPos_noSchema(t *testing.T) { diff --git a/decoder/semantic_tokens.go b/decoder/semantic_tokens.go index 4db090cc..3c6ea45b 100644 --- a/decoder/semantic_tokens.go +++ b/decoder/semantic_tokens.go @@ -4,13 +4,13 @@ import ( "context" "sort" - "github.com/zclconf/go-cty/cty" icontext "github.com/hashicorp/hcl-lang/context" "github.com/hashicorp/hcl-lang/lang" "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" ) // SemanticTokensInFile returns a sequence of semantic tokens diff --git a/decoder/semantic_tokens_expr_test.go b/decoder/semantic_tokens_expr_test.go index 536603a6..d94c4970 100644 --- a/decoder/semantic_tokens_expr_test.go +++ b/decoder/semantic_tokens_expr_test.go @@ -6,12 +6,12 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/zclconf/go-cty/cty" "github.com/hashicorp/hcl-lang/lang" "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 TestDecoder_SemanticTokensInFile_expressions(t *testing.T) { diff --git a/schema/body_schema.go b/schema/body_schema.go index 04f46577..492982a7 100644 --- a/schema/body_schema.go +++ b/schema/body_schema.go @@ -51,7 +51,7 @@ type BodySchema struct { } type BodyExtensions struct { - Count bool // count attribute + count.index refs + Count bool // count attribute + count.index refs } func (be *BodyExtensions) Copy() *BodyExtensions { @@ -60,7 +60,7 @@ func (be *BodyExtensions) Copy() *BodyExtensions { } return &BodyExtensions{ - Count: be.Count, + Count: be.Count, } } From 60f02ababe161fd9fba31ede685c916e57b68b16 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Mon, 10 Oct 2022 12:44:00 -0400 Subject: [PATCH 10/23] Apply suggestions from code review Co-authored-by: Radek Simko --- decoder/body_extensions_test.go | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/decoder/body_extensions_test.go b/decoder/body_extensions_test.go index 8879c748..414daf6a 100644 --- a/decoder/body_extensions_test.go +++ b/decoder/body_extensions_test.go @@ -31,10 +31,10 @@ func TestCompletionAtPos_BodySchema_Extensions(t *testing.T) { "resource": { Labels: []*schema.LabelSchema{ { - Name: "aws_instance", + Name: "type", }, { - Name: "foo", + Name: "name", }, }, Body: &schema.BodySchema{ @@ -85,10 +85,10 @@ func TestCompletionAtPos_BodySchema_Extensions(t *testing.T) { "resource": { Labels: []*schema.LabelSchema{ { - Name: "aws_instance", + Name: "type", }, { - Name: "foo", + Name: "name", }, }, Body: &schema.BodySchema{ @@ -116,10 +116,10 @@ func TestCompletionAtPos_BodySchema_Extensions(t *testing.T) { "resource": { Labels: []*schema.LabelSchema{ { - Name: "aws_instance", + Name: "type", }, { - Name: "foo", + Name: "name", }, }, Body: &schema.BodySchema{ @@ -157,10 +157,10 @@ func TestCompletionAtPos_BodySchema_Extensions(t *testing.T) { "resource": { Labels: []*schema.LabelSchema{ { - Name: "aws_instance", + Name: "type", }, { - Name: "foo", + Name: "name", }, }, Body: &schema.BodySchema{ @@ -210,7 +210,8 @@ func TestCompletionAtPos_BodySchema_Extensions(t *testing.T) { Column: 13, Byte: 55, }, - }, NewText: "count.index", Snippet: "count.index"}, + }, + NewText: "count.index", Snippet: "count.index"}, Kind: lang.TraversalCandidateKind, }, }), @@ -222,10 +223,10 @@ func TestCompletionAtPos_BodySchema_Extensions(t *testing.T) { "resource": { Labels: []*schema.LabelSchema{ { - Name: "aws_instance", + Name: "type", }, { - Name: "foo", + Name: "name", }, }, Body: &schema.BodySchema{ @@ -254,10 +255,10 @@ func TestCompletionAtPos_BodySchema_Extensions(t *testing.T) { "resource": { Labels: []*schema.LabelSchema{ { - Name: "aws_instance", + Name: "type", }, { - Name: "foo", + Name: "name", }, }, Body: &schema.BodySchema{ @@ -295,10 +296,10 @@ func TestCompletionAtPos_BodySchema_Extensions(t *testing.T) { "resource": { Labels: []*schema.LabelSchema{ { - Name: "aws_instance", + Name: "type", }, { - Name: "foo", + Name: "name", }, }, Body: &schema.BodySchema{ From 08c0ed5ffb6a652187ce931f81157ae91f9e215a Mon Sep 17 00:00:00 2001 From: James Pogran Date: Mon, 10 Oct 2022 12:46:12 -0400 Subject: [PATCH 11/23] feedback --- decoder/hover.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/decoder/hover.go b/decoder/hover.go index c1b706e7..94577b10 100644 --- a/decoder/hover.go +++ b/decoder/hover.go @@ -268,8 +268,11 @@ func (d *PathDecoder) hoverDataForExpr(ctx context.Context, expr hcl.Expression, }, }) && icontext.ActiveCountFromContext(ctx) { return &lang.HoverData{ - Content: lang.Markdown("**count.index** fooooo"), - Range: expr.Range(), + Content: lang.MarkupContent{ + Kind: lang.MarkdownKind, + Value: "**count** _optional, number_\n\nThe distinct index number (starting with 0) corresponding to the instance", + }, + Range: expr.Range(), }, nil } From 1af1cce6b193fd8f98f0250b9b89b7d29ff95449 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Mon, 10 Oct 2022 12:49:13 -0400 Subject: [PATCH 12/23] Apply suggestions from code review Co-authored-by: Radek Simko --- decoder/semantic_tokens_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/decoder/semantic_tokens_test.go b/decoder/semantic_tokens_test.go index 7834cab3..5dfedf1a 100644 --- a/decoder/semantic_tokens_test.go +++ b/decoder/semantic_tokens_test.go @@ -7,7 +7,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/zclconf/go-cty/cty" - "github.com/hashicorp/hcl-lang/lang" "github.com/hashicorp/hcl-lang/schema" "github.com/hashicorp/hcl/v2" @@ -853,7 +852,7 @@ resource "vault_auth_backend" "blah" { } } -func TestDecoder_SemanticTokensInFile_expression_extensions(t *testing.T) { +func TestDecoder_SemanticTokensInFile_extensions(t *testing.T) { bodySchema := &schema.BodySchema{ Blocks: map[string]*schema.BlockSchema{ "resource": { From 0623c98774672a7efbe84766ad55c21cdf25e81a Mon Sep 17 00:00:00 2001 From: James Pogran Date: Mon, 10 Oct 2022 12:51:12 -0400 Subject: [PATCH 13/23] feedback --- decoder/hover_test.go | 8 +++++--- decoder/semantic_tokens_expr_test.go | 8 +++++--- decoder/semantic_tokens_test.go | 4 ++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/decoder/hover_test.go b/decoder/hover_test.go index a62c423a..fc540874 100644 --- a/decoder/hover_test.go +++ b/decoder/hover_test.go @@ -8,13 +8,14 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/zclconf/go-cty-debug/ctydebug" + "github.com/zclconf/go-cty/cty" + "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/hashicorp/hcl/v2/json" - "github.com/zclconf/go-cty-debug/ctydebug" - "github.com/zclconf/go-cty/cty" ) func TestDecoder_HoverAtPos_noSchema(t *testing.T) { @@ -236,10 +237,11 @@ func TestDecoder_HoverAtPos_nilBodySchema(t *testing.T) { }, } - ctx := context.Background() for i, tc := range testCases { t.Run(fmt.Sprintf("%d-%s", i, tc.name), func(t *testing.T) { + ctx := context.Background() + f, pDiags := hclsyntax.ParseConfig([]byte(tc.config), "test.tf", hcl.InitialPos) if len(pDiags) > 0 { t.Fatal(pDiags) diff --git a/decoder/semantic_tokens_expr_test.go b/decoder/semantic_tokens_expr_test.go index d94c4970..f32d3ba6 100644 --- a/decoder/semantic_tokens_expr_test.go +++ b/decoder/semantic_tokens_expr_test.go @@ -6,12 +6,13 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/zclconf/go-cty/cty" + "github.com/hashicorp/hcl-lang/lang" "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 TestDecoder_SemanticTokensInFile_expressions(t *testing.T) { @@ -1434,10 +1435,11 @@ EOT }, } - ctx := context.Background() - for i, tc := range testCases { t.Run(fmt.Sprintf("%d-%s", i, tc.name), func(t *testing.T) { + + ctx := context.Background() + bodySchema := &schema.BodySchema{ Attributes: tc.attrSchema, } diff --git a/decoder/semantic_tokens_test.go b/decoder/semantic_tokens_test.go index 5dfedf1a..efd01e35 100644 --- a/decoder/semantic_tokens_test.go +++ b/decoder/semantic_tokens_test.go @@ -1027,12 +1027,12 @@ resource "aws_instance" "app_server" { }, End: hcl.Pos{ Line: 4, - Column: 25, + Column: 26, Byte: 85, }, }, }, - { // .index + { // index Type: lang.TokenTraversalStep, Modifiers: lang.SemanticTokenModifiers{}, Range: hcl.Range{ From e29b159c25dc8b600c2c3812817672846a9d45e1 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Mon, 10 Oct 2022 12:54:39 -0400 Subject: [PATCH 14/23] feedback --- decoder/hover_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/decoder/hover_test.go b/decoder/hover_test.go index fc540874..cd3ce9aa 100644 --- a/decoder/hover_test.go +++ b/decoder/hover_test.go @@ -237,7 +237,6 @@ func TestDecoder_HoverAtPos_nilBodySchema(t *testing.T) { }, } - for i, tc := range testCases { t.Run(fmt.Sprintf("%d-%s", i, tc.name), func(t *testing.T) { ctx := context.Background() From a4b842d2f47e5b5a6927e5a7368d282ea398a18e Mon Sep 17 00:00:00 2001 From: James Pogran Date: Mon, 10 Oct 2022 13:10:56 -0400 Subject: [PATCH 15/23] Extracted count candidate and hover data --- decoder/body_candidates.go | 14 -------------- decoder/decoder.go | 25 +++++++++++++++++++++++++ decoder/expression_candidates.go | 12 +----------- decoder/hover.go | 16 ++-------------- 4 files changed, 28 insertions(+), 39 deletions(-) diff --git a/decoder/body_candidates.go b/decoder/body_candidates.go index 41795c99..a9636770 100644 --- a/decoder/body_candidates.go +++ b/decoder/body_candidates.go @@ -146,17 +146,3 @@ func isBlockDeclarable(body *hclsyntax.Body, blockType string, bSchema *schema.B } return true } - -func countAttributeCandidate(editRng hcl.Range) lang.Candidate { - return lang.Candidate{ - Label: "count", - Detail: "optional, number", - Description: lang.PlainText("The distinct index number (starting with 0) corresponding to the instance"), - Kind: lang.AttributeCandidateKind, - TextEdit: lang.TextEdit{ - NewText: "count", - Snippet: "count = ${1:1}", - Range: editRng, - }, - } -} diff --git a/decoder/decoder.go b/decoder/decoder.go index 4b0499d0..05beee1f 100644 --- a/decoder/decoder.go +++ b/decoder/decoder.go @@ -3,6 +3,7 @@ package decoder 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" @@ -157,3 +158,27 @@ func decodeBody(body hcl.Body, bodySchema *schema.BodySchema) bodyContent { func stringPos(pos hcl.Pos) string { return fmt.Sprintf("%d,%d", pos.Line, pos.Column) } + +func countAttributeCandidate(editRng hcl.Range) lang.Candidate { + return lang.Candidate{ + Label: "count", + Detail: "optional, number", + Description: lang.PlainText("The distinct index number (starting with 0) corresponding to the instance"), + Kind: lang.AttributeCandidateKind, + TextEdit: lang.TextEdit{ + NewText: "count", + Snippet: "count = ${1:1}", + Range: editRng, + }, + } +} + +func countAttributeHoverData(editRng hcl.Range) *lang.HoverData { + return &lang.HoverData{ + Content: lang.MarkupContent{ + Kind: lang.MarkdownKind, + Value: "**count** _optional, number_\n\nThe distinct index number (starting with 0) corresponding to the instance", + }, + Range: editRng, + } +} diff --git a/decoder/expression_candidates.go b/decoder/expression_candidates.go index 947302f7..9f180a97 100644 --- a/decoder/expression_candidates.go +++ b/decoder/expression_candidates.go @@ -464,17 +464,7 @@ func (d *PathDecoder) candidatesForTraversalConstraint(ctx context.Context, tc s ext, ok := icontext.ExtensionsFromContext(ctx) if ok && ext.Count && icontext.ActiveCountFromContext(ctx) { - candidates = append(candidates, lang.Candidate{ - Label: "count.index", - Detail: "number", - Description: lang.PlainText("The distinct index number (starting with 0) corresponding to the instance"), - Kind: lang.TraversalCandidateKind, - TextEdit: lang.TextEdit{ - NewText: "count.index", - Snippet: "count.index", - Range: editRng, - }, - }) + candidates = append(candidates, countAttributeCandidate(editRng)) } if d.pathCtx.ReferenceTargets == nil { diff --git a/decoder/hover.go b/decoder/hover.go index 94577b10..8af68273 100644 --- a/decoder/hover.go +++ b/decoder/hover.go @@ -61,13 +61,7 @@ func (d *PathDecoder) hoverAtPos(ctx context.Context, body *hclsyntax.Body, body if bodySchema.Extensions != nil { if name == "count" && bodySchema.Extensions.Count { - return &lang.HoverData{ - Content: lang.MarkupContent{ - Kind: lang.MarkdownKind, - Value: "**count** _optional, number_\n\nThe distinct index number (starting with 0) corresponding to the instance", - }, - Range: attr.Range(), - }, nil + return countAttributeHoverData(attr.Range()), nil } } @@ -267,13 +261,7 @@ func (d *PathDecoder) hoverDataForExpr(ctx context.Context, expr hcl.Expression, Name: "index", }, }) && icontext.ActiveCountFromContext(ctx) { - return &lang.HoverData{ - Content: lang.MarkupContent{ - Kind: lang.MarkdownKind, - Value: "**count** _optional, number_\n\nThe distinct index number (starting with 0) corresponding to the instance", - }, - Range: expr.Range(), - }, nil + return countAttributeHoverData(expr.Range()), nil } tes, ok := constraints.TraversalExprs() From b1a445277b99f2ec414b312954c435faaca72c9f Mon Sep 17 00:00:00 2001 From: James Pogran Date: Mon, 10 Oct 2022 13:23:43 -0400 Subject: [PATCH 16/23] feedback --- context/context.go | 11 ----------- decoder/body_extensions_test.go | 27 +++++++++++++++------------ decoder/candidates.go | 1 - decoder/decoder.go | 14 ++++++++++++++ decoder/expression_candidates.go | 5 ++--- decoder/hover.go | 1 - decoder/semantic_tokens.go | 17 +++++++++++++++-- decoder/semantic_tokens_test.go | 22 +++++++++++----------- 8 files changed, 57 insertions(+), 41 deletions(-) diff --git a/context/context.go b/context/context.go index 4478852c..7d81291c 100644 --- a/context/context.go +++ b/context/context.go @@ -2,27 +2,16 @@ package context import ( "context" - - "github.com/hashicorp/hcl-lang/schema" ) type bodyExtCtxKey struct{} type bodyActiveCountCtxKey struct{} -func WithExtensions(ctx context.Context, ext *schema.BodyExtensions) context.Context { - return context.WithValue(ctx, bodyExtCtxKey{}, ext) -} - func WithActiveCount(ctx context.Context) context.Context { return context.WithValue(ctx, bodyActiveCountCtxKey{}, true) } -func ExtensionsFromContext(ctx context.Context) (*schema.BodyExtensions, bool) { - ext, ok := ctx.Value(bodyExtCtxKey{}).(*schema.BodyExtensions) - return ext, ok -} - func ActiveCountFromContext(ctx context.Context) bool { return ctx.Value(bodyActiveCountCtxKey{}) != nil } diff --git a/decoder/body_extensions_test.go b/decoder/body_extensions_test.go index 414daf6a..26ff6755 100644 --- a/decoder/body_extensions_test.go +++ b/decoder/body_extensions_test.go @@ -198,20 +198,23 @@ func TestCompletionAtPos_BodySchema_Extensions(t *testing.T) { Kind: lang.PlainTextKind, }, Detail: "number", - TextEdit: lang.TextEdit{Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 3, - Column: 13, - Byte: 55, - }, - End: hcl.Pos{ - Line: 3, - Column: 13, - Byte: 55, + TextEdit: lang.TextEdit{ + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 3, + Column: 13, + Byte: 55, + }, + End: hcl.Pos{ + Line: 3, + Column: 13, + Byte: 55, + }, }, + NewText: "count.index", + Snippet: "count.index", }, - NewText: "count.index", Snippet: "count.index"}, Kind: lang.TraversalCandidateKind, }, }), diff --git a/decoder/candidates.go b/decoder/candidates.go index 13b7d07e..e33a52fe 100644 --- a/decoder/candidates.go +++ b/decoder/candidates.go @@ -50,7 +50,6 @@ func (d *PathDecoder) candidatesAtPos(ctx context.Context, body *hclsyntax.Body, filename := body.Range().Filename if bodySchema.Extensions != nil { - ctx = icontext.WithExtensions(ctx, bodySchema.Extensions) if bodySchema.Extensions.Count { if _, ok := body.Attributes["count"]; ok { // append to context we need count completed diff --git a/decoder/decoder.go b/decoder/decoder.go index 05beee1f..1d1c22e9 100644 --- a/decoder/decoder.go +++ b/decoder/decoder.go @@ -173,6 +173,20 @@ func countAttributeCandidate(editRng hcl.Range) lang.Candidate { } } +func countIndexCandidate(editRng hcl.Range) lang.Candidate { + return lang.Candidate{ + Label: "count.index", + Detail: "number", + Description: lang.PlainText("The distinct index number (starting with 0) corresponding to the instance"), + Kind: lang.TraversalCandidateKind, + TextEdit: lang.TextEdit{ + NewText: "count.index", + Snippet: "count.index", + Range: editRng, + }, + } +} + func countAttributeHoverData(editRng hcl.Range) *lang.HoverData { return &lang.HoverData{ Content: lang.MarkupContent{ diff --git a/decoder/expression_candidates.go b/decoder/expression_candidates.go index 9f180a97..91945e87 100644 --- a/decoder/expression_candidates.go +++ b/decoder/expression_candidates.go @@ -462,9 +462,8 @@ func (d *PathDecoder) constraintToCandidates(ctx context.Context, constraint sch func (d *PathDecoder) candidatesForTraversalConstraint(ctx context.Context, tc schema.TraversalExpr, outerBodyRng, prefixRng, editRng hcl.Range) []lang.Candidate { candidates := make([]lang.Candidate, 0) - ext, ok := icontext.ExtensionsFromContext(ctx) - if ok && ext.Count && icontext.ActiveCountFromContext(ctx) { - candidates = append(candidates, countAttributeCandidate(editRng)) + if icontext.ActiveCountFromContext(ctx) { + candidates = append(candidates, countIndexCandidate(editRng)) } if d.pathCtx.ReferenceTargets == nil { diff --git a/decoder/hover.go b/decoder/hover.go index 8af68273..74284457 100644 --- a/decoder/hover.go +++ b/decoder/hover.go @@ -47,7 +47,6 @@ func (d *PathDecoder) hoverAtPos(ctx context.Context, body *hclsyntax.Body, body filename := body.Range().Filename if bodySchema.Extensions != nil { - ctx = icontext.WithExtensions(ctx, bodySchema.Extensions) if bodySchema.Extensions.Count { if _, ok := body.Attributes["count"]; ok { // append to context we need count provided diff --git a/decoder/semantic_tokens.go b/decoder/semantic_tokens.go index 3c6ea45b..78261d8c 100644 --- a/decoder/semantic_tokens.go +++ b/decoder/semantic_tokens.go @@ -4,13 +4,14 @@ import ( "context" "sort" + "github.com/zclconf/go-cty/cty" + icontext "github.com/hashicorp/hcl-lang/context" "github.com/hashicorp/hcl-lang/lang" "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" ) // SemanticTokensInFile returns a sequence of semantic tokens @@ -185,7 +186,19 @@ func (d *PathDecoder) tokensForExpression(ctx context.Context, expr hclsyntax.Ex tokens = append(tokens, lang.SemanticToken{ Type: lang.TokenTraversalStep, Modifiers: []lang.SemanticTokenModifier{}, - Range: traversal[1].SourceRange(), + Range: hcl.Range{ + Filename: traversal[1].SourceRange().Filename, + Start: hcl.Pos{ + Line: traversal[1].SourceRange().Start.Line, + Column: traversal[1].SourceRange().Start.Column + 1, + Byte: traversal[1].SourceRange().Start.Byte + 1, + }, + End: hcl.Pos{ + Line: traversal[1].SourceRange().End.Line, + Column: traversal[1].SourceRange().End.Column + 1, + Byte: traversal[1].SourceRange().End.Byte + 1, + }, + }, }) return tokens } diff --git a/decoder/semantic_tokens_test.go b/decoder/semantic_tokens_test.go index efd01e35..4e145692 100644 --- a/decoder/semantic_tokens_test.go +++ b/decoder/semantic_tokens_test.go @@ -6,12 +6,12 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/zclconf/go-cty/cty" "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/hashicorp/hcl/v2/json" + "github.com/zclconf/go-cty/cty" ) func TestDecoder_SemanticTokensInFile_emptyBody(t *testing.T) { @@ -1027,7 +1027,7 @@ resource "aws_instance" "app_server" { }, End: hcl.Pos{ Line: 4, - Column: 26, + Column: 25, Byte: 85, }, }, @@ -1039,13 +1039,13 @@ resource "aws_instance" "app_server" { Filename: "test.tf", Start: hcl.Pos{ Line: 4, - Column: 25, - Byte: 85, + Column: 26, + Byte: 86, }, End: hcl.Pos{ Line: 4, - Column: 31, - Byte: 91, + Column: 32, + Byte: 92, }, }, }, @@ -1248,20 +1248,20 @@ resource "aws_instance" "app_server" { }, }, }, - { // .index + { // index Type: lang.TokenTraversalStep, Modifiers: lang.SemanticTokenModifiers{}, Range: hcl.Range{ Filename: "test.tf", Start: hcl.Pos{ Line: 4, - Column: 25, - Byte: 85, + Column: 26, + Byte: 86, }, End: hcl.Pos{ Line: 4, - Column: 31, - Byte: 91, + Column: 32, + Byte: 92, }, }, }, From 533e459f5a9a606f23d3298420b431f8e22564e1 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Tue, 11 Oct 2022 10:33:52 -0400 Subject: [PATCH 17/23] remove context pkg --- context/context.go | 17 ----------------- decoder/candidates.go | 3 +-- decoder/expression_candidates.go | 3 +-- decoder/hover.go | 5 ++--- decoder/semantic_tokens.go | 7 +++---- schema/schema.go | 16 ++++++++++++++++ 6 files changed, 23 insertions(+), 28 deletions(-) delete mode 100644 context/context.go diff --git a/context/context.go b/context/context.go deleted file mode 100644 index 7d81291c..00000000 --- a/context/context.go +++ /dev/null @@ -1,17 +0,0 @@ -package context - -import ( - "context" -) - -type bodyExtCtxKey struct{} - -type bodyActiveCountCtxKey struct{} - -func WithActiveCount(ctx context.Context) context.Context { - return context.WithValue(ctx, bodyActiveCountCtxKey{}, true) -} - -func ActiveCountFromContext(ctx context.Context) bool { - return ctx.Value(bodyActiveCountCtxKey{}) != nil -} diff --git a/decoder/candidates.go b/decoder/candidates.go index e33a52fe..4cf014c7 100644 --- a/decoder/candidates.go +++ b/decoder/candidates.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - icontext "github.com/hashicorp/hcl-lang/context" "github.com/hashicorp/hcl-lang/lang" "github.com/hashicorp/hcl-lang/schema" "github.com/hashicorp/hcl/v2" @@ -53,7 +52,7 @@ func (d *PathDecoder) candidatesAtPos(ctx context.Context, body *hclsyntax.Body, if bodySchema.Extensions.Count { if _, ok := body.Attributes["count"]; ok { // append to context we need count completed - ctx = icontext.WithActiveCount(ctx) + ctx = schema.WithActiveCount(ctx) } } } diff --git a/decoder/expression_candidates.go b/decoder/expression_candidates.go index 91945e87..fba7ced9 100644 --- a/decoder/expression_candidates.go +++ b/decoder/expression_candidates.go @@ -9,7 +9,6 @@ import ( "github.com/zclconf/go-cty/cty" - icontext "github.com/hashicorp/hcl-lang/context" "github.com/hashicorp/hcl-lang/lang" "github.com/hashicorp/hcl-lang/reference" "github.com/hashicorp/hcl-lang/schema" @@ -462,7 +461,7 @@ func (d *PathDecoder) constraintToCandidates(ctx context.Context, constraint sch func (d *PathDecoder) candidatesForTraversalConstraint(ctx context.Context, tc schema.TraversalExpr, outerBodyRng, prefixRng, editRng hcl.Range) []lang.Candidate { candidates := make([]lang.Candidate, 0) - if icontext.ActiveCountFromContext(ctx) { + if schema.ActiveCountFromContext(ctx) { candidates = append(candidates, countIndexCandidate(editRng)) } diff --git a/decoder/hover.go b/decoder/hover.go index 74284457..1f8c4125 100644 --- a/decoder/hover.go +++ b/decoder/hover.go @@ -8,7 +8,6 @@ import ( "github.com/zclconf/go-cty/cty" - icontext "github.com/hashicorp/hcl-lang/context" "github.com/hashicorp/hcl-lang/lang" "github.com/hashicorp/hcl-lang/reference" "github.com/hashicorp/hcl-lang/schema" @@ -50,7 +49,7 @@ func (d *PathDecoder) hoverAtPos(ctx context.Context, body *hclsyntax.Body, body if bodySchema.Extensions.Count { if _, ok := body.Attributes["count"]; ok { // append to context we need count provided - ctx = icontext.WithActiveCount(ctx) + ctx = schema.WithActiveCount(ctx) } } } @@ -259,7 +258,7 @@ func (d *PathDecoder) hoverDataForExpr(ctx context.Context, expr hcl.Expression, lang.AttrStep{ Name: "index", }, - }) && icontext.ActiveCountFromContext(ctx) { + }) && schema.ActiveCountFromContext(ctx) { return countAttributeHoverData(expr.Range()), nil } diff --git a/decoder/semantic_tokens.go b/decoder/semantic_tokens.go index 78261d8c..5e7cfebc 100644 --- a/decoder/semantic_tokens.go +++ b/decoder/semantic_tokens.go @@ -6,7 +6,6 @@ import ( "github.com/zclconf/go-cty/cty" - icontext "github.com/hashicorp/hcl-lang/context" "github.com/hashicorp/hcl-lang/lang" "github.com/hashicorp/hcl-lang/reference" "github.com/hashicorp/hcl-lang/schema" @@ -52,7 +51,7 @@ func (d *PathDecoder) tokensForBody(ctx context.Context, body *hclsyntax.Body, b if bodySchema.Extensions.Count { if _, ok := body.Attributes["count"]; ok { // append to context we need count provided - ctx = icontext.WithActiveCount(ctx) + ctx = schema.WithActiveCount(ctx) } } } @@ -131,7 +130,7 @@ func (d *PathDecoder) tokensForBody(ctx context.Context, body *hclsyntax.Body, b if blockSchema.Body.Extensions.Count { if _, ok := block.Body.Attributes["count"]; ok { // append to context we need count provided - ctx = icontext.WithActiveCount(ctx) + ctx = schema.WithActiveCount(ctx) } } } @@ -173,7 +172,7 @@ func (d *PathDecoder) tokensForExpression(ctx context.Context, expr hclsyntax.Ex Name: "index", }, } - countAvailable := icontext.ActiveCountFromContext(ctx) + countAvailable := schema.ActiveCountFromContext(ctx) // TODO why is countAvailable not true here? // if address.Equals(countIndexAttr) && countAvailable { if address.Equals(countIndexAttr) && countAvailable { diff --git a/schema/schema.go b/schema/schema.go index 1f125686..6cd3e34a 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -1,8 +1,24 @@ package schema +import ( + "context" +) + type schemaImplSigil struct{} // Schema represents any schema (e.g. attribute, label, or a block) type Schema interface { isSchemaImpl() schemaImplSigil } + +type bodyExtCtxKey struct{} + +type bodyActiveCountCtxKey struct{} + +func WithActiveCount(ctx context.Context) context.Context { + return context.WithValue(ctx, bodyActiveCountCtxKey{}, true) +} + +func ActiveCountFromContext(ctx context.Context) bool { + return ctx.Value(bodyActiveCountCtxKey{}) != nil +} From dc65e6da9890399a0a15601928d6d05348b1e278 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Tue, 11 Oct 2022 10:58:28 -0400 Subject: [PATCH 18/23] copy dependent body schema extensions as well --- decoder/decoder.go | 1 + 1 file changed, 1 insertion(+) diff --git a/decoder/decoder.go b/decoder/decoder.go index 1d1c22e9..5fc0977c 100644 --- a/decoder/decoder.go +++ b/decoder/decoder.go @@ -75,6 +75,7 @@ func mergeBlockBodySchemas(block *hcl.Block, blockSchema *schema.BlockSchema) (* mergedSchema.ImpliedOrigins = append(mergedSchema.ImpliedOrigins, depSchema.ImpliedOrigins...) mergedSchema.Targets = depSchema.Targets.Copy() mergedSchema.DocsLink = depSchema.DocsLink.Copy() + mergedSchema.Extensions = depSchema.Extensions.Copy() } return mergedSchema, nil From 0a1ffa5cf3cc71b0878f90218225d6b22b0acf23 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Tue, 11 Oct 2022 11:06:25 -0400 Subject: [PATCH 19/23] formatting --- decoder/expression_candidates.go | 3 +-- decoder/hover.go | 4 +--- decoder/hover_test.go | 5 ++--- decoder/semantic_tokens.go | 12 +++++------- decoder/semantic_tokens_expr_test.go | 3 +-- 5 files changed, 10 insertions(+), 17 deletions(-) diff --git a/decoder/expression_candidates.go b/decoder/expression_candidates.go index fba7ced9..2589622c 100644 --- a/decoder/expression_candidates.go +++ b/decoder/expression_candidates.go @@ -7,13 +7,12 @@ import ( "strconv" "strings" - "github.com/zclconf/go-cty/cty" - "github.com/hashicorp/hcl-lang/lang" "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 (d *PathDecoder) attrValueCandidatesAtPos(ctx context.Context, attr *hclsyntax.Attribute, schema *schema.AttributeSchema, outerBodyRng hcl.Range, pos hcl.Pos) (lang.Candidates, error) { diff --git a/decoder/hover.go b/decoder/hover.go index 1f8c4125..f7bb12af 100644 --- a/decoder/hover.go +++ b/decoder/hover.go @@ -6,13 +6,12 @@ import ( "sort" "strings" - "github.com/zclconf/go-cty/cty" - "github.com/hashicorp/hcl-lang/lang" "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 (d *PathDecoder) HoverAtPos(ctx context.Context, filename string, pos hcl.Pos) (*lang.HoverData, error) { @@ -56,7 +55,6 @@ func (d *PathDecoder) hoverAtPos(ctx context.Context, body *hclsyntax.Body, body for name, attr := range body.Attributes { if attr.Range().ContainsPos(pos) { - if bodySchema.Extensions != nil { if name == "count" && bodySchema.Extensions.Count { return countAttributeHoverData(attr.Range()), nil diff --git a/decoder/hover_test.go b/decoder/hover_test.go index cd3ce9aa..622a8c16 100644 --- a/decoder/hover_test.go +++ b/decoder/hover_test.go @@ -8,14 +8,13 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/zclconf/go-cty-debug/ctydebug" - "github.com/zclconf/go-cty/cty" - "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/hashicorp/hcl/v2/json" + "github.com/zclconf/go-cty-debug/ctydebug" + "github.com/zclconf/go-cty/cty" ) func TestDecoder_HoverAtPos_noSchema(t *testing.T) { diff --git a/decoder/semantic_tokens.go b/decoder/semantic_tokens.go index 5e7cfebc..19e9ab57 100644 --- a/decoder/semantic_tokens.go +++ b/decoder/semantic_tokens.go @@ -4,13 +4,12 @@ import ( "context" "sort" - "github.com/zclconf/go-cty/cty" - "github.com/hashicorp/hcl-lang/lang" "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" ) // SemanticTokensInFile returns a sequence of semantic tokens @@ -46,7 +45,6 @@ func (d *PathDecoder) tokensForBody(ctx context.Context, body *hclsyntax.Body, b return tokens } - // TODO: test for count extension used in root BodySchema if bodySchema.Extensions != nil { if bodySchema.Extensions.Count { if _, ok := body.Attributes["count"]; ok { @@ -125,7 +123,6 @@ func (d *PathDecoder) tokensForBody(ctx context.Context, body *hclsyntax.Body, b } if block.Body != nil { - // TODO: Test for count.index in a sub-block if blockSchema.Body != nil && blockSchema.Body.Extensions != nil { if blockSchema.Body.Extensions.Count { if _, ok := block.Body.Attributes["count"]; ok { @@ -164,6 +161,7 @@ func (d *PathDecoder) tokensForExpression(ctx context.Context, expr hclsyntax.Ex } address, _ := lang.TraversalToAddress(eType.AsTraversal()) + countAvailable := schema.ActiveCountFromContext(ctx) countIndexAttr := lang.Address{ lang.RootStep{ Name: "count", @@ -172,16 +170,16 @@ func (d *PathDecoder) tokensForExpression(ctx context.Context, expr hclsyntax.Ex Name: "index", }, } - countAvailable := schema.ActiveCountFromContext(ctx) - // TODO why is countAvailable not true here? - // if address.Equals(countIndexAttr) && countAvailable { + if address.Equals(countIndexAttr) && countAvailable { traversal := eType.AsTraversal() + tokens = append(tokens, lang.SemanticToken{ Type: lang.TokenTraversalStep, Modifiers: []lang.SemanticTokenModifier{}, Range: traversal[0].SourceRange(), }) + tokens = append(tokens, lang.SemanticToken{ Type: lang.TokenTraversalStep, Modifiers: []lang.SemanticTokenModifier{}, diff --git a/decoder/semantic_tokens_expr_test.go b/decoder/semantic_tokens_expr_test.go index f32d3ba6..cc0a4284 100644 --- a/decoder/semantic_tokens_expr_test.go +++ b/decoder/semantic_tokens_expr_test.go @@ -6,13 +6,12 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/zclconf/go-cty/cty" - "github.com/hashicorp/hcl-lang/lang" "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 TestDecoder_SemanticTokensInFile_expressions(t *testing.T) { From 512c5b71c6586f5d7e4f828d8f9c643ac35045b1 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Tue, 11 Oct 2022 11:12:38 -0400 Subject: [PATCH 20/23] formatting --- decoder/candidates_test.go | 6 +++--- decoder/hover_expressions_test.go | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/decoder/candidates_test.go b/decoder/candidates_test.go index 95f7a1ba..6a7b1781 100644 --- a/decoder/candidates_test.go +++ b/decoder/candidates_test.go @@ -8,14 +8,14 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/zclconf/go-cty-debug/ctydebug" - "github.com/zclconf/go-cty/cty" "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/hashicorp/hcl/v2/json" + "github.com/zclconf/go-cty-debug/ctydebug" + "github.com/zclconf/go-cty/cty" ) func TestDecoder_CandidatesAtPos_noSchema(t *testing.T) { @@ -460,7 +460,7 @@ func TestDecoder_CandidatesAtPos_rightHandSide(t *testing.T) { }, } testConfig := []byte(`myblock "foo" { - num_attr = + num_attr = } `) diff --git a/decoder/hover_expressions_test.go b/decoder/hover_expressions_test.go index 1ff634b6..cde250ed 100644 --- a/decoder/hover_expressions_test.go +++ b/decoder/hover_expressions_test.go @@ -7,14 +7,13 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/zclconf/go-cty-debug/ctydebug" - "github.com/zclconf/go-cty/cty" - "github.com/hashicorp/hcl-lang/lang" "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-debug/ctydebug" + "github.com/zclconf/go-cty/cty" ) func TestDecoder_HoverAtPos_expressions(t *testing.T) { From 9f4bbb7f8adf165bb40114566f675362b0f70eda Mon Sep 17 00:00:00 2001 From: James Pogran Date: Wed, 12 Oct 2022 12:23:51 -0400 Subject: [PATCH 21/23] Apply suggestions from code review Co-authored-by: Radek Simko --- decoder/candidates_test.go | 1 - decoder/hover.go | 5 ++++- decoder/hover_test.go | 5 ++++- decoder/semantic_tokens.go | 5 ++++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/decoder/candidates_test.go b/decoder/candidates_test.go index 6a7b1781..fc7b3beb 100644 --- a/decoder/candidates_test.go +++ b/decoder/candidates_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/hcl-lang/lang" "github.com/hashicorp/hcl-lang/schema" "github.com/hashicorp/hcl/v2" diff --git a/decoder/hover.go b/decoder/hover.go index f7bb12af..7d0239c0 100644 --- a/decoder/hover.go +++ b/decoder/hover.go @@ -248,7 +248,10 @@ func (d *PathDecoder) hoverDataForExpr(ctx context.Context, expr hcl.Expression, }, nil } - address, _ := lang.TraversalToAddress(e.AsTraversal()) + address, err := lang.TraversalToAddress(e.AsTraversal()) + if err != nil { + return nil, err + } if address.Equals(lang.Address{ lang.RootStep{ Name: "count", diff --git a/decoder/hover_test.go b/decoder/hover_test.go index 622a8c16..6e6086fb 100644 --- a/decoder/hover_test.go +++ b/decoder/hover_test.go @@ -974,7 +974,10 @@ func TestDecoder_HoverAtPos_extension(t *testing.T) { } `) - f, _ := hclsyntax.ParseConfig(testConfig, "test.tf", hcl.InitialPos) + f, err := hclsyntax.ParseConfig(testConfig, "test.tf", hcl.InitialPos) + if err != nil { + t.Fatal(err) + } d := testPathDecoder(t, &PathContext{ Schema: bodySchema, diff --git a/decoder/semantic_tokens.go b/decoder/semantic_tokens.go index 19e9ab57..11f0089f 100644 --- a/decoder/semantic_tokens.go +++ b/decoder/semantic_tokens.go @@ -160,7 +160,10 @@ func (d *PathDecoder) tokensForExpression(ctx context.Context, expr hclsyntax.Ex } } - address, _ := lang.TraversalToAddress(eType.AsTraversal()) + address, err := lang.TraversalToAddress(eType.AsTraversal()) + if err != nil { + return tokens + } countAvailable := schema.ActiveCountFromContext(ctx) countIndexAttr := lang.Address{ lang.RootStep{ From a86a9e17a187eaa08862230be6f59807ace30d8d Mon Sep 17 00:00:00 2001 From: James Pogran Date: Thu, 13 Oct 2022 13:00:00 -0400 Subject: [PATCH 22/23] test expression extension in a sub block --- decoder/semantic_tokens_test.go | 231 ++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) diff --git a/decoder/semantic_tokens_test.go b/decoder/semantic_tokens_test.go index 4e145692..c1575c1c 100644 --- a/decoder/semantic_tokens_test.go +++ b/decoder/semantic_tokens_test.go @@ -1404,3 +1404,234 @@ resource "vault_auth_backend" "blah" { t.Fatalf("unexpected tokens: %s", diff) } } + +func TestDecoder_SemanticTokensInFile_extensions_countIndexInSubBlock(t *testing.T) { + bodySchema := &schema.BodySchema{ + Blocks: map[string]*schema.BlockSchema{ + "resource": { + Body: &schema.BodySchema{ + Extensions: &schema.BodyExtensions{ + Count: true, + }, + Attributes: map[string]*schema.AttributeSchema{ + "count": { + Expr: schema.LiteralTypeOnly(cty.Number), + }, + }, + Blocks: map[string]*schema.BlockSchema{ + "block": { + Body: &schema.BodySchema{ + Attributes: map[string]*schema.AttributeSchema{ + "attr": { + Expr: schema.LiteralTypeOnly(cty.Number), + }, + }, + }, + }, + }, + }, + Labels: []*schema.LabelSchema{ + { + Name: "type", + IsDepKey: true, + SemanticTokenModifiers: lang.SemanticTokenModifiers{ + lang.TokenModifierDependent, + }, + }, + {Name: "name"}, + }, + }, + }, + } + + testCfg := []byte(` +resource "foobar" "name" { + count = 1 + block { + attr = count.index + } +} +`) + + f, pDiags := hclsyntax.ParseConfig(testCfg, "test.tf", hcl.InitialPos) + if len(pDiags) > 0 { + t.Fatal(pDiags) + } + + d := testPathDecoder(t, &PathContext{ + Schema: bodySchema, + Files: map[string]*hcl.File{ + "test.tf": f, + }, + }) + + ctx := context.Background() + + tokens, err := d.SemanticTokensInFile(ctx, "test.tf") + if err != nil { + t.Fatal(err) + } + + expectedTokens := []lang.SemanticToken{ + { // resource + Type: lang.TokenBlockType, + Modifiers: []lang.SemanticTokenModifier{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 2, + Column: 1, + Byte: 1, + }, + End: hcl.Pos{ + Line: 2, + Column: 9, + Byte: 9, + }, + }, + }, + { // foobar + Type: lang.TokenBlockLabel, + Modifiers: []lang.SemanticTokenModifier{ + lang.TokenModifierDependent, + }, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 2, + Column: 10, + Byte: 10, + }, + End: hcl.Pos{ + Line: 2, + Column: 18, + Byte: 18, + }, + }, + }, + { // name + Type: lang.TokenBlockLabel, + Modifiers: []lang.SemanticTokenModifier{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 2, + Column: 19, + Byte: 19, + }, + End: hcl.Pos{ + Line: 2, + Column: 25, + Byte: 25, + }, + }, + }, + { // count + Type: lang.TokenAttrName, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 3, + Column: 2, + Byte: 29, + }, + End: hcl.Pos{ + Line: 3, + Column: 7, + Byte: 34, + }, + }, + }, + { // 1 number + Type: lang.TokenNumber, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 3, + Column: 10, + Byte: 37, + }, + End: hcl.Pos{ + Line: 3, + Column: 11, + Byte: 38, + }, + }, + }, + { // block + Type: lang.TokenBlockType, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 4, + Column: 2, + Byte: 40, + }, + End: hcl.Pos{ + Line: 4, + Column: 7, + Byte: 45, + }, + }, + }, + { // attr + Type: lang.TokenAttrName, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 5, + Column: 3, + Byte: 50, + }, + End: hcl.Pos{ + Line: 5, + Column: 7, + Byte: 54, + }, + }, + }, + { // count + Type: lang.TokenTraversalStep, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 5, + Column: 10, + Byte: 57, + }, + End: hcl.Pos{ + Line: 5, + Column: 15, + Byte: 62, + }, + }, + }, + { // index + Type: lang.TokenTraversalStep, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 5, + Column: 16, + Byte: 63, + }, + End: hcl.Pos{ + Line: 5, + Column: 22, + Byte: 69, + }, + }, + }, + } + + diff := cmp.Diff(expectedTokens, tokens) + if diff != "" { + t.Fatalf("unexpected tokens: %s", diff) + } +} From 2aced951cf0d9b151314a583960d7196f7ba04bc Mon Sep 17 00:00:00 2001 From: James Pogran Date: Thu, 13 Oct 2022 13:32:56 -0400 Subject: [PATCH 23/23] fix missing space --- decoder/candidates_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decoder/candidates_test.go b/decoder/candidates_test.go index fc7b3beb..d4397b32 100644 --- a/decoder/candidates_test.go +++ b/decoder/candidates_test.go @@ -459,7 +459,7 @@ func TestDecoder_CandidatesAtPos_rightHandSide(t *testing.T) { }, } testConfig := []byte(`myblock "foo" { - num_attr = + num_attr = } `)