st1020.go raw

   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) (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  		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  }
 102