917fbe66e7
This function serves the use-case of surgically renaming references to variables in an existing file without disturbing other tokens.
202 lines
5.2 KiB
Go
202 lines
5.2 KiB
Go
package hclwrite
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/hcl2/hcl"
|
|
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
type Expression struct {
|
|
inTree
|
|
|
|
absTraversals nodeSet
|
|
}
|
|
|
|
func newExpression() *Expression {
|
|
return &Expression{
|
|
inTree: newInTree(),
|
|
absTraversals: newNodeSet(),
|
|
}
|
|
}
|
|
|
|
// NewExpressionLiteral constructs an an expression that represents the given
|
|
// literal value.
|
|
//
|
|
// Since an unknown value cannot be represented in source code, this function
|
|
// will panic if the given value is unknown or contains a nested unknown value.
|
|
// Use val.IsWhollyKnown before calling to be sure.
|
|
//
|
|
// HCL native syntax does not directly represent lists, maps, and sets, and
|
|
// instead relies on the automatic conversions to those collection types from
|
|
// either list or tuple constructor syntax. Therefore converting collection
|
|
// values to source code and re-reading them will lose type information, and
|
|
// the reader must provide a suitable type at decode time to recover the
|
|
// original value.
|
|
func NewExpressionLiteral(val cty.Value) *Expression {
|
|
toks := TokensForValue(val)
|
|
expr := newExpression()
|
|
expr.children.AppendUnstructuredTokens(toks)
|
|
return expr
|
|
}
|
|
|
|
// NewExpressionAbsTraversal constructs an expression that represents the
|
|
// given traversal, which must be absolute or this function will panic.
|
|
func NewExpressionAbsTraversal(traversal hcl.Traversal) *Expression {
|
|
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
|
|
|
|
steps nodeSet
|
|
}
|
|
|
|
func newTraversal() *Traversal {
|
|
return &Traversal{
|
|
inTree: newInTree(),
|
|
steps: newNodeSet(),
|
|
}
|
|
}
|
|
|
|
type TraverseName struct {
|
|
inTree
|
|
|
|
name *node
|
|
}
|
|
|
|
func newTraverseName() *TraverseName {
|
|
return &TraverseName{
|
|
inTree: newInTree(),
|
|
}
|
|
}
|
|
|
|
type TraverseIndex struct {
|
|
inTree
|
|
|
|
key *node
|
|
}
|
|
|
|
func newTraverseIndex() *TraverseIndex {
|
|
return &TraverseIndex{
|
|
inTree: newInTree(),
|
|
}
|
|
}
|