Merge pull request #19453 from hashicorp/b-whole-body-diag-rendering
command/format: Fix rendering of attribute-agnostic diagnostics
This commit is contained in:
commit
9e5677adeb
|
@ -58,23 +58,11 @@ func Diagnostic(diag tfdiags.Diagnostic, sources map[string][]byte, color *color
|
||||||
if sourceRefs.Context != nil {
|
if sourceRefs.Context != nil {
|
||||||
snippetRange = sourceRefs.Context.ToHCL()
|
snippetRange = sourceRefs.Context.ToHCL()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the snippet includes the highlight. This should be true
|
// Make sure the snippet includes the highlight. This should be true
|
||||||
// for any reasonable diagnostic, but we'll make sure.
|
// for any reasonable diagnostic, but we'll make sure.
|
||||||
snippetRange = hcl.RangeOver(snippetRange, highlightRange)
|
snippetRange = hcl.RangeOver(snippetRange, highlightRange)
|
||||||
|
|
||||||
// We can't illustrate an empty range, so we'll turn such ranges into
|
|
||||||
// single-character ranges, which might not be totally valid (may point
|
|
||||||
// off the end of a line, or off the end of the file) but are good
|
|
||||||
// enough for the bounds checks we do below.
|
|
||||||
if snippetRange.Empty() {
|
|
||||||
snippetRange.End.Byte++
|
|
||||||
snippetRange.End.Column++
|
|
||||||
}
|
|
||||||
if highlightRange.Empty() {
|
|
||||||
highlightRange.End.Byte++
|
|
||||||
highlightRange.End.Column++
|
|
||||||
}
|
|
||||||
|
|
||||||
var src []byte
|
var src []byte
|
||||||
if sources != nil {
|
if sources != nil {
|
||||||
src = sources[snippetRange.Filename]
|
src = sources[snippetRange.Filename]
|
||||||
|
@ -86,12 +74,25 @@ func Diagnostic(diag tfdiags.Diagnostic, sources map[string][]byte, color *color
|
||||||
// a not-so-helpful error message.
|
// a not-so-helpful error message.
|
||||||
fmt.Fprintf(&buf, " on %s line %d:\n (source code not available)\n", highlightRange.Filename, highlightRange.Start.Line)
|
fmt.Fprintf(&buf, " on %s line %d:\n (source code not available)\n", highlightRange.Filename, highlightRange.Start.Line)
|
||||||
} else {
|
} else {
|
||||||
contextStr := sourceCodeContextStr(src, highlightRange)
|
file, offset := parseRange(src, highlightRange)
|
||||||
|
|
||||||
|
headerRange := highlightRange
|
||||||
|
if snippetRange.Empty() {
|
||||||
|
// We assume that empty range signals diagnostic
|
||||||
|
// related to the whole body, so we lookup the definition
|
||||||
|
// instead of attempting to render empty range
|
||||||
|
snippetRange = hcled.ContextDefRange(file, offset-1)
|
||||||
|
headerRange = snippetRange
|
||||||
|
}
|
||||||
|
|
||||||
|
contextStr := hcled.ContextString(file, offset-1)
|
||||||
if contextStr != "" {
|
if contextStr != "" {
|
||||||
contextStr = ", in " + contextStr
|
contextStr = ", in " + contextStr
|
||||||
}
|
}
|
||||||
fmt.Fprintf(&buf, " on %s line %d%s:\n", highlightRange.Filename, highlightRange.Start.Line, contextStr)
|
|
||||||
|
|
||||||
|
fmt.Fprintf(&buf, " on %s line %d%s:\n", headerRange.Filename, headerRange.Start.Line, contextStr)
|
||||||
|
|
||||||
|
// Config snippet rendering
|
||||||
sc := hcl.NewRangeScanner(src, highlightRange.Filename, bufio.ScanLines)
|
sc := hcl.NewRangeScanner(src, highlightRange.Filename, bufio.ScanLines)
|
||||||
for sc.Scan() {
|
for sc.Scan() {
|
||||||
lineRange := sc.Range()
|
lineRange := sc.Range()
|
||||||
|
@ -185,7 +186,7 @@ func Diagnostic(diag tfdiags.Diagnostic, sources map[string][]byte, color *color
|
||||||
// An empty string is returned if no suitable description is available, e.g.
|
// An empty string is returned if no suitable description is available, e.g.
|
||||||
// because the source is invalid, or because the offset is not inside any sort
|
// because the source is invalid, or because the offset is not inside any sort
|
||||||
// of identifiable container.
|
// of identifiable container.
|
||||||
func sourceCodeContextStr(src []byte, rng hcl.Range) string {
|
func parseRange(src []byte, rng hcl.Range) (*hcl.File, int) {
|
||||||
filename := rng.Filename
|
filename := rng.Filename
|
||||||
offset := rng.Start.Byte
|
offset := rng.Start.Byte
|
||||||
|
|
||||||
|
@ -203,10 +204,10 @@ func sourceCodeContextStr(src []byte, rng hcl.Range) string {
|
||||||
file, diags = parser.ParseHCL(src, filename)
|
file, diags = parser.ParseHCL(src, filename)
|
||||||
}
|
}
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
return ""
|
return file, offset
|
||||||
}
|
}
|
||||||
|
|
||||||
return hcled.ContextString(file, offset)
|
return file, offset
|
||||||
}
|
}
|
||||||
|
|
||||||
// traversalStr produces a representation of an HCL traversal that is compact,
|
// traversalStr produces a representation of an HCL traversal that is compact,
|
||||||
|
|
|
@ -148,11 +148,11 @@ func TestOutputWithoutValueShouldFail(t *testing.T) {
|
||||||
if code != 1 {
|
if code != 1 {
|
||||||
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
|
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||||
}
|
}
|
||||||
wantError := `The attribute "value" is required, but no definition was found.`
|
wantError := `The argument "value" is required, but no definition was found.`
|
||||||
if !strings.Contains(ui.ErrorWriter.String(), wantError) {
|
if !strings.Contains(ui.ErrorWriter.String(), wantError) {
|
||||||
t.Fatalf("Missing error string %q\n\n'%s'", wantError, ui.ErrorWriter.String())
|
t.Fatalf("Missing error string %q\n\n'%s'", wantError, ui.ErrorWriter.String())
|
||||||
}
|
}
|
||||||
wantError = `An attribute named "values" is not expected here. Did you mean "value"?`
|
wantError = `An argument named "values" is not expected here. Did you mean "value"?`
|
||||||
if !strings.Contains(ui.ErrorWriter.String(), wantError) {
|
if !strings.Contains(ui.ErrorWriter.String(), wantError) {
|
||||||
t.Fatalf("Missing error string %q\n\n'%s'", wantError, ui.ErrorWriter.String())
|
t.Fatalf("Missing error string %q\n\n'%s'", wantError, ui.ErrorWriter.String())
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,7 +102,7 @@ func TestParserLoadConfigFileFailureMessages(t *testing.T) {
|
||||||
{
|
{
|
||||||
"invalid-files/unexpected-attr.tf",
|
"invalid-files/unexpected-attr.tf",
|
||||||
hcl.DiagError,
|
hcl.DiagError,
|
||||||
"Unsupported attribute",
|
"Unsupported argument",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"invalid-files/unexpected-block.tf",
|
"invalid-files/unexpected-block.tf",
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -73,7 +73,7 @@ require (
|
||||||
github.com/hashicorp/go-version v0.0.0-20180322230233-23480c066577
|
github.com/hashicorp/go-version v0.0.0-20180322230233-23480c066577
|
||||||
github.com/hashicorp/golang-lru v0.5.0 // indirect
|
github.com/hashicorp/golang-lru v0.5.0 // indirect
|
||||||
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f
|
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f
|
||||||
github.com/hashicorp/hcl2 v0.0.0-20180925175540-3f1c5474d4f7
|
github.com/hashicorp/hcl2 v0.0.0-20181126233546-67424e43b184
|
||||||
github.com/hashicorp/hil v0.0.0-20170627220502-fa9f258a9250
|
github.com/hashicorp/hil v0.0.0-20170627220502-fa9f258a9250
|
||||||
github.com/hashicorp/logutils v0.0.0-20150609070431-0dc08b1671f3
|
github.com/hashicorp/logutils v0.0.0-20150609070431-0dc08b1671f3
|
||||||
github.com/hashicorp/memberlist v0.1.0 // indirect
|
github.com/hashicorp/memberlist v0.1.0 // indirect
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -50,6 +50,8 @@ github.com/blang/semver v0.0.0-20170202183821-4a1e882c79dc h1:J/iAaGTCZYfT/allw6
|
||||||
github.com/blang/semver v0.0.0-20170202183821-4a1e882c79dc/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
github.com/blang/semver v0.0.0-20170202183821-4a1e882c79dc/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||||
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
||||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||||
|
github.com/bsm/go-vlq v0.0.0-20150828105119-ec6e8d4f5f4e h1:D64GF/Xr5zSUnM3q1Jylzo4sK7szhP/ON+nb2DB5XJA=
|
||||||
|
github.com/bsm/go-vlq v0.0.0-20150828105119-ec6e8d4f5f4e/go.mod h1:N+BjUcTjSxc2mtRGSCPsat1kze3CUtvJN3/jTXlp29k=
|
||||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20161106042343-c914be64f07d h1:aG5FcWiZTOhPQzYIxwxSR1zEOxzL32fwr1CsaCfhO6w=
|
github.com/chzyer/readline v0.0.0-20161106042343-c914be64f07d h1:aG5FcWiZTOhPQzYIxwxSR1zEOxzL32fwr1CsaCfhO6w=
|
||||||
|
@ -166,6 +168,10 @@ github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f h1:UdxlrJz4JOnY8W+Db
|
||||||
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
||||||
github.com/hashicorp/hcl2 v0.0.0-20180925175540-3f1c5474d4f7 h1:lNBI8nmNlIpLl8Lqf+9JLBmNGrU91Nlt71q7cGpZi+k=
|
github.com/hashicorp/hcl2 v0.0.0-20180925175540-3f1c5474d4f7 h1:lNBI8nmNlIpLl8Lqf+9JLBmNGrU91Nlt71q7cGpZi+k=
|
||||||
github.com/hashicorp/hcl2 v0.0.0-20180925175540-3f1c5474d4f7/go.mod h1:hrRmgPTdXWuNLpHcv7cRXL+ofoFsY76vDylTzH8eu3E=
|
github.com/hashicorp/hcl2 v0.0.0-20180925175540-3f1c5474d4f7/go.mod h1:hrRmgPTdXWuNLpHcv7cRXL+ofoFsY76vDylTzH8eu3E=
|
||||||
|
github.com/hashicorp/hcl2 v0.0.0-20181123160133-f901b36da204 h1:/rUVBMdrcpWOwNw5KFhqrbMVnbgsN1zJOp5sAEx2XXQ=
|
||||||
|
github.com/hashicorp/hcl2 v0.0.0-20181123160133-f901b36da204/go.mod h1:4nBvwJRETsbpa0LQ7FbXXVFmo0Crvhya1Dmpbm7cVow=
|
||||||
|
github.com/hashicorp/hcl2 v0.0.0-20181126233546-67424e43b184 h1:cBDzEsx+Tm9vHp9WiUeBlhPazwbbKJxOiRAT+pVLuag=
|
||||||
|
github.com/hashicorp/hcl2 v0.0.0-20181126233546-67424e43b184/go.mod h1:4nBvwJRETsbpa0LQ7FbXXVFmo0Crvhya1Dmpbm7cVow=
|
||||||
github.com/hashicorp/hil v0.0.0-20170627220502-fa9f258a9250 h1:fooK5IvDL/KIsi4LxF/JH68nVdrBSiGNPhS2JAQjtjo=
|
github.com/hashicorp/hil v0.0.0-20170627220502-fa9f258a9250 h1:fooK5IvDL/KIsi4LxF/JH68nVdrBSiGNPhS2JAQjtjo=
|
||||||
github.com/hashicorp/hil v0.0.0-20170627220502-fa9f258a9250/go.mod h1:KHvg/R2/dPtaePb16oW4qIyzkMxXOL38xjRN64adsts=
|
github.com/hashicorp/hil v0.0.0-20170627220502-fa9f258a9250/go.mod h1:KHvg/R2/dPtaePb16oW4qIyzkMxXOL38xjRN64adsts=
|
||||||
github.com/hashicorp/logutils v0.0.0-20150609070431-0dc08b1671f3 h1:oD64EFjELI9RY9yoWlfua58r+etdnoIC871z+rr6lkA=
|
github.com/hashicorp/logutils v0.0.0-20150609070431-0dc08b1671f3 h1:oD64EFjELI9RY9yoWlfua58r+etdnoIC871z+rr6lkA=
|
||||||
|
|
|
@ -40,6 +40,10 @@
|
||||||
// present then any attributes or blocks not matched by another valid tag
|
// present then any attributes or blocks not matched by another valid tag
|
||||||
// will cause an error diagnostic.
|
// will cause an error diagnostic.
|
||||||
//
|
//
|
||||||
|
// Only a subset of this tagging/typing vocabulary is supported for the
|
||||||
|
// "Encode" family of functions. See the EncodeIntoBody docs for full details
|
||||||
|
// on the constraints there.
|
||||||
|
//
|
||||||
// Broadly-speaking this package deals with two types of error. The first is
|
// Broadly-speaking this package deals with two types of error. The first is
|
||||||
// errors in the configuration itself, which are returned as diagnostics
|
// errors in the configuration itself, which are returned as diagnostics
|
||||||
// written with the configuration author as the target audience. The second
|
// written with the configuration author as the target audience. The second
|
||||||
|
|
|
@ -0,0 +1,191 @@
|
||||||
|
package gohcl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl2/hclwrite"
|
||||||
|
"github.com/zclconf/go-cty/cty/gocty"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EncodeIntoBody replaces the contents of the given hclwrite Body with
|
||||||
|
// attributes and blocks derived from the given value, which must be a
|
||||||
|
// struct value or a pointer to a struct value with the struct tags defined
|
||||||
|
// in this package.
|
||||||
|
//
|
||||||
|
// This function can work only with fully-decoded data. It will ignore any
|
||||||
|
// fields tagged as "remain", any fields that decode attributes into either
|
||||||
|
// hcl.Attribute or hcl.Expression values, and any fields that decode blocks
|
||||||
|
// into hcl.Attributes values. This function does not have enough information
|
||||||
|
// to complete the decoding of these types.
|
||||||
|
//
|
||||||
|
// Any fields tagged as "label" are ignored by this function. Use EncodeAsBlock
|
||||||
|
// to produce a whole hclwrite.Block including block labels.
|
||||||
|
//
|
||||||
|
// As long as a suitable value is given to encode and the destination body
|
||||||
|
// is non-nil, this function will always complete. It will panic in case of
|
||||||
|
// any errors in the calling program, such as passing an inappropriate type
|
||||||
|
// or a nil body.
|
||||||
|
//
|
||||||
|
// The layout of the resulting HCL source is derived from the ordering of
|
||||||
|
// the struct fields, with blank lines around nested blocks of different types.
|
||||||
|
// Fields representing attributes should usually precede those representing
|
||||||
|
// blocks so that the attributes can group togather in the result. For more
|
||||||
|
// control, use the hclwrite API directly.
|
||||||
|
func EncodeIntoBody(val interface{}, dst *hclwrite.Body) {
|
||||||
|
rv := reflect.ValueOf(val)
|
||||||
|
ty := rv.Type()
|
||||||
|
if ty.Kind() == reflect.Ptr {
|
||||||
|
rv = rv.Elem()
|
||||||
|
ty = rv.Type()
|
||||||
|
}
|
||||||
|
if ty.Kind() != reflect.Struct {
|
||||||
|
panic(fmt.Sprintf("value is %s, not struct", ty.Kind()))
|
||||||
|
}
|
||||||
|
|
||||||
|
tags := getFieldTags(ty)
|
||||||
|
populateBody(rv, ty, tags, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeAsBlock creates a new hclwrite.Block populated with the data from
|
||||||
|
// the given value, which must be a struct or pointer to struct with the
|
||||||
|
// struct tags defined in this package.
|
||||||
|
//
|
||||||
|
// If the given struct type has fields tagged with "label" tags then they
|
||||||
|
// will be used in order to annotate the created block with labels.
|
||||||
|
//
|
||||||
|
// This function has the same constraints as EncodeIntoBody and will panic
|
||||||
|
// if they are violated.
|
||||||
|
func EncodeAsBlock(val interface{}, blockType string) *hclwrite.Block {
|
||||||
|
rv := reflect.ValueOf(val)
|
||||||
|
ty := rv.Type()
|
||||||
|
if ty.Kind() == reflect.Ptr {
|
||||||
|
rv = rv.Elem()
|
||||||
|
ty = rv.Type()
|
||||||
|
}
|
||||||
|
if ty.Kind() != reflect.Struct {
|
||||||
|
panic(fmt.Sprintf("value is %s, not struct", ty.Kind()))
|
||||||
|
}
|
||||||
|
|
||||||
|
tags := getFieldTags(ty)
|
||||||
|
labels := make([]string, len(tags.Labels))
|
||||||
|
for i, lf := range tags.Labels {
|
||||||
|
lv := rv.Field(lf.FieldIndex)
|
||||||
|
// We just stringify whatever we find. It should always be a string
|
||||||
|
// but if not then we'll still do something reasonable.
|
||||||
|
labels[i] = fmt.Sprintf("%s", lv.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
block := hclwrite.NewBlock(blockType, labels)
|
||||||
|
populateBody(rv, ty, tags, block.Body())
|
||||||
|
return block
|
||||||
|
}
|
||||||
|
|
||||||
|
func populateBody(rv reflect.Value, ty reflect.Type, tags *fieldTags, dst *hclwrite.Body) {
|
||||||
|
nameIdxs := make(map[string]int, len(tags.Attributes)+len(tags.Blocks))
|
||||||
|
namesOrder := make([]string, 0, len(tags.Attributes)+len(tags.Blocks))
|
||||||
|
for n, i := range tags.Attributes {
|
||||||
|
nameIdxs[n] = i
|
||||||
|
namesOrder = append(namesOrder, n)
|
||||||
|
}
|
||||||
|
for n, i := range tags.Blocks {
|
||||||
|
nameIdxs[n] = i
|
||||||
|
namesOrder = append(namesOrder, n)
|
||||||
|
}
|
||||||
|
sort.SliceStable(namesOrder, func(i, j int) bool {
|
||||||
|
ni, nj := namesOrder[i], namesOrder[j]
|
||||||
|
return nameIdxs[ni] < nameIdxs[nj]
|
||||||
|
})
|
||||||
|
|
||||||
|
dst.Clear()
|
||||||
|
|
||||||
|
prevWasBlock := false
|
||||||
|
for _, name := range namesOrder {
|
||||||
|
fieldIdx := nameIdxs[name]
|
||||||
|
field := ty.Field(fieldIdx)
|
||||||
|
fieldTy := field.Type
|
||||||
|
fieldVal := rv.Field(fieldIdx)
|
||||||
|
|
||||||
|
if fieldTy.Kind() == reflect.Ptr {
|
||||||
|
fieldTy = fieldTy.Elem()
|
||||||
|
fieldVal = fieldVal.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, isAttr := tags.Attributes[name]; isAttr {
|
||||||
|
|
||||||
|
if exprType.AssignableTo(fieldTy) || attrType.AssignableTo(fieldTy) {
|
||||||
|
continue // ignore undecoded fields
|
||||||
|
}
|
||||||
|
if !fieldVal.IsValid() {
|
||||||
|
continue // ignore (field value is nil pointer)
|
||||||
|
}
|
||||||
|
if fieldTy.Kind() == reflect.Ptr && fieldVal.IsNil() {
|
||||||
|
continue // ignore
|
||||||
|
}
|
||||||
|
if prevWasBlock {
|
||||||
|
dst.AppendNewline()
|
||||||
|
prevWasBlock = false
|
||||||
|
}
|
||||||
|
|
||||||
|
valTy, err := gocty.ImpliedType(fieldVal.Interface())
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("cannot encode %T as HCL expression: %s", fieldVal.Interface(), err))
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := gocty.ToCtyValue(fieldVal.Interface(), valTy)
|
||||||
|
if err != nil {
|
||||||
|
// This should never happen, since we should always be able
|
||||||
|
// to decode into the implied type.
|
||||||
|
panic(fmt.Sprintf("failed to encode %T as %#v: %s", fieldVal.Interface(), valTy, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
dst.SetAttributeValue(name, val)
|
||||||
|
|
||||||
|
} else { // must be a block, then
|
||||||
|
elemTy := fieldTy
|
||||||
|
isSeq := false
|
||||||
|
if elemTy.Kind() == reflect.Slice || elemTy.Kind() == reflect.Array {
|
||||||
|
isSeq = true
|
||||||
|
elemTy = elemTy.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if bodyType.AssignableTo(elemTy) || attrsType.AssignableTo(elemTy) {
|
||||||
|
continue // ignore undecoded fields
|
||||||
|
}
|
||||||
|
prevWasBlock = false
|
||||||
|
|
||||||
|
if isSeq {
|
||||||
|
l := fieldVal.Len()
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
elemVal := fieldVal.Index(i)
|
||||||
|
if !elemVal.IsValid() {
|
||||||
|
continue // ignore (elem value is nil pointer)
|
||||||
|
}
|
||||||
|
if elemTy.Kind() == reflect.Ptr && elemVal.IsNil() {
|
||||||
|
continue // ignore
|
||||||
|
}
|
||||||
|
block := EncodeAsBlock(elemVal.Interface(), name)
|
||||||
|
if !prevWasBlock {
|
||||||
|
dst.AppendNewline()
|
||||||
|
prevWasBlock = true
|
||||||
|
}
|
||||||
|
dst.AppendBlock(block)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !fieldVal.IsValid() {
|
||||||
|
continue // ignore (field value is nil pointer)
|
||||||
|
}
|
||||||
|
if elemTy.Kind() == reflect.Ptr && fieldVal.IsNil() {
|
||||||
|
continue // ignore
|
||||||
|
}
|
||||||
|
block := EncodeAsBlock(fieldVal.Interface(), name)
|
||||||
|
if !prevWasBlock {
|
||||||
|
dst.AppendNewline()
|
||||||
|
prevWasBlock = true
|
||||||
|
}
|
||||||
|
dst.AppendBlock(block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -132,7 +132,7 @@ type RelativeTraversalExpr struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *RelativeTraversalExpr) walkChildNodes(w internalWalkFunc) {
|
func (e *RelativeTraversalExpr) walkChildNodes(w internalWalkFunc) {
|
||||||
e.Source = w(e.Source).(Expression)
|
w(e.Source)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *RelativeTraversalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
func (e *RelativeTraversalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||||
|
@ -181,8 +181,8 @@ type FunctionCallExpr struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *FunctionCallExpr) walkChildNodes(w internalWalkFunc) {
|
func (e *FunctionCallExpr) walkChildNodes(w internalWalkFunc) {
|
||||||
for i, arg := range e.Args {
|
for _, arg := range e.Args {
|
||||||
e.Args[i] = w(arg).(Expression)
|
w(arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -463,9 +463,9 @@ type ConditionalExpr struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ConditionalExpr) walkChildNodes(w internalWalkFunc) {
|
func (e *ConditionalExpr) walkChildNodes(w internalWalkFunc) {
|
||||||
e.Condition = w(e.Condition).(Expression)
|
w(e.Condition)
|
||||||
e.TrueResult = w(e.TrueResult).(Expression)
|
w(e.TrueResult)
|
||||||
e.FalseResult = w(e.FalseResult).(Expression)
|
w(e.FalseResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||||
|
@ -593,8 +593,8 @@ type IndexExpr struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *IndexExpr) walkChildNodes(w internalWalkFunc) {
|
func (e *IndexExpr) walkChildNodes(w internalWalkFunc) {
|
||||||
e.Collection = w(e.Collection).(Expression)
|
w(e.Collection)
|
||||||
e.Key = w(e.Key).(Expression)
|
w(e.Key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *IndexExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
func (e *IndexExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||||
|
@ -625,8 +625,8 @@ type TupleConsExpr struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *TupleConsExpr) walkChildNodes(w internalWalkFunc) {
|
func (e *TupleConsExpr) walkChildNodes(w internalWalkFunc) {
|
||||||
for i, expr := range e.Exprs {
|
for _, expr := range e.Exprs {
|
||||||
e.Exprs[i] = w(expr).(Expression)
|
w(expr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -674,9 +674,9 @@ type ObjectConsItem struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ObjectConsExpr) walkChildNodes(w internalWalkFunc) {
|
func (e *ObjectConsExpr) walkChildNodes(w internalWalkFunc) {
|
||||||
for i, item := range e.Items {
|
for _, item := range e.Items {
|
||||||
e.Items[i].KeyExpr = w(item.KeyExpr).(Expression)
|
w(item.KeyExpr)
|
||||||
e.Items[i].ValueExpr = w(item.ValueExpr).(Expression)
|
w(item.ValueExpr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -792,7 +792,7 @@ func (e *ObjectConsKeyExpr) walkChildNodes(w internalWalkFunc) {
|
||||||
// We only treat our wrapped expression as a real expression if we're
|
// We only treat our wrapped expression as a real expression if we're
|
||||||
// not going to interpret it as a literal.
|
// not going to interpret it as a literal.
|
||||||
if e.literalName() == "" {
|
if e.literalName() == "" {
|
||||||
e.Wrapped = w(e.Wrapped).(Expression)
|
w(e.Wrapped)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1157,7 +1157,7 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ForExpr) walkChildNodes(w internalWalkFunc) {
|
func (e *ForExpr) walkChildNodes(w internalWalkFunc) {
|
||||||
e.CollExpr = w(e.CollExpr).(Expression)
|
w(e.CollExpr)
|
||||||
|
|
||||||
scopeNames := map[string]struct{}{}
|
scopeNames := map[string]struct{}{}
|
||||||
if e.KeyVar != "" {
|
if e.KeyVar != "" {
|
||||||
|
@ -1170,17 +1170,17 @@ func (e *ForExpr) walkChildNodes(w internalWalkFunc) {
|
||||||
if e.KeyExpr != nil {
|
if e.KeyExpr != nil {
|
||||||
w(ChildScope{
|
w(ChildScope{
|
||||||
LocalNames: scopeNames,
|
LocalNames: scopeNames,
|
||||||
Expr: &e.KeyExpr,
|
Expr: e.KeyExpr,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
w(ChildScope{
|
w(ChildScope{
|
||||||
LocalNames: scopeNames,
|
LocalNames: scopeNames,
|
||||||
Expr: &e.ValExpr,
|
Expr: e.ValExpr,
|
||||||
})
|
})
|
||||||
if e.CondExpr != nil {
|
if e.CondExpr != nil {
|
||||||
w(ChildScope{
|
w(ChildScope{
|
||||||
LocalNames: scopeNames,
|
LocalNames: scopeNames,
|
||||||
Expr: &e.CondExpr,
|
Expr: e.CondExpr,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1266,8 +1266,8 @@ func (e *SplatExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *SplatExpr) walkChildNodes(w internalWalkFunc) {
|
func (e *SplatExpr) walkChildNodes(w internalWalkFunc) {
|
||||||
e.Source = w(e.Source).(Expression)
|
w(e.Source)
|
||||||
e.Each = w(e.Each).(Expression)
|
w(e.Each)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *SplatExpr) Range() hcl.Range {
|
func (e *SplatExpr) Range() hcl.Range {
|
||||||
|
|
|
@ -129,8 +129,8 @@ type BinaryOpExpr struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *BinaryOpExpr) walkChildNodes(w internalWalkFunc) {
|
func (e *BinaryOpExpr) walkChildNodes(w internalWalkFunc) {
|
||||||
e.LHS = w(e.LHS).(Expression)
|
w(e.LHS)
|
||||||
e.RHS = w(e.RHS).(Expression)
|
w(e.RHS)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *BinaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
func (e *BinaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||||
|
@ -212,7 +212,7 @@ type UnaryOpExpr struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *UnaryOpExpr) walkChildNodes(w internalWalkFunc) {
|
func (e *UnaryOpExpr) walkChildNodes(w internalWalkFunc) {
|
||||||
e.Val = w(e.Val).(Expression)
|
w(e.Val)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *UnaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
func (e *UnaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||||
|
|
|
@ -16,8 +16,8 @@ type TemplateExpr struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *TemplateExpr) walkChildNodes(w internalWalkFunc) {
|
func (e *TemplateExpr) walkChildNodes(w internalWalkFunc) {
|
||||||
for i, part := range e.Parts {
|
for _, part := range e.Parts {
|
||||||
e.Parts[i] = w(part).(Expression)
|
w(part)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ type TemplateJoinExpr struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *TemplateJoinExpr) walkChildNodes(w internalWalkFunc) {
|
func (e *TemplateJoinExpr) walkChildNodes(w internalWalkFunc) {
|
||||||
e.Tuple = w(e.Tuple).(Expression)
|
w(e.Tuple)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *TemplateJoinExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
func (e *TemplateJoinExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||||
|
@ -184,7 +184,7 @@ type TemplateWrapExpr struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *TemplateWrapExpr) walkChildNodes(w internalWalkFunc) {
|
func (e *TemplateWrapExpr) walkChildNodes(w internalWalkFunc) {
|
||||||
e.Wrapped = w(e.Wrapped).(Expression)
|
w(e.Wrapped)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *TemplateWrapExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
func (e *TemplateWrapExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||||
|
|
|
@ -3,6 +3,8 @@ package hclsyntax
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl2/hcl"
|
||||||
)
|
)
|
||||||
|
|
||||||
type navigation struct {
|
type navigation struct {
|
||||||
|
@ -39,3 +41,19 @@ func (n navigation) ContextString(offset int) string {
|
||||||
}
|
}
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n navigation) ContextDefRange(offset int) hcl.Range {
|
||||||
|
var block *Block
|
||||||
|
for _, candidate := range n.root.Blocks {
|
||||||
|
if candidate.Range().ContainsOffset(offset) {
|
||||||
|
block = candidate
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if block == nil {
|
||||||
|
return hcl.Range{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return block.DefRange()
|
||||||
|
}
|
||||||
|
|
|
@ -19,4 +19,4 @@ type Node interface {
|
||||||
Range() hcl.Range
|
Range() hcl.Range
|
||||||
}
|
}
|
||||||
|
|
||||||
type internalWalkFunc func(Node) Node
|
type internalWalkFunc func(Node)
|
||||||
|
|
|
@ -273,7 +273,10 @@ Token:
|
||||||
return &Block{
|
return &Block{
|
||||||
Type: blockType,
|
Type: blockType,
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
Body: nil,
|
Body: &Body{
|
||||||
|
SrcRange: ident.Range,
|
||||||
|
EndRange: ident.Range,
|
||||||
|
},
|
||||||
|
|
||||||
TypeRange: ident.Range,
|
TypeRange: ident.Range,
|
||||||
LabelRanges: labelRanges,
|
LabelRanges: labelRanges,
|
||||||
|
|
|
@ -47,8 +47,8 @@ type Body struct {
|
||||||
var assertBodyImplBody hcl.Body = &Body{}
|
var assertBodyImplBody hcl.Body = &Body{}
|
||||||
|
|
||||||
func (b *Body) walkChildNodes(w internalWalkFunc) {
|
func (b *Body) walkChildNodes(w internalWalkFunc) {
|
||||||
b.Attributes = w(b.Attributes).(Attributes)
|
w(b.Attributes)
|
||||||
b.Blocks = w(b.Blocks).(Blocks)
|
w(b.Blocks)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Body) Range() hcl.Range {
|
func (b *Body) Range() hcl.Range {
|
||||||
|
@ -86,8 +86,8 @@ func (b *Body) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostic
|
||||||
|
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
Severity: hcl.DiagError,
|
Severity: hcl.DiagError,
|
||||||
Summary: "Unsupported attribute",
|
Summary: "Unsupported argument",
|
||||||
Detail: fmt.Sprintf("An attribute named %q is not expected here.%s", name, suggestion),
|
Detail: fmt.Sprintf("An argument named %q is not expected here.%s", name, suggestion),
|
||||||
Subject: &attr.NameRange,
|
Subject: &attr.NameRange,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,7 @@ func (b *Body) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostic
|
||||||
// Is there an attribute of the same name?
|
// Is there an attribute of the same name?
|
||||||
for _, attrS := range schema.Attributes {
|
for _, attrS := range schema.Attributes {
|
||||||
if attrS.Name == blockTy {
|
if attrS.Name == blockTy {
|
||||||
suggestion = fmt.Sprintf(" Did you mean to define attribute %q?", blockTy)
|
suggestion = fmt.Sprintf(" Did you mean to define argument %q? If so, use the equals sign to assign it a value.", blockTy)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,8 +151,8 @@ func (b *Body) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Bod
|
||||||
if attrS.Required {
|
if attrS.Required {
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
Severity: hcl.DiagError,
|
Severity: hcl.DiagError,
|
||||||
Summary: "Missing required attribute",
|
Summary: "Missing required argument",
|
||||||
Detail: fmt.Sprintf("The attribute %q is required, but no definition was found.", attrS.Name),
|
Detail: fmt.Sprintf("The argument %q is required, but no definition was found.", attrS.Name),
|
||||||
Subject: b.MissingItemRange().Ptr(),
|
Subject: b.MissingItemRange().Ptr(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -286,8 +286,8 @@ func (b *Body) MissingItemRange() hcl.Range {
|
||||||
type Attributes map[string]*Attribute
|
type Attributes map[string]*Attribute
|
||||||
|
|
||||||
func (a Attributes) walkChildNodes(w internalWalkFunc) {
|
func (a Attributes) walkChildNodes(w internalWalkFunc) {
|
||||||
for k, attr := range a {
|
for _, attr := range a {
|
||||||
a[k] = w(attr).(*Attribute)
|
w(attr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,7 +321,7 @@ type Attribute struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Attribute) walkChildNodes(w internalWalkFunc) {
|
func (a *Attribute) walkChildNodes(w internalWalkFunc) {
|
||||||
a.Expr = w(a.Expr).(Expression)
|
w(a.Expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Attribute) Range() hcl.Range {
|
func (a *Attribute) Range() hcl.Range {
|
||||||
|
@ -346,8 +346,8 @@ func (a *Attribute) AsHCLAttribute() *hcl.Attribute {
|
||||||
type Blocks []*Block
|
type Blocks []*Block
|
||||||
|
|
||||||
func (bs Blocks) walkChildNodes(w internalWalkFunc) {
|
func (bs Blocks) walkChildNodes(w internalWalkFunc) {
|
||||||
for i, block := range bs {
|
for _, block := range bs {
|
||||||
bs[i] = w(block).(*Block)
|
w(block)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,9 +378,13 @@ type Block struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Block) walkChildNodes(w internalWalkFunc) {
|
func (b *Block) walkChildNodes(w internalWalkFunc) {
|
||||||
b.Body = w(b.Body).(*Body)
|
w(b.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Block) Range() hcl.Range {
|
func (b *Block) Range() hcl.Range {
|
||||||
return hcl.RangeBetween(b.TypeRange, b.CloseBraceRange)
|
return hcl.RangeBetween(b.TypeRange, b.CloseBraceRange)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Block) DefRange() hcl.Range {
|
||||||
|
return hcl.RangeBetween(b.TypeRange, b.OpenBraceRange)
|
||||||
|
}
|
||||||
|
|
|
@ -72,15 +72,15 @@ func (w *variablesWalker) Exit(n Node) hcl.Diagnostics {
|
||||||
// that the child scope struct wraps.
|
// that the child scope struct wraps.
|
||||||
type ChildScope struct {
|
type ChildScope struct {
|
||||||
LocalNames map[string]struct{}
|
LocalNames map[string]struct{}
|
||||||
Expr *Expression // pointer because it can be replaced on walk
|
Expr Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e ChildScope) walkChildNodes(w internalWalkFunc) {
|
func (e ChildScope) walkChildNodes(w internalWalkFunc) {
|
||||||
*(e.Expr) = w(*(e.Expr)).(Expression)
|
w(e.Expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Range returns the range of the expression that the ChildScope is
|
// Range returns the range of the expression that the ChildScope is
|
||||||
// encapsulating. It isn't really very useful to call Range on a ChildScope.
|
// encapsulating. It isn't really very useful to call Range on a ChildScope.
|
||||||
func (e ChildScope) Range() hcl.Range {
|
func (e ChildScope) Range() hcl.Range {
|
||||||
return (*e.Expr).Range()
|
return e.Expr.Range()
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,8 @@ type VisitFunc func(node Node) hcl.Diagnostics
|
||||||
// and returned as a single set.
|
// and returned as a single set.
|
||||||
func VisitAll(node Node, f VisitFunc) hcl.Diagnostics {
|
func VisitAll(node Node, f VisitFunc) hcl.Diagnostics {
|
||||||
diags := f(node)
|
diags := f(node)
|
||||||
node.walkChildNodes(func(node Node) Node {
|
node.walkChildNodes(func(node Node) {
|
||||||
diags = append(diags, VisitAll(node, f)...)
|
diags = append(diags, VisitAll(node, f)...)
|
||||||
return node
|
|
||||||
})
|
})
|
||||||
return diags
|
return diags
|
||||||
}
|
}
|
||||||
|
@ -33,47 +32,10 @@ type Walker interface {
|
||||||
// Enter and Exit functions.
|
// Enter and Exit functions.
|
||||||
func Walk(node Node, w Walker) hcl.Diagnostics {
|
func Walk(node Node, w Walker) hcl.Diagnostics {
|
||||||
diags := w.Enter(node)
|
diags := w.Enter(node)
|
||||||
node.walkChildNodes(func(node Node) Node {
|
node.walkChildNodes(func(node Node) {
|
||||||
diags = append(diags, Walk(node, w)...)
|
diags = append(diags, Walk(node, w)...)
|
||||||
return node
|
|
||||||
})
|
})
|
||||||
moreDiags := w.Exit(node)
|
moreDiags := w.Exit(node)
|
||||||
diags = append(diags, moreDiags...)
|
diags = append(diags, moreDiags...)
|
||||||
return diags
|
return diags
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transformer is an interface used with Transform
|
|
||||||
type Transformer interface {
|
|
||||||
// Transform accepts a node and returns a replacement node along with
|
|
||||||
// a flag for whether to also visit child nodes. If the flag is false,
|
|
||||||
// none of the child nodes will be visited and the TransformExit method
|
|
||||||
// will not be called for the node.
|
|
||||||
//
|
|
||||||
// It is acceptable and appropriate for Transform to return the same node
|
|
||||||
// it was given, for situations where no transform is needed.
|
|
||||||
Transform(node Node) (Node, bool, hcl.Diagnostics)
|
|
||||||
|
|
||||||
// TransformExit signals the end of transformations of child nodes of the
|
|
||||||
// given node. If Transform returned a new node, the given node is the
|
|
||||||
// node that was returned, rather than the node that was originally
|
|
||||||
// encountered.
|
|
||||||
TransformExit(node Node) hcl.Diagnostics
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transform allows for in-place transformations of an AST starting with a
|
|
||||||
// particular node. The provider Transformer implementation drives the
|
|
||||||
// transformation process. The return value is the node that replaced the
|
|
||||||
// given top-level node.
|
|
||||||
func Transform(node Node, t Transformer) (Node, hcl.Diagnostics) {
|
|
||||||
newNode, descend, diags := t.Transform(node)
|
|
||||||
if !descend {
|
|
||||||
return newNode, diags
|
|
||||||
}
|
|
||||||
node.walkChildNodes(func(node Node) Node {
|
|
||||||
newNode, newDiags := Transform(node, t)
|
|
||||||
diags = append(diags, newDiags...)
|
|
||||||
return newNode
|
|
||||||
})
|
|
||||||
diags = append(diags, t.TransformExit(newNode)...)
|
|
||||||
return newNode, diags
|
|
||||||
}
|
|
||||||
|
|
|
@ -18,3 +18,17 @@ func ContextString(file *hcl.File, offset int) string {
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type contextDefRanger interface {
|
||||||
|
ContextDefRange(offset int) hcl.Range
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContextDefRange(file *hcl.File, offset int) hcl.Range {
|
||||||
|
if cser, ok := file.Nav.(contextDefRanger); ok {
|
||||||
|
defRange := cser.ContextDefRange(offset)
|
||||||
|
if !defRange.Empty() {
|
||||||
|
return defRange
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return file.Body.MissingItemRange()
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,17 @@ type File struct {
|
||||||
body *node
|
body *node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewEmptyFile constructs a new file with no content, ready to be mutated
|
||||||
|
// by other calls that append to its body.
|
||||||
|
func NewEmptyFile() *File {
|
||||||
|
f := &File{
|
||||||
|
inTree: newInTree(),
|
||||||
|
}
|
||||||
|
body := newBody()
|
||||||
|
f.body = f.children.Append(body)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
// Body returns the root body of the file, which contains the top-level
|
// Body returns the root body of the file, which contains the top-level
|
||||||
// attributes and blocks.
|
// attributes and blocks.
|
||||||
func (f *File) Body() *Body {
|
func (f *File) Body() *Body {
|
||||||
|
@ -22,7 +33,7 @@ func (f *File) Body() *Body {
|
||||||
//
|
//
|
||||||
// The tokens first have a simple formatting pass applied that adjusts only
|
// The tokens first have a simple formatting pass applied that adjusts only
|
||||||
// the spaces between them.
|
// the spaces between them.
|
||||||
func (f *File) WriteTo(wr io.Writer) (int, error) {
|
func (f *File) WriteTo(wr io.Writer) (int64, error) {
|
||||||
tokens := f.inTree.children.BuildTokens(nil)
|
tokens := f.inTree.children.BuildTokens(nil)
|
||||||
format(tokens)
|
format(tokens)
|
||||||
return tokens.WriteTo(wr)
|
return tokens.WriteTo(wr)
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
package hclwrite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Attribute struct {
|
||||||
|
inTree
|
||||||
|
|
||||||
|
leadComments *node
|
||||||
|
name *node
|
||||||
|
expr *node
|
||||||
|
lineComments *node
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAttribute() *Attribute {
|
||||||
|
return &Attribute{
|
||||||
|
inTree: newInTree(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Attribute) init(name string, expr *Expression) {
|
||||||
|
expr.assertUnattached()
|
||||||
|
|
||||||
|
nameTok := newIdentToken(name)
|
||||||
|
nameObj := newIdentifier(nameTok)
|
||||||
|
a.leadComments = a.children.Append(newComments(nil))
|
||||||
|
a.name = a.children.Append(nameObj)
|
||||||
|
a.children.AppendUnstructuredTokens(Tokens{
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenEqual,
|
||||||
|
Bytes: []byte{'='},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
a.expr = a.children.Append(expr)
|
||||||
|
a.expr.list = a.children
|
||||||
|
a.lineComments = a.children.Append(newComments(nil))
|
||||||
|
a.children.AppendUnstructuredTokens(Tokens{
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenNewline,
|
||||||
|
Bytes: []byte{'\n'},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Attribute) Expr() *Expression {
|
||||||
|
return a.expr.content.(*Expression)
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package hclwrite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Block struct {
|
||||||
|
inTree
|
||||||
|
|
||||||
|
leadComments *node
|
||||||
|
typeName *node
|
||||||
|
labels nodeSet
|
||||||
|
open *node
|
||||||
|
body *node
|
||||||
|
close *node
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBlock() *Block {
|
||||||
|
return &Block{
|
||||||
|
inTree: newInTree(),
|
||||||
|
labels: newNodeSet(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBlock constructs a new, empty block with the given type name and labels.
|
||||||
|
func NewBlock(typeName string, labels []string) *Block {
|
||||||
|
block := newBlock()
|
||||||
|
block.init(typeName, labels)
|
||||||
|
return block
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Block) init(typeName string, labels []string) {
|
||||||
|
nameTok := newIdentToken(typeName)
|
||||||
|
nameObj := newIdentifier(nameTok)
|
||||||
|
b.leadComments = b.children.Append(newComments(nil))
|
||||||
|
b.typeName = b.children.Append(nameObj)
|
||||||
|
for _, label := range labels {
|
||||||
|
labelToks := TokensForValue(cty.StringVal(label))
|
||||||
|
labelObj := newQuoted(labelToks)
|
||||||
|
labelNode := b.children.Append(labelObj)
|
||||||
|
b.labels.Add(labelNode)
|
||||||
|
}
|
||||||
|
b.open = b.children.AppendUnstructuredTokens(Tokens{
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenOBrace,
|
||||||
|
Bytes: []byte{'{'},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenNewline,
|
||||||
|
Bytes: []byte{'\n'},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
body := newBody() // initially totally empty; caller can append to it subsequently
|
||||||
|
b.body = b.children.Append(body)
|
||||||
|
b.close = b.children.AppendUnstructuredTokens(Tokens{
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenCBrace,
|
||||||
|
Bytes: []byte{'}'},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenNewline,
|
||||||
|
Bytes: []byte{'\n'},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body returns the body that represents the content of the receiving block.
|
||||||
|
//
|
||||||
|
// Appending to or otherwise modifying this body will make changes to the
|
||||||
|
// tokens that are generated between the blocks open and close braces.
|
||||||
|
func (b *Block) Body() *Body {
|
||||||
|
return b.body.content.(*Body)
|
||||||
|
}
|
|
@ -12,6 +12,13 @@ type Body struct {
|
||||||
items nodeSet
|
items nodeSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newBody() *Body {
|
||||||
|
return &Body{
|
||||||
|
inTree: newInTree(),
|
||||||
|
items: newNodeSet(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Body) appendItem(c nodeContent) *node {
|
func (b *Body) appendItem(c nodeContent) *node {
|
||||||
nn := b.children.Append(c)
|
nn := b.children.Append(c)
|
||||||
b.items.Add(nn)
|
b.items.Add(nn)
|
||||||
|
@ -25,10 +32,40 @@ func (b *Body) appendItemNode(nn *node) *node {
|
||||||
return nn
|
return nn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear removes all of the items from the body, making it empty.
|
||||||
|
func (b *Body) Clear() {
|
||||||
|
b.children.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Body) AppendUnstructuredTokens(ts Tokens) {
|
func (b *Body) AppendUnstructuredTokens(ts Tokens) {
|
||||||
b.inTree.children.Append(ts)
|
b.inTree.children.Append(ts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attributes returns a new map of all of the attributes in the body, with
|
||||||
|
// the attribute names as the keys.
|
||||||
|
func (b *Body) Attributes() map[string]*Attribute {
|
||||||
|
ret := make(map[string]*Attribute)
|
||||||
|
for n := range b.items {
|
||||||
|
if attr, isAttr := n.content.(*Attribute); isAttr {
|
||||||
|
nameObj := attr.name.content.(*identifier)
|
||||||
|
name := string(nameObj.token.Bytes)
|
||||||
|
ret[name] = attr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blocks returns a new slice of all the blocks in the body.
|
||||||
|
func (b *Body) Blocks() []*Block {
|
||||||
|
ret := make([]*Block, 0, len(b.items))
|
||||||
|
for n := range b.items {
|
||||||
|
if block, isBlock := n.content.(*Block); isBlock {
|
||||||
|
ret = append(ret, block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
// GetAttribute returns the attribute from the body that has the given name,
|
// GetAttribute returns the attribute from the body that has the given name,
|
||||||
// or returns nil if there is currently no matching attribute.
|
// or returns nil if there is currently no matching attribute.
|
||||||
func (b *Body) GetAttribute(name string) *Attribute {
|
func (b *Body) GetAttribute(name string) *Attribute {
|
||||||
|
@ -67,7 +104,7 @@ func (b *Body) SetAttributeValue(name string, val cty.Value) *Attribute {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetAttributeTraversal either replaces the expression of an existing attribute
|
// SetAttributeTraversal either replaces the expression of an existing attribute
|
||||||
// of the given name or adds a new attribute definition to the end of the block.
|
// of the given name or adds a new attribute definition to the end of the body.
|
||||||
//
|
//
|
||||||
// The new expression is given as a hcl.Traversal, which must be an absolute
|
// The new expression is given as a hcl.Traversal, which must be an absolute
|
||||||
// traversal. To set a literal value, use SetAttributeValue.
|
// traversal. To set a literal value, use SetAttributeValue.
|
||||||
|
@ -75,59 +112,42 @@ func (b *Body) SetAttributeValue(name string, val cty.Value) *Attribute {
|
||||||
// The return value is the attribute that was either modified in-place or
|
// The return value is the attribute that was either modified in-place or
|
||||||
// created.
|
// created.
|
||||||
func (b *Body) SetAttributeTraversal(name string, traversal hcl.Traversal) *Attribute {
|
func (b *Body) SetAttributeTraversal(name string, traversal hcl.Traversal) *Attribute {
|
||||||
panic("Body.SetAttributeTraversal not yet implemented")
|
attr := b.GetAttribute(name)
|
||||||
}
|
expr := NewExpressionAbsTraversal(traversal)
|
||||||
|
if attr != nil {
|
||||||
type Attribute struct {
|
attr.expr = attr.expr.ReplaceWith(expr)
|
||||||
inTree
|
} else {
|
||||||
|
attr := newAttribute()
|
||||||
leadComments *node
|
attr.init(name, expr)
|
||||||
name *node
|
b.appendItem(attr)
|
||||||
expr *node
|
|
||||||
lineComments *node
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAttribute() *Attribute {
|
|
||||||
return &Attribute{
|
|
||||||
inTree: newInTree(),
|
|
||||||
}
|
}
|
||||||
|
return attr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Attribute) init(name string, expr *Expression) {
|
// AppendBlock appends an existing block (which must not be already attached
|
||||||
expr.assertUnattached()
|
// to a body) to the end of the receiving body.
|
||||||
|
func (b *Body) AppendBlock(block *Block) *Block {
|
||||||
|
b.appendItem(block)
|
||||||
|
return block
|
||||||
|
}
|
||||||
|
|
||||||
nameTok := newIdentToken(name)
|
// AppendNewBlock appends a new nested block to the end of the receiving body
|
||||||
nameObj := newIdentifier(nameTok)
|
// with the given type name and labels.
|
||||||
a.leadComments = a.children.Append(newComments(nil))
|
func (b *Body) AppendNewBlock(typeName string, labels []string) *Block {
|
||||||
a.name = a.children.Append(nameObj)
|
block := newBlock()
|
||||||
a.children.AppendUnstructuredTokens(Tokens{
|
block.init(typeName, labels)
|
||||||
{
|
b.appendItem(block)
|
||||||
Type: hclsyntax.TokenEqual,
|
return block
|
||||||
Bytes: []byte{'='},
|
}
|
||||||
},
|
|
||||||
})
|
// AppendNewline appends a newline token to th end of the receiving body,
|
||||||
a.expr = a.children.Append(expr)
|
// which generally serves as a separator between different sets of body
|
||||||
a.expr.list = a.children
|
// contents.
|
||||||
a.lineComments = a.children.Append(newComments(nil))
|
func (b *Body) AppendNewline() {
|
||||||
a.children.AppendUnstructuredTokens(Tokens{
|
b.AppendUnstructuredTokens(Tokens{
|
||||||
{
|
{
|
||||||
Type: hclsyntax.TokenNewline,
|
Type: hclsyntax.TokenNewline,
|
||||||
Bytes: []byte{'\n'},
|
Bytes: []byte{'\n'},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Attribute) Expr() *Expression {
|
|
||||||
return a.expr.content.(*Expression)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Block struct {
|
|
||||||
inTree
|
|
||||||
|
|
||||||
leadComments *node
|
|
||||||
typeName *node
|
|
||||||
labels nodeSet
|
|
||||||
open *node
|
|
||||||
body *node
|
|
||||||
close *node
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
package hclwrite
|
package hclwrite
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl2/hcl"
|
||||||
|
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -41,9 +44,125 @@ func NewExpressionLiteral(val cty.Value) *Expression {
|
||||||
// NewExpressionAbsTraversal constructs an expression that represents the
|
// NewExpressionAbsTraversal constructs an expression that represents the
|
||||||
// given traversal, which must be absolute or this function will panic.
|
// given traversal, which must be absolute or this function will panic.
|
||||||
func NewExpressionAbsTraversal(traversal hcl.Traversal) *Expression {
|
func NewExpressionAbsTraversal(traversal hcl.Traversal) *Expression {
|
||||||
panic("NewExpressionAbsTraversal not yet implemented")
|
if traversal.IsRelative() {
|
||||||
|
panic("can't construct expression from relative traversal")
|
||||||
|
}
|
||||||
|
|
||||||
|
physT := newTraversal()
|
||||||
|
rootName := traversal.RootName()
|
||||||
|
steps := traversal[1:]
|
||||||
|
|
||||||
|
{
|
||||||
|
tn := newTraverseName()
|
||||||
|
tn.name = tn.children.Append(newIdentifier(&Token{
|
||||||
|
Type: hclsyntax.TokenIdent,
|
||||||
|
Bytes: []byte(rootName),
|
||||||
|
}))
|
||||||
|
physT.steps.Add(physT.children.Append(tn))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, step := range steps {
|
||||||
|
switch ts := step.(type) {
|
||||||
|
case hcl.TraverseAttr:
|
||||||
|
tn := newTraverseName()
|
||||||
|
tn.children.AppendUnstructuredTokens(Tokens{
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenDot,
|
||||||
|
Bytes: []byte{'.'},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
tn.name = tn.children.Append(newIdentifier(&Token{
|
||||||
|
Type: hclsyntax.TokenIdent,
|
||||||
|
Bytes: []byte(ts.Name),
|
||||||
|
}))
|
||||||
|
physT.steps.Add(physT.children.Append(tn))
|
||||||
|
case hcl.TraverseIndex:
|
||||||
|
ti := newTraverseIndex()
|
||||||
|
ti.children.AppendUnstructuredTokens(Tokens{
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenOBrack,
|
||||||
|
Bytes: []byte{'['},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
indexExpr := NewExpressionLiteral(ts.Key)
|
||||||
|
ti.key = ti.children.Append(indexExpr)
|
||||||
|
ti.children.AppendUnstructuredTokens(Tokens{
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenCBrack,
|
||||||
|
Bytes: []byte{']'},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
physT.steps.Add(physT.children.Append(ti))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expr := newExpression()
|
||||||
|
expr.absTraversals.Add(expr.children.Append(physT))
|
||||||
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Variables returns the absolute traversals that exist within the receiving
|
||||||
|
// expression.
|
||||||
|
func (e *Expression) Variables() []*Traversal {
|
||||||
|
nodes := e.absTraversals.List()
|
||||||
|
ret := make([]*Traversal, len(nodes))
|
||||||
|
for i, node := range nodes {
|
||||||
|
ret[i] = node.content.(*Traversal)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenameVariablePrefix examines each of the absolute traversals in the
|
||||||
|
// receiving expression to see if they have the given sequence of names as
|
||||||
|
// a prefix prefix. If so, they are updated in place to have the given
|
||||||
|
// replacement names instead of that prefix.
|
||||||
|
//
|
||||||
|
// This can be used to implement symbol renaming. The calling application can
|
||||||
|
// visit all relevant expressions in its input and apply the same renaming
|
||||||
|
// to implement a global symbol rename.
|
||||||
|
//
|
||||||
|
// The search and replacement traversals must be the same length, or this
|
||||||
|
// method will panic. Only attribute access operations can be matched and
|
||||||
|
// replaced. Index steps never match the prefix.
|
||||||
|
func (e *Expression) RenameVariablePrefix(search, replacement []string) {
|
||||||
|
if len(search) != len(replacement) {
|
||||||
|
panic(fmt.Sprintf("search and replacement length mismatch (%d and %d)", len(search), len(replacement)))
|
||||||
|
}
|
||||||
|
Traversals:
|
||||||
|
for node := range e.absTraversals {
|
||||||
|
traversal := node.content.(*Traversal)
|
||||||
|
if len(traversal.steps) < len(search) {
|
||||||
|
// If it's shorter then it can't have our prefix
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
stepNodes := traversal.steps.List()
|
||||||
|
for i, name := range search {
|
||||||
|
step, isName := stepNodes[i].content.(*TraverseName)
|
||||||
|
if !isName {
|
||||||
|
continue Traversals // only name nodes can match
|
||||||
|
}
|
||||||
|
foundNameBytes := step.name.content.(*identifier).token.Bytes
|
||||||
|
if len(foundNameBytes) != len(name) {
|
||||||
|
continue Traversals
|
||||||
|
}
|
||||||
|
if string(foundNameBytes) != name {
|
||||||
|
continue Traversals
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here then the prefix matched, so now we'll swap in
|
||||||
|
// the replacement strings.
|
||||||
|
for i, name := range replacement {
|
||||||
|
step := stepNodes[i].content.(*TraverseName)
|
||||||
|
token := step.name.content.(*identifier).token
|
||||||
|
token.Bytes = []byte(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traversal represents a sequence of variable, attribute, and/or index
|
||||||
|
// operations.
|
||||||
type Traversal struct {
|
type Traversal struct {
|
||||||
inTree
|
inTree
|
||||||
|
|
||||||
|
|
|
@ -4,4 +4,8 @@
|
||||||
// It operates at a different level of abstraction than the main HCL parser
|
// It operates at a different level of abstraction than the main HCL parser
|
||||||
// and AST, since details such as the placement of comments and newlines
|
// and AST, since details such as the placement of comments and newlines
|
||||||
// are preserved when unchanged.
|
// are preserved when unchanged.
|
||||||
|
//
|
||||||
|
// The hclwrite API follows a similar principle to XML/HTML DOM, allowing nodes
|
||||||
|
// to be read out, created and inserted, etc. Nodes represent syntax constructs
|
||||||
|
// rather than semantic concepts.
|
||||||
package hclwrite
|
package hclwrite
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl2/hcl"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
@ -25,6 +26,19 @@ func TokensForValue(val cty.Value) Tokens {
|
||||||
return toks
|
return toks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TokensForTraversal returns a sequence of tokens that represents the given
|
||||||
|
// traversal.
|
||||||
|
//
|
||||||
|
// If the traversal is absolute then the result is a self-contained, valid
|
||||||
|
// reference expression. If the traversal is relative then the returned tokens
|
||||||
|
// could be appended to some other expression tokens to traverse into the
|
||||||
|
// represented expression.
|
||||||
|
func TokensForTraversal(traversal hcl.Traversal) Tokens {
|
||||||
|
toks := appendTokensForTraversal(traversal, nil)
|
||||||
|
format(toks) // fiddle with the SpacesBefore field to get canonical spacing
|
||||||
|
return toks
|
||||||
|
}
|
||||||
|
|
||||||
func appendTokensForValue(val cty.Value, toks Tokens) Tokens {
|
func appendTokensForValue(val cty.Value, toks Tokens) Tokens {
|
||||||
switch {
|
switch {
|
||||||
|
|
||||||
|
@ -143,6 +157,47 @@ func appendTokensForValue(val cty.Value, toks Tokens) Tokens {
|
||||||
return toks
|
return toks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func appendTokensForTraversal(traversal hcl.Traversal, toks Tokens) Tokens {
|
||||||
|
for _, step := range traversal {
|
||||||
|
appendTokensForTraversalStep(step, toks)
|
||||||
|
}
|
||||||
|
return toks
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendTokensForTraversalStep(step hcl.Traverser, toks Tokens) {
|
||||||
|
switch ts := step.(type) {
|
||||||
|
case hcl.TraverseRoot:
|
||||||
|
toks = append(toks, &Token{
|
||||||
|
Type: hclsyntax.TokenIdent,
|
||||||
|
Bytes: []byte(ts.Name),
|
||||||
|
})
|
||||||
|
case hcl.TraverseAttr:
|
||||||
|
toks = append(
|
||||||
|
toks,
|
||||||
|
&Token{
|
||||||
|
Type: hclsyntax.TokenDot,
|
||||||
|
Bytes: []byte{'.'},
|
||||||
|
},
|
||||||
|
&Token{
|
||||||
|
Type: hclsyntax.TokenIdent,
|
||||||
|
Bytes: []byte(ts.Name),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
case hcl.TraverseIndex:
|
||||||
|
toks = append(toks, &Token{
|
||||||
|
Type: hclsyntax.TokenOBrack,
|
||||||
|
Bytes: []byte{'['},
|
||||||
|
})
|
||||||
|
appendTokensForValue(ts.Key, toks)
|
||||||
|
toks = append(toks, &Token{
|
||||||
|
Type: hclsyntax.TokenCBrack,
|
||||||
|
Bytes: []byte{']'},
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unsupported traversal step type %T", step))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func escapeQuotedStringLit(s string) []byte {
|
func escapeQuotedStringLit(s string) []byte {
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -104,6 +104,11 @@ func (ns *nodes) BuildTokens(to Tokens) Tokens {
|
||||||
return to
|
return to
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ns *nodes) Clear() {
|
||||||
|
ns.first = nil
|
||||||
|
ns.last = nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ns *nodes) Append(c nodeContent) *node {
|
func (ns *nodes) Append(c nodeContent) *node {
|
||||||
n := &node{
|
n := &node{
|
||||||
content: c,
|
content: c,
|
||||||
|
|
|
@ -372,6 +372,7 @@ func parseTraversal(nativeTraversal hcl.Traversal, from inputTokens) (before inp
|
||||||
before, step, after := parseTraversalStep(nativeStep, stepAfter)
|
before, step, after := parseTraversalStep(nativeStep, stepAfter)
|
||||||
children.AppendUnstructuredTokens(before.Tokens())
|
children.AppendUnstructuredTokens(before.Tokens())
|
||||||
children.AppendNode(step)
|
children.AppendNode(step)
|
||||||
|
traversal.steps.Add(step)
|
||||||
stepAfter = after
|
stepAfter = after
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ func (ts Tokens) Columns() int {
|
||||||
// WriteTo takes an io.Writer and writes the bytes for each token to it,
|
// WriteTo takes an io.Writer and writes the bytes for each token to it,
|
||||||
// along with the spacing that separates each token. In other words, this
|
// along with the spacing that separates each token. In other words, this
|
||||||
// allows serializing the tokens to a file or other such byte stream.
|
// allows serializing the tokens to a file or other such byte stream.
|
||||||
func (ts Tokens) WriteTo(wr io.Writer) (int, error) {
|
func (ts Tokens) WriteTo(wr io.Writer) (int64, error) {
|
||||||
// We know we're going to be writing a lot of small chunks of repeated
|
// We know we're going to be writing a lot of small chunks of repeated
|
||||||
// space characters, so we'll prepare a buffer of these that we can
|
// space characters, so we'll prepare a buffer of these that we can
|
||||||
// easily pass to wr.Write without any further allocation.
|
// easily pass to wr.Write without any further allocation.
|
||||||
|
@ -60,7 +60,7 @@ func (ts Tokens) WriteTo(wr io.Writer) (int, error) {
|
||||||
spaces[i] = ' '
|
spaces[i] = ' '
|
||||||
}
|
}
|
||||||
|
|
||||||
var n int
|
var n int64
|
||||||
var err error
|
var err error
|
||||||
for _, token := range ts {
|
for _, token := range ts {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -74,7 +74,7 @@ func (ts Tokens) WriteTo(wr io.Writer) (int, error) {
|
||||||
}
|
}
|
||||||
var thisN int
|
var thisN int
|
||||||
thisN, err = wr.Write(spaces[:thisChunk])
|
thisN, err = wr.Write(spaces[:thisChunk])
|
||||||
n += thisN
|
n += int64(thisN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ func (ts Tokens) WriteTo(wr io.Writer) (int, error) {
|
||||||
|
|
||||||
var thisN int
|
var thisN int
|
||||||
thisN, err = wr.Write(token.Bytes)
|
thisN, err = wr.Write(token.Bytes)
|
||||||
n += thisN
|
n += int64(thisN)
|
||||||
}
|
}
|
||||||
|
|
||||||
return n, err
|
return n, err
|
||||||
|
|
|
@ -343,7 +343,7 @@ github.com/hashicorp/hcl/hcl/scanner
|
||||||
github.com/hashicorp/hcl/hcl/strconv
|
github.com/hashicorp/hcl/hcl/strconv
|
||||||
github.com/hashicorp/hcl/json/scanner
|
github.com/hashicorp/hcl/json/scanner
|
||||||
github.com/hashicorp/hcl/json/token
|
github.com/hashicorp/hcl/json/token
|
||||||
# github.com/hashicorp/hcl2 v0.0.0-20180925175540-3f1c5474d4f7
|
# github.com/hashicorp/hcl2 v0.0.0-20181126233546-67424e43b184
|
||||||
github.com/hashicorp/hcl2/hcl
|
github.com/hashicorp/hcl2/hcl
|
||||||
github.com/hashicorp/hcl2/hcl/hclsyntax
|
github.com/hashicorp/hcl2/hcl/hclsyntax
|
||||||
github.com/hashicorp/hcl2/hcldec
|
github.com/hashicorp/hcl2/hcldec
|
||||||
|
|
Loading…
Reference in New Issue