cmd/protoc-gen-go: generate deprecation comments

Generate deprecation comments on packages, enums, enum values, messages,
and fields.

Change-Id: I8a94aff535078d33d1cc6104cff17e062cbfe94f
Reviewed-on: https://go-review.googlesource.com/136355
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/cmd/protoc-gen-go/main.go b/cmd/protoc-gen-go/main.go
index 2c94298..d5bef26 100644
--- a/cmd/protoc-gen-go/main.go
+++ b/cmd/protoc-gen-go/main.go
@@ -89,7 +89,11 @@
 
 	g := gen.NewGeneratedFile(f.GeneratedFilenamePrefix+".pb.go", f.GoImportPath)
 	g.P("// Code generated by protoc-gen-go. DO NOT EDIT.")
-	g.P("// source: ", f.Desc.Path())
+	if f.Proto.GetOptions().GetDeprecated() {
+		g.P("// ", f.Desc.Path(), " is a deprecated file.")
+	} else {
+		g.P("// source: ", f.Desc.Path())
+	}
 	g.P()
 	const filePackageField = 2 // FileDescriptorProto.package
 	genComment(g, f, []int32{filePackageField})
@@ -229,13 +233,13 @@
 
 func genEnum(gen *protogen.Plugin, g *protogen.GeneratedFile, f *File, enum *protogen.Enum) {
 	genComment(g, f, enum.Path)
-	// TODO: deprecation
-	g.P("type ", enum.GoIdent, " int32")
+	g.P("type ", enum.GoIdent, " int32",
+		deprecationComment(enumOptions(gen, enum).GetDeprecated()))
 	g.P("const (")
 	for _, value := range enum.Values {
 		genComment(g, f, value.Path)
-		// TODO: deprecation
-		g.P(value.GoIdent, " ", enum.GoIdent, " = ", value.Desc.Number())
+		g.P(value.GoIdent, " ", enum.GoIdent, " = ", value.Desc.Number(),
+			deprecationComment(enumValueOptions(gen, value).GetDeprecated()))
 	}
 	g.P(")")
 	g.P()
@@ -321,8 +325,13 @@
 		return
 	}
 
-	genComment(g, f, message.Path)
-	// TODO: deprecation
+	hasComment := genComment(g, f, message.Path)
+	if messageOptions(gen, message).GetDeprecated() {
+		if hasComment {
+			g.P("//")
+		}
+		g.P(deprecationComment(true))
+	}
 	g.P("type ", message.GoIdent, " struct {")
 	for _, field := range message.Fields {
 		if field.OneofType != nil {
@@ -352,7 +361,8 @@
 				fmt.Sprintf("protobuf_val:%q", fieldProtobufTag(val)),
 			)
 		}
-		g.P(field.GoName, " ", goType, " `", strings.Join(tags, " "), "`")
+		g.P(field.GoName, " ", goType, " `", strings.Join(tags, " "), "`",
+			deprecationComment(fieldOptions(gen, field).GetDeprecated()))
 	}
 	g.P("XXX_NoUnkeyedLiteral struct{} `json:\"-\"`")
 
@@ -524,6 +534,9 @@
 		}
 		goType, pointer := fieldGoType(g, field)
 		defaultValue := fieldDefaultValue(g, message, field)
+		if fieldOptions(gen, field).GetDeprecated() {
+			g.P(deprecationComment(true))
+		}
 		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 {")
@@ -830,6 +843,14 @@
 	return hasComment
 }
 
+// deprecationComment returns a standard deprecation comment if deprecated is true.
+func deprecationComment(deprecated bool) string {
+	if !deprecated {
+		return ""
+	}
+	return "// Deprecated: Do not use."
+}
+
 // pathKey converts a location path to a string suitable for use as a map key.
 func pathKey(path []int32) string {
 	var buf []byte
diff --git a/cmd/protoc-gen-go/options.go b/cmd/protoc-gen-go/options.go
index a3828ea..53e3e78 100644
--- a/cmd/protoc-gen-go/options.go
+++ b/cmd/protoc-gen-go/options.go
@@ -9,6 +9,7 @@
 package main
 
 import (
+	"github.com/golang/protobuf/proto"
 	descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
 	"google.golang.org/proto/protogen"
 	"google.golang.org/proto/reflect/protoreflect"
@@ -16,26 +17,106 @@
 
 // messageOptions returns the MessageOptions for a message.
 func messageOptions(gen *protogen.Plugin, message *protogen.Message) *descpb.MessageOptions {
-	file, ok := descriptorFile(gen, message.Desc)
-	if !ok {
+	d := getDescriptorProto(gen, message.Desc, message.Path)
+	if d == nil {
 		return nil
 	}
-	desc := file.Proto.MessageType[message.Path[1]]
-	for i := 3; i < len(message.Path); i += 2 {
-		desc = desc.NestedType[message.Path[1]]
-	}
-	return desc.GetOptions()
+	return d.(*descpb.DescriptorProto).GetOptions()
 }
 
-func descriptorFile(gen *protogen.Plugin, desc protoreflect.Descriptor) (*protogen.File, bool) {
+// fieldOptions returns the FieldOptions for a message.
+func fieldOptions(gen *protogen.Plugin, field *protogen.Field) *descpb.FieldOptions {
+	d := getDescriptorProto(gen, field.Desc, field.Path)
+	if d == nil {
+		return nil
+	}
+	return d.(*descpb.FieldDescriptorProto).GetOptions()
+}
+
+// enumOptions returns the EnumOptions for an enum
+func enumOptions(gen *protogen.Plugin, enum *protogen.Enum) *descpb.EnumOptions {
+	d := getDescriptorProto(gen, enum.Desc, enum.Path)
+	if d == nil {
+		return nil
+	}
+	return d.(*descpb.EnumDescriptorProto).GetOptions()
+}
+
+// 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)
+	if d == nil {
+		return nil
+	}
+	return d.(*descpb.EnumValueDescriptorProto).GetOptions()
+}
+
+func getDescriptorProto(gen *protogen.Plugin, desc protoreflect.Descriptor, path []int32) proto.Message {
+	var p proto.Message
+	// Look up the FileDescriptorProto.
 	for {
 		if fdesc, ok := desc.(protoreflect.FileDescriptor); ok {
-			return gen.FileByName(fdesc.Path())
+			file, ok := gen.FileByName(fdesc.Path())
+			if !ok {
+				return nil
+			}
+			p = file.Proto
+			break
 		}
 		var ok bool
 		desc, ok = desc.Parent()
 		if !ok {
-			return nil, false
+			return nil
 		}
 	}
+	const (
+		// field numbers in FileDescriptorProto
+		filePackageField   = 2 // package
+		fileMessageField   = 4 // message_type
+		fileEnumField      = 5 // enum_type
+		fileExtensionField = 7 // extension
+		// field numbers in DescriptorProto
+		messageFieldField     = 2 // field
+		messageMessageField   = 3 // nested_type
+		messageEnumField      = 4 // enum_type
+		messageExtensionField = 6 // extension
+		messageOneofField     = 8 // oneof_decl
+		// field numbers in EnumDescriptorProto
+		enumValueField = 2 // value
+	)
+	for len(path) > 1 {
+		switch d := p.(type) {
+		case *descpb.FileDescriptorProto:
+			switch path[0] {
+			case fileMessageField:
+				p = d.MessageType[path[1]]
+			case fileEnumField:
+				p = d.EnumType[path[1]]
+			default:
+				return nil
+			}
+		case *descpb.DescriptorProto:
+			switch path[0] {
+			case messageFieldField:
+				p = d.Field[path[1]]
+			case messageMessageField:
+				p = d.NestedType[path[1]]
+			case messageEnumField:
+				p = d.EnumType[path[1]]
+			default:
+				return nil
+			}
+		case *descpb.EnumDescriptorProto:
+			switch path[0] {
+			case enumValueField:
+				p = d.Value[path[1]]
+			default:
+				return nil
+			}
+		default:
+			return nil
+		}
+		path = path[2:]
+	}
+	return p
 }
diff --git a/cmd/protoc-gen-go/testdata/comments/comments.pb.go b/cmd/protoc-gen-go/testdata/comments/comments.pb.go
index 682a960..39fb376 100644
--- a/cmd/protoc-gen-go/testdata/comments/comments.pb.go
+++ b/cmd/protoc-gen-go/testdata/comments/comments.pb.go
@@ -1,9 +1,9 @@
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // source: comments/comments.proto
 
-// COMMENT: package goproto.protoc.proto2
+// COMMENT: package goproto.protoc.comments;
 
-package proto2
+package comments
 
 import (
 	fmt "fmt"
@@ -215,25 +215,25 @@
 var xxx_messageInfo_Message2_Message2B proto.InternalMessageInfo
 
 func init() {
-	proto.RegisterType((*Message1)(nil), "goproto.protoc.proto2.Message1")
-	proto.RegisterType((*Message1_Message1A)(nil), "goproto.protoc.proto2.Message1.Message1A")
-	proto.RegisterType((*Message1_Message1B)(nil), "goproto.protoc.proto2.Message1.Message1B")
-	proto.RegisterType((*Message2)(nil), "goproto.protoc.proto2.Message2")
-	proto.RegisterType((*Message2_Message2A)(nil), "goproto.protoc.proto2.Message2.Message2A")
-	proto.RegisterType((*Message2_Message2B)(nil), "goproto.protoc.proto2.Message2.Message2B")
+	proto.RegisterType((*Message1)(nil), "goproto.protoc.comments.Message1")
+	proto.RegisterType((*Message1_Message1A)(nil), "goproto.protoc.comments.Message1.Message1A")
+	proto.RegisterType((*Message1_Message1B)(nil), "goproto.protoc.comments.Message1.Message1B")
+	proto.RegisterType((*Message2)(nil), "goproto.protoc.comments.Message2")
+	proto.RegisterType((*Message2_Message2A)(nil), "goproto.protoc.comments.Message2.Message2A")
+	proto.RegisterType((*Message2_Message2B)(nil), "goproto.protoc.comments.Message2.Message2B")
 }
 
 func init() { proto.RegisterFile("comments/comments.proto", fileDescriptor_885e8293f1fab554) }
 
 var fileDescriptor_885e8293f1fab554 = []byte{
-	// 136 bytes of a gzipped FileDescriptorProto
+	// 135 bytes of a gzipped FileDescriptorProto
 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4f, 0xce, 0xcf, 0xcd,
-	0x4d, 0xcd, 0x2b, 0x29, 0xd6, 0x87, 0x31, 0xf4, 0x0a, 0x8a, 0xf2, 0x4b, 0xf2, 0x85, 0x44, 0xd3,
-	0xf3, 0xc1, 0x0c, 0x08, 0x37, 0x19, 0x42, 0x19, 0x29, 0xa9, 0x70, 0x71, 0xf8, 0xa6, 0x16, 0x17,
-	0x27, 0xa6, 0xa7, 0x1a, 0x4a, 0x71, 0x73, 0x71, 0xc2, 0xd8, 0x8e, 0xc8, 0x1c, 0x27, 0x24, 0x55,
-	0x46, 0x48, 0x12, 0x46, 0xc8, 0xaa, 0x8c, 0x9c, 0x9c, 0xac, 0xa3, 0x2c, 0xd3, 0xf3, 0xf3, 0xd3,
-	0x73, 0x52, 0xf5, 0xd2, 0xf3, 0x73, 0x12, 0xf3, 0xd2, 0xf5, 0xf2, 0x8b, 0xd2, 0xf5, 0xc1, 0xf6,
-	0xe8, 0x27, 0xe7, 0xa6, 0x40, 0x58, 0xc9, 0xba, 0xe9, 0xa9, 0x79, 0xba, 0xe9, 0xf9, 0xfa, 0x25,
-	0xa9, 0xc5, 0x25, 0x29, 0x89, 0x25, 0x89, 0x10, 0x61, 0x23, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff,
-	0x32, 0x8f, 0xcd, 0x4f, 0xb9, 0x00, 0x00, 0x00,
+	0x4d, 0xcd, 0x2b, 0x29, 0xd6, 0x87, 0x31, 0xf4, 0x0a, 0x8a, 0xf2, 0x4b, 0xf2, 0x85, 0xc4, 0xd3,
+	0xf3, 0xc1, 0x0c, 0x08, 0x37, 0x59, 0x0f, 0x26, 0xad, 0xa4, 0xc2, 0xc5, 0xe1, 0x9b, 0x5a, 0x5c,
+	0x9c, 0x98, 0x9e, 0x6a, 0x28, 0xc5, 0xcd, 0xc5, 0x09, 0x63, 0x3b, 0x22, 0x73, 0x9c, 0x90, 0x54,
+	0x19, 0x21, 0x49, 0x18, 0x21, 0xab, 0x32, 0x72, 0x72, 0xb2, 0x8d, 0xb2, 0x4e, 0xcf, 0xcf, 0x4f,
+	0xcf, 0x49, 0xd5, 0x4b, 0xcf, 0xcf, 0x49, 0xcc, 0x4b, 0xd7, 0xcb, 0x2f, 0x4a, 0xd7, 0x07, 0x5b,
+	0xa8, 0x9f, 0x9c, 0x9b, 0x02, 0x61, 0x25, 0xeb, 0xa6, 0xa7, 0xe6, 0xe9, 0xa6, 0xe7, 0xeb, 0x97,
+	0xa4, 0x16, 0x97, 0xa4, 0x24, 0x96, 0x24, 0xc2, 0x5d, 0x0a, 0x08, 0x00, 0x00, 0xff, 0xff, 0x2e,
+	0x72, 0xe4, 0xcc, 0xbd, 0x00, 0x00, 0x00,
 }
diff --git a/cmd/protoc-gen-go/testdata/comments/comments.proto b/cmd/protoc-gen-go/testdata/comments/comments.proto
index d97c8dc..7ee51ce 100644
--- a/cmd/protoc-gen-go/testdata/comments/comments.proto
+++ b/cmd/protoc-gen-go/testdata/comments/comments.proto
@@ -4,10 +4,10 @@
 
 syntax = "proto2";
 
-// COMMENT: package goproto.protoc.proto2
-package goproto.protoc.proto2;
+// COMMENT: package goproto.protoc.comments;
+package goproto.protoc.comments;
 
-option go_package = "google.golang.org/proto/cmd/protoc-gen-go/testdata/proto2";
+option go_package = "google.golang.org/proto/cmd/protoc-gen-go/testdata/comments";
 
 // COMMENT: Message1
 message Message1 {
diff --git a/cmd/protoc-gen-go/testdata/comments/deprecated.pb.go b/cmd/protoc-gen-go/testdata/comments/deprecated.pb.go
new file mode 100644
index 0000000..e820c3b
--- /dev/null
+++ b/cmd/protoc-gen-go/testdata/comments/deprecated.pb.go
@@ -0,0 +1,107 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// comments/deprecated.proto is a deprecated file.
+
+package comments
+
+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 DeprecatedEnum int32 // Deprecated: Do not use.
+const (
+	DeprecatedEnum_DEPRECATED DeprecatedEnum = 0 // Deprecated: Do not use.
+)
+
+var DeprecatedEnum_name = map[int32]string{
+	0: "DEPRECATED",
+}
+
+var DeprecatedEnum_value = map[string]int32{
+	"DEPRECATED": 0,
+}
+
+func (x DeprecatedEnum) String() string {
+	return proto.EnumName(DeprecatedEnum_name, int32(x))
+}
+
+func (DeprecatedEnum) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_0336e614ee2de5f7, []int{0}
+}
+
+// Deprecated: Do not use.
+type DeprecatedMessage struct {
+	DeprecatedField      string   `protobuf:"bytes,1,opt,name=deprecated_field,json=deprecatedField,proto3" json:"deprecated_field,omitempty"` // Deprecated: Do not use.
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *DeprecatedMessage) Reset()         { *m = DeprecatedMessage{} }
+func (m *DeprecatedMessage) String() string { return proto.CompactTextString(m) }
+func (*DeprecatedMessage) ProtoMessage()    {}
+func (*DeprecatedMessage) Descriptor() ([]byte, []int) {
+	return fileDescriptor_0336e614ee2de5f7, []int{0}
+}
+
+func (m *DeprecatedMessage) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_DeprecatedMessage.Unmarshal(m, b)
+}
+func (m *DeprecatedMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_DeprecatedMessage.Marshal(b, m, deterministic)
+}
+func (m *DeprecatedMessage) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_DeprecatedMessage.Merge(m, src)
+}
+func (m *DeprecatedMessage) XXX_Size() int {
+	return xxx_messageInfo_DeprecatedMessage.Size(m)
+}
+func (m *DeprecatedMessage) XXX_DiscardUnknown() {
+	xxx_messageInfo_DeprecatedMessage.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_DeprecatedMessage proto.InternalMessageInfo
+
+// Deprecated: Do not use.
+func (m *DeprecatedMessage) GetDeprecatedField() string {
+	if m != nil {
+		return m.DeprecatedField
+	}
+	return ""
+}
+
+func init() {
+	proto.RegisterType((*DeprecatedMessage)(nil), "goproto.protoc.comments.DeprecatedMessage")
+	proto.RegisterEnum("goproto.protoc.comments.DeprecatedEnum", DeprecatedEnum_name, DeprecatedEnum_value)
+}
+
+func init() { proto.RegisterFile("comments/deprecated.proto", fileDescriptor_0336e614ee2de5f7) }
+
+var fileDescriptor_0336e614ee2de5f7 = []byte{
+	// 202 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x44, 0x8e, 0x31, 0x4b, 0x04, 0x31,
+	0x10, 0x85, 0x9d, 0x14, 0xa2, 0x29, 0xf4, 0x4c, 0x63, 0xbc, 0x4a, 0xac, 0x0e, 0x61, 0x93, 0xc2,
+	0x4e, 0x1b, 0x3d, 0x37, 0xd7, 0x09, 0x72, 0x58, 0xd9, 0x48, 0x4c, 0xc6, 0x41, 0xd8, 0x64, 0x96,
+	0x4d, 0xfc, 0x6f, 0xfe, 0x3c, 0x71, 0x97, 0xbd, 0xed, 0xde, 0xe3, 0x7d, 0x3c, 0x3e, 0x79, 0x15,
+	0x38, 0x25, 0xcc, 0xb5, 0xd8, 0x88, 0xfd, 0x80, 0xc1, 0x57, 0x8c, 0xa6, 0x1f, 0xb8, 0xb2, 0xba,
+	0x24, 0x1e, 0xc3, 0x54, 0x83, 0x99, 0xc9, 0x9b, 0x9d, 0xbc, 0x68, 0x0f, 0xf0, 0x0b, 0x96, 0xe2,
+	0x09, 0x55, 0x23, 0x57, 0xcb, 0xc3, 0xc7, 0xd7, 0x37, 0x76, 0x51, 0xc3, 0x35, 0x6c, 0x4e, 0xb7,
+	0x42, 0xc3, 0xfe, 0x7c, 0xd9, 0x76, 0xff, 0xd3, 0xbd, 0xd0, 0x70, 0xbb, 0x91, 0x67, 0xcb, 0x8f,
+	0xcb, 0x3f, 0x49, 0x29, 0x29, 0x5b, 0xf7, 0xba, 0x77, 0xcf, 0x4f, 0x6f, 0xae, 0x5d, 0x1d, 0xad,
+	0xc5, 0x09, 0xac, 0x85, 0x86, 0xed, 0xe3, 0xfb, 0x03, 0x31, 0x53, 0x87, 0x86, 0xb8, 0xf3, 0x99,
+	0x0c, 0x0f, 0x64, 0x47, 0x2d, 0x1b, 0x52, 0x9c, 0x52, 0x68, 0x08, 0x73, 0x43, 0x6c, 0x2b, 0x96,
+	0x1a, 0x7d, 0xf5, 0x76, 0x16, 0xfe, 0x05, 0xf8, 0x3c, 0x1e, 0x99, 0xbb, 0xbf, 0x00, 0x00, 0x00,
+	0xff, 0xff, 0x58, 0x8a, 0xbc, 0xc2, 0xf0, 0x00, 0x00, 0x00,
+}
diff --git a/cmd/protoc-gen-go/testdata/comments/deprecated.proto b/cmd/protoc-gen-go/testdata/comments/deprecated.proto
new file mode 100644
index 0000000..a33cb33
--- /dev/null
+++ b/cmd/protoc-gen-go/testdata/comments/deprecated.proto
@@ -0,0 +1,20 @@
+// 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 = "proto3";
+
+package goproto.protoc.comments;
+
+option deprecated = true;
+option go_package = "google.golang.org/proto/cmd/protoc-gen-go/testdata/comments";
+
+message DeprecatedMessage {
+  option deprecated = true;
+  string deprecated_field = 1 [deprecated=true];
+}
+
+enum DeprecatedEnum {
+  option deprecated = true;
+  DEPRECATED = 0 [deprecated=true];
+}