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 {
|
||||
snippetRange = sourceRefs.Context.ToHCL()
|
||||
}
|
||||
|
||||
// Make sure the snippet includes the highlight. This should be true
|
||||
// for any reasonable diagnostic, but we'll make sure.
|
||||
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
|
||||
if sources != nil {
|
||||
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.
|
||||
fmt.Fprintf(&buf, " on %s line %d:\n (source code not available)\n", highlightRange.Filename, highlightRange.Start.Line)
|
||||
} 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 != "" {
|
||||
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)
|
||||
for sc.Scan() {
|
||||
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.
|
||||
// because the source is invalid, or because the offset is not inside any sort
|
||||
// of identifiable container.
|
||||
func sourceCodeContextStr(src []byte, rng hcl.Range) string {
|
||||
func parseRange(src []byte, rng hcl.Range) (*hcl.File, int) {
|
||||
filename := rng.Filename
|
||||
offset := rng.Start.Byte
|
||||
|
||||
|
@ -203,10 +204,10 @@ func sourceCodeContextStr(src []byte, rng hcl.Range) string {
|
|||
file, diags = parser.ParseHCL(src, filename)
|
||||
}
|
||||
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,
|
||||
|
|
|
@ -148,11 +148,11 @@ func TestOutputWithoutValueShouldFail(t *testing.T) {
|
|||
if code != 1 {
|
||||
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) {
|
||||
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) {
|
||||
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",
|
||||
hcl.DiagError,
|
||||
"Unsupported attribute",
|
||||
"Unsupported argument",
|
||||
},
|
||||
{
|
||||
"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/golang-lru v0.5.0 // indirect
|
||||
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/logutils v0.0.0-20150609070431-0dc08b1671f3
|
||||
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/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
||||
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/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
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/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-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/go.mod h1:KHvg/R2/dPtaePb16oW4qIyzkMxXOL38xjRN64adsts=
|
||||
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
|
||||
// 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
|
||||
// errors in the configuration itself, which are returned as diagnostics
|
||||
// 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) {
|
||||
e.Source = w(e.Source).(Expression)
|
||||
w(e.Source)
|
||||
}
|
||||
|
||||
func (e *RelativeTraversalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
|
@ -181,8 +181,8 @@ type FunctionCallExpr struct {
|
|||
}
|
||||
|
||||
func (e *FunctionCallExpr) walkChildNodes(w internalWalkFunc) {
|
||||
for i, arg := range e.Args {
|
||||
e.Args[i] = w(arg).(Expression)
|
||||
for _, arg := range e.Args {
|
||||
w(arg)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -463,9 +463,9 @@ type ConditionalExpr struct {
|
|||
}
|
||||
|
||||
func (e *ConditionalExpr) walkChildNodes(w internalWalkFunc) {
|
||||
e.Condition = w(e.Condition).(Expression)
|
||||
e.TrueResult = w(e.TrueResult).(Expression)
|
||||
e.FalseResult = w(e.FalseResult).(Expression)
|
||||
w(e.Condition)
|
||||
w(e.TrueResult)
|
||||
w(e.FalseResult)
|
||||
}
|
||||
|
||||
func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
|
@ -593,8 +593,8 @@ type IndexExpr struct {
|
|||
}
|
||||
|
||||
func (e *IndexExpr) walkChildNodes(w internalWalkFunc) {
|
||||
e.Collection = w(e.Collection).(Expression)
|
||||
e.Key = w(e.Key).(Expression)
|
||||
w(e.Collection)
|
||||
w(e.Key)
|
||||
}
|
||||
|
||||
func (e *IndexExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
|
@ -625,8 +625,8 @@ type TupleConsExpr struct {
|
|||
}
|
||||
|
||||
func (e *TupleConsExpr) walkChildNodes(w internalWalkFunc) {
|
||||
for i, expr := range e.Exprs {
|
||||
e.Exprs[i] = w(expr).(Expression)
|
||||
for _, expr := range e.Exprs {
|
||||
w(expr)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -674,9 +674,9 @@ type ObjectConsItem struct {
|
|||
}
|
||||
|
||||
func (e *ObjectConsExpr) walkChildNodes(w internalWalkFunc) {
|
||||
for i, item := range e.Items {
|
||||
e.Items[i].KeyExpr = w(item.KeyExpr).(Expression)
|
||||
e.Items[i].ValueExpr = w(item.ValueExpr).(Expression)
|
||||
for _, item := range e.Items {
|
||||
w(item.KeyExpr)
|
||||
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
|
||||
// not going to interpret it as a literal.
|
||||
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) {
|
||||
e.CollExpr = w(e.CollExpr).(Expression)
|
||||
w(e.CollExpr)
|
||||
|
||||
scopeNames := map[string]struct{}{}
|
||||
if e.KeyVar != "" {
|
||||
|
@ -1170,17 +1170,17 @@ func (e *ForExpr) walkChildNodes(w internalWalkFunc) {
|
|||
if e.KeyExpr != nil {
|
||||
w(ChildScope{
|
||||
LocalNames: scopeNames,
|
||||
Expr: &e.KeyExpr,
|
||||
Expr: e.KeyExpr,
|
||||
})
|
||||
}
|
||||
w(ChildScope{
|
||||
LocalNames: scopeNames,
|
||||
Expr: &e.ValExpr,
|
||||
Expr: e.ValExpr,
|
||||
})
|
||||
if e.CondExpr != nil {
|
||||
w(ChildScope{
|
||||
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) {
|
||||
e.Source = w(e.Source).(Expression)
|
||||
e.Each = w(e.Each).(Expression)
|
||||
w(e.Source)
|
||||
w(e.Each)
|
||||
}
|
||||
|
||||
func (e *SplatExpr) Range() hcl.Range {
|
||||
|
|
|
@ -129,8 +129,8 @@ type BinaryOpExpr struct {
|
|||
}
|
||||
|
||||
func (e *BinaryOpExpr) walkChildNodes(w internalWalkFunc) {
|
||||
e.LHS = w(e.LHS).(Expression)
|
||||
e.RHS = w(e.RHS).(Expression)
|
||||
w(e.LHS)
|
||||
w(e.RHS)
|
||||
}
|
||||
|
||||
func (e *BinaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
|
@ -212,7 +212,7 @@ type UnaryOpExpr struct {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -16,8 +16,8 @@ type TemplateExpr struct {
|
|||
}
|
||||
|
||||
func (e *TemplateExpr) walkChildNodes(w internalWalkFunc) {
|
||||
for i, part := range e.Parts {
|
||||
e.Parts[i] = w(part).(Expression)
|
||||
for _, part := range e.Parts {
|
||||
w(part)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,7 +98,7 @@ type TemplateJoinExpr struct {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
@ -184,7 +184,7 @@ type TemplateWrapExpr struct {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -3,6 +3,8 @@ package hclsyntax
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
)
|
||||
|
||||
type navigation struct {
|
||||
|
@ -39,3 +41,19 @@ func (n navigation) ContextString(offset int) 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
|
||||
}
|
||||
|
||||
type internalWalkFunc func(Node) Node
|
||||
type internalWalkFunc func(Node)
|
||||
|
|
|
@ -273,7 +273,10 @@ Token:
|
|||
return &Block{
|
||||
Type: blockType,
|
||||
Labels: labels,
|
||||
Body: nil,
|
||||
Body: &Body{
|
||||
SrcRange: ident.Range,
|
||||
EndRange: ident.Range,
|
||||
},
|
||||
|
||||
TypeRange: ident.Range,
|
||||
LabelRanges: labelRanges,
|
||||
|
|
|
@ -47,8 +47,8 @@ type Body struct {
|
|||
var assertBodyImplBody hcl.Body = &Body{}
|
||||
|
||||
func (b *Body) walkChildNodes(w internalWalkFunc) {
|
||||
b.Attributes = w(b.Attributes).(Attributes)
|
||||
b.Blocks = w(b.Blocks).(Blocks)
|
||||
w(b.Attributes)
|
||||
w(b.Blocks)
|
||||
}
|
||||
|
||||
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{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unsupported attribute",
|
||||
Detail: fmt.Sprintf("An attribute named %q is not expected here.%s", name, suggestion),
|
||||
Summary: "Unsupported argument",
|
||||
Detail: fmt.Sprintf("An argument named %q is not expected here.%s", name, suggestion),
|
||||
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?
|
||||
for _, attrS := range schema.Attributes {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -151,8 +151,8 @@ func (b *Body) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Bod
|
|||
if attrS.Required {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Missing required attribute",
|
||||
Detail: fmt.Sprintf("The attribute %q is required, but no definition was found.", attrS.Name),
|
||||
Summary: "Missing required argument",
|
||||
Detail: fmt.Sprintf("The argument %q is required, but no definition was found.", attrS.Name),
|
||||
Subject: b.MissingItemRange().Ptr(),
|
||||
})
|
||||
}
|
||||
|
@ -286,8 +286,8 @@ func (b *Body) MissingItemRange() hcl.Range {
|
|||
type Attributes map[string]*Attribute
|
||||
|
||||
func (a Attributes) walkChildNodes(w internalWalkFunc) {
|
||||
for k, attr := range a {
|
||||
a[k] = w(attr).(*Attribute)
|
||||
for _, attr := range a {
|
||||
w(attr)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -321,7 +321,7 @@ type Attribute struct {
|
|||
}
|
||||
|
||||
func (a *Attribute) walkChildNodes(w internalWalkFunc) {
|
||||
a.Expr = w(a.Expr).(Expression)
|
||||
w(a.Expr)
|
||||
}
|
||||
|
||||
func (a *Attribute) Range() hcl.Range {
|
||||
|
@ -346,8 +346,8 @@ func (a *Attribute) AsHCLAttribute() *hcl.Attribute {
|
|||
type Blocks []*Block
|
||||
|
||||
func (bs Blocks) walkChildNodes(w internalWalkFunc) {
|
||||
for i, block := range bs {
|
||||
bs[i] = w(block).(*Block)
|
||||
for _, block := range bs {
|
||||
w(block)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -378,9 +378,13 @@ type Block struct {
|
|||
}
|
||||
|
||||
func (b *Block) walkChildNodes(w internalWalkFunc) {
|
||||
b.Body = w(b.Body).(*Body)
|
||||
w(b.Body)
|
||||
}
|
||||
|
||||
func (b *Block) Range() hcl.Range {
|
||||
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.
|
||||
type ChildScope struct {
|
||||
LocalNames map[string]struct{}
|
||||
Expr *Expression // pointer because it can be replaced on walk
|
||||
Expr Expression
|
||||
}
|
||||
|
||||
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
|
||||
// encapsulating. It isn't really very useful to call Range on a ChildScope.
|
||||
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.
|
||||
func VisitAll(node Node, f VisitFunc) hcl.Diagnostics {
|
||||
diags := f(node)
|
||||
node.walkChildNodes(func(node Node) Node {
|
||||
node.walkChildNodes(func(node Node) {
|
||||
diags = append(diags, VisitAll(node, f)...)
|
||||
return node
|
||||
})
|
||||
return diags
|
||||
}
|
||||
|
@ -33,47 +32,10 @@ type Walker interface {
|
|||
// Enter and Exit functions.
|
||||
func Walk(node Node, w Walker) hcl.Diagnostics {
|
||||
diags := w.Enter(node)
|
||||
node.walkChildNodes(func(node Node) Node {
|
||||
node.walkChildNodes(func(node Node) {
|
||||
diags = append(diags, Walk(node, w)...)
|
||||
return node
|
||||
})
|
||||
moreDiags := w.Exit(node)
|
||||
diags = append(diags, moreDiags...)
|
||||
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 ""
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
// attributes and blocks.
|
||||
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 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)
|
||||
format(tokens)
|
||||
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
|
||||
}
|
||||
|
||||
func newBody() *Body {
|
||||
return &Body{
|
||||
inTree: newInTree(),
|
||||
items: newNodeSet(),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Body) appendItem(c nodeContent) *node {
|
||||
nn := b.children.Append(c)
|
||||
b.items.Add(nn)
|
||||
|
@ -25,10 +32,40 @@ func (b *Body) appendItemNode(nn *node) *node {
|
|||
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) {
|
||||
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,
|
||||
// or returns nil if there is currently no matching 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
|
||||
// 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
|
||||
// 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
|
||||
// created.
|
||||
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 {
|
||||
attr.expr = attr.expr.ReplaceWith(expr)
|
||||
} else {
|
||||
attr := newAttribute()
|
||||
attr.init(name, expr)
|
||||
b.appendItem(attr)
|
||||
}
|
||||
return attr
|
||||
}
|
||||
|
||||
type Attribute struct {
|
||||
inTree
|
||||
|
||||
leadComments *node
|
||||
name *node
|
||||
expr *node
|
||||
lineComments *node
|
||||
// AppendBlock appends an existing block (which must not be already attached
|
||||
// to a body) to the end of the receiving body.
|
||||
func (b *Body) AppendBlock(block *Block) *Block {
|
||||
b.appendItem(block)
|
||||
return block
|
||||
}
|
||||
|
||||
func newAttribute() *Attribute {
|
||||
return &Attribute{
|
||||
inTree: newInTree(),
|
||||
}
|
||||
// AppendNewBlock appends a new nested block to the end of the receiving body
|
||||
// with the given type name and labels.
|
||||
func (b *Body) AppendNewBlock(typeName string, labels []string) *Block {
|
||||
block := newBlock()
|
||||
block.init(typeName, labels)
|
||||
b.appendItem(block)
|
||||
return block
|
||||
}
|
||||
|
||||
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{
|
||||
// AppendNewline appends a newline token to th end of the receiving body,
|
||||
// which generally serves as a separator between different sets of body
|
||||
// contents.
|
||||
func (b *Body) AppendNewline() {
|
||||
b.AppendUnstructuredTokens(Tokens{
|
||||
{
|
||||
Type: hclsyntax.TokenNewline,
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
|
@ -41,9 +44,125 @@ func NewExpressionLiteral(val cty.Value) *Expression {
|
|||
// NewExpressionAbsTraversal constructs an expression that represents the
|
||||
// given traversal, which must be absolute or this function will panic.
|
||||
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 {
|
||||
inTree
|
||||
|
||||
|
|
|
@ -4,4 +4,8 @@
|
|||
// 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
|
||||
// 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
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
@ -25,6 +26,19 @@ func TokensForValue(val cty.Value) Tokens {
|
|||
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 {
|
||||
switch {
|
||||
|
||||
|
@ -143,6 +157,47 @@ func appendTokensForValue(val cty.Value, toks Tokens) Tokens {
|
|||
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 {
|
||||
if len(s) == 0 {
|
||||
return nil
|
||||
|
|
|
@ -104,6 +104,11 @@ func (ns *nodes) BuildTokens(to Tokens) Tokens {
|
|||
return to
|
||||
}
|
||||
|
||||
func (ns *nodes) Clear() {
|
||||
ns.first = nil
|
||||
ns.last = nil
|
||||
}
|
||||
|
||||
func (ns *nodes) Append(c nodeContent) *node {
|
||||
n := &node{
|
||||
content: c,
|
||||
|
|
|
@ -372,6 +372,7 @@ func parseTraversal(nativeTraversal hcl.Traversal, from inputTokens) (before inp
|
|||
before, step, after := parseTraversalStep(nativeStep, stepAfter)
|
||||
children.AppendUnstructuredTokens(before.Tokens())
|
||||
children.AppendNode(step)
|
||||
traversal.steps.Add(step)
|
||||
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,
|
||||
// along with the spacing that separates each token. In other words, this
|
||||
// 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
|
||||
// space characters, so we'll prepare a buffer of these that we can
|
||||
// easily pass to wr.Write without any further allocation.
|
||||
|
@ -60,7 +60,7 @@ func (ts Tokens) WriteTo(wr io.Writer) (int, error) {
|
|||
spaces[i] = ' '
|
||||
}
|
||||
|
||||
var n int
|
||||
var n int64
|
||||
var err error
|
||||
for _, token := range ts {
|
||||
if err != nil {
|
||||
|
@ -74,7 +74,7 @@ func (ts Tokens) WriteTo(wr io.Writer) (int, error) {
|
|||
}
|
||||
var thisN int
|
||||
thisN, err = wr.Write(spaces[:thisChunk])
|
||||
n += thisN
|
||||
n += int64(thisN)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ func (ts Tokens) WriteTo(wr io.Writer) (int, error) {
|
|||
|
||||
var thisN int
|
||||
thisN, err = wr.Write(token.Bytes)
|
||||
n += thisN
|
||||
n += int64(thisN)
|
||||
}
|
||||
|
||||
return n, err
|
||||
|
|
|
@ -343,7 +343,7 @@ github.com/hashicorp/hcl/hcl/scanner
|
|||
github.com/hashicorp/hcl/hcl/strconv
|
||||
github.com/hashicorp/hcl/json/scanner
|
||||
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/hclsyntax
|
||||
github.com/hashicorp/hcl2/hcldec
|
||||
|
|
Loading…
Reference in New Issue