From 90cb64b09effbbc880035ae22bf3a31ed32d723d Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Wed, 30 Nov 2022 11:11:11 +0000 Subject: [PATCH 1/2] decoder: Add failing tests for deeper dynamic in completion+hover+semtok --- decoder/body_extensions_test.go | 93 +++++++++++++++++++++++- decoder/hover_test.go | 71 ++++++++++++++++++ decoder/semantic_tokens_test.go | 125 ++++++++++++++++++++++++++++++++ 3 files changed, 288 insertions(+), 1 deletion(-) diff --git a/decoder/body_extensions_test.go b/decoder/body_extensions_test.go index d3f2add6..6fcfd438 100644 --- a/decoder/body_extensions_test.go +++ b/decoder/body_extensions_test.go @@ -2553,7 +2553,6 @@ resource "aws_elastic_beanstalk_environment" "example" { }), "", }, - // never complete dynamic as a dynamic label { "never complete dynamic as a dynamic label", &schema.BodySchema{ @@ -2620,6 +2619,98 @@ resource "aws_elastic_beanstalk_environment" "example" { }), "", }, + { + "deeper nesting support", + &schema.BodySchema{ + Blocks: map[string]*schema.BlockSchema{ + "resource": { + Labels: []*schema.LabelSchema{ + { + Name: "type", + IsDepKey: true, + Completable: true, + }, + {Name: "name"}, + }, + Body: &schema.BodySchema{ + Extensions: &schema.BodyExtensions{ + DynamicBlocks: true, + }, + Blocks: make(map[string]*schema.BlockSchema, 0), + }, + DependentBody: map[schema.SchemaKey]*schema.BodySchema{ + schema.NewSchemaKey(schema.DependencyKeys{ + Labels: []schema.LabelDependent{ + {Index: 0, Value: "aws_instance"}, + }, + }): { + Blocks: map[string]*schema.BlockSchema{ + "foo": { + Body: &schema.BodySchema{ + Blocks: map[string]*schema.BlockSchema{ + "bar": { + Body: &schema.BodySchema{ + Blocks: map[string]*schema.BlockSchema{ + "baz": { + Body: schema.NewBodySchema(), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + `resource "aws_instance" "example" { + foo { + bar { + + } + } +}`, + hcl.Pos{Line: 4, Column: 7, Byte: 60}, + lang.CompleteCandidates([]lang.Candidate{ + { + Label: "baz", + Detail: "Block", + Kind: lang.BlockCandidateKind, + TextEdit: lang.TextEdit{ + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 4, Column: 7, Byte: 60}, + End: hcl.Pos{Line: 4, Column: 7, Byte: 60}, + }, + NewText: "baz", + Snippet: "baz {\n ${1}\n}", + }, + }, + { + Label: "dynamic", + Description: lang.MarkupContent{ + Value: "A dynamic block to produce blocks dynamically by iterating over a given complex value", + Kind: lang.MarkdownKind, + }, + Detail: "Block, map", + Kind: lang.BlockCandidateKind, + TriggerSuggest: true, + TextEdit: lang.TextEdit{ + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 4, Column: 7, Byte: 60}, + End: hcl.Pos{Line: 4, Column: 7, Byte: 60}, + }, + NewText: "dynamic", + Snippet: "dynamic \"${1}\" {\n ${2}\n}", + }, + }, + }), + "", + }, } for i, tc := range testCases { diff --git a/decoder/hover_test.go b/decoder/hover_test.go index ea1b2acb..6eef727d 100644 --- a/decoder/hover_test.go +++ b/decoder/hover_test.go @@ -1512,6 +1512,77 @@ func TestDecoder_HoverAtPos_extensions_dynamic(t *testing.T) { }, }, }, + + { + "deeper nesting support", + &schema.BodySchema{ + Blocks: map[string]*schema.BlockSchema{ + "resource": { + Labels: []*schema.LabelSchema{ + { + Name: "type", + IsDepKey: true, + Completable: true, + }, + {Name: "name"}, + }, + Body: &schema.BodySchema{ + Extensions: &schema.BodyExtensions{ + DynamicBlocks: true, + }, + Blocks: make(map[string]*schema.BlockSchema, 0), + }, + DependentBody: map[schema.SchemaKey]*schema.BodySchema{ + schema.NewSchemaKey(schema.DependencyKeys{ + Labels: []schema.LabelDependent{ + {Index: 0, Value: "aws_instance"}, + }, + }): { + Blocks: map[string]*schema.BlockSchema{ + "foo": { + Body: &schema.BodySchema{ + Blocks: map[string]*schema.BlockSchema{ + "bar": { + Body: &schema.BodySchema{ + Blocks: map[string]*schema.BlockSchema{ + "baz": { + Body: schema.NewBodySchema(), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + `resource "aws_instance" "example" { + foo { + bar { + dynamic "baz" { + + } + } + } +}`, + hcl.Pos{Line: 4, Column: 10, Byte: 63}, + &lang.HoverData{ + Content: lang.MarkupContent{ + Value: "**dynamic** _Block, map_\n\n" + + "A dynamic block to produce blocks dynamically by iterating over a given complex value", + Kind: lang.MarkdownKind, + }, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 4, Column: 7, Byte: 60}, + End: hcl.Pos{Line: 4, Column: 14, Byte: 67}, + }, + }, + }, } for i, tc := range testCases { diff --git a/decoder/semantic_tokens_test.go b/decoder/semantic_tokens_test.go index d21183f5..b4183c77 100644 --- a/decoder/semantic_tokens_test.go +++ b/decoder/semantic_tokens_test.go @@ -2156,6 +2156,131 @@ func TestDecoder_SemanticTokensInFile_extensions_dynamic(t *testing.T) { }, }, }, + { + "deeper nested dynamic blocks", + &schema.BodySchema{ + Blocks: map[string]*schema.BlockSchema{ + "myblock": { + Labels: []*schema.LabelSchema{ + { + Name: "type", + IsDepKey: true, + Completable: true, + SemanticTokenModifiers: lang.SemanticTokenModifiers{lang.TokenModifierDependent}, + }, + {Name: "name"}, + }, + Body: &schema.BodySchema{ + Extensions: &schema.BodyExtensions{ + DynamicBlocks: true, + }, + Blocks: make(map[string]*schema.BlockSchema, 0), + }, + DependentBody: map[schema.SchemaKey]*schema.BodySchema{ + schema.NewSchemaKey(schema.DependencyKeys{ + Labels: []schema.LabelDependent{ + {Index: 0, Value: "foo"}, + }, + }): { + Blocks: map[string]*schema.BlockSchema{ + "setting": { + Body: &schema.BodySchema{ + Blocks: map[string]*schema.BlockSchema{ + "foo": { + Body: &schema.BodySchema{ + Blocks: map[string]*schema.BlockSchema{ + "bar": { + Body: schema.NewBodySchema(), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + `myblock "foo" "bar" { + setting { + foo { + dynamic "bar" { + + } + } + } +}`, + []lang.SemanticToken{ + { // myblock + Type: lang.TokenBlockType, + Modifiers: []lang.SemanticTokenModifier{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + }, + }, + { // "foo" + Type: lang.TokenBlockLabel, + Modifiers: []lang.SemanticTokenModifier{ + lang.TokenModifierDependent, + }, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 9, Byte: 8}, + End: hcl.Pos{Line: 1, Column: 14, Byte: 13}, + }, + }, + { // "bar" + Type: lang.TokenBlockLabel, + Modifiers: []lang.SemanticTokenModifier{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 1, Column: 20, Byte: 19}, + }, + }, + { // setting + Type: lang.TokenBlockType, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 3, Byte: 24}, + End: hcl.Pos{Line: 2, Column: 10, Byte: 31}, + }, + }, + { // foo + Type: lang.TokenBlockType, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 3, Column: 5, Byte: 38}, + End: hcl.Pos{Line: 3, Column: 8, Byte: 41}, + }, + }, + { // dynamic + Type: lang.TokenBlockType, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 4, Column: 7, Byte: 50}, + End: hcl.Pos{Line: 4, Column: 14, Byte: 57}, + }, + }, + { // "bar" + Type: lang.TokenBlockLabel, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 4, Column: 15, Byte: 58}, + End: hcl.Pos{Line: 4, Column: 20, Byte: 63}, + }, + }, + }, + }, } for i, tc := range testCases { From 5e12299be2128b02e2e475563bb55dbd24d7ffe1 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Wed, 30 Nov 2022 11:58:27 +0000 Subject: [PATCH 2/2] decoder: recognise deeper nesting of dynamic block --- decoder/decoder.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/decoder/decoder.go b/decoder/decoder.go index 837bbe18..6b05f997 100644 --- a/decoder/decoder.go +++ b/decoder/decoder.go @@ -62,12 +62,14 @@ func mergeBlockBodySchemas(block *hcl.Block, blockSchema *schema.BlockSchema) (* } for bType, block := range depSchema.Blocks { if _, exists := mergedSchema.Blocks[bType]; !exists { + // propagate DynamicBlocks extension to any nested blocks if mergedSchema.Extensions != nil && mergedSchema.Extensions.DynamicBlocks { if block.Body.Extensions == nil { block.Body.Extensions = &schema.BodyExtensions{} } block.Body.Extensions.DynamicBlocks = true } + mergedSchema.Blocks[bType] = block } else { // Skip duplicate block type @@ -94,6 +96,21 @@ func mergeBlockBodySchemas(block *hcl.Block, blockSchema *schema.BlockSchema) (* mergedSchema.Extensions = depSchema.Extensions.Copy() } } else if !ok && mergedSchema.Extensions != nil && mergedSchema.Extensions.DynamicBlocks && len(mergedSchema.Blocks) > 0 { + // dynamic blocks are only relevant for dependent schemas, + // but we may end up here because the schema is a result + // of merged static + dependent schema from previous iteration + + // propagate DynamicBlocks extension to any nested blocks + if mergedSchema.Extensions != nil && mergedSchema.Extensions.DynamicBlocks { + for bType, block := range mergedSchema.Blocks { + if block.Body.Extensions == nil { + block.Body.Extensions = &schema.BodyExtensions{} + } + block.Body.Extensions.DynamicBlocks = true + mergedSchema.Blocks[bType] = block + } + } + mergedSchema.Blocks["dynamic"] = buildDynamicBlockSchema(mergedSchema) }