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