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 }