github.com/terraform-linters/tflint@v0.51.2-0.20240520175844-3750771571b6/terraform/lang/function_calls.go (about)

     1  package lang
     2  
     3  import (
     4  	"strings"
     5  
     6  	"github.com/hashicorp/hcl/v2"
     7  	"github.com/hashicorp/hcl/v2/hclsyntax"
     8  	"github.com/hashicorp/hcl/v2/json"
     9  	"github.com/zclconf/go-cty/cty"
    10  )
    11  
    12  // FunctionCall represents a function call in an HCL expression.
    13  // The difference with hclsyntax.FunctionCallExpr is that
    14  // function calls are also available in JSON syntax.
    15  type FunctionCall struct {
    16  	Name      string
    17  	ArgsCount int
    18  }
    19  
    20  // FunctionCallsInExpr finds all of the function calls in the given expression.
    21  func FunctionCallsInExpr(expr hcl.Expression) ([]*FunctionCall, hcl.Diagnostics) {
    22  	if expr == nil {
    23  		return nil, nil
    24  	}
    25  
    26  	// For JSON syntax, walker is not implemented,
    27  	// so extract the hclsyntax.Node that we can walk on.
    28  	// See https://github.com/hashicorp/hcl/issues/543
    29  	nodes, diags := walkableNodesInExpr(expr)
    30  	ret := []*FunctionCall{}
    31  
    32  	for _, node := range nodes {
    33  		visitDiags := hclsyntax.VisitAll(node, func(n hclsyntax.Node) hcl.Diagnostics {
    34  			if funcCallExpr, ok := n.(*hclsyntax.FunctionCallExpr); ok {
    35  				ret = append(ret, &FunctionCall{
    36  					Name:      funcCallExpr.Name,
    37  					ArgsCount: len(funcCallExpr.Args),
    38  				})
    39  			}
    40  			return nil
    41  		})
    42  		diags = diags.Extend(visitDiags)
    43  	}
    44  	return ret, diags
    45  }
    46  
    47  // IsProviderDefined returns true if the function is provider-defined.
    48  func (f *FunctionCall) IsProviderDefined() bool {
    49  	return strings.HasPrefix(f.Name, "provider::")
    50  }
    51  
    52  // walkableNodesInExpr returns hclsyntax.Node from the given expression.
    53  // If the expression is an hclsyntax expression, it is returned as is.
    54  // If the expression is a JSON expression, it is parsed and
    55  // hclsyntax.Node it contains is returned.
    56  func walkableNodesInExpr(expr hcl.Expression) ([]hclsyntax.Node, hcl.Diagnostics) {
    57  	nodes := []hclsyntax.Node{}
    58  
    59  	expr = hcl.UnwrapExpressionUntil(expr, func(expr hcl.Expression) bool {
    60  		_, native := expr.(hclsyntax.Expression)
    61  		return native || json.IsJSONExpression(expr)
    62  	})
    63  	if expr == nil {
    64  		return nil, nil
    65  	}
    66  
    67  	if json.IsJSONExpression(expr) {
    68  		// HACK: For JSON expressions, we can get the JSON value as a literal
    69  		//       without any prior HCL parsing by evaluating it in a nil context.
    70  		//       We can take advantage of this property to walk through cty.Value
    71  		//       that may contain HCL expressions instead of walking through
    72  		//       expression nodes directly.
    73  		//       See https://github.com/hashicorp/hcl/issues/642
    74  		val, diags := expr.Value(nil)
    75  		if diags.HasErrors() {
    76  			return nodes, diags
    77  		}
    78  
    79  		err := cty.Walk(val, func(path cty.Path, v cty.Value) (bool, error) {
    80  			if v.Type() != cty.String || v.IsNull() || !v.IsKnown() {
    81  				return true, nil
    82  			}
    83  
    84  			node, parseDiags := hclsyntax.ParseTemplate([]byte(v.AsString()), expr.Range().Filename, expr.Range().Start)
    85  			if diags.HasErrors() {
    86  				diags = diags.Extend(parseDiags)
    87  				return true, nil
    88  			}
    89  
    90  			nodes = append(nodes, node)
    91  			return true, nil
    92  		})
    93  		if err != nil {
    94  			return nodes, hcl.Diagnostics{{
    95  				Severity: hcl.DiagError,
    96  				Summary:  "Failed to walk the expression value",
    97  				Detail:   err.Error(),
    98  				Subject:  expr.Range().Ptr(),
    99  			}}
   100  		}
   101  
   102  		return nodes, diags
   103  	}
   104  
   105  	// The JSON syntax is already processed, so it's guaranteed to be native syntax.
   106  	nodes = append(nodes, expr.(hclsyntax.Expression))
   107  
   108  	return nodes, nil
   109  }