diff --git a/src/ContractGenerator/ContractGenerator.cs b/src/ContractGenerator/ContractGenerator.cs index ab44cf5..210d509 100644 --- a/src/ContractGenerator/ContractGenerator.cs +++ b/src/ContractGenerator/ContractGenerator.cs @@ -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. /// - public CodeGeneratorResponse Generate(Stream stdin) + public static CodeGeneratorResponse Generate(Stream stdin) { throw new NotImplementedException(); } diff --git a/src/ContractGenerator/ProtoUtils.cs b/src/ContractGenerator/ProtoUtils.cs index 4a4ca44..87720c0 100644 --- a/src/ContractGenerator/ProtoUtils.cs +++ b/src/ContractGenerator/ProtoUtils.cs @@ -1,3 +1,4 @@ +using System.Text; using Google.Protobuf.Reflection; namespace ContractGenerator; @@ -16,6 +17,84 @@ public static string GetAccessLevel(byte flags) return (flags & FlagConstants.InternalAccess) != 0 ? "internal" : "public"; } + /// + /// 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 + /// + public static string GetCsharpComments(IDescriptor desc, bool leading) + { + return GetPrefixedComments(desc, leading, "//"); + } + + /// + /// 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 + /// + private static string GetPrefixedComments(IDescriptor desc, bool leading, string prefix) + { + var outComments = new List(); + + if (leading) + { + GetComment(desc, CommentType.LeadingDetached, outComments); + var leadingComments = new List(); + GetComment(desc, CommentType.Leading, leadingComments); + outComments.AddRange(leadingComments); + } + else + { + GetComment(desc, CommentType.Trailing, outComments); + } + + return GenerateCommentsWithPrefix(outComments, prefix); + } + + private static string GenerateCommentsWithPrefix(IEnumerable 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 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 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); @@ -157,4 +236,11 @@ internal static string UnderscoresToCamelCase(string input, bool capNextLetter, result = '_' + result; return result; } + + private enum CommentType + { + Leading, + Trailing, + LeadingDetached + } } diff --git a/test/ContractGenerator.Tests/ProtoUtilsTests.cs b/test/ContractGenerator.Tests/ProtoUtilsTests.cs index 5303171..e5bcaaf 100644 --- a/test/ContractGenerator.Tests/ProtoUtilsTests.cs +++ b/test/ContractGenerator.Tests/ProtoUtilsTests.cs @@ -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 +"; + + + // Assert: Verify the expected result + Assert.Equal(expectedComments, comments); + } + [Fact] public void GetPropertyName_ReturnsCorrectPropertyName() { diff --git a/test/ContractGenerator.Tests/scripts/generate_descriptor.py b/test/ContractGenerator.Tests/scripts/generate_descriptor.py index 31d7197..fb03f74 100644 --- a/test/ContractGenerator.Tests/scripts/generate_descriptor.py +++ b/test/ContractGenerator.Tests/scripts/generate_descriptor.py @@ -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 ] diff --git a/test/ContractGenerator.Tests/testcases/helloworld/contract.proto b/test/ContractGenerator.Tests/testcases/helloworld/contract.proto index 76ca41d..56d2259 100644 --- a/test/ContractGenerator.Tests/testcases/helloworld/contract.proto +++ b/test/ContractGenerator.Tests/testcases/helloworld/contract.proto @@ -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";