github.com/Johnny2210/revive@v1.0.8-0.20210625134200-febf37ccd0f5/rule/confusing-naming.go (about)

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