diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f35aac5a..0206cf2f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ CollectionAdapter for (#1509) - Introduced new generator parameter `scala3_sources` (also available via scalapb.proto) to generate sources that are compatible with Scala 3 (#1576) +- aux_*_options now accept "*" in the target field to match all entities in + scope. ## [0.11.13] - Added input and output message type for method descriptor (#1503) diff --git a/compiler-plugin/src/main/scala/scalapb/compiler/DescriptorImplicits.scala b/compiler-plugin/src/main/scala/scalapb/compiler/DescriptorImplicits.scala index cc8eca64b..3bd9357c5 100644 --- a/compiler-plugin/src/main/scala/scalapb/compiler/DescriptorImplicits.scala +++ b/compiler-plugin/src/main/scala/scalapb/compiler/DescriptorImplicits.scala @@ -330,7 +330,7 @@ class DescriptorImplicits private[compiler] ( (fd.getFile.scalaOptions.getAuxFieldOptionsList.asScala .collect { - case opt if opt.getTarget == fd.getFullName() => opt.getOptions + case opt if Helper.targetMatches(opt.getTarget(), fd.getFullName()) => opt.getOptions } :+ localOptions).reduce[FieldOptions]((left, right) => left.toBuilder.mergeFrom(right).build() ) @@ -532,9 +532,9 @@ class DescriptorImplicits private[compiler] ( val localOptions = message.getOptions.getExtension[MessageOptions](Scalapb.message) message.getFile.scalaOptions.getAuxMessageOptionsList.asScala - .find(_.getTarget == message.getFullName()) - .fold(localOptions)(aux => - MessageOptions.newBuilder(aux.getOptions).mergeFrom(localOptions).build + .filter(o => Helper.targetMatches(o.getTarget, message.getFullName())) + .foldLeft(localOptions)((local, aux) => + MessageOptions.newBuilder(aux.getOptions).mergeFrom(local).build ) } @@ -772,9 +772,9 @@ class DescriptorImplicits private[compiler] ( val localOptions = enumDescriptor.getOptions.getExtension[EnumOptions](Scalapb.enumOptions) enumDescriptor.getFile.scalaOptions.getAuxEnumOptionsList.asScala - .find(_.getTarget == enumDescriptor.getFullName()) - .fold(localOptions)(aux => - EnumOptions.newBuilder(aux.getOptions).mergeFrom(localOptions).build + .filter(o => Helper.targetMatches(o.getTarget, enumDescriptor.getFullName())) + .foldLeft(localOptions)((local, aux) => + EnumOptions.newBuilder(aux.getOptions).mergeFrom(local).build ) } @@ -862,9 +862,9 @@ class DescriptorImplicits private[compiler] ( val localOptions = enumValue.getOptions.getExtension[EnumValueOptions](Scalapb.enumValue) enumValue.getFile.scalaOptions.getAuxEnumValueOptionsList.asScala - .find(_.getTarget == enumValue.getFullName()) - .fold(localOptions)(aux => - EnumValueOptions.newBuilder(aux.getOptions).mergeFrom(localOptions).build + .filter(o => Helper.targetMatches(o.getTarget, enumValue.getFullName())) + .foldLeft(localOptions)((local, aux) => + EnumValueOptions.newBuilder(aux.getOptions).mergeFrom(local).build ) } @@ -1239,4 +1239,11 @@ object Helper { .replace(">", ">") .replace("\\", "&92;") } + + // Does the pattern matches the name. If the pattern is "*" the name always matches, otherwise + // must be an exact match. This may evolve in the future. + def targetMatches(pattern: String, name: String): Boolean = pattern match { + case "*" => true + case o => name == o + } } diff --git a/compiler-plugin/src/test/scala/scalapb/compiler/DescriptorImplicitsSpec.scala b/compiler-plugin/src/test/scala/scalapb/compiler/DescriptorImplicitsSpec.scala index 075e5b193..119e2a72e 100644 --- a/compiler-plugin/src/test/scala/scalapb/compiler/DescriptorImplicitsSpec.scala +++ b/compiler-plugin/src/test/scala/scalapb/compiler/DescriptorImplicitsSpec.scala @@ -122,6 +122,25 @@ class DescriptorImplicitsSpec extends AnyFlatSpec with Matchers with ProtocInvoc .find(_.getFullName() == "inside_disable_flat.proto") .get .disableOutput must be(false) + } + + "Helpers.targetMatches" should "do exact match when not *" in { + Helper.targetMatches("foo", "foo") must be(true) + Helper.targetMatches("foo", "") must be(false) + Helper.targetMatches("foo", "bar") must be(false) + Helper.targetMatches("", "") must be(true) + Helper.targetMatches("", "foo") must be(false) + } + + "Helpers.targetMatches" should "return true when *" in { + Helper.targetMatches("*", "foo") must be(true) + Helper.targetMatches("*", "bar") must be(true) + Helper.targetMatches("*", "") must be(true) + Helper.targetMatches("*", "*") must be(true) + } + "Helpers.targetMatches" should "not support * as a generic wildcard" in { + Helper.targetMatches("foo.*", "foo.bar") must be(false) + Helper.targetMatches("foo.*.bar", "bar.giz.bar") must be(false) } } diff --git a/docs/src/main/markdown/customizations.md b/docs/src/main/markdown/customizations.md index 0855ce7cb..6f5977a5c 100644 --- a/docs/src/main/markdown/customizations.md +++ b/docs/src/main/markdown/customizations.md @@ -281,7 +281,8 @@ option (scalapb.options) = { }; ``` -The list `aux_message_options` contains options targeted at different messages define under the same proto package of the package-scoped options. The `target` name needs to be fully-qualified message name in the protobuf namespace. Similar to `aux_message_options`, we also have `aux_enum_options`, `aux_enum_value_options` and `aux_field_options`. See [example usage here](https://github.com/scalapb/ScalaPB/tree/master/e2e/src/main/protobuf/scoped). +The list `aux_message_options` contains options targeted at different messages define under the same proto package of the package-scoped options. The `target` name needs to be fully-qualified message name in the protobuf namespace. Similar to `aux_message_options`, we also have `aux_enum_options`, `aux_enum_value_options` and `aux_field_options`. See [example usage here](https://github.com/scalapb/ScalaPB/tree/master/e2e/src/main/protobuf/scoped). If the target is set `*` then the options will be +applied to all the entities in the file or package (depending on the `scope` option). ## Primitive wrappers diff --git a/e2e/src/main/protobuf/scoped/wildcard.proto b/e2e/src/main/protobuf/scoped/wildcard.proto new file mode 100644 index 000000000..054032e03 --- /dev/null +++ b/e2e/src/main/protobuf/scoped/wildcard.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package scalapb.e2e.scoped; + +import "scalapb/scalapb.proto"; + +option (scalapb.options) = { + aux_message_options: [ + { + target: "scalapb.e2e.scoped.Wild2", + options: { + extends: "scalapb.e2e.scoped.SomeTrait" + } + }, + { + target: "*" + options: { + extends: "scalapb.e2e.scoped.WildcardTrait" + } + } + ] +}; + +message Wild1 {} + +message Wild2 {} \ No newline at end of file diff --git a/e2e/src/main/scala/scalapb/e2e/scoped/WildcardTrait.scala b/e2e/src/main/scala/scalapb/e2e/scoped/WildcardTrait.scala new file mode 100644 index 000000000..88643b3a9 --- /dev/null +++ b/e2e/src/main/scala/scalapb/e2e/scoped/WildcardTrait.scala @@ -0,0 +1,3 @@ +package scalapb.e2e.scoped + +trait WildcardTrait diff --git a/e2e/src/test/scala/scoped/WildcardSpec.scala b/e2e/src/test/scala/scoped/WildcardSpec.scala new file mode 100644 index 000000000..5ae150bd3 --- /dev/null +++ b/e2e/src/test/scala/scoped/WildcardSpec.scala @@ -0,0 +1,11 @@ +package scalapb.e2e.scoped + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.must.Matchers +import scalapb.changed.scoped._ + +class WildcardSpec extends AnyFlatSpec with Matchers { + assert(Wild1().isInstanceOf[WildcardTrait]) + assert(Wild2().isInstanceOf[WildcardTrait]) + assert(Wild2().isInstanceOf[SomeTrait]) +} diff --git a/protobuf/scalapb/scalapb.proto b/protobuf/scalapb/scalapb.proto index ef5f03ca7..0680c6ff0 100644 --- a/protobuf/scalapb/scalapb.proto +++ b/protobuf/scalapb/scalapb.proto @@ -109,7 +109,8 @@ message ScalaPbOptions { // This is useful when you can't add a dependency on scalapb.proto from the proto file that // defines the message. message AuxMessageOptions { - // The fully-qualified name of the message in the proto name space. + // The fully-qualified name of the message in the proto name space. Set to `*` to apply to all + // messages in scope. optional string target = 1; // Options to apply to the message. If there are any options defined on the target message @@ -121,7 +122,8 @@ message ScalaPbOptions { // This is useful when you can't add a dependency on scalapb.proto from the proto file that // defines the field. message AuxFieldOptions { - // The fully-qualified name of the field in the proto name space. + // The fully-qualified name of the field in the proto name space. Set to `*` to apply to all + // fields in scope. optional string target = 1; // Options to apply to the field. If there are any options defined on the target message @@ -133,7 +135,8 @@ message ScalaPbOptions { // This is useful when you can't add a dependency on scalapb.proto from the proto file that // defines the enum. message AuxEnumOptions { - // The fully-qualified name of the enum in the proto name space. + // The fully-qualified name of the enum in the proto name space. Set to `*` to apply to + // all enums in scope. optional string target = 1; // Options to apply to the enum. If there are any options defined on the target enum @@ -145,7 +148,8 @@ message ScalaPbOptions { // options. This is useful when you can't add a dependency on scalapb.proto from the proto // file that defines the enum. message AuxEnumValueOptions { - // The fully-qualified name of the enum value in the proto name space. + // The fully-qualified name of the enum value in the proto name space. Set to `*` to apply + // to all enum values in scope. optional string target = 1; // Options to apply to the enum value. If there are any options defined on