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"
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"
33 func CheckPackageComment(pass *analysis.Pass) (interface{}, error) {
34 // - At least one file in a non-main package should have a package comment
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
42 if pass.Pkg.Name() == "main" {
46 for _, f := range pass.Files {
47 if code.IsInTest(pass, f) {
50 if f.Doc != nil && len(f.Doc.List) > 0 {
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))
60 for _, f := range pass.Files {
61 if code.IsInTest(pass, f) {
64 report.Report(pass, f, "at least one file in a package should have a package comment", report.ShortRange())
70 func CheckDotImports(pass *analysis.Pass) (interface{}, error) {
71 for _, f := range pass.Files {
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 {
82 if imp.Name != nil && imp.Name.Name == "." && !code.IsInTest(pass, f) {
83 report.Report(pass, imp, "should not use dot imports", report.FilterGenerated())
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)
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
106 // If there's more than one import per path, we flag that
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)))
113 report.Report(pass, value[0], s, opts...)
120 func CheckBlankImports(pass *analysis.Pass) (interface{}, error) {
122 for _, f := range pass.Files {
123 if code.IsMainLike(pass) || code.IsInTest(pass, f) {
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.
131 // We don't directly process the GenDecl so that we can
132 // correctly handle the following:
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) {
144 if node.Tok != token.IMPORT {
147 if node.Lparen == token.NoPos && node.Doc != nil {
148 skip[node.Specs[0]] = true
154 for i, imp := range f.Imports {
155 pos := fset.Position(imp.Pos())
157 if !astutil.IsBlank(imp.Name) {
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
164 prev := f.Imports[i-1]
165 prevPos := fset.Position(prev.Pos())
166 if pos.Line-1 == prevPos.Line && astutil.IsBlank(prev.Name) {
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")
179 func CheckIncDec(pass *analysis.Pass) (interface{}, error) {
180 // TODO(dh): this can be noisy for function bodies that look like this:
186 fn := func(node ast.Node) {
187 assign := node.(*ast.AssignStmt)
188 if assign.Tok != token.ADD_ASSIGN && assign.Tok != token.SUB_ASSIGN {
191 if (len(assign.Lhs) != 1 || len(assign.Rhs) != 1) ||
192 !astutil.IsIntLiteral(assign.Rhs[0], "1") {
198 case token.ADD_ASSIGN:
200 case token.SUB_ASSIGN:
204 report.Report(pass, assign, fmt.Sprintf("should replace %s with %s%s", report.Render(pass, assign), report.Render(pass, assign.Lhs[0]), suffix))
206 code.Preorder(pass, fn, (*ast.AssignStmt)(nil))
210 func CheckErrorReturn(pass *analysis.Pass) (interface{}, error) {
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 {
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.
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())
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 {
241 if !ast.IsExported(fn.Name()) || code.IsMain(pass) || code.IsInTest(pass, fn) {
244 sig := fn.Type().(*types.Signature)
245 if sig.Recv() != nil && !ast.IsExported(typeutil.Dereference(sig.Recv().Type()).(*types.Named).Obj().Name()) {
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")
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
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())
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())
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{}
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
299 if typeutil.Dereference(recv.Type()) != T.Type() {
300 // skip embedded methods
306 if recv.Name() != "" && recv.Name() != "_" {
314 for name, count := range names {
315 seen = append(seen, fmt.Sprintf("%dx %q", count, name))
319 report.Report(pass, firstFn, fmt.Sprintf("methods on the same type should have the same receiver name (seen %s)", strings.Join(seen, ", ")))
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) {
329 for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
330 if fn.Synthetic != 0 || fn.Parent() != nil {
333 params := fn.Signature.Params()
334 if params.Len() < 2 {
337 if types.TypeString(params.At(0).Type(), nil) == "context.Context" {
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())
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
360 for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
361 objNames[fn.Package()][fn.Name()] = true
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
371 for _, block := range fn.Blocks {
373 for _, ins := range block.Instrs {
374 call, ok := ins.(*ir.Call)
378 if !irutil.IsCallToAny(call.Common(), "errors.New", "fmt.Errorf") {
382 k, ok := call.Common().Args[0].(*ir.Const)
387 s := constant.StringVal(k.Value)
392 case '.', ':', '!', '\n':
393 report.Report(pass, call, "error strings should not end with punctuation or a newline")
395 idx := strings.IndexByte(s, ' ')
397 // single word error message, probably not a real
398 // error but something used in tests or during
403 first, n := utf8.DecodeRuneInString(word)
404 if !unicode.IsUpper(first) {
407 for _, c := range word[n:] {
408 if unicode.IsUpper(c) {
409 // Word is probably an initialism or
410 // multi-word function name
415 if strings.ContainsRune(word, '(') {
416 // Might be a function call
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
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.
429 // It could still be a proper noun, though.
431 report.Report(pass, call, "error strings should not be capitalized")
438 func CheckTimeNames(pass *analysis.Pass) (interface{}, error) {
439 suffixes := []string{
440 "Sec", "Secs", "Seconds",
442 "Milli", "Millis", "Milliseconds",
443 "Usec", "Usecs", "Microseconds",
446 fn := func(names []*ast.Ident) {
447 for _, name := range names {
448 if _, ok := pass.TypesInfo.Defs[name]; !ok {
451 T := pass.TypesInfo.TypeOf(name)
452 if !typeutil.IsType(T, "time.Duration") && !typeutil.IsType(T, "*time.Duration") {
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))
464 fn2 := func(node ast.Node) {
465 switch node := node.(type) {
469 for _, field := range node.List {
472 case *ast.AssignStmt:
473 if node.Tok != token.DEFINE {
476 var names []*ast.Ident
477 for _, lhs := range node.Lhs {
478 if lhs, ok := lhs.(*ast.Ident); ok {
479 names = append(names, lhs)
486 code.Preorder(pass, fn2, (*ast.ValueSpec)(nil), (*ast.FieldList)(nil), (*ast.AssignStmt)(nil))
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 {
497 for _, spec := range gen.Specs {
498 spec := spec.(*ast.ValueSpec)
499 if len(spec.Names) != len(spec.Values) {
503 for i, name := range spec.Names {
504 val := spec.Values[i]
505 if !code.IsCallToAny(pass, val, "errors.New", "fmt.Errorf") {
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
515 if name.IsExported() {
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))
528 var httpStatusCodes = map[int]string{
529 100: "StatusContinue",
530 101: "StatusSwitchingProtocols",
531 102: "StatusProcessing",
533 201: "StatusCreated",
534 202: "StatusAccepted",
535 203: "StatusNonAuthoritativeInfo",
536 204: "StatusNoContent",
537 205: "StatusResetContent",
538 206: "StatusPartialContent",
539 207: "StatusMultiStatus",
540 208: "StatusAlreadyReported",
542 300: "StatusMultipleChoices",
543 301: "StatusMovedPermanently",
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",
561 411: "StatusLengthRequired",
562 412: "StatusPreconditionFailed",
563 413: "StatusRequestEntityTooLarge",
564 414: "StatusRequestURITooLong",
565 415: "StatusUnsupportedMediaType",
566 416: "StatusRequestedRangeNotSatisfiable",
567 417: "StatusExpectationFailed",
569 422: "StatusUnprocessableEntity",
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",
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
595 fn := func(node ast.Node) {
596 call := node.(*ast.CallExpr)
599 switch code.CallName(pass, call) {
600 case "net/http.Error":
602 case "net/http.Redirect":
604 case "net/http.StatusText":
606 case "net/http.RedirectHandler":
611 lit, ok := call.Args[arg].(*ast.BasicLit)
615 if whitelist[lit.Value] {
619 n, err := strconv.Atoi(lit.Value)
623 s, ok := httpStatusCodes[n]
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))))
631 code.Preorder(pass, fn, (*ast.CallExpr)(nil))
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())
646 code.Preorder(pass, fn, (*ast.SwitchStmt)(nil))
651 checkYodaConditionsQ = pattern.MustParse(`(BinaryExpr left@(BasicLit _ _) tok@(Or "==" "!=") right@(Not (BasicLit _ _)))`)
652 checkYodaConditionsR = pattern.MustParse(`(BinaryExpr right tok left)`)
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...)))
663 code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
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 {
674 type invalid struct {
678 var invalids []invalid
681 for off, r := range lit.Value {
682 if unicode.Is(unicode.Cf, r) {
683 invalids = append(invalids, invalid{r, off})
685 } else if unicode.Is(unicode.Cc, r) && r != '\n' && r != '\t' && r != '\r' {
686 invalids = append(invalids, invalid{r, off})
691 switch len(invalids) {
698 } else if hasControl {
705 msg := fmt.Sprintf("string literal contains the Unicode %s character %U, consider using the %q escape sequence instead", kind, r.r, r.r)
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),
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)),
724 report.Report(pass, lit, msg, report.Fixes(edit, delete))
727 if hasFormat && hasControl {
728 kind = "format and control"
729 } else if hasFormat {
731 } else if hasControl {
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),
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)),
753 edit := analysis.SuggestedFix{
754 Message: fmt.Sprintf("replace all %s characters with escape sequences", kind),
757 delete := analysis.SuggestedFix{
758 Message: fmt.Sprintf("delete all %s characters", kind),
759 TextEdits: deletions,
761 report.Report(pass, lit, msg, report.Fixes(edit, delete))
764 code.Preorder(pass, fn, (*ast.BasicLit)(nil))
768 func CheckExportedFunctionDocs(pass *analysis.Pass) (interface{}, error) {
769 fn := func(node ast.Node) {
770 if code.IsInTest(pass, node) {
774 decl := node.(*ast.FuncDecl)
778 if !ast.IsExported(decl.Name.Name) {
782 if decl.Recv != nil {
784 switch T := decl.Recv.List[0].Type.(type) {
786 if !ast.IsExported(T.X.(*ast.Ident).Name) {
790 if !ast.IsExported(T.Name) {
794 lint.ExhaustiveTypeSwitch(T)
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())
803 code.Preorder(pass, fn, (*ast.FuncDecl)(nil))
807 func CheckExportedTypeDocs(pass *analysis.Pass) (interface{}, error) {
808 var genDecl *ast.GenDecl
809 fn := func(node ast.Node, push bool) bool {
814 if code.IsInTest(pass, node) {
818 switch node := node.(type) {
820 if node.Tok == token.IMPORT {
826 if !ast.IsExported(node.Name.Name) {
832 if len(genDecl.Specs) != 1 {
833 // more than one spec in the GenDecl, don't validate the
837 if genDecl.Lparen.IsValid() {
838 // 'type ( T )' is weird, don't guess the user's intention
848 articles := [...]string{"A", "An", "The"}
849 for _, a := range articles {
850 if strings.HasPrefix(s, a+" ") {
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())
859 case *ast.FuncLit, *ast.FuncDecl:
862 lint.ExhaustiveTypeSwitch(node)
867 pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.GenDecl)(nil), (*ast.TypeSpec)(nil), (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil)}, fn)
871 func CheckExportedVarDocs(pass *analysis.Pass) (interface{}, error) {
872 var genDecl *ast.GenDecl
873 fn := func(node ast.Node, push bool) bool {
878 if code.IsInTest(pass, node) {
882 switch node := node.(type) {
884 if node.Tok == token.IMPORT {
890 if genDecl.Lparen.IsValid() || len(node.Names) > 1 {
891 // Don't try to guess the user's intention
894 name := node.Names[0].Name
895 if !ast.IsExported(name) {
898 if genDecl.Doc == nil {
902 if !strings.HasPrefix(genDecl.Doc.Text(), prefix) {
904 if genDecl.Tok == token.CONST {
907 report.Report(pass, genDecl.Doc, fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, name, prefix), report.FilterGenerated())
910 case *ast.FuncLit, *ast.FuncDecl:
913 lint.ExhaustiveTypeSwitch(node)
918 pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.GenDecl)(nil), (*ast.ValueSpec)(nil), (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil)}, fn)