2018-01-21 18:04:41 -08:00
package rule
2017-11-26 19:28:18 -08:00
import (
"fmt"
"go/ast"
"go/token"
2025-04-24 14:29:39 +02:00
"path/filepath"
2017-11-26 19:28:18 -08:00
"strings"
2025-04-24 14:29:39 +02:00
"sync"
2017-11-26 19:28:18 -08:00
2025-05-26 13:18:38 +02:00
"github.com/mgechev/revive/internal/astutils"
2025-07-11 14:45:38 +03:00
"github.com/mgechev/revive/internal/rule"
2018-01-24 15:44:03 -08:00
"github.com/mgechev/revive/lint"
2017-11-26 19:28:18 -08:00
)
2024-11-19 23:58:14 +02:00
var knownNameExceptions = map [ string ] bool {
"LastInsertId" : true , // must match database/sql
"kWh" : true ,
}
2025-04-16 12:30:28 +03:00
// defaultBadPackageNames is the list of "bad" package names from https://go.dev/wiki/CodeReviewComments#package-names
// and https://go.dev/blog/package-names#bad-package-names.
// The rule warns about the usage of any package name in this list if skipPackageNameChecks is false.
// Values in the list should be lowercased.
var defaultBadPackageNames = map [ string ] struct { } {
"common" : { } ,
"interfaces" : { } ,
"misc" : { } ,
"types" : { } ,
"util" : { } ,
"utils" : { } ,
}
2024-12-01 17:44:41 +02:00
// VarNamingRule lints the name of a variable.
2021-10-17 20:34:48 +02:00
type VarNamingRule struct {
2025-07-11 14:45:38 +03:00
allowList [ ] string
blockList [ ] string
skipInitialismNameChecks bool // if true disable enforcing capitals for common initialisms
2025-04-16 12:30:28 +03:00
allowUpperCaseConst bool // if true - allows to use UPPER_SOME_NAMES for constants
skipPackageNameChecks bool // check for meaningless and user-defined bad package names
extraBadPackageNames map [ string ] struct { } // inactive if skipPackageNameChecks is false
2025-04-24 14:29:39 +02:00
pkgNameAlreadyChecked syncSet // set of packages names already checked
2021-10-17 20:34:48 +02:00
}
2017-11-26 19:28:18 -08:00
2024-12-13 21:38:46 +01:00
// Configure validates the rule configuration, and configures the rule accordingly.
//
// Configuration implements the [lint.ConfigurableRule] interface.
func ( r * VarNamingRule ) Configure ( arguments lint . Arguments ) error {
2025-04-24 14:29:39 +02:00
r . pkgNameAlreadyChecked = syncSet { elements : map [ string ] struct { } { } }
2023-08-12 13:45:42 +07:00
if len ( arguments ) >= 1 {
2024-12-11 19:35:58 +01:00
list , err := getList ( arguments [ 0 ] , "allowlist" )
if err != nil {
return err
}
r . allowList = list
2023-08-12 13:45:42 +07:00
}
2023-07-31 12:09:38 +05:00
2023-08-12 13:45:42 +07:00
if len ( arguments ) >= 2 {
2024-12-11 19:35:58 +01:00
list , err := getList ( arguments [ 1 ] , "blocklist" )
if err != nil {
return err
}
r . blockList = list
2023-08-12 13:45:42 +07:00
}
if len ( arguments ) >= 3 {
// not pretty code because should keep compatibility with TOML (no mixed array types) and new map parameters
thirdArgument := arguments [ 2 ]
2023-09-24 08:44:02 +02:00
asSlice , ok := thirdArgument . ( [ ] any )
2023-08-12 13:45:42 +07:00
if ! ok {
2024-12-11 19:35:58 +01:00
return fmt . Errorf ( "invalid third argument to the var-naming rule. Expecting a %s of type slice, got %T" , "options" , arguments [ 2 ] )
2023-08-12 13:45:42 +07:00
}
if len ( asSlice ) != 1 {
2024-12-11 19:35:58 +01:00
return fmt . Errorf ( "invalid third argument to the var-naming rule. Expecting a %s of type slice, of len==1, but %d" , "options" , len ( asSlice ) )
2023-08-12 13:45:42 +07:00
}
2023-09-24 08:44:02 +02:00
args , ok := asSlice [ 0 ] . ( map [ string ] any )
2023-08-12 13:45:42 +07:00
if ! ok {
2024-12-11 19:35:58 +01:00
return fmt . Errorf ( "invalid third argument to the var-naming rule. Expecting a %s of type slice, of len==1, with map, but %T" , "options" , asSlice [ 0 ] )
2023-07-31 12:09:38 +05:00
}
2025-03-28 01:34:20 -07:00
for k , v := range args {
switch {
2025-07-11 14:45:38 +03:00
case isRuleOption ( k , "skipInitialismNameChecks" ) :
r . skipInitialismNameChecks = fmt . Sprint ( v ) == "true"
2025-03-28 01:34:20 -07:00
case isRuleOption ( k , "upperCaseConst" ) :
r . allowUpperCaseConst = fmt . Sprint ( v ) == "true"
case isRuleOption ( k , "skipPackageNameChecks" ) :
r . skipPackageNameChecks = fmt . Sprint ( v ) == "true"
2025-04-16 12:30:28 +03:00
case isRuleOption ( k , "extraBadPackageNames" ) :
2025-06-16 22:04:53 +02:00
extraBadPackageNames , ok := v . ( [ ] any )
2025-04-16 12:30:28 +03:00
if ! ok {
return fmt . Errorf ( "invalid third argument to the var-naming rule. Expecting extraBadPackageNames of type slice of strings, but %T" , v )
}
2025-06-20 14:03:56 +03:00
for i , name := range extraBadPackageNames {
2025-04-16 12:30:28 +03:00
if r . extraBadPackageNames == nil {
r . extraBadPackageNames = map [ string ] struct { } { }
}
2025-06-20 14:03:56 +03:00
n , ok := name . ( string )
if ! ok {
return fmt . Errorf ( "invalid third argument to the var-naming rule: expected element %d of extraBadPackageNames to be a string, but got %v(%T)" , i , name , name )
}
r . extraBadPackageNames [ strings . ToLower ( n ) ] = struct { } { }
2025-04-16 12:30:28 +03:00
}
2025-03-28 01:34:20 -07:00
}
}
2018-09-15 14:33:28 -07:00
}
2024-12-11 19:35:58 +01:00
return nil
2022-04-10 09:06:59 +02:00
}
// Apply applies the rule to given file.
2024-12-13 21:38:46 +01:00
func ( r * VarNamingRule ) Apply ( file * lint . File , _ lint . Arguments ) [ ] lint . Failure {
2022-04-10 09:06:59 +02:00
var failures [ ] lint . Failure
2025-04-24 14:29:39 +02:00
onFailure := func ( failure lint . Failure ) {
failures = append ( failures , failure )
}
2018-09-15 14:33:28 -07:00
2025-04-24 14:29:39 +02:00
if ! r . skipPackageNameChecks {
r . applyPackageCheckRules ( file , onFailure )
}
2022-04-10 09:06:59 +02:00
2025-04-24 14:29:39 +02:00
fileAst := file . AST
2017-11-26 19:28:18 -08:00
walker := lintNames {
2025-07-11 14:45:38 +03:00
file : file ,
fileAst : fileAst ,
onFailure : onFailure ,
allowList : r . allowList ,
blockList : r . blockList ,
skipInitialismChecks : r . skipInitialismNameChecks ,
upperCaseConst : r . allowUpperCaseConst ,
2017-11-26 19:28:18 -08:00
}
ast . Walk ( & walker , fileAst )
return failures
}
// Name returns the rule name.
2022-04-10 11:55:13 +02:00
func ( * VarNamingRule ) Name ( ) string {
2018-05-26 21:28:31 -07:00
return "var-naming"
2017-11-26 19:28:18 -08:00
}
2025-04-24 14:29:39 +02:00
func ( r * VarNamingRule ) applyPackageCheckRules ( file * lint . File , onFailure func ( failure lint . Failure ) ) {
fileDir := filepath . Dir ( file . Name )
// Protect pkgsWithNameFailure from concurrent modifications
r . pkgNameAlreadyChecked . Lock ( )
defer r . pkgNameAlreadyChecked . Unlock ( )
if r . pkgNameAlreadyChecked . has ( fileDir ) {
return
}
r . pkgNameAlreadyChecked . add ( fileDir ) // mark this package as already checked
pkgNameNode := file . AST . Name
pkgName := pkgNameNode . Name
pkgNameLower := strings . ToLower ( pkgName )
if _ , ok := r . extraBadPackageNames [ pkgNameLower ] ; ok {
onFailure ( r . pkgNameFailure ( pkgNameNode , "avoid bad package names" ) )
return
}
if _ , ok := defaultBadPackageNames [ pkgNameLower ] ; ok {
onFailure ( r . pkgNameFailure ( pkgNameNode , "avoid meaningless package names" ) )
return
}
// Package names need slightly different handling than other names.
if strings . Contains ( pkgName , "_" ) && ! strings . HasSuffix ( pkgName , "_test" ) {
onFailure ( r . pkgNameFailure ( pkgNameNode , "don't use an underscore in package name" ) )
}
2025-04-25 10:32:03 +02:00
if hasUpperCaseLetter ( pkgName ) {
2025-04-24 14:29:39 +02:00
onFailure ( r . pkgNameFailure ( pkgNameNode , "don't use MixedCaps in package names; %s should be %s" , pkgName , pkgNameLower ) )
}
}
func ( * VarNamingRule ) pkgNameFailure ( node ast . Node , msg string , args ... any ) lint . Failure {
return lint . Failure {
Failure : fmt . Sprintf ( msg , args ... ) ,
Confidence : 1 ,
Node : node ,
Category : lint . FailureCategoryNaming ,
}
}
2025-05-26 12:40:17 +03:00
type lintNames struct {
2025-07-11 14:45:38 +03:00
file * lint . File
fileAst * ast . File
onFailure func ( lint . Failure )
allowList [ ] string
blockList [ ] string
skipInitialismChecks bool
upperCaseConst bool
2025-05-26 12:40:17 +03:00
}
2023-07-31 12:09:38 +05:00
func ( w * lintNames ) checkList ( fl * ast . FieldList , thing string ) {
2018-01-24 15:17:19 -08:00
if fl == nil {
return
}
for _ , f := range fl . List {
for _ , id := range f . Names {
2023-07-31 12:09:38 +05:00
w . check ( id , thing )
2018-01-24 15:17:19 -08:00
}
}
}
2023-07-31 12:09:38 +05:00
func ( w * lintNames ) check ( id * ast . Ident , thing string ) {
2018-01-24 15:17:19 -08:00
if id . Name == "_" {
return
}
if knownNameExceptions [ id . Name ] {
return
}
2023-07-31 12:09:38 +05:00
// #851 upperCaseConst support
// if it's const
2025-04-25 10:32:03 +02:00
if thing == token . CONST . String ( ) && w . upperCaseConst && isUpperCaseConst ( id . Name ) {
2023-07-31 12:09:38 +05:00
return
}
2018-01-24 15:17:19 -08:00
// Handle two common styles from other languages that don't belong in Go.
2025-04-25 10:32:03 +02:00
if isUpperUnderscore ( id . Name ) {
2018-01-24 15:44:03 -08:00
w . onFailure ( lint . Failure {
2018-01-24 15:17:19 -08:00
Failure : "don't use ALL_CAPS in Go names; use CamelCase" ,
Confidence : 0.8 ,
Node : id ,
2025-01-18 13:16:19 +02:00
Category : lint . FailureCategoryNaming ,
2018-01-24 15:17:19 -08:00
} )
return
}
2025-07-11 14:45:38 +03:00
should := rule . Name ( id . Name , w . allowList , w . blockList , w . skipInitialismChecks )
2018-01-24 15:17:19 -08:00
if id . Name == should {
return
}
if len ( id . Name ) > 2 && strings . Contains ( id . Name [ 1 : ] , "_" ) {
2018-01-24 15:44:03 -08:00
w . onFailure ( lint . Failure {
2018-01-24 15:17:19 -08:00
Failure : fmt . Sprintf ( "don't use underscores in Go names; %s %s should be %s" , thing , id . Name , should ) ,
Confidence : 0.9 ,
Node : id ,
2025-01-18 13:16:19 +02:00
Category : lint . FailureCategoryNaming ,
2018-01-24 15:17:19 -08:00
} )
return
}
2018-01-24 15:44:03 -08:00
w . onFailure ( lint . Failure {
2018-01-24 15:17:19 -08:00
Failure : fmt . Sprintf ( "%s %s should be %s" , thing , id . Name , should ) ,
Confidence : 0.8 ,
Node : id ,
2025-01-18 13:16:19 +02:00
Category : lint . FailureCategoryNaming ,
2018-01-24 15:17:19 -08:00
} )
}
2017-11-26 19:28:18 -08:00
func ( w * lintNames ) Visit ( n ast . Node ) ast . Visitor {
switch v := n . ( type ) {
case * ast . AssignStmt :
if v . Tok == token . ASSIGN {
return w
}
for _ , exp := range v . Lhs {
if id , ok := exp . ( * ast . Ident ) ; ok {
2023-07-31 12:09:38 +05:00
w . check ( id , "var" )
2017-11-26 19:28:18 -08:00
}
}
case * ast . FuncDecl :
2022-01-18 15:26:29 +01:00
funcName := v . Name . Name
if w . file . IsTest ( ) &&
( strings . HasPrefix ( funcName , "Example" ) ||
strings . HasPrefix ( funcName , "Test" ) ||
strings . HasPrefix ( funcName , "Benchmark" ) ||
strings . HasPrefix ( funcName , "Fuzz" ) ) {
2017-11-26 19:28:18 -08:00
return w
}
thing := "func"
if v . Recv != nil {
thing = "method"
}
// Exclude naming warnings for functions that are exported to C but
// not exported in the Go API.
// See https://github.com/golang/lint/issues/144.
2025-05-26 13:18:38 +02:00
if ast . IsExported ( v . Name . Name ) || ! astutils . IsCgoExported ( v ) {
2023-07-31 12:09:38 +05:00
w . check ( v . Name , thing )
2017-11-26 19:28:18 -08:00
}
2023-07-31 12:09:38 +05:00
w . checkList ( v . Type . Params , thing + " parameter" )
w . checkList ( v . Type . Results , thing + " result" )
2017-11-26 19:28:18 -08:00
case * ast . GenDecl :
if v . Tok == token . IMPORT {
return w
}
2023-07-31 12:09:38 +05:00
thing := v . Tok . String ( )
2017-11-26 19:28:18 -08:00
for _ , spec := range v . Specs {
switch s := spec . ( type ) {
case * ast . TypeSpec :
2023-07-31 12:09:38 +05:00
w . check ( s . Name , thing )
2017-11-26 19:28:18 -08:00
case * ast . ValueSpec :
for _ , id := range s . Names {
2023-07-31 12:09:38 +05:00
w . check ( id , thing )
2017-11-26 19:28:18 -08:00
}
}
}
case * ast . InterfaceType :
// Do not check interface method names.
2021-10-14 20:56:29 +02:00
// They are often constrained by the method names of concrete types.
2017-11-26 19:28:18 -08:00
for _ , x := range v . Methods . List {
ft , ok := x . Type . ( * ast . FuncType )
if ! ok { // might be an embedded interface name
continue
}
2023-07-31 12:09:38 +05:00
w . checkList ( ft . Params , "interface method parameter" )
w . checkList ( ft . Results , "interface method result" )
2017-11-26 19:28:18 -08:00
}
case * ast . RangeStmt :
if v . Tok == token . ASSIGN {
return w
}
if id , ok := v . Key . ( * ast . Ident ) ; ok {
2023-07-31 12:09:38 +05:00
w . check ( id , "range var" )
2017-11-26 19:28:18 -08:00
}
if id , ok := v . Value . ( * ast . Ident ) ; ok {
2023-07-31 12:09:38 +05:00
w . check ( id , "range var" )
2017-11-26 19:28:18 -08:00
}
case * ast . StructType :
for _ , f := range v . Fields . List {
for _ , id := range f . Names {
2023-07-31 12:09:38 +05:00
w . check ( id , "struct field" )
2017-11-26 19:28:18 -08:00
}
}
}
return w
}
2018-09-15 14:33:28 -07:00
2025-04-25 10:32:03 +02:00
// isUpperCaseConst checks if a string is in constant name format like `SOME_CONST`, `SOME_CONST_2`, `X123_3`, `_SOME_PRIVATE_CONST`.
// See #851, #865.
func isUpperCaseConst ( s string ) bool {
2025-05-26 12:40:17 +03:00
if s == "" {
2025-04-25 10:32:03 +02:00
return false
}
r := [ ] rune ( s )
c := r [ 0 ]
if len ( r ) == 1 {
return isUpper ( c )
}
if c != '_' && ! isUpper ( c ) { // Must start with an uppercase letter or underscore
return false
}
for i , c := range r {
switch {
case isUpperOrDigit ( c ) :
continue
case c == '_' :
// Underscore must be followed by at least one uppercase letter or digit
if i + 1 >= len ( s ) || ! isUpperOrDigit ( r [ i + 1 ] ) {
return false
}
default :
return false
}
}
return true
}
// hasUpperCaseLetter checks if a string contains at least one upper case letter.
func hasUpperCaseLetter ( s string ) bool {
for _ , r := range s {
if isUpper ( r ) {
return true
}
}
return false
}
// isUpperOrDigit checks if a rune is an uppercase letter or digit.
func isUpperOrDigit ( r rune ) bool {
return isUpper ( r ) || isDigit ( r )
}
// isUpper checks if rune is a simple digit.
//
// We don't use unicode.IsDigit as it returns true for a large variety of digits that are not 0-9.
func isDigit ( r rune ) bool {
return r >= '0' && r <= '9'
}
// isUpper checks if rune is ASCII upper case letter
//
// We restrict to A-Z because unicode.IsUpper returns true for a large variety of letters.
func isUpper ( r rune ) bool {
return r >= 'A' && r <= 'Z'
}
// isUpperUnderscore detects variable that are made from upper case letters, underscore, or digits.
//
// Short variable names are considered OK.
func isUpperUnderscore ( s string ) bool {
if ! strings . Contains ( s , "_" ) {
return false
}
if len ( s ) <= 5 {
// avoid false positives
return false
}
for _ , r := range s {
if r == '_' || isUpperOrDigit ( r ) {
continue
}
return false
}
return true
}
2024-12-11 19:35:58 +01:00
func getList ( arg any , argName string ) ( [ ] string , error ) {
2024-10-29 13:48:49 +02:00
args , ok := arg . ( [ ] any )
2018-09-15 14:33:28 -07:00
if ! ok {
2024-12-11 19:35:58 +01:00
return nil , fmt . Errorf ( "invalid argument to the var-naming rule. Expecting a %s of type slice with initialisms, got %T" , argName , arg )
2018-09-15 14:33:28 -07:00
}
var list [ ] string
2024-10-29 13:48:49 +02:00
for _ , v := range args {
val , ok := v . ( string )
if ! ok {
2025-03-28 01:34:20 -07:00
return nil , fmt . Errorf ( "invalid %v values of the var-naming rule. Expecting slice of strings but got element of type %T" , v , arg )
2018-09-15 14:33:28 -07:00
}
2024-10-29 13:48:49 +02:00
list = append ( list , val )
2018-09-15 14:33:28 -07:00
}
2024-12-11 19:35:58 +01:00
return list , nil
2018-09-15 14:33:28 -07:00
}
2025-04-24 14:29:39 +02:00
type syncSet struct {
sync . Mutex
elements map [ string ] struct { }
}
func ( sm * syncSet ) has ( s string ) bool {
_ , result := sm . elements [ s ]
return result
}
func ( sm * syncSet ) add ( s string ) {
sm . elements [ s ] = struct { } { }
}