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

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