github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/stylecheck/st1022/st1022.go (about) 1 package st1022 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/token" 7 "strings" 8 9 "github.com/amarpal/go-tools/analysis/code" 10 "github.com/amarpal/go-tools/analysis/facts/generated" 11 "github.com/amarpal/go-tools/analysis/lint" 12 "github.com/amarpal/go-tools/analysis/report" 13 14 "golang.org/x/tools/go/analysis" 15 "golang.org/x/tools/go/analysis/passes/inspect" 16 "golang.org/x/tools/go/ast/inspector" 17 ) 18 19 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ 20 Analyzer: &analysis.Analyzer{ 21 Name: "ST1022", 22 Run: run, 23 Requires: []*analysis.Analyzer{generated.Analyzer, inspect.Analyzer}, 24 }, 25 Doc: &lint.Documentation{ 26 Title: "The documentation of an exported variable or constant should start with variable's name", 27 Text: `Doc comments work best as complete sentences, which 28 allow a wide variety of automated presentations. The first sentence 29 should be a one-sentence summary that starts with the name being 30 declared. 31 32 If every doc comment begins with the name of the item it describes, 33 you can use the \'doc\' subcommand of the \'go\' tool and run the output 34 through grep. 35 36 See https://golang.org/doc/effective_go.html#commentary for more 37 information on how to write good documentation.`, 38 Since: "2020.1", 39 NonDefault: true, 40 MergeIf: lint.MergeIfAny, 41 }, 42 }) 43 44 var Analyzer = SCAnalyzer.Analyzer 45 46 func run(pass *analysis.Pass) (interface{}, error) { 47 var genDecl *ast.GenDecl 48 fn := func(node ast.Node, push bool) bool { 49 if !push { 50 genDecl = nil 51 return false 52 } 53 if code.IsInTest(pass, node) { 54 return false 55 } 56 57 switch node := node.(type) { 58 case *ast.GenDecl: 59 if node.Tok == token.IMPORT { 60 return false 61 } 62 genDecl = node 63 return true 64 case *ast.ValueSpec: 65 if genDecl.Lparen.IsValid() || len(node.Names) > 1 { 66 // Don't try to guess the user's intention 67 return false 68 } 69 name := node.Names[0].Name 70 if !ast.IsExported(name) { 71 return false 72 } 73 text, ok := docText(genDecl.Doc) 74 if !ok { 75 return false 76 } 77 prefix := name + " " 78 if !strings.HasPrefix(text, prefix) { 79 kind := "var" 80 if genDecl.Tok == token.CONST { 81 kind = "const" 82 } 83 report.Report(pass, genDecl.Doc, fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, name, prefix), report.FilterGenerated()) 84 } 85 return false 86 case *ast.FuncLit, *ast.FuncDecl: 87 return false 88 default: 89 lint.ExhaustiveTypeSwitch(node) 90 return false 91 } 92 } 93 94 pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.GenDecl)(nil), (*ast.ValueSpec)(nil), (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil)}, fn) 95 return nil, nil 96 } 97 98 func docText(doc *ast.CommentGroup) (string, bool) { 99 if doc == nil { 100 return "", false 101 } 102 // We trim spaces primarily because of /**/ style comments, which often have leading space. 103 text := strings.TrimSpace(doc.Text()) 104 return text, text != "" 105 }