github.com/songshiyun/revive@v1.1.5-0.20220323112655-f8433a19b3c5/rule/confusing-naming.go (about) 1 package rule 2 3 import ( 4 "fmt" 5 "go/ast" 6 "strings" 7 "sync" 8 9 "github.com/songshiyun/revive/lint" 10 ) 11 12 type referenceMethod struct { 13 fileName string 14 id *ast.Ident 15 } 16 17 type pkgMethods struct { 18 pkg *lint.Package 19 methods map[string]map[string]*referenceMethod 20 mu *sync.Mutex 21 } 22 23 type packages struct { 24 pkgs []pkgMethods 25 mu sync.Mutex 26 } 27 28 func (ps *packages) methodNames(lp *lint.Package) pkgMethods { 29 ps.mu.Lock() 30 31 for _, pkg := range ps.pkgs { 32 if pkg.pkg == lp { 33 ps.mu.Unlock() 34 return pkg 35 } 36 } 37 38 pkgm := pkgMethods{pkg: lp, methods: make(map[string]map[string]*referenceMethod), mu: &sync.Mutex{}} 39 ps.pkgs = append(ps.pkgs, pkgm) 40 41 ps.mu.Unlock() 42 return pkgm 43 } 44 45 var allPkgs = packages{pkgs: make([]pkgMethods, 1)} 46 47 // ConfusingNamingRule lints method names that differ only by capitalization 48 type ConfusingNamingRule struct{} 49 50 // Apply applies the rule to given file. 51 func (r *ConfusingNamingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { 52 var failures []lint.Failure 53 fileAst := file.AST 54 pkgm := allPkgs.methodNames(file.Pkg) 55 walker := lintConfusingNames{ 56 fileName: file.Name, 57 pkgm: pkgm, 58 onFailure: func(failure lint.Failure) { 59 failures = append(failures, failure) 60 }, 61 } 62 63 ast.Walk(&walker, fileAst) 64 65 return failures 66 } 67 68 // Name returns the rule name. 69 func (r *ConfusingNamingRule) Name() string { 70 return "confusing-naming" 71 } 72 73 // checkMethodName checks if a given method/function name is similar (just case differences) to other method/function of the same struct/file. 74 func checkMethodName(holder string, id *ast.Ident, w *lintConfusingNames) { 75 if id.Name == "init" && holder == defaultStructName { 76 // ignore init functions 77 return 78 } 79 80 pkgm := w.pkgm 81 name := strings.ToUpper(id.Name) 82 83 pkgm.mu.Lock() 84 defer pkgm.mu.Unlock() 85 86 if pkgm.methods[holder] != nil { 87 if pkgm.methods[holder][name] != nil { 88 refMethod := pkgm.methods[holder][name] 89 // confusing names 90 var kind string 91 if holder == defaultStructName { 92 kind = "function" 93 } else { 94 kind = "method" 95 } 96 var fileName string 97 if w.fileName == refMethod.fileName { 98 fileName = "the same source file" 99 } else { 100 fileName = refMethod.fileName 101 } 102 w.onFailure(lint.Failure{ 103 Failure: fmt.Sprintf("Method '%s' differs only by capitalization to %s '%s' in %s", id.Name, kind, refMethod.id.Name, fileName), 104 Confidence: 1, 105 Node: id, 106 Category: "naming", 107 }) 108 109 return 110 } 111 } else { 112 pkgm.methods[holder] = make(map[string]*referenceMethod, 1) 113 } 114 115 // update the black list 116 if pkgm.methods[holder] == nil { 117 println("no entry for '", holder, "'") 118 } 119 pkgm.methods[holder][name] = &referenceMethod{fileName: w.fileName, id: id} 120 } 121 122 type lintConfusingNames struct { 123 fileName string 124 pkgm pkgMethods 125 onFailure func(lint.Failure) 126 } 127 128 const defaultStructName = "_" // used to map functions 129 130 // getStructName of a function receiver. Defaults to defaultStructName 131 func getStructName(r *ast.FieldList) string { 132 result := defaultStructName 133 134 if r == nil || len(r.List) < 1 { 135 return result 136 } 137 138 t := r.List[0].Type 139 140 if p, _ := t.(*ast.StarExpr); p != nil { // if a pointer receiver => dereference pointer receiver types 141 t = p.X 142 } 143 144 if p, _ := t.(*ast.Ident); p != nil { 145 result = p.Name 146 } 147 148 return result 149 } 150 151 func checkStructFields(fields *ast.FieldList, structName string, w *lintConfusingNames) { 152 bl := make(map[string]bool, len(fields.List)) 153 for _, f := range fields.List { 154 for _, id := range f.Names { 155 normName := strings.ToUpper(id.Name) 156 if bl[normName] { 157 w.onFailure(lint.Failure{ 158 Failure: fmt.Sprintf("Field '%s' differs only by capitalization to other field in the struct type %s", id.Name, structName), 159 Confidence: 1, 160 Node: id, 161 Category: "naming", 162 }) 163 } else { 164 bl[normName] = true 165 } 166 } 167 } 168 } 169 170 func (w *lintConfusingNames) Visit(n ast.Node) ast.Visitor { 171 switch v := n.(type) { 172 case *ast.FuncDecl: 173 // Exclude naming warnings for functions that are exported to C but 174 // not exported in the Go API. 175 // See https://github.com/golang/lint/issues/144. 176 if ast.IsExported(v.Name.Name) || !isCgoExported(v) { 177 checkMethodName(getStructName(v.Recv), v.Name, w) 178 } 179 case *ast.TypeSpec: 180 if s, ok := v.Type.(*ast.StructType); ok { 181 checkStructFields(s.Fields, v.Name.Name, w) 182 } 183 184 default: 185 // will add other checks like field names, struct names, etc. 186 } 187 188 return w 189 }