From bc1fb9464910994ac5d9bfb2a0cbb11a783b42bc Mon Sep 17 00:00:00 2001 From: Joshua Humphries <2035234+jhump@users.noreply.github.com> Date: Tue, 13 Dec 2022 23:07:45 -0500 Subject: [PATCH] image filtering: don't include entire enclosing message/service (#1659) If an enclosing message is not part of the transitive dependency graph, but a nested message therein is, the enclosing message will be stripped of its fields. This produces a smaller filtered descriptor set but does not impede the use of the results for dynamic messages or dynamic RPC. Similarly, if a method is indicated as a type filter, its enclosing service will be stripped of other unreferenced methods. Finally, this attempts to fix/clarify the behavior around when custom options are included since they are also known extensions. If known extensions are included in the filtered set AND one of the options message types is part of the transitive dependency graph, all of the relevant custom options will be included. Otherwise, if custom options are included, ones actually referenced in options on the elements in the transitive dependency graph will be present. --- .../bufimage/bufimageutil/bufimageutil.go | 402 +++++++++--------- .../bufimageutil/bufimageutil_test.go | 6 + .../testdata/nesting/recursenested.txtar | 3 - .../testdata/nesting/usingother.txtar | 13 - .../bufimageutil/testdata/options/Files.txtar | 291 +++++++++++++ .../bufimageutil/testdata/options/a.proto | 5 + .../options/all-exclude-options.txtar | 1 + .../testdata/options/all-with-Files.txtar | 340 +++++++++++++++ .../bufimageutil/testdata/options/all.txtar | 4 + .../testdata/options/options.proto | 4 + .../testdata/options/pkg.FooService.txtar | 4 + 11 files changed, 862 insertions(+), 211 deletions(-) create mode 100644 private/bufpkg/bufimage/bufimageutil/testdata/options/Files.txtar create mode 100644 private/bufpkg/bufimage/bufimageutil/testdata/options/all-with-Files.txtar diff --git a/private/bufpkg/bufimage/bufimageutil/bufimageutil.go b/private/bufpkg/bufimage/bufimageutil/bufimageutil.go index 5d28958d27..b4901ae5cb 100644 --- a/private/bufpkg/bufimage/bufimageutil/bufimageutil.go +++ b/private/bufpkg/bufimage/bufimageutil/bufimageutil.go @@ -206,10 +206,14 @@ func ImageFilteredByTypesWithOptions(image bufimage.Image, types []string, opts // Find all types to include in filtered image. closure := newTransitiveClosure() for _, startingDescriptor := range startingDescriptors { - if err := closure.addElement(startingDescriptor, "", imageIndex, options); err != nil { + if err := closure.addElement(startingDescriptor, "", false, imageIndex, options); err != nil { return nil, err } } + // After all typs are added, add their known extensions + if err := closure.addExtensions(imageIndex, options); err != nil { + return nil, err + } // Create a new image with only the required descriptors. var includedFiles []bufimage.ImageFile for _, imageFile := range image.Files() { @@ -255,29 +259,13 @@ func ImageFilteredByTypesWithOptions(image bufimage.Image, types []string, opts } imageFileDescriptor.WeakDependency = imageFileDescriptor.WeakDependency[:i] - trimMessages, err := trimMessageDescriptor(imageFileDescriptor.MessageType, closure.elements) - if err != nil { - return nil, err - } - imageFileDescriptor.MessageType = trimMessages - trimEnums, err := trimEnumDescriptor(imageFileDescriptor.EnumType, closure.elements) - if err != nil { - return nil, err - } - imageFileDescriptor.EnumType = trimEnums - trimExtensions, err := trimExtensionDescriptors(imageFileDescriptor.Extension, closure.elements) - if err != nil { - return nil, err - } - imageFileDescriptor.Extension = trimExtensions - i = 0 + imageFileDescriptor.MessageType = trimMessageDescriptors(imageFileDescriptor.MessageType, closure.elements) + imageFileDescriptor.EnumType = trimSlice(imageFileDescriptor.EnumType, closure.elements) + imageFileDescriptor.Extension = trimSlice(imageFileDescriptor.Extension, closure.elements) + imageFileDescriptor.Service = trimSlice(imageFileDescriptor.Service, closure.elements) for _, serviceDescriptor := range imageFileDescriptor.Service { - if _, ok := closure.elements[serviceDescriptor]; ok { - imageFileDescriptor.Service[i] = serviceDescriptor - i++ - } + serviceDescriptor.Method = trimSlice(serviceDescriptor.Method, closure.elements) } - imageFileDescriptor.Service = imageFileDescriptor.Service[:i] // TODO: With some from/to mappings, perhaps even sourcecodeinfo // isn't too bad. @@ -286,72 +274,73 @@ func ImageFilteredByTypesWithOptions(image bufimage.Image, types []string, opts return bufimage.NewImage(includedFiles) } -// trimMessageDescriptor removes (nested) messages and nested enums from a slice +// trimMessageDescriptors removes (nested) messages and nested enums from a slice // of message descriptors if their type names are not found in the toKeep map. -func trimMessageDescriptor(in []*descriptorpb.DescriptorProto, toKeep map[namedDescriptor]struct{}) ([]*descriptorpb.DescriptorProto, error) { - i := 0 +func trimMessageDescriptors(in []*descriptorpb.DescriptorProto, toKeep map[namedDescriptor]closureInclusionMode) []*descriptorpb.DescriptorProto { + in = trimSlice(in, toKeep) for _, messageDescriptor := range in { - if _, ok := toKeep[messageDescriptor]; ok { - trimMessages, err := trimMessageDescriptor(messageDescriptor.NestedType, toKeep) - if err != nil { - return nil, err - } - messageDescriptor.NestedType = trimMessages - trimEnums, err := trimEnumDescriptor(messageDescriptor.EnumType, toKeep) - if err != nil { - return nil, err - } - messageDescriptor.EnumType = trimEnums - trimExtensions, err := trimExtensionDescriptors(messageDescriptor.Extension, toKeep) - if err != nil { - return nil, err - } - messageDescriptor.Extension = trimExtensions - in[i] = messageDescriptor - i++ - } - } - return in[:i], nil -} - -// trimEnumDescriptor removes enums from a slice of enum descriptors if their -// type names are not found in the toKeep map. -func trimEnumDescriptor(in []*descriptorpb.EnumDescriptorProto, toKeep map[namedDescriptor]struct{}) ([]*descriptorpb.EnumDescriptorProto, error) { - i := 0 - for _, enumDescriptor := range in { - if _, ok := toKeep[enumDescriptor]; ok { - in[i] = enumDescriptor - i++ + mode, ok := toKeep[messageDescriptor] + if !ok { + continue } + if mode == inclusionModeEnclosing { + // if this is just an enclosing element, we only care about it as a namespace for + // other types and don't care about the rest of its contents + messageDescriptor.Field = nil + messageDescriptor.OneofDecl = nil + messageDescriptor.ExtensionRange = nil + messageDescriptor.ReservedRange = nil + messageDescriptor.ReservedName = nil + } + messageDescriptor.NestedType = trimMessageDescriptors(messageDescriptor.NestedType, toKeep) + messageDescriptor.EnumType = trimSlice(messageDescriptor.EnumType, toKeep) + messageDescriptor.Extension = trimSlice(messageDescriptor.Extension, toKeep) } - return in[:i], nil + return in } -// trimExtensionDescriptors removes fields from a slice of field descriptors if their -// type names are not found in the toKeep map. -func trimExtensionDescriptors(in []*descriptorpb.FieldDescriptorProto, toKeep map[namedDescriptor]struct{}) ([]*descriptorpb.FieldDescriptorProto, error) { +// trimSlice removes elements from a slice of descriptors if they are +// not present in the given map. +func trimSlice[T namedDescriptor](in []T, toKeep map[namedDescriptor]closureInclusionMode) []T { i := 0 - for _, fieldDescriptor := range in { - if _, ok := toKeep[fieldDescriptor]; ok { - in[i] = fieldDescriptor + for _, descriptor := range in { + if _, ok := toKeep[descriptor]; ok { + in[i] = descriptor i++ } } - return in[:i], nil + return in[:i] } // transitiveClosure accumulates the elements, files, and needed imports for a // subset of an image. When an element is added to the closure, all of its // dependencies are recursively added. type transitiveClosure struct { - elements map[namedDescriptor]struct{} + elements map[namedDescriptor]closureInclusionMode files map[string]struct{} imports map[string]map[string]struct{} } +type closureInclusionMode int + +const ( + // Element is included in closure because it is directly reachable from a root. + inclusionModeExplicit = closureInclusionMode(iota) + // Element is included in closure because it is a message or service that + // *contains* an explicitly included element but is not itself directly + // reachable. + inclusionModeEnclosing + // Element is included in closure because it is implied by the presence of a + // custom option. For example, a field element with a custom option implies + // the presence of google.protobuf.FieldOptions. An option type could instead be + // explicitly included if it is also directly reachable (i.e. some type in the + // graph explicitly refers to the option type). + inclusionModeImplicit +) + func newTransitiveClosure() *transitiveClosure { return &transitiveClosure{ - elements: map[namedDescriptor]struct{}{}, + elements: map[namedDescriptor]closureInclusionMode{}, files: map[string]struct{}{}, imports: map[string]map[string]struct{}{}, } @@ -380,6 +369,7 @@ func (t *transitiveClosure) addFile(file string, imageIndex *imageIndex, opts *i func (t *transitiveClosure) addElement( descriptor namedDescriptor, referrerFile string, + impliedByCustomOption bool, imageIndex *imageIndex, opts *imageFilterOptions, ) error { @@ -391,69 +381,36 @@ func (t *transitiveClosure) addElement( t.addImport(referrerFile, descriptorInfo.file) } - if _, ok := t.elements[descriptor]; ok { + if existingMode, ok := t.elements[descriptor]; ok && existingMode != inclusionModeEnclosing { + if existingMode == inclusionModeImplicit && !impliedByCustomOption { + // upgrade from implied to explicitly part of closure + t.elements[descriptor] = inclusionModeExplicit + } return nil // already added this element } - t.elements[descriptor] = struct{}{} + if impliedByCustomOption { + t.elements[descriptor] = inclusionModeImplicit + } else { + t.elements[descriptor] = inclusionModeExplicit + } + + // if this type is enclosed inside another, add enclosing types + if err := t.addEnclosing(descriptorInfo.parent, descriptorInfo.file, imageIndex, opts); err != nil { + return err + } + // add any custom options and their dependencies + if err := t.exploreCustomOptions(descriptor, descriptorInfo.file, imageIndex, opts); err != nil { + return err + } switch typedDescriptor := descriptor.(type) { case *descriptorpb.DescriptorProto: + // Options and types for all fields for _, field := range typedDescriptor.GetField() { - switch field.GetType() { - case descriptorpb.FieldDescriptorProto_TYPE_ENUM, - descriptorpb.FieldDescriptorProto_TYPE_MESSAGE, - descriptorpb.FieldDescriptorProto_TYPE_GROUP: - typeName := strings.TrimPrefix(field.GetTypeName(), ".") - typeDescriptor, ok := imageIndex.ByName[typeName] - if !ok { - return fmt.Errorf("missing %q", typeName) - } - if err := t.addElement(typeDescriptor, descriptorInfo.file, imageIndex, opts); err != nil { - return err - } - case descriptorpb.FieldDescriptorProto_TYPE_DOUBLE, - descriptorpb.FieldDescriptorProto_TYPE_FLOAT, - descriptorpb.FieldDescriptorProto_TYPE_INT64, - descriptorpb.FieldDescriptorProto_TYPE_UINT64, - descriptorpb.FieldDescriptorProto_TYPE_INT32, - descriptorpb.FieldDescriptorProto_TYPE_FIXED64, - descriptorpb.FieldDescriptorProto_TYPE_FIXED32, - descriptorpb.FieldDescriptorProto_TYPE_BOOL, - descriptorpb.FieldDescriptorProto_TYPE_STRING, - descriptorpb.FieldDescriptorProto_TYPE_BYTES, - descriptorpb.FieldDescriptorProto_TYPE_UINT32, - descriptorpb.FieldDescriptorProto_TYPE_SFIXED32, - descriptorpb.FieldDescriptorProto_TYPE_SFIXED64, - descriptorpb.FieldDescriptorProto_TYPE_SINT32, - descriptorpb.FieldDescriptorProto_TYPE_SINT64: - // nothing to explore for the field type, but - // there might be custom field options - default: - return fmt.Errorf("unknown field type %d", field.GetType()) - } - // Field options - if err := t.exploreCustomOptions(field, descriptorInfo.file, imageIndex, opts); err != nil { + if err := t.addFieldType(field, descriptorInfo.file, imageIndex, opts); err != nil { return err } - } - // Extensions declared for this message. - // TODO: We currently exclude all extensions for descriptor options types (and instead - // only gather the ones used in relevant custom options). But if the descriptor option - // type were a named type for filtering, we SHOULD include all of them. - if opts.includeKnownExtensions && !isOptionsTypeName(descriptorInfo.fullName) { - for _, extendsDescriptor := range imageIndex.NameToExtensions[descriptorInfo.fullName] { - if err := t.addElement(extendsDescriptor, "", imageIndex, opts); err != nil { - return err - } - } - } - // Messages in which this message is nested - if _, ok := descriptorInfo.parent.(*descriptorpb.DescriptorProto); ok { - // TODO: we don't actually want or need the entire parent message unless it is actually - // referenced by other parts of the schema. As a reference from a nested message, we - // only care about it as a namespace (so all of its other elements, aside from needed - // nested types, could be omitted). - if err := t.addElement(descriptorInfo.parent, "", imageIndex, opts); err != nil { + if err := t.exploreCustomOptions(field, referrerFile, imageIndex, opts); err != nil { return err } } @@ -469,52 +426,28 @@ func (t *transitiveClosure) addElement( return err } } - // Message options - if err := t.exploreCustomOptions(typedDescriptor, descriptorInfo.file, imageIndex, opts); err != nil { - return err - } + case *descriptorpb.EnumDescriptorProto: - // Parent messages - if _, ok := descriptorInfo.parent.(*descriptorpb.DescriptorProto); ok { - // TODO: ditto above: unless the parent message is used elsewhere, we don't need the - // entire message; we only need it as a placeholder for namespacing. - if err := t.addElement(descriptorInfo.parent, "", imageIndex, opts); err != nil { - return err - } - } for _, enumValue := range typedDescriptor.GetValue() { if err := t.exploreCustomOptions(enumValue, descriptorInfo.file, imageIndex, opts); err != nil { return err } } - // Enum options - if err := t.exploreCustomOptions(typedDescriptor, descriptorInfo.file, imageIndex, opts); err != nil { - return err - } + case *descriptorpb.ServiceDescriptorProto: for _, method := range typedDescriptor.GetMethod() { - if err := t.addElement(method, "", imageIndex, opts); err != nil { + if err := t.addElement(method, "", false, imageIndex, opts); err != nil { return err } } - // Service options - if err := t.exploreCustomOptions(typedDescriptor, descriptorInfo.file, imageIndex, opts); err != nil { - return err - } - case *descriptorpb.MethodDescriptorProto: - // in case method was directly named as a filter type, make sure we include parent service - // TODO: if the service is not also named as a filter type, we could prune the service down - // to only the named methods and shrink the size of the filtered image further. - if err := t.addElement(descriptorInfo.parent, "", imageIndex, opts); err != nil { - return err - } + case *descriptorpb.MethodDescriptorProto: inputName := strings.TrimPrefix(typedDescriptor.GetInputType(), ".") inputDescriptor, ok := imageIndex.ByName[inputName] if !ok { return fmt.Errorf("missing %q", inputName) } - if err := t.addElement(inputDescriptor, descriptorInfo.file, imageIndex, opts); err != nil { + if err := t.addElement(inputDescriptor, descriptorInfo.file, false, imageIndex, opts); err != nil { return err } @@ -523,19 +456,16 @@ func (t *transitiveClosure) addElement( if !ok { return fmt.Errorf("missing %q", outputName) } - if err := t.addElement(outputDescriptor, descriptorInfo.file, imageIndex, opts); err != nil { - return err - } - - // Method options - if err := t.exploreCustomOptions(typedDescriptor, descriptorInfo.file, imageIndex, opts); err != nil { + if err := t.addElement(outputDescriptor, descriptorInfo.file, false, imageIndex, opts); err != nil { return err } case *descriptorpb.FieldDescriptorProto: - // Regular fields get handled by protosource.Message, only - // protosource.Fields's for extends definitions should reach - // here. + // Regular fields are handled above in message descriptor case. + // We should only find our way here for extensions. + if typedDescriptor.Extendee == nil { + return errorUnsupportedFilterType(descriptor, descriptorInfo.fullName) + } if typedDescriptor.GetExtendee() == "" { return fmt.Errorf("expected extendee for field %q to not be empty", descriptorInfo.fullName) } @@ -544,49 +474,131 @@ func (t *transitiveClosure) addElement( if !ok { return fmt.Errorf("missing %q", extendeeName) } - if err := t.addElement(extendeeDescriptor, descriptorInfo.file, imageIndex, opts); err != nil { + if err := t.addElement(extendeeDescriptor, descriptorInfo.file, impliedByCustomOption, imageIndex, opts); err != nil { + return err + } + if err := t.addFieldType(typedDescriptor, descriptorInfo.file, imageIndex, opts); err != nil { return err } - switch typedDescriptor.GetType() { - case descriptorpb.FieldDescriptorProto_TYPE_ENUM, - descriptorpb.FieldDescriptorProto_TYPE_MESSAGE, - descriptorpb.FieldDescriptorProto_TYPE_GROUP: - typeName := strings.TrimPrefix(typedDescriptor.GetTypeName(), ".") - typeDescriptor, ok := imageIndex.ByName[typeName] - if !ok { - return fmt.Errorf("missing %q", typeName) - } - err := t.addElement(typeDescriptor, descriptorInfo.file, imageIndex, opts) - if err != nil { - return err - } - case descriptorpb.FieldDescriptorProto_TYPE_DOUBLE, - descriptorpb.FieldDescriptorProto_TYPE_FLOAT, - descriptorpb.FieldDescriptorProto_TYPE_INT64, - descriptorpb.FieldDescriptorProto_TYPE_UINT64, - descriptorpb.FieldDescriptorProto_TYPE_INT32, - descriptorpb.FieldDescriptorProto_TYPE_FIXED64, - descriptorpb.FieldDescriptorProto_TYPE_FIXED32, - descriptorpb.FieldDescriptorProto_TYPE_BOOL, - descriptorpb.FieldDescriptorProto_TYPE_STRING, - descriptorpb.FieldDescriptorProto_TYPE_BYTES, - descriptorpb.FieldDescriptorProto_TYPE_UINT32, - descriptorpb.FieldDescriptorProto_TYPE_SFIXED32, - descriptorpb.FieldDescriptorProto_TYPE_SFIXED64, - descriptorpb.FieldDescriptorProto_TYPE_SINT32, - descriptorpb.FieldDescriptorProto_TYPE_SINT64: - // nothing to follow, custom options handled below. - default: - return fmt.Errorf("unknown field type %d", typedDescriptor.GetType()) - } - if err := t.exploreCustomOptions(typedDescriptor, descriptorInfo.file, imageIndex, opts); err != nil { + default: + return errorUnsupportedFilterType(descriptor, descriptorInfo.fullName) + } + + return nil +} + +func errorUnsupportedFilterType(descriptor namedDescriptor, fullName string) error { + var descriptorType string + switch d := descriptor.(type) { + case *descriptorpb.FileDescriptorProto: + descriptorType = "file" + case *descriptorpb.DescriptorProto: + descriptorType = "message" + case *descriptorpb.FieldDescriptorProto: + if d.Extendee != nil { + descriptorType = "extension field" + } else { + descriptorType = "non-extension field" + } + case *descriptorpb.OneofDescriptorProto: + descriptorType = "oneof" + case *descriptorpb.EnumDescriptorProto: + descriptorType = "enum" + case *descriptorpb.EnumValueDescriptorProto: + descriptorType = "enum value" + case *descriptorpb.ServiceDescriptorProto: + descriptorType = "service" + case *descriptorpb.MethodDescriptorProto: + descriptorType = "method" + default: + descriptorType = fmt.Sprintf("%T", d) + } + return fmt.Errorf("%s is unsupported filter type: %s", fullName, descriptorType) +} + +func (t *transitiveClosure) addEnclosing(descriptor namedDescriptor, enclosingFile string, imageIndex *imageIndex, opts *imageFilterOptions) error { + // loop through all enclosing parents since nesting level + // could be arbitrarily deep + for descriptor != nil { + _, isMsg := descriptor.(*descriptorpb.DescriptorProto) + _, isSvc := descriptor.(*descriptorpb.ServiceDescriptorProto) + if !isMsg && !isSvc { + break // not an enclosing type + } + if _, ok := t.elements[descriptor]; ok { + break // already in closure + } + t.elements[descriptor] = inclusionModeEnclosing + if err := t.exploreCustomOptions(descriptor, enclosingFile, imageIndex, opts); err != nil { + return err + } + // now move into this element's parent + descriptor = imageIndex.ByDescriptor[descriptor].parent + } + return nil +} + +func (t *transitiveClosure) addFieldType(field *descriptorpb.FieldDescriptorProto, referrerFile string, imageIndex *imageIndex, opts *imageFilterOptions) error { + switch field.GetType() { + case descriptorpb.FieldDescriptorProto_TYPE_ENUM, + descriptorpb.FieldDescriptorProto_TYPE_MESSAGE, + descriptorpb.FieldDescriptorProto_TYPE_GROUP: + typeName := strings.TrimPrefix(field.GetTypeName(), ".") + typeDescriptor, ok := imageIndex.ByName[typeName] + if !ok { + return fmt.Errorf("missing %q", typeName) + } + err := t.addElement(typeDescriptor, referrerFile, false, imageIndex, opts) + if err != nil { return err } + case descriptorpb.FieldDescriptorProto_TYPE_DOUBLE, + descriptorpb.FieldDescriptorProto_TYPE_FLOAT, + descriptorpb.FieldDescriptorProto_TYPE_INT64, + descriptorpb.FieldDescriptorProto_TYPE_UINT64, + descriptorpb.FieldDescriptorProto_TYPE_INT32, + descriptorpb.FieldDescriptorProto_TYPE_FIXED64, + descriptorpb.FieldDescriptorProto_TYPE_FIXED32, + descriptorpb.FieldDescriptorProto_TYPE_BOOL, + descriptorpb.FieldDescriptorProto_TYPE_STRING, + descriptorpb.FieldDescriptorProto_TYPE_BYTES, + descriptorpb.FieldDescriptorProto_TYPE_UINT32, + descriptorpb.FieldDescriptorProto_TYPE_SFIXED32, + descriptorpb.FieldDescriptorProto_TYPE_SFIXED64, + descriptorpb.FieldDescriptorProto_TYPE_SINT32, + descriptorpb.FieldDescriptorProto_TYPE_SINT64: + // nothing to follow, custom options handled below. default: - return fmt.Errorf("unexpected protosource type %T", typedDescriptor) + return fmt.Errorf("unknown field type %d", field.GetType()) } + return nil +} +func (t *transitiveClosure) addExtensions( + imageIndex *imageIndex, + opts *imageFilterOptions, +) error { + if !opts.includeKnownExtensions { + return nil // nothing to do + } + for e, mode := range t.elements { + if mode != inclusionModeExplicit { + // we only collect extensions for messages that are directly reachable/referenced. + continue + } + msgDescriptor, ok := e.(*descriptorpb.DescriptorProto) + if !ok { + // not a message, nothing to do + continue + } + descriptorInfo := imageIndex.ByDescriptor[msgDescriptor] + for _, extendsDescriptor := range imageIndex.NameToExtensions[descriptorInfo.fullName] { + if err := t.addElement(extendsDescriptor, "", false, imageIndex, opts); err != nil { + return err + } + } + } return nil } @@ -636,7 +648,7 @@ func (t *transitiveClosure) exploreCustomOptions( err = fmt.Errorf("cannot find ext no %d on %s", fd.Number(), optionsName) return false } - err = t.addElement(field, referrerFile, imageIndex, opts) + err = t.addElement(field, referrerFile, true, imageIndex, opts) return err == nil }) return err diff --git a/private/bufpkg/bufimage/bufimageutil/bufimageutil_test.go b/private/bufpkg/bufimage/bufimageutil/bufimageutil_test.go index 4f0a071791..2d5f7cac38 100644 --- a/private/bufpkg/bufimage/bufimageutil/bufimageutil_test.go +++ b/private/bufpkg/bufimage/bufimageutil/bufimageutil_test.go @@ -62,6 +62,12 @@ func TestOptions(t *testing.T) { t.Run("exclude-options", func(t *testing.T) { runDiffTest(t, "testdata/options", []string{"pkg.Foo", "pkg.FooEnum", "pkg.FooService"}, "all-exclude-options.txtar", WithExcludeCustomOptions()) }) + t.Run("files", func(t *testing.T) { + runDiffTest(t, "testdata/options", []string{"Files"}, "Files.txtar") + }) + t.Run("all-with-files", func(t *testing.T) { + runDiffTest(t, "testdata/options", []string{"pkg.Foo", "pkg.FooEnum", "pkg.FooService", "Files"}, "all-with-Files.txtar") + }) } func TestNesting(t *testing.T) { diff --git a/private/bufpkg/bufimage/bufimageutil/testdata/nesting/recursenested.txtar b/private/bufpkg/bufimage/bufimageutil/testdata/nesting/recursenested.txtar index 50f1fb5bcb..4fe6961c8b 100644 --- a/private/bufpkg/bufimage/bufimageutil/testdata/nesting/recursenested.txtar +++ b/private/bufpkg/bufimage/bufimageutil/testdata/nesting/recursenested.txtar @@ -2,10 +2,7 @@ syntax = "proto3"; package pkg; message Foo { - string x = 1; - NestedFoo nested_foo = 2; message NestedFoo { - string nested_x = 1; message NestedNestedFoo { string nested_nested_x = 1; } diff --git a/private/bufpkg/bufimage/bufimageutil/testdata/nesting/usingother.txtar b/private/bufpkg/bufimage/bufimageutil/testdata/nesting/usingother.txtar index 6f6fe75964..83eada7dac 100644 --- a/private/bufpkg/bufimage/bufimageutil/testdata/nesting/usingother.txtar +++ b/private/bufpkg/bufimage/bufimageutil/testdata/nesting/usingother.txtar @@ -2,8 +2,6 @@ syntax = "proto3"; package pkg; message Bar { - FooEnum foo_enum = 1; - Foo.NestedFoo nested_foo = 2; enum NestedBarEnum { NESTED_BAR_ENUM_X = 0; NESTED_BAR_ENUM_Y = 1; @@ -12,14 +10,3 @@ message Bar { message Baz { Bar.NestedBarEnum nested_bar_enum = 1; } -message Foo { - string x = 1; - NestedFoo nested_foo = 2; - message NestedFoo { - string nested_x = 1; - } -} -enum FooEnum { - FOO_ENUM_X = 0; - FOO_ENUM_Y = 1; -} diff --git a/private/bufpkg/bufimage/bufimageutil/testdata/options/Files.txtar b/private/bufpkg/bufimage/bufimageutil/testdata/options/Files.txtar new file mode 100644 index 0000000000..ef2a3614a3 --- /dev/null +++ b/private/bufpkg/bufimage/bufimageutil/testdata/options/Files.txtar @@ -0,0 +1,291 @@ +-- google/protobuf/descriptor.proto -- +syntax = "proto2"; +package google.protobuf; +option cc_enable_arenas = true; +option csharp_namespace = "Google.Protobuf.Reflection"; +option go_package = "google.golang.org/protobuf/types/descriptorpb"; +option java_outer_classname = "DescriptorProtos"; +option java_package = "com.google.protobuf"; +option objc_class_prefix = "GPB"; +option optimize_for = SPEED; +message DescriptorProto { + optional string name = 1; + repeated FieldDescriptorProto field = 2; + repeated DescriptorProto nested_type = 3; + repeated EnumDescriptorProto enum_type = 4; + repeated ExtensionRange extension_range = 5; + repeated FieldDescriptorProto extension = 6; + optional MessageOptions options = 7; + repeated OneofDescriptorProto oneof_decl = 8; + repeated ReservedRange reserved_range = 9; + repeated string reserved_name = 10; + message ExtensionRange { + optional int32 start = 1; + optional int32 end = 2; + optional ExtensionRangeOptions options = 3; + } + message ReservedRange { + optional int32 start = 1; + optional int32 end = 2; + } +} +message EnumDescriptorProto { + optional string name = 1; + repeated EnumValueDescriptorProto value = 2; + optional EnumOptions options = 3; + repeated EnumReservedRange reserved_range = 4; + repeated string reserved_name = 5; + message EnumReservedRange { + optional int32 start = 1; + optional int32 end = 2; + } +} +message EnumOptions { + optional bool allow_alias = 2; + optional bool deprecated = 3 [default = false]; + repeated UninterpretedOption uninterpreted_option = 999; + extensions 1000 to max; + reserved 5; +} +message EnumValueDescriptorProto { + optional string name = 1; + optional int32 number = 2; + optional EnumValueOptions options = 3; +} +message EnumValueOptions { + optional bool deprecated = 1 [default = false]; + repeated UninterpretedOption uninterpreted_option = 999; + extensions 1000 to max; +} +message ExtensionRangeOptions { + repeated UninterpretedOption uninterpreted_option = 999; + extensions 1000 to max; +} +message FieldDescriptorProto { + optional string name = 1; + optional string extendee = 2; + optional int32 number = 3; + optional Label label = 4; + optional Type type = 5; + optional string type_name = 6; + optional string default_value = 7; + optional FieldOptions options = 8; + optional int32 oneof_index = 9; + optional string json_name = 10; + optional bool proto3_optional = 17; + enum Label { + LABEL_OPTIONAL = 1; + LABEL_REQUIRED = 2; + LABEL_REPEATED = 3; + } + enum Type { + 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; + } +} +message FieldOptions { + optional CType ctype = 1 [default = STRING]; + optional bool packed = 2; + optional bool deprecated = 3 [default = false]; + optional bool lazy = 5 [default = false]; + optional JSType jstype = 6 [default = JS_NORMAL]; + optional bool weak = 10 [default = false]; + optional bool unverified_lazy = 15 [default = false]; + repeated UninterpretedOption uninterpreted_option = 999; + enum CType { + STRING = 0; + CORD = 1; + STRING_PIECE = 2; + } + enum JSType { + JS_NORMAL = 0; + JS_STRING = 1; + JS_NUMBER = 2; + } + extensions 1000 to max; + reserved 4; +} +message FileDescriptorProto { + optional string name = 1; + optional string package = 2; + repeated string dependency = 3; + repeated DescriptorProto message_type = 4; + repeated EnumDescriptorProto enum_type = 5; + repeated ServiceDescriptorProto service = 6; + repeated FieldDescriptorProto extension = 7; + optional FileOptions options = 8; + optional SourceCodeInfo source_code_info = 9; + repeated int32 public_dependency = 10; + repeated int32 weak_dependency = 11; + optional string syntax = 12; +} +message FileDescriptorSet { + repeated FileDescriptorProto file = 1; +} +message FileOptions { + optional string java_package = 1; + optional string java_outer_classname = 8; + optional OptimizeMode optimize_for = 9 [default = SPEED]; + optional bool java_multiple_files = 10 [default = false]; + optional string go_package = 11; + optional bool cc_generic_services = 16 [default = false]; + optional bool java_generic_services = 17 [default = false]; + optional bool py_generic_services = 18 [default = false]; + optional bool java_generate_equals_and_hash = 20 [deprecated = true]; + optional bool deprecated = 23 [default = false]; + optional bool java_string_check_utf8 = 27 [default = false]; + optional bool cc_enable_arenas = 31 [default = true]; + optional string objc_class_prefix = 36; + optional string csharp_namespace = 37; + optional string swift_prefix = 39; + optional string php_class_prefix = 40; + optional string php_namespace = 41; + optional bool php_generic_services = 42 [default = false]; + optional string php_metadata_namespace = 44; + optional string ruby_package = 45; + repeated UninterpretedOption uninterpreted_option = 999; + enum OptimizeMode { + SPEED = 1; + CODE_SIZE = 2; + LITE_RUNTIME = 3; + } + extensions 1000 to max; + reserved 38; +} +message MessageOptions { + optional bool message_set_wire_format = 1 [default = false]; + optional bool no_standard_descriptor_accessor = 2 [default = false]; + optional bool deprecated = 3 [default = false]; + optional bool map_entry = 7; + repeated UninterpretedOption uninterpreted_option = 999; + extensions 1000 to max; + reserved 4, 5, 6, 8, 9; +} +message MethodDescriptorProto { + optional string name = 1; + optional string input_type = 2; + optional string output_type = 3; + optional MethodOptions options = 4; + optional bool client_streaming = 5 [default = false]; + optional bool server_streaming = 6 [default = false]; +} +message MethodOptions { + optional bool deprecated = 33 [default = false]; + optional IdempotencyLevel idempotency_level = 34 [default = IDEMPOTENCY_UNKNOWN]; + repeated UninterpretedOption uninterpreted_option = 999; + enum IdempotencyLevel { + IDEMPOTENCY_UNKNOWN = 0; + NO_SIDE_EFFECTS = 1; + IDEMPOTENT = 2; + } + extensions 1000 to max; +} +message OneofDescriptorProto { + optional string name = 1; + optional OneofOptions options = 2; +} +message OneofOptions { + repeated UninterpretedOption uninterpreted_option = 999; + extensions 1000 to max; +} +message ServiceDescriptorProto { + optional string name = 1; + repeated MethodDescriptorProto method = 2; + optional ServiceOptions options = 3; +} +message ServiceOptions { + optional bool deprecated = 33 [default = false]; + repeated UninterpretedOption uninterpreted_option = 999; + extensions 1000 to max; +} +message SourceCodeInfo { + repeated Location location = 1; + message Location { + repeated int32 path = 1 [packed = true]; + repeated int32 span = 2 [packed = true]; + optional string leading_comments = 3; + optional string trailing_comments = 4; + repeated string leading_detached_comments = 6; + } +} +message UninterpretedOption { + repeated NamePart name = 2; + optional string identifier_value = 3; + optional uint64 positive_int_value = 4; + optional int64 negative_int_value = 5; + optional double double_value = 6; + optional bytes string_value = 7; + optional string aggregate_value = 8; + message NamePart { + required string name_part = 1; + required bool is_extension = 2; + } +} +-- options.proto -- +syntax = "proto3"; +import "google/protobuf/descriptor.proto"; +message Files { + google.protobuf.FileDescriptorSet files = 1; +} +message UnusedOption { + string foo = 1; +} +message UsedOption { + string foo = 1; + extend google.protobuf.FileOptions { + optional UsedOption file_foo = 50000; + optional UnusedOption file_bar = 50001; + optional string file_baz = 50002; + } +} +extend google.protobuf.EnumOptions { + optional UsedOption enum_foo = 50000; + optional UnusedOption enum_bar = 50001; + optional string enum_baz = 50002; +} +extend google.protobuf.EnumValueOptions { + optional UsedOption enum_value_foo = 50000; + optional UnusedOption enum_value_bar = 50001; + optional string enum_value_baz = 50002; +} +extend google.protobuf.FieldOptions { + optional UsedOption field_foo = 50000; + optional UnusedOption field_bar = 50001; + optional string field_baz = 50002; +} +extend google.protobuf.MessageOptions { + optional UsedOption message_foo = 50000; + optional UnusedOption message_bar = 50001; + optional string message_baz = 50002; +} +extend google.protobuf.MethodOptions { + optional UsedOption method_foo = 50000; + optional UnusedOption method_bar = 50001; + optional string method_baz = 50002; +} +extend google.protobuf.OneofOptions { + optional UsedOption oneof_foo = 50000; + optional UnusedOption oneof_bar = 50001; + optional string oneof_baz = 50002; +} +extend google.protobuf.ServiceOptions { + optional UsedOption service_foo = 50000; + optional UnusedOption service_bar = 50001; + optional string service_baz = 50002; +} diff --git a/private/bufpkg/bufimage/bufimageutil/testdata/options/a.proto b/private/bufpkg/bufimage/bufimageutil/testdata/options/a.proto index 596948b47c..d731f15faa 100644 --- a/private/bufpkg/bufimage/bufimageutil/testdata/options/a.proto +++ b/private/bufpkg/bufimage/bufimageutil/testdata/options/a.proto @@ -48,6 +48,11 @@ service FooService { option (method_foo).foo = "str"; option (method_baz) = "str"; }; + + rpc DoNot(Empty) returns (Empty) { + option (method_foo).foo = "str"; + option (method_baz) = "str"; + }; } extend Foo { diff --git a/private/bufpkg/bufimage/bufimageutil/testdata/options/all-exclude-options.txtar b/private/bufpkg/bufimage/bufimageutil/testdata/options/all-exclude-options.txtar index c90d82e9a1..052623406f 100644 --- a/private/bufpkg/bufimage/bufimageutil/testdata/options/all-exclude-options.txtar +++ b/private/bufpkg/bufimage/bufimageutil/testdata/options/all-exclude-options.txtar @@ -18,6 +18,7 @@ enum FooEnum { } service FooService { rpc Do ( Empty ) returns ( Empty ); + rpc DoNot ( Empty ) returns ( Empty ); } extend Foo { optional string extension = 11; diff --git a/private/bufpkg/bufimage/bufimageutil/testdata/options/all-with-Files.txtar b/private/bufpkg/bufimage/bufimageutil/testdata/options/all-with-Files.txtar new file mode 100644 index 0000000000..9335278450 --- /dev/null +++ b/private/bufpkg/bufimage/bufimageutil/testdata/options/all-with-Files.txtar @@ -0,0 +1,340 @@ +-- a.proto -- +syntax = "proto2"; +package pkg; +import "options.proto"; +option (UsedOption.file_baz) = "str"; +option (UsedOption.file_foo) = { foo:"str" }; +message Empty { +} +message Foo { + option (message_baz) = "str"; + option (message_foo) = { foo:"str" }; + optional string foo = 1 [ + jstype = JS_STRING, + (field_baz) = "str", + (field_foo) = { foo:"str" } + ]; + oneof testOneof { + option (oneof_baz) = "str"; + option (oneof_foo) = { foo:"str" }; + string bar = 2; + bytes baz = 3; + } + extensions 10 to max; +} +enum FooEnum { + option deprecated = true; + option (enum_baz) = "str"; + option (enum_foo) = { foo:"str" }; + FOO_ENUM_X = 0; + FOO_ENUM_Y = 1 [ + (enum_value_baz) = "str", + (enum_value_foo) = { foo:"str" } + ]; +} +service FooService { + option (service_baz) = "str"; + option (service_foo) = { foo:"str" }; + rpc Do ( Empty ) returns ( Empty ) { + option (method_baz) = "str"; + option (method_foo) = { foo:"str" }; + } + rpc DoNot ( Empty ) returns ( Empty ) { + option (method_baz) = "str"; + option (method_foo) = { foo:"str" }; + } +} +extend Foo { + optional string extension = 11 [(field_baz) = "str", (field_foo) = { foo:"str" }]; +} +-- google/protobuf/descriptor.proto -- +syntax = "proto2"; +package google.protobuf; +option cc_enable_arenas = true; +option csharp_namespace = "Google.Protobuf.Reflection"; +option go_package = "google.golang.org/protobuf/types/descriptorpb"; +option java_outer_classname = "DescriptorProtos"; +option java_package = "com.google.protobuf"; +option objc_class_prefix = "GPB"; +option optimize_for = SPEED; +message DescriptorProto { + optional string name = 1; + repeated FieldDescriptorProto field = 2; + repeated DescriptorProto nested_type = 3; + repeated EnumDescriptorProto enum_type = 4; + repeated ExtensionRange extension_range = 5; + repeated FieldDescriptorProto extension = 6; + optional MessageOptions options = 7; + repeated OneofDescriptorProto oneof_decl = 8; + repeated ReservedRange reserved_range = 9; + repeated string reserved_name = 10; + message ExtensionRange { + optional int32 start = 1; + optional int32 end = 2; + optional ExtensionRangeOptions options = 3; + } + message ReservedRange { + optional int32 start = 1; + optional int32 end = 2; + } +} +message EnumDescriptorProto { + optional string name = 1; + repeated EnumValueDescriptorProto value = 2; + optional EnumOptions options = 3; + repeated EnumReservedRange reserved_range = 4; + repeated string reserved_name = 5; + message EnumReservedRange { + optional int32 start = 1; + optional int32 end = 2; + } +} +message EnumOptions { + optional bool allow_alias = 2; + optional bool deprecated = 3 [default = false]; + repeated UninterpretedOption uninterpreted_option = 999; + extensions 1000 to max; + reserved 5; +} +message EnumValueDescriptorProto { + optional string name = 1; + optional int32 number = 2; + optional EnumValueOptions options = 3; +} +message EnumValueOptions { + optional bool deprecated = 1 [default = false]; + repeated UninterpretedOption uninterpreted_option = 999; + extensions 1000 to max; +} +message ExtensionRangeOptions { + repeated UninterpretedOption uninterpreted_option = 999; + extensions 1000 to max; +} +message FieldDescriptorProto { + optional string name = 1; + optional string extendee = 2; + optional int32 number = 3; + optional Label label = 4; + optional Type type = 5; + optional string type_name = 6; + optional string default_value = 7; + optional FieldOptions options = 8; + optional int32 oneof_index = 9; + optional string json_name = 10; + optional bool proto3_optional = 17; + enum Label { + LABEL_OPTIONAL = 1; + LABEL_REQUIRED = 2; + LABEL_REPEATED = 3; + } + enum Type { + 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; + } +} +message FieldOptions { + optional CType ctype = 1 [default = STRING]; + optional bool packed = 2; + optional bool deprecated = 3 [default = false]; + optional bool lazy = 5 [default = false]; + optional JSType jstype = 6 [default = JS_NORMAL]; + optional bool weak = 10 [default = false]; + optional bool unverified_lazy = 15 [default = false]; + repeated UninterpretedOption uninterpreted_option = 999; + enum CType { + STRING = 0; + CORD = 1; + STRING_PIECE = 2; + } + enum JSType { + JS_NORMAL = 0; + JS_STRING = 1; + JS_NUMBER = 2; + } + extensions 1000 to max; + reserved 4; +} +message FileDescriptorProto { + optional string name = 1; + optional string package = 2; + repeated string dependency = 3; + repeated DescriptorProto message_type = 4; + repeated EnumDescriptorProto enum_type = 5; + repeated ServiceDescriptorProto service = 6; + repeated FieldDescriptorProto extension = 7; + optional FileOptions options = 8; + optional SourceCodeInfo source_code_info = 9; + repeated int32 public_dependency = 10; + repeated int32 weak_dependency = 11; + optional string syntax = 12; +} +message FileDescriptorSet { + repeated FileDescriptorProto file = 1; +} +message FileOptions { + optional string java_package = 1; + optional string java_outer_classname = 8; + optional OptimizeMode optimize_for = 9 [default = SPEED]; + optional bool java_multiple_files = 10 [default = false]; + optional string go_package = 11; + optional bool cc_generic_services = 16 [default = false]; + optional bool java_generic_services = 17 [default = false]; + optional bool py_generic_services = 18 [default = false]; + optional bool java_generate_equals_and_hash = 20 [deprecated = true]; + optional bool deprecated = 23 [default = false]; + optional bool java_string_check_utf8 = 27 [default = false]; + optional bool cc_enable_arenas = 31 [default = true]; + optional string objc_class_prefix = 36; + optional string csharp_namespace = 37; + optional string swift_prefix = 39; + optional string php_class_prefix = 40; + optional string php_namespace = 41; + optional bool php_generic_services = 42 [default = false]; + optional string php_metadata_namespace = 44; + optional string ruby_package = 45; + repeated UninterpretedOption uninterpreted_option = 999; + enum OptimizeMode { + SPEED = 1; + CODE_SIZE = 2; + LITE_RUNTIME = 3; + } + extensions 1000 to max; + reserved 38; +} +message MessageOptions { + optional bool message_set_wire_format = 1 [default = false]; + optional bool no_standard_descriptor_accessor = 2 [default = false]; + optional bool deprecated = 3 [default = false]; + optional bool map_entry = 7; + repeated UninterpretedOption uninterpreted_option = 999; + extensions 1000 to max; + reserved 4, 5, 6, 8, 9; +} +message MethodDescriptorProto { + optional string name = 1; + optional string input_type = 2; + optional string output_type = 3; + optional MethodOptions options = 4; + optional bool client_streaming = 5 [default = false]; + optional bool server_streaming = 6 [default = false]; +} +message MethodOptions { + optional bool deprecated = 33 [default = false]; + optional IdempotencyLevel idempotency_level = 34 [default = IDEMPOTENCY_UNKNOWN]; + repeated UninterpretedOption uninterpreted_option = 999; + enum IdempotencyLevel { + IDEMPOTENCY_UNKNOWN = 0; + NO_SIDE_EFFECTS = 1; + IDEMPOTENT = 2; + } + extensions 1000 to max; +} +message OneofDescriptorProto { + optional string name = 1; + optional OneofOptions options = 2; +} +message OneofOptions { + repeated UninterpretedOption uninterpreted_option = 999; + extensions 1000 to max; +} +message ServiceDescriptorProto { + optional string name = 1; + repeated MethodDescriptorProto method = 2; + optional ServiceOptions options = 3; +} +message ServiceOptions { + optional bool deprecated = 33 [default = false]; + repeated UninterpretedOption uninterpreted_option = 999; + extensions 1000 to max; +} +message SourceCodeInfo { + repeated Location location = 1; + message Location { + repeated int32 path = 1 [packed = true]; + repeated int32 span = 2 [packed = true]; + optional string leading_comments = 3; + optional string trailing_comments = 4; + repeated string leading_detached_comments = 6; + } +} +message UninterpretedOption { + repeated NamePart name = 2; + optional string identifier_value = 3; + optional uint64 positive_int_value = 4; + optional int64 negative_int_value = 5; + optional double double_value = 6; + optional bytes string_value = 7; + optional string aggregate_value = 8; + message NamePart { + required string name_part = 1; + required bool is_extension = 2; + } +} +-- options.proto -- +syntax = "proto3"; +import "google/protobuf/descriptor.proto"; +message Files { + google.protobuf.FileDescriptorSet files = 1; +} +message UnusedOption { + string foo = 1; +} +message UsedOption { + string foo = 1; + extend google.protobuf.FileOptions { + optional UsedOption file_foo = 50000; + optional UnusedOption file_bar = 50001; + optional string file_baz = 50002; + } +} +extend google.protobuf.EnumOptions { + optional UsedOption enum_foo = 50000; + optional UnusedOption enum_bar = 50001; + optional string enum_baz = 50002; +} +extend google.protobuf.EnumValueOptions { + optional UsedOption enum_value_foo = 50000; + optional UnusedOption enum_value_bar = 50001; + optional string enum_value_baz = 50002; +} +extend google.protobuf.FieldOptions { + optional UsedOption field_foo = 50000; + optional UnusedOption field_bar = 50001; + optional string field_baz = 50002; +} +extend google.protobuf.MessageOptions { + optional UsedOption message_foo = 50000; + optional UnusedOption message_bar = 50001; + optional string message_baz = 50002; +} +extend google.protobuf.MethodOptions { + optional UsedOption method_foo = 50000; + optional UnusedOption method_bar = 50001; + optional string method_baz = 50002; +} +extend google.protobuf.OneofOptions { + optional UsedOption oneof_foo = 50000; + optional UnusedOption oneof_bar = 50001; + optional string oneof_baz = 50002; +} +extend google.protobuf.ServiceOptions { + optional UsedOption service_foo = 50000; + optional UnusedOption service_bar = 50001; + optional string service_baz = 50002; +} diff --git a/private/bufpkg/bufimage/bufimageutil/testdata/options/all.txtar b/private/bufpkg/bufimage/bufimageutil/testdata/options/all.txtar index 4f99d691a3..34f74d17a6 100644 --- a/private/bufpkg/bufimage/bufimageutil/testdata/options/all.txtar +++ b/private/bufpkg/bufimage/bufimageutil/testdata/options/all.txtar @@ -39,6 +39,10 @@ service FooService { option (method_baz) = "str"; option (method_foo) = { foo:"str" }; } + rpc DoNot ( Empty ) returns ( Empty ) { + option (method_baz) = "str"; + option (method_foo) = { foo:"str" }; + } } extend Foo { optional string extension = 11 [(field_baz) = "str", (field_foo) = { foo:"str" }]; diff --git a/private/bufpkg/bufimage/bufimageutil/testdata/options/options.proto b/private/bufpkg/bufimage/bufimageutil/testdata/options/options.proto index 25d96f8852..57676254cb 100644 --- a/private/bufpkg/bufimage/bufimageutil/testdata/options/options.proto +++ b/private/bufpkg/bufimage/bufimageutil/testdata/options/options.proto @@ -50,3 +50,7 @@ extend google.protobuf.MethodOptions { optional UnusedOption method_bar = 50001; optional string method_baz = 50002; } + +message Files { + google.protobuf.FileDescriptorSet files = 1; +} diff --git a/private/bufpkg/bufimage/bufimageutil/testdata/options/pkg.FooService.txtar b/private/bufpkg/bufimage/bufimageutil/testdata/options/pkg.FooService.txtar index 4f6c8675af..616441e671 100644 --- a/private/bufpkg/bufimage/bufimageutil/testdata/options/pkg.FooService.txtar +++ b/private/bufpkg/bufimage/bufimageutil/testdata/options/pkg.FooService.txtar @@ -13,6 +13,10 @@ service FooService { option (method_baz) = "str"; option (method_foo) = { foo:"str" }; } + rpc DoNot ( Empty ) returns ( Empty ) { + option (method_baz) = "str"; + option (method_foo) = { foo:"str" }; + } } -- google/protobuf/descriptor.proto -- syntax = "proto2";