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