Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/ContractGenerator/ContractGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ private static string GetServicesFilename(FileDescriptor fileDescriptor)
/// Generates a set of C# files from the input stream containing the proto source. This is the primary entry-point into
/// the ContractPlugin.
/// </summary>
public CodeGeneratorResponse Generate(Stream stdin)
public static CodeGeneratorResponse Generate(Stream stdin)
{
throw new NotImplementedException();
}
Expand Down
86 changes: 86 additions & 0 deletions src/ContractGenerator/ProtoUtils.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Text;
using Google.Protobuf.Reflection;

namespace ContractGenerator;
Expand All @@ -16,6 +17,84 @@ public static string GetAccessLevel(byte flags)
return (flags & FlagConstants.InternalAccess) != 0 ? "internal" : "public";
}

/// <summary>
/// This Util GetCsharpComments gets/generates C# comments based on the proto. Copied from the C++ original
/// https://github.com/AElfProject/contract-plugin/blob/de625fcb79f83603e29d201c8488f101b40f573c/src/contract_csharp_generator_helpers.h#L37
/// </summary>
public static string GetCsharpComments(IDescriptor desc, bool leading)
{
return GetPrefixedComments(desc, leading, "//");
}

/// <summary>
/// This Util gets the GetPrefixedComments based on the proto. Copied from the C++ original
/// https://github.com/AElfProject/contract-plugin/blob/de625fcb79f83603e29d201c8488f101b40f573c/src/generator_helpers.h#L257
/// </summary>
private static string GetPrefixedComments(IDescriptor desc, bool leading, string prefix)
{
var outComments = new List<string?>();

if (leading)
{
GetComment(desc, CommentType.LeadingDetached, outComments);
var leadingComments = new List<string?>();
GetComment(desc, CommentType.Leading, leadingComments);
outComments.AddRange(leadingComments);
}
else
{
GetComment(desc, CommentType.Trailing, outComments);
}

return GenerateCommentsWithPrefix(outComments, prefix);
}

private static string GenerateCommentsWithPrefix(IEnumerable<string?> input, string prefix)
{
var sb = new StringBuilder();
foreach (var elem in input.Where(elem => !string.IsNullOrEmpty(elem)))
if (elem != null && elem[0] == ' ')
sb.Append(prefix).Append(elem).Append("\n");
else
sb.Append(prefix).Append(" ").Append(elem).Append("\n");

return sb.ToString();
}

private static void GetComment(IDescriptor desc, CommentType type, ICollection<string?> outComments)
{
if (desc.File.ToProto().SourceCodeInfo == null) return;

var locations = desc.File.ToProto().SourceCodeInfo.Location;

foreach (var location in locations)
switch (type)
{
case CommentType.Leading:
case CommentType.Trailing:
{
var comments = type == CommentType.Leading ? location.LeadingComments : location.TrailingComments;
Split(comments, '\n', outComments);
break;
}
case CommentType.LeadingDetached:
{
foreach (var detachedComment in location.LeadingDetachedComments)
Split(detachedComment, '\n', outComments);

break;
}
default:
throw new Exception("Unknown comment type " + type);
}
}

private static void Split(string input, char delim, ICollection<string?> appendTo)
{
var substrings = input.Split(delim);
foreach (var substring in substrings) appendTo.Add(substring);
}

private static string ToCSharpName(string name, FileDescriptor fileDescriptor)
{
var result = GetFileNamespace(fileDescriptor);
Expand Down Expand Up @@ -157,4 +236,11 @@ internal static string UnderscoresToCamelCase(string input, bool capNextLetter,
result = '_' + result;
return result;
}

private enum CommentType
{
Leading,
Trailing,
LeadingDetached
}
}
26 changes: 26 additions & 0 deletions test/ContractGenerator.Tests/ProtoUtilsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,32 @@ public void GetClassName_ReturnsCorrectClassName()
Assert.Equal("global::AElf.Contracts.HelloWorld.HelloWorld", className);
}

[Fact]
public void GetCsharpComments_ReturnsComments()
{
// Arrange: Create a DescriptorBase with a known FullName and File
var fds = GetFileDescriptorSet("helloworld");
var byteStrings = fds.File.Select(f => f.ToByteString());
var fileDescriptors = FileDescriptor.BuildFromByteStrings(byteStrings, _extensionRegistry);
var file = fileDescriptors[^1];

// Act: Call the GetClassName method
var comments = ProtoUtils.GetCsharpComments(file, true);
const string expectedComments = @"// These are test header comments!
// The namespace of this class
// The name of the state class the smart contract is going to use to access blockchain state
// Actions (methods that modify contract state)
// Stores the value in contract state
// Views (methods that don't modify contract state)
// Get the value stored from contract state
// An event that will be emitted from contract method call
Comment on lines +84 to +88
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are not file level comments. Is this output correct? It makes no sense to print out the comments for methods and messages like this.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm these comments are present in the file tho i suppose it would be best to just print the top-level file-proto comments? i.e just // These are test header comments! based on the example below. wdyt?

syntax = "proto3";
// These are test header comments!
import "aelf/options.proto";
import "google/protobuf/empty.proto";
import "google/protobuf/wrappers.proto";
// The namespace of this class
option csharp_namespace = "AElf.Contracts.HelloWorld";

service HelloWorld {
    // The name of the state class the smart contract is going to use to access blockchain state
    option (aelf.csharp_state) = "AElf.Contracts.HelloWorld.HelloWorldState";

    // Actions (methods that modify contract state)
    // Stores the value in contract state
    rpc Update (google.protobuf.StringValue) returns (google.protobuf.Empty) {
    }

    // Views (methods that don't modify contract state)
    // Get the value stored from contract state
    rpc Read (google.protobuf.Empty) returns (google.protobuf.StringValue) {
        option (aelf.is_view) = true;
    }
}

// An event that will be emitted from contract method call
message UpdatedMessage {
    option (aelf.is_event) = true;
    string value = 1;
}

";


// Assert: Verify the expected result
Assert.Equal(expectedComments, comments);
}

[Fact]
public void GetPropertyName_ReturnsCorrectPropertyName()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def get_command(testcase_name):
f'-o"{testcases_dir}/{testcase_name}/{descriptor_filename}"',
"--include_imports",
"--retain_options",
"--include_source_info",
proto_filename
]

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
syntax = "proto3";

// These are test header comments!
import "aelf/options.proto";
import "google/protobuf/empty.proto";
import "google/protobuf/wrappers.proto";
Expand Down