src

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

st1021.go (3256B)


      1 package st1021
      2 
      3 import (
      4 	"fmt"
      5 	"go/ast"
      6 	"go/token"
      7 	"strings"
      8 
      9 	"honnef.co/go/tools/analysis/code"
     10 	"honnef.co/go/tools/analysis/facts/generated"
     11 	"honnef.co/go/tools/analysis/lint"
     12 	"honnef.co/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:     "ST1021",
     22 		Run:      run,
     23 		Requires: []*analysis.Analyzer{generated.Analyzer, inspect.Analyzer},
     24 	},
     25 	Doc: &lint.RawDocumentation{
     26 		Title: "The documentation of an exported type should start with type'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://go.dev/doc/effective_go#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) (any, 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.TypeSpec:
     65 			if !ast.IsExported(node.Name.Name) {
     66 				return false
     67 			}
     68 
     69 			doc := node.Doc
     70 			text, ok := docText(doc)
     71 			if !ok {
     72 				if len(genDecl.Specs) != 1 {
     73 					// more than one spec in the GenDecl, don't validate the
     74 					// docstring
     75 					return false
     76 				}
     77 				if genDecl.Lparen.IsValid() {
     78 					// 'type ( T )' is weird, don't guess the user's intention
     79 					return false
     80 				}
     81 				doc = genDecl.Doc
     82 				text, ok = docText(doc)
     83 				if !ok {
     84 					return false
     85 				}
     86 			}
     87 
     88 			// Check comment before we strip articles in case the type's name is an article.
     89 			if strings.HasPrefix(text, node.Name.Name+" ") {
     90 				return false
     91 			}
     92 
     93 			s := text
     94 			articles := [...]string{"A", "An", "The"}
     95 			for _, a := range articles {
     96 				if strings.HasPrefix(s, a+" ") {
     97 					s = s[len(a)+1:]
     98 					break
     99 				}
    100 			}
    101 			if !strings.HasPrefix(s, node.Name.Name+" ") {
    102 				report.Report(pass, doc, fmt.Sprintf(`comment on exported type %s should be of the form "%s ..." (with optional leading article)`, node.Name.Name, node.Name.Name), report.FilterGenerated())
    103 			}
    104 			return false
    105 		case *ast.FuncLit, *ast.FuncDecl:
    106 			return false
    107 		default:
    108 			lint.ExhaustiveTypeSwitch(node)
    109 			return false
    110 		}
    111 	}
    112 
    113 	pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.GenDecl)(nil), (*ast.TypeSpec)(nil), (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil)}, fn)
    114 	return nil, nil
    115 }
    116 
    117 func docText(doc *ast.CommentGroup) (string, bool) {
    118 	if doc == nil {
    119 		return "", false
    120 	}
    121 	// We trim spaces primarily because of /**/ style comments, which often have leading space.
    122 	text := strings.TrimSpace(doc.Text())
    123 	return text, text != ""
    124 }