github.com/jpreese/tflint@v0.19.2-0.20200908152133-b01686250fb6/rules/terraformrules/terraform_unused_declaration.go (about)

     1  package terraformrules
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/hashicorp/hcl/v2"
     8  	"github.com/hashicorp/terraform/addrs"
     9  	"github.com/hashicorp/terraform/configs"
    10  	"github.com/hashicorp/terraform/lang"
    11  	"github.com/terraform-linters/tflint/tflint"
    12  )
    13  
    14  // TerraformUnusedDeclarationsRule checks whether variables, data sources, or locals are declared but unused
    15  type TerraformUnusedDeclarationsRule struct{}
    16  
    17  type declarations struct {
    18  	Variables     map[string]*configs.Variable
    19  	DataResources map[string]*configs.Resource
    20  	Locals        map[string]*configs.Local
    21  }
    22  
    23  // NewTerraformUnusedDeclarationsRule returns a new rule
    24  func NewTerraformUnusedDeclarationsRule() *TerraformUnusedDeclarationsRule {
    25  	return &TerraformUnusedDeclarationsRule{}
    26  }
    27  
    28  // Name returns the rule name
    29  func (r *TerraformUnusedDeclarationsRule) Name() string {
    30  	return "terraform_unused_declarations"
    31  }
    32  
    33  // Enabled returns whether the rule is enabled by default
    34  func (r *TerraformUnusedDeclarationsRule) Enabled() bool {
    35  	return false
    36  }
    37  
    38  // Severity returns the rule severity
    39  func (r *TerraformUnusedDeclarationsRule) Severity() string {
    40  	return tflint.WARNING
    41  }
    42  
    43  // Link returns the rule reference link
    44  func (r *TerraformUnusedDeclarationsRule) Link() string {
    45  	return tflint.ReferenceLink(r.Name())
    46  }
    47  
    48  // Check emits issues for any variables, locals, and data sources that are declared but not used
    49  func (r *TerraformUnusedDeclarationsRule) Check(runner *tflint.Runner) error {
    50  	if !runner.TFConfig.Path.IsRoot() {
    51  		// This rule does not evaluate child modules.
    52  		return nil
    53  	}
    54  
    55  	log.Printf("[TRACE] Check `%s` rule for `%s` runner", r.Name(), runner.TFConfigPath())
    56  
    57  	decl := r.declarations(runner.TFConfig.Module)
    58  	err := runner.WalkExpressions(func(expr hcl.Expression) error {
    59  		return r.checkForRefsInExpr(expr, decl)
    60  	})
    61  	if err != nil {
    62  		return err
    63  	}
    64  
    65  	for _, variable := range decl.Variables {
    66  		runner.EmitIssue(
    67  			r,
    68  			fmt.Sprintf(`variable "%s" is declared but not used`, variable.Name),
    69  			variable.DeclRange,
    70  		)
    71  	}
    72  	for _, data := range decl.DataResources {
    73  		runner.EmitIssue(
    74  			r,
    75  			fmt.Sprintf(`data "%s" "%s" is declared but not used`, data.Type, data.Name),
    76  			data.DeclRange,
    77  		)
    78  	}
    79  	for _, local := range decl.Locals {
    80  		runner.EmitIssue(
    81  			r,
    82  			fmt.Sprintf(`local.%s is declared but not used`, local.Name),
    83  			local.DeclRange,
    84  		)
    85  	}
    86  
    87  	return nil
    88  }
    89  
    90  func (r *TerraformUnusedDeclarationsRule) declarations(module *configs.Module) *declarations {
    91  	decl := &declarations{
    92  		Variables:     make(map[string]*configs.Variable, len(module.Variables)),
    93  		DataResources: make(map[string]*configs.Resource, len(module.DataResources)),
    94  		Locals:        make(map[string]*configs.Local, len(module.Locals)),
    95  	}
    96  
    97  	for k, v := range module.Variables {
    98  		decl.Variables[k] = v
    99  	}
   100  	for k, v := range module.DataResources {
   101  		decl.DataResources[k] = v
   102  	}
   103  	for k, v := range module.Locals {
   104  		decl.Locals[k] = v
   105  	}
   106  
   107  	return decl
   108  }
   109  
   110  func (r *TerraformUnusedDeclarationsRule) checkForRefsInExpr(expr hcl.Expression, decl *declarations) error {
   111  	refs, diags := lang.ReferencesInExpr(expr)
   112  	if diags.HasErrors() {
   113  		log.Printf("[DEBUG] Cannot find references in expression, ignoring: %v", diags.Err())
   114  		return nil
   115  	}
   116  
   117  	for _, ref := range refs {
   118  		switch sub := ref.Subject.(type) {
   119  		case addrs.InputVariable:
   120  			delete(decl.Variables, sub.Name)
   121  		case addrs.LocalValue:
   122  			delete(decl.Locals, sub.Name)
   123  		case addrs.Resource:
   124  			delete(decl.DataResources, sub.String())
   125  		case addrs.ResourceInstance:
   126  			delete(decl.DataResources, sub.Resource.String())
   127  		}
   128  	}
   129  
   130  	return nil
   131  }