github.com/golangci/go-tools@v0.0.0-20190318060251-af6baa5dc196/stylecheck/names.go (about)

     1  // Copyright (c) 2013 The Go Authors. All rights reserved.
     2  // Copyright (c) 2018 Dominik Honnef. All rights reserved.
     3  
     4  package stylecheck
     5  
     6  import (
     7  	"go/ast"
     8  	"go/token"
     9  	"strings"
    10  	"unicode"
    11  
    12  	"github.com/golangci/go-tools/lint"
    13  	. "github.com/golangci/go-tools/lint/lintdsl"
    14  )
    15  
    16  // knownNameExceptions is a set of names that are known to be exempt from naming checks.
    17  // This is usually because they are constrained by having to match names in the
    18  // standard library.
    19  var knownNameExceptions = map[string]bool{
    20  	"LastInsertId": true, // must match database/sql
    21  	"kWh":          true,
    22  }
    23  
    24  func (c *Checker) CheckNames(j *lint.Job) {
    25  	// A large part of this function is copied from
    26  	// github.com/golang/lint, Copyright (c) 2013 The Go Authors,
    27  	// licensed under the BSD 3-clause license.
    28  
    29  	allCaps := func(s string) bool {
    30  		for _, r := range s {
    31  			if !((r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_') {
    32  				return false
    33  			}
    34  		}
    35  		return true
    36  	}
    37  
    38  	check := func(id *ast.Ident, thing string, initialisms map[string]bool) {
    39  		if id.Name == "_" {
    40  			return
    41  		}
    42  		if knownNameExceptions[id.Name] {
    43  			return
    44  		}
    45  
    46  		// Handle two common styles from other languages that don't belong in Go.
    47  		if len(id.Name) >= 5 && allCaps(id.Name) && strings.Contains(id.Name, "_") {
    48  			j.Errorf(id, "should not use ALL_CAPS in Go names; use CamelCase instead")
    49  			return
    50  		}
    51  
    52  		should := lintName(id.Name, initialisms)
    53  		if id.Name == should {
    54  			return
    55  		}
    56  
    57  		if len(id.Name) > 2 && strings.Contains(id.Name[1:len(id.Name)-1], "_") {
    58  			j.Errorf(id, "should not use underscores in Go names; %s %s should be %s", thing, id.Name, should)
    59  			return
    60  		}
    61  		j.Errorf(id, "%s %s should be %s", thing, id.Name, should)
    62  	}
    63  	checkList := func(fl *ast.FieldList, thing string, initialisms map[string]bool) {
    64  		if fl == nil {
    65  			return
    66  		}
    67  		for _, f := range fl.List {
    68  			for _, id := range f.Names {
    69  				check(id, thing, initialisms)
    70  			}
    71  		}
    72  	}
    73  
    74  	for _, pkg := range j.Program.InitialPackages {
    75  		initialisms := make(map[string]bool, len(pkg.Config.Initialisms))
    76  		for _, word := range pkg.Config.Initialisms {
    77  			initialisms[word] = true
    78  		}
    79  		for _, f := range pkg.Syntax {
    80  			// Package names need slightly different handling than other names.
    81  			if !strings.HasSuffix(f.Name.Name, "_test") && strings.Contains(f.Name.Name, "_") {
    82  				j.Errorf(f, "should not use underscores in package names")
    83  			}
    84  			if strings.IndexFunc(f.Name.Name, unicode.IsUpper) != -1 {
    85  				j.Errorf(f, "should not use MixedCaps in package name; %s should be %s", f.Name.Name, strings.ToLower(f.Name.Name))
    86  			}
    87  
    88  			ast.Inspect(f, func(node ast.Node) bool {
    89  				switch v := node.(type) {
    90  				case *ast.AssignStmt:
    91  					if v.Tok != token.DEFINE {
    92  						return true
    93  					}
    94  					for _, exp := range v.Lhs {
    95  						if id, ok := exp.(*ast.Ident); ok {
    96  							check(id, "var", initialisms)
    97  						}
    98  					}
    99  				case *ast.FuncDecl:
   100  					// Functions with no body are defined elsewhere (in
   101  					// assembly, or via go:linkname). These are likely to
   102  					// be something very low level (such as the runtime),
   103  					// where our rules don't apply.
   104  					if v.Body == nil {
   105  						return true
   106  					}
   107  
   108  					if IsInTest(j, v) && (strings.HasPrefix(v.Name.Name, "Example") || strings.HasPrefix(v.Name.Name, "Test") || strings.HasPrefix(v.Name.Name, "Benchmark")) {
   109  						return true
   110  					}
   111  
   112  					thing := "func"
   113  					if v.Recv != nil {
   114  						thing = "method"
   115  					}
   116  
   117  					if !isTechnicallyExported(v) {
   118  						check(v.Name, thing, initialisms)
   119  					}
   120  
   121  					checkList(v.Type.Params, thing+" parameter", initialisms)
   122  					checkList(v.Type.Results, thing+" result", initialisms)
   123  				case *ast.GenDecl:
   124  					if v.Tok == token.IMPORT {
   125  						return true
   126  					}
   127  					var thing string
   128  					switch v.Tok {
   129  					case token.CONST:
   130  						thing = "const"
   131  					case token.TYPE:
   132  						thing = "type"
   133  					case token.VAR:
   134  						thing = "var"
   135  					}
   136  					for _, spec := range v.Specs {
   137  						switch s := spec.(type) {
   138  						case *ast.TypeSpec:
   139  							check(s.Name, thing, initialisms)
   140  						case *ast.ValueSpec:
   141  							for _, id := range s.Names {
   142  								check(id, thing, initialisms)
   143  							}
   144  						}
   145  					}
   146  				case *ast.InterfaceType:
   147  					// Do not check interface method names.
   148  					// They are often constrainted by the method names of concrete types.
   149  					for _, x := range v.Methods.List {
   150  						ft, ok := x.Type.(*ast.FuncType)
   151  						if !ok { // might be an embedded interface name
   152  							continue
   153  						}
   154  						checkList(ft.Params, "interface method parameter", initialisms)
   155  						checkList(ft.Results, "interface method result", initialisms)
   156  					}
   157  				case *ast.RangeStmt:
   158  					if v.Tok == token.ASSIGN {
   159  						return true
   160  					}
   161  					if id, ok := v.Key.(*ast.Ident); ok {
   162  						check(id, "range var", initialisms)
   163  					}
   164  					if id, ok := v.Value.(*ast.Ident); ok {
   165  						check(id, "range var", initialisms)
   166  					}
   167  				case *ast.StructType:
   168  					for _, f := range v.Fields.List {
   169  						for _, id := range f.Names {
   170  							check(id, "struct field", initialisms)
   171  						}
   172  					}
   173  				}
   174  				return true
   175  			})
   176  		}
   177  	}
   178  }
   179  
   180  // lintName returns a different name if it should be different.
   181  func lintName(name string, initialisms map[string]bool) (should string) {
   182  	// A large part of this function is copied from
   183  	// github.com/golang/lint, Copyright (c) 2013 The Go Authors,
   184  	// licensed under the BSD 3-clause license.
   185  
   186  	// Fast path for simple cases: "_" and all lowercase.
   187  	if name == "_" {
   188  		return name
   189  	}
   190  	if strings.IndexFunc(name, func(r rune) bool { return !unicode.IsLower(r) }) == -1 {
   191  		return name
   192  	}
   193  
   194  	// Split camelCase at any lower->upper transition, and split on underscores.
   195  	// Check each word for common initialisms.
   196  	runes := []rune(name)
   197  	w, i := 0, 0 // index of start of word, scan
   198  	for i+1 <= len(runes) {
   199  		eow := false // whether we hit the end of a word
   200  		if i+1 == len(runes) {
   201  			eow = true
   202  		} else if runes[i+1] == '_' && i+1 != len(runes)-1 {
   203  			// underscore; shift the remainder forward over any run of underscores
   204  			eow = true
   205  			n := 1
   206  			for i+n+1 < len(runes) && runes[i+n+1] == '_' {
   207  				n++
   208  			}
   209  
   210  			// Leave at most one underscore if the underscore is between two digits
   211  			if i+n+1 < len(runes) && unicode.IsDigit(runes[i]) && unicode.IsDigit(runes[i+n+1]) {
   212  				n--
   213  			}
   214  
   215  			copy(runes[i+1:], runes[i+n+1:])
   216  			runes = runes[:len(runes)-n]
   217  		} else if unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]) {
   218  			// lower->non-lower
   219  			eow = true
   220  		}
   221  		i++
   222  		if !eow {
   223  			continue
   224  		}
   225  
   226  		// [w,i) is a word.
   227  		word := string(runes[w:i])
   228  		if u := strings.ToUpper(word); initialisms[u] {
   229  			// Keep consistent case, which is lowercase only at the start.
   230  			if w == 0 && unicode.IsLower(runes[w]) {
   231  				u = strings.ToLower(u)
   232  			}
   233  			// All the common initialisms are ASCII,
   234  			// so we can replace the bytes exactly.
   235  			// TODO(dh): this won't be true once we allow custom initialisms
   236  			copy(runes[w:], []rune(u))
   237  		} else if w > 0 && strings.ToLower(word) == word {
   238  			// already all lowercase, and not the first word, so uppercase the first character.
   239  			runes[w] = unicode.ToUpper(runes[w])
   240  		}
   241  		w = i
   242  	}
   243  	return string(runes)
   244  }
   245  
   246  func isTechnicallyExported(f *ast.FuncDecl) bool {
   247  	if f.Recv != nil || f.Doc == nil {
   248  		return false
   249  	}
   250  
   251  	const export = "//export "
   252  	const linkname = "//go:linkname "
   253  	for _, c := range f.Doc.List {
   254  		if strings.HasPrefix(c.Text, export) && len(c.Text) == len(export)+len(f.Name.Name) && c.Text[len(export):] == f.Name.Name {
   255  			return true
   256  		}
   257  
   258  		if strings.HasPrefix(c.Text, linkname) {
   259  			return true
   260  		}
   261  	}
   262  	return false
   263  }