src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

st1020.go (2547B)


      1 package st1020
      2 
      3 import (
      4 	"fmt"
      5 	"go/ast"
      6 	"strings"
      7 
      8 	"honnef.co/go/tools/analysis/code"
      9 	"honnef.co/go/tools/analysis/facts/generated"
     10 	"honnef.co/go/tools/analysis/lint"
     11 	"honnef.co/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.RawDocumentation{
     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://go.dev/doc/effective_go#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) (any, 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 		if strings.HasPrefix(text, "Deprecated: ") {
     59 			return
     60 		}
     61 
     62 		kind := "function"
     63 		if decl.Recv != nil {
     64 			kind = "method"
     65 			var ident *ast.Ident
     66 			T := decl.Recv.List[0].Type
     67 			if T_, ok := T.(*ast.StarExpr); ok {
     68 				T = T_.X
     69 			}
     70 			switch T := T.(type) {
     71 			case *ast.IndexExpr:
     72 				ident = T.X.(*ast.Ident)
     73 			case *ast.IndexListExpr:
     74 				ident = T.X.(*ast.Ident)
     75 			case *ast.Ident:
     76 				ident = T
     77 			default:
     78 				lint.ExhaustiveTypeSwitch(T)
     79 			}
     80 			if !ast.IsExported(ident.Name) {
     81 				return
     82 			}
     83 		}
     84 		prefix := decl.Name.Name + " "
     85 		if !strings.HasPrefix(text, prefix) {
     86 			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())
     87 		}
     88 	}
     89 
     90 	code.Preorder(pass, fn, (*ast.FuncDecl)(nil))
     91 	return nil, nil
     92 }
     93 
     94 func docText(doc *ast.CommentGroup) (string, bool) {
     95 	if doc == nil {
     96 		return "", false
     97 	}
     98 	// We trim spaces primarily because of /**/ style comments, which often have leading space.
     99 	text := strings.TrimSpace(doc.Text())
    100 	return text, text != ""
    101 }