src

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

st1022.go (2807B)


      1 package st1022
      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:     "ST1022",
     22 		Run:      run,
     23 		Requires: []*analysis.Analyzer{generated.Analyzer, inspect.Analyzer},
     24 	},
     25 	Doc: &lint.RawDocumentation{
     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://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.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 }