-
Notifications
You must be signed in to change notification settings - Fork 349
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add new rule to check comparisons with empty list
- Loading branch information
1 parent
642c99c
commit 2b18257
Showing
6 changed files
with
219 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# terraform_empty_list_check | ||
|
||
Disallow comparisons with `[]` when checking if a list is empty. | ||
|
||
## Example | ||
|
||
```hcl | ||
variable "my_list" { | ||
type = list(string) | ||
} | ||
resource "aws_db_instance" "mysql" { | ||
count = var.my_list == [] ? 0 : 1 | ||
instance_class = "m4.2xlarge" | ||
} | ||
``` | ||
|
||
``` | ||
$ tflint | ||
1 issue(s) found: | ||
Warning: List is compared with [] instead of checking if length is 0. (terraform_empty_list_check) | ||
on test.tf line 5: | ||
5: count = var.my_list == [] ? 0 : 1 | ||
Reference: https://github.com/terraform-linters/tflint/blob/master/docs/rules/terraform_empty_list_check.md | ||
``` | ||
|
||
## Why | ||
|
||
The `==` operator can only return true when the two operands have identical types, and the type of `[]` alone (without any further type conversions) is an empty tuple rather than a list of objects, strings, numbers or any other type. Therefore, a comparison with a single `[]` with the goal of checking if a list is empty, will always return false. | ||
|
||
## How To Fix | ||
|
||
Check if a list is empty by checking its length instead. For example: `length(var.my_list) == 0`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package terraformrules | ||
|
||
import ( | ||
"log" | ||
|
||
"github.com/hashicorp/hcl/v2" | ||
"github.com/hashicorp/hcl/v2/hclsyntax" | ||
"github.com/terraform-linters/tflint/tflint" | ||
) | ||
|
||
// TerraformEmptyListCheckRule checks whether is there a comparison with an empty list | ||
type TerraformEmptyListCheckRule struct{} | ||
|
||
// NewTerraformCommentSyntaxRule returns a new rule | ||
func NewTerraformEmptyListCheckRule() *TerraformEmptyListCheckRule { | ||
return &TerraformEmptyListCheckRule{} | ||
} | ||
|
||
// Name returns the rule name | ||
func (r *TerraformEmptyListCheckRule) Name() string { | ||
return "terraform_empty_list_check" | ||
} | ||
|
||
// Enabled returns whether the rule is enabled by default | ||
func (r *TerraformEmptyListCheckRule) Enabled() bool { | ||
return true | ||
} | ||
|
||
// Severity returns the rule severity | ||
func (r *TerraformEmptyListCheckRule) Severity() tflint.Severity { | ||
return tflint.WARNING | ||
} | ||
|
||
// Link returns the rule reference link | ||
func (r *TerraformEmptyListCheckRule) Link() string { | ||
return tflint.ReferenceLink(r.Name()) | ||
} | ||
|
||
// Check checks whether the list is being compared with static empty list | ||
func (r *TerraformEmptyListCheckRule) Check(runner *tflint.Runner) error { | ||
if !runner.TFConfig.Path.IsRoot() { | ||
// This rule does not evaluate child modules. | ||
return nil | ||
} | ||
|
||
log.Printf("[TRACE] Check `%s` rule for `%s` runner", r.Name(), runner.TFConfigPath()) | ||
|
||
files := make(map[string]*struct{}) | ||
for _, variable := range runner.TFConfig.Module.Variables { | ||
files[variable.DeclRange.Filename] = nil | ||
} | ||
|
||
for filename := range files { | ||
if err := r.checkEmptyList(runner, filename); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (r *TerraformEmptyListCheckRule) checkEmptyList(runner *tflint.Runner, filename string) error { | ||
return runner.WalkExpressions(func(expr hcl.Expression) error { | ||
if conditionalExpr, ok := expr.(*hclsyntax.ConditionalExpr); ok { | ||
if binaryOpExpr, ok := conditionalExpr.Condition.(*hclsyntax.BinaryOpExpr); ok { | ||
if binaryOpExpr.Op.Type.FriendlyName() == "bool" { | ||
if right, ok := binaryOpExpr.RHS.(*hclsyntax.TupleConsExpr); ok { | ||
checkEmptyList(right, runner, r, binaryOpExpr) | ||
} | ||
if left, ok := binaryOpExpr.LHS.(*hclsyntax.TupleConsExpr); ok { | ||
checkEmptyList(left, runner, r, binaryOpExpr) | ||
} | ||
} | ||
} | ||
} | ||
return nil | ||
}) | ||
} | ||
|
||
func checkEmptyList(tupleConsExpr *hclsyntax.TupleConsExpr, runner *tflint.Runner, r *TerraformEmptyListCheckRule, binaryOpExpr *hclsyntax.BinaryOpExpr) { | ||
if len(tupleConsExpr.Exprs) == 0 { | ||
runner.EmitIssue( | ||
r, | ||
"List is compared with [] instead of checking if length is 0.", | ||
binaryOpExpr.Range(), | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package terraformrules | ||
|
||
import ( | ||
"testing" | ||
|
||
hcl "github.com/hashicorp/hcl/v2" | ||
"github.com/terraform-linters/tflint/tflint" | ||
) | ||
|
||
func Test_TerraformEmptyListCheckRule(t *testing.T) { | ||
cases := []struct { | ||
Name string | ||
Content string | ||
Expected tflint.Issues | ||
}{ | ||
{ | ||
Name: "comparing with [] is not recommended", | ||
Content: ` | ||
variable "my_list" { | ||
type = list(string) | ||
} | ||
resource "aws_db_instance" "mysql" { | ||
count = var.my_list == [] ? 0 : 1 | ||
instance_class = "m4.2xlarge" | ||
}`, | ||
Expected: tflint.Issues{ | ||
{ | ||
Rule: NewTerraformEmptyListCheckRule(), | ||
Message: "List is compared with [] instead of checking if length is 0.", | ||
Range: hcl.Range{ | ||
Filename: "resource.tf", | ||
Start: hcl.Pos{Line: 6, Column: 10}, | ||
End: hcl.Pos{Line: 6, Column: 27}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
Name: "negatively comparing with [] is not recommended", | ||
Content: ` | ||
variable "my_list" { | ||
type = list(string) | ||
} | ||
resource "aws_db_instance" "mysql" { | ||
count = var.my_list != [] ? 1 : 0 | ||
instance_class = "m4.2xlarge" | ||
}`, | ||
Expected: tflint.Issues{ | ||
{ | ||
Rule: NewTerraformEmptyListCheckRule(), | ||
Message: "List is compared with [] instead of checking if length is 0.", | ||
Range: hcl.Range{ | ||
Filename: "resource.tf", | ||
Start: hcl.Pos{Line: 6, Column: 10}, | ||
End: hcl.Pos{Line: 6, Column: 27}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
Name: "checking if length is 0 is recommended", | ||
Content: ` | ||
variable "my_list" { | ||
type = list(string) | ||
} | ||
resource "aws_db_instance" "mysql" { | ||
count = length(var.my_list) == 0 ? 1 : 0 | ||
instance_class = "m4.2xlarge" | ||
}`, | ||
Expected: tflint.Issues{}, | ||
}, | ||
} | ||
|
||
rule := NewTerraformEmptyListCheckRule() | ||
|
||
for _, tc := range cases { | ||
runner := tflint.TestRunner(t, map[string]string{"resource.tf": tc.Content}) | ||
|
||
if err := rule.Check(runner); err != nil { | ||
t.Fatalf("Unexpected error occurred: %s", err) | ||
} | ||
|
||
tflint.AssertIssues(t, tc.Expected, runner.Issues) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
variable "my_list" { | ||
type = list(string) | ||
} | ||
resource "aws_db_instance" "mysql" { | ||
count = var.my_list == [] ? 0 : 1 | ||
instance_class = "m4.2xlarge" | ||
} |