7d8f0ff870
All of the other subdivisions of a block were already nodes, but we'd represented the labels as an undifferentiated set of nodes belonging directly to the block's child node list. Now that we support replacing the labels in the public API, that's a good excuse to refactor this slightly to make the labels their own node. As well as being consistent with everything else in Block, this also makes it easier to implement the Block.SetLabels operation because we can just change the children of the labels node, rather than having to carefully identify and extract the individual child nodes of the block that happen to represent labels. Internally this models the labels in a similar sort of way as the content of a body, although we've kept the public API directly on the Block type here because that's a more straightforward model for the use-cases we currently know and matches better with the API of hcl.Block. This is just an internal change for consistency. I also added a few tests for having comments interspersed with labels while I was here, because that helped to better exercise the new parseBlockLabels function.
480 lines
9.5 KiB
Go
480 lines
9.5 KiB
Go
package hclwrite
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
|
)
|
|
|
|
func TestBlockType(t *testing.T) {
|
|
tests := []struct {
|
|
src string
|
|
want string
|
|
}{
|
|
{
|
|
`
|
|
service {
|
|
attr0 = "val0"
|
|
}
|
|
`,
|
|
"service",
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(fmt.Sprintf("%s", test.want), func(t *testing.T) {
|
|
f, diags := ParseConfig([]byte(test.src), "", hcl.Pos{Line: 1, Column: 1})
|
|
if len(diags) != 0 {
|
|
for _, diag := range diags {
|
|
t.Logf("- %s", diag.Error())
|
|
}
|
|
t.Fatalf("unexpected diagnostics")
|
|
}
|
|
|
|
block := f.Body().Blocks()[0]
|
|
got := string(block.Type())
|
|
if got != test.want {
|
|
t.Errorf("wrong result\ngot: %s\nwant: %s", got, test.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBlockLabels(t *testing.T) {
|
|
tests := []struct {
|
|
src string
|
|
want []string
|
|
}{
|
|
{
|
|
`
|
|
nolabel {
|
|
}
|
|
`,
|
|
[]string{},
|
|
},
|
|
{
|
|
`
|
|
quoted "label1" {
|
|
}
|
|
`,
|
|
[]string{"label1"},
|
|
},
|
|
{
|
|
`
|
|
quoted "label1" "label2" {
|
|
}
|
|
`,
|
|
[]string{"label1", "label2"},
|
|
},
|
|
{
|
|
`
|
|
quoted "label1" /* foo */ "label2" {
|
|
}
|
|
`,
|
|
[]string{"label1", "label2"},
|
|
},
|
|
{
|
|
`
|
|
unquoted label1 {
|
|
}
|
|
`,
|
|
[]string{"label1"},
|
|
},
|
|
{
|
|
`
|
|
unquoted label1 /* foo */ label2 {
|
|
}
|
|
`,
|
|
[]string{"label1", "label2"},
|
|
},
|
|
{
|
|
`
|
|
mixed label1 "label2" {
|
|
}
|
|
`,
|
|
[]string{"label1", "label2"},
|
|
},
|
|
{
|
|
`
|
|
escape "\u0041" {
|
|
}
|
|
`,
|
|
[]string{"\u0041"},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(fmt.Sprintf("%s", strings.Join(test.want, " ")), func(t *testing.T) {
|
|
f, diags := ParseConfig([]byte(test.src), "", hcl.Pos{Line: 1, Column: 1})
|
|
if len(diags) != 0 {
|
|
for _, diag := range diags {
|
|
t.Logf("- %s", diag.Error())
|
|
}
|
|
t.Fatalf("unexpected diagnostics")
|
|
}
|
|
|
|
block := f.Body().Blocks()[0]
|
|
got := block.Labels()
|
|
if !reflect.DeepEqual(got, test.want) {
|
|
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBlockSetType(t *testing.T) {
|
|
tests := []struct {
|
|
src string
|
|
oldTypeName string
|
|
newTypeName string
|
|
labels []string
|
|
want Tokens
|
|
}{
|
|
{
|
|
"foo {}",
|
|
"foo",
|
|
"bar",
|
|
nil,
|
|
Tokens{
|
|
{
|
|
Type: hclsyntax.TokenIdent,
|
|
Bytes: []byte(`bar`),
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenOBrace,
|
|
Bytes: []byte{'{'},
|
|
SpacesBefore: 1,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenCBrace,
|
|
Bytes: []byte{'}'},
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenEOF,
|
|
Bytes: []byte{},
|
|
SpacesBefore: 0,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(fmt.Sprintf("%s %s %s in %s", test.oldTypeName, test.newTypeName, test.labels, test.src), func(t *testing.T) {
|
|
f, diags := ParseConfig([]byte(test.src), "", hcl.Pos{Line: 1, Column: 1})
|
|
if len(diags) != 0 {
|
|
for _, diag := range diags {
|
|
t.Logf("- %s", diag.Error())
|
|
}
|
|
t.Fatalf("unexpected diagnostics")
|
|
}
|
|
|
|
b := f.Body().FirstMatchingBlock(test.oldTypeName, test.labels)
|
|
b.SetType(test.newTypeName)
|
|
got := f.BuildTokens(nil)
|
|
format(got)
|
|
if !reflect.DeepEqual(got, test.want) {
|
|
diff := cmp.Diff(test.want, got)
|
|
t.Errorf("wrong result\ngot: %s\nwant: %s\ndiff:\n%s", spew.Sdump(got), spew.Sdump(test.want), diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBlockSetLabels(t *testing.T) {
|
|
tests := []struct {
|
|
src string
|
|
typeName string
|
|
oldLabels []string
|
|
newLabels []string
|
|
want Tokens
|
|
}{
|
|
{
|
|
`foo "hoge" {}`,
|
|
"foo",
|
|
[]string{"hoge"},
|
|
[]string{"fuga"}, // update first label
|
|
Tokens{
|
|
{
|
|
Type: hclsyntax.TokenIdent,
|
|
Bytes: []byte(`foo`),
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenOQuote,
|
|
Bytes: []byte(`"`),
|
|
SpacesBefore: 1,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenQuotedLit,
|
|
Bytes: []byte(`fuga`),
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenCQuote,
|
|
Bytes: []byte(`"`),
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenOBrace,
|
|
Bytes: []byte{'{'},
|
|
SpacesBefore: 1,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenCBrace,
|
|
Bytes: []byte{'}'},
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenEOF,
|
|
Bytes: []byte{},
|
|
SpacesBefore: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
`foo "hoge" "fuga" {}`,
|
|
"foo",
|
|
[]string{"hoge", "fuga"},
|
|
[]string{"hoge", "piyo"}, // update second label
|
|
Tokens{
|
|
{
|
|
Type: hclsyntax.TokenIdent,
|
|
Bytes: []byte(`foo`),
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenOQuote,
|
|
Bytes: []byte(`"`),
|
|
SpacesBefore: 1,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenQuotedLit,
|
|
Bytes: []byte(`hoge`),
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenCQuote,
|
|
Bytes: []byte(`"`),
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenOQuote,
|
|
Bytes: []byte(`"`),
|
|
SpacesBefore: 1,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenQuotedLit,
|
|
Bytes: []byte(`piyo`),
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenCQuote,
|
|
Bytes: []byte(`"`),
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenOBrace,
|
|
Bytes: []byte{'{'},
|
|
SpacesBefore: 1,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenCBrace,
|
|
Bytes: []byte{'}'},
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenEOF,
|
|
Bytes: []byte{},
|
|
SpacesBefore: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
`foo {}`,
|
|
"foo",
|
|
[]string{},
|
|
[]string{"fuga"}, // insert a new label to empty list
|
|
Tokens{
|
|
{
|
|
Type: hclsyntax.TokenIdent,
|
|
Bytes: []byte(`foo`),
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenOQuote,
|
|
Bytes: []byte(`"`),
|
|
SpacesBefore: 1,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenQuotedLit,
|
|
Bytes: []byte(`fuga`),
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenCQuote,
|
|
Bytes: []byte(`"`),
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenOBrace,
|
|
Bytes: []byte{'{'},
|
|
SpacesBefore: 1,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenCBrace,
|
|
Bytes: []byte{'}'},
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenEOF,
|
|
Bytes: []byte{},
|
|
SpacesBefore: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
`foo "hoge" {}`,
|
|
"foo",
|
|
[]string{"hoge"},
|
|
[]string{}, // remove all labels
|
|
Tokens{
|
|
{
|
|
Type: hclsyntax.TokenIdent,
|
|
Bytes: []byte(`foo`),
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenOBrace,
|
|
Bytes: []byte{'{'},
|
|
SpacesBefore: 1,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenCBrace,
|
|
Bytes: []byte{'}'},
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenEOF,
|
|
Bytes: []byte{},
|
|
SpacesBefore: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
`foo "hoge" /* fuga */ "piyo" {}`,
|
|
"foo",
|
|
[]string{"hoge", "piyo"},
|
|
[]string{"fuga"}, // force quoted form even if the old one is unquoted.
|
|
Tokens{
|
|
{
|
|
Type: hclsyntax.TokenIdent,
|
|
Bytes: []byte(`foo`),
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenOQuote,
|
|
Bytes: []byte(`"`),
|
|
SpacesBefore: 1,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenQuotedLit,
|
|
Bytes: []byte(`fuga`),
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenCQuote,
|
|
Bytes: []byte(`"`),
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenOBrace,
|
|
Bytes: []byte{'{'},
|
|
SpacesBefore: 1,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenCBrace,
|
|
Bytes: []byte{'}'},
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenEOF,
|
|
Bytes: []byte{},
|
|
SpacesBefore: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
`foo "hoge" /* foo */ "" {}`,
|
|
"foo",
|
|
[]string{"hoge"},
|
|
[]string{"fuga"}, // force quoted form even if the old one is unquoted.
|
|
Tokens{
|
|
{
|
|
Type: hclsyntax.TokenIdent,
|
|
Bytes: []byte(`foo`),
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenOQuote,
|
|
Bytes: []byte(`"`),
|
|
SpacesBefore: 1,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenQuotedLit,
|
|
Bytes: []byte(`fuga`),
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenCQuote,
|
|
Bytes: []byte(`"`),
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenOBrace,
|
|
Bytes: []byte{'{'},
|
|
SpacesBefore: 1,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenCBrace,
|
|
Bytes: []byte{'}'},
|
|
SpacesBefore: 0,
|
|
},
|
|
{
|
|
Type: hclsyntax.TokenEOF,
|
|
Bytes: []byte{},
|
|
SpacesBefore: 0,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(fmt.Sprintf("%s %s %s in %s", test.typeName, test.oldLabels, test.newLabels, test.src), func(t *testing.T) {
|
|
f, diags := ParseConfig([]byte(test.src), "", hcl.Pos{Line: 1, Column: 1})
|
|
if len(diags) != 0 {
|
|
for _, diag := range diags {
|
|
t.Logf("- %s", diag.Error())
|
|
}
|
|
t.Fatalf("unexpected diagnostics")
|
|
}
|
|
|
|
b := f.Body().FirstMatchingBlock(test.typeName, test.oldLabels)
|
|
b.SetLabels(test.newLabels)
|
|
got := f.BuildTokens(nil)
|
|
format(got)
|
|
if !reflect.DeepEqual(got, test.want) {
|
|
diff := cmp.Diff(test.want, got)
|
|
t.Errorf("wrong result\ngot: %s\nwant: %s\ndiff:\n%s", spew.Sdump(got), spew.Sdump(test.want), diff)
|
|
}
|
|
})
|
|
}
|
|
}
|