blob: 7ecb0b77fc8535acd3a16194da9a8056c846f802 [file] [log] [blame]
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fix
import (
"testing"
)
func TestConvertToSetter(t *testing.T) {
tests := []test{
{
desc: "assignment",
srcfiles: []string{"pkg.go"},
in: `
mypb := &pb2.M2{
I32: proto.Int32(23),
M: &pb2.M2{
I32: proto.Int32(42),
},
}
_ = mypb
for idx, val := range []int32{123, 456} {
idx, val := idx, val
go func() { println(idx, val) }()
}
`,
want: map[Level]string{
Red: `
m2h2 := &pb2.M2{}
m2h2.SetI32(42)
mypb := &pb2.M2{}
mypb.SetI32(23)
mypb.SetM(m2h2)
_ = mypb
for idx, val := range []int32{123, 456} {
idx, val := idx, val
go func() { println(idx, val) }()
}
`,
},
},
{
desc: "multi assignment",
srcfiles: []string{"pkg.go"},
in: `
my1, my2 := &pb2.M2{
I32: proto.Int32(23),
}, &pb2.M2{
I64: proto.Int64(12345),
}
_, _ = my1, my2
`,
want: map[Level]string{
Red: `
my1 := &pb2.M2{}
my1.SetI32(23)
my2 := &pb2.M2{}
my2.SetI64(12345)
_, _ = my1, my2
`,
},
},
{
desc: "struct literal field",
srcfiles: []string{"pkg.go"},
in: `
func() *pb2.M2 {
return &pb2.M2{
I32: proto.Int32(23),
}
}()
`,
want: map[Level]string{
Red: `
func() *pb2.M2 {
m2h2 := &pb2.M2{}
m2h2.SetI32(23)
return m2h2
}()
`,
},
},
{
desc: "nested builder",
srcfiles: []string{"pkg.go"},
in: `
_ = &pb2.M2{
I32: proto.Int32(23),
M: &pb2.M2{
I32: proto.Int32(42),
},
Is: []int32{10, 11},
}
`,
want: map[Level]string{
Red: `
m2h2 := &pb2.M2{}
m2h2.SetI32(42)
m2h3 := &pb2.M2{}
m2h3.SetI32(23)
m2h3.SetM(m2h2)
m2h3.SetIs([]int32{10, 11})
_ = m2h3
`,
},
},
{
desc: "if conditional",
srcfiles: []string{"pkg.go"},
extra: `func validateProto(msg *pb2.M2) bool { return false }`,
in: `
if msg := (&pb2.M2{
I32: proto.Int32(23),
M: &pb2.M2{
I32: proto.Int32(42),
},
Is: []int32{10, 11},
}); validateProto(msg) {
println("validated")
}
`,
want: map[Level]string{
Red: `
m2h2 := &pb2.M2{}
m2h2.SetI32(42)
m2h3 := &pb2.M2{}
m2h3.SetI32(23)
m2h3.SetM(m2h2)
m2h3.SetIs([]int32{10, 11})
if msg := (m2h3); validateProto(msg) {
println("validated")
}
`,
},
},
{
desc: "case clause",
srcfiles: []string{"pkg.go"},
extra: `func validateProto(msg *pb2.M2) bool { return false }`,
in: `
switch {
case validateProto(nil):
_ = &pb2.M2{
I32: proto.Int32(23),
}
}
`,
want: map[Level]string{
Red: `
switch {
case validateProto(nil):
m2h2 := &pb2.M2{}
m2h2.SetI32(23)
_ = m2h2
}
`,
},
},
{
desc: "select clause",
srcfiles: []string{"pkg.go"},
in: `
dummy := make(chan bool)
select {
case <-dummy:
_ = &pb2.M2{
I32: proto.Int32(23),
}
}
`,
want: map[Level]string{
Red: `
dummy := make(chan bool)
select {
case <-dummy:
m2h2 := &pb2.M2{}
m2h2.SetI32(23)
_ = m2h2
}
`,
},
},
{
desc: "never nil byte expression",
srcfiles: []string{"pkg.go"},
in: `
var b []byte
m2.Bytes = b[2:3]
m2.Bytes = b[:3]
m2.Bytes = b[2:]
m2.Bytes = b[:]
m2.Bytes = b[0:]
m2.Bytes = b[:0]
m2.Bytes = b[0:0]
`,
want: map[Level]string{
Green: `
var b []byte
m2.SetBytes(b[2:3])
m2.SetBytes(b[:3])
m2.SetBytes(b[2:])
if x := b[:]; x != nil {
m2.SetBytes(x)
} else {
m2.ClearBytes()
}
if x := b[0:]; x != nil {
m2.SetBytes(x)
} else {
m2.ClearBytes()
}
if x := b[:0]; x != nil {
m2.SetBytes(x)
} else {
m2.ClearBytes()
}
if x := b[0:0]; x != nil {
m2.SetBytes(x)
} else {
m2.ClearBytes()
}
`,
},
},
{
desc: "variable declaration block",
srcfiles: []string{"pkg.go"},
in: `
var (
_ = &pb2.M2{
I32: proto.Int32(23),
}
)
`,
want: map[Level]string{
Red: `
m2h2 := &pb2.M2{}
m2h2.SetI32(23)
var (
_ = m2h2
)
`,
},
},
{
desc: "defer statement",
srcfiles: []string{"pkg.go"},
extra: `func validateProto(msg *pb2.M2) bool { return false }`,
in: `
defer validateProto(&pb2.M2{
I32: proto.Int32(23),
})
`,
want: map[Level]string{
Red: `
m2h2 := &pb2.M2{}
m2h2.SetI32(23)
defer validateProto(m2h2)
`,
},
},
{
desc: "go statement",
srcfiles: []string{"pkg.go"},
extra: `func validateProto(msg *pb2.M2) bool { return false }`,
in: `
go validateProto(&pb2.M2{
I32: proto.Int32(23),
})
`,
want: map[Level]string{
Red: `
m2h2 := &pb2.M2{}
m2h2.SetI32(23)
go validateProto(m2h2)
`,
},
},
{
desc: "for loop",
srcfiles: []string{"pkg.go"},
extra: `func validateProto(msg *pb2.M2) bool { return false }`,
in: `
for m2 := (&pb2.M2{
I32: proto.Int32(23),
}); m2 != nil; m2 = nil {
}
`,
want: map[Level]string{
Red: `
m2h2 := &pb2.M2{}
m2h2.SetI32(23)
for m2 := (m2h2); m2 != nil; m2 = nil {
}
`,
},
},
{
desc: "range statement",
srcfiles: []string{"pkg.go"},
in: `
for _, idx := range []int{1, 2, 3} {
_ = idx
_ = &pb2.M2{
I32: proto.Int32(23),
}
}
`,
want: map[Level]string{
Red: `
for _, idx := range []int{1, 2, 3} {
_ = idx
m2h2 := &pb2.M2{}
m2h2.SetI32(23)
_ = m2h2
}
`,
},
},
{
desc: "labeled statement",
srcfiles: []string{"pkg.go"},
in: `
goto validate
println("this line is skipped")
validate:
println(&pb2.M2{
I32: proto.Int32(23),
})
`,
want: map[Level]string{
Red: `
goto validate
println("this line is skipped")
validate:
m2h2 := &pb2.M2{}
m2h2.SetI32(23)
println(m2h2)
`,
},
},
{
desc: "slice",
srcfiles: []string{"pkg.go"},
in: `
for _, msg := range []*pb2.M2{
{
I32: proto.Int32(23),
},
&pb2.M2{
I64: proto.Int64(123),
},
} {
println(msg)
}
`,
want: map[Level]string{
Red: `
m2h2 := &pb2.M2{}
m2h2.SetI32(23)
m2h3 := &pb2.M2{}
m2h3.SetI64(123)
for _, msg := range []*pb2.M2{
m2h2,
m2h3,
} {
println(msg)
}
`,
},
},
{
desc: "conditional assignment",
srcfiles: []string{"pkg.go"},
in: `
var mypb *pb2.M2
if true {
mypb = &pb2.M2{
I32: proto.Int32(23),
}
}
_ = mypb
`,
want: map[Level]string{
Red: `
var mypb *pb2.M2
if true {
mypb = &pb2.M2{}
mypb.SetI32(23)
}
_ = mypb
`,
},
},
{
desc: "assignment with different type",
srcfiles: []string{"pkg.go"},
in: `
var mypb proto.Message
if true {
mypb = &pb2.M2{
I32: proto.Int32(23),
}
}
_ = mypb
`,
want: map[Level]string{
Green: `
var mypb proto.Message
if true {
m2h2 := &pb2.M2{}
m2h2.SetI32(23)
mypb = m2h2
}
_ = mypb
`,
},
},
{
desc: "slice with comments",
srcfiles: []string{"pkg.go"},
in: `
for _, msg := range []*pb2.M2{
// Comment above first literal
{
I32: proto.Int32(23), // End-of-line comment for I32
},
// Comment above second literal
&pb2.M2{
// Comment above I64
I64: proto.Int64(/* before 123 */ 123 /* after 123 */),
// Comment at the end of the second literal
}, // End-of-line comment after second literal
} {
println(msg)
}
`,
want: map[Level]string{
Red: `
// Comment above first literal
m2h2 := &pb2.M2{}
m2h2.SetI32(23) // End-of-line comment for I32
// Comment above second literal
m2h3 := &pb2.M2{}
// Comment above I64
m2h3.SetI64(123 /* after 123 */)
// Comment at the end of the second literal
for _, msg := range []*pb2.M2{
m2h2,
m2h3, // End-of-line comment after second literal
} {
println(msg)
}
`,
},
},
}
runTableTests(t, tests)
}
func TestPointerDereference(t *testing.T) {
tests := []test{
{
desc: "assignment, lhs proto field dereference",
srcfiles: []string{"pkg.go"},
in: `
if val := m2.I32; val != nil {
*val = int32(42)
}
`,
want: map[Level]string{
Red: `
if m2.HasI32() {
m2.SetI32(int32(42))
}
`,
},
},
{
desc: "multi-assign, lhs proto field dereference",
srcfiles: []string{"pkg.go"},
in: `
var i int
if val := m2.I32; val != nil {
i, *val, i = 5, int32(42), 6
}
_ = i
`,
want: map[Level]string{
Red: `
var i int
if m2.HasI32() {
i = 5
m2.SetI32(int32(42))
i = 6
}
_ = i
`,
},
},
{
desc: "multiple assignments, lhs proto field dereference",
srcfiles: []string{"pkg.go"},
in: `
var i int
if val := m2.I32; val != nil {
*val, i = int32(42), 5
i, *val = 6, int32(43)
}
_ = i
`,
want: map[Level]string{
Red: `
var i int
if m2.HasI32() {
m2.SetI32(int32(42))
i = 5
i = 6
m2.SetI32(int32(43))
}
_ = i
`,
},
},
{
desc: "multi-assign, lhs and rhs",
srcfiles: []string{"pkg.go"},
in: `
var i int
if val := m2.I32; val != nil {
*val, i = *val+2, 5
i, *val = 6, *val+5
}
_ = i
`,
want: map[Level]string{
Red: `
var i int
if m2.HasI32() {
m2.SetI32(m2.GetI32() + 2)
i = 5
i = 6
m2.SetI32(m2.GetI32() + 5)
}
_ = i
`,
},
},
{
desc: "ifstmt: potential side effect initializer",
srcfiles: []string{"pkg.go"},
extra: "func f(m *pb2.M2) *int32 { return m.I32 }",
in: `
if val := f(m2); val != nil {
*val = int32(42)
}
`,
want: map[Level]string{
Red: `
if val := f(m2); val != nil {
*val = int32(42)
}
`,
},
},
}
runTableTests(t, tests)
}
func TestEnum(t *testing.T) {
tests := []test{
{
desc: "basic case",
srcfiles: []string{"pkg.go"},
in: `
_ = &pb2.M2{
E: pb2.M2_E_VAL.Enum(),
}
`,
want: map[Level]string{
Red: `
m2h2 := &pb2.M2{}
m2h2.SetE(pb2.M2_E_VAL)
_ = m2h2
`,
},
},
{
desc: "free function with explicit receiver",
srcfiles: []string{"pkg.go"},
in: `
_ = &pb2.M2{
E: pb2.M2_Enum.Enum(pb2.M2_E_VAL),
}
`,
want: map[Level]string{
Red: `
m2h2 := &pb2.M2{}
m2h2.SetE(pb2.M2_E_VAL)
_ = m2h2
`,
},
},
}
runTableTests(t, tests)
}
func TestNameGeneration(t *testing.T) {
tests := []test{
{
desc: "basic case",
srcfiles: []string{"pkg.go"},
in: `
m2h2 := 5
{
_ = &pb2.M2{
S: proto.String("Hello World!"),
}
}
_ = m2h2
`,
want: map[Level]string{
Red: `
m2h2 := 5
{
m2h3 := &pb2.M2{}
m2h3.SetS("Hello World!")
_ = m2h3
}
_ = m2h2
`,
},
},
{
desc: "local definition",
srcfiles: []string{"pkg.go"},
in: `
if m2h2 := int32(5); m2h2 < 5 {
_ = &pb2.M2{
S: proto.String("Hello World!"),
I32: proto.Int32(m2h2),
}
}
`,
want: map[Level]string{
Red: `
if m2h2 := int32(5); m2h2 < 5 {
m2h3 := &pb2.M2{}
m2h3.SetS("Hello World!")
m2h3.SetI32(m2h2)
_ = m2h3
}
`,
},
},
{
desc: "sub message",
srcfiles: []string{"pkg.go"},
in: `
if op2 := (&pb2.OtherProto2{}); op2 != nil {
_ = &pb2.OtherProto2{
M: &pb2.OtherProto2{},
Ms: []*pb2.OtherProto2{op2},
}
}
`,
want: map[Level]string{
Red: `
if op2 := (&pb2.OtherProto2{}); op2 != nil {
op2h2 := &pb2.OtherProto2{}
op2h2.SetM(&pb2.OtherProto2{})
op2h2.SetMs([]*pb2.OtherProto2{op2})
_ = op2h2
}
`,
},
},
{
desc: "within if statement",
srcfiles: []string{"pkg.go"},
extra: "func extra(*pb2.M2) bool { return true }",
in: `
if extra(&pb2.M2{I32: proto.Int32(42)}) {
}
if extra(&pb2.M2{I32: proto.Int32(42)}) {
}
`,
want: map[Level]string{
Green: `
m2h2 := &pb2.M2{}
m2h2.SetI32(42)
if extra(m2h2) {
}
m2h3 := &pb2.M2{}
m2h3.SetI32(42)
if extra(m2h3) {
}
`,
},
},
}
runTableTests(t, tests)
}
func TestParentheses(t *testing.T) {
tests := []test{
{
desc: "basic case",
srcfiles: []string{"pkg.go"},
in: `
p := int32(42)
m2.I32 = &((p))
`,
want: map[Level]string{
Red: `
p := int32(42)
m2.SetI32(p)
`,
},
},
}
runTableTests(t, tests)
}
func TestUnshadow(t *testing.T) {
tests := []test{
{
desc: "struct literal",
srcfiles: []string{"pkg.go"},
in: `
m2 = &pb2.M2{M: m2}
`,
want: map[Level]string{
Green: `
m2h2 := m2
m2 = &pb2.M2{}
m2.SetM(m2h2)
`,
},
},
{
desc: "selectorexpr",
srcfiles: []string{"pkg.go"},
in: `
var unrelated struct{
zone string
}
zone := &pb2.M2{S: proto.String(unrelated.zone)}
_ = zone
`,
want: map[Level]string{
Green: `
var unrelated struct {
zone string
}
zone := &pb2.M2{}
zone.SetS(unrelated.zone)
_ = zone
`,
},
},
{
desc: "struct literal, define",
srcfiles: []string{"pkg.go"},
in: `
{
m2 := &pb2.M2{M: m2}
_ = m2
}
`,
want: map[Level]string{
Green: `
{
m2h2 := m2
m2 := &pb2.M2{}
m2.SetM(m2h2)
_ = m2
}
`,
},
},
{
desc: "append",
srcfiles: []string{"pkg.go"},
in: `
all := &pb2.M2{}
all.Ms = append(all.Ms, m2)
`,
want: map[Level]string{
Green: `
all := &pb2.M2{}
all.SetMs(append(all.GetMs(), m2))
`,
},
},
}
runTableTests(t, tests)
}
func TestConflictingNames(t *testing.T) {
tests := []test{
{
desc: "struct literal",
srcfiles: []string{"pkg.go"},
in: `
m := &pb2.SetterNameConflict{
Stat: proto.Int32(int32(5)),
SetStat: proto.Int32(int32(42)),
GetStat_: proto.Int32(int32(42)),
HasStat: proto.Int32(int32(42)),
ClearStat: proto.Int32(int32(42)),
}
_ = m
_ = *m.Stat
if m.Stat != nil || m.Stat == nil {
m.Stat = nil
}
`,
want: map[Level]string{
// The SetGetStat_ is wrong. It should be SetGetStat.
// Fixing it requires the proto descriptor and thus,
// is not worth it.
// Technically we should also generate Get_Stat()
// instead of GetStat() but again it requires the
// proto descriptor.
// Both of these cases should be extremely rare.
Green: `
m := &pb2.SetterNameConflict{}
m.Set_Stat(int32(5))
m.SetSetStat(int32(42))
m.SetGetStat_(int32(42))
m.SetHasStat(int32(42))
m.SetClearStat(int32(42))
_ = m
_ = m.GetStat()
if m.Has_Stat() || !m.Has_Stat() {
m.Clear_Stat()
}
`,
},
},
}
runTableTests(t, tests)
}