From 9bf8c5e58ff390ab874aab8a433c683891d036c7 Mon Sep 17 00:00:00 2001 From: Aeshan Wijetunge Date: Tue, 3 Oct 2023 10:34:16 +0800 Subject: [PATCH 1/6] wip: initial comment-logic copied from GRPC source --- src/ContractGenerator/ProtoUtils.cs | 67 +++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/ContractGenerator/ProtoUtils.cs b/src/ContractGenerator/ProtoUtils.cs index d912e06..251b351 100644 --- a/src/ContractGenerator/ProtoUtils.cs +++ b/src/ContractGenerator/ProtoUtils.cs @@ -14,6 +14,73 @@ public static string GetClassName(IDescriptor descriptor) // return flags & Flags.INTERNAL_ACCESS ? "internal" : "public"; // } + /// + /// This Util GetCsharpComments gets/generates C# comments based on the proto. Copied from the C++ original + /// https://github.com/protocolbuffers/protobuf/blob/e57166b65a6d1d55fc7b18beaae000565f617f22/src/google/protobuf/compiler/csharp/csharp_helpers.cc#L255C35-L255C50 + /// + public static string GetCsharpComments(TDescriptor desc, bool leading) + { + return GetPrefixedComments(desc, leading, "//"); + } + + /// + /// This Util gets the GetPrefixedComments based on the proto. Copied from the C++ original + /// https://github.com/protocolbuffers/protobuf/blob/e57166b65a6d1d55fc7b18beaae000565f617f22/src/google/protobuf/compiler/csharp/csharp_helpers.cc#L255C35-L255C50 + /// + private static string GetPrefixedComments(TDescriptor desc, bool leading, string prefix) + { + List outComments = new List(); + + if (leading) + { + GetComment(desc, COMMENTTYPE.LeadingDetached, outComments); + List leadingComments = new List(); + GetComment(desc, COMMENTTYPE.Leading, leadingComments); + outComments.AddRange(leadingComments); + } + else + { + GetComment(desc, COMMENTTYPE.Trailing, outComments); + } + + return GenerateCommentsWithPrefix(outComments, prefix); + } + + public enum CommentType + { + Leading, + Trailing, + LeadingDetached + } + + private static void GetComment(TDescriptor desc, CommentType type, List outComments) + { + SourceLocation location = new SourceLocation(); + if (!GetSourceLocation(desc, location)) + { + return; + } + + if (type == CommentType.Leading || type == CommentType.Trailing) + { + string comments = type == CommentType.Leading ? location.leading_comments : location.trailing_comments; + Split(comments, '\n', outComments); + } + else if (type == CommentType.LeadingDetached) + { + foreach (string detachedComment in location.leading_detached_comments) + { + Split(detachedComment, '\n', outComments); + outComments.Add(""); // Add an empty line separator + } + } + else + { + Console.WriteLine("Unknown comment type " + type); + Environment.Exit(1); + } + } + private static string ToCSharpName(string name, FileDescriptor fileDescriptor) { var result = GetFileNamespace(fileDescriptor); From 8146a968be48713111148f2175e673c2711ab7b9 Mon Sep 17 00:00:00 2001 From: Aeshan Wijetunge Date: Thu, 5 Oct 2023 15:02:23 +0800 Subject: [PATCH 2/6] test: fixed unit-test for GetCSharpComments. python-script required an extra param --- src/ContractGenerator/ContractGenerator.cs | 2 +- src/ContractGenerator/ProtoUtils.cs | 89 ++++++++++++------- .../ProtoUtilsTests.cs | 26 ++++++ .../scripts/generate_descriptor.py | 1 + .../testcases/helloworld/contract.proto | 2 +- 5 files changed, 85 insertions(+), 35 deletions(-) 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 4f05fe4..2cbd286 100644 --- a/src/ContractGenerator/ProtoUtils.cs +++ b/src/ContractGenerator/ProtoUtils.cs @@ -1,9 +1,17 @@ +using System.Text; using Google.Protobuf.Reflection; namespace ContractGenerator; public class ProtoUtils { + public enum CommentType + { + Leading, + Trailing, + LeadingDetached + } + //TODO Implement https://github.com/protocolbuffers/protobuf/blob/e57166b65a6d1d55fc7b18beaae000565f617f22/src/google/protobuf/compiler/csharp/names.cc#L73 public static string GetClassName(IDescriptor descriptor) { @@ -20,7 +28,7 @@ public static string GetAccessLevel(byte flags) /// This Util GetCsharpComments gets/generates C# comments based on the proto. Copied from the C++ original /// https://github.com/protocolbuffers/protobuf/blob/e57166b65a6d1d55fc7b18beaae000565f617f22/src/google/protobuf/compiler/csharp/csharp_helpers.cc#L255C35-L255C50 /// - public static string GetCsharpComments(TDescriptor desc, bool leading) + public static string GetCsharpComments(IDescriptor desc, bool leading) { return GetPrefixedComments(desc, leading, "//"); } @@ -29,58 +37,73 @@ public static string GetCsharpComments(TDescriptor desc, bool leadi /// This Util gets the GetPrefixedComments based on the proto. Copied from the C++ original /// https://github.com/protocolbuffers/protobuf/blob/e57166b65a6d1d55fc7b18beaae000565f617f22/src/google/protobuf/compiler/csharp/csharp_helpers.cc#L255C35-L255C50 /// - private static string GetPrefixedComments(TDescriptor desc, bool leading, string prefix) + private static string GetPrefixedComments(IDescriptor desc, bool leading, string prefix) { - List outComments = new List(); + var outComments = new List(); if (leading) { - GetComment(desc, COMMENTTYPE.LeadingDetached, outComments); - List leadingComments = new List(); - GetComment(desc, COMMENTTYPE.Leading, leadingComments); + GetComment(desc, CommentType.LeadingDetached, outComments); + var leadingComments = new List(); + GetComment(desc, CommentType.Leading, leadingComments); outComments.AddRange(leadingComments); } else { - GetComment(desc, COMMENTTYPE.Trailing, outComments); + GetComment(desc, CommentType.Trailing, outComments); } return GenerateCommentsWithPrefix(outComments, prefix); } - public enum CommentType + private static string GenerateCommentsWithPrefix(List input, string prefix) { - Leading, - Trailing, - LeadingDetached + var sb = new StringBuilder(); + foreach (var elem in input) + if (string.IsNullOrEmpty(elem)) + sb.Append(prefix).Append("\n"); + else if (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(TDescriptor desc, CommentType type, List outComments) + private static void GetComment(IDescriptor desc, CommentType type, ICollection outComments) { - SourceLocation location = new SourceLocation(); - if (!GetSourceLocation(desc, location)) - { - return; - } + if (desc.File.ToProto().SourceCodeInfo == null) return; - if (type == CommentType.Leading || type == CommentType.Trailing) - { - string comments = type == CommentType.Leading ? location.leading_comments : location.trailing_comments; - Split(comments, '\n', outComments); - } - else if (type == CommentType.LeadingDetached) - { - foreach (string detachedComment in location.leading_detached_comments) + var locations = desc.File.ToProto().SourceCodeInfo.Location; + + foreach (var location in locations) + switch (type) { - Split(detachedComment, '\n', outComments); - outComments.Add(""); // Add an empty line separator + 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); + outComments.Add(""); // Add an empty line separator + } + + break; + } + default: + throw new Exception("Unknown comment type " + type); } - } - else - { - Console.WriteLine("Unknown comment type " + type); - Environment.Exit(1); - } + } + + private static void Split(string s, char delim, ICollection appendTo) + { + var reader = new StringReader(s); + while (reader.ReadLine() is { } line) appendTo.Add(line); } private static string ToCSharpName(string name, FileDescriptor fileDescriptor) 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"; From 3e4e6224b96866ff3c804f2851c92447bd391538 Mon Sep 17 00:00:00 2001 From: Aeshan Wijetunge Date: Thu, 5 Oct 2023 15:51:16 +0800 Subject: [PATCH 3/6] fix: splitter to split by delim --- src/ContractGenerator/ProtoUtils.cs | 11 ++++--- .../ProtoUtilsTests.cs | 31 ++++++++++++++++++- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/ContractGenerator/ProtoUtils.cs b/src/ContractGenerator/ProtoUtils.cs index 2cbd286..295c523 100644 --- a/src/ContractGenerator/ProtoUtils.cs +++ b/src/ContractGenerator/ProtoUtils.cs @@ -5,7 +5,7 @@ namespace ContractGenerator; public class ProtoUtils { - public enum CommentType + private enum CommentType { Leading, Trailing, @@ -100,10 +100,13 @@ private static void GetComment(IDescriptor desc, CommentType type, ICollection appendTo) + private static void Split(string input, char delim, ICollection appendTo) { - var reader = new StringReader(s); - while (reader.ReadLine() is { } line) appendTo.Add(line); + var substrings = input.Split(delim); + foreach (var substring in substrings) + { + appendTo.Add(substring); + } } private static string ToCSharpName(string name, FileDescriptor fileDescriptor) diff --git a/test/ContractGenerator.Tests/ProtoUtilsTests.cs b/test/ContractGenerator.Tests/ProtoUtilsTests.cs index e5bcaaf..0b2f896 100644 --- a/test/ContractGenerator.Tests/ProtoUtilsTests.cs +++ b/test/ContractGenerator.Tests/ProtoUtilsTests.cs @@ -78,14 +78,43 @@ public void GetCsharpComments_ReturnsComments() // Act: Call the GetClassName method var comments = ProtoUtils.GetCsharpComments(file, true); - const string expectedComments = @"// These are test header comments! + 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 +// +// +// +// +// +// +// +// "; From 170130e6dff4459a2cdfb88d49b051813ac16d0e Mon Sep 17 00:00:00 2001 From: Aeshan Wijetunge Date: Thu, 5 Oct 2023 15:52:07 +0800 Subject: [PATCH 4/6] refactor: format code --- src/ContractGenerator/ProtoUtils.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/ContractGenerator/ProtoUtils.cs b/src/ContractGenerator/ProtoUtils.cs index 295c523..aef5c47 100644 --- a/src/ContractGenerator/ProtoUtils.cs +++ b/src/ContractGenerator/ProtoUtils.cs @@ -5,13 +5,6 @@ namespace ContractGenerator; public class ProtoUtils { - private enum CommentType - { - Leading, - Trailing, - LeadingDetached - } - //TODO Implement https://github.com/protocolbuffers/protobuf/blob/e57166b65a6d1d55fc7b18beaae000565f617f22/src/google/protobuf/compiler/csharp/names.cc#L73 public static string GetClassName(IDescriptor descriptor) { @@ -103,10 +96,7 @@ private static void GetComment(IDescriptor desc, CommentType type, ICollection appendTo) { var substrings = input.Split(delim); - foreach (var substring in substrings) - { - appendTo.Add(substring); - } + foreach (var substring in substrings) appendTo.Add(substring); } private static string ToCSharpName(string name, FileDescriptor fileDescriptor) @@ -250,4 +240,11 @@ internal static string UnderscoresToCamelCase(string input, bool capNextLetter, result = '_' + result; return result; } + + private enum CommentType + { + Leading, + Trailing, + LeadingDetached + } } From 9b75cca454ad32de3a4787289b39963ef247ab16 Mon Sep 17 00:00:00 2001 From: Aeshan Wijetunge Date: Fri, 6 Oct 2023 11:46:40 +0800 Subject: [PATCH 5/6] fix: remove extra line-breaks + unnecessary spaces in comments-output --- src/ContractGenerator/ProtoUtils.cs | 12 +++---- .../ProtoUtilsTests.cs | 31 +------------------ 2 files changed, 5 insertions(+), 38 deletions(-) diff --git a/src/ContractGenerator/ProtoUtils.cs b/src/ContractGenerator/ProtoUtils.cs index aef5c47..b095a7e 100644 --- a/src/ContractGenerator/ProtoUtils.cs +++ b/src/ContractGenerator/ProtoUtils.cs @@ -49,16 +49,15 @@ private static string GetPrefixedComments(IDescriptor desc, bool leading, string return GenerateCommentsWithPrefix(outComments, prefix); } - private static string GenerateCommentsWithPrefix(List input, string prefix) + private static string GenerateCommentsWithPrefix(IEnumerable input, string prefix) { var sb = new StringBuilder(); - foreach (var elem in input) - if (string.IsNullOrEmpty(elem)) - sb.Append(prefix).Append("\n"); - else if (elem[0] == ' ') + 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(); } @@ -81,10 +80,7 @@ private static void GetComment(IDescriptor desc, CommentType type, ICollection Date: Fri, 6 Oct 2023 16:02:20 +0800 Subject: [PATCH 6/6] fix: fix incorrect urls in comments --- src/ContractGenerator/ProtoUtils.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ContractGenerator/ProtoUtils.cs b/src/ContractGenerator/ProtoUtils.cs index b095a7e..87720c0 100644 --- a/src/ContractGenerator/ProtoUtils.cs +++ b/src/ContractGenerator/ProtoUtils.cs @@ -19,7 +19,7 @@ public static string GetAccessLevel(byte flags) /// /// This Util GetCsharpComments gets/generates C# comments based on the proto. Copied from the C++ original - /// https://github.com/protocolbuffers/protobuf/blob/e57166b65a6d1d55fc7b18beaae000565f617f22/src/google/protobuf/compiler/csharp/csharp_helpers.cc#L255C35-L255C50 + /// https://github.com/AElfProject/contract-plugin/blob/de625fcb79f83603e29d201c8488f101b40f573c/src/contract_csharp_generator_helpers.h#L37 /// public static string GetCsharpComments(IDescriptor desc, bool leading) { @@ -28,7 +28,7 @@ public static string GetCsharpComments(IDescriptor desc, bool leading) /// /// This Util gets the GetPrefixedComments based on the proto. Copied from the C++ original - /// https://github.com/protocolbuffers/protobuf/blob/e57166b65a6d1d55fc7b18beaae000565f617f22/src/google/protobuf/compiler/csharp/csharp_helpers.cc#L255C35-L255C50 + /// https://github.com/AElfProject/contract-plugin/blob/de625fcb79f83603e29d201c8488f101b40f573c/src/generator_helpers.h#L257 /// private static string GetPrefixedComments(IDescriptor desc, bool leading, string prefix) {