github.com/songshiyun/revive@v1.1.5-0.20220323112655-f8433a19b3c5/rule/utils.go (about)

     1  package rule
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"go/ast"
     7  	"go/printer"
     8  	"go/token"
     9  	"go/types"
    10  	"regexp"
    11  	"strings"
    12  
    13  	"github.com/songshiyun/revive/lint"
    14  )
    15  
    16  const styleGuideBase = "https://golang.org/wiki/CodeReviewComments"
    17  
    18  // isBlank returns whether id is the blank identifier "_".
    19  // If id == nil, the answer is false.
    20  func isBlank(id *ast.Ident) bool { return id != nil && id.Name == "_" }
    21  
    22  var commonMethods = map[string]bool{
    23  	"Error":     true,
    24  	"Read":      true,
    25  	"ServeHTTP": true,
    26  	"String":    true,
    27  	"Write":     true,
    28  	"Unwrap":    true,
    29  }
    30  
    31  func receiverType(fn *ast.FuncDecl) string {
    32  	switch e := fn.Recv.List[0].Type.(type) {
    33  	case *ast.Ident:
    34  		return e.Name
    35  	case *ast.StarExpr:
    36  		if id, ok := e.X.(*ast.Ident); ok {
    37  			return id.Name
    38  		}
    39  	}
    40  	// The parser accepts much more than just the legal forms.
    41  	return "invalid-type"
    42  }
    43  
    44  var knownNameExceptions = map[string]bool{
    45  	"LastInsertId": true, // must match database/sql
    46  	"kWh":          true,
    47  }
    48  
    49  func isCgoExported(f *ast.FuncDecl) bool {
    50  	if f.Recv != nil || f.Doc == nil {
    51  		return false
    52  	}
    53  
    54  	cgoExport := regexp.MustCompile(fmt.Sprintf("(?m)^//export %s$", regexp.QuoteMeta(f.Name.Name)))
    55  	for _, c := range f.Doc.List {
    56  		if cgoExport.MatchString(c.Text) {
    57  			return true
    58  		}
    59  	}
    60  	return false
    61  }
    62  
    63  var allCapsRE = regexp.MustCompile(`^[A-Z0-9_]+$`)
    64  
    65  func isIdent(expr ast.Expr, ident string) bool {
    66  	id, ok := expr.(*ast.Ident)
    67  	return ok && id.Name == ident
    68  }
    69  
    70  var zeroLiteral = map[string]bool{
    71  	"false": true, // bool
    72  	// runes
    73  	`'\x00'`: true,
    74  	`'\000'`: true,
    75  	// strings
    76  	`""`: true,
    77  	"``": true,
    78  	// numerics
    79  	"0":   true,
    80  	"0.":  true,
    81  	"0.0": true,
    82  	"0i":  true,
    83  }
    84  
    85  func validType(T types.Type) bool {
    86  	return T != nil &&
    87  		T != types.Typ[types.Invalid] &&
    88  		!strings.Contains(T.String(), "invalid type") // good but not foolproof
    89  }
    90  
    91  // isPkgDot checks if the expression is <pkg>.<name>
    92  func isPkgDot(expr ast.Expr, pkg, name string) bool {
    93  	sel, ok := expr.(*ast.SelectorExpr)
    94  	return ok && isIdent(sel.X, pkg) && isIdent(sel.Sel, name)
    95  }
    96  
    97  func srcLine(src []byte, p token.Position) string {
    98  	// Run to end of line in both directions if not at line start/end.
    99  	lo, hi := p.Offset, p.Offset+1
   100  	for lo > 0 && src[lo-1] != '\n' {
   101  		lo--
   102  	}
   103  	for hi < len(src) && src[hi-1] != '\n' {
   104  		hi++
   105  	}
   106  	return string(src[lo:hi])
   107  }
   108  
   109  // pick yields a list of nodes by picking them from a sub-ast with root node n.
   110  // Nodes are selected by applying the fselect function
   111  // f function is applied to each selected node before inserting it in the final result.
   112  // If f==nil then it defaults to the identity function (ie it returns the node itself)
   113  func pick(n ast.Node, fselect func(n ast.Node) bool, f func(n ast.Node) []ast.Node) []ast.Node {
   114  	var result []ast.Node
   115  
   116  	if n == nil {
   117  		return result
   118  	}
   119  
   120  	if f == nil {
   121  		f = func(n ast.Node) []ast.Node { return []ast.Node{n} }
   122  	}
   123  
   124  	onSelect := func(n ast.Node) {
   125  		result = append(result, f(n)...)
   126  	}
   127  	p := picker{fselect: fselect, onSelect: onSelect}
   128  	ast.Walk(p, n)
   129  	return result
   130  }
   131  
   132  type picker struct {
   133  	fselect  func(n ast.Node) bool
   134  	onSelect func(n ast.Node)
   135  }
   136  
   137  func (p picker) Visit(node ast.Node) ast.Visitor {
   138  	if p.fselect == nil {
   139  		return nil
   140  	}
   141  
   142  	if p.fselect(node) {
   143  		p.onSelect(node)
   144  	}
   145  
   146  	return p
   147  }
   148  
   149  // isBoolOp returns true if the given token corresponds to
   150  // a bool operator
   151  func isBoolOp(t token.Token) bool {
   152  	switch t {
   153  	case token.LAND, token.LOR, token.EQL, token.NEQ:
   154  		return true
   155  	}
   156  
   157  	return false
   158  }
   159  
   160  const (
   161  	trueName  = "true"
   162  	falseName = "false"
   163  )
   164  
   165  func isExprABooleanLit(n ast.Node) (lexeme string, ok bool) {
   166  	oper, ok := n.(*ast.Ident)
   167  
   168  	if !ok {
   169  		return "", false
   170  	}
   171  
   172  	return oper.Name, (oper.Name == trueName || oper.Name == falseName)
   173  }
   174  
   175  // gofmt returns a string representation of an AST subtree.
   176  func gofmt(x interface{}) string {
   177  	buf := bytes.Buffer{}
   178  	fs := token.NewFileSet()
   179  	printer.Fprint(&buf, fs, x)
   180  	return buf.String()
   181  }
   182  
   183  // checkNumberOfArguments fails if the given number of arguments is not, at least, the expected one
   184  func checkNumberOfArguments(expected int, args lint.Arguments, ruleName string) {
   185  	if len(args) < expected {
   186  		panic(fmt.Sprintf("not enough arguments for %s rule, expected %d, got %d. Please check the rule's documentation", ruleName, expected, len(args)))
   187  	}
   188  }