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  }