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