1 package st1000
2 3 import (
4 "fmt"
5 "go/ast"
6 "strings"
7 8 "honnef.co/go/tools/analysis/code"
9 "honnef.co/go/tools/analysis/lint"
10 "honnef.co/go/tools/analysis/report"
11 12 "golang.org/x/tools/go/analysis"
13 )
14 15 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
16 Analyzer: &analysis.Analyzer{
17 Name: "ST1000",
18 Run: run,
19 },
20 Doc: &lint.RawDocumentation{
21 Title: `Incorrect or missing package comment`,
22 Text: `Packages must have a package comment that is formatted according to
23 the guidelines laid out in
24 https://go.dev/wiki/CodeReviewComments#package-comments.`,
25 Since: "2019.1",
26 NonDefault: true,
27 MergeIf: lint.MergeIfAny,
28 },
29 })
30 31 var Analyzer = SCAnalyzer.Analyzer
32 33 func run(pass *analysis.Pass) (interface{}, error) {
34 // - At least one file in a non-main package should have a package comment
35 //
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
40 // the real world.
41 42 if pass.Pkg.Name() == "main" {
43 return nil, nil
44 }
45 hasDocs := false
46 for _, f := range pass.Files {
47 if code.IsInTest(pass, f) {
48 continue
49 }
50 text, ok := docText(f.Doc)
51 if ok {
52 hasDocs = true
53 prefix := "Package " + f.Name.Name
54 isNonAlpha := func(b byte) bool {
55 // This check only considers ASCII, which can lead to false negatives for some malformed package
56 // comments.
57 return !((b >= '0' && b <= '9') || (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z'))
58 }
59 if !strings.HasPrefix(text, prefix) || (len(text) > len(prefix) && !isNonAlpha(text[len(prefix)])) {
60 report.Report(pass, f.Doc, fmt.Sprintf(`package comment should be of the form "%s..."`, prefix))
61 }
62 }
63 }
64 65 if !hasDocs {
66 for _, f := range pass.Files {
67 if code.IsInTest(pass, f) {
68 continue
69 }
70 report.Report(pass, f, "at least one file in a package should have a package comment", report.ShortRange())
71 }
72 }
73 return nil, nil
74 }
75 76 func docText(doc *ast.CommentGroup) (string, bool) {
77 if doc == nil {
78 return "", false
79 }
80 // We trim spaces primarily because of /**/ style comments, which often have leading space.
81 text := strings.TrimSpace(doc.Text())
82 return text, text != ""
83 }
84