diff --git a/rules/terraform_unused_declarations.go b/rules/terraform_unused_declarations.go index dc490ce..5d1ddaf 100644 --- a/rules/terraform_unused_declarations.go +++ b/rules/terraform_unused_declarations.go @@ -119,7 +119,19 @@ func (r *TerraformUnusedDeclarationsRule) declarations(runner *terraform.Runner) { Type: "variable", LabelNames: []string{"name"}, - Body: &hclext.BodySchema{}, + Body: &hclext.BodySchema{ + Blocks: []hclext.BlockSchema{ + { + Type: "validation", + Body: &hclext.BodySchema{ + Attributes: []hclext.AttributeSchema{ + {Name: "condition"}, + {Name: "error_message"}, + }, + }, + }, + }, + }, }, { Type: "data", @@ -150,9 +162,21 @@ func (r *TerraformUnusedDeclarationsRule) declarations(runner *terraform.Runner) } func (r *TerraformUnusedDeclarationsRule) checkForRefsInExpr(expr hcl.Expression, decl *declarations) { +ReferenceLoop: for _, ref := range lang.ReferencesInExpr(expr) { switch sub := ref.Subject.(type) { case addrs.InputVariable: + // Input variables can refer to themselves as var.NAME inside validation blocks. + // Do not mark such expressions as used, skip to next reference. + if varBlock, exists := decl.Variables[sub.Name]; exists { + for _, validationBlock := range varBlock.Body.Blocks { + for _, attr := range validationBlock.Body.Attributes { + if attr.Expr.Range().Overlaps(expr.Range()) { + continue ReferenceLoop + } + } + } + } delete(decl.Variables, sub.Name) case addrs.LocalValue: delete(decl.Locals, sub.Name) diff --git a/rules/terraform_unused_declarations_test.go b/rules/terraform_unused_declarations_test.go index f66bdff..f79262e 100644 --- a/rules/terraform_unused_declarations_test.go +++ b/rules/terraform_unused_declarations_test.go @@ -179,6 +179,30 @@ output "d" { `, Expected: helper.Issues{}, }, + { + Name: "variable used in validation block", + Content: ` +variable "unused" { + validation { + condition = var.unused != "" + error_message = "variable should be empty string. got: ${var.unused}" + } +} +`, + Expected: helper.Issues{ + { + Rule: NewTerraformUnusedDeclarationsRule(), + Message: `variable "unused" is declared but not used`, + Range: hcl.Range{ + Filename: "config.tf", + Start: hcl.Pos{Line: 2, Column: 1}, + End: hcl.Pos{Line: 2, Column: 18}, + }, + }, + }, + Fixed: ` +`, + }, { Name: "json", JSON: true,