diff --git a/README.md b/README.md index 2bdb47e..daadbc1 100644 --- a/README.md +++ b/README.md @@ -533,6 +533,7 @@ List of all available rules. The rules ported from `golint` are left unchanged a | [`datarace`](./RULES_DESCRIPTIONS.md#datarace) | n/a | Spots potential dataraces | no | no | | [`comment-spacings`](./RULES_DESCRIPTIONS.md#comment-spacings) | []string | Warns on malformed comments | no | no | | [`redundant-import-alias`](./RULES_DESCRIPTIONS.md#redundant-import-alias) | n/a | Warns on import aliases matching the imported package name | no | no | +| [`import-alias-naming`](./RULES_DESCRIPTIONS.md#import-alias-naming) | string (defaults to ^[a-z][a-z0-9]{0,}$) | Conventions around the naming of import aliases. | no | no | ## Configurable rules diff --git a/RULES_DESCRIPTIONS.md b/RULES_DESCRIPTIONS.md index 3d78110..4561e20 100644 --- a/RULES_DESCRIPTIONS.md +++ b/RULES_DESCRIPTIONS.md @@ -44,6 +44,7 @@ List of all available rules. - [indent-error-flow](#indent-error-flow) - [imports-blacklist](#imports-blacklist) - [import-shadowing](#import-shadowing) + - [import-alias-naming](#import-alias-naming) - [line-length-limit](#line-length-limit) - [max-public-structs](#max-public-structs) - [modifies-parameter](#modifies-parameter) @@ -489,6 +490,22 @@ name of an imported package. This rule spots identifiers that shadow an import. _Configuration_: N/A +## import-alias-naming + +_Description_: Aligns with Go's naming conventions, as outlined in the official +[blog post](https://go.dev/blog/package-names). It enforces clear and lowercase import alias names, echoing +the principles of good package naming. Users can follow these guidelines by default or define a custom regex rule. +Importantly, aliases with underscores ("_") are always allowed. + +_Configuration_: (string) regular expression to match the aliases (default: ^[a-z][a-z0-9]{0,}$) + +Example: + +```toml +[rule.import-alias-naming] + arguments =["^[a-z][a-z0-9]{0,}$"] +``` + ## line-length-limit _Description_: Warns in the presence of code lines longer than a configured maximum. diff --git a/config/config.go b/config/config.go index 7643d27..4385459 100644 --- a/config/config.go +++ b/config/config.go @@ -88,6 +88,7 @@ var allRules = append([]lint.Rule{ &rule.CommentSpacingsRule{}, &rule.IfReturnRule{}, &rule.RedundantImportAlias{}, + &rule.ImportAliasNamingRule{}, }, defaultRules...) var allFormatters = []lint.Formatter{ diff --git a/rule/import-alias-naming.go b/rule/import-alias-naming.go new file mode 100644 index 0000000..292392b --- /dev/null +++ b/rule/import-alias-naming.go @@ -0,0 +1,79 @@ +package rule + +import ( + "fmt" + "regexp" + "sync" + + "github.com/mgechev/revive/lint" +) + +// ImportAliasNamingRule lints import alias naming. +type ImportAliasNamingRule struct { + configured bool + namingRuleRegexp *regexp.Regexp + sync.Mutex +} + +const defaultNamingRule = "^[a-z][a-z0-9]{0,}$" + +var defaultNamingRuleRegexp = regexp.MustCompile(defaultNamingRule) + +func (r *ImportAliasNamingRule) configure(arguments lint.Arguments) { + r.Lock() + defer r.Unlock() + if r.configured { + return + } + + if len(arguments) < 1 { + r.namingRuleRegexp = defaultNamingRuleRegexp + return + } + + namingRule, ok := arguments[0].(string) // Alt. non panicking version + if !ok { + panic(fmt.Sprintf("Invalid argument '%v' for 'import-alias-naming' rule. Expecting string, got %T", arguments[0], arguments[0])) + } + + var err error + r.namingRuleRegexp, err = regexp.Compile(namingRule) + if err != nil { + panic(fmt.Sprintf("Invalid argument to the import-alias-naming rule. Expecting %q to be a valid regular expression, got: %v", namingRule, err)) + } +} + +// Apply applies the rule to given file. +func (r *ImportAliasNamingRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + r.configure(arguments) + + var failures []lint.Failure + + for _, is := range file.AST.Imports { + path := is.Path + if path == nil { + continue + } + + alias := is.Name + if alias == nil || alias.Name == "_" { + continue + } + + if !r.namingRuleRegexp.MatchString(alias.Name) { + failures = append(failures, lint.Failure{ + Confidence: 1, + Failure: fmt.Sprintf("import name (%s) must match the regular expression: %s", alias.Name, r.namingRuleRegexp.String()), + Node: alias, + Category: "imports", + }) + } + } + + return failures +} + +// Name returns the rule name. +func (*ImportAliasNamingRule) Name() string { + return "import-alias-naming" +} diff --git a/test/import-alias-naming_test.go b/test/import-alias-naming_test.go new file mode 100644 index 0000000..e772a09 --- /dev/null +++ b/test/import-alias-naming_test.go @@ -0,0 +1,15 @@ +package test + +import ( + "testing" + + "github.com/mgechev/revive/lint" + "github.com/mgechev/revive/rule" +) + +func TestImportAliasNaming(t *testing.T) { + testRule(t, "import-alias-naming", &rule.ImportAliasNamingRule{}) + testRule(t, "import-alias-naming-custom-config", &rule.ImportAliasNamingRule{}, &lint.RuleConfig{ + Arguments: []any{`^[a-z]+$`}, + }) +} diff --git a/testdata/import-alias-naming-custom-config.go b/testdata/import-alias-naming-custom-config.go new file mode 100644 index 0000000..7fc4d55 --- /dev/null +++ b/testdata/import-alias-naming-custom-config.go @@ -0,0 +1,16 @@ +package fixtures + +import ( + _ "strings" + bar_foo "strings" // MATCH /import name (bar_foo) must match the regular expression: ^[a-z]+$/ + fooBAR "strings" // MATCH /import name (fooBAR) must match the regular expression: ^[a-z]+$/ + v1 "strings" // MATCH /import name (v1) must match the regular expression: ^[a-z]+$/ + magical "magic/hat" +) + +func somefunc() { + fooBAR.Clone("") + bar_foo.Clone("") + v1.Clone("") + magical.Clone("") +} diff --git a/testdata/import-alias-naming.go b/testdata/import-alias-naming.go new file mode 100644 index 0000000..6e2c339 --- /dev/null +++ b/testdata/import-alias-naming.go @@ -0,0 +1,16 @@ +package fixtures + +import ( + _ "strings" + bar_foo "strings" // MATCH /import name (bar_foo) must match the regular expression: ^[a-z][a-z0-9]{0,}$/ + fooBAR "strings" // MATCH /import name (fooBAR) must match the regular expression: ^[a-z][a-z0-9]{0,}$/ + v1 "strings" + magical "magic/hat" +) + +func somefunc() { + fooBAR.Clone("") + bar_foo.Clone("") + v1.Clone("") + magical.Clone("") +}