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

     1  package s1018
     2  
     3  import (
     4  	"go/ast"
     5  
     6  	"github.com/amarpal/go-tools/analysis/code"
     7  	"github.com/amarpal/go-tools/analysis/edit"
     8  	"github.com/amarpal/go-tools/analysis/facts/generated"
     9  	"github.com/amarpal/go-tools/analysis/lint"
    10  	"github.com/amarpal/go-tools/analysis/report"
    11  	"github.com/amarpal/go-tools/go/types/typeutil"
    12  	"github.com/amarpal/go-tools/pattern"
    13  
    14  	"golang.org/x/tools/go/analysis"
    15  	"golang.org/x/tools/go/analysis/passes/inspect"
    16  )
    17  
    18  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
    19  	Analyzer: &analysis.Analyzer{
    20  		Name:     "S1018",
    21  		Run:      run,
    22  		Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
    23  	},
    24  	Doc: &lint.Documentation{
    25  		Title: `Use \"copy\" for sliding elements`,
    26  		Text: `\'copy()\' permits using the same source and destination slice, even with
    27  overlapping ranges. This makes it ideal for sliding elements in a
    28  slice.`,
    29  
    30  		Before: `
    31  for i := 0; i < n; i++ {
    32      bs[i] = bs[offset+i]
    33  }`,
    34  		After:   `copy(bs[:n], bs[offset:])`,
    35  		Since:   "2017.1",
    36  		MergeIf: lint.MergeIfAny,
    37  	},
    38  })
    39  
    40  var Analyzer = SCAnalyzer.Analyzer
    41  
    42  var (
    43  	checkLoopSlideQ = pattern.MustParse(`
    44  		(ForStmt
    45  			(AssignStmt initvar@(Ident _) _ (IntegerLiteral "0"))
    46  			(BinaryExpr initvar "<" limit@(Ident _))
    47  			(IncDecStmt initvar "++")
    48  			[(AssignStmt
    49  				(IndexExpr slice@(Ident _) initvar)
    50  				"="
    51  				(IndexExpr slice (BinaryExpr offset@(Ident _) "+" initvar)))])`)
    52  	checkLoopSlideR = pattern.MustParse(`
    53  		(CallExpr
    54  			(Ident "copy")
    55  			[(SliceExpr slice nil limit nil)
    56  				(SliceExpr slice offset nil nil)])`)
    57  )
    58  
    59  func run(pass *analysis.Pass) (interface{}, error) {
    60  	// TODO(dh): detect bs[i+offset] in addition to bs[offset+i]
    61  	// TODO(dh): consider merging this function with LintLoopCopy
    62  	// TODO(dh): detect length that is an expression, not a variable name
    63  	// TODO(dh): support sliding to a different offset than the beginning of the slice
    64  
    65  	fn := func(node ast.Node) {
    66  		loop := node.(*ast.ForStmt)
    67  		m, edits, ok := code.MatchAndEdit(pass, checkLoopSlideQ, checkLoopSlideR, loop)
    68  		if !ok {
    69  			return
    70  		}
    71  		typ := pass.TypesInfo.TypeOf(m.State["slice"].(*ast.Ident))
    72  		// The pattern probably needs a core type, but All is fine, too. Either way we only accept slices.
    73  		if !typeutil.All(typ, typeutil.IsSlice) {
    74  			return
    75  		}
    76  
    77  		report.Report(pass, loop, "should use copy() instead of loop for sliding slice elements",
    78  			report.ShortRange(),
    79  			report.FilterGenerated(),
    80  			report.Fixes(edit.Fix("use copy() instead of loop", edits...)))
    81  	}
    82  	code.Preorder(pass, fn, (*ast.ForStmt)(nil))
    83  	return nil, nil
    84  }