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 }