Skip to content

Commit 74f4303

Browse files
committed
tflint: Sending expression nodes as a text representation
1 parent 18bced3 commit 74f4303

File tree

3 files changed

+106
-49
lines changed

3 files changed

+106
-49
lines changed

plugin/plugin.go

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import (
55
"net/rpc"
66

77
plugin "github.com/hashicorp/go-plugin"
8-
"github.com/hashicorp/hcl/v2"
9-
"github.com/hashicorp/hcl/v2/hclsyntax"
108
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
119
)
1210

@@ -36,28 +34,4 @@ func (RuleSetPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, er
3634
// the type of the related structure is registered in gob at the initial time.
3735
func init() {
3836
gob.Register(tflint.Error{})
39-
// https://github.com/hashicorp/hcl/blob/v2.0.0/hclsyntax/expression.go
40-
gob.Register(&hclsyntax.LiteralValueExpr{})
41-
gob.Register(&hclsyntax.ScopeTraversalExpr{})
42-
gob.Register(&hclsyntax.RelativeTraversalExpr{})
43-
gob.Register(&hclsyntax.FunctionCallExpr{})
44-
gob.Register(&hclsyntax.ConditionalExpr{})
45-
gob.Register(&hclsyntax.IndexExpr{})
46-
gob.Register(&hclsyntax.TupleConsExpr{})
47-
gob.Register(&hclsyntax.ObjectConsExpr{})
48-
gob.Register(&hclsyntax.ObjectConsKeyExpr{})
49-
gob.Register(&hclsyntax.ForExpr{})
50-
gob.Register(&hclsyntax.SplatExpr{})
51-
// https://github.com/hashicorp/hcl/blob/v2.0.0/hclsyntax/expression_ops.go
52-
gob.Register(&hclsyntax.BinaryOpExpr{})
53-
gob.Register(&hclsyntax.UnaryOpExpr{})
54-
// https://github.com/hashicorp/hcl/blob/v2.0.0/hclsyntax/expression_template.go
55-
gob.Register(&hclsyntax.TemplateExpr{})
56-
gob.Register(&hclsyntax.TemplateJoinExpr{})
57-
gob.Register(&hclsyntax.TemplateWrapExpr{})
58-
// https://github.com/hashicorp/hcl/blob/v2.0.0/traversal.go
59-
gob.Register(hcl.TraverseRoot{})
60-
gob.Register(hcl.TraverseAttr{})
61-
gob.Register(hcl.TraverseIndex{})
62-
gob.Register(hcl.TraverseSplat{})
6337
}

tflint/client.go

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ package tflint
22

33
import (
44
"fmt"
5+
"io/ioutil"
56
"log"
67
"net"
78
"net/rpc"
9+
"strings"
810

911
hcl "github.com/hashicorp/hcl/v2"
12+
"github.com/hashicorp/hcl/v2/hclsyntax"
1013
"github.com/zclconf/go-cty/cty"
1114
"github.com/zclconf/go-cty/cty/gocty"
1215
)
@@ -30,10 +33,20 @@ type AttributesRequest struct {
3033

3134
// AttributesResponse is the interface used to communicate via RPC.
3235
type AttributesResponse struct {
33-
Attributes []*hcl.Attribute
36+
Attributes []*Attribute
3437
Err error
3538
}
3639

40+
// Attribute is an intermediate representation of hcl.Attribute.
41+
// It has an expression as a string of bytes so that hcl.Expression is not transferred via RPC.
42+
type Attribute struct {
43+
Name string
44+
Expr []byte
45+
ExprRange hcl.Range
46+
Range hcl.Range
47+
NameRange hcl.Range
48+
}
49+
3750
// WalkResourceAttributes queries the host process, receives a list of attributes that match the conditions,
3851
// and passes each to the walker function.
3952
func (c *Client) WalkResourceAttributes(resource, attributeName string, walker func(*hcl.Attribute) error) error {
@@ -48,7 +61,18 @@ func (c *Client) WalkResourceAttributes(resource, attributeName string, walker f
4861
}
4962

5063
for _, attribute := range response.Attributes {
51-
if err := walker(attribute); err != nil {
64+
expr, diags := parseExpression(attribute.Expr, attribute.ExprRange.Filename, attribute.ExprRange.Start)
65+
if diags.HasErrors() {
66+
return diags
67+
}
68+
attr := &hcl.Attribute{
69+
Name: attribute.Name,
70+
Expr: expr,
71+
Range: attribute.Range,
72+
NameRange: attribute.NameRange,
73+
}
74+
75+
if err := walker(attr); err != nil {
5276
return err
5377
}
5478
}
@@ -58,8 +82,9 @@ func (c *Client) WalkResourceAttributes(resource, attributeName string, walker f
5882

5983
// EvalExprRequest is the interface used to communicate via RPC.
6084
type EvalExprRequest struct {
61-
Expr hcl.Expression
62-
Ret interface{}
85+
Expr []byte
86+
ExprRange hcl.Range
87+
Ret interface{}
6388
}
6489

6590
// EvalExprResponse is the interface used to communicate with RPC.
@@ -74,7 +99,16 @@ func (c *Client) EvaluateExpr(expr hcl.Expression, ret interface{}) error {
7499
var response EvalExprResponse
75100
var err error
76101

77-
if err := c.rpcClient.Call("Plugin.EvalExpr", EvalExprRequest{Expr: expr, Ret: ret}, &response); err != nil {
102+
src, err := ioutil.ReadFile(expr.Range().Filename)
103+
if err != nil {
104+
return err
105+
}
106+
req := EvalExprRequest{
107+
Expr: expr.Range().SliceBytes(src),
108+
ExprRange: expr.Range(),
109+
Ret: ret,
110+
}
111+
if err := c.rpcClient.Call("Plugin.EvalExpr", req, &response); err != nil {
78112
return err
79113
}
80114
if response.Err != nil {
@@ -101,21 +135,28 @@ func (c *Client) EvaluateExpr(expr hcl.Expression, ret interface{}) error {
101135

102136
// EmitIssueRequest is the interface used to communicate via RPC.
103137
type EmitIssueRequest struct {
104-
Rule *RuleObject
105-
Message string
106-
Location hcl.Range
107-
Meta Metadata
138+
Rule *RuleObject
139+
Message string
140+
Location hcl.Range
141+
Expr []byte
142+
ExprRange hcl.Range
108143
}
109144

110145
// EmitIssue emits attributes to build the issue to the host process
111146
// Note that the passed rule need to be converted to generic objects
112147
// because the custom structure defined in the plugin cannot be sent via RPC.
113148
func (c *Client) EmitIssue(rule Rule, message string, location hcl.Range, meta Metadata) error {
149+
src, err := ioutil.ReadFile(meta.Expr.Range().Filename)
150+
if err != nil {
151+
return err
152+
}
153+
114154
req := &EmitIssueRequest{
115-
Rule: newObjectFromRule(rule),
116-
Message: message,
117-
Location: location,
118-
Meta: meta,
155+
Rule: newObjectFromRule(rule),
156+
Message: message,
157+
Location: location,
158+
Expr: meta.Expr.Range().SliceBytes(src),
159+
ExprRange: meta.Expr.Range(),
119160
}
120161
if err := c.rpcClient.Call("Plugin.EmitIssue", &req, new(interface{})); err != nil {
121162
return err
@@ -143,3 +184,21 @@ func (*Client) EnsureNoError(err error, proc func() error) error {
143184
return err
144185
}
145186
}
187+
188+
func parseExpression(src []byte, filename string, start hcl.Pos) (hcl.Expression, hcl.Diagnostics) {
189+
if strings.HasSuffix(filename, ".tf") {
190+
return hclsyntax.ParseExpression(src, filename, start)
191+
}
192+
193+
if strings.HasSuffix(filename, ".tf.json") {
194+
return nil, hcl.Diagnostics{
195+
&hcl.Diagnostic{
196+
Severity: hcl.DiagError,
197+
Summary: "JSON configuration syntax is not supported",
198+
Subject: &hcl.Range{Filename: filename, Start: start, End: start},
199+
},
200+
}
201+
}
202+
203+
panic(fmt.Sprintf("Unexpected file: %s", filename))
204+
}

tflint/client_test.go

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package tflint
33
import (
44
"encoding/gob"
55
"errors"
6+
"io/ioutil"
67
"net"
78
"net/rpc"
9+
"os"
810
"testing"
911

1012
"github.com/google/go-cmp/cmp"
@@ -19,16 +21,15 @@ type mockServer struct {
1921
}
2022

2123
func (*mockServer) Attributes(req *AttributesRequest, resp *AttributesResponse) error {
22-
expr, diags := hclsyntax.ParseExpression([]byte("1"), "example.tf", hcl.Pos{Line: 1, Column: 1})
23-
if diags.HasErrors() {
24-
*resp = AttributesResponse{Attributes: []*hcl.Attribute{}, Err: diags}
25-
return nil
26-
}
27-
28-
*resp = AttributesResponse{Attributes: []*hcl.Attribute{
24+
*resp = AttributesResponse{Attributes: []*Attribute{
2925
{
3026
Name: req.AttributeName,
31-
Expr: expr,
27+
Expr: []byte("1"),
28+
ExprRange: hcl.Range{
29+
Filename: "example.tf",
30+
Start: hcl.Pos{Line: 1, Column: 1},
31+
End: hcl.Pos{Line: 2, Column: 2},
32+
},
3233
Range: hcl.Range{
3334
Start: hcl.Pos{Line: 1, Column: 1},
3435
End: hcl.Pos{Line: 2, Column: 2},
@@ -109,7 +110,16 @@ func Test_EvaluateExpr(t *testing.T) {
109110
client, server := startMockServer(t)
110111
defer server.Listener.Close()
111112

112-
expr, diags := hclsyntax.ParseExpression([]byte("1"), "example.tf", hcl.Pos{Line: 1, Column: 1})
113+
file, err := ioutil.TempFile("", "tflint-test-evaluateExpr-*.tf")
114+
if err != nil {
115+
t.Fatal(err)
116+
}
117+
defer os.Remove(file.Name())
118+
if _, err := file.Write([]byte("1")); err != nil {
119+
t.Fatal(err)
120+
}
121+
122+
expr, diags := hclsyntax.ParseExpression([]byte("1"), file.Name(), hcl.Pos{Line: 1, Column: 1})
113123
if diags.HasErrors() {
114124
t.Fatal(diags)
115125
}
@@ -136,7 +146,21 @@ func Test_EmitIssue(t *testing.T) {
136146
client, server := startMockServer(t)
137147
defer server.Listener.Close()
138148

139-
if err := client.EmitIssue(&testRule{}, "test", hcl.Range{}, Metadata{}); err != nil {
149+
file, err := ioutil.TempFile("", "tflint-test-evaluateExpr-*.tf")
150+
if err != nil {
151+
t.Fatal(err)
152+
}
153+
defer os.Remove(file.Name())
154+
if _, err := file.Write([]byte("1")); err != nil {
155+
t.Fatal(err)
156+
}
157+
158+
expr, diags := hclsyntax.ParseExpression([]byte("1"), file.Name(), hcl.Pos{Line: 1, Column: 1})
159+
if diags.HasErrors() {
160+
t.Fatal(diags)
161+
}
162+
163+
if err := client.EmitIssue(&testRule{}, file.Name(), hcl.Range{}, Metadata{Expr: expr}); err != nil {
140164
t.Fatal(err)
141165
}
142166
}

0 commit comments

Comments
 (0)