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

     1  package s1000
     2  
     3  import (
     4  	"go/ast"
     5  
     6  	"github.com/amarpal/go-tools/analysis/code"
     7  	"github.com/amarpal/go-tools/analysis/facts/generated"
     8  	"github.com/amarpal/go-tools/analysis/lint"
     9  	"github.com/amarpal/go-tools/analysis/report"
    10  	"github.com/amarpal/go-tools/pattern"
    11  
    12  	"golang.org/x/tools/go/analysis"
    13  	"golang.org/x/tools/go/analysis/passes/inspect"
    14  )
    15  
    16  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
    17  	Analyzer: &analysis.Analyzer{
    18  		Name:     "S1000",
    19  		Run:      run,
    20  		Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
    21  	},
    22  	Doc: &lint.Documentation{
    23  		Title: `Use plain channel send or receive instead of single-case select`,
    24  		Text: `Select statements with a single case can be replaced with a simple
    25  send or receive.`,
    26  		Before: `
    27  select {
    28  case x := <-ch:
    29      fmt.Println(x)
    30  }`,
    31  		After: `
    32  x := <-ch
    33  fmt.Println(x)
    34  `,
    35  		Since:   "2017.1",
    36  		MergeIf: lint.MergeIfAny,
    37  	},
    38  })
    39  
    40  var Analyzer = SCAnalyzer.Analyzer
    41  
    42  var (
    43  	checkSingleCaseSelectQ1 = pattern.MustParse(`
    44  		(ForStmt
    45  			nil nil nil
    46  			select@(SelectStmt
    47  				(CommClause
    48  					(Or
    49  						(UnaryExpr "<-" _)
    50  						(AssignStmt _ _ (UnaryExpr "<-" _)))
    51  					_)))`)
    52  	checkSingleCaseSelectQ2 = pattern.MustParse(`(SelectStmt (CommClause _ _))`)
    53  )
    54  
    55  func run(pass *analysis.Pass) (interface{}, error) {
    56  	seen := map[ast.Node]struct{}{}
    57  	fn := func(node ast.Node) {
    58  		if m, ok := code.Match(pass, checkSingleCaseSelectQ1, node); ok {
    59  			seen[m.State["select"].(ast.Node)] = struct{}{}
    60  			report.Report(pass, node, "should use for range instead of for { select {} }", report.FilterGenerated())
    61  		} else if _, ok := code.Match(pass, checkSingleCaseSelectQ2, node); ok {
    62  			if _, ok := seen[node]; !ok {
    63  				report.Report(pass, node, "should use a simple channel send/receive instead of select with a single case",
    64  					report.ShortRange(),
    65  					report.FilterGenerated())
    66  			}
    67  		}
    68  	}
    69  	code.Preorder(pass, fn, (*ast.ForStmt)(nil), (*ast.SelectStmt)(nil))
    70  	return nil, nil
    71  }