Skip to content

Commit

Permalink
fix: support of deeper nesting of dynamic block (#167)
Browse files Browse the repository at this point in the history
* decoder: Add failing tests for deeper dynamic in completion+hover+semtok

* decoder: recognise deeper nesting of dynamic block
  • Loading branch information
radeksimko authored Nov 30, 2022
1 parent 2b1ae40 commit 9a3d8b3
Show file tree
Hide file tree
Showing 4 changed files with 305 additions and 1 deletion.
93 changes: 92 additions & 1 deletion decoder/body_extensions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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 {
Expand Down
17 changes: 17 additions & 0 deletions decoder/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}

Expand Down
71 changes: 71 additions & 0 deletions decoder/hover_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
125 changes: 125 additions & 0 deletions decoder/semantic_tokens_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 9a3d8b3

Please sign in to comment.