Update go version

This commit is contained in:
dwrz
2026-08-21 10:23:37 +00:00
parent 78248a6145
commit c2e2d9ea02
466 changed files with 67766 additions and 2881 deletions

View File

@@ -0,0 +1,239 @@
package s1017
import (
"fmt"
"go/ast"
"go/token"
"reflect"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ast/astutil"
"honnef.co/go/tools/knowledge"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1017",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Replace manual trimming with \'strings.TrimPrefix\'`,
Text: `Instead of using \'strings.HasPrefix\' and manual slicing, use the
\'strings.TrimPrefix\' function. If the string doesn't start with the
prefix, the original string will be returned. Using \'strings.TrimPrefix\'
reduces complexity, and avoids common bugs, such as off-by-one
mistakes.`,
Before: `
if strings.HasPrefix(str, prefix) {
str = str[len(prefix):]
}`,
After: `str = strings.TrimPrefix(str, prefix)`,
Since: "2017.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
sameNonDynamic := func(node1, node2 ast.Node) bool {
if reflect.TypeOf(node1) != reflect.TypeOf(node2) {
return false
}
switch node1 := node1.(type) {
case *ast.Ident:
return pass.TypesInfo.ObjectOf(node1) == pass.TypesInfo.ObjectOf(node2.(*ast.Ident))
case *ast.SelectorExpr, *ast.IndexExpr:
return astutil.Equal(node1, node2)
case *ast.BasicLit:
return astutil.Equal(node1, node2)
}
return false
}
isLenOnIdent := func(fn ast.Expr, ident ast.Expr) bool {
call, ok := fn.(*ast.CallExpr)
if !ok {
return false
}
if !code.IsCallTo(pass, call, "len") {
return false
}
if len(call.Args) != 1 {
return false
}
return sameNonDynamic(call.Args[knowledge.Arg("len.v")], ident)
}
seen := make(map[ast.Node]struct{})
fn := func(node ast.Node) {
var pkg string
var fun string
ifstmt := node.(*ast.IfStmt)
if ifstmt.Init != nil {
return
}
if ifstmt.Else != nil {
seen[ifstmt.Else] = struct{}{}
return
}
if _, ok := seen[ifstmt]; ok {
return
}
if len(ifstmt.Body.List) != 1 {
return
}
condCall, ok := ifstmt.Cond.(*ast.CallExpr)
if !ok {
return
}
condCallName := code.CallName(pass, condCall)
switch condCallName {
case "strings.HasPrefix":
pkg = "strings"
fun = "HasPrefix"
case "strings.HasSuffix":
pkg = "strings"
fun = "HasSuffix"
case "strings.Contains":
pkg = "strings"
fun = "Contains"
case "bytes.HasPrefix":
pkg = "bytes"
fun = "HasPrefix"
case "bytes.HasSuffix":
pkg = "bytes"
fun = "HasSuffix"
case "bytes.Contains":
pkg = "bytes"
fun = "Contains"
default:
return
}
assign, ok := ifstmt.Body.List[0].(*ast.AssignStmt)
if !ok {
return
}
if assign.Tok != token.ASSIGN {
return
}
if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {
return
}
if !sameNonDynamic(condCall.Args[0], assign.Lhs[0]) {
return
}
switch rhs := assign.Rhs[0].(type) {
case *ast.CallExpr:
if len(rhs.Args) < 2 || !sameNonDynamic(condCall.Args[0], rhs.Args[0]) || !sameNonDynamic(condCall.Args[1], rhs.Args[1]) {
return
}
rhsName := code.CallName(pass, rhs)
if condCallName == "strings.HasPrefix" && rhsName == "strings.TrimPrefix" ||
condCallName == "strings.HasSuffix" && rhsName == "strings.TrimSuffix" ||
condCallName == "strings.Contains" && rhsName == "strings.Replace" ||
condCallName == "bytes.HasPrefix" && rhsName == "bytes.TrimPrefix" ||
condCallName == "bytes.HasSuffix" && rhsName == "bytes.TrimSuffix" ||
condCallName == "bytes.Contains" && rhsName == "bytes.Replace" {
report.Report(pass, ifstmt, fmt.Sprintf("should replace this if statement with an unconditional %s", rhsName), report.FilterGenerated())
}
case *ast.SliceExpr:
slice := rhs
if !ok {
return
}
if slice.Slice3 {
return
}
if !sameNonDynamic(slice.X, condCall.Args[0]) {
return
}
validateOffset := func(off ast.Expr) bool {
switch off := off.(type) {
case *ast.CallExpr:
return isLenOnIdent(off, condCall.Args[1])
case *ast.BasicLit:
if pkg != "strings" {
return false
}
if _, ok := condCall.Args[1].(*ast.BasicLit); !ok {
// Only allow manual slicing with an integer
// literal if the second argument to HasPrefix
// was a string literal.
return false
}
s, ok1 := code.ExprToString(pass, condCall.Args[1])
n, ok2 := code.ExprToInt(pass, off)
if !ok1 || !ok2 || n != int64(len(s)) {
return false
}
return true
default:
return false
}
}
switch fun {
case "HasPrefix":
// TODO(dh) We could detect a High that is len(s), but another
// rule will already flag that, anyway.
if slice.High != nil {
return
}
if !validateOffset(slice.Low) {
return
}
case "HasSuffix":
if slice.Low != nil {
n, ok := code.ExprToInt(pass, slice.Low)
if !ok || n != 0 {
return
}
}
switch index := slice.High.(type) {
case *ast.BinaryExpr:
if index.Op != token.SUB {
return
}
if !isLenOnIdent(index.X, condCall.Args[0]) {
return
}
if !validateOffset(index.Y) {
return
}
default:
return
}
default:
return
}
var replacement string
switch fun {
case "HasPrefix":
replacement = "TrimPrefix"
case "HasSuffix":
replacement = "TrimSuffix"
}
report.Report(pass, ifstmt, fmt.Sprintf("should replace this if statement with an unconditional %s.%s", pkg, replacement),
report.ShortRange(),
report.FilterGenerated())
}
}
code.Preorder(pass, fn, (*ast.IfStmt)(nil))
return nil, nil
}