mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-03-28 19:21:22 +00:00
protogen: generate .meta file with code annotations
When the generator parameter 'annotate_code' is provided, generate a .meta file containing a GeneratedCodeInfo message describing the generated code's relation to the source .proto file. Annotations are added with (*protogen.GeneratedFile).Annotate, which takes the name of a Go identifier (e.g., "SomeMessage" or "SomeMessage.GetField") and an associated source location. The generator examines the generated AST to determine source offsets for the symbols. Change the []int32 "Path" in protogen types to a "Location", which also captures the source file name. Change-Id: Icd2340875831f40a1f91d495e3bd7ea381475c77 Reviewed-on: https://go-review.googlesource.com/c/139759 Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
This commit is contained in:
parent
d277b5236a
commit
162c12703c
@ -74,9 +74,11 @@ func genService(gen *protogen.Plugin, file *fileInfo, g *protogen.GeneratedFile,
|
||||
g.P("//")
|
||||
g.P(deprecationComment)
|
||||
}
|
||||
g.Annotate(clientName, service.Location)
|
||||
g.P("type ", clientName, " interface {")
|
||||
for _, method := range service.Methods {
|
||||
genComment(g, file, method.Path)
|
||||
genComment(g, file, method.Location)
|
||||
g.Annotate(clientName+"."+method.GoName, method.Location)
|
||||
g.P(clientSignature(g, method))
|
||||
}
|
||||
g.P("}")
|
||||
@ -118,9 +120,11 @@ func genService(gen *protogen.Plugin, file *fileInfo, g *protogen.GeneratedFile,
|
||||
g.P("//")
|
||||
g.P(deprecationComment)
|
||||
}
|
||||
g.Annotate(serverType, service.Location)
|
||||
g.P("type ", serverType, " interface {")
|
||||
for _, method := range service.Methods {
|
||||
genComment(g, file, method.Path)
|
||||
genComment(g, file, method.Location)
|
||||
g.Annotate(serverType+"."+method.GoName, method.Location)
|
||||
g.P(serverSignature(g, method))
|
||||
}
|
||||
g.P("}")
|
||||
@ -384,8 +388,8 @@ func ident(name string) protogen.GoIdent {
|
||||
}
|
||||
}
|
||||
|
||||
func genComment(g *protogen.GeneratedFile, file *fileInfo, path []int32) (hasComment bool) {
|
||||
for _, loc := range file.locationMap[pathKey(path)] {
|
||||
func genComment(g *protogen.GeneratedFile, file *fileInfo, loc protogen.Location) (hasComment bool) {
|
||||
for _, loc := range file.locationMap[pathKey(loc.Path)] {
|
||||
if loc.LeadingComments == nil {
|
||||
continue
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import (
|
||||
|
||||
// serviceOptions returns the options for a service.
|
||||
func serviceOptions(gen *protogen.Plugin, service *protogen.Service) *descpb.ServiceOptions {
|
||||
d := getDescriptorProto(gen, service.Desc, service.Path)
|
||||
d := getDescriptorProto(gen, service.Desc, service.Location.Path)
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
@ -26,7 +26,7 @@ func serviceOptions(gen *protogen.Plugin, service *protogen.Service) *descpb.Ser
|
||||
|
||||
// methodOptions returns the options for a method.
|
||||
func methodOptions(gen *protogen.Plugin, method *protogen.Method) *descpb.MethodOptions {
|
||||
d := getDescriptorProto(gen, method.Desc, method.Path)
|
||||
d := getDescriptorProto(gen, method.Desc, method.Location.Path)
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -7,9 +7,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
|
||||
"github.com/golang/protobuf/v2/internal/protogen/goldentest"
|
||||
)
|
||||
|
||||
@ -23,3 +29,65 @@ func init() {
|
||||
func TestGolden(t *testing.T) {
|
||||
goldentest.Run(t, *regenerate)
|
||||
}
|
||||
|
||||
func TestAnnotations(t *testing.T) {
|
||||
workdir, err := ioutil.TempDir("", "proto-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(workdir)
|
||||
|
||||
goldentest.Protoc(t, []string{"--go_out=paths=source_relative,annotate_code:" + workdir, "-Itestdata/annotations", "testdata/annotations/annotations.proto"})
|
||||
sourceFile, err := ioutil.ReadFile(filepath.Join(workdir, "annotations.pb.go"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
metaFile, err := ioutil.ReadFile(filepath.Join(workdir, "annotations.pb.go.meta"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gotInfo := &descpb.GeneratedCodeInfo{}
|
||||
if err := proto.UnmarshalText(string(metaFile), gotInfo); err != nil {
|
||||
t.Fatalf("can't parse meta file: %v", err)
|
||||
}
|
||||
|
||||
wantInfo := &descpb.GeneratedCodeInfo{}
|
||||
for _, want := range []struct {
|
||||
prefix, text, suffix string
|
||||
path []int32
|
||||
}{{
|
||||
"type ", "AnnotationsTestEnum", " int32",
|
||||
[]int32{5 /* enum_type */, 0},
|
||||
}, {
|
||||
"\t", "AnnotationsTestEnum_ANNOTATIONS_TEST_ENUM_VALUE", " AnnotationsTestEnum = 0",
|
||||
[]int32{5 /* enum_type */, 0, 2 /* value */, 0},
|
||||
}, {
|
||||
"type ", "AnnotationsTestMessage", " struct {",
|
||||
[]int32{4 /* message_type */, 0},
|
||||
}, {
|
||||
"\t", "AnnotationsTestField", " ",
|
||||
[]int32{4 /* message_type */, 0, 2 /* field */, 0},
|
||||
}, {
|
||||
"func (m *AnnotationsTestMessage) ", "GetAnnotationsTestField", "() string {",
|
||||
[]int32{4 /* message_type */, 0, 2 /* field */, 0},
|
||||
}} {
|
||||
s := want.prefix + want.text + want.suffix
|
||||
pos := bytes.Index(sourceFile, []byte(s))
|
||||
if pos < 0 {
|
||||
t.Errorf("source file does not contain: %v", s)
|
||||
continue
|
||||
}
|
||||
begin := pos + len(want.prefix)
|
||||
end := begin + len(want.text)
|
||||
wantInfo.Annotation = append(wantInfo.Annotation, &descpb.GeneratedCodeInfo_Annotation{
|
||||
Path: want.path,
|
||||
Begin: proto.Int32(int32(begin)),
|
||||
End: proto.Int32(int32(end)),
|
||||
SourceFile: proto.String("annotations.proto"),
|
||||
})
|
||||
}
|
||||
if !proto.Equal(gotInfo, wantInfo) {
|
||||
t.Errorf("unexpected annotations for annotations.proto; got:\n%v\nwant:\n%v",
|
||||
proto.MarshalTextString(gotInfo), proto.MarshalTextString(wantInfo))
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ func GenerateFile(gen *protogen.Plugin, file *protogen.File, g *protogen.Generat
|
||||
}
|
||||
g.P()
|
||||
const filePackageField = 2 // FileDescriptorProto.package
|
||||
genComment(g, f, []int32{filePackageField})
|
||||
genComment(g, f, protogen.Location{Path: []int32{filePackageField}})
|
||||
g.P()
|
||||
g.P("package ", f.GoPackageName)
|
||||
g.P()
|
||||
@ -237,12 +237,14 @@ func genFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileI
|
||||
}
|
||||
|
||||
func genEnum(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, enum *protogen.Enum) {
|
||||
genComment(g, f, enum.Path)
|
||||
genComment(g, f, enum.Location)
|
||||
g.Annotate(enum.GoIdent.GoName, enum.Location)
|
||||
g.P("type ", enum.GoIdent, " int32",
|
||||
deprecationComment(enumOptions(gen, enum).GetDeprecated()))
|
||||
g.P("const (")
|
||||
for _, value := range enum.Values {
|
||||
genComment(g, f, value.Path)
|
||||
genComment(g, f, value.Location)
|
||||
g.Annotate(value.GoIdent.GoName, value.Location)
|
||||
g.P(value.GoIdent, " ", enum.GoIdent, " = ", value.Desc.Number(),
|
||||
deprecationComment(enumValueOptions(gen, value).GetDeprecated()))
|
||||
}
|
||||
@ -294,8 +296,8 @@ func genEnum(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, enum
|
||||
}
|
||||
|
||||
var indexes []string
|
||||
for i := 1; i < len(enum.Path); i += 2 {
|
||||
indexes = append(indexes, strconv.Itoa(int(enum.Path[i])))
|
||||
for i := 1; i < len(enum.Location.Path); i += 2 {
|
||||
indexes = append(indexes, strconv.Itoa(int(enum.Location.Path[i])))
|
||||
}
|
||||
g.P("func (", enum.GoIdent, ") EnumDescriptor() ([]byte, []int) {")
|
||||
g.P("return ", f.descriptorVar, ", []int{", strings.Join(indexes, ","), "}")
|
||||
@ -333,13 +335,14 @@ func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, me
|
||||
return
|
||||
}
|
||||
|
||||
hasComment := genComment(g, f, message.Path)
|
||||
hasComment := genComment(g, f, message.Location)
|
||||
if messageOptions(gen, message).GetDeprecated() {
|
||||
if hasComment {
|
||||
g.P("//")
|
||||
}
|
||||
g.P(deprecationComment(true))
|
||||
}
|
||||
g.Annotate(message.GoIdent.GoName, message.Location)
|
||||
g.P("type ", message.GoIdent, " struct {")
|
||||
for _, field := range message.Fields {
|
||||
if field.OneofType != nil {
|
||||
@ -352,7 +355,7 @@ func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, me
|
||||
}
|
||||
continue
|
||||
}
|
||||
genComment(g, f, field.Path)
|
||||
genComment(g, f, field.Location)
|
||||
goType, pointer := fieldGoType(g, field)
|
||||
if pointer {
|
||||
goType = "*" + goType
|
||||
@ -369,6 +372,7 @@ func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, me
|
||||
fmt.Sprintf("protobuf_val:%q", fieldProtobufTag(val)),
|
||||
)
|
||||
}
|
||||
g.Annotate(message.GoIdent.GoName+"."+field.GoName, field.Location)
|
||||
g.P(field.GoName, " ", goType, " `", strings.Join(tags, " "), "`",
|
||||
deprecationComment(fieldOptions(gen, field).GetDeprecated()))
|
||||
}
|
||||
@ -402,8 +406,8 @@ func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, me
|
||||
g.P("func (*", message.GoIdent, ") ProtoMessage() {}")
|
||||
// Descriptor
|
||||
var indexes []string
|
||||
for i := 1; i < len(message.Path); i += 2 {
|
||||
indexes = append(indexes, strconv.Itoa(int(message.Path[i])))
|
||||
for i := 1; i < len(message.Location.Path); i += 2 {
|
||||
indexes = append(indexes, strconv.Itoa(int(message.Location.Path[i])))
|
||||
}
|
||||
g.P("func (*", message.GoIdent, ") Descriptor() ([]byte, []int) {")
|
||||
g.P("return ", f.descriptorVar, ", []int{", strings.Join(indexes, ","), "}")
|
||||
@ -547,6 +551,7 @@ func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, me
|
||||
if fieldOptions(gen, field).GetDeprecated() {
|
||||
g.P(deprecationComment(true))
|
||||
}
|
||||
g.Annotate(message.GoIdent.GoName+".Get"+field.GoName, field.Location)
|
||||
g.P("func (m *", message.GoIdent, ") Get", field.GoName, "() ", goType, " {")
|
||||
if field.OneofType != nil {
|
||||
g.P("if x, ok := m.Get", field.OneofType.GoName, "().(*", fieldOneofType(field), "); ok {")
|
||||
@ -897,8 +902,8 @@ func genRegisterExtension(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fi
|
||||
}
|
||||
}
|
||||
|
||||
func genComment(g *protogen.GeneratedFile, f *fileInfo, path []int32) (hasComment bool) {
|
||||
for _, loc := range f.locationMap[pathKey(path)] {
|
||||
func genComment(g *protogen.GeneratedFile, f *fileInfo, loc protogen.Location) (hasComment bool) {
|
||||
for _, loc := range f.locationMap[pathKey(loc.Path)] {
|
||||
if loc.LeadingComments == nil {
|
||||
continue
|
||||
}
|
||||
|
@ -15,14 +15,15 @@ import (
|
||||
|
||||
// genOneofField generates the struct field for a oneof.
|
||||
func genOneofField(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, message *protogen.Message, oneof *protogen.Oneof) {
|
||||
if genComment(g, f, oneof.Path) {
|
||||
if genComment(g, f, oneof.Location) {
|
||||
g.P("//")
|
||||
}
|
||||
g.P("// Types that are valid to be assigned to ", oneof.GoName, ":")
|
||||
for _, field := range oneof.Fields {
|
||||
genComment(g, f, field.Path)
|
||||
genComment(g, f, field.Location)
|
||||
g.P("//\t*", fieldOneofType(field))
|
||||
}
|
||||
g.Annotate(message.GoIdent.GoName+"."+oneof.GoName, oneof.Location)
|
||||
g.P(oneof.GoName, " ", oneofInterfaceName(message, oneof), " `protobuf_oneof:\"", oneof.Desc.Name(), "\"`")
|
||||
}
|
||||
|
||||
@ -38,7 +39,10 @@ func genOneofTypes(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo,
|
||||
g.P("}")
|
||||
g.P()
|
||||
for _, field := range oneof.Fields {
|
||||
g.P("type ", fieldOneofType(field), " struct {")
|
||||
name := fieldOneofType(field)
|
||||
g.Annotate(name.GoName, field.Location)
|
||||
g.Annotate(name.GoName+"."+field.GoName, field.Location)
|
||||
g.P("type ", name, " struct {")
|
||||
goType, _ := fieldGoType(g, field)
|
||||
tags := []string{
|
||||
fmt.Sprintf("protobuf:%q", fieldProtobufTag(field)),
|
||||
@ -51,6 +55,7 @@ func genOneofTypes(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo,
|
||||
g.P("func (*", fieldOneofType(field), ") ", ifName, "() {}")
|
||||
g.P()
|
||||
}
|
||||
g.Annotate(message.GoIdent.GoName+".Get"+oneof.GoName, oneof.Location)
|
||||
g.P("func (m *", message.GoIdent.GoName, ") Get", oneof.GoName, "() ", ifName, " {")
|
||||
g.P("if m != nil {")
|
||||
g.P("return m.", oneof.GoName)
|
||||
|
@ -17,7 +17,7 @@ import (
|
||||
|
||||
// messageOptions returns the MessageOptions for a message.
|
||||
func messageOptions(gen *protogen.Plugin, message *protogen.Message) *descpb.MessageOptions {
|
||||
d := getDescriptorProto(gen, message.Desc, message.Path)
|
||||
d := getDescriptorProto(gen, message.Desc, message.Location.Path)
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
@ -26,7 +26,7 @@ func messageOptions(gen *protogen.Plugin, message *protogen.Message) *descpb.Mes
|
||||
|
||||
// fieldOptions returns the FieldOptions for a message.
|
||||
func fieldOptions(gen *protogen.Plugin, field *protogen.Field) *descpb.FieldOptions {
|
||||
d := getDescriptorProto(gen, field.Desc, field.Path)
|
||||
d := getDescriptorProto(gen, field.Desc, field.Location.Path)
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
@ -35,7 +35,7 @@ func fieldOptions(gen *protogen.Plugin, field *protogen.Field) *descpb.FieldOpti
|
||||
|
||||
// enumOptions returns the EnumOptions for an enum
|
||||
func enumOptions(gen *protogen.Plugin, enum *protogen.Enum) *descpb.EnumOptions {
|
||||
d := getDescriptorProto(gen, enum.Desc, enum.Path)
|
||||
d := getDescriptorProto(gen, enum.Desc, enum.Location.Path)
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
@ -44,7 +44,7 @@ func enumOptions(gen *protogen.Plugin, enum *protogen.Enum) *descpb.EnumOptions
|
||||
|
||||
// enumValueOptions returns the EnumValueOptions for an enum value
|
||||
func enumValueOptions(gen *protogen.Plugin, value *protogen.EnumValue) *descpb.EnumValueOptions {
|
||||
d := getDescriptorProto(gen, value.Desc, value.Path)
|
||||
d := getDescriptorProto(gen, value.Desc, value.Location.Path)
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
|
121
cmd/protoc-gen-go/testdata/annotations/annotations.pb.go
vendored
Normal file
121
cmd/protoc-gen-go/testdata/annotations/annotations.pb.go
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: annotations/annotations.proto
|
||||
|
||||
package annotations
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type AnnotationsTestEnum int32
|
||||
|
||||
const (
|
||||
AnnotationsTestEnum_ANNOTATIONS_TEST_ENUM_VALUE AnnotationsTestEnum = 0
|
||||
)
|
||||
|
||||
var AnnotationsTestEnum_name = map[int32]string{
|
||||
0: "ANNOTATIONS_TEST_ENUM_VALUE",
|
||||
}
|
||||
|
||||
var AnnotationsTestEnum_value = map[string]int32{
|
||||
"ANNOTATIONS_TEST_ENUM_VALUE": 0,
|
||||
}
|
||||
|
||||
func (x AnnotationsTestEnum) Enum() *AnnotationsTestEnum {
|
||||
p := new(AnnotationsTestEnum)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x AnnotationsTestEnum) String() string {
|
||||
return proto.EnumName(AnnotationsTestEnum_name, int32(x))
|
||||
}
|
||||
|
||||
func (x *AnnotationsTestEnum) UnmarshalJSON(data []byte) error {
|
||||
value, err := proto.UnmarshalJSONEnum(AnnotationsTestEnum_value, data, "AnnotationsTestEnum")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*x = AnnotationsTestEnum(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (AnnotationsTestEnum) EnumDescriptor() ([]byte, []int) {
|
||||
return fileDescriptor_21dfaf6fd39fa3b7, []int{0}
|
||||
}
|
||||
|
||||
type AnnotationsTestMessage struct {
|
||||
AnnotationsTestField *string `protobuf:"bytes,1,opt,name=AnnotationsTestField" json:"AnnotationsTestField,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *AnnotationsTestMessage) Reset() { *m = AnnotationsTestMessage{} }
|
||||
func (m *AnnotationsTestMessage) String() string { return proto.CompactTextString(m) }
|
||||
func (*AnnotationsTestMessage) ProtoMessage() {}
|
||||
func (*AnnotationsTestMessage) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_21dfaf6fd39fa3b7, []int{0}
|
||||
}
|
||||
|
||||
func (m *AnnotationsTestMessage) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_AnnotationsTestMessage.Unmarshal(m, b)
|
||||
}
|
||||
func (m *AnnotationsTestMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_AnnotationsTestMessage.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *AnnotationsTestMessage) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_AnnotationsTestMessage.Merge(m, src)
|
||||
}
|
||||
func (m *AnnotationsTestMessage) XXX_Size() int {
|
||||
return xxx_messageInfo_AnnotationsTestMessage.Size(m)
|
||||
}
|
||||
func (m *AnnotationsTestMessage) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_AnnotationsTestMessage.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_AnnotationsTestMessage proto.InternalMessageInfo
|
||||
|
||||
func (m *AnnotationsTestMessage) GetAnnotationsTestField() string {
|
||||
if m != nil && m.AnnotationsTestField != nil {
|
||||
return *m.AnnotationsTestField
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterEnum("goproto.protoc.annotations.AnnotationsTestEnum", AnnotationsTestEnum_name, AnnotationsTestEnum_value)
|
||||
proto.RegisterType((*AnnotationsTestMessage)(nil), "goproto.protoc.annotations.AnnotationsTestMessage")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("annotations/annotations.proto", fileDescriptor_21dfaf6fd39fa3b7) }
|
||||
|
||||
var fileDescriptor_21dfaf6fd39fa3b7 = []byte{
|
||||
// 194 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4d, 0xcc, 0xcb, 0xcb,
|
||||
0x2f, 0x49, 0x2c, 0xc9, 0xcc, 0xcf, 0x2b, 0xd6, 0x47, 0x62, 0xeb, 0x15, 0x14, 0xe5, 0x97, 0xe4,
|
||||
0x0b, 0x49, 0xa5, 0xe7, 0x83, 0x19, 0x10, 0x6e, 0xb2, 0x1e, 0x92, 0x0a, 0x25, 0x1f, 0x2e, 0x31,
|
||||
0x47, 0x04, 0x37, 0x24, 0xb5, 0xb8, 0xc4, 0x37, 0xb5, 0xb8, 0x38, 0x31, 0x3d, 0x55, 0xc8, 0x88,
|
||||
0x4b, 0x04, 0x4d, 0xc6, 0x2d, 0x33, 0x35, 0x27, 0x45, 0x82, 0x51, 0x81, 0x51, 0x83, 0x33, 0x08,
|
||||
0xab, 0x9c, 0x96, 0x19, 0x97, 0x30, 0x9a, 0xb8, 0x6b, 0x5e, 0x69, 0xae, 0x90, 0x3c, 0x97, 0xb4,
|
||||
0xa3, 0x9f, 0x9f, 0x7f, 0x88, 0x63, 0x88, 0xa7, 0xbf, 0x5f, 0x70, 0x7c, 0x88, 0x6b, 0x70, 0x48,
|
||||
0xbc, 0xab, 0x5f, 0xa8, 0x6f, 0x7c, 0x98, 0xa3, 0x4f, 0xa8, 0xab, 0x00, 0x83, 0x93, 0x5b, 0x94,
|
||||
0x4b, 0x7a, 0x66, 0x49, 0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, 0xae, 0x7e, 0x7a, 0x7e, 0x4e, 0x62,
|
||||
0x5e, 0xba, 0x3e, 0xd8, 0xb5, 0x49, 0xa5, 0x69, 0xfa, 0x65, 0x46, 0xfa, 0xc9, 0xb9, 0x29, 0x10,
|
||||
0x7e, 0xb2, 0x6e, 0x7a, 0x6a, 0x9e, 0x6e, 0x7a, 0xbe, 0x7e, 0x49, 0x6a, 0x71, 0x49, 0x4a, 0x62,
|
||||
0x49, 0x22, 0xb2, 0x7f, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x40, 0xd6, 0xe5, 0x9d, 0x09, 0x01,
|
||||
0x00, 0x00,
|
||||
}
|
17
cmd/protoc-gen-go/testdata/annotations/annotations.proto
vendored
Normal file
17
cmd/protoc-gen-go/testdata/annotations/annotations.proto
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2018 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.
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package goproto.protoc.annotations;
|
||||
|
||||
option go_package = "github.com/golang/protobuf/v2/cmd/protoc-gen-go/testdata/annotations";
|
||||
|
||||
message AnnotationsTestMessage {
|
||||
optional string AnnotationsTestField = 1;
|
||||
}
|
||||
|
||||
enum AnnotationsTestEnum {
|
||||
ANNOTATIONS_TEST_ENUM_VALUE = 0;
|
||||
}
|
@ -55,7 +55,7 @@ func Run(t *testing.T, regenerate bool) {
|
||||
for _, sources := range packages {
|
||||
args := []string{"-Itestdata", "--go_out=paths=source_relative:" + workdir}
|
||||
args = append(args, sources...)
|
||||
protoc(t, args)
|
||||
Protoc(t, args)
|
||||
}
|
||||
|
||||
// Compare each generated file to the golden version.
|
||||
@ -111,7 +111,9 @@ func Run(t *testing.T, regenerate bool) {
|
||||
|
||||
var fdescRE = regexp.MustCompile(`(?ms)^var fileDescriptor.*}`)
|
||||
|
||||
func protoc(t *testing.T, args []string) {
|
||||
// Protoc runs protoc, using the function registered with Plugin as the protoc-gen-go plugin.
|
||||
func Protoc(t *testing.T, args []string) {
|
||||
t.Helper()
|
||||
cmd := exec.Command("protoc", "--plugin=protoc-gen-go="+os.Args[0])
|
||||
cmd.Args = append(cmd.Args, args...)
|
||||
// We set the RUN_AS_PROTOC_PLUGIN environment variable to indicate that
|
||||
|
@ -101,6 +101,7 @@ type Plugin struct {
|
||||
fileReg *protoregistry.Files
|
||||
messagesByName map[protoreflect.FullName]*Message
|
||||
enumsByName map[protoreflect.FullName]*Enum
|
||||
annotateCode bool
|
||||
pathType pathType
|
||||
genFiles []*GeneratedFile
|
||||
opts *Options
|
||||
@ -180,7 +181,13 @@ func New(req *pluginpb.CodeGeneratorRequest, opts *Options) (*Plugin, error) {
|
||||
return nil, fmt.Errorf(`unknown path type %q: want "import" or "source_relative"`, value)
|
||||
}
|
||||
case "annotate_code":
|
||||
// TODO
|
||||
switch value {
|
||||
case "true", "":
|
||||
gen.annotateCode = true
|
||||
case "false":
|
||||
default:
|
||||
return nil, fmt.Errorf(`bad value for parameter %q: want "true" or "false"`, param)
|
||||
}
|
||||
default:
|
||||
if param[0] == 'M' {
|
||||
importPaths[param[1:]] = GoImportPath(value)
|
||||
@ -331,17 +338,29 @@ func (gen *Plugin) Response() *pluginpb.CodeGeneratorResponse {
|
||||
resp.Error = proto.String(gen.err.Error())
|
||||
return resp
|
||||
}
|
||||
for _, gf := range gen.genFiles {
|
||||
content, err := gf.Content()
|
||||
for _, g := range gen.genFiles {
|
||||
content, err := g.content()
|
||||
if err != nil {
|
||||
return &pluginpb.CodeGeneratorResponse{
|
||||
Error: proto.String(err.Error()),
|
||||
}
|
||||
}
|
||||
resp.File = append(resp.File, &pluginpb.CodeGeneratorResponse_File{
|
||||
Name: proto.String(gf.filename),
|
||||
Name: proto.String(g.filename),
|
||||
Content: proto.String(string(content)),
|
||||
})
|
||||
if gen.annotateCode && strings.HasSuffix(g.filename, ".go") {
|
||||
meta, err := g.metaFile(content)
|
||||
if err != nil {
|
||||
return &pluginpb.CodeGeneratorResponse{
|
||||
Error: proto.String(err.Error()),
|
||||
}
|
||||
}
|
||||
resp.File = append(resp.File, &pluginpb.CodeGeneratorResponse_File{
|
||||
Name: proto.String(g.filename + ".meta"),
|
||||
Content: proto.String(meta),
|
||||
})
|
||||
}
|
||||
}
|
||||
return resp
|
||||
}
|
||||
@ -438,6 +457,13 @@ func newFile(gen *Plugin, p *descpb.FileDescriptorProto, packageName GoPackageNa
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (f *File) location(path ...int32) Location {
|
||||
return Location{
|
||||
SourceFile: f.Desc.Path(),
|
||||
Path: path,
|
||||
}
|
||||
}
|
||||
|
||||
// goPackageOption interprets a file's go_package option.
|
||||
// If there is no go_package, it returns ("", "").
|
||||
// If there's a simple name, it returns (pkg, "").
|
||||
@ -468,20 +494,20 @@ type Message struct {
|
||||
Messages []*Message // nested message declarations
|
||||
Enums []*Enum // nested enum declarations
|
||||
Extensions []*Extension // nested extension declarations
|
||||
Path []int32 // location path of this message
|
||||
Location Location // location of this message
|
||||
}
|
||||
|
||||
func newMessage(gen *Plugin, f *File, parent *Message, desc protoreflect.MessageDescriptor) *Message {
|
||||
var path []int32
|
||||
var loc Location
|
||||
if parent != nil {
|
||||
path = pathAppend(parent.Path, messageMessageField, int32(desc.Index()))
|
||||
loc = parent.Location.appendPath(messageMessageField, int32(desc.Index()))
|
||||
} else {
|
||||
path = []int32{fileMessageField, int32(desc.Index())}
|
||||
loc = f.location(fileMessageField, int32(desc.Index()))
|
||||
}
|
||||
message := &Message{
|
||||
Desc: desc,
|
||||
GoIdent: newGoIdent(f, desc),
|
||||
Path: path,
|
||||
Desc: desc,
|
||||
GoIdent: newGoIdent(f, desc),
|
||||
Location: loc,
|
||||
}
|
||||
gen.messagesByName[desc.FullName()] = message
|
||||
for i, mdescs := 0, desc.Messages(); i < mdescs.Len(); i++ {
|
||||
@ -585,24 +611,24 @@ type Field struct {
|
||||
MessageType *Message // type for message or group fields; nil otherwise
|
||||
EnumType *Enum // type for enum fields; nil otherwise
|
||||
OneofType *Oneof // containing oneof; nil if not part of a oneof
|
||||
Path []int32 // location path of this field
|
||||
Location Location // location of this field
|
||||
}
|
||||
|
||||
func newField(gen *Plugin, f *File, message *Message, desc protoreflect.FieldDescriptor) *Field {
|
||||
var path []int32
|
||||
var loc Location
|
||||
switch {
|
||||
case desc.ExtendedType() != nil && message == nil:
|
||||
path = []int32{fileExtensionField, int32(desc.Index())}
|
||||
loc = f.location(fileExtensionField, int32(desc.Index()))
|
||||
case desc.ExtendedType() != nil && message != nil:
|
||||
path = pathAppend(message.Path, messageExtensionField, int32(desc.Index()))
|
||||
loc = message.Location.appendPath(messageExtensionField, int32(desc.Index()))
|
||||
default:
|
||||
path = pathAppend(message.Path, messageFieldField, int32(desc.Index()))
|
||||
loc = message.Location.appendPath(messageFieldField, int32(desc.Index()))
|
||||
}
|
||||
field := &Field{
|
||||
Desc: desc,
|
||||
GoName: camelCase(string(desc.Name())),
|
||||
ParentMessage: message,
|
||||
Path: path,
|
||||
Location: loc,
|
||||
}
|
||||
if desc.OneofType() != nil {
|
||||
field.OneofType = message.Oneofs[desc.OneofType().Index()]
|
||||
@ -649,7 +675,7 @@ type Oneof struct {
|
||||
GoName string // Go field name of this oneof
|
||||
ParentMessage *Message // message in which this oneof occurs
|
||||
Fields []*Field // fields that are part of this oneof
|
||||
Path []int32 // location path of this oneof
|
||||
Location Location // location of this oneof
|
||||
}
|
||||
|
||||
func newOneof(gen *Plugin, f *File, message *Message, desc protoreflect.OneofDescriptor) *Oneof {
|
||||
@ -657,7 +683,7 @@ func newOneof(gen *Plugin, f *File, message *Message, desc protoreflect.OneofDes
|
||||
Desc: desc,
|
||||
ParentMessage: message,
|
||||
GoName: camelCase(string(desc.Name())),
|
||||
Path: pathAppend(message.Path, messageOneofField, int32(desc.Index())),
|
||||
Location: message.Location.appendPath(messageOneofField, int32(desc.Index())),
|
||||
}
|
||||
}
|
||||
|
||||
@ -671,22 +697,22 @@ func (oneof *Oneof) init(gen *Plugin, parent *Message) {
|
||||
type Enum struct {
|
||||
Desc protoreflect.EnumDescriptor
|
||||
|
||||
GoIdent GoIdent // name of the generated Go type
|
||||
Values []*EnumValue // enum values
|
||||
Path []int32 // location path of this enum
|
||||
GoIdent GoIdent // name of the generated Go type
|
||||
Values []*EnumValue // enum values
|
||||
Location Location // location of this enum
|
||||
}
|
||||
|
||||
func newEnum(gen *Plugin, f *File, parent *Message, desc protoreflect.EnumDescriptor) *Enum {
|
||||
var path []int32
|
||||
var loc Location
|
||||
if parent != nil {
|
||||
path = pathAppend(parent.Path, messageEnumField, int32(desc.Index()))
|
||||
loc = parent.Location.appendPath(messageEnumField, int32(desc.Index()))
|
||||
} else {
|
||||
path = []int32{fileEnumField, int32(desc.Index())}
|
||||
loc = f.location(fileEnumField, int32(desc.Index()))
|
||||
}
|
||||
enum := &Enum{
|
||||
Desc: desc,
|
||||
GoIdent: newGoIdent(f, desc),
|
||||
Path: path,
|
||||
Desc: desc,
|
||||
GoIdent: newGoIdent(f, desc),
|
||||
Location: loc,
|
||||
}
|
||||
gen.enumsByName[desc.FullName()] = enum
|
||||
for i, evdescs := 0, enum.Desc.Values(); i < evdescs.Len(); i++ {
|
||||
@ -699,8 +725,8 @@ func newEnum(gen *Plugin, f *File, parent *Message, desc protoreflect.EnumDescri
|
||||
type EnumValue struct {
|
||||
Desc protoreflect.EnumValueDescriptor
|
||||
|
||||
GoIdent GoIdent // name of the generated Go type
|
||||
Path []int32 // location path of this enum value
|
||||
GoIdent GoIdent // name of the generated Go type
|
||||
Location Location // location of this enum value
|
||||
}
|
||||
|
||||
func newEnumValue(gen *Plugin, f *File, message *Message, enum *Enum, desc protoreflect.EnumValueDescriptor) *EnumValue {
|
||||
@ -719,7 +745,7 @@ func newEnumValue(gen *Plugin, f *File, message *Message, enum *Enum, desc proto
|
||||
GoName: name,
|
||||
GoImportPath: f.GoImportPath,
|
||||
},
|
||||
Path: pathAppend(enum.Path, enumValueField, int32(desc.Index())),
|
||||
Location: enum.Location.appendPath(enumValueField, int32(desc.Index())),
|
||||
}
|
||||
}
|
||||
|
||||
@ -732,6 +758,7 @@ type GeneratedFile struct {
|
||||
packageNames map[GoImportPath]GoPackageName
|
||||
usedPackageNames map[GoPackageName]bool
|
||||
manualImports map[GoImportPath]bool
|
||||
annotations map[string][]Location
|
||||
}
|
||||
|
||||
// NewGeneratedFile creates a new generated file with the given filename
|
||||
@ -744,6 +771,7 @@ func (gen *Plugin) NewGeneratedFile(filename string, goImportPath GoImportPath)
|
||||
packageNames: make(map[GoImportPath]GoPackageName),
|
||||
usedPackageNames: make(map[GoPackageName]bool),
|
||||
manualImports: make(map[GoImportPath]bool),
|
||||
annotations: make(map[string][]Location),
|
||||
}
|
||||
gen.genFiles = append(gen.genFiles, g)
|
||||
return g
|
||||
@ -753,16 +781,16 @@ func (gen *Plugin) NewGeneratedFile(filename string, goImportPath GoImportPath)
|
||||
type Service struct {
|
||||
Desc protoreflect.ServiceDescriptor
|
||||
|
||||
GoName string
|
||||
Path []int32 // location path of this service
|
||||
Methods []*Method // service method definitions
|
||||
GoName string
|
||||
Location Location // location of this service
|
||||
Methods []*Method // service method definitions
|
||||
}
|
||||
|
||||
func newService(gen *Plugin, f *File, desc protoreflect.ServiceDescriptor) *Service {
|
||||
service := &Service{
|
||||
Desc: desc,
|
||||
GoName: camelCase(string(desc.Name())),
|
||||
Path: []int32{fileServiceField, int32(desc.Index())},
|
||||
Desc: desc,
|
||||
GoName: camelCase(string(desc.Name())),
|
||||
Location: f.location(fileServiceField, int32(desc.Index())),
|
||||
}
|
||||
for i, mdescs := 0, desc.Methods(); i < mdescs.Len(); i++ {
|
||||
service.Methods = append(service.Methods, newMethod(gen, f, service, mdescs.Get(i)))
|
||||
@ -776,7 +804,7 @@ type Method struct {
|
||||
|
||||
GoName string
|
||||
ParentService *Service
|
||||
Path []int32 // location path of this method
|
||||
Location Location // location of this method
|
||||
InputType *Message
|
||||
OutputType *Message
|
||||
}
|
||||
@ -786,7 +814,7 @@ func newMethod(gen *Plugin, f *File, service *Service, desc protoreflect.MethodD
|
||||
Desc: desc,
|
||||
GoName: camelCase(string(desc.Name())),
|
||||
ParentService: service,
|
||||
Path: pathAppend(service.Path, serviceMethodField, int32(desc.Index())),
|
||||
Location: service.Location.appendPath(serviceMethodField, int32(desc.Index())),
|
||||
}
|
||||
return method
|
||||
}
|
||||
@ -814,8 +842,6 @@ func (method *Method) init(gen *Plugin) error {
|
||||
// P prints a line to the generated output. It converts each parameter to a
|
||||
// string following the same rules as fmt.Print. It never inserts spaces
|
||||
// between parameters.
|
||||
//
|
||||
// TODO: .meta file annotations.
|
||||
func (g *GeneratedFile) P(v ...interface{}) {
|
||||
for _, x := range v {
|
||||
switch x := x.(type) {
|
||||
@ -863,8 +889,18 @@ func (g *GeneratedFile) Write(p []byte) (n int, err error) {
|
||||
return g.buf.Write(p)
|
||||
}
|
||||
|
||||
// Content returns the contents of the generated file.
|
||||
func (g *GeneratedFile) Content() ([]byte, error) {
|
||||
// Annotate associates a symbol in a generated Go file with a location in a
|
||||
// source .proto file.
|
||||
//
|
||||
// The symbol may refer to a type, constant, variable, function, method, or
|
||||
// struct field. The "T.sel" syntax is used to identify the method or field
|
||||
// 'sel' on type 'T'.
|
||||
func (g *GeneratedFile) Annotate(symbol string, loc Location) {
|
||||
g.annotations[symbol] = append(g.annotations[symbol], loc)
|
||||
}
|
||||
|
||||
// content returns the contents of the generated file.
|
||||
func (g *GeneratedFile) content() ([]byte, error) {
|
||||
if !strings.HasSuffix(g.filename, ".go") {
|
||||
return g.buf.Bytes(), nil
|
||||
}
|
||||
@ -912,9 +948,72 @@ func (g *GeneratedFile) Content() ([]byte, error) {
|
||||
if err = (&printer.Config{Mode: printer.TabIndent | printer.UseSpaces, Tabwidth: 8}).Fprint(&out, fset, file); err != nil {
|
||||
return nil, fmt.Errorf("%v: can not reformat Go source: %v", g.filename, err)
|
||||
}
|
||||
// TODO: Annotations.
|
||||
return out.Bytes(), nil
|
||||
}
|
||||
|
||||
// metaFile returns the contents of the file's metadata file, which is a
|
||||
// text formatted string of the google.protobuf.GeneratedCodeInfo.
|
||||
func (g *GeneratedFile) metaFile(content []byte) (string, error) {
|
||||
fset := token.NewFileSet()
|
||||
astFile, err := parser.ParseFile(fset, "", content, 0)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
info := &descpb.GeneratedCodeInfo{}
|
||||
|
||||
seenAnnotations := make(map[string]bool)
|
||||
annotate := func(s string, ident *ast.Ident) {
|
||||
seenAnnotations[s] = true
|
||||
for _, loc := range g.annotations[s] {
|
||||
info.Annotation = append(info.Annotation, &descpb.GeneratedCodeInfo_Annotation{
|
||||
SourceFile: proto.String(loc.SourceFile),
|
||||
Path: loc.Path,
|
||||
Begin: proto.Int32(int32(fset.Position(ident.Pos()).Offset)),
|
||||
End: proto.Int32(int32(fset.Position(ident.End()).Offset)),
|
||||
})
|
||||
}
|
||||
}
|
||||
for _, decl := range astFile.Decls {
|
||||
switch decl := decl.(type) {
|
||||
case *ast.GenDecl:
|
||||
for _, spec := range decl.Specs {
|
||||
switch spec := spec.(type) {
|
||||
case *ast.TypeSpec:
|
||||
annotate(spec.Name.Name, spec.Name)
|
||||
if st, ok := spec.Type.(*ast.StructType); ok {
|
||||
for _, field := range st.Fields.List {
|
||||
for _, name := range field.Names {
|
||||
annotate(spec.Name.Name+"."+name.Name, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
case *ast.ValueSpec:
|
||||
for _, name := range spec.Names {
|
||||
annotate(name.Name, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
case *ast.FuncDecl:
|
||||
if decl.Recv == nil {
|
||||
annotate(decl.Name.Name, decl.Name)
|
||||
} else {
|
||||
recv := decl.Recv.List[0].Type
|
||||
if s, ok := recv.(*ast.StarExpr); ok {
|
||||
recv = s.X
|
||||
}
|
||||
if id, ok := recv.(*ast.Ident); ok {
|
||||
annotate(id.Name+"."+decl.Name.Name, decl.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for a := range g.annotations {
|
||||
if !seenAnnotations[a] {
|
||||
return "", fmt.Errorf("%v: no symbol matching annotation %q", g.filename, a)
|
||||
}
|
||||
}
|
||||
|
||||
return proto.CompactTextString(info), nil
|
||||
}
|
||||
|
||||
type pathType int
|
||||
@ -952,11 +1051,22 @@ const (
|
||||
serviceStreamField = 4 // stream
|
||||
)
|
||||
|
||||
// pathAppend appends elements to a location path.
|
||||
// It does not alias the original path.
|
||||
func pathAppend(path []int32, a ...int32) []int32 {
|
||||
var n []int32
|
||||
n = append(n, path...)
|
||||
n = append(n, a...)
|
||||
return n
|
||||
// A Location is a location in a .proto source file.
|
||||
//
|
||||
// See the google.protobuf.SourceCodeInfo documentation in descriptor.proto
|
||||
// for details.
|
||||
type Location struct {
|
||||
SourceFile string
|
||||
Path []int32
|
||||
}
|
||||
|
||||
// appendPath add elements to a Location's path, returning a new Location.
|
||||
func (loc Location) appendPath(a ...int32) Location {
|
||||
var n []int32
|
||||
n = append(n, loc.Path...)
|
||||
n = append(n, a...)
|
||||
return Location{
|
||||
SourceFile: loc.SourceFile,
|
||||
Path: n,
|
||||
}
|
||||
}
|
||||
|
@ -295,9 +295,9 @@ var _ = bar1.X // "golang.org/y/bar"
|
||||
var _ = baz.X // "golang.org/x/baz"
|
||||
var _ = string1.X // "golang.org/z/string"
|
||||
`
|
||||
got, err := g.Content()
|
||||
got, err := g.content()
|
||||
if err != nil {
|
||||
t.Fatalf("g.Content() = %v", err)
|
||||
t.Fatalf("g.content() = %v", err)
|
||||
}
|
||||
if want != string(got) {
|
||||
t.Fatalf(`want:
|
||||
@ -331,9 +331,9 @@ import bar "prefix/golang.org/x/bar"
|
||||
|
||||
var _ = bar.X
|
||||
`
|
||||
got, err := g.Content()
|
||||
got, err := g.content()
|
||||
if err != nil {
|
||||
t.Fatalf("g.Content() = %v", err)
|
||||
t.Fatalf("g.content() = %v", err)
|
||||
}
|
||||
if want != string(got) {
|
||||
t.Fatalf(`want:
|
||||
|
Loading…
x
Reference in New Issue
Block a user