1818f36094
The hclsyntax package permits block labels to be blank quoted strings, and defers to the application to determine if this is valid or not. This commit updates hclwrite to allow the same behaviour. This is in response to an upstream bug report on Terraform, which uses hclwrite to implement its fmt subcommand. Given a block with a blank quoted string label, it currently deletes the label when formatting. This is inconsistent with Terraform's behaviour when parsing HCL, which treats empty labels differently from missing labels.
487 lines
9.5 KiB
Go
487 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"},
|
|
},
|
|
{
|
|
`
|
|
blank "" {
|
|
}
|
|
`,
|
|
[]string{""},
|
|
},
|
|
}
|
|
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|