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

     1  package st1000
     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/lint"
    10  	"github.com/amarpal/go-tools/analysis/report"
    11  
    12  	"golang.org/x/tools/go/analysis"
    13  )
    14  
    15  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
    16  	Analyzer: &analysis.Analyzer{
    17  		Name: "ST1000",
    18  		Run:  run,
    19  	},
    20  	Doc: &lint.Documentation{
    21  		Title: `Incorrect or missing package comment`,
    22  		Text: `Packages must have a package comment that is formatted according to
    23  the guidelines laid out in
    24  https://go.dev/wiki/CodeReviewComments#package-comments.`,
    25  		Since:      "2019.1",
    26  		NonDefault: true,
    27  		MergeIf:    lint.MergeIfAny,
    28  	},
    29  })
    30  
    31  var Analyzer = SCAnalyzer.Analyzer
    32  
    33  func run(pass *analysis.Pass) (interface{}, error) {
    34  	// - At least one file in a non-main package should have a package comment
    35  	//
    36  	// - The comment should be of the form
    37  	// "Package x ...". This has a slight potential for false
    38  	// positives, as multiple files can have package comments, in
    39  	// which case they get appended. But that doesn't happen a lot in
    40  	// the real world.
    41  
    42  	if pass.Pkg.Name() == "main" {
    43  		return nil, nil
    44  	}
    45  	hasDocs := false
    46  	for _, f := range pass.Files {
    47  		if code.IsInTest(pass, f) {
    48  			continue
    49  		}
    50  		text, ok := docText(f.Doc)
    51  		if ok {
    52  			hasDocs = true
    53  			prefix := "Package " + f.Name.Name
    54  			isNonAlpha := func(b byte) bool {
    55  				// This check only considers ASCII, which can lead to false negatives for some malformed package
    56  				// comments.
    57  				return !((b >= '0' && b <= '9') || (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z'))
    58  			}
    59  			if !strings.HasPrefix(text, prefix) || (len(text) > len(prefix) && !isNonAlpha(text[len(prefix)])) {
    60  				report.Report(pass, f.Doc, fmt.Sprintf(`package comment should be of the form "%s..."`, prefix))
    61  			}
    62  		}
    63  	}
    64  
    65  	if !hasDocs {
    66  		for _, f := range pass.Files {
    67  			if code.IsInTest(pass, f) {
    68  				continue
    69  			}
    70  			report.Report(pass, f, "at least one file in a package should have a package comment", report.ShortRange())
    71  		}
    72  	}
    73  	return nil, nil
    74  }
    75  
    76  func docText(doc *ast.CommentGroup) (string, bool) {
    77  	if doc == nil {
    78  		return "", false
    79  	}
    80  	// We trim spaces primarily because of /**/ style comments, which often have leading space.
    81  	text := strings.TrimSpace(doc.Text())
    82  	return text, text != ""
    83  }