Repository: uber/prototool
Pull request #267 (2019-01-04 03:21:58)

Finish MVP of reflect and extract packages
This should be the majority of the functionality we need to support the initial breaking change detector. I'll add more testing as well later, but the actual function signatures may be in flux so I wanted to leave this as-is until the breaking change detector is done.


Patch:
diff --git a/.codecov.yml b/.codecov.yml
index c93e665..aca5c08 100644
--- a/.codecov.yml
+++ b/.codecov.yml
@@ -16,4 +16,4 @@ ignore:
   - /example/gen/proto/go/foo
   - /example/gen/proto/go/sub
   - /internal/cmd/testdata/grpc/gen/grpcpb
-  - /internal/reflect/gen/uber/proto/reflect
+  - /internal/reflect/gen/uber/proto/reflect/v1
diff --git a/Makefile b/Makefile
index e218d9c..6bda7e6 100644
--- a/Makefile
+++ b/Makefile
@@ -72,7 +72,7 @@ internalgen: install
 	rm -rf internal/cmd/testdata/grpc/gen
 	prototool generate internal/cmd/testdata/grpc
 	rm -rf internal/reflect/gen
-	prototool all internal/reflect
+	prototool all internal/reflect/proto
 	rm -f etc/config/example/prototool.yaml
 	prototool config init etc/config/example --uncomment
 
diff --git a/internal/extract/extract.go b/internal/extract/extract.go
index 2d8fab7..0f67969 100644
--- a/internal/extract/extract.go
+++ b/internal/extract/extract.go
@@ -24,18 +24,18 @@ package extract
 import (
 	"fmt"
 
-	reflectpb "github.com/uber/prototool/internal/reflect/gen/uber/proto/reflect"
+	reflectv1 "github.com/uber/prototool/internal/reflect/gen/uber/proto/reflect/v1"
 )
 
 // PackageSet is the Golang wrapper for the Protobuf PackageSet object.
 type PackageSet struct {
-	protoMessage *reflectpb.PackageSet
+	protoMessage *reflectv1.PackageSet
 
 	packageNameToPackage map[string]*Package
 }
 
 // ProtoMessage returns the underlying Protobuf messge.
-func (p *PackageSet) ProtoMessage() *reflectpb.PackageSet {
+func (p *PackageSet) ProtoMessage() *reflectv1.PackageSet {
 	return p.protoMessage
 }
 
@@ -46,18 +46,26 @@ func (p *PackageSet) PackageNameToPackage() map[string]*Package {
 
 // Package is the Golang wrapper for the Protobuf Package object.
 type Package struct {
-	protoMessage *reflectpb.Package
+	protoMessage *reflectv1.Package
 
 	packageSet                 *PackageSet
 	dependencyNameToDependency map[string]*Package
 	importerNameToImporter     map[string]*Package
+	enumNameToEnum             map[string]*Enum
+	messageNameToMessage       map[string]*Message
+	serviceNameToService       map[string]*Service
 }
 
 // ProtoMessage returns the underlying Protobuf messge.
-func (p *Package) ProtoMessage() *reflectpb.Package {
+func (p *Package) ProtoMessage() *reflectv1.Package {
 	return p.protoMessage
 }
 
+// FullyQualifiedName returns the fully-qualified name.
+func (p *Package) FullyQualifiedName() string {
+	return p.protoMessage.Name
+}
+
 // PackageSet returns the parent PackageSet.
 func (p *Package) PackageSet() *PackageSet {
 	return p.packageSet
@@ -73,8 +81,86 @@ func (p *Package) ImporterNameToImporter() map[string]*Package {
 	return p.importerNameToImporter
 }
 
+// EnumNameToEnum returns the nested enums of the given Package.
+func (p *Package) EnumNameToEnum() map[string]*Enum {
+	return p.enumNameToEnum
+}
+
+// MessageNameToMessage returns the nested messages of the given Package.
+func (p *Package) MessageNameToMessage() map[string]*Message {
+	return p.messageNameToMessage
+}
+
+// ServiceNameToService returns the nested services of the given Package.
+func (p *Package) ServiceNameToService() map[string]*Service {
+	return p.serviceNameToService
+}
+
+// Enum is the Golang wrapper for the Protobuf Enum object.
+type Enum struct {
+	protoMessage *reflectv1.Enum
+
+	fullyQualifiedName string
+}
+
+// ProtoMessage returns the underlying Protobuf messge.
+func (e *Enum) ProtoMessage() *reflectv1.Enum {
+	return e.protoMessage
+}
+
+// FullyQualifiedName returns the fully-qualified name.
+func (e *Enum) FullyQualifiedName() string {
+	return e.fullyQualifiedName
+}
+
+// Message is the Golang wrapper for the Protobuf Message object.
+type Message struct {
+	protoMessage *reflectv1.Message
+
+	fullyQualifiedName         string
+	nestedEnumNameToEnum       map[string]*Enum
+	nestedMessageNameToMessage map[string]*Message
+}
+
+// ProtoMessage returns the underlying Protobuf messge.
+func (m *Message) ProtoMessage() *reflectv1.Message {
+	return m.protoMessage
+}
+
+// FullyQualifiedName returns the fully-qualified name.
+func (m *Message) FullyQualifiedName() string {
+	return m.fullyQualifiedName
+}
+
+// NestedEnumNameToEnum returns the nested enums of the given Message.
+func (m *Message) NestedEnumNameToEnum() map[string]*Enum {
+	return m.nestedEnumNameToEnum
+}
+
+// NestedMessageNameToMessage returns the nested messages of the given Message.
+func (m *Message) NestedMessageNameToMessage() map[string]*Message {
+	return m.nestedMessageNameToMessage
+}
+
+// Service is the Golang wrapper for the Protobuf Service object.
+type Service struct {
+	protoMessage *reflectv1.Service
+
+	fullyQualifiedName string
+}
+
+// ProtoMessage returns the underlying Protobuf messge.
+func (s *Service) ProtoMessage() *reflectv1.Service {
+	return s.protoMessage
+}
+
+// FullyQualifiedName returns the fully-qualified name.
+func (s *Service) FullyQualifiedName() string {
+	return s.fullyQualifiedName
+}
+
 // NewPackageSet returns a new PackageSet for the given reflect PackageSet.
-func NewPackageSet(protoMessage *reflectpb.PackageSet) (*PackageSet, error) {
+func NewPackageSet(protoMessage *reflectv1.PackageSet) (*PackageSet, error) {
 	packageSet := &PackageSet{
 		protoMessage:         protoMessage,
 		packageNameToPackage: make(map[string]*Package),
@@ -85,6 +171,9 @@ func NewPackageSet(protoMessage *reflectpb.PackageSet) (*PackageSet, error) {
 			packageSet:                 packageSet,
 			dependencyNameToDependency: make(map[string]*Package),
 			importerNameToImporter:     make(map[string]*Package),
+			enumNameToEnum:             make(map[string]*Enum),
+			messageNameToMessage:       make(map[string]*Message),
+			serviceNameToService:       make(map[string]*Service),
 		}
 	}
 	for packageName, pkg := range packageSet.packageNameToPackage {
@@ -97,5 +186,53 @@ func NewPackageSet(protoMessage *reflectpb.PackageSet) (*PackageSet, error) {
 			dependency.importerNameToImporter[packageName] = pkg
 		}
 	}
+	for packageName, pkg := range packageSet.packageNameToPackage {
+		for _, enum := range pkg.protoMessage.Enums {
+			pkg.enumNameToEnum[enum.Name] = newEnum(enum, packageName)
+		}
+		for _, message := range pkg.protoMessage.Messages {
+			pkg.messageNameToMessage[message.Name] = newMessage(message, packageName)
+		}
+		for _, service := range pkg.protoMessage.Services {
+			pkg.serviceNameToService[service.Name] = newService(service, packageName)
+		}
+	}
 	return packageSet, nil
 }
+
+func newEnum(protoMessage *reflectv1.Enum, encapsulatingFullyQualifiedName string) *Enum {
+	return &Enum{
+		protoMessage:       protoMessage,
+		fullyQualifiedName: getFullyQualifiedName(encapsulatingFullyQualifiedName, protoMessage.Name),
+	}
+}
+
+func newMessage(protoMessage *reflectv1.Message, encapsulatingFullyQualifiedName string) *Message {
+	message := &Message{
+		protoMessage:               protoMessage,
+		fullyQualifiedName:         getFullyQualifiedName(encapsulatingFullyQualifiedName, protoMessage.Name),
+		nestedEnumNameToEnum:       make(map[string]*Enum),
+		nestedMessageNameToMessage: make(map[string]*Message),
+	}
+	for _, nestedEnum := range protoMessage.NestedEnums {
+		message.nestedEnumNameToEnum[nestedEnum.Name] = newEnum(nestedEnum, message.fullyQualifiedName)
+	}
+	for _, nestedMessage := range protoMessage.NestedMessages {
+		message.nestedMessageNameToMessage[nestedMessage.Name] = newMessage(nestedMessage, message.fullyQualifiedName)
+	}
+	return message
+}
+
+func newService(protoMessage *reflectv1.Service, encapsulatingFullyQualifiedName string) *Service {
+	return &Service{
+		protoMessage:       protoMessage,
+		fullyQualifiedName: getFullyQualifiedName(encapsulatingFullyQualifiedName, protoMessage.Name),
+	}
+}
+
+func getFullyQualifiedName(encapsulatingFullyQualifiedName string, name string) string {
+	if encapsulatingFullyQualifiedName == "" {
+		return name
+	}
+	return encapsulatingFullyQualifiedName + "." + name
+}
diff --git a/internal/lint/check_fields_not_reserved.go b/internal/lint/check_fields_not_reserved.go
new file mode 100644
index 0000000..4740f9a
--- /dev/null
+++ b/internal/lint/check_fields_not_reserved.go
@@ -0,0 +1,81 @@
+// Copyright (c) 2019 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package lint
+
+import (
+	"strings"
+
+	"github.com/emicklei/proto"
+	"github.com/uber/prototool/internal/text"
+)
+
+var fieldsNotReservedLinter = NewLinter(
+	"FIELDS_NOT_RESERVED",
+	`Verifies that no message or enum has a reserved field.`,
+	checkFieldsNotReserved,
+)
+
+func checkFieldsNotReserved(add func(*text.Failure), dirPath string, descriptors []*proto.Proto) error {
+	return runVisitor(fieldsNotReservedVisitor{baseAddVisitor: newBaseAddVisitor(add), outerNameStack: make([]string, 0)}, descriptors)
+}
+
+type fieldsNotReservedVisitor struct {
+	baseAddVisitor
+	outerNameStack []string
+}
+
+func (v fieldsNotReservedVisitor) VisitMessage(message *proto.Message) {
+	// Push name on stack.
+	v.outerNameStack = append(
+		v.outerNameStack,
+		message.Name,
+	)
+	for _, element := range message.Elements {
+		element.Accept(v)
+	}
+	// Pop name after visiting.
+	v.outerNameStack = v.outerNameStack[:len(v.outerNameStack)-1]
+}
+
+func (v fieldsNotReservedVisitor) VisitEnum(enum *proto.Enum) {
+	// Push name on stack.
+	v.outerNameStack = append(
+		v.outerNameStack,
+		enum.Name,
+	)
+	for _, element := range enum.Elements {
+		element.Accept(v)
+	}
+	// Pop after visiting.
+	v.outerNameStack = v.outerNameStack[:len(v.outerNameStack)-1]
+}
+
+func (v fieldsNotReservedVisitor) VisitReserved(reserved *proto.Reserved) {
+	var reservedType string
+	// A reserved can't have both ranges and field names in the same
+	// field.
+	if len(reserved.Ranges) > 0 {
+		reservedType = "field numbers"
+	} else {
+		reservedType = "field names"
+	}
+	v.AddFailuref(reserved.Position, `%q cannot have reserved %s, use the deprecated field option instead.`, strings.Join(v.outerNameStack, "."), reservedType)
+}
diff --git a/internal/lint/check_message_fields_no_json_name.go b/internal/lint/check_message_fields_no_json_name.go
index c32f50d..5704ebc 100644
--- a/internal/lint/check_message_fields_no_json_name.go
+++ b/internal/lint/check_message_fields_no_json_name.go
@@ -27,7 +27,7 @@ import (
 
 var messageFieldsNoJSONNameLinter = NewLinter(
 	"MESSAGE_FIELDS_NO_JSON_NAME",
-	`Verifies that no message field has the the "json_name" option set.`,
+	`Verifies that no message field has the "json_name" option set.`,
 	checkMessageFieldsNoJSONName,
 )
 
diff --git a/internal/lint/lint.go b/internal/lint/lint.go
index f3e41c2..2149e8f 100644
--- a/internal/lint/lint.go
+++ b/internal/lint/lint.go
@@ -46,6 +46,7 @@ var (
 		enumZeroValuesInvalidExceptMessageLinter,
 		enumsHaveCommentsLinter,
 		enumsNoAllowAliasLinter,
+		fieldsNotReservedLinter,
 		fileOptionsEqualGoPackageLastTwoSuffixLinter,
 		fileOptionsEqualGoPackagePbSuffixLinter,
 		fileOptionsEqualJavaMultipleFilesTrueLinter,
diff --git a/internal/reflect/gen/uber/proto/reflect/reflect.pb.go b/internal/reflect/gen/uber/proto/reflect/reflect.pb.go
deleted file mode 100644
index e0d98ed..0000000
--- a/internal/reflect/gen/uber/proto/reflect/reflect.pb.go
+++ /dev/null
@@ -1,147 +0,0 @@
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// source: uber/proto/reflect/reflect.proto
-
-package reflectpb
-
-import proto "github.com/golang/protobuf/proto"
-import fmt "fmt"
-import 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
-
-// PackageSet is a set of Packages.
-type PackageSet struct {
-	// packages contains the set of Packages.
-	//
-	// These will all be unique, and can be referenced by their name field.
-	// These will also be sorted by name.
-	Packages             []*Package `protobuf:"bytes,1,rep,name=packages,proto3" json:"packages,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}   `json:"-"`
-	XXX_unrecognized     []byte     `json:"-"`
-	XXX_sizecache        int32      `json:"-"`
-}
-
-func (m *PackageSet) Reset()         { *m = PackageSet{} }
-func (m *PackageSet) String() string { return proto.CompactTextString(m) }
-func (*PackageSet) ProtoMessage()    {}
-func (*PackageSet) Descriptor() ([]byte, []int) {
-	return fileDescriptor_reflect_226b6aeea8384819, []int{0}
-}
-func (m *PackageSet) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_PackageSet.Unmarshal(m, b)
-}
-func (m *PackageSet) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_PackageSet.Marshal(b, m, deterministic)
-}
-func (dst *PackageSet) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_PackageSet.Merge(dst, src)
-}
-func (m *PackageSet) XXX_Size() int {
-	return xxx_messageInfo_PackageSet.Size(m)
-}
-func (m *PackageSet) XXX_DiscardUnknown() {
-	xxx_messageInfo_PackageSet.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_PackageSet proto.InternalMessageInfo
-
-func (m *PackageSet) GetPackages() []*Package {
-	if m != nil {
-		return m.Packages
-	}
-	return nil
-}
-
-// Package describes a Protobuf package, constructed from a set of Protobuf
-// files that have the same "package" value.
-type Package struct {
-	// name is the fully-qualified name of the package.
-	//
-	// The package name can be thought of as the unique identifier for a
-	// Package message, and is used as a reference.
-	//
-	// This does not include the prefix '.' found in the traditional package
-	// fully-qualified name.
-	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
-	// dependency_names contains the names of this packages' dependencies.
-	//
-	// These will be sorted.
-	// If you have a set of Package messages, this will correspond to the name
-	// field of other Packages in the set.
-	DependencyNames      []string `protobuf:"bytes,2,rep,name=dependency_names,json=dependencyNames,proto3" json:"dependency_names,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
-}
-
-func (m *Package) Reset()         { *m = Package{} }
-func (m *Package) String() string { return proto.CompactTextString(m) }
-func (*Package) ProtoMessage()    {}
-func (*Package) Descriptor() ([]byte, []int) {
-	return fileDescriptor_reflect_226b6aeea8384819, []int{1}
-}
-func (m *Package) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_Package.Unmarshal(m, b)
-}
-func (m *Package) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_Package.Marshal(b, m, deterministic)
-}
-func (dst *Package) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_Package.Merge(dst, src)
-}
-func (m *Package) XXX_Size() int {
-	return xxx_messageInfo_Package.Size(m)
-}
-func (m *Package) XXX_DiscardUnknown() {
-	xxx_messageInfo_Package.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_Package proto.InternalMessageInfo
-
-func (m *Package) GetName() string {
-	if m != nil {
-		return m.Name
-	}
-	return ""
-}
-
-func (m *Package) GetDependencyNames() []string {
-	if m != nil {
-		return m.DependencyNames
-	}
-	return nil
-}
-
-func init() {
-	proto.RegisterType((*PackageSet)(nil), "uber.proto.reflect.PackageSet")
-	proto.RegisterType((*Package)(nil), "uber.proto.reflect.Package")
-}
-
-func init() {
-	proto.RegisterFile("uber/proto/reflect/reflect.proto", fileDescriptor_reflect_226b6aeea8384819)
-}
-
-var fileDescriptor_reflect_226b6aeea8384819 = []byte{
-	// 179 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x28, 0x4d, 0x4a, 0x2d,
-	0xd2, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0xd7, 0x2f, 0x4a, 0x4d, 0xcb, 0x49, 0x4d, 0x2e, 0x81, 0xd1,
-	0x7a, 0x60, 0x51, 0x21, 0x21, 0x90, 0x0a, 0x08, 0x5b, 0x0f, 0x2a, 0xa3, 0xe4, 0xca, 0xc5, 0x15,
-	0x90, 0x98, 0x9c, 0x9d, 0x98, 0x9e, 0x1a, 0x9c, 0x5a, 0x22, 0x64, 0xce, 0xc5, 0x51, 0x00, 0xe1,
-	0x15, 0x4b, 0x30, 0x2a, 0x30, 0x6b, 0x70, 0x1b, 0x49, 0xeb, 0x61, 0x6a, 0xd2, 0x83, 0xea, 0x08,
-	0x82, 0x2b, 0x56, 0xf2, 0xe0, 0x62, 0x87, 0x0a, 0x0a, 0x09, 0x71, 0xb1, 0xe4, 0x25, 0xe6, 0xa6,
-	0x4a, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x06, 0x81, 0xd9, 0x42, 0x9a, 0x5c, 0x02, 0x29, 0xa9, 0x05,
-	0xa9, 0x79, 0x29, 0xa9, 0x79, 0xc9, 0x95, 0xf1, 0x20, 0xa1, 0x62, 0x09, 0x26, 0x05, 0x66, 0x0d,
-	0xce, 0x20, 0x7e, 0x84, 0xb8, 0x1f, 0x48, 0xd8, 0xc9, 0x98, 0x4b, 0x2c, 0x39, 0x3f, 0x17, 0x8b,
-	0xad, 0x4e, 0x3c, 0x41, 0x10, 0x46, 0x00, 0x48, 0x34, 0x80, 0x31, 0x8a, 0x13, 0x2a, 0x51, 0x90,
-	0x94, 0xc4, 0x06, 0x56, 0x69, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x51, 0xf7, 0x17, 0xfb, 0x04,
-	0x01, 0x00, 0x00,
-}
diff --git a/internal/reflect/gen/uber/proto/reflect/v1/reflect.pb.go b/internal/reflect/gen/uber/proto/reflect/v1/reflect.pb.go
new file mode 100644
index 0000000..c59d29c
--- /dev/null
+++ b/internal/reflect/gen/uber/proto/reflect/v1/reflect.pb.go
@@ -0,0 +1,830 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: uber/proto/reflect/v1/reflect.proto
+
+package reflectv1
+
+/*
+Package uber.proto.reflect.v1 contains structures to represent information
+about Protobuf definitions on a per-package basis. This differs from
+the structures in descriptor.proto which represent Protobuf definitions
+on a per-file basis.
+
+This package does not represent all information about a Protobuf package
+at this time, be added in the future.
+
+A non-comprehensive list of excluded items:
+
+- Source code information.
+- All options.
+- Reserved ranges and names.
+- Message field default values.
+- Message field oneof indexes.
+- Message field JSON names.
+- Anything to do with extensions.
+
+Excluded items that should not be relevant at a package level:
+
+- Public vs non-public dependencies are all grouped into the same list.
+- Syntax information.
+- Fields vs extenstions.
+- Extension ranges.
+*/
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import 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
+
+// Label is the label of the message field.
+//
+// The numbers match the FieldDescriptorProto.Label numbers.
+//
+// Note that a map field will come up as repeated, with type TYPE_MESSAGE,
+// and the type_name will be *Entry. We may parse this from FieldDescriptorProtos
+// in the future and add a LABEL_MAP.
+type MessageField_Label int32
+
+const (
+	MessageField_LABEL_INVALID  MessageField_Label = 0
+	MessageField_LABEL_OPTIONAL MessageField_Label = 1
+	MessageField_LABEL_REQUIRED MessageField_Label = 2
+	MessageField_LABEL_REPEATED MessageField_Label = 3
+)
+
+var MessageField_Label_name = map[int32]string{
+	0: "LABEL_INVALID",
+	1: "LABEL_OPTIONAL",
+	2: "LABEL_REQUIRED",
+	3: "LABEL_REPEATED",
+}
+var MessageField_Label_value = map[string]int32{
+	"LABEL_INVALID":  0,
+	"LABEL_OPTIONAL": 1,
+	"LABEL_REQUIRED": 2,
+	"LABEL_REPEATED": 3,
+}
+
+func (x MessageField_Label) String() string {
+	return proto.EnumName(MessageField_Label_name, int32(x))
+}
+func (MessageField_Label) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_reflect_471a22e7f54c4e9f, []int{5, 0}
+}
+
+// Type is the type of the message field.
+//
+// The numbers match the FieldDescriptorProto.Type numbers.
+type MessageField_Type int32
+
+const (
+	MessageField_TYPE_INVALID  MessageField_Type = 0
+	MessageField_TYPE_DOUBLE   MessageField_Type = 1
+	MessageField_TYPE_FLOAT    MessageField_Type = 2
+	MessageField_TYPE_INT64    MessageField_Type = 3
+	MessageField_TYPE_UINT64   MessageField_Type = 4
+	MessageField_TYPE_INT32    MessageField_Type = 5
+	MessageField_TYPE_FIXED64  MessageField_Type = 6
+	MessageField_TYPE_FIXED32  MessageField_Type = 7
+	MessageField_TYPE_BOOL     MessageField_Type = 8
+	MessageField_TYPE_STRING   MessageField_Type = 9
+	MessageField_TYPE_GROUP    MessageField_Type = 10
+	MessageField_TYPE_MESSAGE  MessageField_Type = 11
+	MessageField_TYPE_BYTES    MessageField_Type = 12
+	MessageField_TYPE_UINT32   MessageField_Type = 13
+	MessageField_TYPE_ENUM     MessageField_Type = 14
+	MessageField_TYPE_SFIXED32 MessageField_Type = 15
+	MessageField_TYPE_SFIXED64 MessageField_Type = 16
+	MessageField_TYPE_SINT32   MessageField_Type = 17
+	MessageField_TYPE_SINT64   MessageField_Type = 18
+)
+
+var MessageField_Type_name = map[int32]string{
+	0:  "TYPE_INVALID",
+	1:  "TYPE_DOUBLE",
+	2:  "TYPE_FLOAT",
+	3:  "TYPE_INT64",
+	4:  "TYPE_UINT64",
+	5:  "TYPE_INT32",
+	6:  "TYPE_FIXED64",
+	7:  "TYPE_FIXED32",
+	8:  "TYPE_BOOL",
+	9:  "TYPE_STRING",
+	10: "TYPE_GROUP",
+	11: "TYPE_MESSAGE",
+	12: "TYPE_BYTES",
+	13: "TYPE_UINT32",
+	14: "TYPE_ENUM",
+	15: "TYPE_SFIXED32",
+	16: "TYPE_SFIXED64",
+	17: "TYPE_SINT32",
+	18: "TYPE_SINT64",
+}
+var MessageField_Type_value = map[string]int32{
+	"TYPE_INVALID":  0,
+	"TYPE_DOUBLE":   1,
+	"TYPE_FLOAT":    2,
+	"TYPE_INT64":    3,
+	"TYPE_UINT64":   4,
+	"TYPE_INT32":    5,
+	"TYPE_FIXED64":  6,
+	"TYPE_FIXED32":  7,
+	"TYPE_BOOL":     8,
+	"TYPE_STRING":   9,
+	"TYPE_GROUP":    10,
+	"TYPE_MESSAGE":  11,
+	"TYPE_BYTES":    12,
+	"TYPE_UINT32":   13,
+	"TYPE_ENUM":     14,
+	"TYPE_SFIXED32": 15,
+	"TYPE_SFIXED64": 16,
+	"TYPE_SINT32":   17,
+	"TYPE_SINT64":   18,
+}
+
+func (x MessageField_Type) String() string {
+	return proto.EnumName(MessageField_Type_name, int32(x))
+}
+func (MessageField_Type) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_reflect_471a22e7f54c4e9f, []int{5, 1}
+}
+
+// PackageSet is a set of Packages.
+type PackageSet struct {
+	// packages contains the set of Packages.
+	//
+	// These will all be unique, and can be referenced by their name field.
+	// These will also be sorted by name.
+	Packages             []*Package `protobuf:"bytes,1,rep,name=packages,proto3" json:"packages,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}   `json:"-"`
+	XXX_unrecognized     []byte     `json:"-"`
+	XXX_sizecache        int32      `json:"-"`
+}
+
+func (m *PackageSet) Reset()         { *m = PackageSet{} }
+func (m *PackageSet) String() string { return proto.CompactTextString(m) }
+func (*PackageSet) ProtoMessage()    {}
+func (*PackageSet) Descriptor() ([]byte, []int) {
+	return fileDescriptor_reflect_471a22e7f54c4e9f, []int{0}
+}
+func (m *PackageSet) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_PackageSet.Unmarshal(m, b)
+}
+func (m *PackageSet) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_PackageSet.Marshal(b, m, deterministic)
+}
+func (dst *PackageSet) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_PackageSet.Merge(dst, src)
+}
+func (m *PackageSet) XXX_Size() int {
+	return xxx_messageInfo_PackageSet.Size(m)
+}
+func (m *PackageSet) XXX_DiscardUnknown() {
+	xxx_messageInfo_PackageSet.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_PackageSet proto.InternalMessageInfo
+
+func (m *PackageSet) GetPackages() []*Package {
+	if m != nil {
+		return m.Packages
+	}
+	return nil
+}
+
+// Package describes a Protobuf package, constructed from a set of Protobuf
+// files that have the same "package" value.
+type Package struct {
+	// name is the fully-qualified name of the package.
+	//
+	// The package name can be thought of as the unique identifier for a
+	// Package message, and is used as a reference.
+	//
+	// This does not include the prefix '.' found in the traditional package
+	// fully-qualified name.
+	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	// dependency_names contains the names of this packages' dependencies.
+	//
+	// These will be sorted.
+	// If you have a set of Package messages, this will correspond to the name
+	// field of other Packages in the set.
+	DependencyNames []string `protobuf:"bytes,2,rep,name=dependency_names,json=dependencyNames,proto3" json:"dependency_names,omitempty"`
+	// enums contains the top-level enums within this package.
+	//
+	// These will be sorted by name.
+	// Nested enums will be within Messages.
+	Enums []*Enum `protobuf:"bytes,3,rep,name=enums,proto3" json:"enums,omitempty"`
+	// messages contains the top-level messages within this package.
+	//
+	// These will be sorted by name.
+	// Nested messages will be within Messages.
+	Messages []*Message `protobuf:"bytes,4,rep,name=messages,proto3" json:"messages,omitempty"`
+	// services contains the services within this package.
+	//
+	// These will be sorted by name.
+	Services             []*Service `protobuf:"bytes,5,rep,name=services,proto3" json:"services,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}   `json:"-"`
+	XXX_unrecognized     []byte     `json:"-"`
+	XXX_sizecache        int32      `json:"-"`
+}
+
+func (m *Package) Reset()         { *m = Package{} }
+func (m *Package) String() string { return proto.CompactTextString(m) }
+func (*Package) ProtoMessage()    {}
+func (*Package) Descriptor() ([]byte, []int) {
+	return fileDescriptor_reflect_471a22e7f54c4e9f, []int{1}
+}
+func (m *Package) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Package.Unmarshal(m, b)
+}
+func (m *Package) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Package.Marshal(b, m, deterministic)
+}
+func (dst *Package) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Package.Merge(dst, src)
+}
+func (m *Package) XXX_Size() int {
+	return xxx_messageInfo_Package.Size(m)
+}
+func (m *Package) XXX_DiscardUnknown() {
+	xxx_messageInfo_Package.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Package proto.InternalMessageInfo
+
+func (m *Package) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
+func (m *Package) GetDependencyNames() []string {
+	if m != nil {
+		return m.DependencyNames
+	}
+	return nil
+}
+
+func (m *Package) GetEnums() []*Enum {
+	if m != nil {
+		return m.Enums
+	}
+	return nil
+}
+
+func (m *Package) GetMessages() []*Message {
+	if m != nil {
+		return m.Messages
+	}
+	return nil
+}
+
+func (m *Package) GetServices() []*Service {
+	if m != nil {
+		return m.Services
+	}
+	return nil
+}
+
+// Enum describes a Protobuf enum.
+type Enum struct {
+	// name is the name of the enum.
+	//
+	// If this is a nested enum, this will not contain the name of the
+	// encapsulating message.
+	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	// enum_values contains the enum values.
+	//
+	// These will be sorted by number.
+	EnumValues           []*EnumValue `protobuf:"bytes,2,rep,name=enum_values,json=enumValues,proto3" json:"enum_values,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}     `json:"-"`
+	XXX_unrecognized     []byte       `json:"-"`
+	XXX_sizecache        int32        `json:"-"`
+}
+
+func (m *Enum) Reset()         { *m = Enum{} }
+func (m *Enum) String() string { return proto.CompactTextString(m) }
+func (*Enum) ProtoMessage()    {}
+func (*Enum) Descriptor() ([]byte, []int) {
+	return fileDescriptor_reflect_471a22e7f54c4e9f, []int{2}
+}
+func (m *Enum) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Enum.Unmarshal(m, b)
+}
+func (m *Enum) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Enum.Marshal(b, m, deterministic)
+}
+func (dst *Enum) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Enum.Merge(dst, src)
+}
+func (m *Enum) XXX_Size() int {
+	return xxx_messageInfo_Enum.Size(m)
+}
+func (m *Enum) XXX_DiscardUnknown() {
+	xxx_messageInfo_Enum.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Enum proto.InternalMessageInfo
+
+func (m *Enum) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
+func (m *Enum) GetEnumValues() []*EnumValue {
+	if m != nil {
+		return m.EnumValues
+	}
+	return nil
+}
+
+// EnumValue describes a Protobuf enum value.
+type EnumValue struct {
+	// name contains the value name.
+	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	// number contains the value number.
+	Number               int32    `protobuf:"varint,2,opt,name=number,proto3" json:"number,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *EnumValue) Reset()         { *m = EnumValue{} }
+func (m *EnumValue) String() string { return proto.CompactTextString(m) }
+func (*EnumValue) ProtoMessage()    {}
+func (*EnumValue) Descriptor() ([]byte, []int) {
+	return fileDescriptor_reflect_471a22e7f54c4e9f, []int{3}
+}
+func (m *EnumValue) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_EnumValue.Unmarshal(m, b)
+}
+func (m *EnumValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_EnumValue.Marshal(b, m, deterministic)
+}
+func (dst *EnumValue) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_EnumValue.Merge(dst, src)
+}
+func (m *EnumValue) XXX_Size() int {
+	return xxx_messageInfo_EnumValue.Size(m)
+}
+func (m *EnumValue) XXX_DiscardUnknown() {
+	xxx_messageInfo_EnumValue.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_EnumValue proto.InternalMessageInfo
+
+func (m *EnumValue) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
+func (m *EnumValue) GetNumber() int32 {
+	if m != nil {
+		return m.Number
+	}
+	return 0
+}
+
+// Message describes a Protobuf message.
+type Message struct {
+	// name is the name of the message.
+	//
+	// If this is a nested message, this will not contain the name of the
+	// encapsulating message.
+	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	// message_fields contains the message fields.
+	//
+	// This does not include any extended fields.
+	// These will be sorted by number.
+	MessageFields []*MessageField `protobuf:"bytes,2,rep,name=message_fields,json=messageFields,proto3" json:"message_fields,omitempty"`
+	// message_oneofs contains the oneofs.
+	//
+	// These will be sorted by name.
+	MessageOneofs []*MessageOneof `protobuf:"bytes,3,rep,name=message_oneofs,json=messageOneofs,proto3" json:"message_oneofs,omitempty"`
+	// nested_message contains the messages directly nested on this message.
+	//
+	// These will be sorted by name.
+	NestedMessages []*Message `protobuf:"bytes,4,rep,name=nested_messages,json=nestedMessages,proto3" json:"nested_messages,omitempty"`
+	// nested_enums contains the enums directed nested on this message.
+	//
+	// These will be sorted by name.
+	NestedEnums          []*Enum  `protobuf:"bytes,5,rep,name=nested_enums,json=nestedEnums,proto3" json:"nested_enums,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *Message) Reset()         { *m = Message{} }
+func (m *Message) String() string { return proto.CompactTextString(m) }
+func (*Message) ProtoMessage()    {}
+func (*Message) Descriptor() ([]byte, []int) {
+	return fileDescriptor_reflect_471a22e7f54c4e9f, []int{4}
+}
+func (m *Message) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Message.Unmarshal(m, b)
+}
+func (m *Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Message.Marshal(b, m, deterministic)
+}
+func (dst *Message) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Message.Merge(dst, src)
+}
+func (m *Message) XXX_Size() int {
+	return xxx_messageInfo_Message.Size(m)
+}
+func (m *Message) XXX_DiscardUnknown() {
+	xxx_messageInfo_Message.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Message proto.InternalMessageInfo
+
+func (m *Message) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
+func (m *Message) GetMessageFields() []*MessageField {
+	if m != nil {
+		return m.MessageFields
+	}
+	return nil
+}
+
+func (m *Message) GetMessageOneofs() []*MessageOneof {
+	if m != nil {
+		return m.MessageOneofs
+	}
+	return nil
+}
+
+func (m *Message) GetNestedMessages() []*Message {
+	if m != nil {
+		return m.NestedMessages
+	}
+	return nil
+}
+
+func (m *Message) GetNestedEnums() []*Enum {
+	if m != nil {
+		return m.NestedEnums
+	}
+	return nil
+}
+
+// MessageField describes a Protobuf message field.
+type MessageField struct {
+	// name is the name of the message field.
+	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	// number is the number of the message field.
+	Number int32 `protobuf:"varint,2,opt,name=number,proto3" json:"number,omitempty"`
+	// label is the label of the message field.
+	Label MessageField_Label `protobuf:"varint,3,opt,name=label,proto3,enum=uber.proto.reflect.v1.MessageField_Label" json:"label,omitempty"`
+	// type is the type of the message field.
+	Type MessageField_Type `protobuf:"varint,4,opt,name=type,proto3,enum=uber.proto.reflect.v1.MessageField_Type" json:"type,omitempty"`
+	// type_name is the fully-qualified name of the type for message and enum
+	// fields.
+	//
+	// This does not include the prefix '.' found in the traditional package
+	// fully-qualified name. If this is a nested message, the parent messages
+	// will be part of the name.
+	TypeName             string   `protobuf:"bytes,5,opt,name=type_name,json=typeName,proto3" json:"type_name,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *MessageField) Reset()         { *m = MessageField{} }
+func (m *MessageField) String() string { return proto.CompactTextString(m) }
+func (*MessageField) ProtoMessage()    {}
+func (*MessageField) Descriptor() ([]byte, []int) {
+	return fileDescriptor_reflect_471a22e7f54c4e9f, []int{5}
+}
+func (m *MessageField) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_MessageField.Unmarshal(m, b)
+}
+func (m *MessageField) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_MessageField.Marshal(b, m, deterministic)
+}
+func (dst *MessageField) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_MessageField.Merge(dst, src)
+}
+func (m *MessageField) XXX_Size() int {
+	return xxx_messageInfo_MessageField.Size(m)
+}
+func (m *MessageField) XXX_DiscardUnknown() {
+	xxx_messageInfo_MessageField.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_MessageField proto.InternalMessageInfo
+
+func (m *MessageField) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
+func (m *MessageField) GetNumber() int32 {
+	if m != nil {
+		return m.Number
+	}
+	return 0
+}
+
+func (m *MessageField) GetLabel() MessageField_Label {
+	if m != nil {
+		return m.Label
+	}
+	return MessageField_LABEL_INVALID
+}
+
+func (m *MessageField) GetType() MessageField_Type {
+	if m != nil {
+		return m.Type
+	}
+	return MessageField_TYPE_INVALID
+}
+
+func (m *MessageField) GetTypeName() string {
+	if m != nil {
+		return m.TypeName
+	}
+	return ""
+}
+
+// MessageOneof describes a Protobuf message oneof.
+type MessageOneof struct {
+	// name is the name of the message oneof.
+	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	// field_numbers are the field numbers in the encapsulating type that make up
+	// this message oneof.
+	//
+	// This will be sorted.
+	FieldNumbers         []int32  `protobuf:"varint,2,rep,packed,name=field_numbers,json=fieldNumbers,proto3" json:"field_numbers,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *MessageOneof) Reset()         { *m = MessageOneof{} }
+func (m *MessageOneof) String() string { return proto.CompactTextString(m) }
+func (*MessageOneof) ProtoMessage()    {}
+func (*MessageOneof) Descriptor() ([]byte, []int) {
+	return fileDescriptor_reflect_471a22e7f54c4e9f, []int{6}
+}
+func (m *MessageOneof) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_MessageOneof.Unmarshal(m, b)
+}
+func (m *MessageOneof) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_MessageOneof.Marshal(b, m, deterministic)
+}
+func (dst *MessageOneof) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_MessageOneof.Merge(dst, src)
+}
+func (m *MessageOneof) XXX_Size() int {
+	return xxx_messageInfo_MessageOneof.Size(m)
+}
+func (m *MessageOneof) XXX_DiscardUnknown() {
+	xxx_messageInfo_MessageOneof.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_MessageOneof proto.InternalMessageInfo
+
+func (m *MessageOneof) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
+func (m *MessageOneof) GetFieldNumbers() []int32 {
+	if m != nil {
+		return m.FieldNumbers
+	}
+	return nil
+}
+
+// Service describes a Protobuf service.
+type Service struct {
+	// name is the name of the service.
+	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	// service_methods are the service methods.
+	//
+	// These will be sorted by name.
+	ServiceMethods       []*ServiceMethod `protobuf:"bytes,2,rep,name=service_methods,json=serviceMethods,proto3" json:"service_methods,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}         `json:"-"`
+	XXX_unrecognized     []byte           `json:"-"`
+	XXX_sizecache        int32            `json:"-"`
+}
+
+func (m *Service) Reset()         { *m = Service{} }
+func (m *Service) String() string { return proto.CompactTextString(m) }
+func (*Service) ProtoMessage()    {}
+func (*Service) Descriptor() ([]byte, []int) {
+	return fileDescriptor_reflect_471a22e7f54c4e9f, []int{7}
+}
+func (m *Service) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Service.Unmarshal(m, b)
+}
+func (m *Service) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Service.Marshal(b, m, deterministic)
+}
+func (dst *Service) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Service.Merge(dst, src)
+}
+func (m *Service) XXX_Size() int {
+	return xxx_messageInfo_Service.Size(m)
+}
+func (m *Service) XXX_DiscardUnknown() {
+	xxx_messageInfo_Service.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Service proto.InternalMessageInfo
+
+func (m *Service) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
+func (m *Service) GetServiceMethods() []*ServiceMethod {
+	if m != nil {
+		return m.ServiceMethods
+	}
+	return nil
+}
+
+// ServiceMethod describes a Protobuf service method.
+type ServiceMethod struct {
+	// name is the name of the service method.
+	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	// request_type_name is the fully-qualified name of request message type.
+	//
+	// This does not include the prefix '.' found in the traditional package
+	// fully-qualified name. If this is a nested message, the parent messages
+	// will be part of the name.
+	RequestTypeName string `protobuf:"bytes,2,opt,name=request_type_name,json=requestTypeName,proto3" json:"request_type_name,omitempty"`
+	// response_type_name is the fully-qualified name of response message type.
+	//
+	// This does not include the prefix '.' found in the traditional package
+	// fully-qualified name. If this is a nested message, the parent messages
+	// will be part of the name.
+	ResponseTypeName string `protobuf:"bytes,3,opt,name=response_type_name,json=responseTypeName,proto3" json:"response_type_name,omitempty"`
+	// client_streaming representing whether this is a client-side streaming
+	// method
+	ClientStreaming bool `protobuf:"varint,4,opt,name=client_streaming,json=clientStreaming,proto3" json:"client_streaming,omitempty"`
+	// server_streaming representing whether this is a server-side streaming
+	// method
+	ServerStreaming      bool     `protobuf:"varint,5,opt,name=server_streaming,json=serverStreaming,proto3" json:"server_streaming,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *ServiceMethod) Reset()         { *m = ServiceMethod{} }
+func (m *ServiceMethod) String() string { return proto.CompactTextString(m) }
+func (*ServiceMethod) ProtoMessage()    {}
+func (*ServiceMethod) Descriptor() ([]byte, []int) {
+	return fileDescriptor_reflect_471a22e7f54c4e9f, []int{8}
+}
+func (m *ServiceMethod) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_ServiceMethod.Unmarshal(m, b)
+}
+func (m *ServiceMethod) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_ServiceMethod.Marshal(b, m, deterministic)
+}
+func (dst *ServiceMethod) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ServiceMethod.Merge(dst, src)
+}
+func (m *ServiceMethod) XXX_Size() int {
+	return xxx_messageInfo_ServiceMethod.Size(m)
+}
+func (m *ServiceMethod) XXX_DiscardUnknown() {
+	xxx_messageInfo_ServiceMethod.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ServiceMethod proto.InternalMessageInfo
+
+func (m *ServiceMethod) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
+func (m *ServiceMethod) GetRequestTypeName() string {
+	if m != nil {
+		return m.RequestTypeName
+	}
+	return ""
+}
+
+func (m *ServiceMethod) GetResponseTypeName() string {
+	if m != nil {
+		return m.ResponseTypeName
+	}
+	return ""
+}
+
+func (m *ServiceMethod) GetClientStreaming() bool {
+	if m != nil {
+		return m.ClientStreaming
+	}
+	return false
+}
+
+func (m *ServiceMethod) GetServerStreaming() bool {
+	if m != nil {
+		return m.ServerStreaming
+	}
+	return false
+}
+
+func init() {
+	proto.RegisterType((*PackageSet)(nil), "uber.proto.reflect.v1.PackageSet")
+	proto.RegisterType((*Package)(nil), "uber.proto.reflect.v1.Package")
+	proto.RegisterType((*Enum)(nil), "uber.proto.reflect.v1.Enum")
+	proto.RegisterType((*EnumValue)(nil), "uber.proto.reflect.v1.EnumValue")
+	proto.RegisterType((*Message)(nil), "uber.proto.reflect.v1.Message")
+	proto.RegisterType((*MessageField)(nil), "uber.proto.reflect.v1.MessageField")
+	proto.RegisterType((*MessageOneof)(nil), "uber.proto.reflect.v1.MessageOneof")
+	proto.RegisterType((*Service)(nil), "uber.proto.reflect.v1.Service")
+	proto.RegisterType((*ServiceMethod)(nil), "uber.proto.reflect.v1.ServiceMethod")
+	proto.RegisterEnum("uber.proto.reflect.v1.MessageField_Label", MessageField_Label_name, MessageField_Label_value)
+	proto.RegisterEnum("uber.proto.reflect.v1.MessageField_Type", MessageField_Type_name, MessageField_Type_value)
+}
+
+func init() {
+	proto.RegisterFile("uber/proto/reflect/v1/reflect.proto", fileDescriptor_reflect_471a22e7f54c4e9f)
+}
+
+var fileDescriptor_reflect_471a22e7f54c4e9f = []byte{
+	// 800 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x54, 0xdf, 0x8f, 0xea, 0x44,
+	0x14, 0xb6, 0xd0, 0xee, 0x2e, 0x87, 0x5f, 0xb3, 0x93, 0x68, 0x6a, 0x6e, 0x62, 0x48, 0xd7, 0x07,
+	0xd6, 0x18, 0x36, 0xcb, 0x6e, 0x30, 0x31, 0x46, 0x03, 0xa1, 0x8b, 0x98, 0x42, 0x71, 0x28, 0x1b,
+	0xaf, 0x89, 0x69, 0x58, 0x98, 0xbd, 0x12, 0x69, 0xc1, 0x4e, 0x21, 0xd9, 0x7f, 0xce, 0x67, 0xdf,
+	0xfd, 0x53, 0x7c, 0xf2, 0xcd, 0xcc, 0x4c, 0x3b, 0x14, 0x53, 0xb9, 0x77, 0x9f, 0x3a, 0xf3, 0x9d,
+	0xef, 0x3b, 0xf3, 0x9d, 0xe9, 0x39, 0x03, 0x57, 0xbb, 0x27, 0x1a, 0xdd, 0x6c, 0xa3, 0x4d, 0xbc,
+	0xb9, 0x89, 0xe8, 0xf3, 0x9a, 0x2e, 0xe2, 0x9b, 0xfd, 0x6d, 0xba, 0x6c, 0x89, 0x00, 0xfe, 0x98,
+	0x93, 0xe4, 0xba, 0x95, 0x46, 0xf6, 0xb7, 0xd6, 0xf7, 0x00, 0x93, 0xf9, 0xe2, 0xb7, 0xf9, 0x3b,
+	0x3a, 0xa5, 0x31, 0xfe, 0x1a, 0x2e, 0xb6, 0x72, 0xc7, 0x4c, 0xad, 0x51, 0x6c, 0x96, 0xdb, 0x9f,
+	0xb5, 0x72, 0x75, 0xad, 0x44, 0x44, 0x14, 0xdf, 0xfa, 0x5b, 0x83, 0xf3, 0x04, 0xc5, 0x18, 0xf4,
+	0x70, 0x1e, 0x50, 0x53, 0x6b, 0x68, 0xcd, 0x12, 0x11, 0x6b, 0x7c, 0x0d, 0x68, 0x49, 0xb7, 0x34,
+	0x5c, 0xd2, 0x70, 0xf1, 0xe2, 0x73, 0x88, 0x99, 0x85, 0x46, 0xb1, 0x59, 0x22, 0xf5, 0x03, 0x3e,
+	0xe6, 0x30, 0xbe, 0x05, 0x83, 0x86, 0xbb, 0x80, 0x99, 0x45, 0xe1, 0xe1, 0xcd, 0xff, 0x78, 0xb0,
+	0xc3, 0x5d, 0x40, 0x24, 0x93, 0x3b, 0x0f, 0x28, 0x63, 0xc2, 0xb9, 0x7e, 0xd2, 0xf9, 0x48, 0xd2,
+	0x88, 0xe2, 0x73, 0x2d, 0xa3, 0xd1, 0x7e, 0xb5, 0xa0, 0xcc, 0x34, 0x4e, 0x6a, 0xa7, 0x92, 0x46,
+	0x14, 0xdf, 0xfa, 0x05, 0x74, 0x6e, 0x23, 0xb7, 0xe2, 0x2e, 0x94, 0xb9, 0x39, 0x7f, 0x3f, 0x5f,
+	0xef, 0x92, 0x62, 0xcb, 0xed, 0xc6, 0x89, 0x62, 0x1e, 0x39, 0x91, 0x00, 0x4d, 0x97, 0xcc, 0xfa,
+	0x0a, 0x4a, 0x2a, 0x90, 0x7b, 0xc6, 0x27, 0x70, 0x16, 0xee, 0x82, 0x27, 0x1a, 0x99, 0x85, 0x86,
+	0xd6, 0x34, 0x48, 0xb2, 0xb3, 0xfe, 0x28, 0xc0, 0x79, 0x52, 0x69, 0xae, 0xee, 0x07, 0xa8, 0x25,
+	0xf5, 0xfb, 0xcf, 0x2b, 0xba, 0x5e, 0xa6, 0xf6, 0xae, 0x4e, 0xdf, 0xda, 0x03, 0xe7, 0x92, 0x6a,
+	0x90, 0xd9, 0xb1, 0x6c, 0xae, 0x4d, 0x48, 0x37, 0xcf, 0xe9, 0x7f, 0x7b, 0x4f, 0x2e, 0x97, 0x73,
+	0x55, 0x2e, 0xb1, 0x63, 0x78, 0x00, 0xf5, 0x90, 0xb2, 0x98, 0x2e, 0xfd, 0x57, 0xfe, 0xce, 0x9a,
+	0x94, 0x8d, 0xd2, 0x9f, 0xfa, 0x2d, 0x54, 0x92, 0x44, 0xb2, 0x95, 0x8c, 0xf7, 0xb7, 0x52, 0x59,
+	0x0a, 0xf8, 0x9a, 0x59, 0xff, 0xe8, 0x50, 0xc9, 0x16, 0xfd, 0x9a, 0xdb, 0xc7, 0xdf, 0x81, 0xb1,
+	0x9e, 0x3f, 0xd1, 0xb5, 0x59, 0x6c, 0x68, 0xcd, 0x5a, 0xfb, 0xfa, 0x03, 0x2e, 0xb5, 0xe5, 0x70,
+	0x01, 0x91, 0x3a, 0xfc, 0x0d, 0xe8, 0xf1, 0xcb, 0x96, 0x9a, 0xba, 0xd0, 0x37, 0x3f, 0x44, 0xef,
+	0xbd, 0x6c, 0x29, 0x11, 0x2a, 0xfc, 0x06, 0x4a, 0xfc, 0x2b, 0x86, 0xcc, 0x34, 0x84, 0xdf, 0x0b,
+	0x0e, 0xf0, 0xe9, 0xb2, 0x1e, 0xc1, 0x10, 0x47, 0xe1, 0x4b, 0xa8, 0x3a, 0xdd, 0x9e, 0xed, 0xf8,
+	0xc3, 0xf1, 0x63, 0xd7, 0x19, 0xf6, 0xd1, 0x47, 0x18, 0x43, 0x4d, 0x42, 0xee, 0xc4, 0x1b, 0xba,
+	0xe3, 0xae, 0x83, 0xb4, 0x03, 0x46, 0xec, 0x1f, 0x67, 0x43, 0x62, 0xf7, 0x51, 0x21, 0x8b, 0x4d,
+	0xec, 0xae, 0x67, 0xf7, 0x51, 0xd1, 0xfa, 0xb3, 0x00, 0x3a, 0xf7, 0x80, 0x11, 0x54, 0xbc, 0xb7,
+	0x13, 0x3b, 0x93, 0xb6, 0x0e, 0x65, 0x81, 0xf4, 0xdd, 0x59, 0xcf, 0xb1, 0x91, 0x86, 0x6b, 0x00,
+	0x02, 0x78, 0x70, 0xdc, 0xae, 0x87, 0x0a, 0x6a, 0x3f, 0x1c, 0x7b, 0x9d, 0x7b, 0x54, 0x54, 0x82,
+	0x99, 0x04, 0xf4, 0x2c, 0xe1, 0xae, 0x8d, 0x0c, 0x75, 0xc6, 0xc3, 0xf0, 0x27, 0xbb, 0xdf, 0xb9,
+	0x47, 0x67, 0xc7, 0xc8, 0x5d, 0x1b, 0x9d, 0xe3, 0x2a, 0x94, 0x04, 0xd2, 0x73, 0x5d, 0x07, 0x5d,
+	0xa8, 0x9c, 0x53, 0x8f, 0x0c, 0xc7, 0x03, 0x54, 0x52, 0x39, 0x07, 0xc4, 0x9d, 0x4d, 0x10, 0xa8,
+	0x0c, 0x23, 0x7b, 0x3a, 0xed, 0x0e, 0x6c, 0x54, 0x56, 0x8c, 0xde, 0x5b, 0xcf, 0x9e, 0xa2, 0xca,
+	0x91, 0xad, 0xbb, 0x36, 0xaa, 0xaa, 0x23, 0xec, 0xf1, 0x6c, 0x84, 0x6a, 0xfc, 0x46, 0xe5, 0x11,
+	0xa9, 0x89, 0xfa, 0x7f, 0xa0, 0xce, 0x3d, 0x42, 0x07, 0x23, 0x32, 0xcb, 0xe5, 0x11, 0xd0, 0xb9,
+	0x47, 0xd8, 0x1a, 0xa8, 0xd6, 0x13, 0x53, 0x91, 0xdb, 0x7a, 0x57, 0x50, 0x15, 0x83, 0xeb, 0xcb,
+	0x96, 0x93, 0xf3, 0x6b, 0x90, 0x8a, 0x00, 0xc7, 0x12, 0xb3, 0xd6, 0x70, 0x9e, 0x3c, 0x59, 0xb9,
+	0x39, 0x46, 0x50, 0x4f, 0x1e, 0x32, 0x3f, 0xa0, 0xf1, 0xaf, 0x1b, 0xf5, 0x0a, 0x7c, 0x7e, 0xfa,
+	0xfd, 0x1b, 0x09, 0x32, 0xa9, 0xb1, 0xec, 0x96, 0x59, 0x7f, 0x69, 0x50, 0x3d, 0x62, 0xe4, 0x1e,
+	0xfa, 0x05, 0x5c, 0x46, 0xf4, 0xf7, 0x1d, 0x65, 0xb1, 0x7f, 0x68, 0xd2, 0x82, 0x20, 0xd4, 0x93,
+	0x80, 0x97, 0xf4, 0x2a, 0xfe, 0x12, 0x70, 0x44, 0xd9, 0x76, 0x13, 0x32, 0x9a, 0x21, 0x17, 0x05,
+	0x19, 0xa5, 0x11, 0xc5, 0xbe, 0x06, 0xb4, 0x58, 0xaf, 0x68, 0x18, 0xfb, 0x2c, 0x8e, 0xe8, 0x3c,
+	0x58, 0x85, 0xef, 0xc4, 0x00, 0x5d, 0x90, 0xba, 0xc4, 0xa7, 0x29, 0xcc, 0xa9, 0xdc, 0x3c, 0x8d,
+	0x32, 0x54, 0x43, 0x52, 0x25, 0xae, 0xa8, 0xbd, 0x0e, 0x7c, 0xba, 0xd8, 0x04, 0xf9, 0x17, 0xd2,
+	0xab, 0x10, 0xb9, 0x9e, 0xf0, 0xc0, 0x44, 0xfb, 0xb9, 0x94, 0xc4, 0xf6, 0xb7, 0x4f, 0x67, 0x82,
+	0x7c, 0xf7, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xcd, 0xe5, 0x7e, 0x34, 0x9e, 0x07, 0x00, 0x00,
+}
diff --git a/internal/reflect/proto/prototool.yaml b/internal/reflect/proto/prototool.yaml
index 707c33e..248cbd6 100644
--- a/internal/reflect/proto/prototool.yaml
+++ b/internal/reflect/proto/prototool.yaml
@@ -1,5 +1,22 @@
 protoc:
   version: 3.6.1
+lint:
+  rules:
+    add:
+      - ENUM_FIELD_PREFIXES_EXCEPT_MESSAGE
+      - ENUM_ZERO_VALUES_INVALID_EXCEPT_MESSAGE
+      - ENUMS_HAVE_COMMENTS
+      - FILE_OPTIONS_EQUAL_GO_PACKAGE_LAST_TWO_SUFFIX
+      - GOGO_NOT_IMPORTED
+      - MESSAGE_FIELDS_NO_JSON_NAME
+      - MESSAGES_HAVE_COMMENTS_EXCEPT_REQUEST_RESPONSE_TYPES
+      - PACKAGE_MAJOR_VERSIONED
+      - REQUEST_RESPONSE_NAMES_MATCH_RPC
+      - RPCS_HAVE_COMMENTS
+    remove:
+      - ENUM_FIELD_PREFIXES
+      - ENUM_ZERO_VALUES_INVALID
+      - FILE_OPTIONS_EQUAL_GO_PACKAGE_PB_SUFFIX
 generate:
   go_options:
     import_path: github.com/uber/prototool/internal/reflect/proto
diff --git a/internal/reflect/proto/uber/proto/reflect/reflect.proto b/internal/reflect/proto/uber/proto/reflect/reflect.proto
deleted file mode 100644
index 2d0d462..0000000
--- a/internal/reflect/proto/uber/proto/reflect/reflect.proto
+++ /dev/null
@@ -1,36 +0,0 @@
-syntax = "proto3";
-
-package uber.proto.reflect;
-
-option go_package = "reflectpb";
-option java_multiple_files = true;
-option java_outer_classname = "ReflectProto";
-option java_package = "com.uber.proto.reflect";
-
-// PackageSet is a set of Packages.
-message PackageSet {
-  // packages contains the set of Packages.
-  //
-  // These will all be unique, and can be referenced by their name field.
-  // These will also be sorted by name.
-  repeated Package packages = 1;
-}
-
-// Package describes a Protobuf package, constructed from a set of Protobuf
-// files that have the same "package" value.
-message Package {
-  // name is the fully-qualified name of the package.
-  //
-  // The package name can be thought of as the unique identifier for a
-  // Package message, and is used as a reference.
-  //
-  // This does not include the prefix '.' found in the traditional package
-  // fully-qualified name.
-  string name = 1;
-  // dependency_names contains the names of this packages' dependencies.
-  //
-  // These will be sorted.
-  // If you have a set of Package messages, this will correspond to the name
-  // field of other Packages in the set.
-  repeated string dependency_names = 2;
-}
diff --git a/internal/reflect/proto/uber/proto/reflect/v1/reflect.proto b/internal/reflect/proto/uber/proto/reflect/v1/reflect.proto
new file mode 100644
index 0000000..f35d0eb
--- /dev/null
+++ b/internal/reflect/proto/uber/proto/reflect/v1/reflect.proto
@@ -0,0 +1,222 @@
+syntax = "proto3";
+
+// Package uber.proto.reflect.v1 contains structures to represent information
+// about Protobuf definitions on a per-package basis. This differs from
+// the structures in descriptor.proto which represent Protobuf definitions
+// on a per-file basis.
+//
+// This package does not represent all information about a Protobuf package
+// at this time, be added in the future.
+//
+// A non-comprehensive list of excluded items:
+//
+// - Source code information.
+// - All options.
+// - Reserved ranges and names.
+// - Message field default values.
+// - Message field oneof indexes.
+// - Message field JSON names.
+// - Anything to do with extensions.
+//
+// Excluded items that should not be relevant at a package level:
+//
+// - Public vs non-public dependencies are all grouped into the same list.
+// - Syntax information.
+// - Fields vs extenstions.
+// - Extension ranges.
+package uber.proto.reflect.v1;
+
+option go_package = "reflectv1";
+option java_multiple_files = true;
+option java_outer_classname = "ReflectProto";
+option java_package = "com.uber.proto.reflect.v1";
+
+// PackageSet is a set of Packages.
+message PackageSet {
+  // packages contains the set of Packages.
+  //
+  // These will all be unique, and can be referenced by their name field.
+  // These will also be sorted by name.
+  repeated Package packages = 1;
+}
+
+// Package describes a Protobuf package, constructed from a set of Protobuf
+// files that have the same "package" value.
+message Package {
+  // name is the fully-qualified name of the package.
+  //
+  // The package name can be thought of as the unique identifier for a
+  // Package message, and is used as a reference.
+  //
+  // This does not include the prefix '.' found in the traditional package
+  // fully-qualified name.
+  string name = 1;
+  // dependency_names contains the names of this packages' dependencies.
+  //
+  // These will be sorted.
+  // If you have a set of Package messages, this will correspond to the name
+  // field of other Packages in the set.
+  repeated string dependency_names = 2;
+  // enums contains the top-level enums within this package.
+  //
+  // These will be sorted by name.
+  // Nested enums will be within Messages.
+  repeated Enum enums = 3;
+  // messages contains the top-level messages within this package.
+  //
+  // These will be sorted by name.
+  // Nested messages will be within Messages.
+  repeated Message messages = 4;
+  // services contains the services within this package.
+  //
+  // These will be sorted by name.
+  repeated Service services = 5;
+}
+
+// Enum describes a Protobuf enum.
+message Enum {
+  // name is the name of the enum.
+  //
+  // If this is a nested enum, this will not contain the name of the
+  // encapsulating message.
+  string name = 1;
+  // enum_values contains the enum values.
+  //
+  // These will be sorted by number.
+  repeated EnumValue enum_values = 2;
+}
+
+// EnumValue describes a Protobuf enum value.
+message EnumValue {
+  // name contains the value name.
+  string name = 1;
+  // number contains the value number.
+  int32 number = 2;
+}
+
+// Message describes a Protobuf message.
+message Message {
+  // name is the name of the message.
+  //
+  // If this is a nested message, this will not contain the name of the
+  // encapsulating message.
+  string name = 1;
+  // message_fields contains the message fields.
+  //
+  // This does not include any extended fields.
+  // These will be sorted by number.
+  repeated MessageField message_fields = 2;
+  // message_oneofs contains the oneofs.
+  //
+  // These will be sorted by name.
+  repeated MessageOneof message_oneofs = 3;
+  // nested_message contains the messages directly nested on this message.
+  //
+  // These will be sorted by name.
+  repeated Message nested_messages = 4;
+  // nested_enums contains the enums directed nested on this message.
+  //
+  // These will be sorted by name.
+  repeated Enum nested_enums = 5;
+}
+
+// MessageField describes a Protobuf message field.
+message MessageField {
+  // Label is the label of the message field.
+  //
+  // The numbers match the FieldDescriptorProto.Label numbers.
+  //
+  // Note that a map field will come up as repeated, with type TYPE_MESSAGE,
+  // and the type_name will be *Entry. We may parse this from FieldDescriptorProtos
+  // in the future and add a LABEL_MAP.
+  enum Label {
+    LABEL_INVALID = 0;
+    LABEL_OPTIONAL = 1;
+    LABEL_REQUIRED = 2;
+    LABEL_REPEATED = 3;
+  }
+  // Type is the type of the message field.
+  //
+  // The numbers match the FieldDescriptorProto.Type numbers.
+  enum Type {
+    TYPE_INVALID = 0;
+    TYPE_DOUBLE = 1;
+    TYPE_FLOAT = 2;
+    TYPE_INT64 = 3;
+    TYPE_UINT64 = 4;
+    TYPE_INT32 = 5;
+    TYPE_FIXED64 = 6;
+    TYPE_FIXED32 = 7;
+    TYPE_BOOL = 8;
+    TYPE_STRING = 9;
+    TYPE_GROUP = 10;
+    TYPE_MESSAGE = 11;
+    TYPE_BYTES = 12;
+    TYPE_UINT32 = 13;
+    TYPE_ENUM = 14;
+    TYPE_SFIXED32 = 15;
+    TYPE_SFIXED64 = 16;
+    TYPE_SINT32 = 17;
+    TYPE_SINT64 = 18;
+  }
+  // name is the name of the message field.
+  string name = 1;
+  // number is the number of the message field.
+  int32 number = 2;
+  // label is the label of the message field.
+  Label label = 3;
+  // type is the type of the message field.
+  Type type = 4;
+  // type_name is the fully-qualified name of the type for message and enum
+  // fields.
+  //
+  // This does not include the prefix '.' found in the traditional package
+  // fully-qualified name. If this is a nested message, the parent messages
+  // will be part of the name.
+  string type_name = 5;
+}
+
+// MessageOneof describes a Protobuf message oneof.
+message MessageOneof {
+  // name is the name of the message oneof.
+  string name = 1;
+  // field_numbers are the field numbers in the encapsulating type that make up
+  // this message oneof.
+  //
+  // This will be sorted.
+  repeated int32 field_numbers = 2;
+}
+
+// Service describes a Protobuf service.
+message Service {
+  // name is the name of the service.
+  string name = 1;
+  // service_methods are the service methods.
+  //
+  // These will be sorted by name.
+  repeated ServiceMethod service_methods = 2;
+}
+
+// ServiceMethod describes a Protobuf service method.
+message ServiceMethod {
+  // name is the name of the service method.
+  string name = 1;
+  // request_type_name is the fully-qualified name of request message type.
+  //
+  // This does not include the prefix '.' found in the traditional package
+  // fully-qualified name. If this is a nested message, the parent messages
+  // will be part of the name.
+  string request_type_name = 2;
+  // response_type_name is the fully-qualified name of response message type.
+  //
+  // This does not include the prefix '.' found in the traditional package
+  // fully-qualified name. If this is a nested message, the parent messages
+  // will be part of the name.
+  string response_type_name = 3;
+  // client_streaming representing whether this is a client-side streaming
+  // method
+  bool client_streaming = 4;
+  // server_streaming representing whether this is a server-side streaming
+  // method
+  bool server_streaming = 5;
+}
diff --git a/internal/reflect/reflect.go b/internal/reflect/reflect.go
index 874421f..2a99253 100644
--- a/internal/reflect/reflect.go
+++ b/internal/reflect/reflect.go
@@ -27,7 +27,7 @@ import (
 
 	"github.com/golang/protobuf/proto"
 	"github.com/golang/protobuf/protoc-gen-go/descriptor"
-	reflectpb "github.com/uber/prototool/internal/reflect/gen/uber/proto/reflect"
+	reflectv1 "github.com/uber/prototool/internal/reflect/gen/uber/proto/reflect/v1"
 	"github.com/uber/prototool/internal/strs"
 )
 
@@ -36,47 +36,37 @@ import (
 //
 // The FileDescriptorSets can have FileDescriptorProtos with the same name, but
 // they must be equal.
-func NewPackageSet(fileDescriptorSets ...*descriptor.FileDescriptorSet) (*reflectpb.PackageSet, error) {
+func NewPackageSet(fileDescriptorSets ...*descriptor.FileDescriptorSet) (*reflectv1.PackageSet, error) {
 	packageNameToFileNameToFileDescriptorProto, err := getPackageNameToFileNameToFileDescriptorProto(fileDescriptorSets)
 	if err != nil {
 		return nil, err
 	}
-	fileNameToPackageName, err := getFileNameToPackageName(packageNameToFileNameToFileDescriptorProto)
+	packageNameToPackage, err := getBasePackageNameToPackage(packageNameToFileNameToFileDescriptorProto)
 	if err != nil {
 		return nil, err
 	}
-	packageNameToPackage := make(map[string]*reflectpb.Package)
-	for packageName := range packageNameToFileNameToFileDescriptorProto {
-		packageNameToPackage[packageName] = &reflectpb.Package{
-			Name: packageName,
-		}
+	if err := populateDependencies(packageNameToPackage, packageNameToFileNameToFileDescriptorProto); err != nil {
+		return nil, err
 	}
-	for packageName, fileNameTofileDescriptorProto := range packageNameToFileNameToFileDescriptorProto {
-		for _, fileDescriptorProto := range fileNameTofileDescriptorProto {
-			for _, depFileName := range fileDescriptorProto.GetDependency() {
-				depPackageName, ok := fileNameToPackageName[depFileName]
-				if !ok {
-					return nil, fmt.Errorf("no package for dep %s", depFileName)
-				}
-				if depPackageName != packageName {
-					packageNameToPackage[packageName].DependencyNames = append(packageNameToPackage[packageName].DependencyNames, depPackageName)
-				}
-			}
+	for packageName, fileNameToFileDescriptorProto := range packageNameToFileNameToFileDescriptorProto {
+		pkg, ok := packageNameToPackage[packageName]
+		if !ok {
+			return nil, fmt.Errorf("no package for name %s", packageName)
+		}
+		if err := populateEnums(pkg, fileNameToFileDescriptorProto); err != nil {
+			return nil, err
+		}
+		if err := populateMessages(pkg, fileNameToFileDescriptorProto); err != nil {
+			return nil, err
+		}
+		if err := populateServices(pkg, fileNameToFileDescriptorProto); err != nil {
+			return nil, err
 		}
 	}
-	for _, pkg := range packageNameToPackage {
-		pkg.DependencyNames = strs.DedupeSort(pkg.DependencyNames, nil)
-	}
-	packageSet := &reflectpb.PackageSet{
-		Packages: make([]*reflectpb.Package, 0, len(packageNameToPackage)),
-	}
-	for _, pkg := range packageNameToPackage {
-		packageSet.Packages = append(packageSet.Packages, pkg)
-	}
-	sort.Sort(sortPackages(packageSet.Packages))
-	return packageSet, nil
+	return getPackageSet(packageNameToPackage)
 }
 
+// helper for NewPackageSet
 func getPackageNameToFileNameToFileDescriptorProto(fileDescriptorSets []*descriptor.FileDescriptorSet) (map[string]map[string]*descriptor.FileDescriptorProto, error) {
 	packageNameToFileNameToFileDescriptorProto := make(map[string]map[string]*descriptor.FileDescriptorProto)
 	for _, fileDescriptorSet := range fileDescriptorSets {
@@ -118,6 +108,119 @@ func getPackageNameToFileNameToFileDescriptorProto(fileDescriptorSets []*descrip
 	return packageNameToFileNameToFileDescriptorProto, nil
 }
 
+// helper for NewPackageSet
+func getBasePackageNameToPackage(
+	packageNameToFileNameToFileDescriptorProto map[string]map[string]*descriptor.FileDescriptorProto,
+) (map[string]*reflectv1.Package, error) {
+	packageNameToPackage := make(map[string]*reflectv1.Package)
+	for packageName := range packageNameToFileNameToFileDescriptorProto {
+		packageNameToPackage[packageName] = &reflectv1.Package{
+			Name: packageName,
+		}
+	}
+	return packageNameToPackage, nil
+}
+
+// helper for NewPackageSet
+func populateDependencies(
+	packageNameToPackage map[string]*reflectv1.Package,
+	packageNameToFileNameToFileDescriptorProto map[string]map[string]*descriptor.FileDescriptorProto,
+) error {
+	fileNameToPackageName, err := getFileNameToPackageName(packageNameToFileNameToFileDescriptorProto)
+	if err != nil {
+		return err
+	}
+	for packageName, fileNameTofileDescriptorProto := range packageNameToFileNameToFileDescriptorProto {
+		for _, fileDescriptorProto := range fileNameTofileDescriptorProto {
+			for _, depFileName := range fileDescriptorProto.GetDependency() {
+				depPackageName, ok := fileNameToPackageName[depFileName]
+				if !ok {
+					return fmt.Errorf("no package for dep %s", depFileName)
+				}
+				if depPackageName != packageName {
+					packageNameToPackage[packageName].DependencyNames = append(packageNameToPackage[packageName].DependencyNames, depPackageName)
+				}
+			}
+		}
+	}
+	for _, pkg := range packageNameToPackage {
+		if len(pkg.DependencyNames) != 0 {
+			pkg.DependencyNames = strs.DedupeSort(pkg.DependencyNames, nil)
+		}
+	}
+	return nil
+}
+
+// helper for NewPackageSet
+func populateEnums(
+	pkg *reflectv1.Package,
+	fileNameToFileDescriptorProto map[string]*descriptor.FileDescriptorProto,
+) error {
+	for _, fileDescriptorProto := range fileNameToFileDescriptorProto {
+		enums, err := getEnums(fileDescriptorProto.GetEnumType())
+		if err != nil {
+			return err
+		}
+		if len(enums) > 0 {
+			pkg.Enums = append(pkg.Enums, enums...)
+		}
+	}
+	sort.Slice(pkg.Enums, func(i int, j int) bool { return pkg.Enums[i].Name < pkg.Enums[j].Name })
+	return nil
+}
+
+// helper for NewPackageSet
+func populateMessages(
+	pkg *reflectv1.Package,
+	fileNameToFileDescriptorProto map[string]*descriptor.FileDescriptorProto,
+) error {
+	for _, fileDescriptorProto := range fileNameToFileDescriptorProto {
+		messages, err := getMessages(fileDescriptorProto.GetMessageType())
+		if err != nil {
+			return err
+		}
+		if len(messages) > 0 {
+			pkg.Messages = append(pkg.Messages, messages...)
+		}
+	}
+	sort.Slice(pkg.Messages, func(i int, j int) bool { return pkg.Messages[i].Name < pkg.Messages[j].Name })
+	return nil
+}
+
+// helper for NewPackageSet
+func populateServices(
+	pkg *reflectv1.Package,
+	fileNameToFileDescriptorProto map[string]*descriptor.FileDescriptorProto,
+) error {
+	for _, fileDescriptorProto := range fileNameToFileDescriptorProto {
+		services, err := getServices(fileDescriptorProto.GetService())
+		if err != nil {
+			return err
+		}
+		if len(services) > 0 {
+			pkg.Services = append(pkg.Services, services...)
+		}
+	}
+	sort.Slice(pkg.Services, func(i int, j int) bool { return pkg.Services[i].Name < pkg.Services[j].Name })
+	return nil
+}
+
+// helper for NewPackageSet
+func getPackageSet(packageNameToPackage map[string]*reflectv1.Package) (*reflectv1.PackageSet, error) {
+	if len(packageNameToPackage) == 0 {
+		return &reflectv1.PackageSet{}, nil
+	}
+	packageSet := &reflectv1.PackageSet{
+		Packages: make([]*reflectv1.Package, 0, len(packageNameToPackage)),
+	}
+	for _, pkg := range packageNameToPackage {
+		packageSet.Packages = append(packageSet.Packages, pkg)
+	}
+	sort.Slice(packageSet.Packages, func(i int, j int) bool { return packageSet.Packages[i].Name < packageSet.Packages[j].Name })
+	return packageSet, nil
+}
+
+// helper for populateDependencies
 func getFileNameToPackageName(packageNameToFileNameToFileDescriptorProto map[string]map[string]*descriptor.FileDescriptorProto) (map[string]string, error) {
 	fileNameToPackageName := make(map[string]string)
 	for packageName, fileNameToFileDescriptorProto := range packageNameToFileNameToFileDescriptorProto {
@@ -135,19 +238,161 @@ func getFileNameToPackageName(packageNameToFileNameToFileDescriptorProto map[str
 	return fileNameToPackageName, nil
 }
 
-type sortPackages []*reflectpb.Package
+func getEnums(enumDescriptorProtos []*descriptor.EnumDescriptorProto) ([]*reflectv1.Enum, error) {
+	if len(enumDescriptorProtos) == 0 {
+		return nil, nil
+	}
+	enums := make([]*reflectv1.Enum, 0, len(enumDescriptorProtos))
+	for _, enumDescriptorProto := range enumDescriptorProtos {
+		enum, err := newEnum(enumDescriptorProto)
+		if err != nil {
+			return nil, err
+		}
+		enums = append(enums, enum)
+	}
+	sort.Slice(enums, func(i int, j int) bool { return enums[i].Name < enums[j].Name })
+	return enums, nil
+}
+
+func newEnum(enumDescriptorProto *descriptor.EnumDescriptorProto) (*reflectv1.Enum, error) {
+	enum := &reflectv1.Enum{
+		Name: enumDescriptorProto.GetName(),
+	}
+	for _, enumValueDescriptorProto := range enumDescriptorProto.GetValue() {
+		enum.EnumValues = append(enum.EnumValues, &reflectv1.EnumValue{
+			Name:   enumValueDescriptorProto.GetName(),
+			Number: enumValueDescriptorProto.GetNumber(),
+		})
+	}
+	sort.Slice(enum.EnumValues, func(i int, j int) bool { return enum.EnumValues[i].Number < enum.EnumValues[j].Number })
+	return enum, nil
+}
+
+func getMessages(descriptorProtos []*descriptor.DescriptorProto) ([]*reflectv1.Message, error) {
+	if len(descriptorProtos) == 0 {
+		return nil, nil
+	}
+	messages := make([]*reflectv1.Message, 0, len(descriptorProtos))
+	for _, descriptorProto := range descriptorProtos {
+		message, err := newMessage(descriptorProto)
+		if err != nil {
+			return nil, err
+		}
+		messages = append(messages, message)
+	}
+	sort.Slice(messages, func(i int, j int) bool { return messages[i].Name < messages[j].Name })
+	return messages, nil
+}
+
+func newMessage(descriptorProto *descriptor.DescriptorProto) (*reflectv1.Message, error) {
+	nestedMessages, err := getMessages(descriptorProto.GetNestedType())
+	if err != nil {
+		return nil, err
+	}
+	nestedEnums, err := getEnums(descriptorProto.GetEnumType())
+	if err != nil {
+		return nil, err
+	}
+	message := &reflectv1.Message{
+		Name:           descriptorProto.GetName(),
+		NestedMessages: nestedMessages,
+		NestedEnums:    nestedEnums,
+	}
+	nameToMessageOneof := make(map[string]*reflectv1.MessageOneof, len(descriptorProto.GetOneofDecl()))
+	for _, oneofDescriptorProto := range descriptorProto.GetOneofDecl() {
+		nameToMessageOneof[oneofDescriptorProto.GetName()] = &reflectv1.MessageOneof{
+			Name: oneofDescriptorProto.GetName(),
+		}
+	}
+	for _, fieldDescriptorProto := range descriptorProto.GetField() {
+		typeName := fieldDescriptorProto.GetTypeName()
+		if typeName != "" {
+			typeName, err = verifyFullyQualifiedNameAndStrip(typeName)
+			if err != nil {
+				return nil, err
+			}
+		}
+		message.MessageFields = append(message.MessageFields, &reflectv1.MessageField{
+			Name:   fieldDescriptorProto.GetName(),
+			Number: fieldDescriptorProto.GetNumber(),
+			// TODO: this is technically unsafe since we're just working on the assumption
+			// that the numbers match up...which they do, but this isn't future proof
+			// however, the values for descriptor.proto have not changed since proto1
+			// which isn't even OSS, so we're probably fine for 10-20 years
+			Type:     reflectv1.MessageField_Type(fieldDescriptorProto.GetType()),
+			Label:    reflectv1.MessageField_Label(fieldDescriptorProto.GetLabel()),
+			TypeName: typeName,
+		})
+		if fieldDescriptorProto.OneofIndex != nil {
+			// TODO: super unsafe
+			messageOneof := nameToMessageOneof[descriptorProto.GetOneofDecl()[fieldDescriptorProto.GetOneofIndex()].GetName()]
+			messageOneof.FieldNumbers = append(messageOneof.FieldNumbers, fieldDescriptorProto.GetNumber())
+		}
+	}
+	for _, messageOneof := range nameToMessageOneof {
+		sort.Slice(messageOneof.FieldNumbers, func(i int, j int) bool { return messageOneof.FieldNumbers[i] < messageOneof.FieldNumbers[j] })
+		message.MessageOneofs = append(message.MessageOneofs, messageOneof)
+	}
+	sort.Slice(message.MessageFields, func(i int, j int) bool { return message.MessageFields[i].Number < message.MessageFields[j].Number })
+	sort.Slice(message.MessageOneofs, func(i int, j int) bool { return message.MessageOneofs[i].Name < message.MessageOneofs[j].Name })
+	return message, nil
+}
+
+func getServices(serviceDescriptorProtos []*descriptor.ServiceDescriptorProto) ([]*reflectv1.Service, error) {
+	if len(serviceDescriptorProtos) == 0 {
+		return nil, nil
+	}
+	services := make([]*reflectv1.Service, 0, len(serviceDescriptorProtos))
+	for _, serviceDescriptorProto := range serviceDescriptorProtos {
+		service, err := newService(serviceDescriptorProto)
+		if err != nil {
+			return nil, err
+		}
+		services = append(services, service)
+	}
+	sort.Slice(services, func(i int, j int) bool { return services[i].Name < services[j].Name })
+	return services, nil
+}
+
+func newService(serviceDescriptorProto *descriptor.ServiceDescriptorProto) (*reflectv1.Service, error) {
+	service := &reflectv1.Service{
+		Name: serviceDescriptorProto.GetName(),
+	}
+	for _, methodDescriptorProto := range serviceDescriptorProto.GetMethod() {
+		serviceMethod, err := newServiceMethod(methodDescriptorProto)
+		if err != nil {
+			return nil, err
+		}
+		service.ServiceMethods = append(service.ServiceMethods, serviceMethod)
+	}
+	sort.Slice(service.ServiceMethods, func(i int, j int) bool { return service.ServiceMethods[i].Name < service.ServiceMethods[j].Name })
+	return service, nil
+}
 
-func (s sortPackages) Len() int          { return len(s) }
-func (s sortPackages) Swap(i int, j int) { s[i], s[j] = s[j], s[i] }
-func (s sortPackages) Less(i int, j int) bool {
-	if s[i] == nil && s[j] == nil {
-		return false
+func newServiceMethod(methodDescriptorProto *descriptor.MethodDescriptorProto) (*reflectv1.ServiceMethod, error) {
+	requestTypeName, err := verifyFullyQualifiedNameAndStrip(methodDescriptorProto.GetInputType())
+	if err != nil {
+		return nil, err
 	}
-	if s[i] == nil && s[j] != nil {
-		return true
+	responseTypeName, err := verifyFullyQualifiedNameAndStrip(methodDescriptorProto.GetOutputType())
+	if err != nil {
+		return nil, err
+	}
+	return &reflectv1.ServiceMethod{
+		Name:             methodDescriptorProto.GetName(),
+		RequestTypeName:  requestTypeName,
+		ResponseTypeName: responseTypeName,
+		ClientStreaming:  methodDescriptorProto.GetClientStreaming(),
+		ServerStreaming:  methodDescriptorProto.GetServerStreaming(),
+	}, nil
+}
+
+func verifyFullyQualifiedNameAndStrip(s string) (string, error) {
+	if s == "" {
+		return "", fmt.Errorf("name empty")
 	}
-	if s[i] != nil && s[j] == nil {
-		return false
+	if s[0] != '.' {
+		return "", fmt.Errorf("%s does not start with '.'", s)
 	}
-	return s[i].Name < s[j].Name
+	return s[1:], nil
 }


Test patch:
diff --git a/internal/cmd/cmd_test.go b/internal/cmd/cmd_test.go
index 6b67af4..f1f3c2b 100644
--- a/internal/cmd/cmd_test.go
+++ b/internal/cmd/cmd_test.go
@@ -388,6 +388,17 @@ func TestLint(t *testing.T) {
 		"testdata/lint/nojsonname/foo.proto",
 	)
 
+	assertDoLintFile(
+		t,
+		false,
+		`11:3:FIELDS_NOT_RESERVED
+                 12:3:FIELDS_NOT_RESERVED
+                 14:5:FIELDS_NOT_RESERVED
+                 15:5:FIELDS_NOT_RESERVED
+                 20:5:FIELDS_NOT_RESERVED`,
+		"testdata/lint/noreserved/foo.proto",
+	)
+
 	assertDoLintFile(
 		t,
 		false,
diff --git a/internal/cmd/testdata/lint/noreserved/foo.proto b/internal/cmd/testdata/lint/noreserved/foo.proto
new file mode 100644
index 0000000..07d6143
--- /dev/null
+++ b/internal/cmd/testdata/lint/noreserved/foo.proto
@@ -0,0 +1,22 @@
+syntax = "proto3";
+
+package foo;
+
+option go_package = "foopb";
+option java_multiple_files = true;
+option java_outer_classname = "FooProto";
+option java_package = "com.foo";
+
+message Foo {
+  reserved 1 to 3, 4;
+  reserved "bar";
+  enum Bar {
+    reserved 4 to 6, 7;
+    reserved "foobar", "barfoo";
+    FOO_BAR_INVALID = 0;
+    FOO_BAR_UNSET = 1;
+  }
+  message FooFoo {
+    reserved 1;
+  }
+}
\ No newline at end of file
diff --git a/internal/cmd/testdata/lint/noreserved/prototool.yaml b/internal/cmd/testdata/lint/noreserved/prototool.yaml
new file mode 100644
index 0000000..0ce5981
--- /dev/null
+++ b/internal/cmd/testdata/lint/noreserved/prototool.yaml
@@ -0,0 +1,4 @@
+lint:
+  rules:
+    add:
+      - FIELDS_NOT_RESERVED
diff --git a/internal/cmd/testdata/reflect/one/prototool.yaml b/internal/cmd/testdata/reflect/one/prototool.yaml
new file mode 100644
index 0000000..d776229
--- /dev/null
+++ b/internal/cmd/testdata/reflect/one/prototool.yaml
@@ -0,0 +1,14 @@
+lint:
+  rules:
+    add:
+      - ENUM_FIELD_PREFIXES_EXCEPT_MESSAGE
+      - ENUM_ZERO_VALUES_INVALID_EXCEPT_MESSAGE
+      - FILE_OPTIONS_EQUAL_GO_PACKAGE_LAST_TWO_SUFFIX
+      - GOGO_NOT_IMPORTED
+      - MESSAGE_FIELDS_NO_JSON_NAME
+      - PACKAGE_MAJOR_VERSIONED
+      - REQUEST_RESPONSE_NAMES_MATCH_RPC
+    remove:
+      - ENUM_FIELD_PREFIXES
+      - ENUM_ZERO_VALUES_INVALID
+      - FILE_OPTIONS_EQUAL_GO_PACKAGE_PB_SUFFIX
diff --git a/internal/cmd/testdata/reflect/one/uber/proto/bar/v1/one.proto b/internal/cmd/testdata/reflect/one/uber/proto/bar/v1/one.proto
new file mode 100644
index 0000000..de2e0cd
--- /dev/null
+++ b/internal/cmd/testdata/reflect/one/uber/proto/bar/v1/one.proto
@@ -0,0 +1,28 @@
+syntax = "proto3";
+
+package uber.proto.bar.v1;
+
+option go_package = "barv1";
+option java_multiple_files = true;
+option java_outer_classname = "OneProto";
+option java_package = "com.uber.proto.bar.v1";
+
+import "uber/proto/bar/v1/two.proto";
+
+message OneFoo {
+  int64 one = 1;
+  string two = 2;
+  OneBar one_bar = 3;
+}
+
+message OneBar {
+  int64 one = 1;
+  string two = 2;
+}
+
+message OneBaz {
+  OneFoo one_foo = 1;
+  OneBar one_bar = 2;
+  TwoFoo two_foo = 3;
+  TwoBar two_bar = 4;
+}
diff --git a/internal/cmd/testdata/reflect/one/uber/proto/bar/v1/two.proto b/internal/cmd/testdata/reflect/one/uber/proto/bar/v1/two.proto
new file mode 100644
index 0000000..9ef7f72
--- /dev/null
+++ b/internal/cmd/testdata/reflect/one/uber/proto/bar/v1/two.proto
@@ -0,0 +1,19 @@
+syntax = "proto3";
+
+package uber.proto.bar.v1;
+
+option go_package = "barv1";
+option java_multiple_files = true;
+option java_outer_classname = "TwoProto";
+option java_package = "com.uber.proto.bar.v1";
+
+message TwoFoo {
+  int64 one = 1;
+  string two = 2;
+  TwoBar two_bar = 3;
+}
+
+message TwoBar {
+  int64 one = 1;
+  string two = 2;
+}
diff --git a/internal/cmd/testdata/reflect/one/uber/proto/foo/v1/one.proto b/internal/cmd/testdata/reflect/one/uber/proto/foo/v1/one.proto
new file mode 100644
index 0000000..de704cb
--- /dev/null
+++ b/internal/cmd/testdata/reflect/one/uber/proto/foo/v1/one.proto
@@ -0,0 +1,85 @@
+syntax = "proto3";
+
+package uber.proto.foo.v1;
+
+option go_package = "foov1";
+option java_multiple_files = true;
+option java_outer_classname = "OneProto";
+option java_package = "com.uber.proto.foo.v1";
+
+import "uber/proto/bar/v1/one.proto";
+import "uber/proto/bar/v1/two.proto";
+import "uber/proto/foo/v1/two.proto";
+
+message OneFoo {
+  int64 one = 1;
+  string two = 2;
+  OneBar one_bar = 3;
+}
+
+message OneBar {
+  int64 one = 1;
+  string two = 2;
+}
+
+message OneBaz {
+  OneFoo one_foo = 1;
+  OneBar one_bar = 2;
+  TwoFoo two_foo = 3;
+  TwoBar two_bar = 4;
+}
+
+message OneBat {
+  bar.v1.OneFoo one_foo = 1;
+  bar.v1.OneBar one_bar = 2;
+  uber.proto.bar.v1.TwoFoo two_foo = 3;
+  uber.proto.bar.v1.TwoBar two_bar = 4;
+}
+
+enum Enum {
+  ENUM_INVALID = 0;
+  ENUM_FOO = 1;
+  ENUM_BAR = 2;
+}
+
+message Simple {
+  message Nested {
+    int64 one = 1;
+    string two = 2;
+  }
+  enum NestedEnum {
+    NESTED_ENUM_INVALID = 0;
+    NESTED_ENUM_FOO = 1;
+    NESTED_ENUM_BAR = 2;
+  }
+
+  int64 one = 1;
+  string two = 2;
+  repeated int64 three = 3;
+  map<int64, string> four = 4;
+  oneof test_oneof {
+    int64 five = 5;
+    OneBat one_bat = 6;
+  }
+}
+
+message FooRequest {
+  int64 one = 1;
+}
+
+message FooResponse {
+  int64 one = 1;
+}
+
+message BarRequest {
+  int64 one = 1;
+}
+
+message BarResponse {
+  int64 one = 1;
+}
+
+service SomeAPI {
+  rpc Foo(FooRequest) returns (FooResponse);
+  rpc Bar(BarRequest) returns (BarResponse);
+}
diff --git a/internal/cmd/testdata/reflect/one/uber/proto/foo/v1/two.proto b/internal/cmd/testdata/reflect/one/uber/proto/foo/v1/two.proto
new file mode 100644
index 0000000..0744a85
--- /dev/null
+++ b/internal/cmd/testdata/reflect/one/uber/proto/foo/v1/two.proto
@@ -0,0 +1,19 @@
+syntax = "proto3";
+
+package uber.proto.foo.v1;
+
+option go_package = "foov1";
+option java_multiple_files = true;
+option java_outer_classname = "TwoProto";
+option java_package = "com.uber.proto.foo.v1";
+
+message TwoFoo {
+  int64 one = 1;
+  string two = 2;
+  TwoBar two_bar = 3;
+}
+
+message TwoBar {
+  int64 one = 1;
+  string two = 2;
+}
diff --git a/internal/extract/extract_test.go b/internal/extract/extract_test.go
new file mode 100644
index 0000000..144a9f1
--- /dev/null
+++ b/internal/extract/extract_test.go
@@ -0,0 +1,56 @@
+// Copyright (c) 2019 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package extract
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"github.com/uber/prototool/internal/reflect"
+	ptesting "github.com/uber/prototool/internal/testing"
+)
+
+func TestOne(t *testing.T) {
+	packageSet := requireGetPackageSet(t, "one")
+	packageNameToPackage := packageSet.PackageNameToPackage()
+	_, ok := packageNameToPackage["uber.proto.foo.v1"]
+	require.True(t, ok)
+	_, ok = packageNameToPackage["uber.proto.bar.v1"]
+	require.True(t, ok)
+}
+
+func requireGetPackageSet(t *testing.T, subDirPath string) *PackageSet {
+	packageSet, err := getPackageSet(subDirPath)
+	require.NoError(t, err)
+	return packageSet
+}
+
+func getPackageSet(subDirPath string) (*PackageSet, error) {
+	fileDescriptorSets, err := ptesting.GetFileDescriptorSets("../cmd/testdata/reflect", "../cmd/testdata/reflect/"+subDirPath)
+	if err != nil {
+		return nil, err
+	}
+	reflectPackageSet, err := reflect.NewPackageSet(fileDescriptorSets...)
+	if err != nil {
+		return nil, err
+	}
+	return NewPackageSet(reflectPackageSet)
+}
diff --git a/internal/reflect/reflect_test.go b/internal/reflect/reflect_test.go
new file mode 100644
index 0000000..561a396
--- /dev/null
+++ b/internal/reflect/reflect_test.go
@@ -0,0 +1,527 @@
+// Copyright (c) 2019 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package reflect
+
+import (
+	"testing"
+
+	"github.com/golang/protobuf/jsonpb"
+	"github.com/stretchr/testify/require"
+	reflectv1 "github.com/uber/prototool/internal/reflect/gen/uber/proto/reflect/v1"
+	ptesting "github.com/uber/prototool/internal/testing"
+)
+
+func TestOne(t *testing.T) {
+	testNewPackageSet(
+		t,
+		"one",
+		`
+{
+  "packages": [
+    {
+      "name": "uber.proto.bar.v1",
+      "messages": [
+        {
+          "name": "OneBar",
+          "messageFields": [
+            {
+              "name": "one",
+              "number": 1,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_INT64"
+            },
+            {
+              "name": "two",
+              "number": 2,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_STRING"
+            }
+          ]
+        },
+        {
+          "name": "OneBaz",
+          "messageFields": [
+            {
+              "name": "one_foo",
+              "number": 1,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_MESSAGE",
+              "typeName": "uber.proto.bar.v1.OneFoo"
+            },
+            {
+              "name": "one_bar",
+              "number": 2,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_MESSAGE",
+              "typeName": "uber.proto.bar.v1.OneBar"
+            },
+            {
+              "name": "two_foo",
+              "number": 3,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_MESSAGE",
+              "typeName": "uber.proto.bar.v1.TwoFoo"
+            },
+            {
+              "name": "two_bar",
+              "number": 4,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_MESSAGE",
+              "typeName": "uber.proto.bar.v1.TwoBar"
+            }
+          ]
+        },
+        {
+          "name": "OneFoo",
+          "messageFields": [
+            {
+              "name": "one",
+              "number": 1,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_INT64"
+            },
+            {
+              "name": "two",
+              "number": 2,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_STRING"
+            },
+            {
+              "name": "one_bar",
+              "number": 3,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_MESSAGE",
+              "typeName": "uber.proto.bar.v1.OneBar"
+            }
+          ]
+        },
+        {
+          "name": "TwoBar",
+          "messageFields": [
+            {
+              "name": "one",
+              "number": 1,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_INT64"
+            },
+            {
+              "name": "two",
+              "number": 2,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_STRING"
+            }
+          ]
+        },
+        {
+          "name": "TwoFoo",
+          "messageFields": [
+            {
+              "name": "one",
+              "number": 1,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_INT64"
+            },
+            {
+              "name": "two",
+              "number": 2,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_STRING"
+            },
+            {
+              "name": "two_bar",
+              "number": 3,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_MESSAGE",
+              "typeName": "uber.proto.bar.v1.TwoBar"
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "name": "uber.proto.foo.v1",
+      "dependencyNames": [
+        "uber.proto.bar.v1"
+      ],
+      "enums": [
+        {
+          "name": "Enum",
+          "enumValues": [
+            {
+              "name": "ENUM_INVALID"
+            },
+            {
+              "name": "ENUM_FOO",
+              "number": 1
+            },
+            {
+              "name": "ENUM_BAR",
+              "number": 2
+            }
+          ]
+        }
+      ],
+      "messages": [
+        {
+          "name": "BarRequest",
+          "messageFields": [
+            {
+              "name": "one",
+              "number": 1,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_INT64"
+            }
+          ]
+        },
+        {
+          "name": "BarResponse",
+          "messageFields": [
+            {
+              "name": "one",
+              "number": 1,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_INT64"
+            }
+          ]
+        },
+        {
+          "name": "FooRequest",
+          "messageFields": [
+            {
+              "name": "one",
+              "number": 1,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_INT64"
+            }
+          ]
+        },
+        {
+          "name": "FooResponse",
+          "messageFields": [
+            {
+              "name": "one",
+              "number": 1,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_INT64"
+            }
+          ]
+        },
+        {
+          "name": "OneBar",
+          "messageFields": [
+            {
+              "name": "one",
+              "number": 1,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_INT64"
+            },
+            {
+              "name": "two",
+              "number": 2,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_STRING"
+            }
+          ]
+        },
+        {
+          "name": "OneBat",
+          "messageFields": [
+            {
+              "name": "one_foo",
+              "number": 1,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_MESSAGE",
+              "typeName": "uber.proto.bar.v1.OneFoo"
+            },
+            {
+              "name": "one_bar",
+              "number": 2,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_MESSAGE",
+              "typeName": "uber.proto.bar.v1.OneBar"
+            },
+            {
+              "name": "two_foo",
+              "number": 3,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_MESSAGE",
+              "typeName": "uber.proto.bar.v1.TwoFoo"
+            },
+            {
+              "name": "two_bar",
+              "number": 4,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_MESSAGE",
+              "typeName": "uber.proto.bar.v1.TwoBar"
+            }
+          ]
+        },
+        {
+          "name": "OneBaz",
+          "messageFields": [
+            {
+              "name": "one_foo",
+              "number": 1,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_MESSAGE",
+              "typeName": "uber.proto.foo.v1.OneFoo"
+            },
+            {
+              "name": "one_bar",
+              "number": 2,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_MESSAGE",
+              "typeName": "uber.proto.foo.v1.OneBar"
+            },
+            {
+              "name": "two_foo",
+              "number": 3,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_MESSAGE",
+              "typeName": "uber.proto.foo.v1.TwoFoo"
+            },
+            {
+              "name": "two_bar",
+              "number": 4,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_MESSAGE",
+              "typeName": "uber.proto.foo.v1.TwoBar"
+            }
+          ]
+        },
+        {
+          "name": "OneFoo",
+          "messageFields": [
+            {
+              "name": "one",
+              "number": 1,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_INT64"
+            },
+            {
+              "name": "two",
+              "number": 2,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_STRING"
+            },
+            {
+              "name": "one_bar",
+              "number": 3,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_MESSAGE",
+              "typeName": "uber.proto.foo.v1.OneBar"
+            }
+          ]
+        },
+        {
+          "name": "Simple",
+          "messageFields": [
+            {
+              "name": "one",
+              "number": 1,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_INT64"
+            },
+            {
+              "name": "two",
+              "number": 2,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_STRING"
+            },
+            {
+              "name": "three",
+              "number": 3,
+              "label": "LABEL_REPEATED",
+              "type": "TYPE_INT64"
+            },
+            {
+              "name": "four",
+              "number": 4,
+              "label": "LABEL_REPEATED",
+              "type": "TYPE_MESSAGE",
+              "typeName": "uber.proto.foo.v1.Simple.FourEntry"
+            },
+            {
+              "name": "five",
+              "number": 5,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_INT64"
+            },
+            {
+              "name": "one_bat",
+              "number": 6,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_MESSAGE",
+              "typeName": "uber.proto.foo.v1.OneBat"
+            }
+          ],
+          "messageOneofs": [
+            {
+              "name": "test_oneof",
+              "fieldNumbers": [
+                5,
+                6
+              ]
+            }
+          ],
+          "nestedMessages": [
+            {
+              "name": "FourEntry",
+              "messageFields": [
+                {
+                  "name": "key",
+                  "number": 1,
+                  "label": "LABEL_OPTIONAL",
+                  "type": "TYPE_INT64"
+                },
+                {
+                  "name": "value",
+                  "number": 2,
+                  "label": "LABEL_OPTIONAL",
+                  "type": "TYPE_STRING"
+                }
+              ]
+            },
+            {
+              "name": "Nested",
+              "messageFields": [
+                {
+                  "name": "one",
+                  "number": 1,
+                  "label": "LABEL_OPTIONAL",
+                  "type": "TYPE_INT64"
+                },
+                {
+                  "name": "two",
+                  "number": 2,
+                  "label": "LABEL_OPTIONAL",
+                  "type": "TYPE_STRING"
+                }
+              ]
+            }
+          ],
+          "nestedEnums": [
+            {
+              "name": "NestedEnum",
+              "enumValues": [
+                {
+                  "name": "NESTED_ENUM_INVALID"
+                },
+                {
+                  "name": "NESTED_ENUM_FOO",
+                  "number": 1
+                },
+                {
+                  "name": "NESTED_ENUM_BAR",
+                  "number": 2
+                }
+              ]
+            }
+          ]
+        },
+        {
+          "name": "TwoBar",
+          "messageFields": [
+            {
+              "name": "one",
+              "number": 1,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_INT64"
+            },
+            {
+              "name": "two",
+              "number": 2,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_STRING"
+            }
+          ]
+        },
+        {
+          "name": "TwoFoo",
+          "messageFields": [
+            {
+              "name": "one",
+              "number": 1,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_INT64"
+            },
+            {
+              "name": "two",
+              "number": 2,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_STRING"
+            },
+            {
+              "name": "two_bar",
+              "number": 3,
+              "label": "LABEL_OPTIONAL",
+              "type": "TYPE_MESSAGE",
+              "typeName": "uber.proto.foo.v1.TwoBar"
+            }
+          ]
+        }
+      ],
+      "services": [
+        {
+          "name": "SomeAPI",
+          "serviceMethods": [
+            {
+              "name": "Bar",
+              "requestTypeName": "uber.proto.foo.v1.BarRequest",
+              "responseTypeName": "uber.proto.foo.v1.BarResponse"
+            },
+            {
+              "name": "Foo",
+              "requestTypeName": "uber.proto.foo.v1.FooRequest",
+              "responseTypeName": "uber.proto.foo.v1.FooResponse"
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
+`,
+	)
+}
+
+func testNewPackageSet(t *testing.T, subDirPath string, packageSetJSON string) {
+	fileDescriptorSets := ptesting.RequireGetFileDescriptorSets(t, "../cmd/testdata/reflect", "../cmd/testdata/reflect/"+subDirPath)
+	packageSet, err := NewPackageSet(fileDescriptorSets...)
+	require.NoError(t, err)
+	// It's much easier to edit JSON than the actual Golang structs all the type
+	expectedPackageSet := requireUnmarshalPackageSet(t, packageSetJSON)
+	require.Equal(t, expectedPackageSet, packageSet)
+}
+
+func requireUnmarshalPackageSet(t *testing.T, s string) *reflectv1.PackageSet {
+	packageSet, err := unmarshalPackageSet(s)
+	require.NoError(t, err)
+	return packageSet
+}
+
+func unmarshalPackageSet(s string) (*reflectv1.PackageSet, error) {
+	packageSet := &reflectv1.PackageSet{}
+	if err := jsonpb.UnmarshalString(s, packageSet); err != nil {
+		return nil, err
+	}
+	return packageSet, nil
+}
diff --git a/internal/testing/testing.go b/internal/testing/testing.go
new file mode 100644
index 0000000..97953f4
--- /dev/null
+++ b/internal/testing/testing.go
@@ -0,0 +1,99 @@
+// Copyright (c) 2019 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package testing
+
+import (
+	"errors"
+	"fmt"
+	"testing"
+
+	"github.com/golang/protobuf/jsonpb"
+	"github.com/golang/protobuf/protoc-gen-go/descriptor"
+	"github.com/stretchr/testify/require"
+	"github.com/uber/prototool/internal/file"
+	"github.com/uber/prototool/internal/protoc"
+	reflectv1 "github.com/uber/prototool/internal/reflect/gen/uber/proto/reflect/v1"
+	"go.uber.org/multierr"
+)
+
+var jsonMarshaler = &jsonpb.Marshaler{Indent: "  "}
+
+// RequireGetFileDescriptorSets calls GetDescriptorFileSets with require calls.
+func RequireGetFileDescriptorSets(t *testing.T, workDirPath string, dirPath string) []*descriptor.FileDescriptorSet {
+	fileDescriptorSets, err := GetFileDescriptorSets(workDirPath, dirPath)
+	require.NoError(t, err)
+	require.NotEmpty(t, fileDescriptorSets)
+	return fileDescriptorSets
+}
+
+// GetFileDescriptorSets gets the FileDescriptorSets that result from compiling the given dirPath.
+func GetFileDescriptorSets(workDirPath string, dirPath string) ([]*descriptor.FileDescriptorSet, error) {
+	protoSet, err := file.NewProtoSetProvider().GetForDir(workDirPath, dirPath)
+	if err != nil {
+		return nil, err
+	}
+	compileResult, err := protoc.NewCompiler(
+		protoc.CompilerWithFileDescriptorSet(),
+	).Compile(protoSet)
+	if err != nil {
+		return nil, err
+	}
+	if len(compileResult.Failures) > 0 {
+		var err error
+		for _, failure := range compileResult.Failures {
+			err = multierr.Append(err, errors.New(failure.String()))
+		}
+		return nil, err
+	}
+	return compileResult.FileDescriptorSets, nil
+}
+
+// RequirePrintFileDescriptorSetsJSON calls PrintFileDescriptorSetsJSON with require calls.
+func RequirePrintFileDescriptorSetsJSON(t *testing.T, fileDescriptorSets []*descriptor.FileDescriptorSet) {
+	require.NoError(t, PrintFileDescriptorSetsJSON(fileDescriptorSets))
+}
+
+// PrintFileDescriptorSetsJSON prints the FileDescriptorSets to stdout.
+func PrintFileDescriptorSetsJSON(fileDescriptorSets []*descriptor.FileDescriptorSet) error {
+	for _, fileDescriptorSet := range fileDescriptorSets {
+		s, err := jsonMarshaler.MarshalToString(fileDescriptorSet)
+		if err != nil {
+			return err
+		}
+		fmt.Println(s)
+	}
+	return nil
+}
+
+// RequirePrintPackageSetJSON calls PrintPackageSetJSON with require calls.
+func RequirePrintPackageSetJSON(t *testing.T, packageSet *reflectv1.PackageSet) {
+	require.NoError(t, PrintPackageSetJSON(packageSet))
+}
+
+// PrintPackageSetJSON prints the PackageSet to stdout.
+func PrintPackageSetJSON(packageSet *reflectv1.PackageSet) error {
+	s, err := jsonMarshaler.MarshalToString(packageSet)
+	if err != nil {
+		return err
+	}
+	fmt.Println(s)
+	return nil
+}