2020-03-18 17:28:57 +01:00
|
|
|
// Copyright 2016-2020, Pulumi Corporation.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package model
|
|
|
|
|
|
|
|
import (
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
2021-03-17 14:20:05 +01:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
2020-03-18 17:28:57 +01:00
|
|
|
)
|
|
|
|
|
2020-04-03 06:23:12 +02:00
|
|
|
// A BodyItemVisitor is a function that visits and optionally replaces the contents of a body item.
|
|
|
|
type BodyItemVisitor func(n BodyItem) (BodyItem, hcl.Diagnostics)
|
|
|
|
|
2020-04-21 19:24:42 +02:00
|
|
|
func BodyItemIdentityVisitor(n BodyItem) (BodyItem, hcl.Diagnostics) {
|
2020-04-03 06:23:12 +02:00
|
|
|
return n, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func visitBlock(n *Block, pre, post BodyItemVisitor) (BodyItem, hcl.Diagnostics) {
|
|
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
|
|
|
|
var items []BodyItem
|
|
|
|
for _, item := range n.Body.Items {
|
|
|
|
newItem, diags := VisitBodyItem(item, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
if newItem != nil {
|
|
|
|
items = append(items, newItem)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
n.Body.Items = items
|
|
|
|
|
|
|
|
block, diags := post(n)
|
|
|
|
return block, append(diagnostics, diags...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func VisitBodyItem(n BodyItem, pre, post BodyItemVisitor) (BodyItem, hcl.Diagnostics) {
|
|
|
|
if n == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2020-04-21 19:24:42 +02:00
|
|
|
if pre == nil {
|
|
|
|
pre = BodyItemIdentityVisitor
|
|
|
|
}
|
|
|
|
|
2020-04-03 06:23:12 +02:00
|
|
|
nn, preDiags := pre(n)
|
|
|
|
|
|
|
|
var postDiags hcl.Diagnostics
|
2020-04-21 22:52:24 +02:00
|
|
|
if post != nil {
|
|
|
|
switch n := nn.(type) {
|
|
|
|
case *Attribute:
|
|
|
|
nn, postDiags = post(n)
|
|
|
|
case *Block:
|
|
|
|
nn, postDiags = visitBlock(n, pre, post)
|
|
|
|
default:
|
|
|
|
contract.Failf("unexpected node type in visitExpression: %T", n)
|
|
|
|
return nil, nil
|
|
|
|
}
|
2020-04-03 06:23:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return nn, append(preDiags, postDiags...)
|
|
|
|
}
|
|
|
|
|
2020-03-18 17:28:57 +01:00
|
|
|
// An ExpressionVisitor is a function that visits and optionally replaces a node in an expression tree.
|
|
|
|
type ExpressionVisitor func(n Expression) (Expression, hcl.Diagnostics)
|
|
|
|
|
|
|
|
// IdentityVisitor is a ExpressionVisitor that returns the input node unchanged.
|
|
|
|
func IdentityVisitor(n Expression) (Expression, hcl.Diagnostics) {
|
|
|
|
return n, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func visitAnonymousFunction(n *AnonymousFunctionExpression, pre, post ExpressionVisitor) (Expression, hcl.Diagnostics) {
|
|
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
|
|
|
|
body, diags := VisitExpression(n.Body, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
n.Body = body
|
|
|
|
|
|
|
|
expr, diags := post(n)
|
|
|
|
return expr, append(diagnostics, diags...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func visitBinaryOp(n *BinaryOpExpression, pre, post ExpressionVisitor) (Expression, hcl.Diagnostics) {
|
|
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
|
|
|
|
left, diags := VisitExpression(n.LeftOperand, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
right, diags := VisitExpression(n.RightOperand, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
n.LeftOperand, n.RightOperand = left, right
|
|
|
|
|
|
|
|
expr, diags := post(n)
|
|
|
|
return expr, append(diagnostics, diags...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func visitConditional(n *ConditionalExpression, pre, post ExpressionVisitor) (Expression, hcl.Diagnostics) {
|
|
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
|
|
|
|
condition, diags := VisitExpression(n.Condition, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
trueResult, diags := VisitExpression(n.TrueResult, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
falseResult, diags := VisitExpression(n.FalseResult, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
n.Condition, n.TrueResult, n.FalseResult = condition, trueResult, falseResult
|
|
|
|
|
|
|
|
expr, diags := post(n)
|
|
|
|
return expr, append(diagnostics, diags...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func visitFor(n *ForExpression, pre, post ExpressionVisitor) (Expression, hcl.Diagnostics) {
|
|
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
|
|
|
|
collection, diags := VisitExpression(n.Collection, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
key, diags := VisitExpression(n.Key, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
value, diags := VisitExpression(n.Value, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
condition, diags := VisitExpression(n.Condition, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
n.Collection, n.Key, n.Value, n.Condition = collection, key, value, condition
|
|
|
|
|
|
|
|
expr, diags := post(n)
|
|
|
|
return expr, append(diagnostics, diags...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func visitFunctionCall(n *FunctionCallExpression, pre, post ExpressionVisitor) (Expression, hcl.Diagnostics) {
|
|
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
|
|
|
|
args, diags := visitExpressions(n.Args, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
n.Args = args
|
|
|
|
|
|
|
|
expr, diags := post(n)
|
|
|
|
return expr, append(diagnostics, diags...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func visitIndex(n *IndexExpression, pre, post ExpressionVisitor) (Expression, hcl.Diagnostics) {
|
|
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
|
|
|
|
collection, diags := VisitExpression(n.Collection, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
key, diags := VisitExpression(n.Key, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
n.Collection, n.Key = collection, key
|
|
|
|
|
|
|
|
expr, diags := post(n)
|
|
|
|
return expr, append(diagnostics, diags...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func visitObjectCons(n *ObjectConsExpression, pre, post ExpressionVisitor) (Expression, hcl.Diagnostics) {
|
|
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
|
|
|
|
for i, item := range n.Items {
|
|
|
|
key, diags := VisitExpression(item.Key, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
value, diags := VisitExpression(item.Value, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
n.Items[i] = ObjectConsItem{Key: key, Value: value}
|
|
|
|
}
|
|
|
|
|
|
|
|
expr, diags := post(n)
|
|
|
|
return expr, append(diagnostics, diags...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func visitRelativeTraversal(n *RelativeTraversalExpression, pre, post ExpressionVisitor) (Expression, hcl.Diagnostics) {
|
|
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
|
|
|
|
source, diags := VisitExpression(n.Source, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
n.Source = source
|
|
|
|
|
|
|
|
expr, diags := post(n)
|
|
|
|
return expr, append(diagnostics, diags...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func visitSplat(n *SplatExpression, pre, post ExpressionVisitor) (Expression, hcl.Diagnostics) {
|
|
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
|
|
|
|
source, diags := VisitExpression(n.Source, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
each, diags := VisitExpression(n.Each, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
n.Source, n.Each = source, each
|
|
|
|
|
|
|
|
expr, diags := post(n)
|
|
|
|
return expr, append(diagnostics, diags...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func visitTemplate(n *TemplateExpression, pre, post ExpressionVisitor) (Expression, hcl.Diagnostics) {
|
|
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
|
|
|
|
parts, diags := visitExpressions(n.Parts, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
n.Parts = parts
|
|
|
|
|
|
|
|
expr, diags := post(n)
|
|
|
|
return expr, append(diagnostics, diags...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func visitTemplateJoin(n *TemplateJoinExpression, pre, post ExpressionVisitor) (Expression, hcl.Diagnostics) {
|
|
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
|
|
|
|
tuple, diags := VisitExpression(n.Tuple, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
n.Tuple = tuple
|
|
|
|
|
|
|
|
expr, diags := post(n)
|
|
|
|
return expr, append(diagnostics, diags...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func visitTupleCons(n *TupleConsExpression, pre, post ExpressionVisitor) (Expression, hcl.Diagnostics) {
|
|
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
|
|
|
|
expressions, diags := visitExpressions(n.Expressions, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
n.Expressions = expressions
|
|
|
|
|
|
|
|
expr, diags := post(n)
|
|
|
|
return expr, append(diagnostics, diags...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func visitUnaryOp(n *UnaryOpExpression, pre, post ExpressionVisitor) (Expression, hcl.Diagnostics) {
|
|
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
|
|
|
|
operand, diags := VisitExpression(n.Operand, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
|
|
|
|
n.Operand = operand
|
|
|
|
|
|
|
|
expr, diags := post(n)
|
|
|
|
return expr, append(diagnostics, diags...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func visitExpressions(ns []Expression, pre, post ExpressionVisitor) ([]Expression, hcl.Diagnostics) {
|
|
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
|
|
|
|
nils := 0
|
|
|
|
for i, e := range ns {
|
|
|
|
ee, diags := VisitExpression(e, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
if ee == nil {
|
|
|
|
nils++
|
|
|
|
}
|
|
|
|
ns[i] = ee
|
|
|
|
}
|
|
|
|
if nils == 0 {
|
|
|
|
return ns, diagnostics
|
|
|
|
} else if nils == len(ns) {
|
|
|
|
return []Expression{}, diagnostics
|
|
|
|
}
|
|
|
|
|
|
|
|
nns := make([]Expression, 0, len(ns)-nils)
|
|
|
|
for _, e := range ns {
|
|
|
|
if e != nil {
|
|
|
|
nns = append(nns, e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nns, diagnostics
|
|
|
|
}
|
|
|
|
|
|
|
|
// VisitExpression visits each node in an expression tree using the given pre- and post-order visitors. If the preorder
|
|
|
|
// visitor returns a new node, that node's descendents will be visited. VisitExpression returns the result of the
|
|
|
|
// post-order visitor. All diagnostics are accumulated.
|
|
|
|
func VisitExpression(n Expression, pre, post ExpressionVisitor) (Expression, hcl.Diagnostics) {
|
|
|
|
if n == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2020-04-21 19:24:42 +02:00
|
|
|
if pre == nil {
|
|
|
|
pre = IdentityVisitor
|
|
|
|
}
|
|
|
|
|
2020-03-18 17:28:57 +01:00
|
|
|
nn, preDiags := pre(n)
|
|
|
|
|
|
|
|
var postDiags hcl.Diagnostics
|
2020-04-21 22:52:24 +02:00
|
|
|
if post != nil {
|
|
|
|
switch n := nn.(type) {
|
|
|
|
case *AnonymousFunctionExpression:
|
|
|
|
nn, postDiags = visitAnonymousFunction(n, pre, post)
|
|
|
|
case *BinaryOpExpression:
|
|
|
|
nn, postDiags = visitBinaryOp(n, pre, post)
|
|
|
|
case *ConditionalExpression:
|
|
|
|
nn, postDiags = visitConditional(n, pre, post)
|
|
|
|
case *ErrorExpression:
|
|
|
|
nn, postDiags = post(n)
|
|
|
|
case *ForExpression:
|
|
|
|
nn, postDiags = visitFor(n, pre, post)
|
|
|
|
case *FunctionCallExpression:
|
|
|
|
nn, postDiags = visitFunctionCall(n, pre, post)
|
|
|
|
case *IndexExpression:
|
|
|
|
nn, postDiags = visitIndex(n, pre, post)
|
|
|
|
case *LiteralValueExpression:
|
|
|
|
nn, postDiags = post(n)
|
|
|
|
case *ObjectConsExpression:
|
|
|
|
nn, postDiags = visitObjectCons(n, pre, post)
|
|
|
|
case *RelativeTraversalExpression:
|
|
|
|
nn, postDiags = visitRelativeTraversal(n, pre, post)
|
|
|
|
case *ScopeTraversalExpression:
|
|
|
|
nn, postDiags = post(n)
|
|
|
|
case *SplatExpression:
|
|
|
|
nn, postDiags = visitSplat(n, pre, post)
|
|
|
|
case *TemplateExpression:
|
|
|
|
nn, postDiags = visitTemplate(n, pre, post)
|
|
|
|
case *TemplateJoinExpression:
|
|
|
|
nn, postDiags = visitTemplateJoin(n, pre, post)
|
|
|
|
case *TupleConsExpression:
|
|
|
|
nn, postDiags = visitTupleCons(n, pre, post)
|
|
|
|
case *UnaryOpExpression:
|
|
|
|
nn, postDiags = visitUnaryOp(n, pre, post)
|
|
|
|
default:
|
|
|
|
contract.Failf("unexpected node type in visitExpression: %T", n)
|
|
|
|
return nil, nil
|
|
|
|
}
|
2020-03-18 17:28:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return nn, append(preDiags, postDiags...)
|
|
|
|
}
|
2020-04-21 19:24:42 +02:00
|
|
|
|
|
|
|
func visitBlockExpressions(n *Block, pre, post ExpressionVisitor) hcl.Diagnostics {
|
|
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
|
|
|
|
for _, item := range n.Body.Items {
|
|
|
|
diags := VisitExpressions(item, pre, post)
|
|
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
}
|
|
|
|
|
|
|
|
return diagnostics
|
|
|
|
}
|
|
|
|
|
|
|
|
// VisitExpressions visits each expression that descends from the given body item.
|
|
|
|
func VisitExpressions(n BodyItem, pre, post ExpressionVisitor) hcl.Diagnostics {
|
|
|
|
if n == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if pre == nil {
|
|
|
|
pre = IdentityVisitor
|
|
|
|
}
|
|
|
|
|
|
|
|
switch n := n.(type) {
|
|
|
|
case *Attribute:
|
|
|
|
v, diags := VisitExpression(n.Value, pre, post)
|
|
|
|
n.Value = v
|
|
|
|
return diags
|
|
|
|
case *Block:
|
|
|
|
return visitBlockExpressions(n, pre, post)
|
|
|
|
default:
|
|
|
|
contract.Failf("unexpected node type in visitExpression: %T", n)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|