Skip to content

Commit

Permalink
Provide simple access to descriptor declarations in C#
Browse files Browse the repository at this point in the history
This is primarily for access to comments, which would be expected to be available in a protoc plugin.

The implementation has two fiddly aspects:

- We use a Lazy<T> to avoid building the map before cross-linking. An alternative would be to crosslink at the end of the constructor, and remove the calls to CrossLink elsewhere. This would be generally better IMO, but deviate from the Java code.
- The casts to IReadOnlyList<DescriptorBase> are unfortunate. They'll always work, because these lists are always ReadOnlyCollection<T> for a descriptor type... but we can't use IList<DescriptorBase> as that's not covariant, and it's annoyingly fiddly to change the field to be of type ReadOnlyCollection<T>.
  • Loading branch information
jskeet committed Sep 22, 2018
1 parent a6e1cc7 commit 1711999
Show file tree
Hide file tree
Showing 12 changed files with 755 additions and 35 deletions.
3 changes: 3 additions & 0 deletions csharp/generate_protos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ $PROTOC -Isrc --csharp_out=csharp/src/Google.Protobuf \
# Test protos
$PROTOC -Isrc -Icsharp/protos \
--csharp_out=csharp/src/Google.Protobuf.Test/TestProtos \
--descriptor_set_out=csharp/src/Google.Protobuf.Test/testprotos.pb \
--include_source_info \
--include_imports \
csharp/protos/map_unittest_proto3.proto \
csharp/protos/unittest_issues.proto \
csharp/protos/unittest_custom_options_proto3.proto \
Expand Down
30 changes: 30 additions & 0 deletions csharp/protos/unittest_proto3.proto
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,9 @@ message FooResponse {}
message FooClientMessage {}
message FooServerMessage{}

// This is a test service
service TestService {
// This is a test method
rpc Foo(FooRequest) returns (FooResponse);
rpc Bar(BarRequest) returns (BarResponse);
}
Expand All @@ -378,3 +380,31 @@ message BarRequest {}
message BarResponse {}

message TestEmptyMessage {}

// This is leading detached comment 1

// This is leading detached comment 2

// This is a leading comment
message CommentMessage {
// Leading nested message comment
message NestedCommentMessage {
// Leading nested message field comment
string nested_text = 1;
}

// Leading nested enum comment
enum NestedCommentEnum {
// Zero value comment
ZERO_VALUE = 0;
}

// Leading field comment
string text = 1; // Trailing field comment
}

// Leading enum comment
enum CommentEnum {
// Zero value comment
ZERO_VALUE = 0;
}
4 changes: 4 additions & 0 deletions csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,8 @@
<TargetFrameworks>netcoreapp1.0</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
<EmbeddedResource Include="testprotos.pb" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2018 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion

using Google.Protobuf.Reflection;
using NUnit.Framework;
using System.Linq;
using System.Reflection;

namespace Google.Protobuf.Test.Reflection
{
// In reality this isn't a test for DescriptorDeclaration so much as the way they're loaded.
public class DescriptorDeclarationTest
{
static readonly FileDescriptor unitTestProto3Descriptor = LoadProtos();

[Test]
public void ServiceComments()
{
var service = unitTestProto3Descriptor.FindTypeByName<ServiceDescriptor>("TestService");
Assert.NotNull(service.Declaration);
Assert.AreEqual(" This is a test service\n", service.Declaration.LeadingComments);
}

[Test]
public void MethodComments()
{
var service = unitTestProto3Descriptor.FindTypeByName<ServiceDescriptor>("TestService");
var method = service.FindMethodByName("Foo");
Assert.NotNull(method.Declaration);
Assert.AreEqual(" This is a test method\n", method.Declaration.LeadingComments);
}

[Test]
public void MessageComments()
{
var message = unitTestProto3Descriptor.FindTypeByName<MessageDescriptor>("CommentMessage");
Assert.NotNull(message.Declaration);
Assert.AreEqual(" This is a leading comment\n", message.Declaration.LeadingComments);
Assert.AreEqual(new[] { " This is leading detached comment 1\n", " This is leading detached comment 2\n" },
message.Declaration.LeadingDetachedComments);
}

[Test]
public void EnumComments()
{
var descriptor = unitTestProto3Descriptor.FindTypeByName<EnumDescriptor>("CommentEnum");
Assert.NotNull(descriptor.Declaration);
Assert.AreEqual(" Leading enum comment\n", descriptor.Declaration.LeadingComments);
}

[Test]
public void NestedMessageComments()
{
var outer = unitTestProto3Descriptor.FindTypeByName<MessageDescriptor>("CommentMessage");
var nested = outer.FindDescriptor<MessageDescriptor>("NestedCommentMessage");
Assert.NotNull(nested.Declaration);
Assert.AreEqual(" Leading nested message comment\n", nested.Declaration.LeadingComments);
}

[Test]
public void NestedEnumComments()
{
var outer = unitTestProto3Descriptor.FindTypeByName<MessageDescriptor>("CommentMessage");
var nested = outer.FindDescriptor<EnumDescriptor>("NestedCommentEnum");
Assert.NotNull(nested.Declaration);
Assert.AreEqual(" Leading nested enum comment\n", nested.Declaration.LeadingComments);
}

[Test]
public void FieldComments()
{
var message = unitTestProto3Descriptor.FindTypeByName<MessageDescriptor>("CommentMessage");
var field = message.FindFieldByName("text");
Assert.NotNull(field.Declaration);
Assert.AreEqual(" Leading field comment\n", field.Declaration.LeadingComments);
Assert.AreEqual(" Trailing field comment\n", field.Declaration.TrailingComments);
}

[Test]
public void NestedMessageFieldComments()
{
var outer = unitTestProto3Descriptor.FindTypeByName<MessageDescriptor>("CommentMessage");
var nested = outer.FindDescriptor<MessageDescriptor>("NestedCommentMessage");
var field = nested.FindFieldByName("nested_text");
Assert.NotNull(field.Declaration);
Assert.AreEqual(" Leading nested message field comment\n", field.Declaration.LeadingComments);
}

[Test]
public void EnumValueComments()
{
var enumDescriptor = unitTestProto3Descriptor.FindTypeByName<EnumDescriptor>("CommentEnum");
var value = enumDescriptor.FindValueByName("ZERO_VALUE");
Assert.NotNull(value.Declaration);
Assert.AreEqual(" Zero value comment\n", value.Declaration.LeadingComments);
}

[Test]
public void NestedEnumValueComments()
{
var outer = unitTestProto3Descriptor.FindTypeByName<MessageDescriptor>("CommentMessage");
var nested = outer.FindDescriptor<EnumDescriptor>("NestedCommentEnum");
var value = nested.FindValueByName("ZERO_VALUE");
Assert.NotNull(value.Declaration);
Assert.AreEqual(" Zero value comment\n", value.Declaration.LeadingComments);
}

private static FileDescriptor LoadProtos()
{
var type = typeof(DescriptorDeclarationTest);
// TODO: Make this simpler :)
FileDescriptorSet descriptorSet;
using (var stream = type.GetTypeInfo().Assembly.GetManifestResourceStream($"Google.Protobuf.Test.testprotos.pb"))
{
descriptorSet = FileDescriptorSet.Parser.ParseFrom(stream);
}
var byteStrings = descriptorSet.File.Select(f => f.ToByteString()).ToList();
var descriptors = FileDescriptor.BuildFromByteStrings(byteStrings);
return descriptors.Single(d => d.Name == "unittest_proto3.proto");
}
}
}
Loading

0 comments on commit 1711999

Please sign in to comment.