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

     1  package sa1024
     2  
     3  import (
     4  	"go/constant"
     5  	"sort"
     6  
     7  	"github.com/amarpal/go-tools/analysis/callcheck"
     8  	"github.com/amarpal/go-tools/analysis/lint"
     9  	"github.com/amarpal/go-tools/internal/passes/buildir"
    10  
    11  	"golang.org/x/tools/go/analysis"
    12  )
    13  
    14  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
    15  	Analyzer: &analysis.Analyzer{
    16  		Name:     "SA1024",
    17  		Requires: []*analysis.Analyzer{buildir.Analyzer},
    18  		Run:      callcheck.Analyzer(rules),
    19  	},
    20  	Doc: &lint.Documentation{
    21  		Title: `A string cutset contains duplicate characters`,
    22  		Text: `The \'strings.TrimLeft\' and \'strings.TrimRight\' functions take cutsets, not
    23  prefixes. A cutset is treated as a set of characters to remove from a
    24  string. For example,
    25  
    26      strings.TrimLeft("42133word", "1234")
    27  
    28  will result in the string \'"word"\' – any characters that are 1, 2, 3 or
    29  4 are cut from the left of the string.
    30  
    31  In order to remove one string from another, use \'strings.TrimPrefix\' instead.`,
    32  		Since:    "2017.1",
    33  		Severity: lint.SeverityWarning,
    34  		MergeIf:  lint.MergeIfAny,
    35  	},
    36  })
    37  
    38  var Analyzer = SCAnalyzer.Analyzer
    39  
    40  var rules = map[string]callcheck.Check{
    41  	"strings.Trim":      check,
    42  	"strings.TrimLeft":  check,
    43  	"strings.TrimRight": check,
    44  }
    45  
    46  func check(call *callcheck.Call) {
    47  	arg := call.Args[1]
    48  	if !isUniqueStringCutset(arg.Value) {
    49  		const MsgNonUniqueCutset = "cutset contains duplicate characters"
    50  		arg.Invalid(MsgNonUniqueCutset)
    51  	}
    52  }
    53  
    54  func isUniqueStringCutset(v callcheck.Value) bool {
    55  	if c := callcheck.ExtractConstExpectKind(v, constant.String); c != nil {
    56  		s := constant.StringVal(c.Value)
    57  		rs := runeSlice(s)
    58  		if len(rs) < 2 {
    59  			return true
    60  		}
    61  		sort.Sort(rs)
    62  		for i, r := range rs[1:] {
    63  			if rs[i] == r {
    64  				return false
    65  			}
    66  		}
    67  	}
    68  	return true
    69  }
    70  
    71  type runeSlice []rune
    72  
    73  func (rs runeSlice) Len() int               { return len(rs) }
    74  func (rs runeSlice) Less(i int, j int) bool { return rs[i] < rs[j] }
    75  func (rs runeSlice) Swap(i int, j int)      { rs[i], rs[j] = rs[j], rs[i] }