1package checkers 2 3import ( 4 "go/ast" 5 "go/token" 6 "regexp" 7 "strings" 8 9 "github.com/go-critic/go-critic/checkers/internal/astwalk" 10 "github.com/go-critic/go-critic/framework/linter" 11) 12 13func init() { 14 var info linter.CheckerInfo 15 info.Name = "docStub" 16 info.Tags = []string{"style", "experimental"} 17 info.Summary = "Detects comments that silence go lint complaints about doc-comment" 18 info.Before = ` 19// Foo ... 20func Foo() { 21}` 22 info.After = ` 23// (A) - remove the doc-comment stub 24func Foo() {} 25// (B) - replace it with meaningful comment 26// Foo is a demonstration-only function. 27func Foo() {}` 28 29 collection.AddChecker(&info, func(ctx *linter.CheckerContext) (linter.FileWalker, error) { 30 re := `(?i)^\.\.\.$|^\.$|^xxx\.?$|^whatever\.?$` 31 c := &docStubChecker{ 32 ctx: ctx, 33 stubCommentRE: regexp.MustCompile(re), 34 } 35 return c, nil 36 }) 37} 38 39type docStubChecker struct { 40 astwalk.WalkHandler 41 ctx *linter.CheckerContext 42 43 stubCommentRE *regexp.Regexp 44} 45 46func (c *docStubChecker) WalkFile(f *ast.File) { 47 for _, decl := range f.Decls { 48 switch decl := decl.(type) { 49 case *ast.FuncDecl: 50 c.visitDoc(decl, decl.Name, decl.Doc, false) 51 case *ast.GenDecl: 52 if decl.Tok != token.TYPE { 53 continue 54 } 55 if len(decl.Specs) == 1 { 56 spec := decl.Specs[0].(*ast.TypeSpec) 57 // Only 1 spec, use doc from the decl itself. 58 c.visitDoc(spec, spec.Name, decl.Doc, true) 59 } 60 // N specs, use per-spec doc. 61 for _, spec := range decl.Specs { 62 spec := spec.(*ast.TypeSpec) 63 c.visitDoc(spec, spec.Name, spec.Doc, true) 64 } 65 } 66 } 67} 68 69func (c *docStubChecker) visitDoc(decl ast.Node, sym *ast.Ident, doc *ast.CommentGroup, article bool) { 70 if !sym.IsExported() || doc == nil { 71 return 72 } 73 line := strings.TrimSpace(doc.List[0].Text[len("//"):]) 74 if article { 75 // Skip optional article. 76 for _, a := range []string{"The ", "An ", "A "} { 77 if strings.HasPrefix(line, a) { 78 line = line[len(a):] 79 break 80 } 81 } 82 } 83 if !strings.HasPrefix(line, sym.Name) { 84 return 85 } 86 line = strings.TrimSpace(line[len(sym.Name):]) 87 // Now try to detect the "stub" part. 88 if c.stubCommentRE.MatchString(line) { 89 c.warn(decl) 90 } 91} 92 93func (c *docStubChecker) warn(cause ast.Node) { 94 c.ctx.Warn(cause, "silencing go lint doc-comment warnings is unadvised") 95} 96