Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rules to enforce snake_case names #697

Merged
merged 23 commits into from Apr 29, 2020
Merged

Add rules to enforce snake_case names #697

merged 23 commits into from Apr 29, 2020

Conversation

jgeurts
Copy link
Contributor

@jgeurts jgeurts commented Apr 2, 2020

Adds the following rules:

  • terraform_snake_case_data_source_name
  • terraform_snake_case_local_name
  • terraform_snake_case_module_name
  • terraform_snake_case_output_name
  • terraform_snake_case_resource_name
  • terraform_snake_case_variable_name

Please let me know if anything needs changing or if these rules would be more appropriate elsewhere.

@wata727
Copy link
Member

wata727 commented Apr 4, 2020

Hi @jgeurts! Thank you for your contribution.

It's so great, but these rules look very similar to existing terraform_dash_in_* rules.
https://github.com/terraform-linters/tflint/blob/master/docs/rules/terraform_dash_in_data_source_name.md

I think it is better to make these rules into one rule with configurable style options. For instance, terraform_module_pinned_source rule has a style option to select enforced style.
https://github.com/terraform-linters/tflint/blob/master/docs/rules/terraform_module_pinned_source.md

What do you think?

@jgeurts
Copy link
Contributor Author

jgeurts commented Apr 4, 2020

Sounds good! I'm curious to hear your thoughts on the following name/options:

Name rule terraform_naming_convention with the following options:

Name Default Value
enabled true Boolean
options { selector = "default", format = "snake_case" } Array

Options

Array of configuration options:

Name Default Value
selector default default, data, local, module, output, resource, variable
format snake_case snake_case, UPPER_CASE, camelCase, strictCamelCase, PascalCase, StrictPascalCase, kebab-case, null
custom null { regex = String, match = Boolean }, null

format

The format option defines the allowed formats for the identifier. This option accepts an array of the following values, and the identifier can match any of them:

  • camelCase - standard camelCase format - no underscores are allowed between characters, and consecutive capitals are allowed (i.e. both myID and myId are valid).
  • strictCamelCase - same as camelCase, but consecutive capitals are not allowed (i.e. myId is valid, but myID is not).
  • PascalCase - same as camelCase, except the first character must be upper-case.
  • StrictPascalCase - same as strictCamelCase, except the first character must be upper-case.
  • snake_case - standard snake_case format - all characters must be lower-case, and underscores are allowed.
  • UPPER_CASE - same as snake_case, except all characters must be upper-case.
  • kebab-case - all characters must be lower-case, and dashes are allowed.
  • null - signifies "this selector shall not have its format checked".
    This can be useful if you want to enforce no particular format for a specific selector, after applying a group selector.

custom

The custom option defines a custom regex that the identifier must (or must not) match. This option allows you to have a bit more finer-grained control over identifiers, letting you ban (or force) certain patterns and substrings.
Accepts an object with the following properties:

  • regex - String representing a golang regular expression
  • match - true if the identifier must match the regex, false if the identifier must not match the regex.

Examples:

Default - enforce snake_case for everything

rule "terraform_naming_convention" {
  enabled = true
}

Override for specific block type

rule "terraform_naming_convention" {
  enabled = true
  options = [
    {
      selector = "default"
      format = "snake_case"
    },
    {
      selector = "module"
      format = "kebab-case"
    }
  ]
}

Disable for specific block type

rule "terraform_naming_convention" {
  enabled = true
  options = [
    {
      selector = "default"
      format = "snake_case"
    },
    {
      selector = "output"
      format = null
    }
  ]
}

Custom format

rule "terraform_naming_convention" {
  enabled = true
  options = [
    {
      selector = "default"
      custom = {
        regex = "^[a-zA-Z_-]{2,}$"
        match = true
      }
    }
  ]
}

@wata727
Copy link
Member

wata727 commented Apr 5, 2020

It sounds good, but I have some suggestions.

First, I'm thinking whether this rule should be disabled by default. Styling issues often do not affect the actual Terraform workflow. I believe it is best to minimize the issues that appear by default.

Second, the configuration language would be smarter in the following format:

rule "terraform_naming_convetion" {
  enabled = true

  default {
    format = "snake_case"
  }

  resource {
    custom {
      regex = "^[a-zA-Z_-]{2,}$"
      match = true
    }
  }
}

Finally, I have two small concerns:

  • There is no alternative option for terraform_dash_in_* rules. I think snake_case is an alternative in many cases, but I'm not sure that I can follow all users...
  • I'm not sure if we need to support this many format options. For example, kebab-case clearly violates best practice.

@jgeurts
Copy link
Contributor Author

jgeurts commented Apr 5, 2020

Ah nice, I like that config language alternative! Think we would need the default wrapper? For example:

rule "terraform_naming_convention" {
  enabled = true

  format = "snake_case"

  resource {
    custom {
      regex = "^[a-zA-Z_-]{2,}$"
      match = true
    }
  }
}

I’m not sure I understand the first bullet point for an alternative option. Could you explain that a bit more?

I’m good to remove options from format. It could go with only snake_case to begin with unless you’d like any others.

@wata727
Copy link
Member

wata727 commented Apr 5, 2020

Think we would need the default wrapper?

Good suggestion. Your example is cooler than mine 👍

I’m not sure I understand the first bullet point for an alternative option. Could you explain that a bit more?

For example, the terraform_dash_in_* rules simply check that dash is not included in the name, so names like example_FooBar are also allowed. When removing the terraform_dash_in_* rules and integrating them into the terraform_naming_convention rule, it is not possible to enforce names that do not contain dashes while allowing such names. We will need to provide a format like no_dash to support this.

I’m good to remove options from format. It could go with only snake_case to begin with unless you’d like any others.

This might be good. Unless someone needs another format, minimal implementation would be good.

@jgeurts
Copy link
Contributor Author

jgeurts commented Apr 6, 2020

Sounds good, I'll work on those changes and will comment back here when I have something to review!

@jgeurts
Copy link
Contributor Author

jgeurts commented Apr 10, 2020

@wata727 I could use some help, if/when you have some time. This is my first time working with go, so I welcome a strong critique of the code. I'm not familiar with many golang conventions or approaches to keep things concise.

The main part I'm struggling with is how to get the tests to recognize my rule configuration. I have a handful of tests stubbed out for data block naming conventions, but I'm getting errors like Unsupported argument; An argument named "format" is not expected here. or Unsupported argument; An argument named "custom" is not expected here.. Both of which relate to the syntax for the rule.

Any advice would be appreciated!

type TerraformNamingConventionRule struct{}

type terraformNamingConventionRuleConfig struct {
BlockFormatConfig
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

format and custom tags are not recognized when embedding a struct. It needs to be modified as follows:

Suggested change
BlockFormatConfig
Format string `hcl:"format,optional"`
Custom string `hcl:"custom,optional"`

By the way, embedding of Data etc. works correctly because the hcl:"data,block" tag is recognized to the struct itself.

@jgeurts
Copy link
Contributor Author

jgeurts commented Apr 12, 2020

@wata727 Ok, this should be ready for review. Thank you for your help with the changes to this and with my golang programming!

Copy link
Member

@wata727 wata727 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! I have left some comments so please take a look :)

@@ -0,0 +1,238 @@
# terraform_snake_case_data_source_name
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The title seems to remain old.


## How To Fix

Use lower-case characters and separate words with underscores (`_`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also seems to be old. The way to fix will depend on the rule configuration.


* `snake_case` - standard snake_case format - all characters must be lower-case, and underscores are allowed.
* `mixed_snake_case` - modified snake_case format - characters may be upper or lower case, and underscores are allowed.
* `""` - empty string - signifies "this selector shall not have its format checked".
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[IMO] I guess that making an empty string meaningful is a bit counterintuitive. It would be nice to assign a dedicated word such as "none", what do you think?


Name | Default | Value
--- | --- | ---
enabled | `true` | Boolean
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

false?


// Severity returns the rule severity
func (r *TerraformNamingConventionRule) Severity() string {
return tflint.WARNING
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[IMO] I think that the "notice" is appropriate for violating the naming convention. What do you think?

nameValidator := &NameValidator{
IsNamedFormat: false,
Format: custom,
Regexp: regexp.MustCompile(custom),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MustCompile panics if the custom pattern cannot be parsed.
https://golang.org/pkg/regexp/#MustCompile

nameValidator := &NameValidator{
IsNamedFormat: true,
Format: format,
Regexp: regexp.MustCompile("^[a-z][a-z0-9]*(_[a-z0-9]+)*$"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can reduce the cost of compiling each time by compiling these pre-defined regexp as package global variables. In this case, it would be good to use MustCompile.

@mveitas
Copy link
Contributor

mveitas commented Apr 25, 2020

The rule needs to be added to the providers.go as well: https://github.com/terraform-linters/tflint/blob/master/rules/provider.go#L23

@jgeurts
Copy link
Contributor Author

jgeurts commented Apr 25, 2020

@wata727 and @mveitas The latest commits should address all of the review comments. Please let me know if I missed anything or if I can make any other changes to this!

}

// Actually run any checks for modules
if dataNameValidator != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might make sense to move the logic if handling each of the block types into their own methods

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion, I moved the code to separate methods. I'd like to make things a bit more concise, as there's a fair amount of duplication, but I don't think my go abilities & available time allow for that.

@jgeurts
Copy link
Contributor Author

jgeurts commented Apr 27, 2020

Not sure how to kick off the build action again.

GitHub Actions has encountered an internal error when running your job.

@wata727
Copy link
Member

wata727 commented Apr 29, 2020

Not sure how to kick off the build action again.

Rerunned.

Copy link
Member

@wata727 wata727 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! Thank you for a great job!

@wata727 wata727 merged commit 7e6b34b into terraform-linters:master Apr 29, 2020
@jgeurts jgeurts deleted the snake_case_rules branch April 29, 2020 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants