github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/stylecheck/st1022/st1022.go (about)

     1  package st1022
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/token"
     7  	"strings"
     8  
     9  	"github.com/amarpal/go-tools/analysis/code"
    10  	"github.com/amarpal/go-tools/analysis/facts/generated"
    11  	"github.com/amarpal/go-tools/analysis/lint"
    12  	"github.com/amarpal/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.Documentation{
    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://golang.org/doc/effective_go.html#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) (interface{}, 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  }