diff --git a/.golangci.reference.yml b/.golangci.reference.yml index a65423960d8a..f099192b5018 100644 --- a/.golangci.reference.yml +++ b/.golangci.reference.yml @@ -2234,6 +2234,7 @@ linters: - prealloc - predeclared - promlinter + - protogetter - reassign - revive - rowserrcheck @@ -2348,6 +2349,7 @@ linters: - prealloc - predeclared - promlinter + - protogetter - reassign - revive - rowserrcheck diff --git a/go.mod b/go.mod index 7c591dc8fd4b..7e37aef04d0a 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,7 @@ require ( github.com/fatih/color v1.15.0 github.com/firefart/nonamedreturns v1.0.4 github.com/fzipp/gocyclo v0.6.0 + github.com/ghostiam/protogetter v0.2.2 github.com/go-critic/go-critic v0.9.0 github.com/go-xmlfmt/xmlfmt v1.1.2 github.com/gofrs/flock v0.8.1 diff --git a/go.sum b/go.sum index 31c8fb9c5f0c..354525c12d92 100644 --- a/go.sum +++ b/go.sum @@ -144,6 +144,8 @@ github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwV github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= +github.com/ghostiam/protogetter v0.2.2 h1:eWwreOprO2z+4x8eiIFGvA54n2XHoPg01A/XG1rMMKQ= +github.com/ghostiam/protogetter v0.2.2/go.mod h1:KmNLOsy1v04hKbvZs8EfGI1fk39AgTdRDxWNYPfXVc4= github.com/go-critic/go-critic v0.9.0 h1:Pmys9qvU3pSML/3GEQ2Xd9RZ/ip+aXHKILuxczKGV/U= github.com/go-critic/go-critic v0.9.0/go.mod h1:5P8tdXL7m/6qnyG6oRAlYLORvoXH0WDypYgAEmagT40= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= diff --git a/pkg/golinters/protogetter.go b/pkg/golinters/protogetter.go new file mode 100644 index 000000000000..23325ad55e73 --- /dev/null +++ b/pkg/golinters/protogetter.go @@ -0,0 +1,59 @@ +package golinters + +import ( + "sync" + + "github.com/ghostiam/protogetter" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" + "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/result" +) + +func NewProtoGetter() *goanalysis.Linter { + var mu sync.Mutex + var resIssues []goanalysis.Issue + + a := protogetter.NewAnalyzer() + a.Run = func(pass *analysis.Pass) (any, error) { + pgIssues := protogetter.Run(pass, protogetter.GolangciLintMode) + + issues := make([]goanalysis.Issue, len(pgIssues)) + for i, issue := range pgIssues { + report := &result.Issue{ + FromLinter: a.Name, + Pos: issue.Pos, + Text: issue.Message, + Replacement: &result.Replacement{ + Inline: &result.InlineFix{ + StartCol: issue.InlineFix.StartCol, + Length: issue.InlineFix.Length, + NewString: issue.InlineFix.NewString, + }, + }, + } + + issues[i] = goanalysis.NewIssue(report, pass) + } + + if len(issues) == 0 { + return nil, nil + } + + mu.Lock() + resIssues = append(resIssues, issues...) + mu.Unlock() + + return nil, nil + } + + return goanalysis.NewLinter( + a.Name, + a.Doc, + []*analysis.Analyzer{a}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + return resIssues + }).WithLoadMode(goanalysis.LoadModeTypesInfo) +} diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index 4de3a1116f58..87a4c3969e21 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -715,6 +715,13 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle). WithURL("https://github.com/yeya24/promlinter"), + linter.NewConfig(golinters.NewProtoGetter()). + WithSince("v1.55.0"). + WithPresets(linter.PresetBugs). + WithLoadForGoAnalysis(). + WithAutoFix(). + WithURL("https://github.com/ghostiam/protogetter"), + linter.NewConfig(golinters.NewReassign(reassignCfg)). WithSince("1.49.0"). WithPresets(linter.PresetBugs). diff --git a/test/linters_test.go b/test/linters_test.go index dd130db3e7db..75d4f44adf1d 100644 --- a/test/linters_test.go +++ b/test/linters_test.go @@ -32,6 +32,7 @@ func TestSourcesFromTestdataSubDir(t *testing.T) { "loggercheck", "ginkgolinter", "zerologlint", + "protogetter", } for _, dir := range subDirs { diff --git a/test/testdata/protogetter/go.mod b/test/testdata/protogetter/go.mod new file mode 100644 index 000000000000..6120ad190c30 --- /dev/null +++ b/test/testdata/protogetter/go.mod @@ -0,0 +1,16 @@ +module protogetter + +go 1.19 + +require ( + google.golang.org/grpc v1.57.0 + google.golang.org/protobuf v1.31.0 +) + +require ( + github.com/golang/protobuf v1.5.3 // indirect + golang.org/x/net v0.9.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/text v0.9.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect +) diff --git a/test/testdata/protogetter/go.sum b/test/testdata/protogetter/go.sum new file mode 100644 index 000000000000..820c8ebcdca8 --- /dev/null +++ b/test/testdata/protogetter/go.sum @@ -0,0 +1,20 @@ +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= +google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/test/testdata/protogetter/proto/test.go b/test/testdata/protogetter/proto/test.go new file mode 100644 index 000000000000..e48a033c009f --- /dev/null +++ b/test/testdata/protogetter/proto/test.go @@ -0,0 +1,23 @@ +package proto + +func (x *Embedded) CustomMethod() interface{} { + return nil +} + +type Other struct { +} + +func (x *Other) MyMethod(certs *Test) *Embedded { + return nil +} + +func (x *Test) Equal(v *Test) bool { + return false +} + +func (x *Embedded) SetS(s string) { + x.S = s +} + +func (x *Embedded) SetMap(_ map[string]string) { +} diff --git a/test/testdata/protogetter/proto/test.pb.go b/test/testdata/protogetter/proto/test.pb.go new file mode 100644 index 000000000000..64e7b6a37f4e --- /dev/null +++ b/test/testdata/protogetter/proto/test.pb.go @@ -0,0 +1,315 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc v4.23.4 +// source: test.proto + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Test struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + D float64 `protobuf:"fixed64,1,opt,name=d,proto3" json:"d,omitempty"` + F float32 `protobuf:"fixed32,2,opt,name=f,proto3" json:"f,omitempty"` + I32 int32 `protobuf:"varint,3,opt,name=i32,proto3" json:"i32,omitempty"` + I64 int64 `protobuf:"varint,4,opt,name=i64,proto3" json:"i64,omitempty"` + U32 uint32 `protobuf:"varint,5,opt,name=u32,proto3" json:"u32,omitempty"` + U64 uint64 `protobuf:"varint,6,opt,name=u64,proto3" json:"u64,omitempty"` + T bool `protobuf:"varint,7,opt,name=t,proto3" json:"t,omitempty"` + B []byte `protobuf:"bytes,8,opt,name=b,proto3" json:"b,omitempty"` + S string `protobuf:"bytes,9,opt,name=s,proto3" json:"s,omitempty"` + Embedded *Embedded `protobuf:"bytes,10,opt,name=embedded,proto3" json:"embedded,omitempty"` + RepeatedEmbeddeds []*Embedded `protobuf:"bytes,11,rep,name=repeated_embeddeds,json=repeatedEmbeddeds,proto3" json:"repeated_embeddeds,omitempty"` +} + +func (x *Test) Reset() { + *x = Test{} + if protoimpl.UnsafeEnabled { + mi := &file_test_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Test) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test) ProtoMessage() {} + +func (x *Test) ProtoReflect() protoreflect.Message { + mi := &file_test_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test.ProtoReflect.Descriptor instead. +func (*Test) Descriptor() ([]byte, []int) { + return file_test_proto_rawDescGZIP(), []int{0} +} + +func (x *Test) GetD() float64 { + if x != nil { + return x.D + } + return 0 +} + +func (x *Test) GetF() float32 { + if x != nil { + return x.F + } + return 0 +} + +func (x *Test) GetI32() int32 { + if x != nil { + return x.I32 + } + return 0 +} + +func (x *Test) GetI64() int64 { + if x != nil { + return x.I64 + } + return 0 +} + +func (x *Test) GetU32() uint32 { + if x != nil { + return x.U32 + } + return 0 +} + +func (x *Test) GetU64() uint64 { + if x != nil { + return x.U64 + } + return 0 +} + +func (x *Test) GetT() bool { + if x != nil { + return x.T + } + return false +} + +func (x *Test) GetB() []byte { + if x != nil { + return x.B + } + return nil +} + +func (x *Test) GetS() string { + if x != nil { + return x.S + } + return "" +} + +func (x *Test) GetEmbedded() *Embedded { + if x != nil { + return x.Embedded + } + return nil +} + +func (x *Test) GetRepeatedEmbeddeds() []*Embedded { + if x != nil { + return x.RepeatedEmbeddeds + } + return nil +} + +type Embedded struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + S string `protobuf:"bytes,1,opt,name=s,proto3" json:"s,omitempty"` + Embedded *Embedded `protobuf:"bytes,2,opt,name=embedded,proto3" json:"embedded,omitempty"` +} + +func (x *Embedded) Reset() { + *x = Embedded{} + if protoimpl.UnsafeEnabled { + mi := &file_test_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Embedded) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Embedded) ProtoMessage() {} + +func (x *Embedded) ProtoReflect() protoreflect.Message { + mi := &file_test_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Embedded.ProtoReflect.Descriptor instead. +func (*Embedded) Descriptor() ([]byte, []int) { + return file_test_proto_rawDescGZIP(), []int{1} +} + +func (x *Embedded) GetS() string { + if x != nil { + return x.S + } + return "" +} + +func (x *Embedded) GetEmbedded() *Embedded { + if x != nil { + return x.Embedded + } + return nil +} + +var File_test_proto protoreflect.FileDescriptor + +var file_test_proto_rawDesc = []byte{ + 0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf5, 0x01, 0x0a, + 0x04, 0x54, 0x65, 0x73, 0x74, 0x12, 0x0c, 0x0a, 0x01, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, + 0x52, 0x01, 0x64, 0x12, 0x0c, 0x0a, 0x01, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x52, 0x01, + 0x66, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x33, 0x32, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, + 0x69, 0x33, 0x32, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x36, 0x34, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x03, 0x69, 0x36, 0x34, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x33, 0x32, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x03, 0x75, 0x33, 0x32, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x36, 0x34, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x75, 0x36, 0x34, 0x12, 0x0c, 0x0a, 0x01, 0x74, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x01, 0x74, 0x12, 0x0c, 0x0a, 0x01, 0x62, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x01, 0x62, 0x12, 0x0c, 0x0a, 0x01, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x01, 0x73, 0x12, 0x25, 0x0a, 0x08, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, + 0x52, 0x08, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x12, 0x38, 0x0a, 0x12, 0x72, 0x65, + 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x73, + 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, + 0x64, 0x52, 0x11, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x45, 0x6d, 0x62, 0x65, 0x64, + 0x64, 0x65, 0x64, 0x73, 0x22, 0x3f, 0x0a, 0x08, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, + 0x12, 0x0c, 0x0a, 0x01, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x01, 0x73, 0x12, 0x25, + 0x0a, 0x08, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x09, 0x2e, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x52, 0x08, 0x65, 0x6d, 0x62, + 0x65, 0x64, 0x64, 0x65, 0x64, 0x32, 0x1f, 0x0a, 0x07, 0x54, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, + 0x12, 0x14, 0x0a, 0x04, 0x63, 0x61, 0x6c, 0x6c, 0x12, 0x05, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x1a, + 0x05, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x68, 0x6f, 0x73, 0x74, 0x69, 0x61, 0x6d, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x67, 0x65, 0x74, 0x74, 0x65, 0x72, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, + 0x74, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_test_proto_rawDescOnce sync.Once + file_test_proto_rawDescData = file_test_proto_rawDesc +) + +func file_test_proto_rawDescGZIP() []byte { + file_test_proto_rawDescOnce.Do(func() { + file_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_test_proto_rawDescData) + }) + return file_test_proto_rawDescData +} + +var file_test_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_test_proto_goTypes = []interface{}{ + (*Test)(nil), // 0: Test + (*Embedded)(nil), // 1: Embedded +} +var file_test_proto_depIdxs = []int32{ + 1, // 0: Test.embedded:type_name -> Embedded + 1, // 1: Test.repeated_embeddeds:type_name -> Embedded + 1, // 2: Embedded.embedded:type_name -> Embedded + 0, // 3: Testing.call:input_type -> Test + 0, // 4: Testing.call:output_type -> Test + 4, // [4:5] is the sub-list for method output_type + 3, // [3:4] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_test_proto_init() } +func file_test_proto_init() { + if File_test_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Test); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_test_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Embedded); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_test_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_test_proto_goTypes, + DependencyIndexes: file_test_proto_depIdxs, + MessageInfos: file_test_proto_msgTypes, + }.Build() + File_test_proto = out.File + file_test_proto_rawDesc = nil + file_test_proto_goTypes = nil + file_test_proto_depIdxs = nil +} diff --git a/test/testdata/protogetter/proto/test.proto b/test/testdata/protogetter/proto/test.proto new file mode 100644 index 000000000000..83a536a8ba45 --- /dev/null +++ b/test/testdata/protogetter/proto/test.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +option go_package = "github.com/ghostiam/protogetter/testdata/proto"; + +message Test { + double d = 1; + float f = 2; + int32 i32 = 3; + int64 i64 = 4; + uint32 u32 = 5; + uint64 u64 = 6; + bool t = 7; + bytes b = 8; + string s = 9; + Embedded embedded = 10; + repeated Embedded repeated_embeddeds = 11; +} + +message Embedded { + string s = 1; + Embedded embedded = 2; +} + +service Testing { + rpc call(Test) returns (Test); +} diff --git a/test/testdata/protogetter/proto/test_grpc.pb.go b/test/testdata/protogetter/proto/test_grpc.pb.go new file mode 100644 index 000000000000..e4500fd6b4f5 --- /dev/null +++ b/test/testdata/protogetter/proto/test_grpc.pb.go @@ -0,0 +1,109 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc v4.23.4 +// source: test.proto + +package proto + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + Testing_Call_FullMethodName = "/Testing/call" +) + +// TestingClient is the client API for Testing service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type TestingClient interface { + Call(ctx context.Context, in *Test, opts ...grpc.CallOption) (*Test, error) +} + +type testingClient struct { + cc grpc.ClientConnInterface +} + +func NewTestingClient(cc grpc.ClientConnInterface) TestingClient { + return &testingClient{cc} +} + +func (c *testingClient) Call(ctx context.Context, in *Test, opts ...grpc.CallOption) (*Test, error) { + out := new(Test) + err := c.cc.Invoke(ctx, Testing_Call_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// TestingServer is the server API for Testing service. +// All implementations must embed UnimplementedTestingServer +// for forward compatibility +type TestingServer interface { + Call(context.Context, *Test) (*Test, error) + mustEmbedUnimplementedTestingServer() +} + +// UnimplementedTestingServer must be embedded to have forward compatible implementations. +type UnimplementedTestingServer struct { +} + +func (UnimplementedTestingServer) Call(context.Context, *Test) (*Test, error) { + return nil, status.Errorf(codes.Unimplemented, "method Call not implemented") +} +func (UnimplementedTestingServer) mustEmbedUnimplementedTestingServer() {} + +// UnsafeTestingServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to TestingServer will +// result in compilation errors. +type UnsafeTestingServer interface { + mustEmbedUnimplementedTestingServer() +} + +func RegisterTestingServer(s grpc.ServiceRegistrar, srv TestingServer) { + s.RegisterService(&Testing_ServiceDesc, srv) +} + +func _Testing_Call_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Test) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestingServer).Call(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Testing_Call_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestingServer).Call(ctx, req.(*Test)) + } + return interceptor(ctx, in, info, handler) +} + +// Testing_ServiceDesc is the grpc.ServiceDesc for Testing service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Testing_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "Testing", + HandlerType: (*TestingServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "call", + Handler: _Testing_Call_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "test.proto", +} diff --git a/test/testdata/protogetter/protogetter.go b/test/testdata/protogetter/protogetter.go new file mode 100644 index 000000000000..47cd401e250a --- /dev/null +++ b/test/testdata/protogetter/protogetter.go @@ -0,0 +1,151 @@ +//golangcitest:args -Eprotogetter +package protogetter + +import ( + "fmt" + + "protogetter/proto" +) + +type Test struct { + Embedded *proto.Embedded +} + +func testInvalid(t *proto.Test) { + func(...interface{}) {}(t.B, t.D) // want `avoid direct access to proto field t\.B, use t\.GetB\(\) instead` + func(...interface{}) {}(t.GetB(), t.D) // want `avoid direct access to proto field t\.D, use t\.GetD\(\) instead` + func(...interface{}) {}(t.B, t.GetD()) // want `avoid direct access to proto field t\.B, use t\.GetB\(\) instead` + + _ = t.D // want `avoid direct access to proto field t\.D, use t\.GetD\(\) instead` + _ = t.F // want `avoid direct access to proto field t\.F, use t\.GetF\(\) instead` + _ = t.I32 // want `avoid direct access to proto field t\.I32, use t\.GetI32\(\) instead` + _ = t.I64 // want `avoid direct access to proto field t\.I64, use t\.GetI64\(\) instead` + _ = t.U32 // want `avoid direct access to proto field t\.U32, use t\.GetU32\(\) instead` + _ = t.U64 // want `avoid direct access to proto field t\.U64, use t\.GetU64\(\) instead` + _ = t.T // want `avoid direct access to proto field t\.T, use t\.GetT\(\) instead` + _ = t.B // want `avoid direct access to proto field t\.B, use t\.GetB\(\) instead` + _ = t.S // want `avoid direct access to proto field t\.S, use t\.GetS\(\) instead` + _ = t.Embedded // want `avoid direct access to proto field t\.Embedded, use t\.GetEmbedded\(\) instead` + _ = t.Embedded.S // want `avoid direct access to proto field t\.Embedded\.S, use t\.GetEmbedded\(\)\.GetS\(\) instead` + _ = t.GetEmbedded().S // want `avoid direct access to proto field t\.GetEmbedded\(\)\.S, use t\.GetEmbedded\(\)\.GetS\(\) instead` + _ = t.Embedded.Embedded // want `avoid direct access to proto field t\.Embedded\.Embedded, use t\.GetEmbedded\(\)\.GetEmbedded\(\) instead` + _ = t.GetEmbedded().Embedded // want `avoid direct access to proto field t\.GetEmbedded\(\)\.Embedded, use t\.GetEmbedded\(\)\.GetEmbedded\(\) instead` + _ = t.Embedded.Embedded.S // want `avoid direct access to proto field t\.Embedded\.Embedded\.S, use t\.GetEmbedded\(\)\.GetEmbedded\(\).GetS\(\) instead` + _ = t.GetEmbedded().GetEmbedded().S // want `avoid direct access to proto field t\.GetEmbedded\(\)\.GetEmbedded\(\)\.S, use t\.GetEmbedded\(\)\.GetEmbedded\(\)\.GetS\(\) instead` + _ = t.RepeatedEmbeddeds // want `avoid direct access to proto field t\.RepeatedEmbeddeds, use t\.GetRepeatedEmbeddeds\(\) instead` + _ = t.RepeatedEmbeddeds[0] // want `avoid direct access to proto field t\.RepeatedEmbeddeds, use t\.GetRepeatedEmbeddeds\(\) instead` + _ = t.RepeatedEmbeddeds[0].S // want `avoid direct access to proto field t\.RepeatedEmbeddeds\[0\]\.S, use t\.GetRepeatedEmbeddeds\(\)\[0\]\.GetS\(\) instead` + _ = t.GetRepeatedEmbeddeds()[0].S // want `avoid direct access to proto field t\.GetRepeatedEmbeddeds\(\)\[0\]\.S, use t\.GetRepeatedEmbeddeds\(\)\[0\]\.GetS\(\) instead` + _ = t.RepeatedEmbeddeds[0].Embedded // want `avoid direct access to proto field t\.RepeatedEmbeddeds\[0\]\.Embedded, use t\.GetRepeatedEmbeddeds\(\)\[0\]\.GetEmbedded\(\) instead` + _ = t.GetRepeatedEmbeddeds()[0].Embedded // want `avoid direct access to proto field t\.GetRepeatedEmbeddeds\(\)\[0\]\.Embedded, use t\.GetRepeatedEmbeddeds\(\)\[0\]\.GetEmbedded\(\) instead` + _ = t.RepeatedEmbeddeds[0].Embedded.S // want `avoid direct access to proto field t\.RepeatedEmbeddeds\[0\]\.Embedded\.S, use t\.GetRepeatedEmbeddeds\(\)\[0\].GetEmbedded\(\).GetS\(\) instead` + _ = t.GetRepeatedEmbeddeds()[0].GetEmbedded().S // want `avoid direct access to proto field t\.GetRepeatedEmbeddeds\(\)\[0\].GetEmbedded\(\).S, use t\.GetRepeatedEmbeddeds\(\)\[0\].GetEmbedded\(\).GetS\(\) instead` + _ = t.RepeatedEmbeddeds[t.I64].Embedded.S // want `avoid direct access to proto field t\.RepeatedEmbeddeds\[t.I64\]\.Embedded\.S, use t\.GetRepeatedEmbeddeds\(\)\[t\.GetI64\(\)\].GetEmbedded\(\).GetS\(\) instead` + _ = t.GetRepeatedEmbeddeds()[t.I64].GetEmbedded().S // want `avoid direct access to proto field t\.GetRepeatedEmbeddeds\(\)\[t\.I64\]\.GetEmbedded\(\)\.S, use t\.GetRepeatedEmbeddeds\(\)\[t\.GetI64\(\)\]\.GetEmbedded\(\).GetS\(\) instead` + + var many []*proto.Test + manyIndex := 42 + + _ = many[0].T // want `avoid direct access to proto field many\[0\]\.T, use many\[0\]\.GetT\(\) instead` + _ = many[1].Embedded.S // want `avoid direct access to proto field many\[1\]\.Embedded\.S, use many\[1\]\.GetEmbedded\(\)\.GetS\(\) instead` + _ = many[2].GetEmbedded().S // want `avoid direct access to proto field many\[2\]\.GetEmbedded\(\)\.S, use many\[2\].GetEmbedded\(\)\.GetS\(\) instead` + _ = many[3].Embedded.Embedded.S // want `avoid direct access to proto field many\[3\]\.Embedded\.Embedded\.S, use many\[3\].GetEmbedded\(\)\.GetEmbedded\(\)\.GetS\(\) instead` + _ = many[manyIndex].S // want `avoid direct access to proto field many\[manyIndex\]\.S, use many\[manyIndex\]\.GetS\(\) instead` + + test := many[0].Embedded.S == "" || t.Embedded.CustomMethod() == nil || t.S == "" || t.Embedded == nil // want `avoid direct access to proto field many\[0\]\.Embedded\.S, use many\[0\]\.GetEmbedded\(\).GetS\(\) instead` + _ = test + + other := proto.Other{} + _ = other.MyMethod(nil).S // want `avoid direct access to proto field other\.MyMethod\(nil\)\.S, use other\.MyMethod\(nil\)\.GetS\(\) instead` + + ems := t.RepeatedEmbeddeds // want `avoid direct access to proto field t\.RepeatedEmbeddeds, use t\.GetRepeatedEmbeddeds\(\) instead` + _ = ems[len(ems)-1].S // want `avoid direct access to proto field ems\[len\(ems\)-1\]\.S, use ems\[len\(ems\)-1\]\.GetS\(\) instead` + + ch := make(chan string) + ch <- t.S // want `avoid direct access to proto field t\.S, use t\.GetS\(\) instead` + + for _, v := range t.RepeatedEmbeddeds { // want `avoid direct access to proto field t\.RepeatedEmbeddeds, use t\.GetRepeatedEmbeddeds\(\) instead` + _ = v + } + + fn := func(...interface{}) bool { return false } + fn((*proto.Test)(nil).S) // want `avoid direct access to proto field \(\*proto\.Test\)\(nil\)\.S, use \(\*proto\.Test\)\(nil\)\.GetS\(\) instead` + + var ptrs *[]proto.Test + _ = (*ptrs)[42].RepeatedEmbeddeds // want `avoid direct access to proto field \(\*ptrs\)\[42\]\.RepeatedEmbeddeds, use \(\*ptrs\)\[42\].GetRepeatedEmbeddeds\(\) instead` + _ = (*ptrs)[t.I64].RepeatedEmbeddeds // want `avoid direct access to proto field \(\*ptrs\)\[t\.I64\]\.RepeatedEmbeddeds, use \(\*ptrs\)\[t\.GetI64\(\)\].GetRepeatedEmbeddeds\(\) instead` + + var anyType interface{} + _ = anyType.(*proto.Test).S // want `avoid direct access to proto field anyType\.\(\*proto\.Test\)\.S, use anyType\.\(\*proto\.Test\)\.GetS\(\) instead` + + t.Embedded.SetS("test") // want `avoid direct access to proto field t\.Embedded\.SetS\("test"\), use t\.GetEmbedded\(\)\.SetS\("test"\) instead` + t.Embedded.SetMap(map[string]string{"test": "test"}) // want `avoid direct access to proto field t\.Embedded\.SetMap\(map\[string\]string{"test": "test"}\), use t\.GetEmbedded\(\)\.SetMap\(map\[string\]string{"test": "test"}\) instead` +} + +func testValid(t *proto.Test) { + func(...interface{}) {}(t.GetB(), t.GetD()) + func(...interface{}) {}(&t.B, &t.D) + + _, t.T = true, true + _, t.T, _ = true, true, false + _, _, t.T = true, true, false + t.T, _ = true, true + t.D = 2 + t.I32++ + t.I32 += 2 + + fmt.Scanf("Test", &t.S, &t.B, &t.T) + + t.D = 1.0 + t.F = 1.0 + t.I32 = 1 + t.I64 = 1 + t.U32 = 1 + t.U64 = 1 + t.T = true + t.B = []byte{1} + t.S = "1" + t.Embedded = &proto.Embedded{} + t.Embedded.S = "1" + t.GetEmbedded().S = "1" + t.Embedded.Embedded = &proto.Embedded{} + t.GetEmbedded().Embedded = &proto.Embedded{} + t.Embedded.Embedded.S = "1" + t.GetEmbedded().GetEmbedded().S = "1" + t.RepeatedEmbeddeds = []*proto.Embedded{{S: "1"}} + + _ = t.GetD() + _ = t.GetF() + _ = t.GetI32() + _ = t.GetI64() + _ = t.GetU32() + _ = t.GetU64() + _ = t.GetT() + _ = t.GetB() + _ = t.GetS() + _ = t.GetEmbedded() + _ = t.GetEmbedded().GetS() + _ = t.GetEmbedded().GetEmbedded() + _ = t.GetEmbedded().GetEmbedded().GetS() + _ = t.GetRepeatedEmbeddeds() + _ = t.GetRepeatedEmbeddeds()[0] + _ = t.GetRepeatedEmbeddeds()[0].GetS() + _ = t.GetRepeatedEmbeddeds()[0].GetEmbedded() + _ = t.GetRepeatedEmbeddeds()[0].GetEmbedded().GetS() + + other := proto.Other{} + other.MyMethod(nil).CustomMethod() + other.MyMethod(nil).GetS() + + var tt Test + _ = tt.Embedded.GetS() + _ = tt.Embedded.GetEmbedded().GetS() + + ems := t.GetRepeatedEmbeddeds() + _ = ems[len(ems)-1].GetS() + + ch := make(chan string) + ch <- t.GetS() + + t.Equal(&proto.Test{S: "test", I64: 42}) +}