返回 DeepSeek-Reasonix
comments.go
根目录 / tools / repolint / comments.go
1 package main
2
3 import (
4 "fmt"
5 "go/ast"
6 "go/parser"
7 "go/token"
8 "regexp"
9 "strings"
10 )
11
12 const (
13 capDocGoPackage = 40
14 capPackageDoc = 8
15 capDeclDoc = 15
16 capFieldDoc = 3
17 capFloating = 3
18 )
19
20 var (
21 bannerRe = regexp.MustCompile(`^//\s*[-=~*_+#/\x{2500}-\x{257F}]{6,}\s*$`)
22 labelledRe = regexp.MustCompile(`^//\s*[-=~*_+#\x{2500}-\x{257F}]{3,}.*[-=~*_+#\x{2500}-\x{257F}]{3,}\s*$`)
23 anchoredRe = regexp.MustCompile(`\b(TODO|HACK)\(#\d+\)`)
24 bareMarkRe = regexp.MustCompile(`\b(TODO|HACK)\b`)
25 fixmeRe = regexp.MustCompile(`\bFIXME\b`)
26 narrativeRe = regexp.MustCompile(`(?i)\b(phase|stage)\s+\d+[a-z]?\d*\b`)
27 directiveRe = regexp.MustCompile(`^//(go:|lint:|nolint|export |sys |line )`)
28 docCodeRe = regexp.MustCompile(`^//(\t| {4,})`)
29 )
30
31 func checkComments(s *sourceFile) []Finding {
32 var out []Finding
33 limits := s.commentLimits()
34 preamble := s.cgoPreamble()
35
36 for _, cg := range s.file.Comments {
37 if cg == preamble {
38 continue
39 }
40 limit, attached := limits[cg]
41 if !attached {
42 limit = capFloating
43 }
44 start, end := s.line(cg.Pos()), s.line(cg.End())
45 if n := end - start + 1; n > limit {
46 out = append(out, Finding{s.rel, start, ruleEssay,
47 fmt.Sprintf("%d-line comment block exceeds the %d-line limit for this position", n, limit), n - limit})
48 }
49 out = append(out, s.checkCommentText(cg, attached)...)
50 }
51 return out
52 }
53
54 func (s *sourceFile) commentLimits() map[*ast.CommentGroup]int {
55 limits := map[*ast.CommentGroup]int{}
56 if s.file.Doc != nil {
57 if s.rel == "doc.go" || strings.HasSuffix(s.rel, "/doc.go") {
58 limits[s.file.Doc] = capDocGoPackage
59 } else {
60 limits[s.file.Doc] = capPackageDoc
61 }
62 }
63 ast.Inspect(s.file, func(n ast.Node) bool {
64 switch d := n.(type) {
65 case *ast.FuncDecl:
66 set(limits, d.Doc, capDeclDoc)
67 case *ast.GenDecl:
68 set(limits, d.Doc, capDeclDoc)
69 case *ast.TypeSpec:
70 set(limits, d.Doc, capDeclDoc)
71 case *ast.ValueSpec:
72 set(limits, d.Doc, capDeclDoc)
73 case *ast.Field:
74 set(limits, d.Doc, capFieldDoc)
75 }
76 return true
77 })
78 return limits
79 }
80
81 // The block comment preceding `import "C"` is compiler input, not prose.
82 func (s *sourceFile) cgoPreamble() *ast.CommentGroup {
83 for _, decl := range s.file.Decls {
84 gen, ok := decl.(*ast.GenDecl)
85 if !ok || gen.Tok != token.IMPORT || gen.Doc == nil {
86 continue
87 }
88 for _, spec := range gen.Specs {
89 if imp, ok := spec.(*ast.ImportSpec); ok && imp.Path.Value == `"C"` {
90 return gen.Doc
91 }
92 }
93 }
94 return nil
95 }
96
97 func set(limits map[*ast.CommentGroup]int, cg *ast.CommentGroup, limit int) {
98 if cg != nil {
99 limits[cg] = limit
100 }
101 }
102
103 func (s *sourceFile) checkCommentText(cg *ast.CommentGroup, attached bool) []Finding {
104 var out []Finding
105 for _, c := range cg.List {
106 base, ownsLine := s.line(c.Pos()), !s.trailing(c.Pos())
107 // Commented-out code sits in a body or between declarations; a doc
108 // comment describing a wire format is prose that happens to parse.
109 deadCodeCandidate := ownsLine && !attached
110 for i, text := range strings.Split(c.Text, "\n") {
111 line, trimmed := base+i, strings.TrimSpace(text)
112 if directiveRe.MatchString(trimmed) {
113 continue
114 }
115 switch {
116 case bannerRe.MatchString(trimmed), labelledRe.MatchString(trimmed):
117 out = append(out, Finding{s.rel, line, ruleBanner, "section-banner separator", 1})
118 case deadCodeCandidate && !docCodeRe.MatchString(text) && looksLikeCode(trimmed):
119 out = append(out, Finding{s.rel, line, ruleDeadCode, "commented-out code", 1})
120 }
121 if fixmeRe.MatchString(trimmed) {
122 out = append(out, Finding{s.rel, line, ruleMarker, "FIXME is banned: fix it or open an issue and use TODO(#nnn)", 1})
123 } else if bareMarkRe.MatchString(trimmed) && !anchoredRe.MatchString(trimmed) {
124 out = append(out, Finding{s.rel, line, ruleMarker, "TODO/HACK needs a (#nnn) issue anchor", 1})
125 }
126 if narrativeRe.MatchString(trimmed) {
127 out = append(out, Finding{s.rel, line, ruleNarrative, "phase/stage narrative belongs in the commit message", 1})
128 }
129 }
130 }
131 return out
132 }
133
134 func looksLikeCode(text string) bool {
135 body := strings.TrimSpace(strings.TrimLeft(text, "/*"))
136 body = strings.TrimSuffix(body, "*/")
137 if len(body) < 4 || len(body) > 160 {
138 return false
139 }
140 if !strings.Contains(body, ":=") && !strings.ContainsAny(body, "(){};") {
141 return false
142 }
143 _, err := parser.ParseFile(token.NewFileSet(), "", "package p\nfunc _() {\n"+body+"\n}\n", parser.SkipObjectResolution)
144 return err == nil
145 }
146
146 lines GO