diff --git a/.golangci.reference.yml b/.golangci.reference.yml
index 41200df5..a380182c 100644
--- a/.golangci.reference.yml
+++ b/.golangci.reference.yml
@@ -2263,6 +2263,7 @@ linters:
     - prealloc
     - predeclared
     - promlinter
+    - protogetter
     - reassign
     - revive
     - rowserrcheck
@@ -2378,6 +2379,7 @@ linters:
     - prealloc
     - predeclared
     - promlinter
+    - protogetter
     - reassign
     - revive
     - rowserrcheck
diff --git a/go.mod b/go.mod
index 61b1d8be..3b83d7d2 100644
--- a/go.mod
+++ b/go.mod
@@ -34,6 +34,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 f3ecc2a5..85229921 100644
--- a/go.sum
+++ b/go.sum
@@ -146,6 +146,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 00000000..23325ad5
--- /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 59f6ef30..c0cb36ae 100644
--- a/pkg/lint/lintersdb/manager.go
+++ b/pkg/lint/lintersdb/manager.go
@@ -717,6 +717,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 dd130db3..75d4f44a 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 00000000..6120ad19
--- /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 00000000..820c8ebc
--- /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 00000000..e48a033c
--- /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 00000000..64e7b6a3
--- /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 00000000..83a536a8
--- /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 00000000..e4500fd6
--- /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 00000000..47cd401e
--- /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})
+}