<|start_filename|>perfguard/lint/shared_context.go<|end_filename|>
package lint

import (
	"bytes"
	"go/ast"
	"go/printer"
	"go/token"
	"go/types"

	"github.com/go-toolsmith/astfmt"
	"github.com/quasilyte/go-perfguard/internal/resolve"
	"github.com/quasilyte/perf-heatmap/heatmap"
)

type SharedContext struct {
	Target *Target

	Heatmap *heatmap.Index

	Filename string // Filename is a name of file that is being analyzed
	TypeName string // TypeName is a receiver name of the current func
	FuncName string // FuncName is a current func/method name

	// Sym is a currently analyzed function call symbol info.
	// Only relevant for funccall checkers.
	Sym resolve.CallInfo

	Warn func(Warning)

	printer *astfmt.Printer
}

func (ctx *SharedContext) Reset(target *Target) {
	ctx.Target = target
	ctx.printer = nil
}

func (ctx *SharedContext) Sprintf(format string, args ...interface{}) string {
	if len(args) == 0 {
		return format
	}
	if ctx.printer == nil {
		ctx.printer = astfmt.NewPrinter(ctx.Target.Fset)
	}
	return ctx.printer.Sprintf(format, args...)
}

func (ctx *SharedContext) NodeText(n ast.Node) []byte {
	var buf bytes.Buffer
	if err := printer.Fprint(&buf, ctx.Target.Fset, n); err != nil {
		return nil
	}
	return buf.Bytes()
}

func (ctx *SharedContext) Position(n ast.Node) token.Position {
	return ctx.Target.Fset.Position(n.Pos())
}

func (ctx *SharedContext) EndPosition(n ast.Node) token.Position {
	return ctx.Target.Fset.Position(n.End())
}

func (ctx *SharedContext) ObjectOf(x *ast.Ident) types.Object {
	obj := ctx.Target.Types.ObjectOf(x)
	if obj != nil {
		return obj
	}
	return UnknownVar
}

func (ctx *SharedContext) VarOf(x *ast.Ident) *types.Var {
	obj := ctx.Target.Types.ObjectOf(x)
	if obj != nil {
		if v, ok := obj.(*types.Var); ok {
			return v
		}
	}
	return UnknownVar
}

// TypeOf returns the type of expression x.
//
// Unlike TypesInfo.TypeOf, it never returns nil.
// Instead, it returns the Invalid type as a sentinel UnknownType value.
func (ctx *SharedContext) TypeOf(x ast.Expr) types.Type {
	typ := ctx.Target.Types.TypeOf(x)
	if typ != nil {
		return typ
	}
	// Usually it means that some incorrect type info was loaded
	// or the analyzed package was only partially (?) correct.
	// To avoid nil pointer panics we can return a sentinel value
	// that will fail most type assertions as well as kind checks
	// (if the call side expects a *types.Basic).
	return UnknownType
}

// UnknownType is a special sentinel value that is returned from the CheckerContext.TypeOf
// method instead of the nil type.
var UnknownType types.Type = types.Typ[types.Invalid]

var UnknownVar = types.NewVar(
	token.NoPos,
	types.NewPackage("unknown", "unknown"),
	"unknown",
	UnknownType)


<|start_filename|>cmd/perfguard/testdata/rulestest/rangeExprCopy/rangeExprCopy.go<|end_filename|>
package rulestest

var globalArray [400]object

type object struct {
	x, y, z int64
	data    []byte
}

func getArray() [50000]byte {
	return [50000]byte{}
}

func Warn() {
	var localArray [1000]int
	for _, v := range localArray { // want `localArray => &localArray`
		println(v)
	}

	for _, v := range globalArray { // want `globalArray => &globalArray`
		println(v.x)
		println(v.y)
	}

	for _, b := range getArray() { // want `range over big array value expression is ineffective`
		println(b)
	}
}

func Ignore() {
	var localArray [1000]int
	for _, v := range &localArray {
		println(v)
	}

	localSlice := localArray[:]
	for _, v := range localSlice {
		println(v)
	}

	for i := range localArray {
		println(localArray[i])
	}

	for i := range localArray {
		v := &localArray[i]
		println(v)
	}

	for range localArray {
	}

	arrayPtr := &localArray
	for _, v := range arrayPtr {
		println(v)
	}
}


<|start_filename|>perfguard/checkers/funccheckers/boolValuedMap.go<|end_filename|>
package funccheckers

import (
	"go/ast"
	"go/token"
	"go/types"

	"github.com/quasilyte/go-perfguard/perfguard/checkers"
	"github.com/quasilyte/go-perfguard/perfguard/lint"
)

func init() {
	doc := checkers.Doc{
		Name:  "boolValuedMap",
		Score: 3,
	}
	checkers.RegisterFuncChecker(doc, func() checkers.FuncChecker {
		return &boolValuedMapChecker{
			allowedUsages: make(map[*ast.Ident]struct{}),
		}
	})
}

type boolValuedMapChecker struct {
	ctx           *lint.Context
	candidates    []boolValuedMapCandidate
	allowedUsages map[*ast.Ident]struct{}
	nestedFunc    bool
}

type boolValuedMapCandidate struct {
	// A part of a map declaration related to the value.
	// So, in `map[$t]bool` boolValueNode holds the `bool` part.
	boolValueNode ast.Expr

	obj *types.Var

	// Holds `true` expression nodes to be changed to `struct{}{}`.
	assignTrueUsages []ast.Expr

	// Holds `m[k]` expression occurring inside if condition,
	// where we can change it to `_, ok := m[k]; ok`.
	ifCondUsages []ast.Expr

	// Holds `!m[k]` expressions in cond.
	// Like ifCondUsages, but the condition is negated with not operator.
	ifNotCondUsages []*ast.UnaryExpr
}

func (c *boolValuedMapChecker) CheckFunc(ctx *lint.Context, body *ast.BlockStmt) error {
	c.ctx = ctx
	c.candidates = c.candidates[:0]
	c.nestedFunc = false
	for k := range c.allowedUsages {
		delete(c.allowedUsages, k)
	}

	ast.Inspect(body, c.walk)

	for _, candidate := range c.candidates {
		if len(candidate.assignTrueUsages) == 0 {
			continue
		}
		oldNodes := make([]ast.Node, 0, 1+len(candidate.assignTrueUsages))
		newNodes := make([]lint.NodeReplacement, 0, cap(oldNodes))
		oldNodes = append(oldNodes, candidate.boolValueNode)
		newNodes = append(newNodes, lint.NodeReplacement{Text: []byte("struct{}")})
		for _, u := range candidate.assignTrueUsages {
			oldNodes = append(oldNodes, u)
			newNodes = append(newNodes, lint.NodeReplacement{Text: []byte("struct{}{}")})
		}
		for _, u := range candidate.ifCondUsages {
			oldNodes = append(oldNodes, u)
			replacement := make([]byte, 0, 32)
			replacement = append(replacement, "_, ok := "...)
			replacement = append(replacement, ctx.NodeText(u)...)
			replacement = append(replacement, "; ok"...)
			newNodes = append(newNodes, lint.NodeReplacement{Text: replacement})
		}
		for _, u := range candidate.ifNotCondUsages {
			oldNodes = append(oldNodes, u)
			replacement := make([]byte, 0, 32)
			replacement = append(replacement, "_, ok := "...)
			replacement = append(replacement, ctx.NodeText(u.X)...)
			replacement = append(replacement, "; !ok"...)
			newNodes = append(newNodes, lint.NodeReplacement{Text: replacement})
		}
		ctx.MultiChangeSuggest(lint.MultiChangeSuggestParams{
			ReportMessage: "change map[T]bool to map[T]struct{}",
			ReportPos:     candidate.obj.Pos(),
			OldNodes:      oldNodes,
			NewNodes:      newNodes,
		})
	}

	return nil
}

func (c *boolValuedMapChecker) handleMapDefine(n *ast.AssignStmt) bool {
	if c.nestedFunc {
		return false
	}

	if len(n.Lhs) != 1 || len(n.Rhs) != 1 || n.Tok != token.DEFINE {
		return false
	}
	var boolTypeNode *ast.Ident
	var mapTypeNode *ast.MapType
	rhs := n.Rhs[0]
	switch rhs := rhs.(type) {
	case *ast.CallExpr:
		fn, ok := rhs.Fun.(*ast.Ident)
		if !ok || fn.Name != "make" || len(rhs.Args) == 0 {
			return false
		}
		mapTypeNode, _ = rhs.Args[0].(*ast.MapType)

	case *ast.CompositeLit:
		if len(rhs.Elts) != 0 || rhs.Type == nil {
			return false
		}
		mapTypeNode, _ = rhs.Type.(*ast.MapType)
	}
	if mapTypeNode == nil {
		return false
	}

	valueTypeIdent, ok := mapTypeNode.Value.(*ast.Ident)
	if !ok || valueTypeIdent.Name != "bool" {
		return false
	}
	boolTypeNode = valueTypeIdent

	name, ok := n.Lhs[0].(*ast.Ident)
	if !ok {
		return false
	}
	if boolTypeNode == nil {
		return false
	}
	obj := c.ctx.VarOf(name)
	c.track(name, obj, boolTypeNode)
	return true
}

func (c *boolValuedMapChecker) walk(n ast.Node) bool {
	switch n := n.(type) {
	case *ast.FuncLit:
		nestedFunc := c.nestedFunc
		c.nestedFunc = true
		ast.Inspect(n.Body, c.walk)
		c.nestedFunc = nestedFunc
		return false

	case *ast.IfStmt:
		// Check if it's `if m[k] { ... }` usage.
		// Check if it's `if !m[k] { ... }` usage.
		var cond ast.Expr
		var notExpr *ast.UnaryExpr
		unaryExpr, ok := n.Cond.(*ast.UnaryExpr)
		if ok && unaryExpr.Op == token.NOT {
			notExpr = unaryExpr
			cond = notExpr.X
		} else {
			cond = n.Cond
		}

		indexing, ok := cond.(*ast.IndexExpr)
		if !ok {
			return true
		}
		name, ok := indexing.X.(*ast.Ident)
		if !ok {
			return true
		}
		obj := c.ctx.VarOf(name)
		if !c.isBoolValuedMap(obj.Type()) {
			return true
		}
		candidate := c.find(obj)
		if candidate != nil {
			c.allowedUsages[name] = struct{}{}
			if notExpr != nil {
				candidate.ifNotCondUsages = append(candidate.ifNotCondUsages, notExpr)
			} else {
				candidate.ifCondUsages = append(candidate.ifCondUsages, cond)
			}
		}
		return true

	case *ast.RangeStmt:
		// Permit ranging over a tracked map keys.
		if n.Value != nil {
			return true
		}
		name, ok := n.X.(*ast.Ident)
		if !ok {
			return true
		}
		obj := c.ctx.VarOf(name)
		if c.isBoolValuedMap(obj.Type()) {
			c.allowedUsages[name] = struct{}{}
		}

	case *ast.AssignStmt:
		if c.handleMapDefine(n) {
			return false
		}
		// Recognize `m[k] = true` usages.
		if len(n.Lhs) != 1 || len(n.Rhs) != 1 || n.Tok != token.ASSIGN {
			return true
		}
		trueNode, ok := n.Rhs[0].(*ast.Ident)
		if !ok || trueNode.Name != "true" {
			return true
		}
		indexing, ok := n.Lhs[0].(*ast.IndexExpr)
		if !ok {
			return true
		}
		name, ok := indexing.X.(*ast.Ident)
		if !ok {
			return true
		}
		obj := c.ctx.VarOf(name)
		if !c.isBoolValuedMap(obj.Type()) {
			return true
		}
		candidate := c.find(obj)
		if candidate != nil {
			c.allowedUsages[name] = struct{}{}
			candidate.assignTrueUsages = append(candidate.assignTrueUsages, trueNode)
		}

	case *ast.CallExpr:
		// Permit `len(m)` expressions.
		if fn, ok := n.Fun.(*ast.Ident); ok && fn.Name == "len" && len(n.Args) == 1 {
			arg, ok := n.Args[0].(*ast.Ident)
			if !ok {
				return true
			}
			c.allowedUsages[arg] = struct{}{}
		}

	case *ast.Ident:
		// If it's a buffer usage, untrack the candidate.
		// We stop tree traversal when we recognize the usage.
		obj := c.ctx.VarOf(n)
		if c.isBoolValuedMap(obj.Type()) {
			if _, ok := c.allowedUsages[n]; !ok {
				c.untrack(obj, "uncategorized usage")
			}
		}
	}

	return true
}

func (c *boolValuedMapChecker) isBoolValuedMap(typ types.Type) bool {
	m, ok := typ.(*types.Map)
	if !ok {
		return false
	}
	valueType, ok := m.Elem().(*types.Basic)
	return ok && valueType.Kind() == types.Bool
}

func (c *boolValuedMapChecker) track(id *ast.Ident, v *types.Var, boolValueNode ast.Expr) {
	c.candidates = append(c.candidates, boolValuedMapCandidate{
		obj:           v,
		boolValueNode: boolValueNode,
	})
}

func (c *boolValuedMapChecker) find(v *types.Var) *boolValuedMapCandidate {
	for i := range c.candidates {
		if c.candidates[i].obj == v {
			return &c.candidates[i]
		}
	}
	return nil
}

func (c *boolValuedMapChecker) untrack(v *types.Var, reason string) {
	index := -1
	for i := range c.candidates {
		if c.candidates[i].obj == v {
			index = i
			break
		}
	}
	if index == -1 {
		return
	}
	c.candidates[index] = c.candidates[len(c.candidates)-1]
	c.candidates = c.candidates[:len(c.candidates)-1]
}


<|start_filename|>cmd/perfguard/testdata/rulestest/rangeToAppend/rangeToAppend.go<|end_filename|>
package rulestest

func Warn() {
	dstBytes := make([]byte, 0, 10)
	var srcBytes []byte

	mapOfBytes := make(map[string][]byte, 100)

	for _, b := range srcBytes { // want `for … { … } => dstBytes = append(dstBytes, srcBytes...)`
		dstBytes = append(dstBytes, b)
	}

	for _, b := range srcBytes { // want `for … { … } => mapOfBytes["k"] = append(mapOfBytes["k"], srcBytes...)`
		mapOfBytes["k"] = append(mapOfBytes["k"], b)
	}

	for _, b := range srcBytes { // want `for … { … } => srcBytes = append(srcBytes, srcBytes...)`
		srcBytes = append(srcBytes, b)
	}

	{
		type object struct {
			bytes []byte
		}
		o := new(object)
		for _, b := range srcBytes { // want `for … { … } => o.bytes = append(o.bytes, srcBytes...)`
			o.bytes = append(o.bytes, b)
		}
	}
}

func Ignore() {
	var dstBytes []byte
	var srcBytes []byte

	mapOfBytes := make(map[string][]byte)

	dstBytes = append(dstBytes, srcBytes...)
	mapOfBytes["k"] = append(mapOfBytes["k"], srcBytes...)

	for _, b := range srcBytes {
		dstBytes = append(dstBytes, b)
		println(b)
	}

	for _, b := range srcBytes {
		println(b)
	}

	srcBytes = append(srcBytes, srcBytes...)

	{
		var arr [10]byte
		for _, b := range arr {
			dstBytes = append(dstBytes, b)
		}
	}
	{
		m := make(map[string]byte)
		for _, b := range m {
			dstBytes = append(dstBytes, b)
		}
	}

	{
		type object struct {
			bytes []byte
		}
		var objects []*object
		for i, b := range srcBytes {
			objects[i].bytes = append(objects[i].bytes, b)
		}
	}

	{
		type Book struct {
			authorID int
		}
		var books []Book
		m := make(map[int][]Book, 10)
		for _, book := range books {
			m[book.authorID] = append(m[book.authorID], book)
		}
	}

	{
		var ifaces []interface{}
		ints := make([]int, 0, len(ifaces))
		for _, x := range ints {
			ifaces = append(ifaces, x)
		}
		_ = ifaces
	}

	_ = srcBytes
	_ = dstBytes
}


<|start_filename|>_test/httpapp/server.go<|end_filename|>
package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"reflect"
	"regexp"
	"runtime/pprof"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"
	"unicode"
	"unicode/utf8"
)

var _ = utf8.MaxRune

func main() {
	h := newAppHandler()

	http.HandleFunc("/startProfiling", h.makeHandler("startProfiling", h.handleStartProfiling))
	http.HandleFunc("/finishProfiling", h.makeHandler("finishProfiling", h.handleFinishProfiling))
	http.HandleFunc("/stats", h.makeHandler("stats", h.handleStats))
	http.HandleFunc("/formatDate", h.makeHandler("formatDate", h.handleFormatDate))
	http.HandleFunc("/jsonInspect", h.makeHandler("jsonInspect", h.handleJsonInspect))
	http.HandleFunc("/validateIdent", h.makeHandler("validateIdent", h.handleValidateIdent))
	http.HandleFunc("/objectsSum", h.makeHandler("objectsSum", h.handleObjectsSum))

	log.Fatal(http.ListenAndServe(":8080", nil))
}

func newAppHandler() *appHandler {
	return &appHandler{
		numRequests: make(map[string]uint64),
	}
}

type appHandler struct {
	mu              sync.RWMutex
	numRequests     map[string]uint64
	runningProfiler bool
}

type requestStat struct {
	name  string
	count uint64
}

func (h *appHandler) requestStart(name string) {
	log.Printf("start %s request", name)
	h.mu.Lock()
	defer h.mu.Unlock()
	h.numRequests[name]++
}

func (h *appHandler) collectRequestStats() []requestStat {
	items := make([]requestStat, 0, 32)
	h.mu.RLock()
	defer h.mu.RUnlock()
	for k, v := range h.numRequests {
		items = append(items, requestStat{name: k, count: v})
	}
	return items
}

func (h *appHandler) makeHandler(name string, handle func(http.ResponseWriter, *http.Request) error) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		h.requestStart(name)
		err := handle(w, req)
		if err != nil {
			log.Printf("%s error: %v", name, err)
			http.Error(w, "error", http.StatusInternalServerError)
			return
		}
		log.Printf("finished %s request", name)
	}
}

func (h *appHandler) handleStartProfiling(w http.ResponseWriter, req *http.Request) error {
	h.mu.Lock()
	defer h.mu.Unlock()

	if h.runningProfiler {
		return errors.New("already running a profiler")
	}
	f, err := os.Create("cpu.out")
	if err != nil {
		return err
	}
	pprof.StartCPUProfile(f)
	h.runningProfiler = true

	w.Write([]byte("OK\n"))

	return nil
}

func (h *appHandler) handleFinishProfiling(w http.ResponseWriter, req *http.Request) error {
	h.mu.Lock()
	defer h.mu.Unlock()

	h.runningProfiler = false

	pprof.StopCPUProfile()

	w.Write([]byte("OK\n"))

	return nil
}

func (h *appHandler) handleStats(w http.ResponseWriter, req *http.Request) error {
	stats := h.collectRequestStats()
	sort.Slice(stats, func(i, j int) bool {
		return stats[i].name < stats[j].name
	})
	for _, kv := range stats {
		w.Write([]byte(fmt.Sprintf("%s: %d\n", kv.name, kv.count)))
	}
	return nil
}

func (h *appHandler) handleFormatDate(w http.ResponseWriter, req *http.Request) error {
	arg := req.URL.Query().Get("arg")
	isNumber, err := regexp.MatchString(`\d+`, arg)
	if err != nil {
		panic(err)
	}
	if isNumber {
		unixTime, err := strconv.ParseInt(arg, 10, 64)
		if err != nil {
			return err
		}
		t := time.Unix(unixTime, 0)
		s := strings.Join([]string{t.String(), "\n"}, "")
		_, err = io.WriteString(w, s)
		return err
	}
	return errors.New("missing or invalid ?arg parameter")
}

func (h *appHandler) handleJsonInspect(w http.ResponseWriter, req *http.Request) error {
	arg := req.URL.Query().Get("arg")
	var obj map[string]interface{}
	if err := json.Unmarshal([]byte(arg), &obj); err != nil {
		return err
	}
	for k, v := range obj {
		rv := reflect.ValueOf(v)
		fmt.Fprintf(w, "%s: %s\n", k, reflect.TypeOf(rv.Interface()).String())
	}
	if len(obj) == 0 {
		w.Write([]byte("empty object\n"))
	}
	return nil
}

func (h *appHandler) handleValidateIdent(w http.ResponseWriter, req *http.Request) error {
	arg := req.URL.Query().Get("arg")

	if len(arg) == 0 {
		return errors.New("empty arg")
	}
	firstChar := []rune(arg)[0]
	if !unicode.IsLetter(firstChar) {
		return errors.New("first char should be a letter")
	}
	for i, ch := range arg {
		if !unicode.IsLetter(ch) && !unicode.IsNumber(ch) {
			return fmt.Errorf("invalid char at offset %d", i)
		}
	}

	w.Write([]byte("OK\n"))

	return nil
}

type bigObject struct {
	str  string
	x    int
	y    int
	data [512]byte
}

var bigObjectList []bigObject

func init() {
	for i := 0; i < 3000; i++ {
		bigObjectList = append(bigObjectList, bigObject{
			str: fmt.Sprintf("[%d]", i),
			x:   i,
			y:   i * 2,
		})
	}
}

func (h *appHandler) handleObjectsSum(w http.ResponseWriter, req *http.Request) error {
	sum := 0
	for _, obj := range bigObjectList {
		sum += obj.x + obj.y
	}
	fmt.Fprintf(w, "sum=%d", sum)
	return nil
}


<|start_filename|>cmd/perfguard/testdata/rulestest/reflectType/reflectType.go<|end_filename|>
package rulestest

import (
	"fmt"
	"reflect"
)

func Warn(x interface{}) {
	_ = reflect.ValueOf(x).Type() // want `reflect.ValueOf(x).Type() => reflect.TypeOf(x)`
	_ = fmt.Sprintf("%T", x)      // want `fmt.Sprintf("%T", x) => reflect.TypeOf(x).String()`

	rv := reflect.ValueOf(x)
	_ = reflect.TypeOf(rv.Interface())    // want `reflect.TypeOf(rv.Interface()) => rv.Type()`
	_ = fmt.Sprintf("%T", rv.Interface()) // want `fmt.Sprintf("%T", rv.Interface()) => rv.Type().String()`

	_ = reflect.ValueOf(x).Type().Size() // want `reflect.ValueOf(x).Type() => reflect.TypeOf(x)`
}

func Ignore(x interface{}) {
	_ = reflect.TypeOf(x)
	_ = reflect.TypeOf(x).String()

	rv := reflect.ValueOf(x)
	_ = rv.Type()
	_ = rv.Type().String()

	_ = reflect.TypeOf(x).Size()
}


<|start_filename|>perfguard/checkers/funccheckers/prealloc.go<|end_filename|>
package funccheckers

import (
	"fmt"
	"go/ast"
	"go/token"
	"go/types"

	"github.com/go-toolsmith/astequal"
	"github.com/go-toolsmith/typep"
	"github.com/quasilyte/go-perfguard/internal/typeis"
	"github.com/quasilyte/go-perfguard/perfguard/checkers"
	"github.com/quasilyte/go-perfguard/perfguard/lint"
)

func init() {
	doc := checkers.Doc{
		Name:  "prealloc",
		Score: 4,
	}
	checkers.RegisterFuncChecker(doc, func() checkers.FuncChecker {
		return &preallocChecker{allowedUsages: make(map[*ast.Ident]struct{})}
	})
}

type preallocChecker struct {
	ctx *lint.Context

	allowedUsages map[*ast.Ident]struct{}

	nestedFunc bool

	depth int

	candidates []preallocCandidate
}

type preallocCandidate struct {
	obj *types.Var

	declNode ast.Node
	typeExpr ast.Expr

	depth int

	bound ast.Expr

	hotNode ast.Node
}

func (c *preallocChecker) CheckFunc(ctx *lint.Context, body *ast.BlockStmt) error {
	c.ctx = ctx
	c.candidates = c.candidates[:0]
	c.nestedFunc = false
	c.depth = 0
	for k := range c.allowedUsages {
		delete(c.allowedUsages, k)
	}

	ast.Inspect(body, c.walk)

	for _, candidate := range c.candidates {
		if candidate.bound == nil {
			continue
		}
		declValueSpec, _ := candidate.declNode.(*ast.ValueSpec)
		switch {
		case declValueSpec == nil && typeis.Map(candidate.obj.Type()):
			// Fixing from `x := make(map[K]V)` to `x := make(map[K]V, len(bound))`.
			makeCall := &ast.CallExpr{
				Fun: &ast.Ident{Name: "make"},
				Args: []ast.Expr{
					candidate.typeExpr,
					&ast.CallExpr{
						Fun:  &ast.Ident{Name: "len"},
						Args: []ast.Expr{candidate.bound},
					},
				},
			}
			ctx.SuggestNode(lint.SuggestParams{
				OldNode:  candidate.declNode,
				NewNode:  makeCall,
				HotNodes: []ast.Node{candidate.hotNode},
			})

		case declValueSpec == nil && typeis.Slice(candidate.obj.Type()):
			// Fixing from `x := []T{}` to `x := make([]T, 0, len(bound))`.
			makeCall := &ast.CallExpr{
				Fun: &ast.Ident{Name: "make"},
				Args: []ast.Expr{
					candidate.typeExpr,
					zeroLitNode,
					&ast.CallExpr{
						Fun:  &ast.Ident{Name: "len"},
						Args: []ast.Expr{candidate.bound},
					},
				},
			}
			ctx.SuggestNode(lint.SuggestParams{
				OldNode:  candidate.declNode,
				NewNode:  makeCall,
				HotNodes: []ast.Node{candidate.hotNode},
			})

		case declValueSpec != nil && typeis.Map(candidate.obj.Type()):
			// Fixing from `var x = make(map[K]V)` to `var x = make(map[K]V, len(bound))`.
			makeCall := &ast.CallExpr{
				Fun: &ast.Ident{Name: "make"},
				Args: []ast.Expr{
					candidate.typeExpr,
					&ast.CallExpr{
						Fun:  &ast.Ident{Name: "len"},
						Args: []ast.Expr{candidate.bound},
					},
				},
			}
			fixedValueSpec := &ast.ValueSpec{
				Type:   declValueSpec.Type,
				Names:  declValueSpec.Names,
				Values: []ast.Expr{makeCall},
			}
			ctx.SuggestNode(lint.SuggestParams{
				OldNode:  declValueSpec,
				NewNode:  fixedValueSpec,
				HotNodes: []ast.Node{candidate.hotNode},
			})

		case declValueSpec != nil && typeis.Slice(candidate.obj.Type()):
			if declValueSpec.Values == nil {
				// Report that `var x []T` can be changed to `var x = make([]T, 0, len(bound))`,
				// but don'e apply an auto fix here.
				expr := c.ctx.NodeText(candidate.bound)
				ctx.Report(lint.ReportParams{
					PosNode:  declValueSpec,
					Message:  fmt.Sprintf("can use len(%s) as make size hint for %s", expr, candidate.obj.Name()),
					HotNodes: []ast.Node{candidate.hotNode},
				})
			} else {
				makeCall := &ast.CallExpr{
					Fun: &ast.Ident{Name: "make"},
					Args: []ast.Expr{
						candidate.typeExpr,
						zeroLitNode,
						&ast.CallExpr{
							Fun:  &ast.Ident{Name: "len"},
							Args: []ast.Expr{candidate.bound},
						},
					},
				}
				fixedValueSpec := &ast.ValueSpec{
					Type:   declValueSpec.Type,
					Names:  declValueSpec.Names,
					Values: []ast.Expr{makeCall},
				}
				ctx.SuggestNode(lint.SuggestParams{
					OldNode:  declValueSpec,
					NewNode:  fixedValueSpec,
					HotNodes: []ast.Node{candidate.hotNode},
				})
			}
		}

	}

	return nil
}

func (c *preallocChecker) visitDecl(decl ast.Node, declTypeExpr ast.Expr, id *ast.Ident, init ast.Expr) bool {
	obj := c.ctx.VarOf(id)

	switch {
	case init == nil:
		if typeis.Slice(obj.Type()) {
			// Track `var $x []T`.
			candidate := c.track(obj)
			candidate.declNode = decl
			candidate.typeExpr = declTypeExpr
			return false
		}

	case init != nil:
		var typeExpr ast.Expr
		switch e := init.(type) {
		case *ast.CallExpr:
			called, ok := e.Fun.(*ast.Ident)
			if !ok || called.Name != "make" {
				return true
			}
			switch obj.Type().(type) {
			case *types.Slice:
				if len(e.Args) != 2 {
					return true
				}
				lengthArg, ok := e.Args[1].(*ast.BasicLit)
				if !ok || lengthArg.Kind != token.INT || lengthArg.Value != `0` {
					return true
				}
			case *types.Map:
				if len(e.Args) != 1 {
					return true
				}
			}
			typeExpr = e.Args[0]

		case *ast.CompositeLit:
			if len(e.Elts) != 0 {
				return true
			}
			typeExpr = e.Type
		}

		if typeExpr == nil {
			return true
		}

		switch obj.Type().(type) {
		case *types.Map, *types.Slice:
			candidate := c.track(obj)
			candidate.declNode = decl
			candidate.typeExpr = typeExpr
			return false
		}
	}

	return true
}

func (c *preallocChecker) walk(n ast.Node) bool {
	switch n := n.(type) {
	case *ast.FuncLit:
		nestedFunc := c.nestedFunc
		c.nestedFunc = true
		ast.Inspect(n.Body, c.walk)
		c.nestedFunc = nestedFunc
		return false

	case *ast.BlockStmt:
		c.depth++
		for _, stmt := range n.List {
			ast.Inspect(stmt, c.walk)
		}
		c.depth--
		return false

	case *ast.RangeStmt:
		// Even if we have candidates, maybe their depths are incompatible.
		// This code also handles the 0 candidates case.
		skip := true
		for i := range c.candidates {
			if c.candidates[i].depth == c.depth {
				skip = false // Have at least 1 potential candidate
				break
			}
		}
		if skip {
			return true
		}
		rangeKey, _ := n.Key.(*ast.Ident)
		var rangeExpr = n.X
		if !typep.SideEffectFree(c.ctx.Target.Types, rangeExpr) {
			return true
		}
		// TODO: handle more complicated for bodies?
		for _, stmt := range n.Body.List {
			if !c.isSimpleStmt(stmt) {
				return true
			}
		}
		for _, stmt := range n.Body.List {
			assign, ok := stmt.(*ast.AssignStmt)
			if !ok || assign.Tok != token.ASSIGN || len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {
				continue
			}
			// For slice candidates, we're interested in append calls.
			// For map candidates, we're interested in IndexExpr assignments.
			lhs := assign.Lhs[0]
			rhs := assign.Rhs[0]
			switch lhs := lhs.(type) {
			case *ast.IndexExpr:
				if rangeKey == nil || rangeKey.Name == "_" {
					continue
				}
				if !astequal.Expr(lhs.Index, rangeKey) {
					continue
				}
				indexed, ok := lhs.X.(*ast.Ident)
				if !ok {
					continue
				}
				obj := c.ctx.VarOf(indexed)
				if !typeis.Map(obj.Type()) {
					continue
				}
				candidate := c.find(obj)
				if candidate != nil && candidate.bound == nil {
					candidate.bound = rangeExpr
					candidate.hotNode = assign
					c.allowedUsages[indexed] = struct{}{}
				}

			case *ast.Ident:
				call, ok := rhs.(*ast.CallExpr)
				if !ok {
					continue
				}
				called, ok := call.Fun.(*ast.Ident)
				if !ok || called.Name != "append" {
					continue
				}
				if call.Ellipsis.IsValid() || len(call.Args) != 2 {
					continue
				}
				if !astequal.Expr(lhs, call.Args[0]) {
					continue
				}
				obj := c.ctx.VarOf(lhs)
				candidate := c.find(obj)
				if candidate != nil && candidate.bound == nil {
					candidate.bound = rangeExpr
					candidate.hotNode = assign
					c.allowedUsages[lhs] = struct{}{}
					c.allowedUsages[call.Args[0].(*ast.Ident)] = struct{}{}
				}
			}
		}

	case *ast.AssignStmt:
		if c.nestedFunc {
			return true
		}
		if len(n.Lhs) != 1 || len(n.Rhs) != 1 || n.Tok != token.DEFINE {
			return true
		}
		lhs, ok := n.Lhs[0].(*ast.Ident)
		if !ok {
			return true
		}
		return c.visitDecl(n.Rhs[0], nil, lhs, n.Rhs[0])

	case *ast.ValueSpec:
		if c.nestedFunc {
			return true
		}
		if len(n.Names) != 1 {
			return true
		}
		var init ast.Expr
		switch len(n.Values) {
		case 0:
			// init remains nil.
		case 1:
			init = n.Values[0]
		default:
			return true
		}
		return c.visitDecl(n, n.Type, n.Names[0], init)

	case *ast.Ident:
		if len(c.candidates) == 0 {
			return true
		}
		obj := c.ctx.VarOf(n)
		switch obj.Type().(type) {
		case *types.Slice, *types.Map:
			if _, ok := c.allowedUsages[n]; !ok {
				candidate, index := c.findWithIndex(obj)
				if candidate != nil && candidate.bound == nil {
					c.untrackByIndex(index, "uncategorized usage")
				}
			}
		}

	}

	return true
}

func (c *preallocChecker) isSimpleStmt(n ast.Stmt) bool {
	switch n := n.(type) {
	case *ast.AssignStmt, *ast.ExprStmt, *ast.DeclStmt, *ast.IncDecStmt:
		return true

	case *ast.IfStmt:
		if len(n.Body.List) > 4 {
			return false
		}
		for _, stmt := range n.Body.List {
			if !c.isSimpleStmt(stmt) {
				return false
			}
		}
		return true

	default:
		return false
	}
}

func (c *preallocChecker) track(v *types.Var) *preallocCandidate {
	c.candidates = append(c.candidates, preallocCandidate{
		obj:   v,
		depth: c.depth,
	})
	return &c.candidates[len(c.candidates)-1]
}

func (c *preallocChecker) findWithIndex(v *types.Var) (candidate *preallocCandidate, index int) {
	for i := range c.candidates {
		if c.candidates[i].obj == v {
			return &c.candidates[i], i
		}
	}
	return nil, -1
}

func (c *preallocChecker) find(v *types.Var) *preallocCandidate {
	candidate, _ := c.findWithIndex(v)
	return candidate
}

func (c *preallocChecker) untrackByIndex(index int, reason string) {
	c.candidates[index] = c.candidates[len(c.candidates)-1]
	c.candidates = c.candidates[:len(c.candidates)-1]
}


<|start_filename|>perfguard/create_checkers.go<|end_filename|>
package perfguard

import (
	"github.com/quasilyte/go-perfguard/perfguard/checkers"
	"github.com/quasilyte/go-perfguard/perfguard/lint"

	_ "github.com/quasilyte/go-perfguard/perfguard/checkers/callcheckers" // for init()
	_ "github.com/quasilyte/go-perfguard/perfguard/checkers/funccheckers" // for init()
)

type targetChecker struct {
	ctx  lint.SharedContext
	impl checkers.PackageChecker
}

func (c *targetChecker) CheckTarget(target *lint.Target) error {
	c.ctx.Reset(target)
	return c.impl.CheckPackage(&c.ctx, target.Files)
}

func createCheckers(config *Config) []*targetChecker {
	packageCheckers := checkers.Create(func(doc checkers.Doc) bool {
		if doc.NeedsProfile && config.Heatmap == nil {
			return false
		}
		return true
	})

	targetCheckers := make([]*targetChecker, len(packageCheckers))
	for i := range packageCheckers {
		c := &targetChecker{
			impl: packageCheckers[i],
		}
		c.ctx.Heatmap = config.Heatmap
		c.ctx.Warn = config.Warn
		targetCheckers[i] = c
	}

	return targetCheckers
}


<|start_filename|>_script/check.go<|end_filename|>
package main

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/google/go-cmp/cmp"
)

func main() {
	var c checker

	steps := []struct {
		name string
		fn   func() error
	}{
		{"check autogen", c.checkAutogen},
	}
	for _, step := range steps {
		if err := step.fn(); err != nil {
			log.Fatalf("%s: error: %v", step.name, err)
		}
	}
}

type checker struct{}

func (c *checker) checkAutogen() error {
	filenames := []string{
		"opt_rules.go",
		"universal_rules.go",
	}
	readContents := func() ([][]byte, error) {
		var result [][]byte
		for _, filename := range filenames {
			data, err := os.ReadFile(filepath.Join("perfguard", "rulesdata", filename))
			if err != nil {
				return nil, err
			}
			result = append(result, data)
		}
		return result, nil
	}

	contents, err := readContents()
	if err != nil {
		return err
	}

	out, err := exec.Command("go", "generate", "./perfguard").CombinedOutput()
	if err != nil {
		return fmt.Errorf("run go generate: %v: %s", err, out)
	}

	newContents, err := readContents()
	if err != nil {
		return err
	}

	for i, filename := range filenames {
		oldData := contents[i]
		newData := newContents[i]
		if !bytes.Equal(oldData, newData) {
			diff := cmp.Diff(
				strings.Split(string(oldData), "\n"),
				strings.Split(string(newData), "\n"),
			)
			log.Printf("diff (-old +new):\n%s", diff)
			return fmt.Errorf("rulesdata for %s is outdated, run 'go generate ./perfguard'", filename)
		}
	}

	return nil
}


<|start_filename|>perfguard/checkers/funccheckers/utils.go<|end_filename|>
package funccheckers

import (
	"go/ast"
	"go/token"
)

var zeroLitNode = &ast.BasicLit{
	Kind:  token.INT,
	Value: `0`,
}


<|start_filename|>cmd/perfguard/testdata/rulestest/redundantSprint/redundantSprint.go<|end_filename|>
package rulestest

import "fmt"

func Warn() {
	{
		var foo withStringer
		_ = fmt.Sprint(foo)        // want `fmt.Sprint(foo) => foo.String()`
		_ = fmt.Sprintf("%s", foo) // want `fmt.Sprintf("%s", foo) => foo.String()`
		_ = fmt.Sprintf("%v", foo) // want `fmt.Sprintf("%v", foo) => foo.String()`
	}

	{
		var err error
		_ = fmt.Sprint(err)        // want `fmt.Sprint(err) => err.Error()`
		_ = fmt.Sprintf("%s", err) // want `fmt.Sprintf("%s", err) => err.Error()`
		_ = fmt.Sprintf("%v", err) // want `fmt.Sprintf("%v", err) => err.Error()`
	}

	{
		var s string
		_ = fmt.Sprint(s)        // want `fmt.Sprint(s) => s`
		_ = fmt.Sprintf("%s", s) // want `fmt.Sprintf("%s", s) => s`
		_ = fmt.Sprintf("%v", s) // want `fmt.Sprintf("%v", s) => s`

		_ = fmt.Sprint("x")        // want `fmt.Sprint("x") => "x"`
		_ = fmt.Sprintf("%s", "x") // want `fmt.Sprintf("%s", "x") => "x"`
		_ = fmt.Sprintf("%v", "x") // want `fmt.Sprintf("%v", "x") => "x"`
	}

	{
		var b []byte
		_ = fmt.Sprint(b)        // want `fmt.Sprint(b) => string(b)`
		_ = fmt.Sprintf("%s", b) // want `fmt.Sprintf("%s", b) => string(b)`
		_ = fmt.Sprintf("%v", b) // want `fmt.Sprintf("%v", b) => string(b)`
	}
	{
		type myBytes []byte
		var b myBytes
		_ = fmt.Sprint(b)        // want `fmt.Sprint(b) => string(b)`
		_ = fmt.Sprintf("%s", b) // want `fmt.Sprintf("%s", b) => string(b)`
		_ = fmt.Sprintf("%v", b) // want `fmt.Sprintf("%v", b) => string(b)`
	}
	{
		type myString string
		var s myString
		_ = fmt.Sprint(s)        // want `fmt.Sprint(s) => string(s)`
		_ = fmt.Sprintf("%s", s) // want `fmt.Sprintf("%s", s) => string(s)`
		_ = fmt.Sprintf("%v", s) // want `fmt.Sprintf("%v", s) => string(s)`
	}
}

func Ignore() {
	{
		var foo withStringer
		_ = foo.String()
	}

	{
		var err error
		_ = err.Error()
	}

	{
		var s string
		_ = s
		_ = "x"
	}

	{
		var b []byte
		_ = string(b)
	}
	{
		var runes []rune
		_ = string(runes)
	}
	{
		type myBytes []byte
		var b myBytes
		_ = string(b)
	}
	{
		type myString string
		var s myString
		_ = string(s)
	}
	{
		var runes []rune
		_ = fmt.Sprint(runes)
	}
	{
		var runes []int32
		_ = fmt.Sprint(runes)
	}
	{
		var runes []rune
		_ = fmt.Sprint(runes)
		_ = fmt.Sprintf("%s", runes)
		_ = fmt.Sprintf("%v", runes)
	}
}

type withStringer struct{}

func (withStringer) String() string { return "" }


<|start_filename|>perfguard/rulesdata/opt_rules.go<|end_filename|>
// Code generated by "precompile.go". DO NOT EDIT.

package rulesdata

import "github.com/quasilyte/go-ruleguard/ruleguard/ir"

var Opt = &ir.File{
	PkgPath:       "gorules",
	CustomDecls:   []string{},
	BundleImports: []ir.BundleImport{},
	RuleGroups: []ir.RuleGroup{
		{
			Line:        9,
			Name:        "stringConcatAssign",
			MatcherName: "m",
			DocTags:     []string{"o2", "score5"},
			DocSummary:  "Detects string concat in hot paths",
			Rules: []ir.Rule{{
				Line:           10,
				SyntaxPatterns: []ir.PatternString{{Line: 10, Value: "$s += $_"}},
				ReportTemplate: "string concat on the hot path",
				WhereExpr: ir.FilterExpr{
					Line:  11,
					Op:    ir.FilterVarTypeIsOp,
					Src:   "m[\"s\"].Type.Is(`string`)",
					Value: "s",
					Args:  []ir.FilterExpr{{Line: 11, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}},
				},
			}},
		},
		{
			Line:        17,
			Name:        "regexpCompile",
			MatcherName: "m",
			DocTags:     []string{"o1", "score4"},
			DocSummary:  "Detects regexp compilation on hot execution paths",
			Rules: []ir.Rule{{
				Line: 22,
				SyntaxPatterns: []ir.PatternString{
					{Line: 24, Value: "regexp.Compile($*_)"},
					{Line: 25, Value: "regexp.MustCompile($*_)"},
					{Line: 26, Value: "regexp.CompilePOSIX($*_)"},
					{Line: 27, Value: "regexp.MustCompilePOSIX($*_)"},
					{Line: 29, Value: "regexp.Match($*_)"},
					{Line: 30, Value: "regexp.MatchString($*_)"},
					{Line: 31, Value: "regexp.MatchReader($*_)"},
				},
				ReportTemplate: "regexp compilation should be avoided on the hot paths",
			}},
		},
		{
			Line:        37,
			Name:        "sprintfConcat2",
			MatcherName: "m",
			DocTags:     []string{"o2", "score2"},
			DocSummary:  "Detects sprint calls that can be rewritten as a string concat",
			Rules: []ir.Rule{
				{
					Line:            42,
					SyntaxPatterns:  []ir.PatternString{{Line: 42, Value: "fmt.Sprintf(\"%s=%s\", $x, $y)"}},
					ReportTemplate:  "$$ => $x + \"=\" + $y",
					SuggestTemplate: "$x + \"=\" + $y",
					WhereExpr: ir.FilterExpr{
						Line: 43,
						Op:   ir.FilterAndOp,
						Src:  "m[\"x\"].Type.Is(`string`) && m[\"y\"].Type.Is(`string`)",
						Args: []ir.FilterExpr{
							{
								Line:  43,
								Op:    ir.FilterVarTypeIsOp,
								Src:   "m[\"x\"].Type.Is(`string`)",
								Value: "x",
								Args:  []ir.FilterExpr{{Line: 43, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}},
							},
							{
								Line:  43,
								Op:    ir.FilterVarTypeIsOp,
								Src:   "m[\"y\"].Type.Is(`string`)",
								Value: "y",
								Args:  []ir.FilterExpr{{Line: 43, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}},
							},
						},
					},
				},
				{
					Line:            46,
					SyntaxPatterns:  []ir.PatternString{{Line: 46, Value: "fmt.Sprintf(\"%s.%s\", $x, $y)"}},
					ReportTemplate:  "$$ => $x + \".\" + $y",
					SuggestTemplate: "$x + \".\" + $y",
					WhereExpr: ir.FilterExpr{
						Line: 47,
						Op:   ir.FilterAndOp,
						Src:  "m[\"x\"].Type.Is(`string`) && m[\"y\"].Type.Is(`string`)",
						Args: []ir.FilterExpr{
							{
								Line:  47,
								Op:    ir.FilterVarTypeIsOp,
								Src:   "m[\"x\"].Type.Is(`string`)",
								Value: "x",
								Args:  []ir.FilterExpr{{Line: 47, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}},
							},
							{
								Line:  47,
								Op:    ir.FilterVarTypeIsOp,
								Src:   "m[\"y\"].Type.Is(`string`)",
								Value: "y",
								Args:  []ir.FilterExpr{{Line: 47, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}},
							},
						},
					},
				},
				{
					Line:            50,
					SyntaxPatterns:  []ir.PatternString{{Line: 50, Value: "fmt.Sprintf(\"%s/%s\", $x, $y)"}},
					ReportTemplate:  "$$ => $x + \"/\" + $y",
					SuggestTemplate: "$x + \"/\" + $y",
					WhereExpr: ir.FilterExpr{
						Line: 51,
						Op:   ir.FilterAndOp,
						Src:  "m[\"x\"].Type.Is(`string`) && m[\"y\"].Type.Is(`string`)",
						Args: []ir.FilterExpr{
							{
								Line:  51,
								Op:    ir.FilterVarTypeIsOp,
								Src:   "m[\"x\"].Type.Is(`string`)",
								Value: "x",
								Args:  []ir.FilterExpr{{Line: 51, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}},
							},
							{
								Line:  51,
								Op:    ir.FilterVarTypeIsOp,
								Src:   "m[\"y\"].Type.Is(`string`)",
								Value: "y",
								Args:  []ir.FilterExpr{{Line: 51, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}},
							},
						},
					},
				},
				{
					Line:            54,
					SyntaxPatterns:  []ir.PatternString{{Line: 54, Value: "fmt.Sprintf(\"%s:%s\", $x, $y)"}},
					ReportTemplate:  "$$ => $x + \":\" + $y",
					SuggestTemplate: "$x + \":\" + $y",
					WhereExpr: ir.FilterExpr{
						Line: 55,
						Op:   ir.FilterAndOp,
						Src:  "m[\"x\"].Type.Is(`string`) && m[\"y\"].Type.Is(`string`)",
						Args: []ir.FilterExpr{
							{
								Line:  55,
								Op:    ir.FilterVarTypeIsOp,
								Src:   "m[\"x\"].Type.Is(`string`)",
								Value: "x",
								Args:  []ir.FilterExpr{{Line: 55, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}},
							},
							{
								Line:  55,
								Op:    ir.FilterVarTypeIsOp,
								Src:   "m[\"y\"].Type.Is(`string`)",
								Value: "y",
								Args:  []ir.FilterExpr{{Line: 55, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}},
							},
						},
					},
				},
				{
					Line:            58,
					SyntaxPatterns:  []ir.PatternString{{Line: 58, Value: "fmt.Sprintf(\"%s: %s\", $x, $y)"}},
					ReportTemplate:  "$$ => $x + \": \" + $y",
					SuggestTemplate: "$x + \": \" + $y",
					WhereExpr: ir.FilterExpr{
						Line: 59,
						Op:   ir.FilterAndOp,
						Src:  "m[\"x\"].Type.Is(`string`) && m[\"y\"].Type.Is(`string`)",
						Args: []ir.FilterExpr{
							{
								Line:  59,
								Op:    ir.FilterVarTypeIsOp,
								Src:   "m[\"x\"].Type.Is(`string`)",
								Value: "x",
								Args:  []ir.FilterExpr{{Line: 59, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}},
							},
							{
								Line:  59,
								Op:    ir.FilterVarTypeIsOp,
								Src:   "m[\"y\"].Type.Is(`string`)",
								Value: "y",
								Args:  []ir.FilterExpr{{Line: 59, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}},
							},
						},
					},
				},
			},
		},
		{
			Line:        67,
			Name:        "writeString2",
			MatcherName: "m",
			DocTags:     []string{"o2", "score3"},
			DocSummary:  "Detects Write calls that should be rewritten as io.WriteString",
			DocBefore:   "w.Write([]byte(s))",
			DocAfter:    "io.WriteString(w, s)",
			Rules: []ir.Rule{{
				Line:            68,
				SyntaxPatterns:  []ir.PatternString{{Line: 68, Value: "$w.Write([]byte($s))"}},
				ReportTemplate:  "$$ => io.WriteString($w, $s)",
				SuggestTemplate: "io.WriteString($w, $s)",
				WhereExpr: ir.FilterExpr{
					Line: 69,
					Op:   ir.FilterAndOp,
					Src:  "m[\"w\"].Type.Is(\"io.Writer\") && m[\"s\"].Type.Is(`string`) && m[\"s\"].Const",
					Args: []ir.FilterExpr{
						{
							Line: 69,
							Op:   ir.FilterAndOp,
							Src:  "m[\"w\"].Type.Is(\"io.Writer\") && m[\"s\"].Type.Is(`string`)",
							Args: []ir.FilterExpr{
								{
									Line:  69,
									Op:    ir.FilterVarTypeIsOp,
									Src:   "m[\"w\"].Type.Is(\"io.Writer\")",
									Value: "w",
									Args:  []ir.FilterExpr{{Line: 69, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}},
								},
								{
									Line:  69,
									Op:    ir.FilterVarTypeIsOp,
									Src:   "m[\"s\"].Type.Is(`string`)",
									Value: "s",
									Args:  []ir.FilterExpr{{Line: 69, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}},
								},
							},
						},
						{
							Line:  69,
							Op:    ir.FilterVarConstOp,
							Src:   "m[\"s\"].Const",
							Value: "s",
						},
					},
				},
			}},
		},
		{
			Line:        75,
			Name:        "rangeValueCopy",
			MatcherName: "m",
			DocTags:     []string{"o1", "score2"},
			DocSummary:  "Detects range loops that copy large value on every iteration",
			Rules: []ir.Rule{{
				Line: 77,
				SyntaxPatterns: []ir.PatternString{
					{Line: 77, Value: "for $_, $v := range $_"},
					{Line: 77, Value: "for $_, $v = range $_"},
				},
				ReportTemplate: "every iteration copies a large object into $v",
				WhereExpr: ir.FilterExpr{
					Line: 78,
					Op:   ir.FilterGtOp,
					Src:  "m[\"v\"].Type.Size > 128",
					Args: []ir.FilterExpr{
						{
							Line:  78,
							Op:    ir.FilterVarTypeSizeOp,
							Src:   "m[\"v\"].Type.Size",
							Value: "v",
						},
						{
							Line:  78,
							Op:    ir.FilterIntOp,
							Src:   "128",
							Value: int64(128),
						},
					},
				},
			}},
		},
		{
			Line:        84,
			Name:        "constErrorNew",
			MatcherName: "m",
			DocTags:     []string{"o1", "score3"},
			DocSummary:  "Detects errors.New that can be allocated exactly once",
			Rules: []ir.Rule{{
				Line:           85,
				SyntaxPatterns: []ir.PatternString{{Line: 85, Value: "errors.New($x)"}},
				ReportTemplate: "errors with const message can be a global var, allocated only once",
				WhereExpr: ir.FilterExpr{
					Line:  86,
					Op:    ir.FilterVarConstOp,
					Src:   "m[\"x\"].Const",
					Value: "x",
				},
			}},
		},
	},
}



<|start_filename|>perfguard/analyzer.go<|end_filename|>
package perfguard

import (
	"fmt"
	"go/token"
	"path/filepath"
	"strings"
	"time"

	"github.com/quasilyte/go-ruleguard/ruleguard"
	"github.com/quasilyte/go-ruleguard/ruleguard/ir"
	"github.com/quasilyte/perf-heatmap/heatmap"

	"github.com/quasilyte/go-perfguard/internal/resolve"
	"github.com/quasilyte/go-perfguard/perfguard/lint"
	"github.com/quasilyte/go-perfguard/perfguard/rulesdata"
)

//go:generate go run ./_rules/precompile/precompile.go -varname Universal -rules ./_rules/universal_rules.go -o ./rulesdata/universal_rules.go
//go:generate go run ./_rules/precompile/precompile.go -varname Opt -rules ./_rules/opt_rules.go -o ./rulesdata/opt_rules.go

type analyzer struct {
	rulesEngine *ruleguard.Engine

	checkers []*targetChecker

	goVersion ruleguard.GoVersion
	config    *Config
}

func newAnalyzer() *analyzer {
	return &analyzer{}
}

func (a *analyzer) Init(config *Config) error {
	a.config = config
	a.checkers = createCheckers(config)
	return a.initRulesEngine()
}

func (a *analyzer) initRulesEngine() error {
	goVersion, err := ruleguard.ParseGoVersion(a.config.GoVersion)
	if err != nil {
		return fmt.Errorf("parse target Go version: %w", err)
	}
	a.goVersion = goVersion

	rulesEngine := ruleguard.NewEngine()

	fset := token.NewFileSet()
	loadContext := ruleguard.LoadContext{
		Fset: fset,
	}

	toLoad := []struct {
		filename string
		ir       *ir.File
		enabled  bool
	}{
		{"universal_rules.go", rulesdata.Universal, a.config.LoadUniversalRules},
		{"opt_rules.go", rulesdata.Opt, a.config.LoadOptRules},
		// {"lint_rules.go", rulesdata.Lint, a.config.LoadLintRules},
	}
	for _, x := range toLoad {
		if !x.enabled {
			continue
		}
		if err := rulesEngine.LoadFromIR(&loadContext, x.filename, x.ir); err != nil {
			return err
		}
	}

	a.rulesEngine = rulesEngine
	return nil
}

func (a *analyzer) CheckPackage(target *lint.Target) error {
	if err := a.runRules(target); err != nil {
		return err
	}
	for _, c := range a.checkers {
		if err := c.CheckTarget(target); err != nil {
			return err
		}
	}
	return nil
}

func (a *analyzer) hasReformatTag(info *ruleguard.GoRuleInfo) bool {
	for _, tag := range info.Group.DocTags {
		if tag == "reformat" {
			return true
		}
	}
	return false
}

func (a *analyzer) minHeatLevel(info *ruleguard.GoRuleInfo) int {
	for _, tag := range info.Group.DocTags {
		switch tag {
		case "o1":
			return 1
		case "o2":
			return 5
		}
	}
	return 0
}

func (a *analyzer) runRules(target *lint.Target) error {
	ruleguardContext := ruleguard.RunContext{
		Pkg:         target.Pkg,
		Types:       target.Types,
		Sizes:       target.Sizes,
		Fset:        target.Fset,
		GoVersion:   a.goVersion,
		TruncateLen: 100,
	}

	var currentFile *lint.SourceFile

	ruleguardContext.Report = func(data *ruleguard.ReportData) {
		startPos := target.Fset.Position(data.Node.Pos())

		samplesTime := time.Duration(0)
		if a.config.Heatmap != nil {
			minLevel := a.minHeatLevel(&data.RuleInfo)
			if minLevel != 0 {
				endPos := target.Fset.Position(data.Node.End())
				lineFrom := startPos.Line
				lineTo := endPos.Line
				isHot := false
				typeName, funcName := resolve.SplitFuncName(data.Func)
				key := heatmap.Key{
					TypeName: typeName,
					FuncName: funcName,
					Filename: filepath.Base(startPos.Filename),
					PkgName:  target.Pkg.Name(),
				}
				totalValue := int64(0)
				a.config.Heatmap.QueryLineRange(key, lineFrom, lineTo, func(l heatmap.LineStats) bool {
					if l.GlobalHeatLevel >= minLevel {
						isHot = true
					}
					totalValue += l.Value
					return true
				})
				if !isHot {
					return
				}
				samplesTime = time.Duration(totalValue)
			}
		}

		var fixes []lint.TextEdit
		if data.Suggestion != nil {
			s := data.Suggestion
			textEdit := lint.TextEdit{
				From:        s.From,
				To:          s.To,
				Replacement: make([]byte, len(s.Replacement)),
				Reformat:    a.hasReformatTag(&data.RuleInfo),
			}
			copy(textEdit.Replacement, s.Replacement)
			fixes = []lint.TextEdit{textEdit}
		}

		message := strings.ReplaceAll(data.Message, "\n", `\n`)
		a.config.Warn(lint.Warning{
			Filename:    startPos.Filename,
			Line:        startPos.Line,
			Tag:         data.RuleInfo.Group.Name,
			Text:        message,
			Fixes:       fixes,
			SamplesTime: samplesTime,
		})
	}

	for i := range target.Files {
		currentFile = &target.Files[i]
		if err := a.rulesEngine.Run(&ruleguardContext, currentFile.Syntax); err != nil {
			return err
		}
	}

	return nil
}


<|start_filename|>_test/httpapp/wrk/main.go<|end_filename|>
package main

import (
	"fmt"
	"io"
	"math/rand"
	"net/http"
	"net/url"
	"sync"
	"time"
)

func main() {
	numClients := 10
	requestsPerClient := 75000

	var wg sync.WaitGroup
	wg.Add(numClients)

	methodsFreq := map[string]int{
		"stats":              1,
		"jsonInspect":        4,
		"validateIdentError": 4,
		"objectsSum":         5,
		"validateIdent":      10,
		"formatDate":         15,
	}

	var methods []string
	for name, n := range methodsFreq {
		for i := 0; i < n; i++ {
			methods = append(methods, name)
		}
	}
	rand.Seed(time.Now().Unix())
	rand.Shuffle(len(methods), func(i, j int) {
		methods[i], methods[j] = methods[j], methods[i]
	})

	start := time.Now()
	getRequest("http://localhost:8080/startProfiling")
	for i := 0; i < numClients; i++ {
		go func() {
			for i := 0; i < requestsPerClient; i++ {
				method := methods[rand.Intn(len(methods))]
				switch method {
				case "stats":
					doStats()
				case "formatDate":
					doFormatDate()
				case "jsonInspect":
					doJsonInspect()
				case "validateIdent":
					doValidateIdent(true)
				case "validateIdentError":
					doValidateIdent(false)
				case "objectsSum":
					doObjectsSum()
				}
				delayRand := rand.Intn(150)
				if delayRand >= 140 {
					delayRand = 300
				}
				delay := delayRand + 50
				time.Sleep(time.Microsecond * time.Duration(delay))
			}
			wg.Done()
		}()
	}
	wg.Wait()
	getRequest("http://localhost:8080/finishProfiling")
	end := time.Since(start)

	fmt.Printf("made %d requests\n", numClients*requestsPerClient)
	fmt.Printf("elapsed: %.2fs\n", end.Seconds())
	fmt.Printf("requests per second: %d\n", int(float64(numClients*requestsPerClient)/end.Seconds()))
}

func getRequest(urlFormat string, args ...interface{}) {
	resp, err := http.Get(fmt.Sprintf(urlFormat, args...))
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	io.Copy(io.Discard, resp.Body)
}

func doStats() {
	getRequest("http://localhost:8080/stats")
}

func doFormatDate() {
	now := time.Now()
	getRequest("http://localhost:8080/formatDate?arg=%d", now.Unix())
}

func doJsonInspect() {
	data := url.QueryEscape(`{"f": 1, "str": "v", "arr": [1]}`)
	getRequest("http://localhost:8080/jsonInspect?arg=%s", data)
}

func doValidateIdent(valid bool) {
	if valid {
		getRequest("http://localhost:8080/validateIdent?arg=RelativelyLongIdent183ValueButNotTooLong")
	} else {
		getRequest("http://localhost:8080/validateIdent?arg=This_Is_InvalidThis_Is_InvalidThis_Is_InvalidThis_Is_InvalidThis_Is_InvalidThis_Is_Invalid")
	}
}

func doObjectsSum() {
	getRequest("http://localhost:8080/objectsSum")
}


<|start_filename|>perfguard/perfguard.go<|end_filename|>
package perfguard

import (
	"github.com/quasilyte/go-perfguard/perfguard/lint"
	"github.com/quasilyte/go-ruleguard/dsl"
	"github.com/quasilyte/perf-heatmap/heatmap"
)

// This is a temporary kludge to make DSL a direct dependency.
var _ = dsl.Matcher{}

type Analyzer struct {
	impl *analyzer
}

func NewAnalyzer() *Analyzer {
	return &Analyzer{
		impl: newAnalyzer(),
	}
}

type Config struct {
	Heatmap *heatmap.Index

	GoVersion string

	LoadOptRules       bool
	LoadLintRules      bool
	LoadUniversalRules bool

	Warn func(lint.Warning)
}

func (a *Analyzer) Init(config *Config) error {
	return a.impl.Init(config)
}

func (a *Analyzer) CheckPackage(target *lint.Target) error {
	return a.impl.CheckPackage(target)
}


<|start_filename|>cmd/perfguard/testdata/rulestest/sliceClear/sliceClear.go<|end_filename|>
package rulestest

func Warn() {
	const Zero = 0

	{
		var xs []int
		for i := 0; i < len(xs); i++ { // want `for ... { ... } => for i := range xs { xs[i] = 0 }`
			xs[i] = 0
		}
	}

	{
		var xs []byte
		for i := 0; i < len(xs); i++ { // want `for ... { ... } => for i := range xs { xs[i] = Zero }`
			xs[i] = Zero
		}
	}
}

func Ignore() {
	{
		var xs []int
		for i := range xs {
			xs[i] = 0
		}
	}

	{
		var xs []int
		var ys []int
		for i := 0; i < len(xs); i++ {
			ys[i] = 0
		}
	}
}


<|start_filename|>cmd/perfguard/common_flags.go<|end_filename|>
package main

import (
	"flag"
)

func addCommonFlags(r *runner, fs *flag.FlagSet) {
	fs.BoolVar(&r.autofix, "fix", false,
		`apply the suggested fixes automatically, where possible`)
	fs.StringVar(&r.goVersion, "go", "",
		`select the Go version to target; leave as empty string for the latest`)
	fs.BoolVar(&r.absFilenames, "abs", false,
		`print absolute filenames in the output`)
	fs.BoolVar(&r.args.quiet, "quiet", false,
		`do not print extra results information and stats`)
	fs.BoolVar(&r.args.autogen, "autogen", false,
		`whether to analyze autogenerated files`)
}


<|start_filename|>internal/imports/fixer.go<|end_filename|>
package imports

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"path"
	"sort"
	"strconv"
)

type fixer struct {
	config FixConfig
}

func newFixer(config FixConfig) *fixer {
	return &fixer{
		config: config,
	}
}

func (f *fixer) Fix(src []byte) ([]byte, error) {
	fset := token.NewFileSet()
	file, err := parser.ParseFile(fset, "", src, parser.ParseComments)
	if err != nil {
		return nil, err
	}

	getLine := func(pos token.Pos) int {
		return fset.Position(pos).Line
	}
	hasImportC := func(decl *ast.GenDecl) bool {
		for _, spec := range decl.Specs {
			spec := spec.(*ast.ImportSpec)
			importedPath, _ := strconv.Unquote(spec.Path.Value)
			if importedPath == "C" {
				return true
			}
		}
		return false
	}

	commentByLine := make(map[int]*ast.CommentGroup, len(file.Comments))
	for _, c := range file.Comments {
		commentByLine[getLine(c.Pos())] = c
	}

	var importDecls []*ast.GenDecl
	imports := make(map[string]string, 8)
	usages := make(map[string]struct{}, 8)
	inlineComments := make(map[*ast.ImportSpec]*ast.CommentGroup)
	var walkError error
	ast.Inspect(file, func(n ast.Node) bool {
		if n == nil {
			return false
		}
		switch n := n.(type) {
		case *ast.GenDecl:
			if n.Tok != token.IMPORT {
				return true
			}
			importDecls = append(importDecls, n)

		case *ast.ImportSpec:
			importedPath, err := strconv.Unquote(n.Path.Value)
			if err != nil {
				walkError = fmt.Errorf("unquote %q: %w", n.Path.Value, err)
			}
			var localName string
			if n.Name != nil {
				localName = n.Name.Name
			} else {
				localName = path.Base(importedPath)
			}
			imports[localName] = importedPath
			comment := commentByLine[getLine(n.Pos())]
			if comment != nil {
				inlineComments[n] = comment
			}

		case *ast.SelectorExpr:
			xident, ok := n.X.(*ast.Ident)
			if !ok {
				return true
			}
			if xident.Obj != nil {
				// If the parser can resolve it, it's not a package ref.
				return true
			}
			pkgName := xident.Name
			usages[pkgName] = struct{}{}
		}
		return true
	})
	if walkError != nil {
		return nil, walkError
	}

	var missingImports []string
	for pkgName := range usages {
		pkgPath := f.config.Packages[pkgName]
		if pkgPath == "" {
			pkgPath = f.config.StdlibPackages[pkgName]
		}
		if pkgPath == "" {
			continue
		}
		if _, ok := imports[pkgPath]; ok {
			continue
		}
		missingImports = append(missingImports, pkgPath)
	}
	sort.Strings(missingImports)

	unusedImports := make(map[string]struct{})
	for pkgName, pkgPath := range imports {
		if pkgName == "_" || pkgName == "." {
			continue
		}
		if pkgPath == "C" {
			continue
		}
		_, isUsed := usages[pkgName]
		if !isUsed {
			unusedImports[pkgPath] = struct{}{}
		}
	}

	if len(unusedImports) == 0 && len(missingImports) == 0 {
		return src, nil
	}

	var buf bytes.Buffer
	continueFrom := 0
	if len(imports) != 0 {
		startFrom := 0
		addedImports := false
		for i, decl := range importDecls {
			numUsedImports := 0
			for _, spec := range decl.Specs {
				spec := spec.(*ast.ImportSpec)
				importedPath, _ := strconv.Unquote(spec.Path.Value)
				if _, ok := unusedImports[importedPath]; !ok {
					numUsedImports++
				}
			}
			insertImports := i == 0 && len(missingImports) != 0
			isEmpty := !insertImports && numUsedImports == 0
			continueFrom = fset.Position(decl.End()).Offset
			if isEmpty {
				buf.Write(src[startFrom:fset.Position(decl.Pos()).Offset])
				if bytes.HasPrefix(src[continueFrom:], []byte("\n")) {
					continueFrom++
				} else if bytes.HasPrefix(src[continueFrom:], []byte("\r\n")) {
					continueFrom += 2
				}
				startFrom = continueFrom
				continue
			}

			buf.Write(src[startFrom : fset.Position(decl.Pos()).Offset+len("import")])
			importedC := hasImportC(decl)
			addParens := (!addedImports && !importedC) || decl.Lparen.IsValid()
			if addParens {
				buf.WriteString(" (\n")
			} else {
				buf.WriteByte(' ')
			}
			prevLine := 0
			if len(decl.Specs) != 0 {
				prevLine = getLine(decl.Specs[0].Pos()) - 1
			}
			for _, spec := range decl.Specs {
				spec := spec.(*ast.ImportSpec)
				if spec.Doc != nil {
					commentFrom := fset.Position(spec.Doc.Pos()).Offset
					commentTo := fset.Position(spec.Doc.End()).Offset
					buf.WriteByte('\t')
					buf.Write(src[commentFrom:commentTo])
					buf.WriteByte('\n')
				}
				line := getLine(spec.Pos())
				if line-prevLine != 1 {
					// Grouping line break. Write extra imports here.
					if !addedImports && !importedC {
						addedImports = true
						for _, pkgPath := range missingImports {
							fmt.Fprintf(&buf, "\t%q\n", pkgPath)
						}
					}
					buf.WriteByte('\n')
				}
				prevLine = line
				importedPath, _ := strconv.Unquote(spec.Path.Value)
				if _, ok := unusedImports[importedPath]; ok {
					continue
				}
				if addParens {
					buf.WriteByte('\t')
				}
				if spec.Name != nil {
					fmt.Fprintf(&buf, "%s %s", spec.Name.Name, spec.Path.Value)
				} else {
					buf.WriteString(spec.Path.Value)
				}
				comment := inlineComments[spec]
				if comment != nil {
					commentFrom := fset.Position(comment.Pos()).Offset
					commentTo := fset.Position(comment.End()).Offset
					buf.WriteByte(' ')
					buf.Write(src[commentFrom:commentTo])
				}
				if addParens {
					buf.WriteByte('\n')
				}
			}
			// Put extra imports into the first import decl,
			// if not done so yet.
			if !addedImports && !importedC {
				addedImports = true
				for _, pkgPath := range missingImports {
					fmt.Fprintf(&buf, "\t%q\n", pkgPath)
				}
			}
			if addParens {
				buf.WriteByte(')')
			}
			startFrom = continueFrom
		}
	} else {
		buf.Write(src[:fset.Position(file.Name.End()).Offset+1])
		buf.WriteString("\nimport (\n")
		for _, pkgPath := range missingImports {
			fmt.Fprintf(&buf, "\t%q\n", pkgPath)
		}
		buf.WriteString(")\n")
		continueFrom = fset.Position(file.Name.End()).Offset + 1
	}
	buf.Write(src[continueFrom:])

	return buf.Bytes(), nil
}


<|start_filename|>cmd/perfguard/testdata/rulestest/reflectValueKind/reflectValueKind.go<|end_filename|>
package rulestest

import (
	"reflect"
)

func Warn(x interface{}, rv reflect.Value) {
	_ = rv.Type().Kind() // want `rv.Type().Kind() => rv.Kind()`
}

func Ignore(x interface{}, rv reflect.Value) {
	_ = rv.Kind()
}


<|start_filename|>cmd/perfguard/testdata/rulestest/arrayCopy/arrayCopy.go<|end_filename|>
package rulestest

func Warn() {
	{
		var a1 [30]int
		var a2 [30]int
		copy(a1[:], a2[:]) // want `copy(a1[:], a2[:]) => a1 = a2`
		a1 = a2
	}

	{
		type withArray struct {
			arr [10]byte
		}
		var o1 withArray
		var o2 withArray
		copy(o1.arr[:], o2.arr[:]) // want `copy(o1.arr[:], o2.arr[:]) => o1.arr = o2.arr`
		o1.arr = o2.arr
	}

	{
		type withArray struct {
			arr [10]byte
		}
		o1 := new(withArray)
		o2 := new(withArray)
		copy(o1.arr[:], o2.arr[:]) // want `copy(o1.arr[:], o2.arr[:]) => o1.arr = o2.arr`
		o1.arr = o2.arr
	}
}

func Ignore() {
	{
		var a1 [30]int
		var a2 [30]int
		copy(a1[:5], a2[:5])
		copy(a1[:10], a2[:5])
		copy(a1[:5], a2[:10])
	}

	{
		var a1 [10]byte
		var a2 [12]byte
		copy(a1[:], a2[:])
		copy(a2[:], a1[:])
	}

	{
		a1 := new([10]byte)
		a2 := new([12]byte)
		copy(a1[:], a2[:])
		copy(a2[:], a1[:])
	}

	{
		a1 := new([10]byte)
		a2 := new([12]byte)
		copy((*a1)[:], (*a2)[:])
		copy((*a2)[:], (*a1)[:])
	}
}


<|start_filename|>internal/quickfix/quickfix_test.go<|end_filename|>
package quickfix

import (
	"regexp"
	"strings"
	"testing"
)

func TestQuickFixNested(t *testing.T) {
	// Test that we don't end up with broken code if one replacement
	// occurs inside another one.

	// Imagine that we had a `$f($x)` -> `$x` rule.
	// There are at least 3 possible outcomes:
	// 1. g(10); -- outer call replaced
	// 2. 10;    -- both calls replaced
	// 3. f(10); -- inner call replaced
	//
	// We don't care which one will be chosen as long as the
	// result is consistent and doesn't result in some garbage.
	input := `f(g(10));`
	fix1 := TextEdit{
		StartOffset: 0,
		EndOffset:   8,
		Replacement: []byte(`g(10)`),
	}
	fix2 := TextEdit{
		StartOffset: 2,
		EndOffset:   7,
		Replacement: []byte(`10`),
	}

	runTest := func(want string, fixes []TextEdit) {
		out, _ := Apply([]byte(input), fixes)
		have := string(out)
		if have != want {
			t.Errorf("%q %v:\nhave: `%s`\nwant: `%s`", input, fixes, have, want)
		}
	}

	runTest(`g(10);`, []TextEdit{fix1})
	runTest(`g(10);`, []TextEdit{fix1, fix2})
	runTest(`f(10);`, []TextEdit{fix2})
	runTest(`g(10);`, []TextEdit{fix2, fix1})

	// Also test that we can tolerate duplicated rewrites.
	runTest(`g(10);`, []TextEdit{fix1, fix1, fix2, fix1})
	runTest(`g(10);`, []TextEdit{fix2, fix1, fix2, fix2})
}

func TestQuickFix(t *testing.T) {
	tests := map[string][]string{
		// No replacements.
		``:      {},
		` abc `: {},

		// Empty replacements.
		`$a$`:       {``},
		`$a$$b$`:    {``, ``},
		`1$a$2$b$3`: {``, ``},

		// Replacement and original of the same length.
		`  $a$$b$ `:  {`x`, `y`},
		` $a$ $b$  `: {`x`, `y`},

		// Replacement longer than original.
		`$aa$ $bb$ $cc$`:     {`xxx`, `yyy`, `zzz`},
		`  $aa$  $bb$ $cc$`:  {`xxx`, `yyy`, `zzz`},
		` $aa$ $bb$  $cc$  `: {`xxx`, `yyy`, `zzz`},

		// Original longer than replacement.
		`$aa$ $bbb$ $cc$`:    {`x`, `yy`, `z`},
		` $aa$  $bbb$ $cc$ `: {`x`, `yy`, `z`},
		`$aa$ $bbb$  $cc$`:   {`x`, `yy`, `z`},

		// Mixed lengths.
		` $a$    $bb$  $ccc$ `:    {`xx`, `y`, `zz`},
		` $aaa$    $bb$   $cc$  `: {`xxx`, `y`, `z`},
		` $aaa$ $bbb$ $cccc$ $d$`: {`x`, `yyyyyyy`, `zzz`, ``},
		` $d$$aaa$  $bbb$$cccc$`:  {`xxxxxx`, `y`, ``, `zz`},
		`$aaaaaaaaaaaaa$$b$`:      {`x`, `yyyy`},
		`$a$$bbbbb$`:              {`yyyy`, `x`},

		// Multiline.
		`<?php
foo = $x ?
       x :
       y$;`: {`x ?: y`},
	}

	type testCase struct {
		input string
		want  string
		fixes []TextEdit
	}
	varRegex := regexp.MustCompile(`\$[^$]+?\$`)
	createTestCase := func(s string, replacements []string) testCase {
		i := -1
		withReplacements := varRegex.ReplaceAllStringFunc(s, func(x string) string {
			i++
			return replacements[i]
		})

		fixes := make([]TextEdit, len(replacements))

		for i, m := range varRegex.FindAllStringIndex(s, -1) {
			fixup := i * len("$") * 2
			begin := m[0]
			end := m[1]
			fixes[i] = TextEdit{
				StartOffset: begin - fixup,
				EndOffset:   (end - len("$")*2) - fixup,
				Replacement: []byte(replacements[i]),
			}
		}

		return testCase{
			input: strings.ReplaceAll(s, "$", ""),
			want:  withReplacements,
			fixes: fixes,
		}
	}

	for s, replacements := range tests {
		test := createTestCase(s, replacements)
		out, _ := Apply([]byte(test.input), test.fixes)
		have := string(out)
		if have != test.want {
			t.Errorf("%q %q:\nhave: `%s`\nwant: `%s`", test.input, replacements, have, test.want)
		}
	}
}


<|start_filename|>cmd/perfguard/testdata/quickfix/strconv/out/after.go<|end_filename|>
package main

import (
	"strconv"
)

func main() {
	intTests := []int{0, 1, -1, 295}
	for _, v := range intTests {
		println(strconv.Itoa(v))
		println(strconv.Itoa(v))
		println(strconv.Itoa(v))
	}

	int64Tests := []int64{0, 1, -1, 295, 45032}
	for _, v := range int64Tests {
		println(strconv.FormatInt(v, 10))
		println(strconv.FormatInt(v, 10))
		println(strconv.FormatInt(v, 16))
		println(strconv.FormatInt(v, 10))
	}

	uint64Tests := []uint64{0, 1, 295, 45032}
	for _, v := range uint64Tests {
		println(strconv.FormatUint(v, 10))
		println(strconv.FormatUint(v, 10))
		println(strconv.FormatUint(v, 16))
		println(strconv.FormatUint(v, 10))
	}
}


<|start_filename|>cmd/perfguard/testdata/rulestest/writeByte/writeByte.go<|end_filename|>
package rulestest

import (
	"bytes"
	"strings"
)

func Warn(builder *strings.Builder) {
	var buf bytes.Buffer
	buf.WriteRune('\n') // want `buf.WriteRune('\n') => buf.WriteByte('\n')`

	builder.WriteRune('\n') // want `builder.WriteRune('\n') => builder.WriteByte('\n')`
}

func Ignore(builder *strings.Builder, w RuneWriter) {
	builder.WriteRune('ь')
	builder.WriteByte('\n')

	w.WriteRune('\n')
	w.WriteRune('0')
}

type RuneWriter interface {
	WriteRune(r rune) (int, error)
}


<|start_filename|>cmd/perfguard/testdata/rulestest/utf8DecodeRune/utf8DecodeRune.go<|end_filename|>
package rulestest

import (
	"strings"
)

func Warn() {
	_ = []rune("abc")[0]        // want `use utf8.DecodeRuneInString("abc") here`
	_ = []rune(makeString())[0] // want `use utf8.DecodeRuneInString(makeString()) here`
}

func Ignore() {
	{
		r := []uint64{10}
		_ = r[0]
		r2 := []rune{10, 12, 34}
		_ = r2[0]
	}

	{
		// OK: 'runes' is not string-typed.
		var runes []rune
		_ = []rune(runes)[0]
	}

	{
		// OK: not a 0 index.
		var s string
		_ = []rune(s)[1]
	}

	{
		// OK: let's allow using int32 for now?
		var s string
		_ = []int32(s)[0]
	}
}

func getBytes() []byte  { return nil }
func getString() string { return "" }

func makeString() string {
	return strings.Repeat("abc", 3)
}


<|start_filename|>cmd/perfguard/testdata/rulestest/mapClear/mapClear.go<|end_filename|>
package rulestest

func Warn(m map[string]int) {
	{
		m = make(map[string]int, len(m)) // want `m = make(map[string]int, len(m)) => for k := range m { delete(m, k) }`
	}

	{
		type withMap struct {
			m map[int]bool
		}
		o := &withMap{}
		o.m = make(map[int]bool, len(o.m)) // want `o.m = make(map[int]bool, len(o.m)) => for k := range o.m { delete(o.m, k) }`
	}
}

func Ignore(m map[string]int) {
	{
		for k := range m {
			delete(m, k)
		}
	}

	{
		type withMap struct {
			m map[int]bool
		}
		o := &withMap{}
		for k := range o.m {
			delete(o.m, k)
		}
	}
}


<|start_filename|>cmd/perfguard/testdata/rulestest/slicedConv/slicedConv.go<|end_filename|>
package rulestest

func Warn(s string, b []byte) {
	_ = string(b)[:5] // want `string(b)[:5] => string(b[:5])`
	_ = []byte(s)[:5] // want `[]byte(s)[:5] => []byte(s[:5])`
}

func Ignore(s string, b []byte) {
	_ = string(b[:5])
	_ = []byte(s[:5])
}


<|start_filename|>cmd/perfguard/testdata/checkerstest/condReorder/condReorder.go<|end_filename|>
package checkerstest

func Warn1(simple bool) {
	if predicate() && simple { // want `predicate() && simple => simple && predicate()`
	}
	if predicate() || simple { // want `predicate() || simple => simple || predicate()`
	}
}

func Fixed1(simple bool) {
	if simple && predicate() {
	}
	if simple || predicate() {
	}
}

func Warn2(simple bool) {
	for predicate() && simple { // want `predicate() && simple => simple && predicate()`
	}
	for predicate() || simple { // want `predicate() || simple => simple || predicate()`
	}

	for i := 0; predicate() && simple; i++ { // want `predicate() && simple => simple && predicate()`
	}
	for i := 0; predicate() || simple; i++ { // want `predicate() || simple => simple || predicate()`
	}
}

func Fixed2(simple bool) {
	for simple && predicate() {
	}
	for simple || predicate() {
	}

	for i := 0; simple && predicate(); i++ {
	}
	for i := 0; simple || predicate(); i++ {
	}
}

func Warn3(simple bool) {
	{
		_ = predicate() && simple // want `predicate() && simple => simple && predicate()`
	}
}

func Fixed3(simple bool) {
	{
		_ = simple && predicate()
	}
}

func Ignore1(xs []bool) {
	// In the conditions below, we can't change the order
	// even if `xs[0]` is "simpler" than `len(xs) > 0`.
	if len(xs) > 0 && xs[0] {
	}
	if len(xs) >= +0 && xs[0] {
	}
	if len(xs) != 0 && xs[0] {
	}

	type object struct {
		x int
		b bool
	}
	o := new(object)
	if o != nil && o.b {
	}
	if o == nil || o.b {
	}
}

func Ignore2(cond bool, xs []int) {
	// Conditions below are +/- identical.
	// There is no point in moving them around.
	if (cond) && cond {
	}
	if !cond && cond {
	}
	if !!cond && cond {
	}
	if !!!cond && cond {
	}
	if cond && len(xs) > 0 {
	}
	if len(xs) > 0 && cond {
	}
}

func Ignore3(cond bool) {
	// Never suggest to put a function call before a simple expr.
	if cond && predicate() {
	}
	if !cond && predicate() {
	}
	if !!cond && predicate() {
	}
	if !!!cond && predicate() {
	}
}

func Ignore4(isMtime, fitsOctal, needsNano bool) {
	if !isMtime || !fitsOctal || needsNano {
	}
}

func Ignore5() {
	{
		// Can't reorder here: o.Check may alter the o state, making the o.cond different.
		o := &trickyObject{}
		if o.Check() && o.cond {
		}
	}
}

func Ignore6() {
	{
		// Do not reorder map lookups.
		var m map[string]bool
		var k string
		if predicate() && m[k] {
		}
		if predicate() || m[k] {
		}
		if predicate() && !m[k] {
		}
		if predicate() || !m[k] {
		}
	}
}

type trickyObject struct {
	cond bool
}

func (o *trickyObject) Check() bool { return false }

func predicate() bool { return false }


<|start_filename|>cmd/perfguard/testdata/rulestest/convReorder/convReorder.go<|end_filename|>
package rulestest

import (
	"bytes"
	"strings"
)

func Warn(s, s2 string, b, b2 []byte) {
	_ = strings.TrimSpace(string(b)) // want `strings.TrimSpace(string(b)) => string(bytes.TrimSpace(b))`
	_ = bytes.TrimSpace([]byte(s))   // want `bytes.TrimSpace([]byte(s)) => []byte(strings.TrimSpace(s))`

	_ = strings.TrimPrefix(string(b), string(b2)) // want `strings.TrimPrefix(string(b), string(b2)) => string(bytes.TrimPrefix(b, b2))`
	_ = bytes.TrimPrefix([]byte(s), []byte(s2))   // want `bytes.TrimPrefix([]byte(s), []byte(s2)) => []byte(strings.TrimPrefix(s, s2))`
}

func Ignore(s, s2 string, b, b2 []byte) {
	_ = string(bytes.TrimSpace(b))
	_ = []byte(strings.TrimSpace(s))

	_ = string(bytes.TrimPrefix(b, b2))
	_ = []byte(strings.TrimPrefix(s, s2))
}


<|start_filename|>perfguard/checkers/funccheckers/stringsBuilder.go<|end_filename|>
package funccheckers

import (
	"go/ast"
	"go/token"
	"go/types"

	"github.com/quasilyte/go-perfguard/internal/typeis"
	"github.com/quasilyte/go-perfguard/perfguard/checkers"
	"github.com/quasilyte/go-perfguard/perfguard/lint"
)

func init() {
	doc := checkers.Doc{
		Name:  "stringsBuilder",
		Score: 4,
	}
	checkers.RegisterFuncChecker(doc, func() checkers.FuncChecker {
		return &stringsBuilderChecker{
			stringsBuilderExpr: &ast.SelectorExpr{
				X:   &ast.Ident{Name: "strings"},
				Sel: &ast.Ident{Name: "Builder"},
			},
			allowedUsages: make(map[*ast.Ident]struct{}),
		}
	})
}

type stringsBuilderChecker struct {
	ctx                *lint.Context
	candidates         []stringsBuilderCandidate
	stringsBuilderExpr ast.Expr
	allowedUsages      map[*ast.Ident]struct{}
	nestedFunc         bool
}

type stringsBuilderCandidate struct {
	typeNode ast.Expr

	obj *types.Var

	toStringUsages []ast.Node
}

func (c *stringsBuilderChecker) CheckFunc(ctx *lint.Context, body *ast.BlockStmt) error {
	c.ctx = ctx
	c.candidates = c.candidates[:0]
	c.nestedFunc = false
	for k := range c.allowedUsages {
		delete(c.allowedUsages, k)
	}

	ast.Inspect(body, c.walk)

	for _, candidate := range c.candidates {
		if len(candidate.toStringUsages) == 0 {
			continue
		}
		ctx.SuggestNode(lint.SuggestParams{
			OldNode:  candidate.typeNode,
			NewNode:  c.stringsBuilderExpr,
			HotNodes: candidate.toStringUsages,
		})
	}

	return nil
}

func (c *stringsBuilderChecker) walk(n ast.Node) bool {
	switch n := n.(type) {
	case *ast.FuncLit:
		nestedFunc := c.nestedFunc
		c.nestedFunc = true
		ast.Inspect(n.Body, c.walk)
		c.nestedFunc = nestedFunc
		return false

	case *ast.ValueSpec:
		if c.nestedFunc {
			return true
		}
		// Track `var $x bytes.Buffer` variables.
		if len(n.Names) != 1 || n.Values != nil || n.Type == nil {
			return true
		}
		name := n.Names[0]
		obj := c.ctx.VarOf(name)
		if c.isBytesBuffer(obj.Type()) {
			c.track(obj, n.Type)
			return false
		}

	case *ast.AssignStmt:
		if c.nestedFunc {
			return true
		}
		// Track `$x := bytes.Buffer{}` variables.
		// Track `$x := &bytes.Buffer{}` variables.
		if len(n.Lhs) != 1 || len(n.Rhs) != 1 || n.Tok != token.DEFINE {
			return true
		}
		var typeNode ast.Expr
		rhs := n.Rhs[0]
		if unary, ok := rhs.(*ast.UnaryExpr); ok && unary.Op == token.AND {
			rhs = unary.X
		}
		if lit, ok := rhs.(*ast.CompositeLit); ok {
			if len(lit.Elts) != 0 || lit.Type == nil {
				return true
			}
			asSelector, ok := lit.Type.(*ast.SelectorExpr)
			if !ok {
				return true
			}
			if pkgIdent, ok := asSelector.X.(*ast.Ident); !ok || pkgIdent.Name != "bytes" {
				return true
			}
			if asSelector.Sel.Name != "Buffer" {
				return true
			}
			typeNode = lit.Type
		}
		name, ok := n.Lhs[0].(*ast.Ident)
		if !ok {
			return true
		}
		obj := c.ctx.VarOf(name)
		if typeNode != nil && c.isBytesBuffer(obj.Type()) {
			c.track(obj, typeNode)
			return false
		}

	case *ast.CallExpr:
		c.markCallArgs(n)
		selector, ok := n.Fun.(*ast.SelectorExpr)
		if !ok {
			return true
		}
		x, ok := selector.X.(*ast.Ident)
		if !ok {
			return true
		}
		obj := c.ctx.VarOf(x)
		if !c.isBytesBuffer(obj.Type()) {
			return true
		}
		candidate := c.find(obj)
		if candidate != nil {
			c.allowedUsages[x] = struct{}{}
			return c.checkBufferCall(n, candidate, selector.Sel.Name)
		}
		return true

	case *ast.Ident:
		// If it's a buffer usage, untrack the candidate.
		// We stop tree traversal when we recognize the usage.
		obj := c.ctx.VarOf(n)
		if c.isBytesBuffer(obj.Type()) {
			if _, ok := c.allowedUsages[n]; !ok {
				c.untrack(obj, "uncategorized usage")
			}
		}
	}

	return true
}

func (c *stringsBuilderChecker) checkBufferCall(n *ast.CallExpr, candidate *stringsBuilderCandidate, methodName string) bool {
	// "Reset" method is not allowed as it doesn't work that well in
	// strings buider. Buffer will reuse the memory while builder will not.
	switch methodName {
	case "Cap", "Len", "Grow", "Write", "WriteString", "WriteByte", "WriteRune":
		return true

	case "String":
		candidate.toStringUsages = append(candidate.toStringUsages, n.Fun)
		return true

	default:
		c.untrack(candidate.obj, "unsupported method")
		return true
	}
}

func (c *stringsBuilderChecker) markCallArgs(n *ast.CallExpr) {
	if n.Ellipsis.IsValid() {
		return
	}

	fnType, ok := c.ctx.TypeOf(n.Fun).(*types.Signature)
	if !ok {
		return
	}
	params := fnType.Params()

	for i, arg := range n.Args {
		if i >= params.Len() {
			break
		}

		x := arg
		if arg, ok := arg.(*ast.UnaryExpr); ok && arg.Op == token.AND {
			x = arg.X
		}
		ident, ok := x.(*ast.Ident)
		if !ok {
			continue
		}

		obj := c.ctx.VarOf(ident)
		if !c.isBytesBuffer(obj.Type()) {
			continue
		}
		candidate := c.find(obj)
		if candidate == nil {
			continue
		}

		paramType := params.At(i).Type()
		if typeis.Named(paramType, "io", "Writer") {
			c.allowedUsages[ident] = struct{}{}
		}
	}
}

func (c *stringsBuilderChecker) isBytesBuffer(typ types.Type) bool {
	if ptr, ok := typ.(*types.Pointer); ok {
		typ = ptr.Elem()
	}
	return typeis.Named(typ, "bytes", "Buffer")
}

func (c *stringsBuilderChecker) track(v *types.Var, typeNode ast.Expr) {
	c.candidates = append(c.candidates, stringsBuilderCandidate{
		obj:      v,
		typeNode: typeNode,
	})
}

func (c *stringsBuilderChecker) find(v *types.Var) *stringsBuilderCandidate {
	for i := range c.candidates {
		if c.candidates[i].obj == v {
			return &c.candidates[i]
		}
	}
	return nil
}

func (c *stringsBuilderChecker) untrack(v *types.Var, reason string) {
	index := -1
	for i := range c.candidates {
		if c.candidates[i].obj == v {
			index = i
			break
		}
	}
	if index == -1 {
		return
	}
	c.candidates[index] = c.candidates[len(c.candidates)-1]
	c.candidates = c.candidates[:len(c.candidates)-1]
}


<|start_filename|>cmd/perfguard/testdata/rulestest/sprintfConcat/sprintfConcat.go<|end_filename|>
package rulestest

import (
	"bytes"
	"fmt"
)

func Warn(s1, s2 string, buf1, buf2 *bytes.Buffer) {
	_ = fmt.Sprintf("%s%s", s1, s2)     // want `fmt.Sprintf("%s%s", s1, s2) => s1 + s2`
	_ = fmt.Sprintf("%s%s", buf1, buf2) // want `fmt.Sprintf("%s%s", buf1, buf2) => buf1.String() + buf2.String(`
}

func Ignore() {
	var s1, s2 string
	_ = s1 + s2
	_ = s1 + "_" + s2
}


<|start_filename|>cmd/perfguard/testdata/rulestest/utf8DecodeRune/utf8DecodeRune2.go<|end_filename|>
package rulestest

import (
	"unicode/utf8"
)

var _ = utf8.MaxRune

func Warn2() {
	_ = []rune("abc")[0]        // want `_ = []rune("abc")[0] => _, _ = utf8.DecodeRuneInString(_)`
	_ = []rune(makeString())[0] // want ` = []rune(makeString())[0] => _, _ = utf8.DecodeRuneInString(_)`

	{
		ch := []rune("abc")[0] // want `ch := []rune("abc")[0] => ch, _ := utf8.DecodeRuneInString(ch`
		_ = ch
	}

	var ch rune
	{
		ch = []rune("abc")[0] // want `ch = []rune("abc")[0] => ch, _ = utf8.DecodeRuneInString(ch)`
	}
	_ = ch
}


<|start_filename|>cmd/perfguard/testdata/rulestest/fprint/fprint.go<|end_filename|>
package rulestest

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
)

func Warn(w io.Writer) {
	w.Write([]byte(fmt.Sprintf("%+x", 10))) // want `w.Write([]byte(fmt.Sprintf("%+x", 10))) => fmt.Fprintf(w, "%+x", 10)`

	w.Write([]byte(fmt.Sprint(1, 2, 3, 4))) // want `w.Write([]byte(fmt.Sprint(1, 2, 3, 4))) => fmt.Fprint(w, 1, 2, 3, 4)`

	w.Write([]byte(fmt.Sprintln(1, 2, 3, 4))) // want `w.Write([]byte(fmt.Sprintln(1, 2, 3, 4))) => fmt.Fprintln(w, 1, 2, 3, 4)`

	buf := &bytes.Buffer{}

	buf.Write([]byte(fmt.Sprintf("%+x", 10))) // want `buf.Write([]byte(fmt.Sprintf("%+x", 10))) => fmt.Fprintf(buf, "%+x", 10)`

	buf.Write([]byte(fmt.Sprint(1, 2, 3, 4))) // want `buf.Write([]byte(fmt.Sprint(1, 2, 3, 4))) => fmt.Fprint(buf, 1, 2, 3, 4)`

	buf.Write([]byte(fmt.Sprintln(1, 2, 3, 4))) // want `buf.Write([]byte(fmt.Sprintln(1, 2, 3, 4))) => fmt.Fprintln(buf, 1, 2, 3, 4)`

	var i uintptr

	io.WriteString(buf, fmt.Sprint(i)) // want `io.WriteString(buf, fmt.Sprint(i)) => fmt.Fprint(buf, i)`

	io.WriteString(buf, fmt.Sprintf("<%4d>", i)) // want `io.WriteString(buf, fmt.Sprintf("<%4d>", i)) => fmt.Fprintf(buf, "<%4d>", i)`

	io.WriteString(buf, fmt.Sprintln(i, i)) // want `io.WriteString(buf, fmt.Sprintln(i, i)) => fmt.Fprintln(buf, i, i)`

	io.WriteString(os.Stdout, fmt.Sprint(i, i)) // want `io.WriteString(os.Stdout, fmt.Sprint(i, i)) => fmt.Fprint(os.Stdout, i, i)`

	{
		var bw *bufio.Writer
		var key, value string
		if _, err := io.WriteString(bw, fmt.Sprintf("SET %s %s", key, value)); err != nil { // want `io.WriteString(bw, fmt.Sprintf("SET %s %s", key, value)) => fmt.Fprintf(bw, "SET %s %s", key, value)`
			panic(err)
		}
	}
}

func Ignore() {
	{
		var fmt formatter
		var w io.Writer
		w.Write([]byte(fmt.Sprintf()))
		w.Write([]byte(fmt.Sprint()))
		w.Write([]byte(fmt.Sprintln()))

		w.Write([]byte(fmt.Sprintf("%d", 1)))
		w.Write([]byte(fmt.Sprint(1)))
		w.Write([]byte(fmt.Sprintln(1, 2)))
	}
}

type formatter struct{}

func (formatter) Sprintf(args ...interface{}) string  { return "abc" }
func (formatter) Sprint(args ...interface{}) string   { return "abc" }
func (formatter) Sprintln(args ...interface{}) string { return "abc" }


<|start_filename|>cmd/perfguard/testdata/rulestest/indexAlloc/indexAlloc.go<|end_filename|>
package rulestest

import (
	"bytes"
	"net"
	"strings"
)

func Warn() {
	var b []byte
	var b2 []byte
	var s string
	var s2 string

	_ = strings.Index(string(b), s) // want `strings.Index(string(b), s) => bytes.Index(b, []byte(s))`

	_ = strings.Index(string([]byte("12")), s) // want `strings.Index(string([]byte("12")), s) => bytes.Index([]byte("12"), []byte(s))`

	_ = strings.Index(string([]byte{'1', '2'}), s) // want `strings.Index(string([]byte{'1', '2'}), s) => bytes.Index([]byte{'1', '2'}, []byte(s))`

	_ = strings.Index(string(b), "a"+s) // want `strings.Index(string(b), "a"+s) => bytes.Index(b, []byte("a"+s))`

	_ = strings.Contains(string(b), s)  // want `strings.Contains(string(b), s) => bytes.Contains(b, []byte(s))`
	_ = bytes.Index([]byte(s), b)       // want `bytes.Index([]byte(s), b) => strings.Index(s, string(b))`
	_ = bytes.Contains([]byte(s), b)    // want `bytes.Contains([]byte(s), b) => strings.Contains(s, string(b))`
	_ = strings.HasPrefix(string(b), s) // want `strings.HasPrefix(string(b), s) => bytes.HasPrefix(b, []byte(s))`
	_ = strings.HasSuffix(string(b), s) // want `strings.HasSuffix(string(b), s) => bytes.HasSuffix(b, []byte(s))`
	_ = bytes.HasPrefix([]byte(s), b)   // want `bytes.HasPrefix([]byte(s), b) => strings.HasPrefix(s, string(b))`
	_ = bytes.HasSuffix([]byte(s), b)   // want `bytes.HasSuffix([]byte(s), b) => strings.HasSuffix(s, string(b))`

	{
		var tag []byte
		var tagName string
		_ = strings.Contains(string(tag), tagName+":") // want `strings.Contains(string(tag), tagName+":") => bytes.Contains(tag, []byte(tagName+":"))`
	}

	{
		_ = strings.Contains(string(b), "too many layers of packets") // want `strings.Contains(string(b), "too many layers of packets") => bytes.Contains(b, []byte("too many layers of packets")`
	}

	{
		_ = strings.Contains(string(b), string(b2))  // want `strings.Contains(string(b), string(b2)) => bytes.Contains(b, b2)`
		_ = strings.HasPrefix(string(b), string(b2)) // want `strings.HasPrefix(string(b), string(b2)) => bytes.HasPrefix(b, b2)`
		_ = strings.HasSuffix(string(b), string(b2)) // want `strings.HasSuffix(string(b), string(b2)) => bytes.HasSuffix(b, b2)`
		_ = strings.EqualFold(string(b), string(b2)) // want `strings.EqualFold(string(b), string(b2)) => bytes.EqualFold(b, b2)`
		_ = strings.Compare(string(b), string(b2))   // want `strings.Compare(string(b), string(b2)) => bytes.Compare(b, b2)`

		_ = bytes.Contains([]byte(s), []byte(s2))  // want `bytes.Contains([]byte(s), []byte(s2)) => strings.Contains(s, s2)`
		_ = bytes.HasPrefix([]byte(s), []byte(s2)) // want `bytes.HasPrefix([]byte(s), []byte(s2)) => strings.HasPrefix(s, s2)`
		_ = bytes.HasSuffix([]byte(s), []byte(s2)) // want `bytes.HasSuffix([]byte(s), []byte(s2)) => strings.HasSuffix(s, s2)`
		_ = bytes.EqualFold([]byte(s), []byte(s2)) // want `bytes.EqualFold([]byte(s), []byte(s2)) => strings.EqualFold(s, s2)`
		_ = bytes.Compare([]byte(s), []byte(s2))   // want `bytes.Compare([]byte(s), []byte(s2)) => strings.Compare(s, s2)`
	}
}

func Ignore() {
	var b []byte
	var b1 []byte
	var b2 []byte
	var s string
	var s1 string
	var s2 string

	_ = bytes.Index(b1, b2)
	_ = bytes.Index([]byte(b1), b2)
	_ = bytes.Index(b1, []byte(b2))
	_ = bytes.Index([]byte(b1), []byte(b2))

	_ = bytes.Index(b1, []byte(s1))
	_ = bytes.Index([]byte("12"), []byte(s1))
	_ = bytes.Index([]byte{'1', '2'}, []byte(s1))
	_ = bytes.Index(b1, []byte("a"+s2))

	_ = bytes.Index(getBytes(), []byte(s1))
	_ = bytes.Index(getBytes(), []byte("a"+s1))
	_ = bytes.Index(b1, []byte(getString()))
	_ = bytes.Index([]byte("12"), []byte(getString()))
	_ = bytes.Index([]byte{'1', '2'}, []byte(getString()))
	_ = bytes.Index(b1, []byte("a"+getString()))
	_ = strings.Index(string(getBytes()), s1)
	_ = strings.Index(string(getBytes()), "a"+s1)
	_ = strings.Index(string(b1), getString())
	_ = strings.Index(string(getString()), getString())
	_ = strings.Index(string(b1), "a"+getString())

	{
		var b []byte
		var s string
		_ = bytes.Contains(b, []byte(s))
		_ = strings.Index(s, string(b))
		_ = strings.Contains(s, string(b))
		_ = bytes.HasPrefix(b, []byte(s))
		_ = bytes.HasSuffix(b, []byte(s))
		_ = strings.HasPrefix(s, string(b))
		_ = strings.HasSuffix(s, string(b))
	}

	{
		var tag []byte
		var tagName string
		_ = bytes.Contains(tag, []byte(tagName+":"))
	}
	{
		var tag string
		var tagName string
		_ = strings.Contains(string(tag), tagName+":")
	}

	{
		_ = bytes.Contains(b, b2)
		_ = bytes.HasPrefix(b, b2)
		_ = bytes.HasSuffix(b, b2)
		_ = bytes.EqualFold(b, b2)
		_ = bytes.Compare(b, b2)

		_ = strings.Contains(s, s2)
		_ = strings.HasPrefix(s, s2)
		_ = strings.HasSuffix(s, s2)
		_ = strings.EqualFold(s, s2)
		_ = strings.Compare(s, s2)
	}

	{
		var ip1, ip2 net.IP
		_ = bytes.Compare([]byte(ip1), []byte(ip2))
	}
}

func getBytes() []byte  { return nil }
func getString() string { return "" }


<|start_filename|>cmd/perfguard/testdata/rulestest/sprintfError/sprintfError.go<|end_filename|>
package rulestest

import (
	"errors"
	"fmt"
)

func Warn() {
	{
		var filename string
		var line int
		_ = errors.New(fmt.Sprintf("%s:%d", filename, line)) // want `errors.New(fmt.Sprintf("%s:%d", filename, line)) => fmt.Errorf("%s:%d", filename, line)`
	}
}

func Ignore() {
	{
		var filename string
		var line int
		_ = fmt.Errorf("%s:%d", filename, line)
	}
}


<|start_filename|>cmd/perfguard/testdata/quickfix/equalFold/before.go<|end_filename|>
package main

import (
	"bytes"
	"strings"
)

func main() {
	type testCase struct {
		s   string
		sub string
	}

	tests := []testCase{
		{"", ""},
		{"", "x"},
		{"x", ""},
		{"x", "x"},
		{"hello, world", ""},
		{"hello, world", "hello"},
		{"hello, world", "world"},
		{"world", "hello, world"},
		{"hello", "hello, world"},
	}

	var allTests []testCase
	for _, test := range tests {
		allTests = append(allTests, test)
		allTests = append(allTests, testCase{
			s:   " " + test.s,
			sub: test.sub,
		})
		allTests = append(allTests, testCase{
			s:   test.s,
			sub: " " + test.sub,
		})
		allTests = append(allTests, testCase{
			s:   " " + test.s,
			sub: " " + test.sub,
		})
		allTests = append(allTests, testCase{
			s:   strings.ToLower(test.s),
			sub: test.sub,
		})
		allTests = append(allTests, testCase{
			s:   test.s,
			sub: strings.ToLower(test.sub),
		})
		allTests = append(allTests, testCase{
			s:   strings.ToLower(test.s),
			sub: strings.ToLower(test.sub),
		})
		allTests = append(allTests, testCase{
			s:   strings.ToUpper(test.s),
			sub: test.sub,
		})
		allTests = append(allTests, testCase{
			s:   test.s,
			sub: strings.ToUpper(test.sub),
		})
		allTests = append(allTests, testCase{
			s:   strings.ToUpper(test.s),
			sub: strings.ToUpper(test.sub),
		})
	}

	for _, test := range allTests {
		println("hassuffix+tolower", test.s, test.sub,
			strings.HasSuffix(strings.ToLower(test.s), test.sub),
		)
		println("hassuffix+toupper", test.s, test.sub,
			strings.HasSuffix(strings.ToUpper(test.s), test.sub),
		)
		println("hasprefix+tolower", test.s, test.sub,
			strings.HasPrefix(strings.ToLower(test.s), test.sub),
		)
		println("hasprefix+toupper", test.s, test.sub,
			strings.HasPrefix(strings.ToUpper(test.s), test.sub),
		)

		b := []byte(test.s)
		sub := []byte(test.sub)
		println("bytes hassuffix+tolower", test.s, test.sub,
			bytes.HasSuffix(bytes.ToLower(b), sub),
		)
		println("bytes hassuffix+toupper", test.s, test.sub,
			bytes.HasSuffix(bytes.ToUpper(b), sub),
		)
		println("bytes hasprefix+tolower", test.s, test.sub,
			bytes.HasPrefix(bytes.ToLower(b), sub),
		)
		println("bytes hasprefix+toupper", test.s, test.sub,
			bytes.HasPrefix(bytes.ToUpper(b), sub),
		)
	}
}


<|start_filename|>cmd/perfguard/testdata/rulestest/stringsClone/stringsClone.go<|end_filename|>
package rulestest

import (
	"strings"
)

func Warn(s string) {
	_ = string([]byte(s)) // want `string([]byte(s)) => strings.Clone(s)`
}

func Ignore(s string, b []byte) {
	_ = strings.Clone(s)
	_ = string(b)
	_ = string([]byte(b))
	_ = string([]byte("literal"))
}


<|start_filename|>perfguard/lint/context.go<|end_filename|>
package lint

import (
	"go/ast"
	"go/token"
	"path/filepath"
	"strings"
	"time"

	"github.com/quasilyte/perf-heatmap/heatmap"
)

type Context struct {
	*SharedContext

	tag          string
	minHeatLevel int
}

type NodeReplacement struct {
	Text   []byte
	Syntax ast.Node
}

func NewContext(tag string, minHeatLevel int) Context {
	return Context{
		tag:          tag,
		minHeatLevel: minHeatLevel,
	}
}

type MultiChangeSuggestParams struct {
	ReportPos     token.Pos
	ReportMessage string

	OldNodes []ast.Node
	NewNodes []NodeReplacement
	HotNodes []ast.Node

	UseFlatSamples bool
}

func (ctx *Context) MultiChangeSuggest(params MultiChangeSuggestParams) {
	var hotNodes = params.HotNodes
	if len(hotNodes) == 0 {
		hotNodes = params.OldNodes
	}
	samplesValue, matched := ctx.listMatchesHeatmap(hotNodes, params.UseFlatSamples)
	if !matched {
		return
	}

	reportPos := ctx.Target.Fset.Position(params.ReportPos)
	message := params.ReportMessage

	textEdits := make([]TextEdit, 0, len(params.OldNodes))
	for i, oldNode := range params.OldNodes {
		newNode := params.NewNodes[i]
		replacement := newNode.Text
		if newNode.Syntax != nil {
			replacement = ctx.NodeText(newNode.Syntax)
		}
		textEdits = append(textEdits, TextEdit{
			From:        oldNode.Pos(),
			To:          oldNode.End(),
			Replacement: replacement,
		})
	}

	ctx.Warn(Warning{
		Filename:    reportPos.Filename,
		Line:        reportPos.Line,
		Tag:         ctx.tag,
		Text:        message,
		Fixes:       textEdits,
		SamplesTime: time.Duration(samplesValue),
	})
}

type SuggestParams struct {
	OldNode ast.Node
	NewNode ast.Node

	Message string

	HotNodes []ast.Node

	UseFlatSamples bool
}

func (ctx *Context) SuggestNode(params SuggestParams) {
	oldNode := params.OldNode
	newNode := params.NewNode

	var hotNodes = params.HotNodes
	if len(hotNodes) == 0 {
		hotNodes = []ast.Node{oldNode}
	}
	samplesValue, matched := ctx.listMatchesHeatmap(hotNodes, params.UseFlatSamples)
	if !matched {
		return
	}

	startPos := ctx.Target.Fset.Position(oldNode.Pos())

	message := params.Message
	replacement := ctx.NodeText(newNode)
	if message == "" {
		var b strings.Builder
		b.Write(ctx.NodeText(oldNode))
		b.WriteString(" => ")
		b.Write(replacement)
		message = strings.ReplaceAll(b.String(), "\n", `\n`)
	}

	textEdit := TextEdit{
		From:        oldNode.Pos(),
		To:          oldNode.End(),
		Replacement: replacement,
	}
	ctx.Warn(Warning{
		Filename:    startPos.Filename,
		Line:        startPos.Line,
		Tag:         ctx.tag,
		Text:        message,
		Fixes:       []TextEdit{textEdit},
		SamplesTime: time.Duration(samplesValue),
	})
}

type ReportParams struct {
	PosNode ast.Node

	Message string

	HotNodes []ast.Node

	UseFlatSamples bool
}

func (ctx *Context) Report(params ReportParams) {
	startPos := ctx.Target.Fset.Position(params.PosNode.Pos())

	var hotNodes = params.HotNodes
	if len(hotNodes) == 0 {
		hotNodes = []ast.Node{params.PosNode}
	}
	samplesValue, matched := ctx.listMatchesHeatmap(hotNodes, params.UseFlatSamples)
	if !matched {
		return
	}

	message := strings.ReplaceAll(params.Message, "\n", `\n`)

	ctx.Warn(Warning{
		Filename:    startPos.Filename,
		Line:        startPos.Line,
		Tag:         ctx.tag,
		Text:        message,
		SamplesTime: time.Duration(samplesValue),
	})
}

func (ctx *Context) listMatchesHeatmap(nodes []ast.Node, flat bool) (int64, bool) {
	samplesValue := int64(0)
	flatSamplesValue := int64(0)
	matched := false
	for _, heatNode := range nodes {
		if ctx.matchesHeatmap(heatNode, &flatSamplesValue, &samplesValue) {
			matched = true
		}
	}
	value := samplesValue
	if flat {
		if flatSamplesValue == 0 && ctx.Heatmap != nil {
			return 0, false
		}
		value = flatSamplesValue
	}
	return value, matched
}

func (ctx *Context) matchesHeatmap(n ast.Node, flat, cumulative *int64) bool {
	if ctx.Heatmap == nil {
		return true
	}
	minLevel := ctx.minHeatLevel
	if minLevel == 0 {
		return true
	}
	startPos := ctx.Target.Fset.Position(n.Pos())
	endPos := ctx.Target.Fset.Position(n.End())
	lineFrom := startPos.Line
	lineTo := endPos.Line
	isHot := false
	key := heatmap.Key{
		TypeName: ctx.TypeName,
		FuncName: ctx.FuncName,
		Filename: filepath.Base(startPos.Filename),
		PkgName:  ctx.Target.Pkg.Name(),
	}
	flatValueTotal := int64(0)
	cumulativeValueTotal := int64(0)
	ctx.Heatmap.QueryLineRange(key, lineFrom, lineTo, func(l heatmap.LineStats) bool {
		if l.GlobalHeatLevel >= minLevel {
			isHot = true
		}
		cumulativeValueTotal += l.Value
		flatValueTotal += l.FlatValue
		return true
	})
	if isHot {
		*cumulative += cumulativeValueTotal
		*flat += flatValueTotal
	}
	return isHot
}


<|start_filename|>cmd/perfguard/testdata/rulestest/rangeToCopy/rangeToCopy.go<|end_filename|>
package rulestest

func Warn(srcBytes, dstBytes []byte) {
	{
		for i := range srcBytes { // want `for … { … } => copy(dstBytes, srcBytes)`
			dstBytes[i] = srcBytes[i]
		}
	}

	{
		for i, x := range srcBytes { // want `for … { … } => copy(dstBytes, srcBytes)`
			dstBytes[i] = x
		}
	}

	{
		for i := 0; i < len(srcBytes); i++ { // want `for … { … } => copy(dstBytes, srcBytes)`
			dstBytes[i] = srcBytes[i]
		}
	}

	{
		var xs []int
		var ys []int

		{
			for i := range xs { // want `for … { … } => copy(ys, xs)`
				ys[i] = xs[i]
			}
		}

		{
			for i, x := range xs { // want `for … { … } => copy(ys, xs)`
				ys[i] = x
			}
		}
	}
}

func Ignore(srcBytes, dstBytes []byte) {
	copy(dstBytes, srcBytes)

	{
		var xs []int
		var ys []int

		copy(xs, ys)

		for i := range xs {
			println(i)
			ys[i] = xs[i]
		}
	}

	{
		var src []int
		dst := make(map[int]int, len(src))
		for i, x := range src {
			dst[i] = x
		}
		for i := range src {
			dst[i] = src[i]
		}
	}

	{
		var src []int
		dst := make([]interface{}, len(src))
		for i, x := range src {
			dst[i] = x
		}
	}

	{
		type Book struct {
			authorID int
		}
		var books []Book
		m := make(map[int]Book, 10)
		for _, book := range books {
			m[book.authorID] = book
		}
	}
}


<|start_filename|>internal/resolve/fmtstring_test.go<|end_filename|>
package resolve

import (
	"fmt"
	"reflect"
	"testing"
)

func TestFmtString(t *testing.T) {
	tests := []struct {
		s    string
		want []string
	}{
		{"", []string{}},
		{"%%", []string{}},
		{"%%d", []string{}},

		{"%", nil},
		{"%[0]d", nil},
		{"%z", nil},

		{"%%%d", []string{"%d<0>"}},
		{"%%%+d", []string{"%+d<0>"}},
		{"%%%-d", []string{"%-d<0>"}},
		{"%d%d", []string{"%d<0>", "%d<1>"}},
		{"%s%d", []string{"%s<0>", "%d<1>"}},
	}

	resolveArgs := func(s string) []string {
		info, ok := FmtString(s)
		if !ok {
			return nil
		}
		args := make([]string, len(info.Args))
		for i, a := range info.Args {
			args[i] = a.String()
		}
		return args
	}

	for i := range tests {
		test := tests[i]
		t.Run(fmt.Sprintf("test%d", i), func(t *testing.T) {
			have := resolveArgs(test.s)
			if have == nil && test.want != nil {
				t.Fatalf("%q: unexpected nil, want %q", test.s, test.want)
			}
			if !reflect.DeepEqual(have, test.want) {
				t.Fatalf("%q: results mismatched:\nhave: %q\nwant: %q",
					test.s, have, test.want)
			}
		})
	}
}


<|start_filename|>cmd/perfguard/testdata/quickfix/binaryWrite/before.go<|end_filename|>
package main

import (
	"bytes"
	"encoding/binary"
	"strings"
)

func main() {
	{
		var buf strings.Builder
		binary.Write(&buf, binary.LittleEndian, []byte("hello, "))
		binary.Write(&buf, binary.BigEndian, []byte("world"))
		println(buf.String())
	}

	{
		var buf strings.Builder
		binary.Write(&buf, binary.LittleEndian, "hello, ")
		binary.Write(&buf, binary.BigEndian, "world")
		println(buf.String())
	}

	{
		buf := bytes.NewBuffer(make([]byte, 10))
		binary.Write(buf, binary.LittleEndian, []byte("hello, "))
		binary.Write(buf, binary.BigEndian, []byte("world"))
		println(buf.String())
	}

	{
		var buffers [4]bytes.Buffer
		binary.Write(&buffers[0], binary.LittleEndian, []byte("hello, "))
		binary.Write(&buffers[0], binary.BigEndian, []byte("world"))
		println(buffers[0].String())
	}

	{
		var buf strings.Builder
		if err := binary.Write(&buf, binary.LittleEndian, []byte("hello, ")); err != nil {
			panic(err)
		}
		if err := binary.Write(&buf, binary.BigEndian, []byte("world")); err != nil {
			panic(err)
		}
		println(buf.String())
	}

	{
		var buf strings.Builder
		if err := binary.Write(&buf, binary.LittleEndian, "hello, "); err != nil {
			panic(err)
		}
		if err := binary.Write(&buf, binary.BigEndian, "world"); err != nil {
			panic(err)
		}
		println(buf.String())
	}
}


<|start_filename|>cmd/perfguard/testdata/rulestest/bytesCut/bytesCut.go<|end_filename|>
package rulestest

import (
	"bytes"
	"strings"
)

func Warn(s, delim string, b, bDelim []byte) {
	var bOut []byte

	{
		bOut = bytes.Split(b, bDelim)[0]   // want `bOut = bytes.Split(b, bDelim)[0] => bOut, _, _ = bytes.Cut(b, bDelim)`
		bOut2 := bytes.Split(b, bDelim)[0] // want `bOut2 := bytes.Split(b, bDelim)[0] => bOut2, _, _ := bytes.Cut(b, bDelim)`
		_ = bOut2
	}

	_ = bOut
}

func Ignore(s, delim string, b, bDelim []byte) {
	var sOut string
	var bOut []byte

	{
		sOut, _, _ = strings.Cut(s, delim)
		sOut2, _, _ := strings.Cut(s, delim)
		_ = sOut2
	}
	{
		bOut, _, _ = bytes.Cut(b, bDelim)
		bOut2, _, _ := bytes.Cut(b, bDelim)
		_ = bOut2
	}

	_ = sOut
	_ = bOut
}


<|start_filename|>internal/resolve/fmtstring.go<|end_filename|>
package resolve

import (
	"strconv"
	"strings"
	"unicode/utf8"
)

type FmtInfo struct {
	Args []FmtArgInfo
}

type FmtArgInfo struct {
	Flag   byte
	Verb   byte
	ArgNum int
}

func (arg FmtArgInfo) String() string {
	buf := make([]byte, 0, 8)
	buf = append(buf, '%')
	if arg.Flag != 0 {
		buf = append(buf, arg.Flag)
	}
	buf = append(buf, arg.Verb, '<')
	buf = strconv.AppendInt(buf, int64(arg.ArgNum), 10)
	buf = append(buf, '>')
	return string(buf)
}

func FmtString(s string) (FmtInfo, bool) {
	var info FmtInfo

	if !strings.Contains(s, "%") {
		return info, true
	}
	if len(s) > 512 {
		return info, false
	}

	i := 0
	for i < len(s) {
		ch, chLen := utf8.DecodeRuneInString(s[i:])
		if ch == utf8.RuneError {
			return info, false
		}
		i += chLen
		if ch != '%' {
			continue
		}

		if i+1 > len(s) {
			return info, false
		}
		var arg FmtArgInfo
		verbOffset := 0
		switch s[i] {
		case '#', '+', '-', '0':
			arg.Flag = s[i]
			verbOffset++
			if i+2 > len(s) {
				return info, false
			}
		case '%':
			i++
			continue
		}

		switch verb := s[i+verbOffset]; verb {
		case 'd', 'f', 's', 'q', 'v':
			arg.Verb = verb
		default:
			return info, false
		}

		i += verbOffset
		arg.ArgNum = len(info.Args)
		info.Args = append(info.Args, arg)
	}

	return info, true
}


<|start_filename|>cmd/perfguard/testdata/rulestest/bufferString/bufferString.go<|end_filename|>
package rulestest

import (
	"bytes"
	"fmt"
	"io"
	"strings"
)

func Warn(buf *bytes.Buffer, s string, b []byte) {
	_ = strings.Contains(buf.String(), string(b))  // want `strings.Contains(buf.String(), string(b)) => bytes.Contains(buf.Bytes(), b)`
	_ = strings.HasPrefix(buf.String(), string(b)) // want `strings.HasPrefix(buf.String(), string(b)) => bytes.HasPrefix(buf.Bytes(), b)`
	_ = strings.HasSuffix(buf.String(), string(b)) // want `strings.HasSuffix(buf.String(), string(b)) => bytes.HasSuffix(buf.Bytes(), b)`
	_ = strings.Count(buf.String(), string(b))     // want `strings.Count(buf.String(), string(b)) => bytes.Count(buf.Bytes(), b)`
	_ = strings.Index(buf.String(), string(b))     // want `strings.Index(buf.String(), string(b)) => bytes.Index(buf.Bytes(), b)`

	_ = strings.Contains(buf.String(), s)  // want `strings.Contains(buf.String(), s) => bytes.Contains(buf.Bytes(), []byte(s))`
	_ = strings.HasPrefix(buf.String(), s) // want `strings.HasPrefix(buf.String(), s) => bytes.HasPrefix(buf.Bytes(), []byte(s))`
	_ = strings.HasSuffix(buf.String(), s) // want `strings.HasSuffix(buf.String(), s) => bytes.HasSuffix(buf.Bytes(), []byte(s))`
	_ = strings.Count(buf.String(), s)     // want `strings.Count(buf.String(), s) => bytes.Count(buf.Bytes(), []byte(s))`
	_ = strings.Index(buf.String(), s)     // want `strings.Index(buf.String(), s) => bytes.Index(buf.Bytes(), []byte(s))`

	_ = []byte(buf.String()) // want `[]byte(buf.String()) => buf.Bytes()`

	{
		var w io.Writer
		b := bytes.NewBuffer(make([]byte, 10))
		fmt.Fprint(w, b.String())        // want `fmt.Fprint(w, b.String()) => w.Write(b.Bytes())`
		fmt.Fprintf(w, "%s", b.String()) // want `fmt.Fprintf(w, "%s", b.String()) => w.Write(b.Bytes())`
		fmt.Fprintf(w, "%v", b.String()) // want `fmt.Fprintf(w, "%v", b.String()) => w.Write(b.Bytes())`
	}

	{
		var buf1 bytes.Buffer
		var buf2 bytes.Buffer
		_ = strings.Compare(buf1.String(), buf2.String())   // want `strings.Compare(buf1.String(), buf2.String()) => bytes.Compare(buf1.Bytes(), buf2.Bytes())`
		_ = strings.Contains(buf1.String(), buf2.String())  // want `strings.Contains(buf1.String(), buf2.String()) => bytes.Contains(buf1.Bytes(), buf2.Bytes())`
		_ = strings.HasPrefix(buf1.String(), buf2.String()) // want `strings.HasPrefix(buf1.String(), buf2.String()) => bytes.HasPrefix(buf1.Bytes(), buf2.Bytes())`
		_ = strings.HasSuffix(buf1.String(), buf2.String()) // want `strings.HasSuffix(buf1.String(), buf2.String()) => bytes.HasSuffix(buf1.Bytes(), buf2.Bytes())`
		_ = strings.EqualFold(buf1.String(), buf2.String()) // want `strings.EqualFold(buf1.String(), buf2.String()) => bytes.EqualFold(buf1.Bytes(), buf2.Bytes())`
		_ = buf1.Bytes()
		_ = buf2.Bytes()
	}

	{
		var buf1 bytes.Buffer
		_ = strings.Contains(buf1.String(), "foo")  // want `strings.Contains(buf1.String(), "foo") => bytes.Contains(buf1.Bytes(), []byte("foo"))`
		_ = strings.HasPrefix(buf1.String(), "foo") // want `trings.HasPrefix(buf1.String(), "foo") => bytes.HasPrefix(buf1.Bytes(), []byte("foo"))`
		_ = strings.HasSuffix(buf1.String(), "foo") // want `strings.HasSuffix(buf1.String(), "foo") => bytes.HasSuffix(buf1.Bytes(), []byte("foo"))`
		_ = strings.EqualFold(buf1.String(), "foo") // want `strings.EqualFold(buf1.String(), "foo") => bytes.EqualFold(buf1.Bytes(), []byte("foo"))`
		_ = buf1.Bytes()
	}
}

func Ignore(buf *bytes.Buffer, s string, b []byte) {
	_ = bytes.Contains(buf.Bytes(), b)
	_ = bytes.HasPrefix(buf.Bytes(), b)
	_ = bytes.HasSuffix(buf.Bytes(), b)
	_ = bytes.Count(buf.Bytes(), b)
	_ = bytes.Index(buf.Bytes(), b)

	_ = bytes.Contains(buf.Bytes(), []byte(s))
	_ = bytes.HasPrefix(buf.Bytes(), []byte(s))
	_ = bytes.HasSuffix(buf.Bytes(), []byte(s))
	_ = bytes.Count(buf.Bytes(), []byte(s))
	_ = bytes.Index(buf.Bytes(), []byte(s))

	_ = buf.Bytes()
	_ = []byte(buf.Bytes())

	{
		var w io.Writer
		b := &bytes.Buffer{}
		w.Write(b.Bytes())
	}

	{
		var buf1 bytes.Buffer
		var buf2 bytes.Buffer
		_ = bytes.Compare(buf1.Bytes(), buf2.Bytes())
		_ = bytes.Contains(buf1.Bytes(), buf2.Bytes())
		_ = bytes.HasPrefix(buf1.Bytes(), buf2.Bytes())
		_ = bytes.HasSuffix(buf1.Bytes(), buf2.Bytes())
		_ = bytes.EqualFold(buf1.Bytes(), buf2.Bytes())
	}

	{
		var buf1 bytes.Buffer
		_ = bytes.Contains(buf1.Bytes(), []byte("foo"))
		_ = bytes.HasPrefix(buf1.Bytes(), []byte("foo"))
		_ = bytes.HasSuffix(buf1.Bytes(), []byte("foo"))
		_ = bytes.EqualFold(buf1.Bytes(), []byte("foo"))
	}
}


<|start_filename|>perfguard/checkers/checkers.go<|end_filename|>
package checkers

import (
	"fmt"
	"go/ast"

	"github.com/quasilyte/go-perfguard/perfguard/lint"
)

type callCheckerInfo struct {
	doc Doc
	new func() CallChecker
}

type stmtCheckerInfo struct {
	doc Doc
	new func() StmtChecker
}

type funcCheckerInfo struct {
	doc Doc
	new func() FuncChecker
}

var (
	callCheckers = make(map[string]callCheckerInfo)
	stmtCheckers = make(map[string]stmtCheckerInfo)
	funcCheckers = make(map[string]funcCheckerInfo)
)

type Doc struct {
	Name  string
	Score int

	OptLevel int

	NeedsProfile bool
}

type CallChecker interface {
	CheckCall(ctx *lint.Context, call *ast.CallExpr) error
}

type StmtChecker interface {
	CheckStmt(ctx *lint.Context, stmt ast.Stmt) error
}

type FuncChecker interface {
	CheckFunc(ctx *lint.Context, body *ast.BlockStmt) error
}

type PackageChecker interface {
	CheckPackage(ctx *lint.SharedContext, files []lint.SourceFile) error
}

func RegisterCallChecker(doc Doc, constructor func() CallChecker) {
	if _, ok := callCheckers[doc.Name]; ok {
		panic(fmt.Sprintf("%s call checker is already registered", doc.Name))
	}
	callCheckers[doc.Name] = callCheckerInfo{
		doc: doc,
		new: constructor,
	}
}

func RegisterStmtChecker(doc Doc, constructor func() StmtChecker) {
	if _, ok := stmtCheckers[doc.Name]; ok {
		panic(fmt.Sprintf("%s stmt checker is already registered", doc.Name))
	}
	stmtCheckers[doc.Name] = stmtCheckerInfo{
		doc: doc,
		new: constructor,
	}
}

func RegisterFuncChecker(doc Doc, constructor func() FuncChecker) {
	if _, ok := funcCheckers[doc.Name]; ok {
		panic(fmt.Sprintf("%s func checker is already registered", doc.Name))
	}
	funcCheckers[doc.Name] = funcCheckerInfo{
		doc: doc,
		new: constructor,
	}
}

func minHeatLevel(doc *Doc) int {
	if doc.OptLevel == 2 {
		return 5
	}
	return 1
}

func Create(filter func(doc Doc) bool) []PackageChecker {
	callChecker := &callcheckerWalker{}
	for _, c := range callCheckers {
		if filter(c.doc) {
			callChecker.checkers = append(callChecker.checkers, callcheckerWithContext{
				ctx: lint.NewContext(c.doc.Name, minHeatLevel(&c.doc)),
				obj: c.new(),
			})
		}
	}

	stmtChecker := &stmtcheckerWalker{}
	for _, c := range stmtCheckers {
		if filter(c.doc) {
			stmtChecker.checkers = append(stmtChecker.checkers, stmtcheckerWithContext{
				ctx: lint.NewContext(c.doc.Name, minHeatLevel(&c.doc)),
				obj: c.new(),
			})
		}
	}

	funcChecker := &funccheckerWalker{}
	for _, c := range funcCheckers {
		if filter(c.doc) {
			funcChecker.checkers = append(funcChecker.checkers, funccheckerWithContext{
				ctx: lint.NewContext(c.doc.Name, minHeatLevel(&c.doc)),
				obj: c.new(),
			})
		}
	}

	var result []PackageChecker
	result = append(result,
		callChecker,
		stmtChecker,
		funcChecker)

	return result
}


<|start_filename|>cmd/perfguard/checkers_test.go<|end_filename|>
package main

import (
	"path/filepath"
	"testing"
)

func TestCheckers(t *testing.T) {
	checkers := readdir(t, filepath.Join("testdata", "checkerstest"))
	for _, name := range checkers {
		key := filepath.Base(name)
		runLintTest(t, "checkerstest", key)
	}
}


<|start_filename|>cmd/perfguard/testdata/rulestest/regexpStringCopyElim/regexpStringCopyElim.go<|end_filename|>
package rulestest

import (
	"regexp"
)

func Warn() {
	var s string
	var b []byte
	var p []byte

	re := regexp.MustCompile(`\w+`)

	_ = re.Match([]byte(s))            // want `re.Match([]byte(s)) => re.MatchString(s)`
	_ = re.FindIndex([]byte(s))        // want `re.FindIndex([]byte(s)) => re.FindStringIndex(s)`
	_ = re.FindAllIndex([]byte(s), -1) // want `re.FindAllIndex([]byte(s), -1) => re.FindAllStringIndex(s, -1)`

	_ = string(re.ReplaceAll([]byte(s), []byte("foo"))) // want `string(re.ReplaceAll([]byte(s), []byte("foo"))) => re.ReplaceAllString(s, "foo")`

	_ = re.MatchString(string(b))            // want `re.MatchString(string(b)) => re.Match(b)`
	_ = re.FindStringIndex(string(b))        // want `re.FindStringIndex(string(b)) => re.FindIndex(b)`
	_ = re.FindAllStringIndex(string(b), -1) // want `re.FindAllStringIndex(string(b), -1) => re.FindAllIndex(b, -1)`

	_ = []byte(re.ReplaceAllString(string(b), string(p))) // want `[]byte(re.ReplaceAllString(string(b), string(p))) => re.ReplaceAll(b, p)`

	{
		var content []byte
		var targetPath string
		content = []byte(re.ReplaceAllString(string(content), targetPath)) // want `[]byte(re.ReplaceAllString(string(content), targetPath)) => re.ReplaceAll(content, []byte(targetPath))`
	}
	{
		var content string
		var targetPath []byte
		content = string(re.ReplaceAll([]byte(content), targetPath)) // want `string(re.ReplaceAll([]byte(content), targetPath)) => re.ReplaceAllString(content, string(targetPath))`
	}
}

func Ignore() {
	re := regexp.MustCompile(`\w+`)

	{
		var s string
		var b []byte
		_ = re.MatchString(s)
		_ = re.FindStringIndex(s)
		_ = re.FindAllStringIndex(s, -1)
		_ = re.Match(b)
		_ = re.FindIndex(b)
		_ = re.FindAllIndex(b, -1)
	}

	{
		var content []byte
		var targetPath string
		content = re.ReplaceAll(content, []byte(targetPath))
	}
	{
		var content string
		var targetPath []byte
		content = re.ReplaceAllString(content, string(targetPath))
	}
}


<|start_filename|>internal/imports/imports.go<|end_filename|>
package imports

// Fix tries to fix imports from a Go file source code.
//
// It only works with src that can be parsed by a Go parser,
// otherwise a parsing error will be returned.
//
// We perform these operations:
//	1. Remove unused imports from a file.
//	2. Add missing imports.
//
// It does not introduce any formatting changes, unless strictly necessary.
func Fix(config FixConfig, src []byte) ([]byte, error) {
	f := newFixer(config)
	return f.Fix(src)
}

type FixConfig struct {
	StdlibPackages map[string]string
	Packages       map[string]string
}


<|start_filename|>internal/imports/fix_test.go<|end_filename|>
package imports

import (
	"bytes"
	"fmt"
	"strings"
	"testing"

	"github.com/google/go-cmp/cmp"
)

func TestFix(t *testing.T) {
	tests := []struct {
		before string
		after  string
	}{
		// Add a single import to a file without imports.
		{`
package example

func f() string {
	return strings.Repeat("foo", 2)
}`, `
package example

import (
	"strings"
)

func f() string {
	return strings.Repeat("foo", 2)
}`,
		},

		// Add two imports to a file without imports.
		{`
package example

func f() string {
	return strings.Repeat("foo", 2)
}
func g() []byte {
	return bytes.Repeat([]byte("foo"), 2)
}`, `
package example

import (
	"bytes"
	"strings"
)

func f() string {
	return strings.Repeat("foo", 2)
}
func g() []byte {
	return bytes.Repeat([]byte("foo"), 2)
}`,
		},

		// Add two imports to a file with imports in () group.
		{`
package example

import (
	"io"
)

func f(r io.Reader) (string, []byte) {
	return strings.Repeat("foo", 2),
           bytes.Repeat(nil, 2)
}`, `
package example

import (
	"io"
	"bytes"
	"strings"
)

func f(r io.Reader) (string, []byte) {
	return strings.Repeat("foo", 2),
           bytes.Repeat(nil, 2)
}`,
		},

		// Add two imports to a file with 1 unit import.
		{`
package example

import "io"

func f(r io.Reader) (string, []byte) {
	return strings.Repeat("foo", 2),
           bytes.Repeat(nil, 2)
}`, `
package example

import (
	"io"
	"bytes"
	"strings"
)

func f(r io.Reader) (string, []byte) {
	return strings.Repeat("foo", 2),
           bytes.Repeat(nil, 2)
}`,
		},

		// Add one import to a file with 1 unit import.
		{`
package example

import "io"

func f(r io.Reader) string {
	return strings.Repeat("foo", 2)
}`, `
package example

import (
	"io"
	"strings"
)

func f(r io.Reader) string {
	return strings.Repeat("foo", 2)
}`,
		},

		// Add and remove one.
		{`
package example

import "io"

func f() string { return strings.Repeat("foo", 2)
}`, `
package example

import (
	"strings"
)

func f() string { return strings.Repeat("foo", 2)
}`,
		},

		// Removed all imports.
		{`
package example

import "io"

func f() string { return "" }`, `
package example


func f() string { return "" }`,
		},

		// Removed all imports.
		{`
package example

import "io"

import "bytes"

func f() string { return "" }`, `
package example



func f() string { return "" }`,
		},

		// Removed all imports.
		{`
package example

// Comment1
import "io"
// Comment2
import "bytes"
// Comment3

func f() string { return "" }`, `
package example

// Comment1
// Comment2
// Comment3

func f() string { return "" }`,
		},

		// Removed all imports.
		{`
package example

import ("io")
import ("bytes")

func f() string { return "" }`, `
package example


func f() string { return "" }`,
		},

		// Removed all imports.
		{`
package example//OK

import (
	"io"
)
import (
	"bytes"
)

func f() string { return "" }`, `
package example//OK


func f() string { return "" }`,
		},

		// Removed two, added one.
		{`
package example

import (
	"io"
)
import (
	"bytes"
)

func f() int { return rand.Intn(10) }`, `
package example

import (
	"math/rand"
)

func f() int { return rand.Intn(10) }`,
		},

		// Add package from non-std list.
		{`
package example // OK

func f() error {
	return errors.New("ok")
}`, `
package example 
import (
	"github.com/pkg/errors"
)
// OK

func f() error {
	return errors.New("ok")
}`,
		},

		// Remove package from non-std list.
		{`
package example

import (
	"github.com/pkg/errors"
)

func f() string {
	return ""
}`, `
package example


func f() string {
	return ""
}`,
		},

		// Remove renamed package import.
		{`
package example

// This is a comment.
import (
	xerrors "github.com/pkg/errors"
)

func f() string {
	return ""
}`, `
package example

// This is a comment.

func f() string {
	return ""
}`,
		},

		// Do not remove package that is used via local name.
		{`
package example

import (
	pkgerrors "github.com/pkg/errors"
)

func f() error {
	return pkgerrors.New("ok")
}`, `
package example

import (
	pkgerrors "github.com/pkg/errors"
)

func f() error {
	return pkgerrors.New("ok")
}`,
		},

		// Do not remove "_" packages.
		{`
package example

import (
	_ "image/png"
)
import (
	"bytes"
)

func f() int { return rand.Intn(10) }`, `
package example

import (
	_ "image/png"
	"math/rand"
)

func f() int { return rand.Intn(10) }`,
		},

		// No changes in imports: 0 imports.
		{`
package example

func f() int { return 10 }`, `
package example

func f() int { return 10 }`,
		},

		// No changes in imports: 1 import.
		{`
package example

import "io"

func f(r io.Reader) int { return 10 }`, `
package example

import "io"

func f(r io.Reader) int { return 10 }`,
		},

		// No changes in imports: 1 import.
		{`
package example

import (
	"io"
)

func f(r io.Reader) int { return 10 }`, `
package example

import (
	"io"
)

func f(r io.Reader) int { return 10 }`,
		},

		// No changes in imports: 2 imports.
		{`
package example

import (
	"io"
	"strings"
)

func f(r io.Reader) string { return strings.Repeat("x", 10) }`, `
package example

import (
	"io"
	"strings"
)

func f(r io.Reader) string { return strings.Repeat("x", 10) }`,
		},

		// Add second entry due to the first one being renamed.
		{`
package example

import (
	"io"
	str "strings"
)

func f(r io.Reader) string { return str.Repeat("x", 10) }
func f2(r io.Reader) string { return strings.Repeat("x", 10) }`, `
package example

import (
	"io"
	str "strings"
	"strings"
)

func f(r io.Reader) string { return str.Repeat("x", 10) }
func f2(r io.Reader) string { return strings.Repeat("x", 10) }`,
		},

		// Try not to discard the imports grouping.
		{`
package example

import (
	"io"

	"github.com/quasilyte/go-perfguard/otherlib"

	"github.com/quasilyte/lib"
)

func f(x otherlib.T, y lib.T, r io.Reader, b *strings.Builder) {}`, `
package example

import (
	"io"
	"strings"

	"github.com/quasilyte/go-perfguard/otherlib"

	"github.com/quasilyte/lib"
)

func f(x otherlib.T, y lib.T, r io.Reader, b *strings.Builder) {}`,
		},

		// Try not to discard the imports grouping.
		{`
package example

import (
	"io"
)

import (
	"github.com/quasilyte/go-perfguard/otherlib"

	"github.com/quasilyte/lib"
)

func f(x otherlib.T, y lib.T, r io.Reader, b *strings.Builder) {}`, `
package example

import (
	"io"
	"strings"
)

import (
	"github.com/quasilyte/go-perfguard/otherlib"

	"github.com/quasilyte/lib"
)

func f(x otherlib.T, y lib.T, r io.Reader, b *strings.Builder) {}`,
		},

		// Try not to add extra () for unchanged import decls.
		{`
package example

import (
	"io"
)

import "github.com/quasilyte/go-perfguard/otherlib"

func f(x otherlib.T, y lib.T, r io.Reader, b *strings.Builder) {}`, `
package example

import (
	"io"
	"strings"
)

import "github.com/quasilyte/go-perfguard/otherlib"

func f(x otherlib.T, y lib.T, r io.Reader, b *strings.Builder) {}`,
		},

		// Do not inject anything to standalone import C decls.
		{`
package example

// Some C-related comment.
// Should not be removed.
import "C"

import (
	"io"
)

func f(r io.Reader, b *strings.Builder) {}`, `
package example

// Some C-related comment.
// Should not be removed.
import "C"

import (
	"io"
	"strings"
)

func f(r io.Reader, b *strings.Builder) {}`,
		},

		// Try not to lose associated comments.
		{`
package example

// Comment 1.
import "github.com/quasilyte/go-perfguard/otherlib"

// Comment 2.
import (
	// Comment 3.
	"io" // Comment 4.
)

func f(x otherlib.T, y lib.T, r io.Reader, b *strings.Builder) {}`, `
package example

// Comment 1.
import (
	"github.com/quasilyte/go-perfguard/otherlib"
	"strings"
)

// Comment 2.
import (
	// Comment 3.
	"io" // Comment 4.
)

func f(x otherlib.T, y lib.T, r io.Reader, b *strings.Builder) {}`,
		},

		// Try not to lose associated comments.
		{`
package example

// Comment 1.
import "github.com/quasilyte/go-perfguard/otherlib"

// Comment 2.
// Comment 3.
import (
	/* Comment 4. */
	"io" /* Comment 5. */
)

func f(x otherlib.T, y lib.T, r io.Reader, b *strings.Builder) {}`, `
package example

// Comment 1.
import (
	"github.com/quasilyte/go-perfguard/otherlib"
	"strings"
)

// Comment 2.
// Comment 3.
import (
	/* Comment 4. */
	"io" /* Comment 5. */
)

func f(x otherlib.T, y lib.T, r io.Reader, b *strings.Builder) {}`,
		},

		// Replace a single import.
		// TODO: do not insert '(' ')' here?
		{`
package example

import "fmt"

func f(x int) string { return strconv.Itoa(v) }`, `
package example

import (
	"strconv"
)

func f(x int) string { return strconv.Itoa(v) }`,
		},
	}

	for i := range tests {
		test := tests[i]
		t.Run(fmt.Sprintf("test%d", i), func(t *testing.T) {
			config := FixConfig{
				StdlibPackages: map[string]string{
					"strings": "strings",
					"bytes":   "bytes",
					"rand":    "math/rand",
					"errors":  "errors",
					"io":      "io",
					"strconv": "strconv",
				},
				Packages: map[string]string{
					"errors": "github.com/pkg/errors",
				},
			}
			fixed, err := Fix(config, []byte(test.before))
			if err != nil {
				t.Fatal(err)
			}
			have := string(bytes.TrimSpace(fixed))
			want := strings.TrimSpace(test.after)
			if diff := cmp.Diff(have, want); diff != "" {
				t.Errorf("results mismatches (+want -have):\n%s", diff)
				fmt.Println("Before:")
				fmt.Println(test.before)
				fmt.Println("After:")
				fmt.Println(have)
			}
		})
	}
}


<|start_filename|>internal/goutil/goutil.go<|end_filename|>
package goutil

import (
	"go/ast"
	"go/types"
)

func TypeHasPointers(typ types.Type) bool {
	switch typ := typ.(type) {
	case *types.Basic:
		switch typ.Kind() {
		case types.UnsafePointer, types.String, types.UntypedNil, types.UntypedString:
			return true
		}
		return false

	case *types.Named:
		return TypeHasPointers(typ.Underlying())

	case *types.Struct:
		for i := 0; i < typ.NumFields(); i++ {
			if TypeHasPointers(typ.Field(i).Type()) {
				return true
			}
		}
		return false

	case *types.Array:
		return TypeHasPointers(typ.Elem())

	default:
		return true
	}
}

func ContainsIdent(root ast.Node, x *ast.Ident) bool {
	return Contains(root, func(n ast.Node) bool {
		if y, ok := n.(*ast.Ident); ok {
			return x.Name == y.Name
		}
		return false
	})
}

func Contains(root ast.Node, fn func(n ast.Node) bool) bool {
	found := false
	ast.Inspect(root, func(n ast.Node) bool {
		if found {
			return false
		}
		if fn(n) {
			found = true
			return false
		}
		return true
	})
	return found
}


<|start_filename|>cmd/perfguard/testdata/quickfix/boolValuedMap/out/after.go<|end_filename|>
package main

import "sort"

func main() {
	stringsX := []string{
		"",
		"a",
		"str",
		"hello",
		"world",
		"str",
		"a",
	}
	stringsY := []string{
		"hello",
		"world",
	}

	println(intersectionLen(stringsX, stringsY))
	println(intersectionLen(stringsX, stringsX))
	for _, s := range uniq(stringsX) {
		println(s)
	}
	for _, s := range uniq(stringsY) {
		println(s)
	}
	for _, s := range keep(stringsX, stringsY) {
		println(s)
	}
	for _, s := range keep(stringsY, stringsX) {
		println(s)
	}
	for _, s := range keep(stringsX, stringsX) {
		println(s)
	}
}

func uniq(xs []string) []string {
	set := make(map[string]struct{}, len(xs)) // want `change map[T]bool to map[T]struct{}`
	for _, x := range xs {
		set[x] = struct{}{}
	}
	result := make([]string, 0, len(set))
	for k := range set {
		result = append(result, k)
	}
	sort.Strings(result)
	return result
}

func intersectionLen(xs, ys []string) int {
	intersection := make(map[string]struct{}, len(xs)) // want `change map[T]bool to map[T]struct{}`
	for _, x := range xs {
		intersection[x] = struct{}{}
	}
	for _, y := range ys {
		intersection[y] = struct{}{}
	}
	return len(intersection)
}

func keep(orig, allowlist []string) []string {
	set := make(map[string]struct{}, len(allowlist))
	for _, s := range allowlist {
		set[s] = struct{}{}
	}
	var result []string
	for _, s := range orig {
		if _, ok := set[s]; !ok {
			continue
		}
		result = append(result, s)
	}
	return result
}


<|start_filename|>cmd/perfguard/testdata/quickfix/mapClear/out/after.go<|end_filename|>
package main

type withMap struct {
	m map[int]string
}

func main() {
	o := &withMap{
		m: map[int]string{},
	}

	o.m[40] = "40"
	o.m[20] = "20"
	println(len(o.m))
	clearMap(o)
	println(len(o.m))
	o.m[1] = "1"
	println(len(o.m))
	clearMap(o)
	println(len(o.m))
}

func clearMap(o *withMap) {
	for k := range o.m {
		delete(o.m, k)
	}
}


<|start_filename|>perfguard/_rules/opt_rules.go<|end_filename|>
package gorules

import (
	"github.com/quasilyte/go-ruleguard/dsl"
)

//doc:summary Detects string concat in hot paths
//doc:tags    o2 score5
func stringConcatAssign(m dsl.Matcher) {
	m.Match(`$s += $_`).
		Where(m["s"].Type.Is(`string`)).
		Report(`string concat on the hot path`)
}

//doc:summary Detects regexp compilation on hot execution paths
//doc:tags    o1 score4
func regexpCompile(m dsl.Matcher) {
	// TODO: for constant string patterns we can move the regexp compilation
	// to a global scope and use compiled var on the original call site.
	// But this can't be done by the rules.

	m.Match(
		// Explicit compilation.
		`regexp.Compile($*_)`,
		`regexp.MustCompile($*_)`,
		`regexp.CompilePOSIX($*_)`,
		`regexp.MustCompilePOSIX($*_)`,
		// Implicit compilation - these calls do a compile per call without any cache.
		`regexp.Match($*_)`,
		`regexp.MatchString($*_)`,
		`regexp.MatchReader($*_)`,
	).Report(`regexp compilation should be avoided on the hot paths`)
}

//doc:summary Detects sprint calls that can be rewritten as a string concat
//doc:tags    o2 score2
func sprintfConcat2(m dsl.Matcher) {
	// It's impractical to implement this kind of analysis via the rules.
	// I've added a few most common patterns here just in case, but
	// we need to make a generalized form of this optimization later.

	m.Match(`fmt.Sprintf("%s=%s", $x, $y)`).
		Where(m["x"].Type.Is(`string`) && m["y"].Type.Is(`string`)).
		Suggest(`$x + "=" + $y`)

	m.Match(`fmt.Sprintf("%s.%s", $x, $y)`).
		Where(m["x"].Type.Is(`string`) && m["y"].Type.Is(`string`)).
		Suggest(`$x + "." + $y`)

	m.Match(`fmt.Sprintf("%s/%s", $x, $y)`).
		Where(m["x"].Type.Is(`string`) && m["y"].Type.Is(`string`)).
		Suggest(`$x + "/" + $y`)

	m.Match(`fmt.Sprintf("%s:%s", $x, $y)`).
		Where(m["x"].Type.Is(`string`) && m["y"].Type.Is(`string`)).
		Suggest(`$x + ":" + $y`)

	m.Match(`fmt.Sprintf("%s: %s", $x, $y)`).
		Where(m["x"].Type.Is(`string`) && m["y"].Type.Is(`string`)).
		Suggest(`$x + ": " + $y`)
}

//doc:summary Detects Write calls that should be rewritten as io.WriteString
//doc:tags    o2 score3
//doc:before  w.Write([]byte(s))
//doc:after   io.WriteString(w, s)
func writeString2(m dsl.Matcher) {
	m.Match(`$w.Write([]byte($s))`).
		Where(m["w"].Type.Is("io.Writer") && m["s"].Type.Is(`string`) && m["s"].Const).
		Suggest("io.WriteString($w, $s)")
}

//doc:summary Detects range loops that copy large value on every iteration
//doc:tags    o1 score2
func rangeValueCopy(m dsl.Matcher) {
	// TODO: move to a hand-written checker so we can provide a quickfix for this.
	m.Match(`for $_, $v := range $_`, `for $_, $v = range $_`).
		Where(m["v"].Type.Size > 128).
		Report(`every iteration copies a large object into $v`)
}

//doc:summary Detects errors.New that can be allocated exactly once
//doc:tags    o1 score3
func constErrorNew(m dsl.Matcher) {
	m.Match(`errors.New($x)`).
		Where(m["x"].Const).
		Report(`errors with const message can be a global var, allocated only once`)
}


<|start_filename|>perfguard/checkers/callcheckers/bigArgCopy.go<|end_filename|>
package callcheckers

import (
	"fmt"
	"go/ast"
	"go/types"

	"github.com/quasilyte/go-perfguard/internal/goutil"
	"github.com/quasilyte/go-perfguard/perfguard/checkers"
	"github.com/quasilyte/go-perfguard/perfguard/lint"
)

func init() {
	doc := checkers.Doc{
		Name:  "bigArgCopy",
		Score: 2,
	}
	checkers.RegisterCallChecker(doc, func() checkers.CallChecker {
		return &bigArgCopyChecker{}
	})
}

type bigArgCopyChecker struct{}

func (c *bigArgCopyChecker) CheckCall(ctx *lint.Context, call *ast.CallExpr) error {
	c.checkRecv(ctx, call)

	if len(call.Args) == 0 {
		return nil
	}
	fnType, ok := ctx.TypeOf(call.Fun).(*types.Signature)
	if !ok {
		return nil
	}
	numParams := fnType.Params().Len()
	for i, arg := range call.Args {
		if i >= numParams {
			break
		}
		param := fnType.Params().At(i)
		if param.Name() == "" || param.Name() == "_" {
			continue
		}
		if !c.isBig(ctx, param.Type()) {
			continue
		}
		ctx.Report(lint.ReportParams{
			PosNode: arg,
			Message: fmt.Sprintf("expensive %s arg copy (%d bytes), consider passing it by pointer",
				param.Name(), ctx.Target.Sizes.Sizeof(param.Type())),
			UseFlatSamples: true,
		})
	}

	return nil
}

func (c *bigArgCopyChecker) checkRecv(ctx *lint.Context, call *ast.CallExpr) {
	methodExpr, ok := call.Fun.(*ast.SelectorExpr)
	if !ok {
		return
	}
	arg, ok := methodExpr.X.(*ast.Ident)
	if !ok {
		return
	}
	obj := ctx.VarOf(arg)
	typ := obj.Type()
	if ptr, ok := typ.(*types.Pointer); ok {
		typ = ptr.Elem()
	}
	named, ok := typ.(*types.Named)
	if !ok {
		return
	}
	// TODO: is there a better way to find the method?
	// types.LookupFieldOrMethod does linear search.
	result, _, _ := types.LookupFieldOrMethod(named, true, ctx.Target.Pkg, methodExpr.Sel.Name)
	methodObject, ok := result.(*types.Func)
	if !ok {
		return
	}
	methodType := methodObject.Type().(*types.Signature)
	recv := methodType.Recv()
	if recv == nil {
		return
	}
	if !c.isBig(ctx, recv.Type()) {
		return
	}
	ctx.Report(lint.ReportParams{
		PosNode: arg,
		Message: fmt.Sprintf("expensive %s receiver copy (%d bytes), consider passing it by pointer",
			arg.Name, ctx.Target.Sizes.Sizeof(recv.Type())),
		UseFlatSamples: true,
	})
}

func (c *bigArgCopyChecker) isBig(ctx *lint.Context, typ types.Type) bool {
	wordSize := ctx.Target.Sizes.Sizeof(types.Typ[types.Uint])
	size := ctx.Target.Sizes.Sizeof(typ)
	numWords := size / wordSize
	if goutil.TypeHasPointers(typ) {
		return numWords > 32
	}
	return numWords > 64
}


<|start_filename|>cmd/perfguard/testdata/rulestest/stringsJoinConcat/stringsJoinConcat.go<|end_filename|>
package rulestest

import "strings"

func Warn(x, y, z, s1, s2 string) {
	_ = strings.Join([]string{x, y}, "_") // want `strings.Join([]string{x, y}, "_") => x + "_" + y`

	_ = strings.Join([]string{y, x}, "")    // want `strings.Join([]string{y, x}, "") => y + x`
	_ = strings.Join([]string{x, y, z}, "") // want `strings.Join([]string{x, y, z}, "") => x + y + z`

	_ = strings.Join([]string{s1, s2}, concat("<", ">")) // want `strings.Join([]string{s1, s2}, concat("<", ">")) => s1 + concat("<", ">") + s2`
}

func Ignore(s1, s2 string) {
	_ = s1 + s2
	_ = s1 + "_" + s2
	_ = strings.Join([]string{s1, s2, s1}, concat("<", ">"))
	_ = strings.Join([]string{"x", "y"}, "")
	_ = strings.Join([]string{"x", "y"}, "|")
	_ = strings.Join([]string{"x", "y", "z"}, "|")
}

func concat(x, y string) string {
	return x + y
}


<|start_filename|>internal/testfile/testfile.go<|end_filename|>
package testfile

import (
	"fmt"
	"strings"
)

type Annotation struct {
	Filename string
	Line     int
	Text     string
}

func Parse(filename string, data []byte) ([]Annotation, error) {
	var result []Annotation

	lines := strings.Split(string(data), "\n")
	for i, l := range lines {
		if l == "" {
			continue
		}
		lineNum := i + 1
		start := strings.Index(l, "// want `")
		if start == -1 {
			continue
		}
		s := l[start+len("// want `"):]
		end := strings.IndexByte(s, '`')
		if start == -1 {
			return nil, fmt.Errorf("line %d: can't find closing `", lineNum)
		}
		s = s[:end]
		result = append(result, Annotation{
			Filename: filename,
			Line:     lineNum,
			Text:     s,
		})
	}

	return result, nil
}


<|start_filename|>cmd/perfguard/testdata/rulestest/sliceSelfCopy/sliceSelfCopy.go<|end_filename|>
package rulestest

func Warn(b []byte) {
	{
		keep := 10
		from := 20
		for i := 0; i < keep; i++ { // want `for ... { ... } => copy(b[:keep], b[from:])`
			b[i] = b[from+i]
		}
	}
}

func Ignore(b []byte, a [100]int) {
	{
		keep := 10
		from := 20
		copy(b[:keep], b[from:])
	}

	{
		keep := 10
		from := 20
		for i := 0; i < keep; i++ {
			a[i] = a[from+i]
		}
	}
}


<|start_filename|>cmd/perfguard/testdata/quickfix/strconv/before.go<|end_filename|>
package main

import "fmt"

func main() {
	intTests := []int{0, 1, -1, 295}
	for _, v := range intTests {
		println(fmt.Sprintf("%d", v))
		println(fmt.Sprintf("%v", v))
		println(fmt.Sprint(v))
	}

	int64Tests := []int64{0, 1, -1, 295, 45032}
	for _, v := range int64Tests {
		println(fmt.Sprintf("%d", v))
		println(fmt.Sprintf("%v", v))
		println(fmt.Sprintf("%x", v))
		println(fmt.Sprint(v))
	}

	uint64Tests := []uint64{0, 1, 295, 45032}
	for _, v := range uint64Tests {
		println(fmt.Sprintf("%d", v))
		println(fmt.Sprintf("%v", v))
		println(fmt.Sprintf("%x", v))
		println(fmt.Sprint(v))
	}
}


<|start_filename|>cmd/perfguard/testdata/rulestest/binaryWrite/binaryWrite.go<|end_filename|>
package rulestest

import (
	"bytes"
	"encoding/binary"
	"io"
	"strings"
)

func Warn(b []byte, s string) {
	{
		var w io.Writer
		binary.Write(w, binary.LittleEndian, b) // want `binary.Write(w, binary.LittleEndian, b) => w.Write(b)`
		binary.Write(w, binary.BigEndian, b)    // want `binary.Write(w, binary.BigEndian, b) => w.Write(b)`
	}

	{
		var buf bytes.Buffer
		binary.Write(&buf, binary.LittleEndian, b) // want `binary.Write(&buf, binary.LittleEndian, b) => buf.Write(b)`
		binary.Write(&buf, binary.BigEndian, b)    // want `binary.Write(&buf, binary.BigEndian, b) => buf.Write(b)`
	}

	{
		var buf bytes.Buffer
		binary.Write(&buf, binary.LittleEndian, s) // want `binary.Write(&buf, binary.LittleEndian, s) => buf.WriteString(s)`
		binary.Write(&buf, binary.BigEndian, s)    // want `binary.Write(&buf, binary.BigEndian, s) => buf.WriteString(s)`
	}

	{
		buf := &bytes.Buffer{}
		binary.Write(buf, binary.LittleEndian, b) // want `binary.Write(buf, binary.LittleEndian, b) => buf.Write(b)`
		binary.Write(buf, binary.BigEndian, b)    // want `binary.Write(buf, binary.BigEndian, b) => buf.Write(b)`
	}

	{
		var w io.Writer
		if err := binary.Write(w, binary.LittleEndian, b); err != nil { // want `err := binary.Write(w, binary.LittleEndian, b) => _, err := w.Write(b)`
			panic(err)
		}
		if err := binary.Write(w, binary.BigEndian, b); err != nil { // want `err := binary.Write(w, binary.BigEndian, b) => _, err := w.Write(b)`
			panic(err)
		}
	}

	{
		w := &bytes.Buffer{}
		if err := binary.Write(w, binary.LittleEndian, s); err != nil { // want `err := binary.Write(w, binary.LittleEndian, s) => _, err := w.WriteString(s)`
			panic(err)
		}
		if err := binary.Write(w, binary.BigEndian, s); err != nil { // want `err := binary.Write(w, binary.BigEndian, s) => _, err := w.WriteString(s)`
			panic(err)
		}
	}

	{
		var w io.Writer
		err1 := binary.Write(w, binary.LittleEndian, b) // want `err1 := binary.Write(w, binary.LittleEndian, b) => _, err1 := w.Write(b)`
		err2 := binary.Write(w, binary.BigEndian, b)    // want `err2 := binary.Write(w, binary.BigEndian, b) => _, err2 := w.Write(b)`
		if err1 != nil {
			panic(err1)
		}
		if err2 != nil {
			panic(err2)
		}
	}
}

func Ignore(b []byte, s string) {
	{
		var w io.Writer
		w.Write(b)
	}

	{
		var buf bytes.Buffer
		(&buf).Write(b)
	}

	{
		buf := bytes.NewBuffer(make([]byte, 10))
		buf.Write(b)
	}

	{
		var w io.Writer
		if _, err := w.Write(b); err != nil {
			panic(err)
		}
		if _, err := w.Write(b); err != nil {
			panic(err)
		}
	}

	{
		var w io.Writer
		_, err1 := w.Write(b)
		_, err2 := w.Write(b)
		if err1 != nil {
			panic(err1)
		}
		if err2 != nil {
			panic(err2)
		}
	}

	{
		var w io.Writer
		binary.Write(w, binary.LittleEndian, 29)
		binary.Write(w, binary.BigEndian, 29)
	}

	{
		var i int
		buf := bytes.Buffer{}
		binary.Write(&buf, binary.LittleEndian, uint32(i))
		binary.Write(&buf, binary.BigEndian, uint32(i))
	}

	{
		var buf strings.Builder
		buf.WriteString("hello, ")
		buf.WriteString("world")
		println(buf.String())
	}

	{
		var w io.Writer
		if err := binary.Write(w, binary.LittleEndian, s); err != nil {
			panic(err)
		}
		if err := binary.Write(w, binary.BigEndian, s); err != nil {
			panic(err)
		}
	}
}

func checkError(err error) {}


<|start_filename|>perfguard/lint/lint.go<|end_filename|>
package lint

import (
	"go/ast"
	"go/token"
	"go/types"
	"time"
)

type Warning struct {
	Filename string
	Line     int
	Tag      string
	Text     string

	Fixes []TextEdit

	SamplesTime time.Duration
}

type TextEdit struct {
	From        token.Pos
	To          token.Pos
	Replacement []byte
	Reformat    bool
}

type SourceFile struct {
	Syntax *ast.File
}

type Target struct {
	Pkg   *types.Package
	Fset  *token.FileSet
	Types *types.Info
	Sizes types.Sizes
	Files []SourceFile
}


<|start_filename|>cmd/perfguard/testdata/quickfix/rangeRuneSlice/out/after.go<|end_filename|>
package main

func main() {
	tests := []string{
		"",
		"hello",
		"Hello, 世界",
	}
	for _, test := range tests {
		f1(test)
		f2(test)
		f3([]rune(test))
		f4(test, "ab")
	}
}

func f1(s string) {
	i := 0
	for _, ch := range s {
		i++
		println("f1", i, ch)
	}
}

func f2(s string) {
	var ch rune
	for _, ch = range s {
		println("f2", ch)
		break
	}
	println(ch)
}

func f3(runes []rune) {
	var ch rune
	for _, ch = range runes {
		println("f3", ch)
	}
	println(ch)
}

func f4(s1, s2 string) {
	for _, ch1 := range s1 {
		for _, ch2 := range s2 {
			println("f4", ch1, ch2)
		}
	}
}


<|start_filename|>cmd/perfguard/cmd_optimize.go<|end_filename|>
package main

import (
	"errors"
	"flag"
	"io"
)

func cmdOptimize(stdout, stderr io.Writer, args []string) error {
	r := newRunner(stdout, stderr)

	fs := flag.NewFlagSet("perfguard optimize", flag.ExitOnError)
	addCommonFlags(r, fs)
	fs.StringVar(&r.args.heatmapFile, "heatmap", "",
		`a CPU profile that will be used to build a heatmap, needed for IsHot() filters`)
	fs.Float64Var(&r.args.heatmapThreshold, "heatmap-threshold", 0.5,
		`a threshold argument used to create a heatmap, see perf-heatmap docs on it`)
	noColor := fs.Bool("no-color", false, `disable colored output`)
	_ = fs.Parse(args)

	r.targets = fs.Args()
	r.loadOptRules = true
	r.coloredOutput = !*noColor

	if r.args.heatmapFile == "" {
		return errors.New("CPU profile is required, see --heatmap argument")
	}

	return r.Run()
}


<|start_filename|>cmd/perfguard/testdata/rulestest/trim/trim.go<|end_filename|>
package trim

import (
	"bytes"
	"strings"
	"unicode"
)

func Warn(s string, b []byte) {
	_ = strings.TrimLeft(strings.TrimRight(s, "+-"), "+-") // want `strings.TrimLeft(strings.TrimRight(s, "+-"), "+-") => strings.Trim(s, "+-")`
	_ = strings.TrimRight(strings.TrimLeft(s, "+-"), "+-") // want `strings.TrimRight(strings.TrimLeft(s, "+-"), "+-") => strings.Trim(s, "+-")`

	_ = bytes.TrimLeft(bytes.TrimRight(b, "+-"), "+-") // want `bytes.TrimLeft(bytes.TrimRight(b, "+-"), "+-") => bytes.Trim(b, "+-")`
	_ = bytes.TrimRight(bytes.TrimLeft(b, "+-"), "+-") // want `bytes.TrimRight(bytes.TrimLeft(b, "+-"), "+-") => bytes.Trim(b, "+-")`

	_ = strings.TrimFunc(s, unicode.IsSpace) // want `strings.TrimFunc(s, unicode.IsSpace) => strings.TrimSpace(s)`
	_ = bytes.TrimFunc(b, unicode.IsSpace)   // want `bytes.TrimFunc(b, unicode.IsSpace) => bytes.TrimSpace(b)`

	_ = strings.Trim(s, " \n\r")       // want `strings.Trim(s, " \n\r") => strings.TrimSpace(s)`
	_ = strings.Trim(s, " \t\r\n")     // want `strings.Trim(s, " \t\r\n") => strings.TrimSpace(s)`
	_ = strings.Trim(s, "\n\r\t ")     // want `strings.Trim(s, "\n\r\t ") => strings.TrimSpace(s)`
	_ = strings.Trim(s, "\n\r\t \v\f") // want `strings.Trim(s, "\n\r\t \v\f") => strings.TrimSpace(s)`
	_ = bytes.Trim(b, " \t\r\n")       // want `bytes.Trim(b, " \t\r\n") => bytes.TrimSpace(b)`
	_ = bytes.Trim(b, "\n\r\t ")       // want `bytes.Trim(b, "\n\r\t ") => bytes.TrimSpace(b)`
	_ = bytes.Trim(b, "\n\r\t \v\f")   // want `bytes.Trim(b, "\n\r\t \v\f") => bytes.TrimSpace(b)`
}

func Ignore() {

}


<|start_filename|>cmd/perfguard/testdata/checkerstest/prealloc/preallocSlice.go<|end_filename|>
package checkerstest

func Warn1(xs []int) []int {
	if len(xs) == 0 {
		return nil
	}
	var ys = []int{} // want `ys = []int{} => ys = make([]int, 0, len(xs))`
	for _, x := range xs {
		ys = append(ys, x+1)
	}
	if len(xs) < 10 {
		var (
			ys2 = []int{}        // want `ys2 = []int{} => ys2 = make([]int, 0, len(xs))`
			zs2 = make([]int, 0) // want `zs2 = make([]int, 0) => zs2 = make([]int, 0, len(xs))`
		)
		for _, x := range xs {
			println(x)
			ys2 = append(ys2, x+1)
			zs2 = append(zs2, 1)
		}
		return append(ys2, zs2...)
	}
	return ys
}

func Warn2(xs []int) ([]int, []int) {
	var ys []int // want `can use len(xs) as make size hint for ys`
	var zs []int // want `can use len(xs) as make size hint for zs`
	for _, x := range xs {
		ys = append(ys, x+1)
		zs = append(zs, 1)
	}
	return ys, zs
}

func Warn3(xs []int) []int {
	ys := []int{} // want `[]int{} => make([]int, 0, len(xs))`
	for _, x := range xs {
		ys = append(ys, x+1)
	}
	return ys
}

func Warn4(xs map[int]struct{}) []int {
	ys := []int{} // want `[]int{} => make([]int, 0, len(xs))`
	for x := range xs {
		ys = append(ys, x+1)
	}
	return ys
}

func Warn5(xs []map[int]struct{}) []int {
	ys := []int{} // want `[]int{} => make([]int, 0, len(xs[0]))`
	for range xs[0] {
		ys = append(ys, 1)
	}
	return ys
}

func Warn6(xs []map[int]struct{}) []int {
	var ys []int = []int{} // want `ys []int = []int{} => ys []int = make([]int, 0, len(xs[0]))`
	for range xs[0] {
		ys = append(ys, 1)
	}
	return ys
}

func Ignore1(xs []int) []int {
	var ys []int
	if len(xs) == 0 {
		return ys // Untrack ys: returned before appended to
	}
	for _, x := range xs {
		ys = append(ys, x+1)
	}
	return ys
}

func Ignore2(xs []int) []int {
	// Conditional append.
	var ys []int
	for _, x := range xs {
		if x%2 == 0 {
			ys = append(ys, x)
		}
	}
	return ys
}

func Ignore3(xs []int) []int {
	// Conditional range with append.
	var ys []int
	if len(xs) > 10 {
		for _, x := range xs {
			ys = append(ys, x*2)
		}
	}
	return ys
}

func Ignore4() []int {
	// Range expression has side effects.
	var ys []int
	for _, x := range getInts() {
		ys = append(ys, x+1)
	}
	return ys
}

func Ignore5(xs []int) []int {
	// Has continue, so it's effectively a conditional append.
	var ys []int
	for _, x := range xs {
		if x == 0 {
			continue
		}
		ys = append(ys, x+1)
	}
	return ys
}

func Ignore6(xs []int) []int {
	var ys []int
	println(len(ys)) // Untrack ys: used before appended to
	for _, x := range xs {
		ys = append(ys, x+1)
	}
	return ys
}

func Ignore7(xs []int) []int {
	// Has break.
	var ys []int
	for _, x := range xs {
		if x == 0 {
			break
		}
		ys = append(ys, x+1)
	}
	return ys
}

func Ignore8(xs []int) []int {
	// Has break.
	var ys []int
	for _, x := range xs {
		ys = append(ys, x+1)
		break
	}
	return ys
}

func getInts() []int { return []int{42} }


<|start_filename|>cmd/perfguard/testdata/rulestest/strconv/strconv.go<|end_filename|>
package rulestest

import (
	"fmt"
	"strconv"
)

func Warn() {
	var i int
	var i8 int8
	var i16 int16
	var i32 int32
	var i64 int64
	var u uint
	var u8 uint8
	var u16 uint16
	var u32 uint32
	var u64 uint64

	_ = fmt.Sprintf("%d", i)   // want `fmt.Sprintf("%d", i) => strconv.Itoa(i)`
	_ = fmt.Sprintf("%d", i8)  // want `fmt.Sprintf("%d", i8) => strconv.FormatInt(int64(i8), 10)`
	_ = fmt.Sprintf("%d", i16) // want `fmt.Sprintf("%d", i16) => strconv.FormatInt(int64(i16), 10)`
	_ = fmt.Sprintf("%d", i32) // want `fmt.Sprintf("%d", i32) => strconv.FormatInt(int64(i32), 10)`
	_ = fmt.Sprintf("%d", i64) // want `fmt.Sprintf("%d", i64) => strconv.FormatInt(i64, 10)`
	_ = fmt.Sprintf("%d", u)   // want `fmt.Sprintf("%d", u) => strconv.FormatUint(uint64(u), 10)`
	_ = fmt.Sprintf("%d", u8)  // want `fmt.Sprintf("%d", u8) => strconv.FormatUint(uint64(u8), 10)`
	_ = fmt.Sprintf("%d", u16) // want `fmt.Sprintf("%d", u16) => strconv.FormatUint(uint64(u16), 10)`
	_ = fmt.Sprintf("%d", u32) // want `fmt.Sprintf("%d", u32) => strconv.FormatUint(uint64(u32), 10)`
	_ = fmt.Sprintf("%d", u64) // want `fmt.Sprintf("%d", u64) => strconv.FormatUint(u64, 10)`

	_ = fmt.Sprintf("%v", i)   // want `fmt.Sprintf("%v", i) => strconv.Itoa(i)`
	_ = fmt.Sprintf("%v", i8)  // want `fmt.Sprintf("%v", i8) => strconv.FormatInt(int64(i8), 10)`
	_ = fmt.Sprintf("%v", i16) // want `fmt.Sprintf("%v", i16) => strconv.FormatInt(int64(i16), 10)`
	_ = fmt.Sprintf("%v", i32) // want `fmt.Sprintf("%v", i32) => strconv.FormatInt(int64(i32), 10)`
	_ = fmt.Sprintf("%v", i64) // want `fmt.Sprintf("%v", i64) => strconv.FormatInt(i64, 10)`
	_ = fmt.Sprintf("%v", u)   // want `fmt.Sprintf("%v", u) => strconv.FormatUint(uint64(u), 10)`
	_ = fmt.Sprintf("%v", u8)  // want `fmt.Sprintf("%v", u8) => strconv.FormatUint(uint64(u8), 10)`
	_ = fmt.Sprintf("%v", u16) // want `fmt.Sprintf("%v", u16) => strconv.FormatUint(uint64(u16), 10)`
	_ = fmt.Sprintf("%v", u32) // want `fmt.Sprintf("%v", u32) => strconv.FormatUint(uint64(u32), 10)`
	_ = fmt.Sprintf("%v", u64) // want `fmt.Sprintf("%v", u64) => strconv.FormatUint(u64, 10)`

	_ = fmt.Sprint(i)   // want `fmt.Sprint(i) => strconv.Itoa(i)`
	_ = fmt.Sprint(i8)  // want `fmt.Sprint(i8) => strconv.FormatInt(int64(i8), 10)`
	_ = fmt.Sprint(i16) // want `fmt.Sprint(i16) => strconv.FormatInt(int64(i16), 10)`
	_ = fmt.Sprint(i32) // want `fmt.Sprint(i32) => strconv.FormatInt(int64(i32), 10)`
	_ = fmt.Sprint(i64) // want `fmt.Sprint(i64) => strconv.FormatInt(i64, 10)`
	_ = fmt.Sprint(u)   // want `fmt.Sprint(u) => strconv.FormatUint(uint64(u), 10)`
	_ = fmt.Sprint(u8)  // want `fmt.Sprint(u8) => strconv.FormatUint(uint64(u8), 10)`
	_ = fmt.Sprint(u16) // want `fmt.Sprint(u16) => strconv.FormatUint(uint64(u16), 10)`
	_ = fmt.Sprint(u32) // want `fmt.Sprint(u32) => strconv.FormatUint(uint64(u32), 10)`
	_ = fmt.Sprint(u64) // want `fmt.Sprint(u64) => strconv.FormatUint(u64, 10)`

	_ = fmt.Sprintf("%x", i)   // want `fmt.Sprintf("%x", i) => strconv.FormatInt(int64(i), 16)`
	_ = fmt.Sprintf("%x", i8)  // want `fmt.Sprintf("%x", i8) => strconv.FormatInt(int64(i8), 16)`
	_ = fmt.Sprintf("%x", i16) // want `fmt.Sprintf("%x", i16) => strconv.FormatInt(int64(i16), 16)`
	_ = fmt.Sprintf("%x", i32) // want `fmt.Sprintf("%x", i32) => strconv.FormatInt(int64(i32), 16)`
	_ = fmt.Sprintf("%x", i64) // want `fmt.Sprintf("%x", i64) => strconv.FormatInt(i64, 16)`
	_ = fmt.Sprintf("%x", u)   // want `fmt.Sprintf("%x", u) => strconv.FormatUint(uint64(u), 16)`
	_ = fmt.Sprintf("%x", u8)  // want `fmt.Sprintf("%x", u8) => strconv.FormatUint(uint64(u8), 16)`
	_ = fmt.Sprintf("%x", u16) // want `fmt.Sprintf("%x", u16) => strconv.FormatUint(uint64(u16), 16)`
	_ = fmt.Sprintf("%x", u32) // want `fmt.Sprintf("%x", u32) => strconv.FormatUint(uint64(u32), 16)`
	_ = fmt.Sprintf("%x", u64) // want `fmt.Sprintf("%x", u64) => strconv.FormatUint(u64, 16)`
}

func Ignore() {
	var i int
	var i8 int8
	var i16 int16
	var i32 int32
	var i64 int64
	var u uint
	var u8 uint8
	var u16 uint16
	var u32 uint32
	var u64 uint64

	_ = strconv.Itoa(i)
	_ = strconv.FormatInt(int64(i8), 10)
	_ = strconv.FormatInt(int64(i16), 10)
	_ = strconv.FormatInt(int64(i32), 10)
	_ = strconv.FormatInt(i64, 10)
	_ = strconv.FormatUint(uint64(u), 10)
	_ = strconv.FormatUint(uint64(u8), 10)
	_ = strconv.FormatUint(uint64(u16), 10)
	_ = strconv.FormatUint(uint64(u32), 10)
	_ = strconv.FormatUint(u64, 10)

	_ = strconv.Itoa(i)
	_ = strconv.FormatInt(int64(i8), 10)
	_ = strconv.FormatInt(int64(i16), 10)
	_ = strconv.FormatInt(int64(i32), 10)
	_ = strconv.FormatInt(i64, 10)
	_ = strconv.FormatUint(uint64(u), 10)
	_ = strconv.FormatUint(uint64(u8), 10)
	_ = strconv.FormatUint(uint64(u16), 10)
	_ = strconv.FormatUint(uint64(u32), 10)
	_ = strconv.FormatUint(u64, 10)

	_ = strconv.Itoa(i)
	_ = strconv.FormatInt(int64(i8), 10)
	_ = strconv.FormatInt(int64(i16), 10)
	_ = strconv.FormatInt(int64(i32), 10)
	_ = strconv.FormatInt(i64, 10)
	_ = strconv.FormatUint(uint64(u), 10)
	_ = strconv.FormatUint(uint64(u8), 10)
	_ = strconv.FormatUint(uint64(u16), 10)
	_ = strconv.FormatUint(uint64(u32), 10)
	_ = strconv.FormatUint(u64, 10)

	_ = strconv.FormatInt(int64(i), 16)
	_ = strconv.FormatInt(int64(i8), 16)
	_ = strconv.FormatInt(int64(i16), 16)
	_ = strconv.FormatInt(int64(i32), 16)
	_ = strconv.FormatInt(i64, 16)
	_ = strconv.FormatUint(uint64(u), 16)
	_ = strconv.FormatUint(uint64(u8), 16)
	_ = strconv.FormatUint(uint64(u16), 16)
	_ = strconv.FormatUint(uint64(u32), 16)
	_ = strconv.FormatUint(u64, 16)
}


<|start_filename|>cmd/perfguard/testdata/checkerstest/prealloc/preallocMap.go<|end_filename|>
package checkerstest

func Warn1map(xs map[int]int) map[int]int {
	var squares = make(map[int]int) // want `squares = make(map[int]int) => squares = make(map[int]int, len(xs))`
	for k, x := range xs {
		squares[k] = x * x
	}
	return squares
}

func Warn2map(xs map[int]int) map[int]int {
	var squares = map[int]int{} // want `squares = map[int]int{} => squares = make(map[int]int, len(xs))`
	for k, x := range xs {
		squares[k] = x * x
	}
	return squares
}

func Warn3map(xs []int) map[int]int {
	// Like other example, but uses := to define a var.
	squares := make(map[int]int) // want `make(map[int]int) => make(map[int]int, len(xs))`
	for k, x := range xs {
		squares[k] = x * x
	}
	return squares
}

func Warn4map(xs []int) map[int]int {
	// Like other example, but uses := to define a var and a literal instead of a make.
	squares := map[int]int{} // want `map[int]int{} => make(map[int]int, len(xs))`
	for k, x := range xs {
		squares[k] = x * x
	}
	return squares
}

func Warn5map(xs map[string]int) map[string]struct{} {
	// Key-only copying.
	var keys = make(map[string]struct{}) // want `keys = make(map[string]struct{}) => keys = make(map[string]struct{}, len(xs))`
	for k := range xs {
		keys[k] = struct{}{}
	}
	return keys
}

func Ignore1map(xs map[int]int) map[int]int {
	// Copying the values, not the keys.
	var squares = make(map[int]int)
	for _, x := range xs {
		squares[x] = x * x
	}
	return squares
}

func Ignore2map(xs []int) map[int]int {
	// Copying the slice values.
	var squares = make(map[int]int)
	for _, x := range xs {
		squares[x] = x * x
	}
	return squares
}


<|start_filename|>cmd/perfguard/testdata/checkerstest/stringsBuilder/stringsBuilder.go<|end_filename|>
package checkerstest

import (
	"bytes"
	"fmt"
	"io"
	"strings"
)

func Warn1(b []byte) string {
	var buf bytes.Buffer // want `bytes.Buffer => strings.Builder`
	buf.Write(b)
	buf.WriteByte('x')
	buf.WriteRune('Д')
	buf.WriteString("321")
	return buf.String()
}

func Fixed1(b []byte) string {
	var buf strings.Builder
	buf.Write(b)
	buf.WriteByte('x')
	buf.WriteRune('Д')
	buf.WriteString("321")
	return buf.String()
}

func Warn2(b []byte) string {
	buf := bytes.Buffer{} // want `bytes.Buffer => strings.Builder`
	buf.Write(b)
	buf.WriteByte('x')
	buf.WriteRune('Д')
	buf.WriteString("321")
	return buf.String()
}

func Fixed2(b []byte) string {
	buf := strings.Builder{}
	buf.Write(b)
	buf.WriteByte('x')
	buf.WriteRune('Д')
	buf.WriteString("321")
	return buf.String()
}

func Warn3(b []byte) string {
	var buf bytes.Buffer // want `bytes.Buffer => strings.Builder`
	writeData(&buf)
	return buf.String()
}

func Fixed3(b []byte) string {
	var buf strings.Builder
	writeData(&buf)
	return buf.String()
}

func Warn4(b []byte, s string) string {
	var buf bytes.Buffer // want `bytes.Buffer => strings.Builder`
	fmt.Fprintf(&buf, "%s:%d", s, 124)
	return buf.String()
}

func Fixed4(b []byte, s string) string {
	var buf strings.Builder
	fmt.Fprintf(&buf, "%s:%d", s, 124)
	return buf.String()
}

func Warn5(format string, args []interface{}, s string) string {
	buf := bytes.Buffer{} // want `bytes.Buffer => strings.Builder`
	buf.WriteString(fmt.Sprintf(format, args...))
	buf.WriteString(s + s[:2])
	return buf.String()
}

func Fixed5(format string, args []interface{}, s string) string {
	buf := strings.Builder{}
	buf.WriteString(fmt.Sprintf(format, args...))
	buf.WriteString(s + s[:2])
	return buf.String()
}

func Warn6() string {
	f := func() string {
		var buf bytes.Buffer // want `bytes.Buffer => strings.Builder`
		buf.WriteString("123")
		return buf.String()
	}
	return f()
}

func Fixed6() string {
	f := func() string {
		var buf strings.Builder
		buf.WriteString("123")
		return buf.String()
	}
	return f()
}

func Warn7(cond bool) string {
	buf := &bytes.Buffer{} // want `bytes.Buffer => strings.Builder`
	if cond {
		buf.WriteString("true")
	} else {
		buf.WriteString("false")
	}
	return buf.String()
}

func Fixed7(cond bool) string {
	buf := &strings.Builder{}
	if cond {
		buf.WriteString("true")
	} else {
		buf.WriteString("false")
	}
	return buf.String()
}

func Warn8(cond bool) string {
	buf := &bytes.Buffer{} // want `bytes.Buffer => strings.Builder`
	buf.WriteString("ok")
	res := buf.String()
	return res
}

func Fixed8(cond bool) string {
	buf := &strings.Builder{}
	buf.WriteString("ok")
	res := buf.String()
	return res
}

func Ignore1() string {
	// buf.Reset() is used.
	var buf bytes.Buffer
	buf.WriteString("foo")
	res := buf.String()
	buf.Reset()
	return res
}

func Ignore2() []byte {
	// buf.Bytes() is used.
	var buf bytes.Buffer
	buf.WriteString("foo")
	return buf.Bytes()
}

func Ignore3() {
	// buf.String() is never used.
	var buf bytes.Buffer
	buf.WriteString("foo")
	writeData(&buf)
}

func Ignore4() bytes.Buffer {
	// buf is returned.
	var buf bytes.Buffer
	buf.WriteString("foo")
	_ = buf.String()
	return buf
}

func Ignore5() *bytes.Buffer {
	// buf is returned.
	var buf bytes.Buffer
	buf.WriteString("foo")
	_ = buf.String()
	return &buf
}

func Ignore6() string {
	// buf is passed as non-interface argument.
	var buf bytes.Buffer
	buf.WriteString("foo")
	writeDataBuf(&buf)
	return buf.String()
}

func writeData(w io.Writer) {
	w.Write([]byte("data"))
}

func writeDataBuf(w *bytes.Buffer) {
	w.WriteString("data")
}


<|start_filename|>cmd/perfguard/testdata/rulestest/rangeRuneSlice/rangeRuneSlice.go<|end_filename|>
package rulestest

type myString string

func Warn(s string, s2 myString, runes []rune) {
	for _, ch := range []rune(s) { // want `for _, ch := range []rune(s) => for _, ch := range s`
		println(ch)
	}

	for _, ch := range []rune(s2) { // want `for _, ch := range []rune(s2) => for _, ch := range s2`
		println(ch)
	}

	for _, ch := range []rune(string(s2)) { // want `for _, ch := range []rune(string(s2)) => for _, ch := range string(s2)`
		println(ch)
	}

	for range []rune("foo") { // want `for range []rune("foo") => for range "foo"`
	}

	{
		var ch rune
		for _, ch = range []rune(s) { // want `for _, ch = range []rune(s) => for _, ch = range s`
			println(ch)
		}
		for _, ch = range []rune(s2) { // want `for _, ch = range []rune(s2) => for _, ch = range s2`
			println(ch)
		}
		for _, ch = range []rune(string(s2)) { // want `for _, ch = range []rune(string(s2)) => for _, ch = range string(s2)`
			println(ch)
		}
	}

	{
		var ch rune
		for _, ch := range string(runes) { // want `for _, ch := range string(runes) => for _, ch := range runes`
			println(ch)
		}
		for _, ch = range string(runes) { // want `for _, ch = range string(runes) => for _, ch = range runes`
			println(ch)
		}
		for _, ch := range string([]rune(runes)) { // want `for _, ch := range string([]rune(runes)) => for _, ch := range []rune(runes)`
			println(ch)
		}
	}
}

func Ignore(s string, s2 myString, runes []rune) {
	for _, ch := range s {
		println(ch)
	}

	for _, ch := range s2 {
		println(ch)
	}

	for _, ch := range string(s2) {
		println(ch)
	}

	for i, ch := range []rune(s) {
		println(ch)
		println(i)
	}

	for i, ch := range []rune(s2) {
		println(ch)
		println(i)
	}

	for i, ch := range []rune(string(s2)) {
		println(ch)
		println(i)
	}

	for range "foo" {
		println("ok")
	}

	{
		var ch rune
		for _, ch := range runes {
			println(ch)
		}
		for _, ch = range runes {
			println(ch)
		}
		for _, ch := range []rune(runes) {
			println(ch)
		}
	}
}


<|start_filename|>cmd/perfguard/testdata/rulestest/mapAssignOp/mapAssignOp.go<|end_filename|>
package rulestest

func Warn(m map[string]int) {
	{
		m["foo"] = m["foo"] + 1 // want `m["foo"] = m["foo"] + 1 => m["foo"]++`
		m["foo"] += 1           // want `m["foo"] += 1 => m["foo"]++`
	}

	{
		var k string
		m[k] = m[k] + 1 // want `m[k] = m[k] + 1 => m[k]++`
		m[k] += 1       // want `m[k] += 1 => m[k]++`

		m[k] = m[k] + 5 // want `m[k] = m[k] + 5 => m[k] += 5`
		m[k] = m[k] - 5 // want `m[k] = m[k] - 5 => m[k] -= 5`
		m[k] = m[k] / 2 // want `m[k] = m[k] / 2 => m[k] /= 2`
		m[k] = m[k] * 4 // want `m[k] = m[k] * 4 => m[k] *= 4`
	}
}

func Ignore(m map[string]int) {
	{
		m["foo"]++
	}

	{
		var k string
		m[k]++
		m[k] = m["foo"] + 1
		m[k] += 5
		m[k] -= 5
		m[k] /= 5
		m[k] *= 15
	}

	{
		m[getString()] = m[getString()] + 1
	}

	{
		type counter struct {
			value int
		}
		m := make(map[int]*counter)
		m[10].value++
		m[10].value += 1
		m[10].value = m[10].value + 1
	}
}

func getString() string { return "" }


<|start_filename|>cmd/perfguard/testdata/checkerstest/bigArgCopy/bigArgCopy.go<|end_filename|>
package checkerstest

func f(x bigObject) {}

func ignored(_ bigObject) {}
func ignored2(args ...bigObject)
func ignored3(args ...interface{})

type example struct{}

func (example) f(y [1000]byte) {}

func Warn() {
	var o bigObject
	f(o)                              // want `expensive x arg copy`
	f(bigObject(anotherBigObject(o))) // want `expensive x arg copy`

	o.byValue() // want `expensive o receiver copy (10000 bytes)`
	o.byPointer()

	var e example
	e.f([1000]byte{}) // want `expensive y arg copy (1000 bytes), consider passing it by pointer`
}

type bigObject struct {
	data [10000]byte
}

func (o bigObject) byValue()    {}
func (o *bigObject) byPointer() {}

type anotherBigObject struct {
	data [10000]byte
}

func Ignore(o bigObject) {
	// Argument is not really evaluated.
	_ = new([90000]byte)

	// Not a function call: it's a type conversion.
	_ = anotherBigObject(o)
	_ = bigObject(o)

	ignored(o)
	ignored2(o)
	ignored3(o)
}


<|start_filename|>cmd/perfguard/testdata/rulestest/writeString/writeString.go<|end_filename|>
package rulestest

import (
	"bytes"
	"strings"
)

type weirdWriter struct{}

func (w weirdWriter) Write([]byte) error       { return nil }
func (w weirdWriter) WriteString(string) error { return nil }

func Warn(buf *bytes.Buffer, sb *strings.Builder, s string) {
	buf.Write([]byte(s)) // want `buf.Write([]byte(s)) => buf.WriteString(s)`
	sb.Write([]byte(s))  // want `sb.Write([]byte(s)) => sb.WriteString(s)`

	{
		var b bytes.Buffer
		b.Write([]byte(s)) // want `b.Write([]byte(s)) => b.WriteString(s)`
	}
	{
		var sb strings.Builder
		sb.Write([]byte(s)) // want `sb.Write([]byte(s)) => sb.WriteString(s)`
	}
}

func Ignore(buf *bytes.Buffer, sb *strings.Builder, s string) {
	buf.WriteString(s)
	sb.WriteString(s)

	{
		var b bytes.Buffer
		b.WriteString(s)
	}
	{
		var sb strings.Builder
		sb.WriteString(s)
	}

	{
		var ww weirdWriter
		ww.Write([]byte(s))
	}
}


<|start_filename|>perfguard/checkers/callcheckers/bytesToStringFmt.go<|end_filename|>
package callcheckers

import (
	"go/ast"
	"go/token"
	"strconv"

	"github.com/quasilyte/go-perfguard/internal/resolve"
	"github.com/quasilyte/go-perfguard/internal/typeis"
	"github.com/quasilyte/go-perfguard/perfguard/checkers"
	"github.com/quasilyte/go-perfguard/perfguard/lint"
)

func init() {
	doc := checkers.Doc{
		Name:  "bytesToStringFmt",
		Score: 2,
	}
	checkers.RegisterCallChecker(doc, func() checkers.CallChecker {
		return &BytesToStringFmtChecker{}
	})
}

type BytesToStringFmtChecker struct{}

func (c *BytesToStringFmtChecker) CheckCall(ctx *lint.Context, call *ast.CallExpr) error {
	if call.Ellipsis.IsValid() {
		return nil // Skip variadic calls
	}
	if ctx.Sym.PkgPath != "fmt" {
		return nil
	}

	formatArgNum := 0
	switch ctx.Sym.FuncName {
	case "Fprintf":
		formatArgNum = 1
	case "Sprintf", "Printf":
		// OK, argNum is 0
	default:
		return nil
	}
	if len(call.Args) <= formatArgNum+1 {
		return nil
	}

	formatArg, ok := call.Args[formatArgNum].(*ast.BasicLit)
	if !ok || formatArg.Kind != token.STRING {
		return nil
	}
	formatString, err := strconv.Unquote(formatArg.Value)
	if err != nil {
		return nil
	}
	if formatString == "%s" {
		// TODO: #9
		return nil // It's redundantSprint case
	}
	formatInfo, ok := resolve.FmtString(formatString)
	if !ok {
		return nil
	}

	for _, argInfo := range formatInfo.Args {
		if argInfo.Verb != 's' && argInfo.Verb != 'q' {
			continue
		}
		argIndex := argInfo.ArgNum + formatArgNum + 1
		if argIndex >= len(call.Args) {
			break
		}
		convExpr := call.Args[argIndex]
		convInfo := resolve.ConvExpr(ctx.Target.Types, convExpr)
		if convInfo.Arg == nil || !typeis.String(convInfo.DstType) {
			continue
		}
		srcType := ctx.TypeOf(convInfo.Arg)
		if !typeis.ByteSlice(srcType.Underlying()) {
			continue
		}
		ctx.SuggestNode(lint.SuggestParams{
			OldNode: convExpr,
			NewNode: convInfo.Arg,
		})
	}

	return nil
}


<|start_filename|>perfguard/checkers/funcchecker_walker.go<|end_filename|>
package checkers

import (
	"go/ast"

	"github.com/quasilyte/go-perfguard/internal/resolve"
	"github.com/quasilyte/go-perfguard/perfguard/lint"
)

type funccheckerWithContext struct {
	ctx lint.Context
	obj FuncChecker
}

type funccheckerWalker struct {
	checkers []funccheckerWithContext
}

func (w *funccheckerWalker) CheckPackage(ctx *lint.SharedContext, files []lint.SourceFile) error {
	for i := range w.checkers {
		w.checkers[i].ctx.SharedContext = ctx
	}

	var checkError error

	for _, f := range files {
		ctx.Filename = ctx.Position(f.Syntax).Filename
		ast.Inspect(f.Syntax, func(n ast.Node) bool {
			var body *ast.BlockStmt
			switch n := n.(type) {
			case *ast.FuncDecl:
				body = n.Body
				ctx.TypeName, ctx.FuncName = resolve.SplitFuncName(n)
			case *ast.FuncLit:
				body = n.Body
				ctx.TypeName = ""
				ctx.FuncName = ""
			}
			if body != nil {
				for _, c := range w.checkers {
					if err := c.obj.CheckFunc(&c.ctx, body); err != nil {
						checkError = err
					}
				}
			}
			return true
		})
	}

	return checkError
}


<|start_filename|>cmd/perfguard/testdata/quickfix/prealloc/before.go<|end_filename|>
package main

import "sort"

func main() {
	xs := []int{1, 2, 3, 4, 5, 2, 1, 9, 1, 8, 1}
	for _, x := range uniq(xs) {
		println(x)
	}
	for _, x := range uniq([]int{}) {
		println(x)
	}
	println(uniq(nil) == nil)
	println(uniq([]int{}) == nil)

	ys := []int{1, 9, 10, 309}
	for _, x := range intersection(xs, ys) {
		println(x)
	}
	for _, x := range intersection(xs, []int{}) {
		println(x)
	}
	println(intersection(nil, nil) == nil)
	println(intersection([]int{}, []int{}) == nil)

	m := map[string]string{
		"a": "b",
	}
	copymap(m)
}

func intersection(xs, ys []int) []int {
	all := map[int]struct{}{}
	for _, x := range xs {
		all[x] = struct{}{}
	}
	for _, y := range ys {
		all[y] = struct{}{}
	}
	var result []int
	for x := range all {
		result = append(result, x)
	}
	sort.Ints(result)
	return result
}

func uniq(xs []int) []int {
	var set map[int]struct{} = make(map[int]struct{})
	for _, x := range xs {
		set[x] = struct{}{}
	}
	result := []int{}
	for k := range set {
		result = append(result, k)
	}
	sort.Ints(result)
	return result
}

func copymap(m map[string]string) map[string]string {
	copied := map[string]string{}
	for k, v := range m {
		copied[k] = v
	}
	return copied
}


<|start_filename|>cmd/perfguard/utils.go<|end_filename|>
package main

import (
	"go/ast"
	"strings"
)

func isAutogenFile(f *ast.File) bool {
	for _, comment := range f.Comments {
		if isAutogenComment(comment) {
			return true
		}
	}
	return false
}

func isAutogenComment(comment *ast.CommentGroup) bool {
	generated := false
	doNotEdit := false
	for _, c := range comment.List {
		s := strings.ToLower(c.Text)
		if !generated {
			generated = strings.Contains(s, " code generated ") ||
				strings.Contains(s, " generated by ")
		}
		if !doNotEdit {
			doNotEdit = strings.Contains(s, "do not edit") ||
				strings.Contains(s, "don't edit")
		}
		if generated && doNotEdit {
			return true
		}
	}
	return false
}


<|start_filename|>internal/typeis/typeis.go<|end_filename|>
package typeis

import (
	"go/types"
)

func String(typ types.Type) bool {
	if typ, ok := typ.(*types.Basic); ok {
		if typ.Info()&types.IsString != 0 {
			return true
		}
	}
	return false
}

func Slice(typ types.Type) bool {
	_, ok := typ.(*types.Slice)
	return ok
}

func Map(typ types.Type) bool {
	_, ok := typ.(*types.Map)
	return ok
}

func Pointer(typ types.Type) bool {
	_, ok := typ.(*types.Pointer)
	return ok
}

func ByteSlice(typ types.Type) bool {
	if typ, ok := typ.(*types.Slice); ok {
		if typ, ok := typ.Elem().(*types.Basic); ok {
			return typ.Kind() == types.Uint8
		}
	}
	return false
}

func Named(typ types.Type, pkgPath, typeName string) bool {
	if namedType, ok := typ.(*types.Named); ok {
		obj := namedType.Obj()
		return obj.Name() == typeName && obj.Pkg().Path() == pkgPath
	}
	return false
}


<|start_filename|>cmd/perfguard/testdata/rulestest/redundantFprint/redundantFprint.go<|end_filename|>
package rulestest

import (
	"fmt"
	"io"
)

func Warn() {
	{
		var w buffer
		var foo withStringer
		fmt.Fprint(w, foo)        // want `fmt.Fprint(w, foo) => w.WriteString(foo.String())`
		fmt.Fprintf(w, "%s", foo) // want `fmt.Fprintf(w, "%s", foo) => w.WriteString(foo.String())`
		fmt.Fprintf(w, "%v", foo) // want `fmt.Fprintf(w, "%v", foo) => w.WriteString(foo.String())`
	}

	{
		var w buffer
		var err error
		fmt.Fprint(w, err)        // want `fmt.Fprint(w, err) => w.WriteString(err.Error())`
		fmt.Fprintf(w, "%s", err) // want `fmt.Fprintf(w, "%s", err) => w.WriteString(err.Error())`
		fmt.Fprintf(w, "%v", err) // want `fmt.Fprintf(w, "%v", err) => w.WriteString(err.Error())`
	}

	{
		var w buffer
		var s string
		fmt.Fprint(w, s)        // want `fmt.Fprint(w, s) => w.WriteString(s)`
		fmt.Fprintf(w, "%s", s) // want `fmt.Fprintf(w, "%s", s) => w.WriteString(s)`
		fmt.Fprintf(w, "%v", s) // want `fmt.Fprintf(w, "%v", s) => w.WriteString(s)`
	}

	{
		var w buffer
		var b []byte
		fmt.Fprint(w, b)        // want `fmt.Fprint(w, b) => w.Write(b)`
		fmt.Fprintf(w, "%s", b) // want `fmt.Fprintf(w, "%s", b) => w.Write(b)`
		fmt.Fprintf(w, "%v", b) // want `fmt.Fprintf(w, "%v", b) => w.Write(b)`
	}
}

func Ignore() {
	{
		var w io.Writer
		var foo withStringer
		fmt.Fprint(w, foo)
		fmt.Fprintf(w, "%s", foo)
		fmt.Fprintf(w, "%v", foo)
	}

	{
		var w buffer
		var foo withStringer
		w.WriteString(foo.String())
	}

	{
		var w buffer
		var err error
		w.WriteString(err.Error())
	}

	{
		var w buffer
		var s string
		w.WriteString(s)
	}

	{
		var w buffer
		var b []byte
		w.Write(b)
	}
}

type withStringer struct{}

func (withStringer) String() string { return "" }

type buffer struct{}

func (buffer) WriteString(s string) (int, error) { return 0, nil }
func (buffer) Write(b []byte) (int, error)       { return 0, nil }


<|start_filename|>cmd/perfguard/runner.go<|end_filename|>
package main

import (
	"bytes"
	"context"
	"fmt"
	"go/format"
	"go/token"
	"io"
	"math"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"sync/atomic"
	"time"

	"github.com/google/pprof/profile"
	"github.com/quasilyte/perf-heatmap/heatmap"
	"github.com/quasilyte/stdinfo"
	"golang.org/x/tools/go/packages"

	"github.com/quasilyte/go-perfguard/internal/imports"
	"github.com/quasilyte/go-perfguard/internal/quickfix"
	"github.com/quasilyte/go-perfguard/perfguard"
	"github.com/quasilyte/go-perfguard/perfguard/lint"
)

type arguments struct {
	heatmapFile      string
	heatmapThreshold float64

	autogen bool

	quiet bool
}

type statistics struct {
	issuesTotal   int
	issuesFixable int

	numSamples    int
	minSampleTime time.Duration

	affectedSampleTime time.Duration

	pkgfindTime  int64
	pkgloadTime  int64
	analysisTime int64

	numAutogenFiles int
}

// runner unifies both `lint` and `optimize` modes.
type runner struct {
	targets []string
	autofix bool

	analyzer *perfguard.Analyzer

	debugEnabled bool

	args  arguments
	stats statistics

	heatmap          *heatmap.Index
	heatmapPackages  map[string]struct{}
	heatmapFiles     map[string]struct{}
	numFilesSkipped  int
	numFilesAnalyzed int

	wd string

	coloredOutput bool
	absFilenames  bool

	loadLintRules bool
	loadOptRules  bool

	goVersion string

	stdout io.Writer
	stderr io.Writer

	pkgWarnings []lint.Warning

	// We try to avoid reporting more errors than necessary.
	// There is a hard limit on how many errors we'll print.
	// There is also a filter that will exclude any repeated
	// errors from the output (errorSet).
	errorSet    map[string]struct{}
	errorsList  []string
	extraErrors int

	numLoadCalls int
}

func newRunner(stdout, stderr io.Writer) *runner {
	debugEnabled := os.Getenv("PERFGUARD_DEBUG") == "1"
	return &runner{
		stdout:       stdout,
		stderr:       stderr,
		debugEnabled: debugEnabled,

		errorSet: make(map[string]struct{}),
	}
}

func (r *runner) pushErrorf(key, formatString string, args ...interface{}) {
	if _, ok := r.errorSet[key]; ok {
		return
	}
	const maxErrorsNum = 10
	if len(r.errorSet) > maxErrorsNum {
		r.extraErrors++
		return
	}
	r.errorSet[key] = struct{}{}

	msg := fmt.Sprintf(formatString, args...)
	r.errorsList = append(r.errorsList, msg)
}

func (r *runner) printAllErrors() {
	for _, msg := range r.errorsList {
		tag := ">> error"
		if r.coloredOutput {
			tag = "\033[31;1m" + tag + "\033[0m"
		}
		msg := tag + ": " + msg + "\n"
		_, err := io.WriteString(r.stderr, msg)
		if err != nil {
			panic(err)
		}
	}
}

func (r *runner) printDebugf(formatString string, args ...interface{}) {
	if !r.debugEnabled {
		return
	}
	tag := ">> debug"
	if r.coloredOutput {
		tag = "\033[34;1m" + tag + "\033[0m"
	}
	msg := tag + ": " + fmt.Sprintf(formatString, args...) + "\n"
	_, err := io.WriteString(r.stderr, msg)
	if err != nil {
		panic(err)
	}
}

func (r *runner) Run() error {
	if len(r.targets) == 0 {
		return fmt.Errorf("no analysis targets provided")
	}

	ctx := context.Background()
	startTime := time.Now()

	wd, err := os.Getwd()
	if err != nil {
		return err
	}
	r.wd = wd

	if r.args.heatmapFile != "" {
		heatmapIndex, err := r.createHeatmap()
		if err != nil {
			return err
		}
		r.heatmap = heatmapIndex
		r.inspectHeatmap()
	}

	fileSet := token.NewFileSet()
	targetPackages, err := r.findPackages(ctx, fileSet, r.targets)
	if err != nil {
		return fmt.Errorf("load packages: %w", err)
	}

	{
		analyzer, err := r.createAnalyzer()
		if err != nil {
			return fmt.Errorf("create analyzer: %w", err)
		}
		r.analyzer = analyzer
	}

	if r.heatmap != nil {
		filtered := targetPackages[:0]
		numSkipped := 0
		for _, ref := range targetPackages {
			if _, ok := r.heatmapPackages[ref.name]; ok {
				filtered = append(filtered, ref)
			} else {
				r.printDebugf("skip %s (%s) package", ref.name, ref.path)
				numSkipped++
			}
		}
		if numSkipped != 0 {
			r.printDebugf("skipped %d packages", numSkipped)
		}
		targetPackages = filtered
	}

	if r.debugEnabled {
		for _, ref := range targetPackages {
			r.printDebugf("found %s package", ref.path)
		}
	}

	// Small batches -- slow analysis.
	// Batches that are too big -- we'll get out of resources trying
	// loading all of the packages into memory.
	// TODO: batch size should not be static.
	// It should depend on the package relative complexity.
	// For example, we can load hundreds of small packages even on a potatoe computer.
	// For packages with tons of dependencies we can't afford that.
	//
	// Also note that packages.Load utilizes parallelism.
	// So it makes sense to adjust it to the number of CPUs available.
	batchMaxSize := 8 + (runtime.NumCPU() * 4)
	if batchMaxSize > 80 {
		batchMaxSize = 80
	}

	target := &lint.Target{}
	numProcessed := 0
	batchTargets := make([]string, batchMaxSize)
	todoTargets := targetPackages
	for len(todoTargets) != 0 {
		batchSize := batchMaxSize
		if batchSize > len(todoTargets) {
			batchSize = len(todoTargets)
		}
		for i, ref := range todoTargets[:batchSize] {
			batchTargets[i] = ref.path
			if r.debugEnabled {
				r.printDebugf("loading %s package (%d/%d)", ref.path, numProcessed+i+1, len(targetPackages))
			}
		}
		todoTargets = todoTargets[batchSize:]
		batchPackages, err := r.loadPackages(ctx, fileSet, batchTargets[:batchSize])
		if err != nil {
			return err
		}
		for i, pkg := range batchPackages {
			targetPkgPath := batchTargets[i]
			if r.debugEnabled {
				r.printDebugf("analyzing %s package (%d/%d)", targetPkgPath, numProcessed+i+1, len(targetPackages))
			}

			target.Files = target.Files[:0]
			for _, f := range pkg.Syntax {
				if r.heatmapFiles != nil {
					filename := fileSet.Position(f.Pos()).Filename
					if _, ok := r.heatmapFiles[filepath.Base(filename)]; !ok {
						r.numFilesSkipped++
						continue
					}
				}
				isAutogen := isAutogenFile(f)
				if isAutogen {
					r.stats.numAutogenFiles++
					if !r.args.autogen {
						r.numFilesSkipped++
						continue
					}
				}
				r.numFilesAnalyzed++
				target.Files = append(target.Files, lint.SourceFile{
					Syntax: f,
				})
			}
			target.Fset = fileSet
			target.Sizes = pkg.TypesSizes
			target.Types = pkg.TypesInfo
			target.Pkg = pkg.Types

			if err := r.analyzePackage(target); err != nil {
				return fmt.Errorf("checking %s: %w", pkg.PkgPath, err)
			}
		}
		numProcessed += batchSize
	}

	timeElapsed := time.Since(startTime)

	if !r.args.quiet {
		if r.heatmap != nil && r.stats.affectedSampleTime != 0 {
			fmt.Fprintf(r.stderr, "Affected samples time: %s\n", r.stats.affectedSampleTime)
		}
		suffix := "auto-fixable"
		if r.autofix {
			suffix = "fixed"
		}
		fmt.Fprintf(r.stderr, "Found %d issues (%d %s)\n",
			r.stats.issuesTotal, r.stats.issuesFixable, suffix)
	}

	r.printDebugf("batch size: %d", batchMaxSize)

	if r.heatmap != nil {
		r.printDebugf("lines covered by samples: %d", r.stats.numSamples)
		r.printDebugf("min time sample: %s", r.stats.minSampleTime)
	}
	if r.numFilesSkipped == 0 {
		r.printDebugf("analyzed %d files", r.numFilesAnalyzed)
	} else {
		r.printDebugf("analyzed %d files (%d skipped)", r.numFilesAnalyzed, r.numFilesSkipped)
	}
	r.printDebugf("autogen files: %d", r.stats.numAutogenFiles)
	r.printDebugf("packages.Load calls: %d", r.numLoadCalls)
	r.printDebugf("packages.Load time: %s", time.Duration(r.stats.pkgloadTime))
	r.printDebugf("find packages time: %s", time.Duration(r.stats.pkgfindTime))
	r.printDebugf("analysis time: %s", time.Duration(r.stats.analysisTime))
	r.printDebugf("total time: %s", timeElapsed)

	if len(r.errorsList) != 0 {
		r.printAllErrors()
		if r.extraErrors != 0 {
			fmt.Fprintf(r.stderr, "+ %d more errors\n", r.extraErrors)
		}
	}

	return nil
}

func (r *runner) analyzePackage(target *lint.Target) error {
	r.pkgWarnings = r.pkgWarnings[:0]
	start := time.Now()
	err := r.analyzer.CheckPackage(target)
	elapsed := time.Since(start)
	atomic.AddInt64(&r.stats.analysisTime, int64(elapsed))
	if err != nil {
		return err
	}
	if len(r.pkgWarnings) != 0 {
		if err := r.handleWarnings(target); err != nil {
			return err
		}
	}
	return nil
}

func (r *runner) createAnalyzer() (*perfguard.Analyzer, error) {
	a := perfguard.NewAnalyzer()
	initConfig := &perfguard.Config{
		Heatmap: r.heatmap,

		GoVersion: r.goVersion,

		Warn: r.appendWarning,

		LoadUniversalRules: true,
		LoadOptRules:       r.loadOptRules,
		LoadLintRules:      r.loadLintRules,
	}
	if err := a.Init(initConfig); err != nil {
		return nil, err
	}
	return a, nil
}

func (r *runner) appendWarning(w lint.Warning) {
	r.pkgWarnings = append(r.pkgWarnings, w)
}

func (r *runner) reportWarning(w *lint.Warning) {
	filename := w.Filename
	line := strconv.Itoa(w.Line)
	ruleName := w.Tag
	message := w.Text
	if !r.absFilenames {
		rel, err := filepath.Rel(r.wd, filename)
		if err != nil {
			panic(err)
		}
		filename = rel
	}
	if r.coloredOutput {
		filename = "\033[35m" + filename + "\033[0m"
		line = "\033[32m" + line + "\033[0m"
		ruleName = "\033[93m" + ruleName + "\033[0m"
		message = strings.Replace(message, " => ", " \033[35;1m=>\033[0m ", 1)
	}
	var timeString = ""
	if r.heatmap != nil && w.SamplesTime != 0 {
		timeString = " (" + w.SamplesTime.String() + ")"
	}
	fmt.Fprintf(r.stdout, "%s:%s: %s%s: %s\n", filename, line, ruleName, timeString, message)
}

func (r *runner) handleWarnings(target *lint.Target) error {
	// TODO: don't run imports fixing for every modified file?
	// We can infer which rules may affect the imports set.

	type warningWithFix struct {
		w   *lint.Warning
		fix quickfix.TextEdit
	}

	needFmt := make(map[string]struct{})
	fixablePerFile := make(map[string][]warningWithFix)
	for i := range r.pkgWarnings {
		w := &r.pkgWarnings[i]

		r.stats.affectedSampleTime += w.SamplesTime

		r.stats.issuesTotal++
		if len(w.Fixes) != 0 {
			r.stats.issuesFixable++
		}

		if !r.autofix || len(w.Fixes) == 0 {
			r.reportWarning(w)
			continue
		}
		for i := range w.Fixes {
			pos := target.Fset.Position(w.Fixes[i].From)
			from := pos.Offset
			filename := pos.Filename
			endPos := target.Fset.Position(w.Fixes[i].To)
			to := endPos.Offset
			if w.Fixes[i].Reformat || pos.Line != endPos.Line {
				needFmt[filename] = struct{}{}
			}
			fix := quickfix.TextEdit{
				StartOffset: from,
				EndOffset:   to,
				Replacement: w.Fixes[i].Replacement,
			}
			fixablePerFile[filename] = append(fixablePerFile[filename], warningWithFix{w: w, fix: fix})
		}
	}

	importsConfig := imports.FixConfig{
		StdlibPackages: stdinfo.PathByName,
	}

	for filename, pairs := range fixablePerFile {
		quickfix.Sort(pairs, func(i int) quickfix.TextEdit {
			return pairs[i].fix
		})
		edits := make([]quickfix.TextEdit, len(pairs))
		for i, p := range pairs {
			edits[i] = p.fix
		}
		fileText, err := os.ReadFile(filename)
		if err != nil {
			return err
		}
		afterQuickFixes, overlapping := quickfix.Apply(fileText, edits)
		for _, pairIndex := range overlapping {
			r.reportWarning(pairs[pairIndex].w)
		}
		newText, err := imports.Fix(importsConfig, afterQuickFixes)
		if err != nil {
			return fmt.Errorf("fix imports: %w", err)
		}
		if _, ok := needFmt[filename]; ok {
			newText, err = format.Source(newText)
			if err != nil {
				return fmt.Errorf("gofmt: %w", err)
			}
		}
		if err := os.WriteFile(filename, newText, 0o600); err != nil {
			return err
		}
	}

	return nil
}

func (r *runner) loadPackages(ctx context.Context, fset *token.FileSet, targets []string) ([]*packages.Package, error) {
	r.numLoadCalls++

	loadMode := packages.NeedName |
		packages.NeedFiles |
		packages.NeedSyntax |
		packages.NeedTypes |
		packages.NeedTypesInfo |
		packages.NeedTypesSizes
	config := &packages.Config{
		Mode:    loadMode,
		Tests:   false,
		Fset:    fset,
		Context: ctx,
	}
	start := time.Now()
	loaded, err := packages.Load(config, targets...)
	elapsed := int64(time.Since(start))
	atomic.AddInt64(&r.stats.pkgloadTime, elapsed)
	if err != nil {
		return nil, err
	}
	if err := ctx.Err(); err != nil {
		return nil, fmt.Errorf("context error: %w", err)
	}

	if len(loaded) != len(targets) {
		return nil, fmt.Errorf("expected %d packages, got %d", len(targets), len(loaded))
	}

	for _, pkg := range loaded {
		if len(pkg.Errors) != 0 {
			err := pkg.Errors[0]
			r.pushErrorf(err.Msg, "load %s package: %v", pkg.Name, err)
		}
	}

	return loaded, nil
}

type packageRef struct {
	id   string
	name string
	path string
}

// findPackages returns a list of matched packages for given target patterns.
//
// Note that these packages do not include AST files (syntax) or types info.
// We don't load them right away to avoid OOM situations for big projects.
func (r *runner) findPackages(ctx context.Context, fset *token.FileSet, targets []string) ([]packageRef, error) {
	loadMode := packages.NeedName | packages.NeedFiles
	config := &packages.Config{
		Mode:    loadMode,
		Tests:   false,
		Fset:    fset,
		Context: ctx,
	}
	start := time.Now()
	pkgs, err := packages.Load(config, targets...)
	elapsed := int64(time.Since(start))
	r.stats.pkgfindTime += elapsed
	if err != nil {
		return nil, err
	}
	if err := ctx.Err(); err != nil {
		return nil, fmt.Errorf("context error: %w", err)
	}

	if len(pkgs) == 1 {
		pkg := pkgs[0]
		if pkg.PkgPath == "command-line-arguments" && len(targets) == 1 {
			ref := packageRef{
				id:   pkg.ID,
				name: pkg.Name,
				path: targets[0],
			}
			return []packageRef{ref}, nil
		}
	}

	// We specify tests=false, but just in case we're still going
	// to filter the packages here.
	packageSet := make(map[string]struct{})
	var result []packageRef
	for _, pkg := range pkgs {
		if pkg.Name == "" {
			// Empty or invalid package: not interesting.
			continue
		}
		// Skip any test-like package.
		if pkg.Name == "main" && strings.HasSuffix(pkg.PkgPath, ".test") {
			// Implicit main package for tests.
			continue
		}
		if strings.HasSuffix(pkg.Name, "_test") {
			// External test package, like strings_test.
			continue
		}
		if strings.Contains(pkg.ID, ".test]") {
			// Test version of the package.
			continue
		}

		if len(pkg.GoFiles) == 0 {
			continue
		}

		ref := packageRef{
			id:   pkg.ID,
			name: pkg.Name,
			path: pkg.PkgPath,
		}

		if strings.HasPrefix(pkg.PkgPath, "_/") {
			absFilename := pkg.GoFiles[0]
			relFilename, err := filepath.Rel(r.wd, absFilename)
			if err != nil {
				return nil, err
			}
			ref.path = "./" + filepath.Dir(relFilename)
		}

		if _, ok := packageSet[pkg.PkgPath]; ok {
			continue
		}
		packageSet[pkg.PkgPath] = struct{}{}

		result = append(result, ref)
	}

	return result, nil
}

func (r *runner) inspectHeatmap() {
	r.heatmapPackages = make(map[string]struct{})
	r.heatmapFiles = make(map[string]struct{})
	var totalDuration time.Duration
	r.stats.minSampleTime = time.Duration(math.MaxInt64)
	r.heatmap.Inspect(func(l heatmap.LineStats) {
		d := time.Duration(l.Value)
		if l.GlobalHeatLevel == 0 {
			return
		}
		r.stats.numSamples++
		totalDuration += d
		if r.stats.minSampleTime > d {
			r.stats.minSampleTime = d
		}
		r.heatmapPackages[l.Func.PkgName] = struct{}{}
		r.heatmapFiles[filepath.Base(l.Func.Filename)] = struct{}{}
	})
}

func (r *runner) createHeatmap() (*heatmap.Index, error) {
	data, err := os.ReadFile(r.args.heatmapFile)
	if err != nil {
		return nil, err
	}
	pprofProfile, err := profile.Parse(bytes.NewReader(data))
	if err != nil {
		return nil, err
	}
	index := heatmap.NewIndex(heatmap.IndexConfig{
		Threshold: r.args.heatmapThreshold,
	})
	if err := index.AddProfile(pprofProfile); err != nil {
		return nil, err
	}
	return index, nil
}


<|start_filename|>cmd/perfguard/testdata/quickfix/binaryWrite/out/after.go<|end_filename|>
package main

import (
	"bytes"
	"strings"
)

func main() {
	{
		var buf strings.Builder
		buf.Write([]byte("hello, "))
		buf.Write([]byte("world"))
		println(buf.String())
	}

	{
		var buf strings.Builder
		buf.WriteString("hello, ")
		buf.WriteString("world")
		println(buf.String())
	}

	{
		buf := bytes.NewBuffer(make([]byte, 10))
		buf.Write([]byte("hello, "))
		buf.Write([]byte("world"))
		println(buf.String())
	}

	{
		var buffers [4]bytes.Buffer
		buffers[0].Write([]byte("hello, "))
		buffers[0].Write([]byte("world"))
		println(buffers[0].String())
	}

	{
		var buf strings.Builder
		if _, err := buf.Write([]byte("hello, ")); err != nil {
			panic(err)
		}
		if _, err := buf.Write([]byte("world")); err != nil {
			panic(err)
		}
		println(buf.String())
	}

	{
		var buf strings.Builder
		if _, err := buf.WriteString("hello, "); err != nil {
			panic(err)
		}
		if _, err := buf.WriteString("world"); err != nil {
			panic(err)
		}
		println(buf.String())
	}
}


<|start_filename|>cmd/perfguard/testdata/rulestest/equalFold/equalFold.go<|end_filename|>
package rulestest

import (
	"bytes"
	"strings"
)

func Warn() {
	{
		var x, y string
		_ = strings.ToLower(x) == y                    // want `strings.ToLower(x) == y => strings.EqualFold(x, y)`
		_ = strings.ToLower(x) == strings.ToLower(y)   // want `strings.ToLower(x) == strings.ToLower(y) => strings.EqualFold(x, y)`
		_ = x == strings.ToLower(y)                    // want `x == strings.ToLower(y) => strings.EqualFold(x, y)`
		_ = strings.ToLower(x) != "y"                  // want `strings.ToLower(x) != "y" => !strings.EqualFold(x, "y")`
		_ = strings.ToLower(x) == strings.ToLower("y") // want `strings.ToLower(x) == strings.ToLower("y") => strings.EqualFold(x, "y")`
		_ = x == strings.ToLower("y")                  // want `x == strings.ToLower("y") => strings.EqualFold(x, "y")`
		_ = strings.ToUpper(x) == y                    // want `strings.ToUpper(x) == y => strings.EqualFold(x, y)`
		_ = strings.ToUpper(x) != strings.ToUpper(y)   // want `strings.ToUpper(x) != strings.ToUpper(y) => !strings.EqualFold(x, y)`
		_ = x != strings.ToUpper(y)                    // want `x != strings.ToUpper(y) => !strings.EqualFold(x, y)`
		_ = strings.ToUpper(x) == "y"                  // want `strings.ToUpper(x) == "y" => strings.EqualFold(x, "y")`
		_ = strings.ToUpper(x) == strings.ToUpper("y") // want `strings.ToUpper(x) == strings.ToUpper("y") => strings.EqualFold(x, "y")`
		_ = x == strings.ToUpper("y")                  // want `x == strings.ToUpper("y") => strings.EqualFold(x, "y")`
	}

	{
		var x, y []byte
		_ = bytes.Equal(bytes.ToLower(x), y)                          // want `bytes.Equal(bytes.ToLower(x), y) => bytes.EqualFold(x, y)`
		_ = bytes.Equal(bytes.ToLower(x), bytes.ToLower(y))           // want `bytes.Equal(bytes.ToLower(x), bytes.ToLower(y)) => bytes.EqualFold(x, y)`
		_ = !bytes.Equal(x, bytes.ToLower(y))                         // want `bytes.Equal(x, bytes.ToLower(y)) => bytes.EqualFold(x, y)`
		_ = !bytes.Equal(bytes.ToLower(x), []byte("y"))               // want `bytes.Equal(bytes.ToLower(x), []byte("y")) => bytes.EqualFold(x, []byte("y"))`
		_ = bytes.Equal(bytes.ToLower(x), bytes.ToLower([]byte("y"))) // want `bytes.Equal(bytes.ToLower(x), bytes.ToLower([]byte("y"))) => bytes.EqualFold(x, []byte("y"))`
		_ = bytes.Equal(x, bytes.ToLower([]byte("y")))                // want `bytes.Equal(x, bytes.ToLower([]byte("y"))) => bytes.EqualFold(x, []byte("y"))`
		_ = bytes.Equal(bytes.ToUpper(x), y)                          // want `bytes.Equal(bytes.ToUpper(x), y) => bytes.EqualFold(x, y)`
		_ = !bytes.Equal(bytes.ToUpper(x), bytes.ToUpper(y))          // want `bytes.Equal(bytes.ToUpper(x), bytes.ToUpper(y)) => bytes.EqualFold(x, y)`
		_ = bytes.Equal(x, bytes.ToUpper(y))                          // want `bytes.Equal(x, bytes.ToUpper(y)) => bytes.EqualFold(x, y)`
		_ = bytes.Equal(bytes.ToUpper(x), []byte("y"))                // want `bytes.Equal(bytes.ToUpper(x), []byte("y")) => bytes.EqualFold(x, []byte("y"))`
		_ = bytes.Equal(bytes.ToUpper(x), bytes.ToUpper([]byte("y"))) // want `bytes.Equal(bytes.ToUpper(x), bytes.ToUpper([]byte("y"))) => bytes.EqualFold(x, []byte("y"))`
		_ = bytes.Equal(x, bytes.ToUpper([]byte("y")))                // want `bytes.Equal(x, bytes.ToUpper([]byte("y"))) => bytes.EqualFold(x, []byte("y"))`
	}

	{
		var s1, s2 string
		_ = strings.HasPrefix(strings.ToLower(s1), s2) // want `strings.HasPrefix(strings.ToLower(s1), s2) => (len(s1) >= len(s2) && strings.EqualFold(s1[:len(s2)], s2))`
		_ = strings.HasSuffix(strings.ToLower(s1), s2) // want `strings.HasSuffix(strings.ToLower(s1), s2) => (len(s1) >= len(s2) && strings.EqualFold(s1[len(s1)-len(s2):], s2))`
		_ = strings.HasPrefix(strings.ToUpper(s1), s2) // want `strings.HasPrefix(strings.ToUpper(s1), s2) => (len(s1) >= len(s2) && strings.EqualFold(s1[:len(s2)], s2))`
		_ = strings.HasSuffix(strings.ToUpper(s1), s2) // want `strings.HasSuffix(strings.ToUpper(s1), s2) => (len(s1) >= len(s2) && strings.EqualFold(s1[len(s1)-len(s2):], s2))`
	}
	{
		var b1, b2 []byte
		_ = bytes.HasPrefix(bytes.ToLower(b1), b2) // want `bytes.HasPrefix(bytes.ToLower(b1), b2) => (len(b1) >= len(b2) && bytes.EqualFold(b1[:len(b2)], b2))`
		_ = bytes.HasSuffix(bytes.ToLower(b1), b2) // want `bytes.HasSuffix(bytes.ToLower(b1), b2) => (len(b1) >= len(b2) && bytes.EqualFold(b1[len(b1)-len(b2):], b2))`
		_ = bytes.HasPrefix(bytes.ToUpper(b1), b2) // want `bytes.HasPrefix(bytes.ToUpper(b1), b2) => (len(b1) >= len(b2) && bytes.EqualFold(b1[:len(b2)], b2))`
		_ = bytes.HasSuffix(bytes.ToUpper(b1), b2) // want `bytes.HasSuffix(bytes.ToUpper(b1), b2) => (len(b1) >= len(b2) && bytes.EqualFold(b1[len(b1)-len(b2):], b2))`
	}

	{
		var parts []string
		var someString string
		_ = strings.ToLower(parts[0]) == someString // want `strings.ToLower(parts[0]) == someString => strings.EqualFold(parts[0], someString)`
		_ = strings.ToLower(parts[0]) != someString // want `strings.ToLower(parts[0]) != someString => !strings.EqualFold(parts[0], someString)`
	}
}

func Ignore() {
	{
		var x string
		// Same lhs and rhs.
		_ = strings.ToLower(x) == x
		_ = x == strings.ToLower(x)
		_ = strings.ToLower(x) != x
		_ = x != strings.ToLower(x)
		_ = strings.ToUpper(x) == x
		_ = x == strings.ToUpper(x)
		_ = strings.ToUpper(x) != x
		_ = x != strings.ToUpper(x)
	}
	{
		var b []byte
		_ = bytes.Equal(bytes.ToLower(b), b)
		_ = bytes.Equal(b, bytes.ToLower(b))
		_ = bytes.Equal(bytes.ToUpper(b), b)
		_ = bytes.Equal(b, bytes.ToUpper(b))
	}

	concat := func(x, y string) string {
		return x + y
	}

	{
		var x, y string
		_ = strings.EqualFold(x, y)
		_ = strings.EqualFold(x, concat(y, "123"))
		_ = strings.EqualFold(concat(y, "123"), x)
	}

	{
		var x, y []byte
		_ = bytes.EqualFold(x, y)
		_ = bytes.EqualFold(x, append(y, 'a'))
		_ = bytes.EqualFold(append(y, 'a'), x)
	}

	{
		var s1, s2 string
		var b1, b2 []byte
		// Side effects.
		_ = strings.ToLower(s1) == concat(s2, "123")
		_ = bytes.Equal(bytes.ToLower(b1), append(b2, 'a'))
	}

	{
		var s1, s2 string
		_ = (len(s1) >= len(s2) && strings.EqualFold(s1[:len(s2)], s2))
		_ = (len(s1) >= len(s2) && strings.EqualFold(s1[len(s1)-len(s2):], s2))
		_ = (len(s1) >= len(s2) && strings.EqualFold(s1[:len(s2)], s2))
		_ = (len(s1) >= len(s2) && strings.EqualFold(s1[len(s1)-len(s2):], s2))
	}
	{
		var b1, b2 []byte
		_ = (len(b1) >= len(b2) && bytes.EqualFold(b1[:len(b2)], b2))
		_ = (len(b1) >= len(b2) && bytes.EqualFold(b1[len(b1)-len(b2):], b2))
		_ = (len(b1) >= len(b2) && bytes.EqualFold(b1[:len(b2)], b2))
		_ = (len(b1) >= len(b2) && bytes.EqualFold(b1[len(b1)-len(b2):], b2))
	}

	{
		var parts []string
		var someString string
		_ = strings.EqualFold(parts[0], someString)
		_ = !strings.EqualFold(parts[0], someString)
	}
}


<|start_filename|>cmd/perfguard/testdata/rulestest/appendAPI/appendAPI.go<|end_filename|>
package rulestest

import (
	"math/big"
	"strconv"
	"time"
)

func Warn(b []byte, i int, i64 int64, u64 uint64, t *time.Time) {
	b = append(b, strconv.Itoa(i)...) // want `b = append(b, strconv.Itoa(i)...) => b = strconv.AppendInt(b, int64(i), 10)`

	b = append(b, strconv.FormatInt(i64, 10)...) // want `b = append(b, strconv.FormatInt(i64, 10)...) => b = strconv.AppendInt(b, i64, 10)`
	b = append(b, strconv.FormatInt(i64, 16)...) // want `b = append(b, strconv.FormatInt(i64, 16)...) => b = strconv.AppendInt(b, i64, 16)`

	b = append(b, strconv.FormatUint(u64, 10)...) // want `b = append(b, strconv.FormatUint(u64, 10)...) => b = strconv.AppendUint(b, u64, 10)`
	b = append(b, strconv.FormatUint(u64, 8)...)  // want `b = append(b, strconv.FormatUint(u64, 8)...) => b = strconv.AppendUint(b, u64, 8)`

	b = append(b, t.Format(time.UnixDate)...) // want `b = append(b, t.Format(time.UnixDate)...) => b = t.AppendFormat(b, time.UnixDate)`

	{
		var bf big.Float
		b = append(b, bf.String()...)      // want `b = append(b, bf.String()...) => b = bf.Append(b, 'g', 10)`
		b = append(b, bf.Text('g', 15)...) // want `b = append(b, bf.Text('g', 15)...) => b = bf.Append(b, 'g', 15)`
	}
	{
		var bi big.Int
		b = append(b, bi.String()...) // want `b = append(b, bi.String()...) => b = bi.Append(b, 10)`
		b = append(b, bi.Text(16)...) // want `b = append(b, bi.Text(16)...) => b = bi.Append(b, 16)`
	}
}

func Ignore(b []byte, i int, i64 int64, u64 uint64, t *time.Time) {
	b = strconv.AppendInt(b, i64, 10)

	b = strconv.AppendInt(b, i64, 10)
	b = strconv.AppendInt(b, i64, 16)

	b = strconv.AppendUint(b, u64, 10)
	b = strconv.AppendUint(b, u64, 8)

	b = t.AppendFormat(b, time.UnixDate)

	{
		var bf big.Float
		b = bf.Append(b, 'g', 10)
		b = bf.Append(b, 'g', 15)
	}
	{
		var bi big.Int
		b = bi.Append(b, 10)
		b = bi.Append(b, 16)
	}
}


<|start_filename|>perfguard/checkers/funccheckers/condReorder.go<|end_filename|>
package funccheckers

import (
	"go/ast"
	"go/token"

	"github.com/go-toolsmith/astcopy"
	"github.com/quasilyte/go-perfguard/internal/typeis"
	"github.com/quasilyte/go-perfguard/perfguard/checkers"
	"github.com/quasilyte/go-perfguard/perfguard/lint"
)

func init() {
	doc := checkers.Doc{
		Name:  "condReorder",
		Score: 2,
	}
	checkers.RegisterFuncChecker(doc, func() checkers.FuncChecker {
		return &condReorderChecker{}
	})
}

type condReorderChecker struct {
	ctx *lint.Context

	order []int
}

type condReorderExprInfo struct {
	hasCall bool
}

func (c *condReorderChecker) CheckFunc(ctx *lint.Context, body *ast.BlockStmt) error {
	c.ctx = ctx
	c.order = c.order[:0]

	ast.Inspect(body, c.walk)

	return nil
}

func (c *condReorderChecker) walk(n ast.Node) bool {
	// We only analyze the most common places for conditions that may matter.

	switch n := n.(type) {
	case *ast.IfStmt:
		c.walkCond(n.Cond)

	case *ast.ForStmt:
		if n.Cond != nil {
			c.walkCond(n.Cond)
		}

	case *ast.ReturnStmt:
		for _, x := range n.Results {
			c.walkCond(x)
		}

	case *ast.AssignStmt:
		for _, x := range n.Rhs {
			c.walkCond(x)
		}
	}

	return true
}

func (c *condReorderChecker) walkCond(cond ast.Expr) bool {
	e, ok := cond.(*ast.BinaryExpr)
	if !ok {
		return false
	}

	if e.Op != token.LAND && e.Op != token.LOR {
		return false
	}

	stop := false
	if lhs, ok := e.X.(*ast.BinaryExpr); ok && (lhs.Op == token.LAND || lhs.Op == token.LOR) {
		stop = true
		if c.walkCond(lhs) {
			return true
		}
	}
	if rhs, ok := e.X.(*ast.BinaryExpr); ok && (rhs.Op == token.LAND || rhs.Op == token.LOR) {
		stop = true
		if c.walkCond(rhs) {
			return true
		}
	}
	if stop {
		return false
	}

	var lhsInfo condReorderExprInfo
	var rhsInfo condReorderExprInfo
	if !c.exprCost(&lhsInfo, e.X) || !c.exprCost(&rhsInfo, e.Y) {
		return false
	}

	// expensive() && simple => simple && expensive()
	// expensive() || simple => simple || expensive()
	if lhsInfo.hasCall && !rhsInfo.hasCall {
		// Now check that lhs doesn't depend on rhs (and vice versa).
		// This is a rare branch, so we can afford a slow algorithm here.
		if c.independent(e.X, e.Y) {
			reordered := astcopy.BinaryExpr(e)
			reordered.X, reordered.Y = reordered.Y, reordered.X
			c.ctx.SuggestNode(lint.SuggestParams{
				OldNode: e,
				NewNode: reordered,
			})
			return true
		}
	}

	return false
}

func (c *condReorderChecker) independent(x, y ast.Expr) bool {
	var xVars map[string]struct{}
	ast.Inspect(x, func(n ast.Node) bool {
		if id, ok := n.(*ast.Ident); ok {
			if xVars == nil {
				xVars = make(map[string]struct{})
			}
			xVars[id.Name] = struct{}{}
		}
		return true
	})
	found := false
	ast.Inspect(y, func(n ast.Node) bool {
		if found {
			return false
		}
		if id, ok := n.(*ast.Ident); ok {
			if _, ok := xVars[id.Name]; ok {
				found = true
			}
		}
		return true
	})
	return !found
}

func (c *condReorderChecker) exprCost(info *condReorderExprInfo, e ast.Expr) bool {
	switch e := e.(type) {
	case *ast.Ident, *ast.BasicLit:
		// Trivial, can analyze.
		return true

	case *ast.SelectorExpr:
		return c.exprCost(info, e.X)

	case *ast.StarExpr:
		return c.exprCost(info, e.X)

	case *ast.CallExpr:
		called, ok := e.Fun.(*ast.Ident)
		if ok {
			switch called.Name {
			case "len", "cap":
				if len(e.Args) != 1 {
					return false
				}
				return c.exprCost(info, e.Args[0])
			}
		}
		info.hasCall = true
		return true

	case *ast.BinaryExpr:
		switch e.Op {
		case token.ADD, token.SUB:
			// OK.
		default:
			return false
		}
		return c.exprCost(info, e.X) && c.exprCost(info, e.Y)

	case *ast.ParenExpr:
		return c.exprCost(info, e.X)

	case *ast.UnaryExpr:
		switch e.Op {
		case token.ADD, token.SUB, token.NOT:
			return c.exprCost(info, e.X)
		}

	case *ast.IndexExpr:
		if typeis.Map(c.ctx.TypeOf(e.X)) {
			return false
		}
		return c.exprCost(info, e.X) && c.exprCost(info, e.Index)
	}

	return false
}


<|start_filename|>internal/quickfix/quickfix.go<|end_filename|>
package quickfix

import (
	"bytes"
	"sort"
)

// TextEdit is a suggested issue fix.
//
// More or less, it represents our version of the https://godoc.org/golang.org/x/tools/go/analysis#TextEdit
// which is a part of https://godoc.org/golang.org/x/tools/go/analysis#SuggestedFix.
type TextEdit struct {
	StartOffset int
	EndOffset   int

	// Replacement is a text to be inserted as a Replacement.
	Replacement []byte
}

func Sort(slice interface{}, get func(i int) TextEdit) {
	sort.SliceStable(slice, func(i, j int) bool {
		x := get(i)
		y := get(j)
		if x.StartOffset != y.StartOffset {
			return x.StartOffset < y.StartOffset
		}
		return x.EndOffset < y.EndOffset
	})
}

// Apply returns updated src with edits applied to it.
//
// Second return value contains indexes from edits slice
// that were not applied due to overlapping.
func Apply(src []byte, edits []TextEdit) (out []byte, indexes []int) {
	if len(edits) == 0 {
		return src, nil
	}

	Sort(edits, func(i int) TextEdit {
		return edits[i]
	})

	var overlapping []int
	var buf bytes.Buffer
	buf.Grow(len(src))
	offset := 0
	for i, fix := range edits {
		// If we have a nested replacement, apply only outer replacement.
		if offset > fix.StartOffset {
			overlapping = append(overlapping, i)
			continue
		}

		buf.Write(src[offset:fix.StartOffset])
		buf.Write(fix.Replacement)

		offset = fix.EndOffset
	}
	buf.Write(src[offset:])

	return buf.Bytes(), overlapping
}


<|start_filename|>cmd/perfguard/testdata/rulestest/syncPoolPut/syncPoolPut.go<|end_filename|>
package syncPoolNonPtr

import (
	"io"
	"sync"
	"unsafe"
)

var pool sync.Pool

func Warn() {
	{
		var i int
		pool.Put(i)  // want `non-pointer values in sync.Pool involve extra allocation`
		pool.Put(10) // want `non-pointer values in sync.Pool involve extra allocation`
	}

	{
		var u uintptr
		pool.Put(u) // want `non-pointer values in sync.Pool involve extra allocation`
	}

	{
		type point struct {
			x, y int
		}
		var pt point
		pool.Put(pt) // want `non-pointer values in sync.Pool involve extra allocation`
	}

	{
		var a [4]int
		pool.Put(a) // want `non-pointer values in sync.Pool involve extra allocation`
	}

	{
		var b []byte
		pool.Put(b) // want `non-pointer values in sync.Pool involve extra allocation`
	}
}

func Ignore() {
	{
		var i int
		pool.Put(&i)
	}

	{
		type myChan chan int
		var ch chan int
		var myCh myChan
		pool.Put(ch)
		pool.Put(&ch)
		pool.Put(myCh)
		pool.Put(&myCh)
	}

	{
		type point struct {
			x, y int
		}
		var pt point
		pool.Put(&pt)
	}

	{
		type myMap map[int]string
		var m map[int]string
		var myM myMap
		pool.Put(m)
		pool.Put(&m)
		pool.Put(myM)
		pool.Put(&myM)
	}

	{
		type myEface interface{}
		var eface interface{}
		var e2 myEface
		var r io.Reader
		pool.Put(eface)
		pool.Put(&eface)
		pool.Put(e2)
		pool.Put(&e2)
		pool.Put(r)
		pool.Put(&r)
	}

	{
		type myFunc func(int) bool
		var f1 func()
		var f2 myFunc
		pool.Put(f1)
		pool.Put(&f1)
		pool.Put(f2)
		pool.Put(&f2)
		pool.Put(Ignore)
	}

	{
		var ptr unsafe.Pointer
		pool.Put(ptr)
		pool.Put(&ptr)
	}
}


<|start_filename|>cmd/perfguard/rules_go1_18_test.go<|end_filename|>
//go:build go1.18
// +build go1.18

package main

import (
	"path/filepath"
	"testing"
)

func TestRulesGo1_18(t *testing.T) {
	rules := readdir(t, filepath.Join("testdata", "rulestest"))
	for _, name := range rules {
		key := filepath.Base(name)
		if ver := testVersionConstraints[key]; ver != "1.18" {
			continue
		}
		runRulesTest(t, key)
	}
}


<|start_filename|>cmd/perfguard/testdata/rulestest/writeBytes/writeBytes.go<|end_filename|>
package rulestest

import (
	"bytes"
	"io"
)

type weirdWriter struct{}

func (w weirdWriter) Write([]byte) error       { return nil }
func (w weirdWriter) WriteString(string) error { return nil }

func Warn(src *bytes.Buffer, w io.Writer, dst *bytes.Buffer) {
	io.WriteString(w, src.String())        // want `io.WriteString(w, src.String()) => w.Write(src.Bytes())`
	io.WriteString(w, string(src.Bytes())) // want `io.WriteString(w, string(src.Bytes())) => w.Write(src.Bytes())`
	dst.WriteString(src.String())          // want `dst.WriteString(src.String()) => dst.Write(src.Bytes())`

	{
		var buf bytes.Buffer
		buf.WriteString(src.String()) // want `buf.WriteString(src.String()) => buf.Write(src.Bytes())`
	}

	{
		var b []byte
		var buf bytes.Buffer
		bufPtr := &buf
		buf.WriteString(string(b))    // want `buf.WriteString(string(b)) => buf.Write(b)`
		bufPtr.WriteString(string(b)) // want `bufPtr.WriteString(string(b)) => bufPtr.Write(b)`
	}
}

func Ignore(src *bytes.Buffer, w io.Writer, dst *bytes.Buffer) {
	w.Write(src.Bytes())
	w.Write(src.Bytes())
	dst.Write(src.Bytes())

	{
		var ww weirdWriter
		var b []byte
		ww.WriteString(string(src.Bytes()))
		ww.WriteString(string(b))
	}
}
