.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / honnef.co / go / tools@v0.1.1 / stylecheck / lint.go
1 package stylecheck
2
3 import (
4         "fmt"
5         "go/ast"
6         "go/constant"
7         "go/token"
8         "go/types"
9         "sort"
10         "strconv"
11         "strings"
12         "unicode"
13         "unicode/utf8"
14
15         "honnef.co/go/tools/analysis/code"
16         "honnef.co/go/tools/analysis/edit"
17         "honnef.co/go/tools/analysis/lint"
18         "honnef.co/go/tools/analysis/report"
19         "honnef.co/go/tools/config"
20         "honnef.co/go/tools/go/ast/astutil"
21         "honnef.co/go/tools/go/ir"
22         "honnef.co/go/tools/go/ir/irutil"
23         "honnef.co/go/tools/go/types/typeutil"
24         "honnef.co/go/tools/internal/passes/buildir"
25         "honnef.co/go/tools/pattern"
26
27         "golang.org/x/tools/go/analysis"
28         "golang.org/x/tools/go/analysis/passes/inspect"
29         "golang.org/x/tools/go/ast/inspector"
30         gotypeutil "golang.org/x/tools/go/types/typeutil"
31 )
32
33 func CheckPackageComment(pass *analysis.Pass) (interface{}, error) {
34         // - At least one file in a non-main package should have a package comment
35         //
36         // - The comment should be of the form
37         // "Package x ...". This has a slight potential for false
38         // positives, as multiple files can have package comments, in
39         // which case they get appended. But that doesn't happen a lot in
40         // the real world.
41
42         if pass.Pkg.Name() == "main" {
43                 return nil, nil
44         }
45         hasDocs := false
46         for _, f := range pass.Files {
47                 if code.IsInTest(pass, f) {
48                         continue
49                 }
50                 if f.Doc != nil && len(f.Doc.List) > 0 {
51                         hasDocs = true
52                         prefix := "Package " + f.Name.Name + " "
53                         if !strings.HasPrefix(strings.TrimSpace(f.Doc.Text()), prefix) {
54                                 report.Report(pass, f.Doc, fmt.Sprintf(`package comment should be of the form "%s..."`, prefix))
55                         }
56                 }
57         }
58
59         if !hasDocs {
60                 for _, f := range pass.Files {
61                         if code.IsInTest(pass, f) {
62                                 continue
63                         }
64                         report.Report(pass, f, "at least one file in a package should have a package comment", report.ShortRange())
65                 }
66         }
67         return nil, nil
68 }
69
70 func CheckDotImports(pass *analysis.Pass) (interface{}, error) {
71         for _, f := range pass.Files {
72         imports:
73                 for _, imp := range f.Imports {
74                         path := imp.Path.Value
75                         path = path[1 : len(path)-1]
76                         for _, w := range config.For(pass).DotImportWhitelist {
77                                 if w == path {
78                                         continue imports
79                                 }
80                         }
81
82                         if imp.Name != nil && imp.Name.Name == "." && !code.IsInTest(pass, f) {
83                                 report.Report(pass, imp, "should not use dot imports", report.FilterGenerated())
84                         }
85                 }
86         }
87         return nil, nil
88 }
89
90 func CheckDuplicatedImports(pass *analysis.Pass) (interface{}, error) {
91         for _, f := range pass.Files {
92                 // Collect all imports by their import path
93                 imports := make(map[string][]*ast.ImportSpec, len(f.Imports))
94                 for _, imp := range f.Imports {
95                         imports[imp.Path.Value] = append(imports[imp.Path.Value], imp)
96                 }
97
98                 for path, value := range imports {
99                         if path[1:len(path)-1] == "unsafe" {
100                                 // Don't flag unsafe. Cgo generated code imports
101                                 // unsafe using the blank identifier, and most
102                                 // user-written cgo code also imports unsafe
103                                 // explicitly.
104                                 continue
105                         }
106                         // If there's more than one import per path, we flag that
107                         if len(value) > 1 {
108                                 s := fmt.Sprintf("package %s is being imported more than once", path)
109                                 opts := []report.Option{report.FilterGenerated()}
110                                 for _, imp := range value[1:] {
111                                         opts = append(opts, report.Related(imp, fmt.Sprintf("other import of %s", path)))
112                                 }
113                                 report.Report(pass, value[0], s, opts...)
114                         }
115                 }
116         }
117         return nil, nil
118 }
119
120 func CheckBlankImports(pass *analysis.Pass) (interface{}, error) {
121         fset := pass.Fset
122         for _, f := range pass.Files {
123                 if code.IsMainLike(pass) || code.IsInTest(pass, f) {
124                         continue
125                 }
126
127                 // Collect imports of the form `import _ "foo"`, i.e. with no
128                 // parentheses, as their comment will be associated with the
129                 // (paren-free) GenDecl, not the import spec itself.
130                 //
131                 // We don't directly process the GenDecl so that we can
132                 // correctly handle the following:
133                 //
134                 //  import _ "foo"
135                 //  import _ "bar"
136                 //
137                 // where only the first import should get flagged.
138                 skip := map[ast.Spec]bool{}
139                 ast.Inspect(f, func(node ast.Node) bool {
140                         switch node := node.(type) {
141                         case *ast.File:
142                                 return true
143                         case *ast.GenDecl:
144                                 if node.Tok != token.IMPORT {
145                                         return false
146                                 }
147                                 if node.Lparen == token.NoPos && node.Doc != nil {
148                                         skip[node.Specs[0]] = true
149                                 }
150                                 return false
151                         }
152                         return false
153                 })
154                 for i, imp := range f.Imports {
155                         pos := fset.Position(imp.Pos())
156
157                         if !astutil.IsBlank(imp.Name) {
158                                 continue
159                         }
160                         // Only flag the first blank import in a group of imports,
161                         // or don't flag any of them, if the first one is
162                         // commented
163                         if i > 0 {
164                                 prev := f.Imports[i-1]
165                                 prevPos := fset.Position(prev.Pos())
166                                 if pos.Line-1 == prevPos.Line && astutil.IsBlank(prev.Name) {
167                                         continue
168                                 }
169                         }
170
171                         if imp.Doc == nil && imp.Comment == nil && !skip[imp] {
172                                 report.Report(pass, imp, "a blank import should be only in a main or test package, or have a comment justifying it")
173                         }
174                 }
175         }
176         return nil, nil
177 }
178
179 func CheckIncDec(pass *analysis.Pass) (interface{}, error) {
180         // TODO(dh): this can be noisy for function bodies that look like this:
181         //      x += 3
182         //      ...
183         //      x += 2
184         //      ...
185         //      x += 1
186         fn := func(node ast.Node) {
187                 assign := node.(*ast.AssignStmt)
188                 if assign.Tok != token.ADD_ASSIGN && assign.Tok != token.SUB_ASSIGN {
189                         return
190                 }
191                 if (len(assign.Lhs) != 1 || len(assign.Rhs) != 1) ||
192                         !astutil.IsIntLiteral(assign.Rhs[0], "1") {
193                         return
194                 }
195
196                 suffix := ""
197                 switch assign.Tok {
198                 case token.ADD_ASSIGN:
199                         suffix = "++"
200                 case token.SUB_ASSIGN:
201                         suffix = "--"
202                 }
203
204                 report.Report(pass, assign, fmt.Sprintf("should replace %s with %s%s", report.Render(pass, assign), report.Render(pass, assign.Lhs[0]), suffix))
205         }
206         code.Preorder(pass, fn, (*ast.AssignStmt)(nil))
207         return nil, nil
208 }
209
210 func CheckErrorReturn(pass *analysis.Pass) (interface{}, error) {
211 fnLoop:
212         for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
213                 sig := fn.Type().(*types.Signature)
214                 rets := sig.Results()
215                 if rets == nil || rets.Len() < 2 {
216                         continue
217                 }
218
219                 if rets.At(rets.Len()-1).Type() == types.Universe.Lookup("error").Type() {
220                         // Last return type is error. If the function also returns
221                         // errors in other positions, that's fine.
222                         continue
223                 }
224                 for i := rets.Len() - 2; i >= 0; i-- {
225                         if rets.At(i).Type() == types.Universe.Lookup("error").Type() {
226                                 report.Report(pass, rets.At(i), "error should be returned as the last argument", report.ShortRange())
227                                 continue fnLoop
228                         }
229                 }
230         }
231         return nil, nil
232 }
233
234 // CheckUnexportedReturn checks that exported functions on exported
235 // types do not return unexported types.
236 func CheckUnexportedReturn(pass *analysis.Pass) (interface{}, error) {
237         for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
238                 if fn.Synthetic != 0 || fn.Parent() != nil {
239                         continue
240                 }
241                 if !ast.IsExported(fn.Name()) || code.IsMain(pass) || code.IsInTest(pass, fn) {
242                         continue
243                 }
244                 sig := fn.Type().(*types.Signature)
245                 if sig.Recv() != nil && !ast.IsExported(typeutil.Dereference(sig.Recv().Type()).(*types.Named).Obj().Name()) {
246                         continue
247                 }
248                 res := sig.Results()
249                 for i := 0; i < res.Len(); i++ {
250                         if named, ok := typeutil.DereferenceR(res.At(i).Type()).(*types.Named); ok &&
251                                 !ast.IsExported(named.Obj().Name()) &&
252                                 named != types.Universe.Lookup("error").Type() {
253                                 report.Report(pass, fn, "should not return unexported type")
254                         }
255                 }
256         }
257         return nil, nil
258 }
259
260 func CheckReceiverNames(pass *analysis.Pass) (interface{}, error) {
261         irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
262         for _, m := range irpkg.Members {
263                 if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() {
264                         ms := gotypeutil.IntuitiveMethodSet(T.Type(), nil)
265                         for _, sel := range ms {
266                                 fn := sel.Obj().(*types.Func)
267                                 recv := fn.Type().(*types.Signature).Recv()
268                                 if typeutil.Dereference(recv.Type()) != T.Type() {
269                                         // skip embedded methods
270                                         continue
271                                 }
272                                 if recv.Name() == "self" || recv.Name() == "this" {
273                                         report.Report(pass, recv, `receiver name should be a reflection of its identity; don't use generic names such as "this" or "self"`, report.FilterGenerated())
274                                 }
275                                 if recv.Name() == "_" {
276                                         report.Report(pass, recv, "receiver name should not be an underscore, omit the name if it is unused", report.FilterGenerated())
277                                 }
278                         }
279                 }
280         }
281         return nil, nil
282 }
283
284 func CheckReceiverNamesIdentical(pass *analysis.Pass) (interface{}, error) {
285         irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
286         for _, m := range irpkg.Members {
287                 names := map[string]int{}
288
289                 var firstFn *types.Func
290                 if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() {
291                         ms := gotypeutil.IntuitiveMethodSet(T.Type(), nil)
292                         for _, sel := range ms {
293                                 fn := sel.Obj().(*types.Func)
294                                 recv := fn.Type().(*types.Signature).Recv()
295                                 if code.IsGenerated(pass, recv.Pos()) {
296                                         // Don't concern ourselves with methods in generated code
297                                         continue
298                                 }
299                                 if typeutil.Dereference(recv.Type()) != T.Type() {
300                                         // skip embedded methods
301                                         continue
302                                 }
303                                 if firstFn == nil {
304                                         firstFn = fn
305                                 }
306                                 if recv.Name() != "" && recv.Name() != "_" {
307                                         names[recv.Name()]++
308                                 }
309                         }
310                 }
311
312                 if len(names) > 1 {
313                         var seen []string
314                         for name, count := range names {
315                                 seen = append(seen, fmt.Sprintf("%dx %q", count, name))
316                         }
317                         sort.Strings(seen)
318
319                         report.Report(pass, firstFn, fmt.Sprintf("methods on the same type should have the same receiver name (seen %s)", strings.Join(seen, ", ")))
320                 }
321         }
322         return nil, nil
323 }
324
325 func CheckContextFirstArg(pass *analysis.Pass) (interface{}, error) {
326         // TODO(dh): this check doesn't apply to test helpers. Example from the stdlib:
327         //      func helperCommandContext(t *testing.T, ctx context.Context, s ...string) (cmd *exec.Cmd) {
328 fnLoop:
329         for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
330                 if fn.Synthetic != 0 || fn.Parent() != nil {
331                         continue
332                 }
333                 params := fn.Signature.Params()
334                 if params.Len() < 2 {
335                         continue
336                 }
337                 if types.TypeString(params.At(0).Type(), nil) == "context.Context" {
338                         continue
339                 }
340                 for i := 1; i < params.Len(); i++ {
341                         param := params.At(i)
342                         if types.TypeString(param.Type(), nil) == "context.Context" {
343                                 report.Report(pass, param, "context.Context should be the first argument of a function", report.ShortRange())
344                                 continue fnLoop
345                         }
346                 }
347         }
348         return nil, nil
349 }
350
351 func CheckErrorStrings(pass *analysis.Pass) (interface{}, error) {
352         objNames := map[*ir.Package]map[string]bool{}
353         irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
354         objNames[irpkg] = map[string]bool{}
355         for _, m := range irpkg.Members {
356                 if typ, ok := m.(*ir.Type); ok {
357                         objNames[irpkg][typ.Name()] = true
358                 }
359         }
360         for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
361                 objNames[fn.Package()][fn.Name()] = true
362         }
363
364         for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
365                 if code.IsInTest(pass, fn) {
366                         // We don't care about malformed error messages in tests;
367                         // they're usually for direct human consumption, not part
368                         // of an API
369                         continue
370                 }
371                 for _, block := range fn.Blocks {
372                 instrLoop:
373                         for _, ins := range block.Instrs {
374                                 call, ok := ins.(*ir.Call)
375                                 if !ok {
376                                         continue
377                                 }
378                                 if !irutil.IsCallToAny(call.Common(), "errors.New", "fmt.Errorf") {
379                                         continue
380                                 }
381
382                                 k, ok := call.Common().Args[0].(*ir.Const)
383                                 if !ok {
384                                         continue
385                                 }
386
387                                 s := constant.StringVal(k.Value)
388                                 if len(s) == 0 {
389                                         continue
390                                 }
391                                 switch s[len(s)-1] {
392                                 case '.', ':', '!', '\n':
393                                         report.Report(pass, call, "error strings should not end with punctuation or a newline")
394                                 }
395                                 idx := strings.IndexByte(s, ' ')
396                                 if idx == -1 {
397                                         // single word error message, probably not a real
398                                         // error but something used in tests or during
399                                         // debugging
400                                         continue
401                                 }
402                                 word := s[:idx]
403                                 first, n := utf8.DecodeRuneInString(word)
404                                 if !unicode.IsUpper(first) {
405                                         continue
406                                 }
407                                 for _, c := range word[n:] {
408                                         if unicode.IsUpper(c) {
409                                                 // Word is probably an initialism or
410                                                 // multi-word function name
411                                                 continue instrLoop
412                                         }
413                                 }
414
415                                 if strings.ContainsRune(word, '(') {
416                                         // Might be a function call
417                                         continue instrLoop
418                                 }
419                                 word = strings.TrimRightFunc(word, func(r rune) bool { return unicode.IsPunct(r) })
420                                 if objNames[fn.Package()][word] {
421                                         // Word is probably the name of a function or type in this package
422                                         continue
423                                 }
424                                 // First word in error starts with a capital
425                                 // letter, and the word doesn't contain any other
426                                 // capitals, making it unlikely to be an
427                                 // initialism or multi-word function name.
428                                 //
429                                 // It could still be a proper noun, though.
430
431                                 report.Report(pass, call, "error strings should not be capitalized")
432                         }
433                 }
434         }
435         return nil, nil
436 }
437
438 func CheckTimeNames(pass *analysis.Pass) (interface{}, error) {
439         suffixes := []string{
440                 "Sec", "Secs", "Seconds",
441                 "Msec", "Msecs",
442                 "Milli", "Millis", "Milliseconds",
443                 "Usec", "Usecs", "Microseconds",
444                 "MS", "Ms",
445         }
446         fn := func(names []*ast.Ident) {
447                 for _, name := range names {
448                         if _, ok := pass.TypesInfo.Defs[name]; !ok {
449                                 continue
450                         }
451                         T := pass.TypesInfo.TypeOf(name)
452                         if !typeutil.IsType(T, "time.Duration") && !typeutil.IsType(T, "*time.Duration") {
453                                 continue
454                         }
455                         for _, suffix := range suffixes {
456                                 if strings.HasSuffix(name.Name, suffix) {
457                                         report.Report(pass, name, fmt.Sprintf("var %s is of type %v; don't use unit-specific suffix %q", name.Name, T, suffix))
458                                         break
459                                 }
460                         }
461                 }
462         }
463
464         fn2 := func(node ast.Node) {
465                 switch node := node.(type) {
466                 case *ast.ValueSpec:
467                         fn(node.Names)
468                 case *ast.FieldList:
469                         for _, field := range node.List {
470                                 fn(field.Names)
471                         }
472                 case *ast.AssignStmt:
473                         if node.Tok != token.DEFINE {
474                                 break
475                         }
476                         var names []*ast.Ident
477                         for _, lhs := range node.Lhs {
478                                 if lhs, ok := lhs.(*ast.Ident); ok {
479                                         names = append(names, lhs)
480                                 }
481                         }
482                         fn(names)
483                 }
484         }
485
486         code.Preorder(pass, fn2, (*ast.ValueSpec)(nil), (*ast.FieldList)(nil), (*ast.AssignStmt)(nil))
487         return nil, nil
488 }
489
490 func CheckErrorVarNames(pass *analysis.Pass) (interface{}, error) {
491         for _, f := range pass.Files {
492                 for _, decl := range f.Decls {
493                         gen, ok := decl.(*ast.GenDecl)
494                         if !ok || gen.Tok != token.VAR {
495                                 continue
496                         }
497                         for _, spec := range gen.Specs {
498                                 spec := spec.(*ast.ValueSpec)
499                                 if len(spec.Names) != len(spec.Values) {
500                                         continue
501                                 }
502
503                                 for i, name := range spec.Names {
504                                         val := spec.Values[i]
505                                         if !code.IsCallToAny(pass, val, "errors.New", "fmt.Errorf") {
506                                                 continue
507                                         }
508
509                                         if pass.Pkg.Path() == "net/http" && strings.HasPrefix(name.Name, "http2err") {
510                                                 // special case for internal variable names of
511                                                 // bundled HTTP 2 code in net/http
512                                                 continue
513                                         }
514                                         prefix := "err"
515                                         if name.IsExported() {
516                                                 prefix = "Err"
517                                         }
518                                         if !strings.HasPrefix(name.Name, prefix) {
519                                                 report.Report(pass, name, fmt.Sprintf("error var %s should have name of the form %sFoo", name.Name, prefix))
520                                         }
521                                 }
522                         }
523                 }
524         }
525         return nil, nil
526 }
527
528 var httpStatusCodes = map[int]string{
529         100: "StatusContinue",
530         101: "StatusSwitchingProtocols",
531         102: "StatusProcessing",
532         200: "StatusOK",
533         201: "StatusCreated",
534         202: "StatusAccepted",
535         203: "StatusNonAuthoritativeInfo",
536         204: "StatusNoContent",
537         205: "StatusResetContent",
538         206: "StatusPartialContent",
539         207: "StatusMultiStatus",
540         208: "StatusAlreadyReported",
541         226: "StatusIMUsed",
542         300: "StatusMultipleChoices",
543         301: "StatusMovedPermanently",
544         302: "StatusFound",
545         303: "StatusSeeOther",
546         304: "StatusNotModified",
547         305: "StatusUseProxy",
548         307: "StatusTemporaryRedirect",
549         308: "StatusPermanentRedirect",
550         400: "StatusBadRequest",
551         401: "StatusUnauthorized",
552         402: "StatusPaymentRequired",
553         403: "StatusForbidden",
554         404: "StatusNotFound",
555         405: "StatusMethodNotAllowed",
556         406: "StatusNotAcceptable",
557         407: "StatusProxyAuthRequired",
558         408: "StatusRequestTimeout",
559         409: "StatusConflict",
560         410: "StatusGone",
561         411: "StatusLengthRequired",
562         412: "StatusPreconditionFailed",
563         413: "StatusRequestEntityTooLarge",
564         414: "StatusRequestURITooLong",
565         415: "StatusUnsupportedMediaType",
566         416: "StatusRequestedRangeNotSatisfiable",
567         417: "StatusExpectationFailed",
568         418: "StatusTeapot",
569         422: "StatusUnprocessableEntity",
570         423: "StatusLocked",
571         424: "StatusFailedDependency",
572         426: "StatusUpgradeRequired",
573         428: "StatusPreconditionRequired",
574         429: "StatusTooManyRequests",
575         431: "StatusRequestHeaderFieldsTooLarge",
576         451: "StatusUnavailableForLegalReasons",
577         500: "StatusInternalServerError",
578         501: "StatusNotImplemented",
579         502: "StatusBadGateway",
580         503: "StatusServiceUnavailable",
581         504: "StatusGatewayTimeout",
582         505: "StatusHTTPVersionNotSupported",
583         506: "StatusVariantAlsoNegotiates",
584         507: "StatusInsufficientStorage",
585         508: "StatusLoopDetected",
586         510: "StatusNotExtended",
587         511: "StatusNetworkAuthenticationRequired",
588 }
589
590 func CheckHTTPStatusCodes(pass *analysis.Pass) (interface{}, error) {
591         whitelist := map[string]bool{}
592         for _, code := range config.For(pass).HTTPStatusCodeWhitelist {
593                 whitelist[code] = true
594         }
595         fn := func(node ast.Node) {
596                 call := node.(*ast.CallExpr)
597
598                 var arg int
599                 switch code.CallName(pass, call) {
600                 case "net/http.Error":
601                         arg = 2
602                 case "net/http.Redirect":
603                         arg = 3
604                 case "net/http.StatusText":
605                         arg = 0
606                 case "net/http.RedirectHandler":
607                         arg = 1
608                 default:
609                         return
610                 }
611                 lit, ok := call.Args[arg].(*ast.BasicLit)
612                 if !ok {
613                         return
614                 }
615                 if whitelist[lit.Value] {
616                         return
617                 }
618
619                 n, err := strconv.Atoi(lit.Value)
620                 if err != nil {
621                         return
622                 }
623                 s, ok := httpStatusCodes[n]
624                 if !ok {
625                         return
626                 }
627                 report.Report(pass, lit, fmt.Sprintf("should use constant http.%s instead of numeric literal %d", s, n),
628                         report.FilterGenerated(),
629                         report.Fixes(edit.Fix(fmt.Sprintf("use http.%s instead of %d", s, n), edit.ReplaceWithString(pass.Fset, lit, "http."+s))))
630         }
631         code.Preorder(pass, fn, (*ast.CallExpr)(nil))
632         return nil, nil
633 }
634
635 func CheckDefaultCaseOrder(pass *analysis.Pass) (interface{}, error) {
636         fn := func(node ast.Node) {
637                 stmt := node.(*ast.SwitchStmt)
638                 list := stmt.Body.List
639                 for i, c := range list {
640                         if c.(*ast.CaseClause).List == nil && i != 0 && i != len(list)-1 {
641                                 report.Report(pass, c, "default case should be first or last in switch statement", report.FilterGenerated())
642                                 break
643                         }
644                 }
645         }
646         code.Preorder(pass, fn, (*ast.SwitchStmt)(nil))
647         return nil, nil
648 }
649
650 var (
651         checkYodaConditionsQ = pattern.MustParse(`(BinaryExpr left@(BasicLit _ _) tok@(Or "==" "!=") right@(Not (BasicLit _ _)))`)
652         checkYodaConditionsR = pattern.MustParse(`(BinaryExpr right tok left)`)
653 )
654
655 func CheckYodaConditions(pass *analysis.Pass) (interface{}, error) {
656         fn := func(node ast.Node) {
657                 if _, edits, ok := code.MatchAndEdit(pass, checkYodaConditionsQ, checkYodaConditionsR, node); ok {
658                         report.Report(pass, node, "don't use Yoda conditions",
659                                 report.FilterGenerated(),
660                                 report.Fixes(edit.Fix("un-Yoda-fy", edits...)))
661                 }
662         }
663         code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
664         return nil, nil
665 }
666
667 func CheckInvisibleCharacters(pass *analysis.Pass) (interface{}, error) {
668         fn := func(node ast.Node) {
669                 lit := node.(*ast.BasicLit)
670                 if lit.Kind != token.STRING {
671                         return
672                 }
673
674                 type invalid struct {
675                         r   rune
676                         off int
677                 }
678                 var invalids []invalid
679                 hasFormat := false
680                 hasControl := false
681                 for off, r := range lit.Value {
682                         if unicode.Is(unicode.Cf, r) {
683                                 invalids = append(invalids, invalid{r, off})
684                                 hasFormat = true
685                         } else if unicode.Is(unicode.Cc, r) && r != '\n' && r != '\t' && r != '\r' {
686                                 invalids = append(invalids, invalid{r, off})
687                                 hasControl = true
688                         }
689                 }
690
691                 switch len(invalids) {
692                 case 0:
693                         return
694                 case 1:
695                         var kind string
696                         if hasFormat {
697                                 kind = "format"
698                         } else if hasControl {
699                                 kind = "control"
700                         } else {
701                                 panic("unreachable")
702                         }
703
704                         r := invalids[0]
705                         msg := fmt.Sprintf("string literal contains the Unicode %s character %U, consider using the %q escape sequence instead", kind, r.r, r.r)
706
707                         replacement := strconv.QuoteRune(r.r)
708                         replacement = replacement[1 : len(replacement)-1]
709                         edit := analysis.SuggestedFix{
710                                 Message: fmt.Sprintf("replace %s character %U with %q", kind, r.r, r.r),
711                                 TextEdits: []analysis.TextEdit{{
712                                         Pos:     lit.Pos() + token.Pos(r.off),
713                                         End:     lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)),
714                                         NewText: []byte(replacement),
715                                 }},
716                         }
717                         delete := analysis.SuggestedFix{
718                                 Message: fmt.Sprintf("delete %s character %U", kind, r),
719                                 TextEdits: []analysis.TextEdit{{
720                                         Pos: lit.Pos() + token.Pos(r.off),
721                                         End: lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)),
722                                 }},
723                         }
724                         report.Report(pass, lit, msg, report.Fixes(edit, delete))
725                 default:
726                         var kind string
727                         if hasFormat && hasControl {
728                                 kind = "format and control"
729                         } else if hasFormat {
730                                 kind = "format"
731                         } else if hasControl {
732                                 kind = "control"
733                         } else {
734                                 panic("unreachable")
735                         }
736
737                         msg := fmt.Sprintf("string literal contains Unicode %s characters, consider using escape sequences instead", kind)
738                         var edits []analysis.TextEdit
739                         var deletions []analysis.TextEdit
740                         for _, r := range invalids {
741                                 replacement := strconv.QuoteRune(r.r)
742                                 replacement = replacement[1 : len(replacement)-1]
743                                 edits = append(edits, analysis.TextEdit{
744                                         Pos:     lit.Pos() + token.Pos(r.off),
745                                         End:     lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)),
746                                         NewText: []byte(replacement),
747                                 })
748                                 deletions = append(deletions, analysis.TextEdit{
749                                         Pos: lit.Pos() + token.Pos(r.off),
750                                         End: lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)),
751                                 })
752                         }
753                         edit := analysis.SuggestedFix{
754                                 Message:   fmt.Sprintf("replace all %s characters with escape sequences", kind),
755                                 TextEdits: edits,
756                         }
757                         delete := analysis.SuggestedFix{
758                                 Message:   fmt.Sprintf("delete all %s characters", kind),
759                                 TextEdits: deletions,
760                         }
761                         report.Report(pass, lit, msg, report.Fixes(edit, delete))
762                 }
763         }
764         code.Preorder(pass, fn, (*ast.BasicLit)(nil))
765         return nil, nil
766 }
767
768 func CheckExportedFunctionDocs(pass *analysis.Pass) (interface{}, error) {
769         fn := func(node ast.Node) {
770                 if code.IsInTest(pass, node) {
771                         return
772                 }
773
774                 decl := node.(*ast.FuncDecl)
775                 if decl.Doc == nil {
776                         return
777                 }
778                 if !ast.IsExported(decl.Name.Name) {
779                         return
780                 }
781                 kind := "function"
782                 if decl.Recv != nil {
783                         kind = "method"
784                         switch T := decl.Recv.List[0].Type.(type) {
785                         case *ast.StarExpr:
786                                 if !ast.IsExported(T.X.(*ast.Ident).Name) {
787                                         return
788                                 }
789                         case *ast.Ident:
790                                 if !ast.IsExported(T.Name) {
791                                         return
792                                 }
793                         default:
794                                 lint.ExhaustiveTypeSwitch(T)
795                         }
796                 }
797                 prefix := decl.Name.Name + " "
798                 if !strings.HasPrefix(decl.Doc.Text(), prefix) {
799                         report.Report(pass, decl.Doc, fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, decl.Name.Name, prefix), report.FilterGenerated())
800                 }
801         }
802
803         code.Preorder(pass, fn, (*ast.FuncDecl)(nil))
804         return nil, nil
805 }
806
807 func CheckExportedTypeDocs(pass *analysis.Pass) (interface{}, error) {
808         var genDecl *ast.GenDecl
809         fn := func(node ast.Node, push bool) bool {
810                 if !push {
811                         genDecl = nil
812                         return false
813                 }
814                 if code.IsInTest(pass, node) {
815                         return false
816                 }
817
818                 switch node := node.(type) {
819                 case *ast.GenDecl:
820                         if node.Tok == token.IMPORT {
821                                 return false
822                         }
823                         genDecl = node
824                         return true
825                 case *ast.TypeSpec:
826                         if !ast.IsExported(node.Name.Name) {
827                                 return false
828                         }
829
830                         doc := node.Doc
831                         if doc == nil {
832                                 if len(genDecl.Specs) != 1 {
833                                         // more than one spec in the GenDecl, don't validate the
834                                         // docstring
835                                         return false
836                                 }
837                                 if genDecl.Lparen.IsValid() {
838                                         // 'type ( T )' is weird, don't guess the user's intention
839                                         return false
840                                 }
841                                 doc = genDecl.Doc
842                                 if doc == nil {
843                                         return false
844                                 }
845                         }
846
847                         s := doc.Text()
848                         articles := [...]string{"A", "An", "The"}
849                         for _, a := range articles {
850                                 if strings.HasPrefix(s, a+" ") {
851                                         s = s[len(a)+1:]
852                                         break
853                                 }
854                         }
855                         if !strings.HasPrefix(s, node.Name.Name+" ") {
856                                 report.Report(pass, doc, fmt.Sprintf(`comment on exported type %s should be of the form "%s ..." (with optional leading article)`, node.Name.Name, node.Name.Name), report.FilterGenerated())
857                         }
858                         return false
859                 case *ast.FuncLit, *ast.FuncDecl:
860                         return false
861                 default:
862                         lint.ExhaustiveTypeSwitch(node)
863                         return false
864                 }
865         }
866
867         pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.GenDecl)(nil), (*ast.TypeSpec)(nil), (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil)}, fn)
868         return nil, nil
869 }
870
871 func CheckExportedVarDocs(pass *analysis.Pass) (interface{}, error) {
872         var genDecl *ast.GenDecl
873         fn := func(node ast.Node, push bool) bool {
874                 if !push {
875                         genDecl = nil
876                         return false
877                 }
878                 if code.IsInTest(pass, node) {
879                         return false
880                 }
881
882                 switch node := node.(type) {
883                 case *ast.GenDecl:
884                         if node.Tok == token.IMPORT {
885                                 return false
886                         }
887                         genDecl = node
888                         return true
889                 case *ast.ValueSpec:
890                         if genDecl.Lparen.IsValid() || len(node.Names) > 1 {
891                                 // Don't try to guess the user's intention
892                                 return false
893                         }
894                         name := node.Names[0].Name
895                         if !ast.IsExported(name) {
896                                 return false
897                         }
898                         if genDecl.Doc == nil {
899                                 return false
900                         }
901                         prefix := name + " "
902                         if !strings.HasPrefix(genDecl.Doc.Text(), prefix) {
903                                 kind := "var"
904                                 if genDecl.Tok == token.CONST {
905                                         kind = "const"
906                                 }
907                                 report.Report(pass, genDecl.Doc, fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, name, prefix), report.FilterGenerated())
908                         }
909                         return false
910                 case *ast.FuncLit, *ast.FuncDecl:
911                         return false
912                 default:
913                         lint.ExhaustiveTypeSwitch(node)
914                         return false
915                 }
916         }
917
918         pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.GenDecl)(nil), (*ast.ValueSpec)(nil), (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil)}, fn)
919         return nil, nil
920 }