From 27abfc8246b33c5933951760bfc01de27d7802f1 Mon Sep 17 00:00:00 2001 From: Anton Telyshev Date: Tue, 17 Oct 2023 17:25:02 +0300 Subject: [PATCH 1/9] tdd: much more tests --- analyzer/analyzer_test.go | 63 +++++++-- testdata/src/p/p.go | 272 +++++++++++++++++++++++++++++++++++-- testdata/src/p/p.go.golden | 271 ++++++++++++++++++++++++++++++++++++ 3 files changed, 585 insertions(+), 21 deletions(-) create mode 100644 testdata/src/p/p.go.golden diff --git a/analyzer/analyzer_test.go b/analyzer/analyzer_test.go index 2f40036..87d49d2 100644 --- a/analyzer/analyzer_test.go +++ b/analyzer/analyzer_test.go @@ -1,8 +1,10 @@ package analyzer_test import ( - "os" - "path/filepath" + "encoding/hex" + "fmt" + "io" + "strconv" "testing" "github.com/catenacyber/gostrconv/analyzer" @@ -10,11 +12,56 @@ import ( ) func TestAll(t *testing.T) { - wd, err := os.Getwd() - if err != nil { - t.Fatalf("Failed to get wd: %s", err) - } + t.Parallel() + analysistest.RunWithSuggestedFixes(t, analysistest.TestData(), analyzer.Analyzer, "p") +} + +func TestReplacements(t *testing.T) { + t.Parallel() + + cases := []struct { + before, after string + }{ + {before: fmt.Sprintf("%s", "hello"), after: "hello"}, + {before: fmt.Sprintf("%v", "hello"), after: "hello"}, + + {before: fmt.Sprintf("%s", io.EOF), after: io.EOF.Error()}, + {before: fmt.Sprintf("%v", io.EOF), after: io.EOF.Error()}, - testdata := filepath.Join(filepath.Dir(wd), "testdata") - analysistest.Run(t, testdata, analyzer.Analyzer, "p") + {before: fmt.Sprintf("%t", true), after: strconv.FormatBool(true)}, + {before: fmt.Sprintf("%v", true), after: strconv.FormatBool(true)}, + {before: fmt.Sprintf("%t", false), after: strconv.FormatBool(false)}, + {before: fmt.Sprintf("%v", false), after: strconv.FormatBool(false)}, + + {before: fmt.Sprintf("%x", []byte{'a', 'b', 'c'}), after: hex.EncodeToString([]byte{'a', 'b', 'c'})}, + + {before: fmt.Sprintf("%d", 42), after: strconv.Itoa(42)}, + {before: fmt.Sprintf("%v", 42), after: strconv.Itoa(42)}, + {before: fmt.Sprintf("%d", int8(42)), after: strconv.Itoa(int(int8(42)))}, + {before: fmt.Sprintf("%v", int8(42)), after: strconv.Itoa(int(int8(42)))}, + {before: fmt.Sprintf("%d", int16(42)), after: strconv.Itoa(int(int16(42)))}, + {before: fmt.Sprintf("%v", int16(42)), after: strconv.Itoa(int(int16(42)))}, + {before: fmt.Sprintf("%d", int32(42)), after: strconv.Itoa(int(int32(42)))}, + {before: fmt.Sprintf("%v", int32(42)), after: strconv.Itoa(int(int32(42)))}, + {before: fmt.Sprintf("%d", int64(42)), after: strconv.FormatInt(int64(42), 10)}, + {before: fmt.Sprintf("%v", int64(42)), after: strconv.FormatInt(int64(42), 10)}, + + {before: fmt.Sprintf("%d", uint(42)), after: strconv.FormatUint(uint64(uint(42)), 10)}, + {before: fmt.Sprintf("%v", uint(42)), after: strconv.FormatUint(uint64(uint(42)), 10)}, + {before: fmt.Sprintf("%d", uint8(42)), after: strconv.FormatUint(uint64(uint8(42)), 10)}, + {before: fmt.Sprintf("%v", uint8(42)), after: strconv.FormatUint(uint64(uint8(42)), 10)}, + {before: fmt.Sprintf("%d", uint16(42)), after: strconv.FormatUint(uint64(uint16(42)), 10)}, + {before: fmt.Sprintf("%v", uint16(42)), after: strconv.FormatUint(uint64(uint16(42)), 10)}, + {before: fmt.Sprintf("%d", uint32(42)), after: strconv.FormatUint(uint64(uint32(42)), 10)}, + {before: fmt.Sprintf("%v", uint32(42)), after: strconv.FormatUint(uint64(uint32(42)), 10)}, + {before: fmt.Sprintf("%d", uint64(42)), after: strconv.FormatUint(uint64(42), 10)}, + {before: fmt.Sprintf("%v", uint64(42)), after: strconv.FormatUint(uint64(42), 10)}, + } + for _, tt := range cases { + t.Run("", func(t *testing.T) { + if tt.before != tt.after { + t.Fatalf("%s != %s", tt.before, tt.after) + } + }) + } } diff --git a/testdata/src/p/p.go b/testdata/src/p/p.go index 42e9d3e..e47e784 100644 --- a/testdata/src/p/p.go +++ b/testdata/src/p/p.go @@ -1,24 +1,270 @@ package p -import "fmt" +import ( + "errors" + "fmt" + "io" + "log" + "net/url" + "os" + "time" +) -func notSelectorFuncAtAll() { - _ = int32(23) +var errSentinel = errors.New("connection refused") + +func positive() { + var s string + fmt.Sprintf("%s", "hello") // want "fmt.Sprintf can be replaced with just using the string" + fmt.Sprintf("%v", "hello") // want "fmt.Sprintf can be replaced with just using the string" + fmt.Sprintf("%s", s) // want "fmt.Sprintf can be replaced with just using the string" + fmt.Sprintf("%v", s) // want "fmt.Sprintf can be replaced with just using the string" + + var err error + fmt.Sprintf("%s", errSentinel) // want "fmt.Sprintf can be replaced with errSentinel.Error()" + fmt.Sprintf("%v", errSentinel) // want "fmt.Sprintf can be replaced with errSentinel.Error()" + fmt.Sprintf("%s", io.EOF) // want "fmt.Sprintf can be replaced with io.EOF.Error()" + fmt.Sprintf("%v", io.EOF) // want "fmt.Sprintf can be replaced with io.EOF.Error()" + fmt.Sprintf("%s", err) // want "fmt.Sprintf can be replaced with err.Error()" + fmt.Sprintf("%v", err) // want "fmt.Sprintf can be replaced with err.Error()" + + var b bool + fmt.Sprintf("%t", true) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + fmt.Sprintf("%v", true) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + fmt.Sprintf("%t", false) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + fmt.Sprintf("%v", false) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + fmt.Sprintf("%t", b) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + fmt.Sprintf("%v", b) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + + var bs []byte + var ba [3]byte + fmt.Sprintf("%x", []byte{'a'}) // want "fmt.Sprintf can be replaced with faster hex.EncodeToString" + fmt.Sprintf("%x", []uint8{'b'}) // want "fmt.Sprintf can be replaced with faster hex.EncodeToString" + fmt.Sprintf("%x", bs) // want "fmt.Sprintf can be replaced with faster hex.EncodeToString" + fmt.Sprintf("%x", ba) // want "fmt.Sprintf can be replaced with faster hex.EncodeToString" + + var i int + var i8 int8 + var i16 int16 + var i32 int32 + var i64 int64 + fmt.Sprintf("%d", i) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprintf("%v", i) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprintf("%d", 42) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprintf("%v", 42) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprintf("%d", i8) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprintf("%v", i8) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprintf("%d", int8(42)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprintf("%v", int8(42)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprintf("%d", i16) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprintf("%v", i16) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprintf("%d", int16(42)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprintf("%v", int16(42)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprintf("%d", i32) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprintf("%v", i32) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprintf("%d", int32(42)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprintf("%v", int32(42)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprintf("%d", i64) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprintf("%v", i64) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprintf("%d", int64(42)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprintf("%v", int64(42)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + + var ui uint + var ui8 uint8 + var ui16 uint16 + var ui32 uint32 + var ui64 uint64 + fmt.Sprintf("%d", ui) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprintf("%v", ui) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprintf("%d", uint(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprintf("%v", uint(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprintf("%d", ui8) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprintf("%v", ui8) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprintf("%d", uint8(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprintf("%v", uint8(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprintf("%d", ui16) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprintf("%v", ui16) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprintf("%d", uint16(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprintf("%v", uint16(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprintf("%d", ui32) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprintf("%v", ui32) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprintf("%d", uint32(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprintf("%v", uint32(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprintf("%d", ui64) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprintf("%v", ui64) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprintf("%d", uint64(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprintf("%v", uint64(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" } -func notSprintfFuncAtAll() { - fmt.Printf("test") +func suggestedFixesTest() { + _ = func() string { + return fmt.Sprintf("replace me") // want "fmt.Sprintf can be replaced with just using the string" + } + + fmt.Println(fmt.Sprintf("%s", errSentinel)) // want "fmt.Sprintf can be replaced with errSentinel.Error()" + + _ = func() string { + switch { + case true: + return fmt.Sprintf("%t", true) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + } + return "" + } + + var offset int + params := url.Values{} + params.Set("offset", fmt.Sprintf("%d", offset)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + + var pubKey []byte + if verifyPubKey := true; verifyPubKey { + log.Println("pubkey=" + fmt.Sprintf("%x", pubKey)) // want "fmt.Sprintf can be replaced with faster hex.EncodeToString" + } + + var metaHash [16]byte + fn := fmt.Sprintf("%s.%x", "coverage.MetaFilePref", metaHash) + _ = "tmp." + fn + fmt.Sprintf("%d", time.Now().UnixNano()) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" + + var change struct{ User struct{ ID uint } } + var userStr string + if id := change.User.ID; id != 0 { + userStr = fmt.Sprintf("%d", id) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + } + _ = userStr } -func sprintfDifferentNumberOfArgs() { +func negative() { + const val = "val%d" + + _ = int32(42) + + fmt.Scan(42) + fmt.Scanf("%d", 42) + fmt.Println("%d", 42) + fmt.Printf("%d") + fmt.Printf("%v") + fmt.Printf("%d", 42) + fmt.Printf("%s %d", "hello", 42) + + fmt.Fprint(os.Stdout, "%d", 42) + fmt.Fprintf(os.Stdout, "test") + fmt.Fprintf(os.Stdout, "%d") + fmt.Fprintf(os.Stdout, "%v") + fmt.Fprintf(os.Stdout, "%d", 42) + fmt.Fprintf(os.Stdout, "%s %d", "hello", 42) + fmt.Sprintf("test") -} + fmt.Sprintf("%v") + fmt.Sprintf("%d") + fmt.Sprintf("value %d", 42) + fmt.Sprintf(val, 42) + fmt.Sprintf("%s %v", "hello", "world") + fmt.Sprintf("%#v", 42) + fmt.Sprintf("%T", struct{ string }{}) + fmt.Sprintf("%%v", 42) + fmt.Sprintf("%3d", 42) + fmt.Sprintf("% d", 42) + fmt.Sprintf("%-10d", 42) + fmt.Sprintf("%[2]d %[1]d\n", 11, 22) + fmt.Sprintf("%[3]*.[2]*[1]f", 12.0, 2, 6) + fmt.Sprintf("%d %d %#[1]x %#x", 16, 17) -func sprintfNoBasciLit() { - val := "val%d" - fmt.Sprintf(val, 32) -} + // Integer. + fmt.Sprintf("%#x", uint64(42)) + fmt.Sprintf("%#v", uint64(42)) + fmt.Sprintf("%#b", 42) + fmt.Sprintf("%#o", 42) + fmt.Sprintf("%#x", 42) + fmt.Sprintf("%#X", 42) + + fmt.Sprintf("%b", 42) + fmt.Sprintf("%c", 42) + fmt.Sprintf("%o", 42) + fmt.Sprintf("%O", 42) + fmt.Sprintf("%q", 42) + fmt.Sprintf("%x", 42) + fmt.Sprintf("%X", 42) + + // Floating point. + fmt.Sprintf("%9f", 42.42) + fmt.Sprintf("%.2f", 42.42) + fmt.Sprintf("%.2f", 42.42) + fmt.Sprintf("%9.2f", 42.42) + fmt.Sprintf("%9.f", 42.42) + fmt.Sprintf("%.3g", 42.42) + + fmt.Sprintf("%b", float32(42.42)) + fmt.Sprintf("%e", float32(42.42)) + fmt.Sprintf("%E", float32(42.42)) + fmt.Sprintf("%f", float32(42.42)) + fmt.Sprintf("%F", float32(42.42)) + fmt.Sprintf("%g", float32(42.42)) + fmt.Sprintf("%G", float32(42.42)) + fmt.Sprintf("%x", float32(42.42)) + fmt.Sprintf("%X", float32(42.42)) + fmt.Sprintf("%v", float32(42.42)) + + fmt.Sprintf("%b", 42.42) + fmt.Sprintf("%e", 42.42) + fmt.Sprintf("%E", 42.42) + fmt.Sprintf("%f", 42.42) + fmt.Sprintf("%F", 42.42) + fmt.Sprintf("%g", 42.42) + fmt.Sprintf("%G", 42.42) + fmt.Sprintf("%x", 42.42) + fmt.Sprintf("%X", 42.42) + fmt.Sprintf("%v", 42.42) + + fmt.Sprintf("%b", 42i+42) + fmt.Sprintf("%e", 42i+42) + fmt.Sprintf("%E", 42i+42) + fmt.Sprintf("%f", 42i+42) + fmt.Sprintf("%F", 42i+42) + fmt.Sprintf("%g", 42i+42) + fmt.Sprintf("%G", 42i+42) + fmt.Sprintf("%x", 42i+42) + fmt.Sprintf("%X", 42i+42) + fmt.Sprintf("%v", 42i+42) + + // String & slice of bytes. + fmt.Sprintf("%q", "hello") + fmt.Sprintf("%#q", `"hello"`) + fmt.Sprintf("%+q", "hello") + fmt.Sprintf("%X", "hello") + + // Slice. + fmt.Sprintf("%x", []uint16{'d'}) + fmt.Sprintf("%x", []uint32{'d'}) + fmt.Sprintf("%x", []uint64{'d'}) + fmt.Sprintf("%x", []uint{'d'}) + fmt.Sprintf("%x", [1]byte{'c'}) + fmt.Sprintf("%x", [1]uint8{'d'}) + fmt.Sprintf("%x", [1]uint16{'d'}) + fmt.Sprintf("%x", [1]uint32{'d'}) + fmt.Sprintf("%x", [1]uint64{'d'}) + fmt.Sprintf("%x", [1]uint{'d'}) + fmt.Sprintf("%x", []int8{1}) + fmt.Sprintf("%x", []int16{1}) + fmt.Sprintf("%x", []int32{1}) + fmt.Sprintf("%x", []int64{1}) + fmt.Sprintf("%x", []int{1}) + fmt.Sprintf("%x", [...]int8{1}) + fmt.Sprintf("%x", [...]int16{1}) + fmt.Sprintf("%x", [...]int32{1}) + fmt.Sprintf("%x", [...]int64{1}) + fmt.Sprintf("%x", [...]int{1}) + fmt.Sprintf("%x", []string{"hello"}) + fmt.Sprintf("%x", []rune{'a'}) + + fmt.Sprintf("% x", []byte{1, 2, 3}) + fmt.Sprintf("% X", []byte{1, 2, 3}) + fmt.Sprintf("%p", []byte{1, 2, 3}) + fmt.Sprintf("%#p", []byte{1, 2, 3}) -func prinfLikeFunc() { - fmt.Sprintf("%d", 32) // want "fmt.Sprintf can be replaced with faster function strconv.Itoa" + // Pointer. + var ptr *int + fmt.Sprintf("%v", ptr) + fmt.Sprintf("%b", ptr) + fmt.Sprintf("%d", ptr) + fmt.Sprintf("%o", ptr) + fmt.Sprintf("%x", ptr) + fmt.Sprintf("%X", ptr) } diff --git a/testdata/src/p/p.go.golden b/testdata/src/p/p.go.golden new file mode 100644 index 0000000..f70d854 --- /dev/null +++ b/testdata/src/p/p.go.golden @@ -0,0 +1,271 @@ +package p + +import ( + "encoding/hex" + "errors" + "fmt" + "io" + "log" + "os" + "strconv" + "time" +) + +var errSentinel = errors.New("connection refused") + +func positive() { + var s string + "hello" // want "fmt.Sprintf can be replaced with just using the string" + "hello" // want "fmt.Sprintf can be replaced with just using the string" + s // want "fmt.Sprintf can be replaced with just using the string" + s // want "fmt.Sprintf can be replaced with just using the string" + + var err error + errSentinel.Error() // want "fmt.Sprintf can be replaced with errSentinel.Error()" + errSentinel.Error() // want "fmt.Sprintf can be replaced with errSentinel.Error()" + io.EOF.Error() // want "fmt.Sprintf can be replaced with io.EOF.Error()" + io.EOF.Error() // want "fmt.Sprintf can be replaced with io.EOF.Error()" + err.Error() // want "fmt.Sprintf can be replaced with err.Error()" + err.Error() // want "fmt.Sprintf can be replaced with err.Error()" + + var b bool + strconv.FormatBool(true) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + strconv.FormatBool(true) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + strconv.FormatBool(false) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + strconv.FormatBool(false) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + strconv.FormatBool(b) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + strconv.FormatBool(b) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + + var bs []byte + var ba [3]byte + hex.EncodeToString([]byte{'a'}) // want "fmt.Sprintf can be replaced with faster hex.EncodeToString" + hex.EncodeToString([]uint8{'b'}) // want "fmt.Sprintf can be replaced with faster hex.EncodeToString" + hex.EncodeToString(bs) // want "fmt.Sprintf can be replaced with faster hex.EncodeToString" + hex.EncodeToString(ba[:]) // want "fmt.Sprintf can be replaced with faster hex.EncodeToString" + + var i int + var i8 int8 + var i16 int16 + var i32 int32 + var i64 int64 + strconv.Itoa(i) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(i) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(42) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(42) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(int(i8)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(int(i8)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(int(int8(42))) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(int(int8(42))) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(int(i16)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(int(i16)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(int(int16(42))) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(int(int16(42))) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(int(i32)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(int(i32)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(int(int32(42))) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(int(int32(42))) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.FormatInt(i64, 10) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.FormatInt(i64, 10) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.FormatInt(int64(42), 10) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.FormatInt(int64(42), 10) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + + var ui uint + var ui8 uint8 + var ui16 uint16 + var ui32 uint32 + var ui64 uint64 + strconv.FormatUint(uint64(ui), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(ui), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(uint(42)), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(uint(42)), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(ui8), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(ui8), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(uint8(42)), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(uint8(42)), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(ui16), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(ui16), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(uint16(42)), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(uint16(42)), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(ui32), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(ui32), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(uint32(42)), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(uint32(42)), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(ui64, 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(ui64, 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(42), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(42), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" +} + +func suggestedFixesTest() { + _ = func() string { + return "replace me" // want "fmt.Sprintf can be replaced with just using the string" + } + + fmt.Println(errSentinel.Error()) // want "fmt.Sprintf can be replaced with errSentinel.Error()" + + _ = func() string { + switch { + case true: + return strconv.FormatBool(true) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + } + return "" + } + + var offset int + params := url.Values{} + params.Set("offset", strconv.Itoa(offset)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + + var pubKey []byte + if verifyPubKey := true; verifyPubKey { + log.Println("pubkey=" + hex.EncodeToString(pubKey)) // want "fmt.Sprintf can be replaced with faster hex.EncodeToString" + } + + var metaHash [16]byte + fn := fmt.Sprintf("%s.%x", "coverage.MetaFilePref", metaHash) + _ = "tmp." + fn + strconv.FormatInt(time.Now().UnixNano(), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" + + var change struct{ User struct{ ID uint } } + var userStr string + if id := change.User.ID; id != 0 { + userStr = strconv.FormatUint(uint64(id), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + } + _ = userStr +} + +func negative() { + const val = "val%d" + + _ = int32(42) + + fmt.Scan(42) + fmt.Scanf("%d", 42) + fmt.Println("%d", 42) + fmt.Printf("%d") + fmt.Printf("%v") + fmt.Printf("%d", 42) + fmt.Printf("%s %d", "hello", 42) + + fmt.Fprint(os.Stdout, "%d", 42) + fmt.Fprintf(os.Stdout, "test") + fmt.Fprintf(os.Stdout, "%d") + fmt.Fprintf(os.Stdout, "%v") + fmt.Fprintf(os.Stdout, "%d", 42) + fmt.Fprintf(os.Stdout, "%s %d", "hello", 42) + + fmt.Sprintf("test") + fmt.Sprintf("%v") + fmt.Sprintf("%d") + fmt.Sprintf("value %d", 42) + fmt.Sprintf(val, 42) + fmt.Sprintf("%s %v", "hello", "world") + fmt.Sprintf("%#v", 42) + fmt.Sprintf("%T", struct{ string }{}) + fmt.Sprintf("%%v", 42) + fmt.Sprintf("%3d", 42) + fmt.Sprintf("% d", 42) + fmt.Sprintf("%-10d", 42) + fmt.Sprintf("%[2]d %[1]d\n", 11, 22) + fmt.Sprintf("%[3]*.[2]*[1]f", 12.0, 2, 6) + fmt.Sprintf("%d %d %#[1]x %#x", 16, 17) + + // Integer. + fmt.Sprintf("%#x", uint64(42)) + fmt.Sprintf("%#v", uint64(42)) + fmt.Sprintf("%#b", 42) + fmt.Sprintf("%#o", 42) + fmt.Sprintf("%#x", 42) + fmt.Sprintf("%#X", 42) + + fmt.Sprintf("%b", 42) + fmt.Sprintf("%c", 42) + fmt.Sprintf("%o", 42) + fmt.Sprintf("%O", 42) + fmt.Sprintf("%q", 42) + fmt.Sprintf("%x", 42) + fmt.Sprintf("%X", 42) + + // Floating point. + fmt.Sprintf("%9f", 42.42) + fmt.Sprintf("%.2f", 42.42) + fmt.Sprintf("%.2f", 42.42) + fmt.Sprintf("%9.2f", 42.42) + fmt.Sprintf("%9.f", 42.42) + fmt.Sprintf("%.3g", 42.42) + + fmt.Sprintf("%b", float32(42.42)) + fmt.Sprintf("%e", float32(42.42)) + fmt.Sprintf("%E", float32(42.42)) + fmt.Sprintf("%f", float32(42.42)) + fmt.Sprintf("%F", float32(42.42)) + fmt.Sprintf("%g", float32(42.42)) + fmt.Sprintf("%G", float32(42.42)) + fmt.Sprintf("%x", float32(42.42)) + fmt.Sprintf("%X", float32(42.42)) + fmt.Sprintf("%v", float32(42.42)) + + fmt.Sprintf("%b", 42.42) + fmt.Sprintf("%e", 42.42) + fmt.Sprintf("%E", 42.42) + fmt.Sprintf("%f", 42.42) + fmt.Sprintf("%F", 42.42) + fmt.Sprintf("%g", 42.42) + fmt.Sprintf("%G", 42.42) + fmt.Sprintf("%x", 42.42) + fmt.Sprintf("%X", 42.42) + fmt.Sprintf("%v", 42.42) + + fmt.Sprintf("%b", 42i+42) + fmt.Sprintf("%e", 42i+42) + fmt.Sprintf("%E", 42i+42) + fmt.Sprintf("%f", 42i+42) + fmt.Sprintf("%F", 42i+42) + fmt.Sprintf("%g", 42i+42) + fmt.Sprintf("%G", 42i+42) + fmt.Sprintf("%x", 42i+42) + fmt.Sprintf("%X", 42i+42) + fmt.Sprintf("%v", 42i+42) + + // String & slice of bytes. + fmt.Sprintf("%q", "hello") + fmt.Sprintf("%#q", `"hello"`) + fmt.Sprintf("%+q", "hello") + fmt.Sprintf("%X", "hello") + + // Slice. + fmt.Sprintf("%x", []uint16{'d'}) + fmt.Sprintf("%x", []uint32{'d'}) + fmt.Sprintf("%x", []uint64{'d'}) + fmt.Sprintf("%x", []uint{'d'}) + fmt.Sprintf("%x", [1]byte{'c'}) + fmt.Sprintf("%x", [1]uint8{'d'}) + fmt.Sprintf("%x", [1]uint16{'d'}) + fmt.Sprintf("%x", [1]uint32{'d'}) + fmt.Sprintf("%x", [1]uint64{'d'}) + fmt.Sprintf("%x", [1]uint{'d'}) + fmt.Sprintf("%x", []int8{1}) + fmt.Sprintf("%x", []int16{1}) + fmt.Sprintf("%x", []int32{1}) + fmt.Sprintf("%x", []int64{1}) + fmt.Sprintf("%x", []int{1}) + fmt.Sprintf("%x", [...]int8{1}) + fmt.Sprintf("%x", [...]int16{1}) + fmt.Sprintf("%x", [...]int32{1}) + fmt.Sprintf("%x", [...]int64{1}) + fmt.Sprintf("%x", [...]int{1}) + fmt.Sprintf("%x", []string{"hello"}) + fmt.Sprintf("%x", []rune{'a'}) + + fmt.Sprintf("% x", []byte{1, 2, 3}) + fmt.Sprintf("% X", []byte{1, 2, 3}) + fmt.Sprintf("%p", []byte{1, 2, 3}) + fmt.Sprintf("%#p", []byte{1, 2, 3}) + + // Pointer. + var ptr *int + fmt.Sprintf("%v", ptr) + fmt.Sprintf("%b", ptr) + fmt.Sprintf("%d", ptr) + fmt.Sprintf("%o", ptr) + fmt.Sprintf("%x", ptr) + fmt.Sprintf("%X", ptr) +} From b258ef6049d5b6dcce020173c71041c9c57ed0b2 Mon Sep 17 00:00:00 2001 From: Anton Telyshev Date: Tue, 17 Oct 2023 21:03:59 +0300 Subject: [PATCH 2/9] analyzer implementation global refactoring --- analyzer/analyzer.go | 363 +++++++++++------- analyzer/analyzer_test.go | 2 +- {testdata => analyzer/testdata}/src/p/p.go | 10 +- .../testdata}/src/p/p.go.golden | 11 +- go.mod | 6 +- go.sum | 14 +- 6 files changed, 235 insertions(+), 171 deletions(-) rename {testdata => analyzer/testdata}/src/p/p.go (97%) rename {testdata => analyzer/testdata}/src/p/p.go.golden (98%) diff --git a/analyzer/analyzer.go b/analyzer/analyzer.go index d813578..b3a7bcb 100644 --- a/analyzer/analyzer.go +++ b/analyzer/analyzer.go @@ -1,9 +1,12 @@ package analyzer import ( - "fmt" + "bytes" "go/ast" + "go/format" + "go/token" "go/types" + "strconv" "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/ast/inspector" @@ -19,234 +22,296 @@ var Analyzer = &analysis.Analyzer{ } func run(pass *analysis.Pass) (interface{}, error) { - var fmtpkg *types.Package + var fmtSprintfObj types.Object for _, pkg := range pass.Pkg.Imports() { if pkg.Path() == "fmt" { - fmtpkg = pkg + fmtSprintfObj = pkg.Scope().Lookup("Sprintf") } } - if fmtpkg == nil { + if fmtSprintfObj == nil { return nil, nil } - inspector := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + + insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) nodeFilter := []ast.Node{ (*ast.CallExpr)(nil), } - - inspector.Preorder(nodeFilter, func(node ast.Node) { + insp.Preorder(nodeFilter, func(node ast.Node) { call := node.(*ast.CallExpr) called, ok := call.Fun.(*ast.SelectorExpr) if !ok { return } - if pass.TypesInfo.ObjectOf(called.Sel).Pkg() != fmtpkg { - return - } - if called.Sel.Name != "Sprintf" { + if pass.TypesInfo.ObjectOf(called.Sel) != fmtSprintfObj { return } if len(call.Args) != 2 { return } - arg0, ok := call.Args[0].(*ast.BasicLit) + + fmtString, value := call.Args[0], call.Args[1] + + verbLit, ok := fmtString.(*ast.BasicLit) if !ok { return } - var base int - switch arg0.Value { - case `"%d"`, `"%v"`: - base = 10 - case `"%x"`: - base = 16 - case `"%t"`: - base = 1 - case `"%s"`: - base = 0 + verb, err := strconv.Unquote(verbLit.Value) + if err != nil { + verb = "" + } + switch verb { default: return + case "%d", "%v", "%x", "%t", "%s": } - v := pass.TypesInfo.TypeOf(call.Args[1]) - s, isslice := v.(*types.Slice) - a, isarray := v.(*types.Array) - if types.Identical(v, types.Typ[types.Float32]) || types.Identical(v, types.Typ[types.Float64]) { - } else if types.Identical(v, types.Typ[types.String]) && base == 0 { - pass.Report(analysis.Diagnostic{ - Pos: node.Pos(), - End: node.End(), + + valueType := pass.TypesInfo.TypeOf(value) + a, isArray := valueType.(*types.Array) + s, isSlice := valueType.(*types.Slice) + + var d *analysis.Diagnostic + switch { + case isBasicType(valueType, types.String) && oneOf(verb, "%v", "%s"): + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), Message: "fmt.Sprintf can be replaced with just using the string", - // need ro run goimports to fix use of fmt/strconv afterwards SuggestedFixes: []analysis.SuggestedFix{ { - Message: "use strconv.FormatBool", + Message: "Just use string value", TextEdits: []analysis.TextEdit{{ - Pos: node.Pos(), - End: call.Args[1].Pos(), - NewText: []byte(""), - }, - { - Pos: call.Args[1].End(), - End: node.End(), - NewText: []byte(""), - }, - }, + Pos: call.Pos(), + End: call.End(), + NewText: []byte(formatNode(pass.Fset, value)), + }}, }, }, - }) - } else if base == 0 && v.String() == "error" { - pass.Report(analysis.Diagnostic{ - Pos: node.Pos(), - End: node.End(), - Message: "fmt.Sprintf can be replaced with using Error()", - // need ro run goimports to fix use of fmt/strconv afterwards + } + + case types.Implements(valueType, errIface) && oneOf(verb, "%v", "%s"): + errMethodCall := formatNode(pass.Fset, value) + ".Error()" + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: "fmt.Sprintf can be replaced with " + errMethodCall, SuggestedFixes: []analysis.SuggestedFix{ { - Message: "use Error()", + Message: "Use " + errMethodCall, TextEdits: []analysis.TextEdit{{ - Pos: node.Pos(), - End: call.Args[1].Pos(), - NewText: []byte(""), - }, - { - Pos: call.Args[1].End(), - End: node.End(), - NewText: []byte(".Error()"), - }, - }, + Pos: call.Pos(), + End: call.End(), + NewText: []byte(errMethodCall), + }}, }, }, - }) - } else if types.Identical(v, types.Typ[types.Bool]) && base == 1 { - pass.Report(analysis.Diagnostic{ - Pos: node.Pos(), - End: node.End(), - Message: "fmt.Sprintf can be replaced with faster function strconv.FormatBool", - // need ro run goimports to fix use of fmt/strconv afterwards + } + + case isBasicType(valueType, types.Bool) && oneOf(verb, "%v", "%t"): + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: "fmt.Sprintf can be replaced with faster strconv.FormatBool", SuggestedFixes: []analysis.SuggestedFix{ { - Message: "use strconv.FormatBool", + Message: "Use strconv.FormatBool", TextEdits: []analysis.TextEdit{{ - Pos: node.Pos(), - End: call.Args[1].Pos(), + Pos: call.Pos(), + End: value.Pos(), NewText: []byte("strconv.FormatBool("), }}, }, }, - }) - } else if isarray && types.Identical(a.Elem(), types.Typ[types.Uint8]) && base == 16 { - _, ok = call.Args[1].(*ast.Ident) - if ok { - pass.Report(analysis.Diagnostic{ - Pos: node.Pos(), - End: node.End(), - Message: "fmt.Sprintf can be replaced with faster function hex.EncodeToString", - // need ro run goimports to fix use of fmt/encoding/hex afterwards - SuggestedFixes: []analysis.SuggestedFix{ - { - Message: "use hex.EncodeToString", - TextEdits: []analysis.TextEdit{{ - Pos: node.Pos(), - End: call.Args[1].Pos(), + } + + case isArray && isBasicType(a.Elem(), types.Uint8) && oneOf(verb, "%x"): + if _, ok := value.(*ast.Ident); !ok { + // Doesn't support array literals. + return + } + + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: "fmt.Sprintf can be replaced with faster hex.EncodeToString", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Use hex.EncodeToString", + TextEdits: []analysis.TextEdit{ + { + Pos: call.Pos(), + End: value.Pos(), NewText: []byte("hex.EncodeToString("), }, - { - Pos: call.Args[1].End(), - End: call.Args[1].End(), - NewText: []byte("[:]"), - }, + { + Pos: value.End(), + End: value.End(), + NewText: []byte("[:]"), }, }, }, - }) + }, } - } else if isslice && types.Identical(s.Elem(), types.Typ[types.Uint8]) && base == 16 { - pass.Report(analysis.Diagnostic{ - Pos: node.Pos(), - End: node.End(), - Message: "fmt.Sprintf can be replaced with faster function hex.EncodeToString", - // need ro run goimports to fix use of fmt/strconv afterwards + case isSlice && isBasicType(s.Elem(), types.Uint8) && oneOf(verb, "%x"): + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: "fmt.Sprintf can be replaced with faster hex.EncodeToString", SuggestedFixes: []analysis.SuggestedFix{ { - Message: "use hex.EncodeToString", + Message: "Use hex.EncodeToString", TextEdits: []analysis.TextEdit{{ - Pos: node.Pos(), - End: call.Args[1].Pos(), + Pos: call.Pos(), + End: value.Pos(), NewText: []byte("hex.EncodeToString("), }}, }, }, - }) - } else if types.Identical(v, types.Typ[types.Int]) && base == 10 { - pass.Report(analysis.Diagnostic{ - Pos: node.Pos(), - End: node.End(), - Message: "fmt.Sprintf can be replaced with faster function strconv.Itoa", - // need ro run goimports to fix use of fmt/strconv afterwards + } + + case isBasicType(valueType, types.Int8, types.Int16, types.Int32) && oneOf(verb, "%v", "%d"): + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: "fmt.Sprintf can be replaced with faster strconv.Itoa", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Use strconv.Itoa", + TextEdits: []analysis.TextEdit{ + { + Pos: call.Pos(), + End: value.Pos(), + NewText: []byte("strconv.Itoa(int("), + }, + { + Pos: value.End(), + End: value.End(), + NewText: []byte(")"), + }, + }, + }, + }, + } + case isBasicType(valueType, types.Int) && oneOf(verb, "%v", "%d"): + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: "fmt.Sprintf can be replaced with faster strconv.Itoa", SuggestedFixes: []analysis.SuggestedFix{ { - Message: "use strconv.Itoa", + Message: "Use strconv.Itoa", TextEdits: []analysis.TextEdit{{ - Pos: node.Pos(), - End: call.Args[1].Pos(), + Pos: call.Pos(), + End: value.Pos(), NewText: []byte("strconv.Itoa("), }}, }, }, - }) - } else if types.Identical(v, types.Typ[types.Int64]) { - pass.Report(analysis.Diagnostic{ - Pos: node.Pos(), - End: node.End(), - Message: "fmt.Sprintf can be replaced with faster function strconv.FormatInt", - // need ro run goimports to fix use of fmt/strconv afterwards + } + case isBasicType(valueType, types.Int64) && oneOf(verb, "%v", "%d"): + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: "fmt.Sprintf can be replaced with faster strconv.FormatInt", SuggestedFixes: []analysis.SuggestedFix{ { - Message: "use strconv.FormatInt", - TextEdits: []analysis.TextEdit{{ - Pos: node.Pos(), - End: call.Args[1].Pos(), - NewText: []byte("strconv.FormatInt("), - }, + Message: "Use strconv.FormatInt", + TextEdits: []analysis.TextEdit{ + { + Pos: call.Pos(), + End: call.Args[1].Pos(), + NewText: []byte("strconv.FormatInt("), + }, { - Pos: call.Args[1].End(), - End: call.Args[1].End(), - NewText: []byte(fmt.Sprintf(", %d", base)), + Pos: value.End(), + End: value.End(), + NewText: []byte(", 10"), }, }, }, }, - }) - } else if types.Identical(v, types.Typ[types.Uint64]) { - pass.Report(analysis.Diagnostic{ - Pos: node.Pos(), - End: node.End(), - Message: "fmt.Sprintf can be replaced with faster function strconv.FormatUint", - // need ro run goimports to fix use of fmt/strconv afterwards + } + + case isBasicType(valueType, types.Uint8, types.Uint16, types.Uint32, types.Uint) && oneOf(verb, "%v", "%d"): + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: "fmt.Sprintf can be replaced with faster strconv.FormatUint", SuggestedFixes: []analysis.SuggestedFix{ { - Message: "use strconv.FormatUint", - TextEdits: []analysis.TextEdit{{ - Pos: node.Pos(), - End: call.Args[1].Pos(), - NewText: []byte("strconv.FormatUint("), + Message: "Use strconv.FormatUint", + TextEdits: []analysis.TextEdit{ + { + Pos: call.Pos(), + End: value.Pos(), + NewText: []byte("strconv.FormatUint(uint64("), + }, + { + Pos: value.End(), + End: value.End(), + NewText: []byte("), 10"), + }, }, + }, + }, + } + case isBasicType(valueType, types.Uint64) && oneOf(verb, "%v", "%d"): + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: "fmt.Sprintf can be replaced with faster strconv.FormatUint", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Use strconv.FormatUint", + TextEdits: []analysis.TextEdit{ + { + Pos: call.Pos(), + End: value.Pos(), + NewText: []byte("strconv.FormatUint("), + }, { - Pos: call.Args[1].End(), - End: call.Args[1].End(), - NewText: []byte(fmt.Sprintf(", %d", base)), + Pos: value.End(), + End: value.End(), + NewText: []byte(", 10"), }, }, }, }, - }) - } else if types.Identical(v.Underlying(), types.Typ[types.Int]) && base == 10 { - } else if types.Identical(v.Underlying(), types.Typ[types.Int8]) || types.Identical(v.Underlying(), types.Typ[types.Int16]) || types.Identical(v.Underlying(), types.Typ[types.Int32]) { - } else if types.Identical(v.Underlying(), types.Typ[types.Uint8]) || types.Identical(v.Underlying(), types.Typ[types.Uint16]) || types.Identical(v.Underlying(), types.Typ[types.Uint32]) || types.Identical(v.Underlying(), types.Typ[types.Uint]){ - } else if arg0.Value == `"%v"` { - } else if base == 0 { - } else { - pass.Reportf(node.Pos(), "Sprintf can be replaced with faster function from strconv %s/%s", arg0.Value, v.String()) + } + } + + if d != nil { + // Need ro run goimports to fix using of fmt, strconv or encoding/hex afterwards. + pass.Report(*d) } }) return nil, nil } + +var errIface = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) + +func isBasicType(lhs types.Type, expected ...types.BasicKind) bool { + for _, rhs := range expected { + if types.Identical(lhs, types.Typ[rhs]) { + return true + } + } + return false +} + +func formatNode(fset *token.FileSet, node ast.Node) string { + buf := new(bytes.Buffer) + if err := format.Node(buf, fset, node); err != nil { + return "" + } + return buf.String() +} + +func oneOf[T comparable](v T, expected ...T) bool { + for _, rhs := range expected { + if v == rhs { + return true + } + } + return false +} diff --git a/analyzer/analyzer_test.go b/analyzer/analyzer_test.go index 87d49d2..5b21f88 100644 --- a/analyzer/analyzer_test.go +++ b/analyzer/analyzer_test.go @@ -11,7 +11,7 @@ import ( "golang.org/x/tools/go/analysis/analysistest" ) -func TestAll(t *testing.T) { +func TestAnalyzer(t *testing.T) { t.Parallel() analysistest.RunWithSuggestedFixes(t, analysistest.TestData(), analyzer.Analyzer, "p") } diff --git a/testdata/src/p/p.go b/analyzer/testdata/src/p/p.go similarity index 97% rename from testdata/src/p/p.go rename to analyzer/testdata/src/p/p.go index e47e784..233253c 100644 --- a/testdata/src/p/p.go +++ b/analyzer/testdata/src/p/p.go @@ -63,10 +63,10 @@ func positive() { fmt.Sprintf("%v", i32) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" fmt.Sprintf("%d", int32(42)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" fmt.Sprintf("%v", int32(42)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" - fmt.Sprintf("%d", i64) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" - fmt.Sprintf("%v", i64) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" - fmt.Sprintf("%d", int64(42)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" - fmt.Sprintf("%v", int64(42)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprintf("%d", i64) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" + fmt.Sprintf("%v", i64) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" + fmt.Sprintf("%d", int64(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" + fmt.Sprintf("%v", int64(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" var ui uint var ui8 uint8 @@ -97,7 +97,7 @@ func positive() { func suggestedFixesTest() { _ = func() string { - return fmt.Sprintf("replace me") // want "fmt.Sprintf can be replaced with just using the string" + return fmt.Sprintf("%s", "replace me") // want "fmt.Sprintf can be replaced with just using the string" } fmt.Println(fmt.Sprintf("%s", errSentinel)) // want "fmt.Sprintf can be replaced with errSentinel.Error()" diff --git a/testdata/src/p/p.go.golden b/analyzer/testdata/src/p/p.go.golden similarity index 98% rename from testdata/src/p/p.go.golden rename to analyzer/testdata/src/p/p.go.golden index f70d854..d54979f 100644 --- a/testdata/src/p/p.go.golden +++ b/analyzer/testdata/src/p/p.go.golden @@ -1,13 +1,12 @@ package p import ( - "encoding/hex" "errors" "fmt" "io" "log" + "net/url" "os" - "strconv" "time" ) @@ -64,10 +63,10 @@ func positive() { strconv.Itoa(int(i32)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" strconv.Itoa(int(int32(42))) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" strconv.Itoa(int(int32(42))) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" - strconv.FormatInt(i64, 10) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" - strconv.FormatInt(i64, 10) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" - strconv.FormatInt(int64(42), 10) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" - strconv.FormatInt(int64(42), 10) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.FormatInt(i64, 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" + strconv.FormatInt(i64, 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" + strconv.FormatInt(int64(42), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" + strconv.FormatInt(int64(42), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" var ui uint var ui8 uint8 diff --git a/go.mod b/go.mod index f8e33fd..63236f4 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,9 @@ module github.com/catenacyber/gostrconv go 1.19 -require golang.org/x/tools v0.7.0 +require golang.org/x/tools v0.14.0 require ( - golang.org/x/mod v0.9.0 // indirect - golang.org/x/sys v0.6.0 // indirect + golang.org/x/mod v0.13.0 // indirect + golang.org/x/sys v0.13.0 // indirect ) diff --git a/go.sum b/go.sum index 62914aa..19878d6 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= From 07305db375add12b7d1e6ec71a3adbd95e4a5c53 Mon Sep 17 00:00:00 2001 From: Anton Telyshev Date: Tue, 17 Oct 2023 21:26:12 +0300 Subject: [PATCH 3/9] fix binary path; add sanity steps and CI --- .github/workflows/ci.yml | 43 ++++++++++++++++++ Makefile | 19 ++++++++ analyzer/analyzer_test.go | 2 +- cmd/main.go | 15 ------ go.mod | 2 +- main.go | 96 ++------------------------------------- sample/simple.go | 10 ---- 7 files changed, 67 insertions(+), 120 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 Makefile delete mode 100644 cmd/main.go delete mode 100644 sample/simple.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e10ebeb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,43 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + main: + runs-on: ubuntu-latest + env: + GO_VERSION: '1.20' + GOLANGCI_LINT_VERSION: 1.54.2 + CGO_ENABLED: 0 + steps: + - name: Set up Go ${{ env.GO_VERSION }} + uses: actions/setup-go@v4 + with: + go-version: ^${{ env.GO_VERSION }} + + - name: Checkout code + uses: actions/checkout@v3 + + - name: Cache Go modules + uses: actions/cache@v3 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Check and get dependencies + run: | + go mod tidy + git diff --exit-code go.mod + git diff --exit-code go.sum + + - name: Install golangci-lint ${{ env.GOLANGCI_LINT_VERSION }} + run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCI_LINT_VERSION} + + - name: Make + run: make diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d79c2f8 --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +.PHONY: fmt tidy lint test install + +default: fmt tidy lint test install + +fmt: + go fmt ./... + +tidy: + go mod tidy + +lint: + golangci-lint run + +test: + go test ./... + +install: + go install . + gostrconv -h 2>&1 | head -n1 diff --git a/analyzer/analyzer_test.go b/analyzer/analyzer_test.go index 5b21f88..b47b220 100644 --- a/analyzer/analyzer_test.go +++ b/analyzer/analyzer_test.go @@ -22,7 +22,7 @@ func TestReplacements(t *testing.T) { cases := []struct { before, after string }{ - {before: fmt.Sprintf("%s", "hello"), after: "hello"}, + {before: fmt.Sprintf("%s", "hello"), after: "hello"}, //nolint:gosimple //https://staticcheck.io/docs/checks#S1025 {before: fmt.Sprintf("%v", "hello"), after: "hello"}, {before: fmt.Sprintf("%s", io.EOF), after: io.EOF.Error()}, diff --git a/cmd/main.go b/cmd/main.go deleted file mode 100644 index 812ec95..0000000 --- a/cmd/main.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "flag" - - "github.com/catenacyber/gostrconv/analyzer" - "golang.org/x/tools/go/analysis/singlechecker" -) - -func main() { - // Don't use it: just to not crash on -unsafeptr flag from go vet - flag.Bool("unsafeptr", false, "") - - singlechecker.Main(analyzer.Analyzer) -} diff --git a/go.mod b/go.mod index 63236f4..9c2667f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/catenacyber/gostrconv -go 1.19 +go 1.20 require golang.org/x/tools v0.14.0 diff --git a/main.go b/main.go index 771d243..b4f8c12 100644 --- a/main.go +++ b/main.go @@ -1,101 +1,11 @@ package main import ( - "flag" - "fmt" - "go/ast" - "go/parser" - "go/printer" - "go/token" - "io" - "log" - "os" + "github.com/catenacyber/gostrconv/analyzer" - "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/go/analysis/singlechecker" ) -func changeCall(call *ast.CallExpr) { - name := call.Fun.(*ast.SelectorExpr) - pname := name.X.(*ast.Ident) - pname.Name = "strconv" - name.Sel.Name = "FormatInt" - call.Args = call.Args[1:2] - tocast := true - switch a := call.Args[0].(type) { - case *ast.CallExpr: - if len(a.Args) == 1 { - fn, ok := a.Fun.(*ast.Ident) - if ok { - switch fn.Name { - case "int64": - tocast = false - case "int8", "int16", "int32", "int": - tocast = false - fn.Name = "int64" - case "uint64": - name.Sel.Name = "FormatUint" - tocast = false - case "uint8", "uint16", "uint32": - name.Sel.Name = "FormatUint" - tocast = false - fn.Name = "uint64" - } - } - } - } - if tocast { - call.Args[0] = &ast.CallExpr{Fun: &ast.Ident{Name: "int"}, Args: []ast.Expr{call.Args[0]}} - } - call.Args = append(call.Args, &ast.BasicLit{Value: "10"}) -} - func main() { - flag.Parse() - - if len(flag.Args()) < 1 { - log.Fatalf("Expects a golang file") - } - path := flag.Args()[0] - - file, err := os.Open(path) - if err != nil { - log.Fatalf("Failed to open golang file") - } - src, err := io.ReadAll(file) - if err != nil { - log.Fatalf("Failed to read golang file") - } - fset := token.NewFileSet() - f, err := parser.ParseFile(fset, path, src, parser.ParseComments) - if err != nil { - log.Fatalf("Failed to parse golang file") - } - changed := false - ast.Inspect(f, func(n ast.Node) bool { - switch call := n.(type) { - case *ast.CallExpr: - switch name := call.Fun.(type) { - case *ast.SelectorExpr: - if fmt.Sprintf("%s.%s", name.X, name.Sel) == "fmt.Sprintf" { - if len(call.Args) == 2 { - switch arg := call.Args[0].(type) { - case *ast.BasicLit: - if arg.Value == `"%d"` { - changed = true - changeCall(call) - } - } - } - } - } - } - return true - }) - if changed { - if !astutil.UsesImport(f, "fmt") { - astutil.DeleteImport(fset, f, "fmt") - } - astutil.AddImport(fset, f, "strconv") - printer.Fprint(os.Stdout, fset, f) - } + singlechecker.Main(analyzer.Analyzer) } diff --git a/sample/simple.go b/sample/simple.go deleted file mode 100644 index eae31d0..0000000 --- a/sample/simple.go +++ /dev/null @@ -1,10 +0,0 @@ -// You can edit this code! -// Click here and start typing. -package main - -import "fmt" - -func main() { - s := fmt.Sprintf("%d", uint32(42)) - fmt.Printf("Hello, %s", s) -} From 640592a47ff9d2a47f5d02033277e5756fd4b00e Mon Sep 17 00:00:00 2001 From: Anton Telyshev Date: Tue, 17 Oct 2023 22:29:24 +0300 Subject: [PATCH 4/9] support fmt.Sprint --- analyzer/analyzer.go | 65 ++++++++++++++++++----------- analyzer/analyzer_test.go | 13 ++++++ analyzer/testdata/src/p/p.go | 42 ++++++++++++++++++- analyzer/testdata/src/p/p.go.golden | 60 +++++++++++++++++++++----- 4 files changed, 142 insertions(+), 38 deletions(-) diff --git a/analyzer/analyzer.go b/analyzer/analyzer.go index b3a7bcb..de22476 100644 --- a/analyzer/analyzer.go +++ b/analyzer/analyzer.go @@ -22,9 +22,10 @@ var Analyzer = &analysis.Analyzer{ } func run(pass *analysis.Pass) (interface{}, error) { - var fmtSprintfObj types.Object + var fmtSprintObj, fmtSprintfObj types.Object for _, pkg := range pass.Pkg.Imports() { if pkg.Path() == "fmt" { + fmtSprintObj = pkg.Scope().Lookup("Sprint") fmtSprintfObj = pkg.Scope().Lookup("Sprintf") } } @@ -42,23 +43,37 @@ func run(pass *analysis.Pass) (interface{}, error) { if !ok { return } - if pass.TypesInfo.ObjectOf(called.Sel) != fmtSprintfObj { - return - } - if len(call.Args) != 2 { - return - } + calledObj := pass.TypesInfo.ObjectOf(called.Sel) - fmtString, value := call.Args[0], call.Args[1] + var ( + fn string + verb string + value ast.Expr + err error + ) + switch { + case calledObj == fmtSprintObj && len(call.Args) == 1: + fn = "fmt.Sprint" + verb = "%v" + value = call.Args[0] - verbLit, ok := fmtString.(*ast.BasicLit) - if !ok { + case calledObj == fmtSprintfObj && len(call.Args) == 2: + verbLit, ok := call.Args[0].(*ast.BasicLit) + if !ok { + return + } + verb, err = strconv.Unquote(verbLit.Value) + if err != nil { + verb = "" + } + + fn = "fmt.Sprintf" + value = call.Args[1] + + default: return } - verb, err := strconv.Unquote(verbLit.Value) - if err != nil { - verb = "" - } + switch verb { default: return @@ -75,7 +90,7 @@ func run(pass *analysis.Pass) (interface{}, error) { d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), - Message: "fmt.Sprintf can be replaced with just using the string", + Message: fn + " can be replaced with just using the string", SuggestedFixes: []analysis.SuggestedFix{ { Message: "Just use string value", @@ -93,7 +108,7 @@ func run(pass *analysis.Pass) (interface{}, error) { d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), - Message: "fmt.Sprintf can be replaced with " + errMethodCall, + Message: fn + " can be replaced with " + errMethodCall, SuggestedFixes: []analysis.SuggestedFix{ { Message: "Use " + errMethodCall, @@ -110,7 +125,7 @@ func run(pass *analysis.Pass) (interface{}, error) { d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), - Message: "fmt.Sprintf can be replaced with faster strconv.FormatBool", + Message: fn + " can be replaced with faster strconv.FormatBool", SuggestedFixes: []analysis.SuggestedFix{ { Message: "Use strconv.FormatBool", @@ -132,7 +147,7 @@ func run(pass *analysis.Pass) (interface{}, error) { d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), - Message: "fmt.Sprintf can be replaced with faster hex.EncodeToString", + Message: fn + " can be replaced with faster hex.EncodeToString", SuggestedFixes: []analysis.SuggestedFix{ { Message: "Use hex.EncodeToString", @@ -155,7 +170,7 @@ func run(pass *analysis.Pass) (interface{}, error) { d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), - Message: "fmt.Sprintf can be replaced with faster hex.EncodeToString", + Message: fn + " can be replaced with faster hex.EncodeToString", SuggestedFixes: []analysis.SuggestedFix{ { Message: "Use hex.EncodeToString", @@ -172,7 +187,7 @@ func run(pass *analysis.Pass) (interface{}, error) { d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), - Message: "fmt.Sprintf can be replaced with faster strconv.Itoa", + Message: fn + " can be replaced with faster strconv.Itoa", SuggestedFixes: []analysis.SuggestedFix{ { Message: "Use strconv.Itoa", @@ -195,7 +210,7 @@ func run(pass *analysis.Pass) (interface{}, error) { d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), - Message: "fmt.Sprintf can be replaced with faster strconv.Itoa", + Message: fn + " can be replaced with faster strconv.Itoa", SuggestedFixes: []analysis.SuggestedFix{ { Message: "Use strconv.Itoa", @@ -211,14 +226,14 @@ func run(pass *analysis.Pass) (interface{}, error) { d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), - Message: "fmt.Sprintf can be replaced with faster strconv.FormatInt", + Message: fn + " can be replaced with faster strconv.FormatInt", SuggestedFixes: []analysis.SuggestedFix{ { Message: "Use strconv.FormatInt", TextEdits: []analysis.TextEdit{ { Pos: call.Pos(), - End: call.Args[1].Pos(), + End: value.Pos(), NewText: []byte("strconv.FormatInt("), }, { @@ -235,7 +250,7 @@ func run(pass *analysis.Pass) (interface{}, error) { d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), - Message: "fmt.Sprintf can be replaced with faster strconv.FormatUint", + Message: fn + " can be replaced with faster strconv.FormatUint", SuggestedFixes: []analysis.SuggestedFix{ { Message: "Use strconv.FormatUint", @@ -258,7 +273,7 @@ func run(pass *analysis.Pass) (interface{}, error) { d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), - Message: "fmt.Sprintf can be replaced with faster strconv.FormatUint", + Message: fn + " can be replaced with faster strconv.FormatUint", SuggestedFixes: []analysis.SuggestedFix{ { Message: "Use strconv.FormatUint", diff --git a/analyzer/analyzer_test.go b/analyzer/analyzer_test.go index b47b220..541aed4 100644 --- a/analyzer/analyzer_test.go +++ b/analyzer/analyzer_test.go @@ -24,37 +24,50 @@ func TestReplacements(t *testing.T) { }{ {before: fmt.Sprintf("%s", "hello"), after: "hello"}, //nolint:gosimple //https://staticcheck.io/docs/checks#S1025 {before: fmt.Sprintf("%v", "hello"), after: "hello"}, + {before: fmt.Sprint("hello"), after: "hello"}, //nolint:gosimple //https://staticcheck.io/docs/checks#S1039 {before: fmt.Sprintf("%s", io.EOF), after: io.EOF.Error()}, {before: fmt.Sprintf("%v", io.EOF), after: io.EOF.Error()}, {before: fmt.Sprintf("%t", true), after: strconv.FormatBool(true)}, {before: fmt.Sprintf("%v", true), after: strconv.FormatBool(true)}, + {before: fmt.Sprint(true), after: strconv.FormatBool(true)}, {before: fmt.Sprintf("%t", false), after: strconv.FormatBool(false)}, {before: fmt.Sprintf("%v", false), after: strconv.FormatBool(false)}, + {before: fmt.Sprint(false), after: strconv.FormatBool(false)}, {before: fmt.Sprintf("%x", []byte{'a', 'b', 'c'}), after: hex.EncodeToString([]byte{'a', 'b', 'c'})}, {before: fmt.Sprintf("%d", 42), after: strconv.Itoa(42)}, {before: fmt.Sprintf("%v", 42), after: strconv.Itoa(42)}, + {before: fmt.Sprint(42), after: strconv.Itoa(42)}, {before: fmt.Sprintf("%d", int8(42)), after: strconv.Itoa(int(int8(42)))}, {before: fmt.Sprintf("%v", int8(42)), after: strconv.Itoa(int(int8(42)))}, + {before: fmt.Sprint(int8(42)), after: strconv.Itoa(int(int8(42)))}, {before: fmt.Sprintf("%d", int16(42)), after: strconv.Itoa(int(int16(42)))}, {before: fmt.Sprintf("%v", int16(42)), after: strconv.Itoa(int(int16(42)))}, + {before: fmt.Sprint(int16(42)), after: strconv.Itoa(int(int16(42)))}, {before: fmt.Sprintf("%d", int32(42)), after: strconv.Itoa(int(int32(42)))}, {before: fmt.Sprintf("%v", int32(42)), after: strconv.Itoa(int(int32(42)))}, + {before: fmt.Sprint(int32(42)), after: strconv.Itoa(int(int32(42)))}, {before: fmt.Sprintf("%d", int64(42)), after: strconv.FormatInt(int64(42), 10)}, {before: fmt.Sprintf("%v", int64(42)), after: strconv.FormatInt(int64(42), 10)}, + {before: fmt.Sprint(int64(42)), after: strconv.FormatInt(int64(42), 10)}, {before: fmt.Sprintf("%d", uint(42)), after: strconv.FormatUint(uint64(uint(42)), 10)}, {before: fmt.Sprintf("%v", uint(42)), after: strconv.FormatUint(uint64(uint(42)), 10)}, + {before: fmt.Sprint(uint(42)), after: strconv.FormatUint(uint64(uint(42)), 10)}, {before: fmt.Sprintf("%d", uint8(42)), after: strconv.FormatUint(uint64(uint8(42)), 10)}, {before: fmt.Sprintf("%v", uint8(42)), after: strconv.FormatUint(uint64(uint8(42)), 10)}, + {before: fmt.Sprint(uint8(42)), after: strconv.FormatUint(uint64(uint8(42)), 10)}, {before: fmt.Sprintf("%d", uint16(42)), after: strconv.FormatUint(uint64(uint16(42)), 10)}, {before: fmt.Sprintf("%v", uint16(42)), after: strconv.FormatUint(uint64(uint16(42)), 10)}, + {before: fmt.Sprint(uint16(42)), after: strconv.FormatUint(uint64(uint16(42)), 10)}, {before: fmt.Sprintf("%d", uint32(42)), after: strconv.FormatUint(uint64(uint32(42)), 10)}, {before: fmt.Sprintf("%v", uint32(42)), after: strconv.FormatUint(uint64(uint32(42)), 10)}, + {before: fmt.Sprint(uint32(42)), after: strconv.FormatUint(uint64(uint32(42)), 10)}, {before: fmt.Sprintf("%d", uint64(42)), after: strconv.FormatUint(uint64(42), 10)}, + {before: fmt.Sprint(uint64(42)), after: strconv.FormatUint(uint64(42), 10)}, {before: fmt.Sprintf("%v", uint64(42)), after: strconv.FormatUint(uint64(42), 10)}, } for _, tt := range cases { diff --git a/analyzer/testdata/src/p/p.go b/analyzer/testdata/src/p/p.go index 233253c..dbbd2fe 100644 --- a/analyzer/testdata/src/p/p.go +++ b/analyzer/testdata/src/p/p.go @@ -16,24 +16,32 @@ func positive() { var s string fmt.Sprintf("%s", "hello") // want "fmt.Sprintf can be replaced with just using the string" fmt.Sprintf("%v", "hello") // want "fmt.Sprintf can be replaced with just using the string" + fmt.Sprint("hello") // want "fmt.Sprint can be replaced with just using the string" fmt.Sprintf("%s", s) // want "fmt.Sprintf can be replaced with just using the string" fmt.Sprintf("%v", s) // want "fmt.Sprintf can be replaced with just using the string" + fmt.Sprint(s) // want "fmt.Sprint can be replaced with just using the string" var err error fmt.Sprintf("%s", errSentinel) // want "fmt.Sprintf can be replaced with errSentinel.Error()" fmt.Sprintf("%v", errSentinel) // want "fmt.Sprintf can be replaced with errSentinel.Error()" + fmt.Sprint(errSentinel) // want "fmt.Sprint can be replaced with errSentinel.Error()" fmt.Sprintf("%s", io.EOF) // want "fmt.Sprintf can be replaced with io.EOF.Error()" fmt.Sprintf("%v", io.EOF) // want "fmt.Sprintf can be replaced with io.EOF.Error()" + fmt.Sprint(io.EOF) // want "fmt.Sprint can be replaced with io.EOF.Error()" fmt.Sprintf("%s", err) // want "fmt.Sprintf can be replaced with err.Error()" fmt.Sprintf("%v", err) // want "fmt.Sprintf can be replaced with err.Error()" + fmt.Sprint(err) // want "fmt.Sprint can be replaced with err.Error()" var b bool fmt.Sprintf("%t", true) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" fmt.Sprintf("%v", true) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + fmt.Sprint(true) // want "fmt.Sprint can be replaced with faster strconv.FormatBool" fmt.Sprintf("%t", false) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" fmt.Sprintf("%v", false) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + fmt.Sprint(false) // want "fmt.Sprint can be replaced with faster strconv.FormatBool" fmt.Sprintf("%t", b) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" fmt.Sprintf("%v", b) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + fmt.Sprint(b) // want "fmt.Sprint can be replaced with faster strconv.FormatBool" var bs []byte var ba [3]byte @@ -49,24 +57,34 @@ func positive() { var i64 int64 fmt.Sprintf("%d", i) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" fmt.Sprintf("%v", i) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprint(i) // want "fmt.Sprint can be replaced with faster strconv.Itoa" fmt.Sprintf("%d", 42) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" fmt.Sprintf("%v", 42) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprint(42) // want "fmt.Sprint can be replaced with faster strconv.Itoa" fmt.Sprintf("%d", i8) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" fmt.Sprintf("%v", i8) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprint(i8) // want "fmt.Sprint can be replaced with faster strconv.Itoa" fmt.Sprintf("%d", int8(42)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" fmt.Sprintf("%v", int8(42)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprint(int8(42)) // want "fmt.Sprint can be replaced with faster strconv.Itoa" fmt.Sprintf("%d", i16) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" fmt.Sprintf("%v", i16) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprint(i16) // want "fmt.Sprint can be replaced with faster strconv.Itoa" fmt.Sprintf("%d", int16(42)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" fmt.Sprintf("%v", int16(42)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprint(int16(42)) // want "fmt.Sprint can be replaced with faster strconv.Itoa" fmt.Sprintf("%d", i32) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" fmt.Sprintf("%v", i32) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprint(i32) // want "fmt.Sprint can be replaced with faster strconv.Itoa" fmt.Sprintf("%d", int32(42)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" fmt.Sprintf("%v", int32(42)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprint(int32(42)) // want "fmt.Sprint can be replaced with faster strconv.Itoa" fmt.Sprintf("%d", i64) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" fmt.Sprintf("%v", i64) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" + fmt.Sprint(i64) // want "fmt.Sprint can be replaced with faster strconv.FormatInt" fmt.Sprintf("%d", int64(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" fmt.Sprintf("%v", int64(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" + fmt.Sprint(int64(42)) // want "fmt.Sprint can be replaced with faster strconv.FormatInt" var ui uint var ui8 uint8 @@ -75,36 +93,52 @@ func positive() { var ui64 uint64 fmt.Sprintf("%d", ui) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" fmt.Sprintf("%v", ui) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprint(ui) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" fmt.Sprintf("%d", uint(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" fmt.Sprintf("%v", uint(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprint(uint(42)) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" fmt.Sprintf("%d", ui8) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" fmt.Sprintf("%v", ui8) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprint(ui8) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" fmt.Sprintf("%d", uint8(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" fmt.Sprintf("%v", uint8(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprint(uint8(42)) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" fmt.Sprintf("%d", ui16) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" fmt.Sprintf("%v", ui16) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprint(ui16) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" fmt.Sprintf("%d", uint16(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" fmt.Sprintf("%v", uint16(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprint(uint16(42)) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" fmt.Sprintf("%d", ui32) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" fmt.Sprintf("%v", ui32) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprint(ui32) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" fmt.Sprintf("%d", uint32(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" fmt.Sprintf("%v", uint32(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprint(uint32(42)) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" fmt.Sprintf("%d", ui64) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" fmt.Sprintf("%v", ui64) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprint(ui64) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" fmt.Sprintf("%d", uint64(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" fmt.Sprintf("%v", uint64(42)) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprint(uint64(42)) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" } func suggestedFixesTest() { _ = func() string { + if false { + return fmt.Sprint("replace me") // want "fmt.Sprint can be replaced with just using the string" + } return fmt.Sprintf("%s", "replace me") // want "fmt.Sprintf can be replaced with just using the string" } + fmt.Println(fmt.Sprint(errSentinel)) // want "fmt.Sprint can be replaced with errSentinel.Error()" fmt.Println(fmt.Sprintf("%s", errSentinel)) // want "fmt.Sprintf can be replaced with errSentinel.Error()" _ = func() string { - switch { - case true: + switch 42 { + case 1: + return fmt.Sprint(false) // want "fmt.Sprint can be replaced with faster strconv.FormatBool" + case 2: return fmt.Sprintf("%t", true) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" } return "" @@ -113,6 +147,7 @@ func suggestedFixesTest() { var offset int params := url.Values{} params.Set("offset", fmt.Sprintf("%d", offset)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + params.Set("offset", fmt.Sprint(offset)) // want "fmt.Sprint can be replaced with faster strconv.Itoa" var pubKey []byte if verifyPubKey := true; verifyPubKey { @@ -122,11 +157,13 @@ func suggestedFixesTest() { var metaHash [16]byte fn := fmt.Sprintf("%s.%x", "coverage.MetaFilePref", metaHash) _ = "tmp." + fn + fmt.Sprintf("%d", time.Now().UnixNano()) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" + _ = "tmp." + fn + fmt.Sprint(time.Now().UnixNano()) // want "fmt.Sprint can be replaced with faster strconv.FormatInt" var change struct{ User struct{ ID uint } } var userStr string if id := change.User.ID; id != 0 { userStr = fmt.Sprintf("%d", id) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + userStr = fmt.Sprint(id) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" } _ = userStr } @@ -154,6 +191,7 @@ func negative() { fmt.Sprintf("test") fmt.Sprintf("%v") fmt.Sprintf("%d") + fmt.Sprintf("%#d", 42) fmt.Sprintf("value %d", 42) fmt.Sprintf(val, 42) fmt.Sprintf("%s %v", "hello", "world") diff --git a/analyzer/testdata/src/p/p.go.golden b/analyzer/testdata/src/p/p.go.golden index d54979f..da3fea3 100644 --- a/analyzer/testdata/src/p/p.go.golden +++ b/analyzer/testdata/src/p/p.go.golden @@ -16,29 +16,37 @@ func positive() { var s string "hello" // want "fmt.Sprintf can be replaced with just using the string" "hello" // want "fmt.Sprintf can be replaced with just using the string" + "hello" // want "fmt.Sprint can be replaced with just using the string" s // want "fmt.Sprintf can be replaced with just using the string" s // want "fmt.Sprintf can be replaced with just using the string" + s // want "fmt.Sprint can be replaced with just using the string" var err error errSentinel.Error() // want "fmt.Sprintf can be replaced with errSentinel.Error()" errSentinel.Error() // want "fmt.Sprintf can be replaced with errSentinel.Error()" + errSentinel.Error() // want "fmt.Sprint can be replaced with errSentinel.Error()" io.EOF.Error() // want "fmt.Sprintf can be replaced with io.EOF.Error()" io.EOF.Error() // want "fmt.Sprintf can be replaced with io.EOF.Error()" + io.EOF.Error() // want "fmt.Sprint can be replaced with io.EOF.Error()" err.Error() // want "fmt.Sprintf can be replaced with err.Error()" err.Error() // want "fmt.Sprintf can be replaced with err.Error()" + err.Error() // want "fmt.Sprint can be replaced with err.Error()" var b bool - strconv.FormatBool(true) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" - strconv.FormatBool(true) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + strconv.FormatBool(true) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + strconv.FormatBool(true) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + strconv.FormatBool(true) // want "fmt.Sprint can be replaced with faster strconv.FormatBool" strconv.FormatBool(false) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" strconv.FormatBool(false) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" - strconv.FormatBool(b) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" - strconv.FormatBool(b) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + strconv.FormatBool(false) // want "fmt.Sprint can be replaced with faster strconv.FormatBool" + strconv.FormatBool(b) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + strconv.FormatBool(b) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + strconv.FormatBool(b) // want "fmt.Sprint can be replaced with faster strconv.FormatBool" var bs []byte var ba [3]byte hex.EncodeToString([]byte{'a'}) // want "fmt.Sprintf can be replaced with faster hex.EncodeToString" - hex.EncodeToString([]uint8{'b'}) // want "fmt.Sprintf can be replaced with faster hex.EncodeToString" + hex.EncodeToString([]uint8{'b'}) // want "fmt.Sprintf can be replaced with faster hex.EncodeToString" hex.EncodeToString(bs) // want "fmt.Sprintf can be replaced with faster hex.EncodeToString" hex.EncodeToString(ba[:]) // want "fmt.Sprintf can be replaced with faster hex.EncodeToString" @@ -49,24 +57,34 @@ func positive() { var i64 int64 strconv.Itoa(i) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" strconv.Itoa(i) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(i) // want "fmt.Sprint can be replaced with faster strconv.Itoa" strconv.Itoa(42) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" strconv.Itoa(42) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(42) // want "fmt.Sprint can be replaced with faster strconv.Itoa" strconv.Itoa(int(i8)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" strconv.Itoa(int(i8)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(int(i8)) // want "fmt.Sprint can be replaced with faster strconv.Itoa" strconv.Itoa(int(int8(42))) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" strconv.Itoa(int(int8(42))) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(int(int8(42))) // want "fmt.Sprint can be replaced with faster strconv.Itoa" strconv.Itoa(int(i16)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" strconv.Itoa(int(i16)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(int(i16)) // want "fmt.Sprint can be replaced with faster strconv.Itoa" strconv.Itoa(int(int16(42))) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" strconv.Itoa(int(int16(42))) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(int(int16(42))) // want "fmt.Sprint can be replaced with faster strconv.Itoa" strconv.Itoa(int(i32)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" strconv.Itoa(int(i32)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(int(i32)) // want "fmt.Sprint can be replaced with faster strconv.Itoa" strconv.Itoa(int(int32(42))) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" strconv.Itoa(int(int32(42))) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(int(int32(42))) // want "fmt.Sprint can be replaced with faster strconv.Itoa" strconv.FormatInt(i64, 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" strconv.FormatInt(i64, 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" + strconv.FormatInt(i64, 10) // want "fmt.Sprint can be replaced with faster strconv.FormatInt" strconv.FormatInt(int64(42), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" strconv.FormatInt(int64(42), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" + strconv.FormatInt(int64(42), 10) // want "fmt.Sprint can be replaced with faster strconv.FormatInt" var ui uint var ui8 uint8 @@ -75,36 +93,52 @@ func positive() { var ui64 uint64 strconv.FormatUint(uint64(ui), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" strconv.FormatUint(uint64(ui), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(ui), 10) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" strconv.FormatUint(uint64(uint(42)), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" strconv.FormatUint(uint64(uint(42)), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(uint(42)), 10) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" strconv.FormatUint(uint64(ui8), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" strconv.FormatUint(uint64(ui8), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(ui8), 10) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" strconv.FormatUint(uint64(uint8(42)), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" strconv.FormatUint(uint64(uint8(42)), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(uint8(42)), 10) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" strconv.FormatUint(uint64(ui16), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" strconv.FormatUint(uint64(ui16), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(ui16), 10) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" strconv.FormatUint(uint64(uint16(42)), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" strconv.FormatUint(uint64(uint16(42)), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(uint16(42)), 10) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" strconv.FormatUint(uint64(ui32), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" strconv.FormatUint(uint64(ui32), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(ui32), 10) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" strconv.FormatUint(uint64(uint32(42)), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" strconv.FormatUint(uint64(uint32(42)), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" - strconv.FormatUint(ui64, 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" - strconv.FormatUint(ui64, 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" - strconv.FormatUint(uint64(42), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" - strconv.FormatUint(uint64(42), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(uint32(42)), 10) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" + strconv.FormatUint(ui64, 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(ui64, 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(ui64, 10) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(42), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(42), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(42), 10) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" } func suggestedFixesTest() { _ = func() string { + if false { + return "replace me" // want "fmt.Sprint can be replaced with just using the string" + } return "replace me" // want "fmt.Sprintf can be replaced with just using the string" } + fmt.Println(errSentinel.Error()) // want "fmt.Sprint can be replaced with errSentinel.Error()" fmt.Println(errSentinel.Error()) // want "fmt.Sprintf can be replaced with errSentinel.Error()" _ = func() string { - switch { - case true: + switch 42 { + case 1: + return strconv.FormatBool(false) // want "fmt.Sprint can be replaced with faster strconv.FormatBool" + case 2: return strconv.FormatBool(true) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" } return "" @@ -113,6 +147,7 @@ func suggestedFixesTest() { var offset int params := url.Values{} params.Set("offset", strconv.Itoa(offset)) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + params.Set("offset", strconv.Itoa(offset)) // want "fmt.Sprint can be replaced with faster strconv.Itoa" var pubKey []byte if verifyPubKey := true; verifyPubKey { @@ -122,11 +157,13 @@ func suggestedFixesTest() { var metaHash [16]byte fn := fmt.Sprintf("%s.%x", "coverage.MetaFilePref", metaHash) _ = "tmp." + fn + strconv.FormatInt(time.Now().UnixNano(), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" + _ = "tmp." + fn + strconv.FormatInt(time.Now().UnixNano(), 10) // want "fmt.Sprint can be replaced with faster strconv.FormatInt" var change struct{ User struct{ ID uint } } var userStr string if id := change.User.ID; id != 0 { userStr = strconv.FormatUint(uint64(id), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + userStr = strconv.FormatUint(uint64(id), 10) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" } _ = userStr } @@ -154,6 +191,7 @@ func negative() { fmt.Sprintf("test") fmt.Sprintf("%v") fmt.Sprintf("%d") + fmt.Sprintf("%#d", 42) fmt.Sprintf("value %d", 42) fmt.Sprintf(val, 42) fmt.Sprintf("%s %v", "hello", "world") From 61b568fd44fced78535e8a5f25fc715659092495 Mon Sep 17 00:00:00 2001 From: Anton Telyshev Date: Tue, 17 Oct 2023 22:40:43 +0300 Subject: [PATCH 5/9] add replacements benchmarking --- Makefile | 5 +- analyzer/replacements_bench_test.go | 94 +++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 analyzer/replacements_bench_test.go diff --git a/Makefile b/Makefile index d79c2f8..43271ea 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: fmt tidy lint test install +.PHONY: fmt tidy lint test bench install default: fmt tidy lint test install @@ -14,6 +14,9 @@ lint: test: go test ./... +bench: + go test -bench=Bench ./... + install: go install . gostrconv -h 2>&1 | head -n1 diff --git a/analyzer/replacements_bench_test.go b/analyzer/replacements_bench_test.go new file mode 100644 index 0000000..0f8302e --- /dev/null +++ b/analyzer/replacements_bench_test.go @@ -0,0 +1,94 @@ +package analyzer_test + +import ( + "context" + "encoding/hex" + "fmt" + "math" + "strconv" + "testing" +) + +func BenchmarkStringFormatting(b *testing.B) { + b.Run("fmt.Sprintf", func(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = fmt.Sprintf("%s", "hello") //nolint:gosimple //https://staticcheck.io/docs/checks#S1025 + } + }) + + b.Run("just string", func(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = "hello" + } + }) +} + +func BenchmarkErrorFormatting(b *testing.B) { + b.Run("fmt.Sprintf", func(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = fmt.Sprintf("%s", context.DeadlineExceeded) + } + }) + + b.Run("Error()", func(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = context.DeadlineExceeded.Error() + } + }) +} + +func BenchmarkBoolFormatting(b *testing.B) { + b.Run("fmt.Sprintf", func(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = fmt.Sprintf("%t", true) + } + }) + + b.Run("strconv.FormatBool", func(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = strconv.FormatBool(true) + } + }) +} + +func BenchmarkHexEncoding(b *testing.B) { + b.Run("fmt.Sprintf", func(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = fmt.Sprintf("%x", []byte{'a', 'b', 'c'}) + } + }) + + b.Run("hex.EncodeToString", func(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = hex.EncodeToString([]byte{'a', 'b', 'c'}) + } + }) +} + +func BenchmarkIntFormatting(b *testing.B) { + b.Run("fmt.Sprintf", func(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = fmt.Sprintf("%d", math.MaxInt) + } + }) + + b.Run("strconv.Itoa", func(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = strconv.Itoa(math.MaxInt) + } + }) +} + +func BenchmarkUIntFormatting(b *testing.B) { + b.Run("fmt.Sprintf", func(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = fmt.Sprintf("%d", uint64(math.MaxUint)) + } + }) + + b.Run("strconv.FormatUint", func(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = strconv.FormatUint(math.MaxUint, 10) + } + }) +} From 99a871c8187edc7a576a66d29fa3f9a52d500b89 Mon Sep 17 00:00:00 2001 From: Anton Telyshev Date: Tue, 17 Oct 2023 23:04:53 +0300 Subject: [PATCH 6/9] add more negative test cases --- analyzer/analyzer.go | 2 +- analyzer/testdata/src/p/p.go | 86 +++++++++++++++++++++++++++++ analyzer/testdata/src/p/p.go.golden | 86 +++++++++++++++++++++++++++++ 3 files changed, 173 insertions(+), 1 deletion(-) diff --git a/analyzer/analyzer.go b/analyzer/analyzer.go index de22476..1b45526 100644 --- a/analyzer/analyzer.go +++ b/analyzer/analyzer.go @@ -16,7 +16,7 @@ import ( var Analyzer = &analysis.Analyzer{ Name: "gostrconv", - Doc: "Checks that fmt.Sprintf can be replaced with a faster strconv function.", + Doc: "Checks that fmt.Sprintf can be replaced with a faster analogue.", Run: run, Requires: []*analysis.Analyzer{inspect.Analyzer}, } diff --git a/analyzer/testdata/src/p/p.go b/analyzer/testdata/src/p/p.go index dbbd2fe..de25a92 100644 --- a/analyzer/testdata/src/p/p.go +++ b/analyzer/testdata/src/p/p.go @@ -188,9 +188,12 @@ func negative() { fmt.Fprintf(os.Stdout, "%d", 42) fmt.Fprintf(os.Stdout, "%s %d", "hello", 42) + fmt.Sprint("test", 42) + fmt.Sprint(42, 42) fmt.Sprintf("test") fmt.Sprintf("%v") fmt.Sprintf("%d") + fmt.Sprintf("%d", 42, 42) fmt.Sprintf("%#d", 42) fmt.Sprintf("value %d", 42) fmt.Sprintf(val, 42) @@ -306,3 +309,86 @@ func negative() { fmt.Sprintf("%x", ptr) fmt.Sprintf("%X", ptr) } + +func malformed() { + fmt.Sprintf("%d", "example") + fmt.Sprintf("%T", "example") + fmt.Sprintf("%t", "example") + fmt.Sprintf("%b", "example") + fmt.Sprintf("%e", "example") + fmt.Sprintf("%E", "example") + fmt.Sprintf("%f", "example") + fmt.Sprintf("%F", "example") + fmt.Sprintf("%g", "example") + fmt.Sprintf("%G", "example") + fmt.Sprintf("%x", "example") + fmt.Sprintf("%X", "example") + + fmt.Sprintf("%d", errSentinel) + fmt.Sprintf("%T", errSentinel) + fmt.Sprintf("%t", errSentinel) + fmt.Sprintf("%b", errSentinel) + fmt.Sprintf("%e", errSentinel) + fmt.Sprintf("%E", errSentinel) + fmt.Sprintf("%f", errSentinel) + fmt.Sprintf("%F", errSentinel) + fmt.Sprintf("%g", errSentinel) + fmt.Sprintf("%G", errSentinel) + fmt.Sprintf("%x", errSentinel) + fmt.Sprintf("%X", errSentinel) + + fmt.Sprintf("%d", true) + fmt.Sprintf("%T", true) + fmt.Sprintf("%b", true) + fmt.Sprintf("%e", true) + fmt.Sprintf("%E", true) + fmt.Sprintf("%f", true) + fmt.Sprintf("%F", true) + fmt.Sprintf("%g", true) + fmt.Sprintf("%G", true) + fmt.Sprintf("%x", true) + fmt.Sprintf("%X", true) + + var bb []byte + fmt.Sprintf("%d", bb) + fmt.Sprintf("%T", bb) + fmt.Sprintf("%t", bb) + fmt.Sprintf("%b", bb) + fmt.Sprintf("%e", bb) + fmt.Sprintf("%E", bb) + fmt.Sprintf("%f", bb) + fmt.Sprintf("%F", bb) + fmt.Sprintf("%g", bb) + fmt.Sprintf("%G", bb) + fmt.Sprintf("%X", bb) + fmt.Sprintf("%v", bb) + + fmt.Sprintf("%T", 42) + fmt.Sprintf("%t", 42) + fmt.Sprintf("%b", 42) + fmt.Sprintf("%e", 42) + fmt.Sprintf("%E", 42) + fmt.Sprintf("%f", 42) + fmt.Sprintf("%F", 42) + fmt.Sprintf("%g", 42) + fmt.Sprintf("%G", 42) + fmt.Sprintf("%x", 42) + fmt.Sprintf("%X", 42) + + fmt.Sprintf("%T", uint(42)) + fmt.Sprintf("%t", uint(42)) + fmt.Sprintf("%b", uint(42)) + fmt.Sprintf("%e", uint(42)) + fmt.Sprintf("%E", uint(42)) + fmt.Sprintf("%f", uint(42)) + fmt.Sprintf("%F", uint(42)) + fmt.Sprintf("%g", uint(42)) + fmt.Sprintf("%G", uint(42)) + fmt.Sprintf("%x", uint(42)) + fmt.Sprintf("%X", uint(42)) + + fmt.Sprintf("%d", 42.42) + fmt.Sprintf("%d", map[string]string{}) + fmt.Sprint(make(chan int)) + fmt.Sprint([]int{1, 2, 3}) +} diff --git a/analyzer/testdata/src/p/p.go.golden b/analyzer/testdata/src/p/p.go.golden index da3fea3..e188ba2 100644 --- a/analyzer/testdata/src/p/p.go.golden +++ b/analyzer/testdata/src/p/p.go.golden @@ -188,9 +188,12 @@ func negative() { fmt.Fprintf(os.Stdout, "%d", 42) fmt.Fprintf(os.Stdout, "%s %d", "hello", 42) + fmt.Sprint("test", 42) + fmt.Sprint(42, 42) fmt.Sprintf("test") fmt.Sprintf("%v") fmt.Sprintf("%d") + fmt.Sprintf("%d", 42, 42) fmt.Sprintf("%#d", 42) fmt.Sprintf("value %d", 42) fmt.Sprintf(val, 42) @@ -306,3 +309,86 @@ func negative() { fmt.Sprintf("%x", ptr) fmt.Sprintf("%X", ptr) } + +func malformed() { + fmt.Sprintf("%d", "example") + fmt.Sprintf("%T", "example") + fmt.Sprintf("%t", "example") + fmt.Sprintf("%b", "example") + fmt.Sprintf("%e", "example") + fmt.Sprintf("%E", "example") + fmt.Sprintf("%f", "example") + fmt.Sprintf("%F", "example") + fmt.Sprintf("%g", "example") + fmt.Sprintf("%G", "example") + fmt.Sprintf("%x", "example") + fmt.Sprintf("%X", "example") + + fmt.Sprintf("%d", errSentinel) + fmt.Sprintf("%T", errSentinel) + fmt.Sprintf("%t", errSentinel) + fmt.Sprintf("%b", errSentinel) + fmt.Sprintf("%e", errSentinel) + fmt.Sprintf("%E", errSentinel) + fmt.Sprintf("%f", errSentinel) + fmt.Sprintf("%F", errSentinel) + fmt.Sprintf("%g", errSentinel) + fmt.Sprintf("%G", errSentinel) + fmt.Sprintf("%x", errSentinel) + fmt.Sprintf("%X", errSentinel) + + fmt.Sprintf("%d", true) + fmt.Sprintf("%T", true) + fmt.Sprintf("%b", true) + fmt.Sprintf("%e", true) + fmt.Sprintf("%E", true) + fmt.Sprintf("%f", true) + fmt.Sprintf("%F", true) + fmt.Sprintf("%g", true) + fmt.Sprintf("%G", true) + fmt.Sprintf("%x", true) + fmt.Sprintf("%X", true) + + var bb []byte + fmt.Sprintf("%d", bb) + fmt.Sprintf("%T", bb) + fmt.Sprintf("%t", bb) + fmt.Sprintf("%b", bb) + fmt.Sprintf("%e", bb) + fmt.Sprintf("%E", bb) + fmt.Sprintf("%f", bb) + fmt.Sprintf("%F", bb) + fmt.Sprintf("%g", bb) + fmt.Sprintf("%G", bb) + fmt.Sprintf("%X", bb) + fmt.Sprintf("%v", bb) + + fmt.Sprintf("%T", 42) + fmt.Sprintf("%t", 42) + fmt.Sprintf("%b", 42) + fmt.Sprintf("%e", 42) + fmt.Sprintf("%E", 42) + fmt.Sprintf("%f", 42) + fmt.Sprintf("%F", 42) + fmt.Sprintf("%g", 42) + fmt.Sprintf("%G", 42) + fmt.Sprintf("%x", 42) + fmt.Sprintf("%X", 42) + + fmt.Sprintf("%T", uint(42)) + fmt.Sprintf("%t", uint(42)) + fmt.Sprintf("%b", uint(42)) + fmt.Sprintf("%e", uint(42)) + fmt.Sprintf("%E", uint(42)) + fmt.Sprintf("%f", uint(42)) + fmt.Sprintf("%F", uint(42)) + fmt.Sprintf("%g", uint(42)) + fmt.Sprintf("%G", uint(42)) + fmt.Sprintf("%x", uint(42)) + fmt.Sprintf("%X", uint(42)) + + fmt.Sprintf("%d", 42.42) + fmt.Sprintf("%d", map[string]string{}) + fmt.Sprint(make(chan int)) + fmt.Sprint([]int{1, 2, 3}) +} From 6c7f4357754dd2531154dc14c3e6670622231869 Mon Sep 17 00:00:00 2001 From: Anton Telyshev Date: Tue, 17 Oct 2023 23:16:29 +0300 Subject: [PATCH 7/9] update README --- README.md | 30 ++++++++++++++++++++++++++---- analyzer/analyzer.go | 2 +- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9b94438..7ad3077 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,30 @@ # gostrconv -Gostrconv: Golang linter for performance, aiming at usages of `fmt.Sprintf` which have faster alternatives. -# Usage +[![CI](https://github.com/catenacyber/gostrconv/actions/workflows/ci.yml/badge.svg)](https://github.com/catenacyber/gostrconv/actions/workflows/ci.yml) +[![Go Report Card](https://goreportcard.com/badge/github.com/catenacyber/gostrconv)](https://goreportcard.com/report/github.com/catenacyber/gostrconv?dummy=unused) -./gostrconv file.go +Golang linter for performance, aiming at usages of `fmt.Sprintf` which have faster alternatives. -Rewrites `fmt.Sprintf("%d",` into faster `strconv.Itoa` and such +## Installation + +```sh +go get github.com/catenacyber/gostrconv@latest +``` + +## Usage + +```sh +gostrconv --fix ./... +``` + +### Replacements + +``` +fmt.Sprintf("%s", strVal) -> strVal +fmt.Sprintf("%t", boolVal) -> strconv.FormatBool(boolBal) +fmt.Sprintf("%x", hash) -> hex.EncodeToString(hash) +fmt.Sprintf("%d", id) -> strconv.Itoa(id) +fmt.Sprintf("%v", version) -> strconv.FormatUint(uint64(version), 10) +``` + +More in [tests](./analyzer/testdata/src/p/p.go). diff --git a/analyzer/analyzer.go b/analyzer/analyzer.go index 1b45526..8c17491 100644 --- a/analyzer/analyzer.go +++ b/analyzer/analyzer.go @@ -16,7 +16,7 @@ import ( var Analyzer = &analysis.Analyzer{ Name: "gostrconv", - Doc: "Checks that fmt.Sprintf can be replaced with a faster analogue.", + Doc: "Checks that fmt.Sprintf can be replaced with a faster alternative.", Run: run, Requires: []*analysis.Analyzer{inspect.Analyzer}, } From b34efb33c7a3e23c6410a1e919de4ed751855921 Mon Sep 17 00:00:00 2001 From: Anton Telyshev Date: Wed, 18 Oct 2023 17:26:40 +0300 Subject: [PATCH 8/9] early return on verb unquote error --- analyzer/analyzer.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/analyzer/analyzer.go b/analyzer/analyzer.go index 8c17491..eed4311 100644 --- a/analyzer/analyzer.go +++ b/analyzer/analyzer.go @@ -64,7 +64,8 @@ func run(pass *analysis.Pass) (interface{}, error) { } verb, err = strconv.Unquote(verbLit.Value) if err != nil { - verb = "" + // Probably unreachable. + return } fn = "fmt.Sprintf" From c81d12085014fae46cfb0b4ed46a369c287e3ee2 Mon Sep 17 00:00:00 2001 From: Anton Telyshev Date: Wed, 18 Oct 2023 19:55:51 +0300 Subject: [PATCH 9/9] add benchmarks for fmt.Sprint --- analyzer/replacements_bench_test.go | 32 ++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/analyzer/replacements_bench_test.go b/analyzer/replacements_bench_test.go index 0f8302e..57b9b13 100644 --- a/analyzer/replacements_bench_test.go +++ b/analyzer/replacements_bench_test.go @@ -10,6 +10,12 @@ import ( ) func BenchmarkStringFormatting(b *testing.B) { + b.Run("fmt.Sprint", func(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = fmt.Sprint("hello") //nolint:gosimple //https://staticcheck.io/docs/checks#S1039 + } + }) + b.Run("fmt.Sprintf", func(b *testing.B) { for n := 0; n < b.N; n++ { _ = fmt.Sprintf("%s", "hello") //nolint:gosimple //https://staticcheck.io/docs/checks#S1025 @@ -24,6 +30,12 @@ func BenchmarkStringFormatting(b *testing.B) { } func BenchmarkErrorFormatting(b *testing.B) { + b.Run("fmt.Sprint", func(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = fmt.Sprint(context.DeadlineExceeded) + } + }) + b.Run("fmt.Sprintf", func(b *testing.B) { for n := 0; n < b.N; n++ { _ = fmt.Sprintf("%s", context.DeadlineExceeded) @@ -38,6 +50,12 @@ func BenchmarkErrorFormatting(b *testing.B) { } func BenchmarkBoolFormatting(b *testing.B) { + b.Run("fmt.Sprint", func(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = fmt.Sprint(true) + } + }) + b.Run("fmt.Sprintf", func(b *testing.B) { for n := 0; n < b.N; n++ { _ = fmt.Sprintf("%t", true) @@ -66,6 +84,12 @@ func BenchmarkHexEncoding(b *testing.B) { } func BenchmarkIntFormatting(b *testing.B) { + b.Run("fmt.Sprint", func(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = fmt.Sprint(math.MaxInt) + } + }) + b.Run("fmt.Sprintf", func(b *testing.B) { for n := 0; n < b.N; n++ { _ = fmt.Sprintf("%d", math.MaxInt) @@ -79,7 +103,13 @@ func BenchmarkIntFormatting(b *testing.B) { }) } -func BenchmarkUIntFormatting(b *testing.B) { +func BenchmarkUintFormatting(b *testing.B) { + b.Run("fmt.Sprint", func(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = fmt.Sprint(uint64(math.MaxUint)) + } + }) + b.Run("fmt.Sprintf", func(b *testing.B) { for n := 0; n < b.N; n++ { _ = fmt.Sprintf("%d", uint64(math.MaxUint))