diff --git a/helper/runner.go b/helper/runner.go index 3cc1d4f..0517543 100644 --- a/helper/runner.go +++ b/helper/runner.go @@ -5,6 +5,7 @@ import ( "os" "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/terraform-linters/tflint-plugin-sdk/hclext" "github.com/terraform-linters/tflint-plugin-sdk/terraform/addrs" "github.com/terraform-linters/tflint-plugin-sdk/tflint" @@ -124,6 +125,51 @@ func (r *Runner) GetFiles() (map[string]*hcl.File, error) { return r.files, nil } +type nativeWalker struct { + walker tflint.ExprWalker +} + +func (w *nativeWalker) Enter(node hclsyntax.Node) hcl.Diagnostics { + if expr, ok := node.(hcl.Expression); ok { + return w.walker.Enter(expr) + } + return nil +} + +func (w *nativeWalker) Exit(node hclsyntax.Node) hcl.Diagnostics { + if expr, ok := node.(hcl.Expression); ok { + return w.walker.Exit(expr) + } + return nil +} + +func (r *Runner) WalkExpressions(walker tflint.ExprWalker) hcl.Diagnostics { + diags := hcl.Diagnostics{} + for _, file := range r.files { + if body, ok := file.Body.(*hclsyntax.Body); ok { + walkDiags := hclsyntax.Walk(body, &nativeWalker{walker: walker}) + diags = diags.Extend(walkDiags) + continue + } + + // In JSON syntax, everything can be walked as an attribute. + attrs, jsonDiags := file.Body.JustAttributes() + if jsonDiags.HasErrors() { + diags = diags.Extend(jsonDiags) + continue + } + + for _, attr := range attrs { + enterDiags := walker.Enter(attr.Expr) + diags = diags.Extend(enterDiags) + exitDiags := walker.Exit(attr.Expr) + diags = diags.Extend(exitDiags) + } + } + + return diags +} + // DecodeRuleConfig extracts the rule's configuration into the given value func (r *Runner) DecodeRuleConfig(name string, ret interface{}) error { schema := hclext.ImpliedBodySchema(ret) diff --git a/plugin/plugin2host/client.go b/plugin/plugin2host/client.go index 59ba460..00b588b 100644 --- a/plugin/plugin2host/client.go +++ b/plugin/plugin2host/client.go @@ -165,6 +165,62 @@ func (c *GRPCClient) GetFiles() (map[string]*hcl.File, error) { return files, nil } +type nativeWalker struct { + walker tflint.ExprWalker +} + +func (w *nativeWalker) Enter(node hclsyntax.Node) hcl.Diagnostics { + if expr, ok := node.(hcl.Expression); ok { + return w.walker.Enter(expr) + } + return nil +} + +func (w *nativeWalker) Exit(node hclsyntax.Node) hcl.Diagnostics { + if expr, ok := node.(hcl.Expression); ok { + return w.walker.Exit(expr) + } + return nil +} + +func (c *GRPCClient) WalkExpressions(walker tflint.ExprWalker) hcl.Diagnostics { + files, err := c.GetFiles() + if err != nil { + return hcl.Diagnostics{ + { + Severity: hcl.DiagError, + Summary: "failed to call GetFiles()", + Detail: err.Error(), + }, + } + } + + diags := hcl.Diagnostics{} + for _, file := range files { + if body, ok := file.Body.(*hclsyntax.Body); ok { + walkDiags := hclsyntax.Walk(body, &nativeWalker{walker: walker}) + diags = diags.Extend(walkDiags) + continue + } + + // In JSON syntax, everything can be walked as an attribute. + attrs, jsonDiags := file.Body.JustAttributes() + if jsonDiags.HasErrors() { + diags = diags.Extend(jsonDiags) + continue + } + + for _, attr := range attrs { + enterDiags := walker.Enter(attr.Expr) + diags = diags.Extend(enterDiags) + exitDiags := walker.Exit(attr.Expr) + diags = diags.Extend(exitDiags) + } + } + + return diags +} + // DecodeRuleConfig guesses the schema of the rule config from the passed interface and sends the schema to GRPC server. // Content retrieved based on the schema is decoded into the passed interface. func (c *GRPCClient) DecodeRuleConfig(name string, ret interface{}) error { diff --git a/tflint/interface.go b/tflint/interface.go index 4bc4deb..877e477 100644 --- a/tflint/interface.go +++ b/tflint/interface.go @@ -108,6 +108,8 @@ type Runner interface { // This is low level API for accessing information such as comments and syntax. GetFiles() (map[string]*hcl.File, error) + WalkExpressions(walker ExprWalker) hcl.Diagnostics + // DecodeRuleConfig fetches the rule's configuration and reflects the result in the 2nd argument. // The argument is expected to be a pointer to a structure tagged with hclext: // diff --git a/tflint/walker.go b/tflint/walker.go new file mode 100644 index 0000000..5d2b6c0 --- /dev/null +++ b/tflint/walker.go @@ -0,0 +1,20 @@ +package tflint + +import ( + "github.com/hashicorp/hcl/v2" +) + +type ExprWalker interface { + Enter(expr hcl.Expression) hcl.Diagnostics + Exit(expr hcl.Expression) hcl.Diagnostics +} + +type ExprWalkFunc func(expr hcl.Expression) hcl.Diagnostics + +func (f ExprWalkFunc) Enter(expr hcl.Expression) hcl.Diagnostics { + return f(expr) +} + +func (f ExprWalkFunc) Exit(expr hcl.Expression) hcl.Diagnostics { + return nil +}