From 59c20edc3b22f286867e78f0cf8ba05a1a7f1c0f Mon Sep 17 00:00:00 2001
From: Jan Tattermusch <jtattermusch@google.com>
Date: Thu, 28 Apr 2016 09:12:13 -0700
Subject: [PATCH] add comments from .proto file to generated C# files

---
 src/compiler/csharp_generator.cc | 89 +++++++++++++++++++++++++++++---
 1 file changed, 82 insertions(+), 7 deletions(-)

diff --git a/src/compiler/csharp_generator.cc b/src/compiler/csharp_generator.cc
index 0d1404341d..ac0fee1ec4 100644
--- a/src/compiler/csharp_generator.cc
+++ b/src/compiler/csharp_generator.cc
@@ -52,6 +52,7 @@ using grpc::protobuf::MethodDescriptor;
 using grpc::protobuf::io::Printer;
 using grpc::protobuf::io::StringOutputStream;
 using grpc_generator::MethodType;
+using grpc_generator::GetCppComments;
 using grpc_generator::GetMethodType;
 using grpc_generator::METHODTYPE_NO_STREAMING;
 using grpc_generator::METHODTYPE_CLIENT_STREAMING;
@@ -65,6 +66,56 @@ using std::vector;
 namespace grpc_csharp_generator {
 namespace {
 
+// This function is a massaged version of
+// https://github.com/google/protobuf/blob/master/src/google/protobuf/compiler/csharp/csharp_doc_comment.cc
+// Currently, we cannot easily reuse the functionality as
+// google/protobuf/compiler/csharp/csharp_doc_comment.h is not a public header.
+// TODO(jtattermusch): reuse the functionality from google/protobuf.
+void GenerateDocCommentBodyImpl(grpc::protobuf::io::Printer* printer, grpc::protobuf::SourceLocation location) {
+    grpc::string comments = location.leading_comments.empty() ?
+        location.trailing_comments : location.leading_comments;
+  if (comments.empty()) {
+    return;
+  }
+  // XML escaping... no need for apostrophes etc as the whole text is going to be a child
+  // node of a summary element, not part of an attribute.
+  comments = grpc_generator::StringReplace(comments, "&", "&amp;", true);
+  comments = grpc_generator::StringReplace(comments, "<", "&lt;", true);
+
+  std::vector<grpc::string> lines;
+  grpc_generator::Split(comments, '\n', &lines);
+  // TODO: We really should work out which part to put in the summary and which to put in the remarks...
+  // but that needs to be part of a bigger effort to understand the markdown better anyway.
+  printer->Print("/// <summary>\n");
+  bool last_was_empty = false;
+  // We squash multiple blank lines down to one, and remove any trailing blank lines. We need
+  // to preserve the blank lines themselves, as this is relevant in the markdown.
+  // Note that we can't remove leading or trailing whitespace as *that's* relevant in markdown too.
+  // (We don't skip "just whitespace" lines, either.)
+  for (std::vector<grpc::string>::iterator it = lines.begin(); it != lines.end(); ++it) {
+    grpc::string line = *it;
+    if (line.empty()) {
+      last_was_empty = true;
+    } else {
+      if (last_was_empty) {
+          printer->Print("///\n");
+      }
+      last_was_empty = false;
+      printer->Print("/// $line$\n", "line", *it);
+    }
+  }
+  printer->Print("/// </summary>\n");
+}
+
+template <typename DescriptorType>
+void GenerateDocCommentBody(
+  grpc::protobuf::io::Printer* printer, const DescriptorType* descriptor) {
+  grpc::protobuf::SourceLocation location;
+  if (descriptor->GetSourceLocation(&location)) {
+    GenerateDocCommentBodyImpl(printer, location);
+  }
+}
+
 std::string GetServiceClassName(const ServiceDescriptor* service) {
   return service->name();
 }
@@ -242,7 +293,7 @@ void GenerateStaticMethodField(Printer* out, const MethodDescriptor *method) {
 void GenerateServiceDescriptorProperty(Printer* out, const ServiceDescriptor *service) {
   std::ostringstream index;
   index << service->index();
-  out->Print("// service descriptor\n");
+  out->Print("/// <summary>Service descriptor</summary>\n");
   out->Print("public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor\n");
   out->Print("{\n");
   out->Print("  get { return $umbrella$.Descriptor.Services[$index$]; }\n",
@@ -253,7 +304,8 @@ void GenerateServiceDescriptorProperty(Printer* out, const ServiceDescriptor *se
 }
 
 void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) {
-  out->Print("// client interface\n");
+  out->Print("/// <summary>Client for $servicename$</summary>\n",
+             "servicename", GetServiceClassName(service));
   out->Print("[System.Obsolete(\"Client side interfaced will be removed "
              "in the next release. Use client class directly.\")]\n");
   out->Print("public interface $name$\n", "name",
@@ -266,6 +318,7 @@ void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) {
 
     if (method_type == METHODTYPE_NO_STREAMING) {
       // unary calls have an extra synchronous stub method
+      GenerateDocCommentBody(out, method);
       out->Print(
           "$response$ $methodname$($request$ request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));\n",
           "methodname", method->name(), "request",
@@ -273,6 +326,7 @@ void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) {
           GetClassName(method->output_type()));
 
       // overload taking CallOptions as a param
+      GenerateDocCommentBody(out, method);
       out->Print(
           "$response$ $methodname$($request$ request, CallOptions options);\n",
           "methodname", method->name(), "request",
@@ -284,6 +338,7 @@ void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) {
     if (method_type == METHODTYPE_NO_STREAMING) {
       method_name += "Async";  // prevent name clash with synchronous method.
     }
+    GenerateDocCommentBody(out, method);
     out->Print(
         "$returntype$ $methodname$($request_maybe$Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));\n",
         "methodname", method_name, "request_maybe",
@@ -291,6 +346,7 @@ void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) {
         GetMethodReturnTypeClient(method));
 
     // overload taking CallOptions as a param
+    GenerateDocCommentBody(out, method);
     out->Print(
         "$returntype$ $methodname$($request_maybe$CallOptions options);\n",
         "methodname", method_name, "request_maybe",
@@ -303,7 +359,8 @@ void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) {
 }
 
 void GenerateServerInterface(Printer* out, const ServiceDescriptor *service) {
-  out->Print("// server-side interface\n");
+  out->Print("/// <summary>Interface of server-side implementations of $servicename$</summary>\n",
+             "servicename", GetServiceClassName(service));
   out->Print("[System.Obsolete(\"Service implementations should inherit"
       " from the generated abstract base class instead.\")]\n");
   out->Print("public interface $name$\n", "name",
@@ -312,6 +369,7 @@ void GenerateServerInterface(Printer* out, const ServiceDescriptor *service) {
   out->Indent();
   for (int i = 0; i < service->method_count(); i++) {
     const MethodDescriptor *method = service->method(i);
+    GenerateDocCommentBody(out, method);
     out->Print(
         "$returntype$ $methodname$($request$$response_stream_maybe$, "
         "ServerCallContext context);\n",
@@ -326,13 +384,15 @@ void GenerateServerInterface(Printer* out, const ServiceDescriptor *service) {
 }
 
 void GenerateServerClass(Printer* out, const ServiceDescriptor *service) {
-  out->Print("// server-side abstract class\n");
+  out->Print("/// <summary>Base class for server-side implementations of $servicename$</summary>\n",
+             "servicename", GetServiceClassName(service));
   out->Print("public abstract class $name$\n", "name",
              GetServerClassName(service));
   out->Print("{\n");
   out->Indent();
   for (int i = 0; i < service->method_count(); i++) {
     const MethodDescriptor *method = service->method(i);
+    GenerateDocCommentBody(out, method);
     out->Print(
         "public virtual $returntype$ $methodname$($request$$response_stream_maybe$, "
         "ServerCallContext context)\n",
@@ -353,7 +413,8 @@ void GenerateServerClass(Printer* out, const ServiceDescriptor *service) {
 }
 
 void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
-  out->Print("// client stub\n");
+  out->Print("/// <summary>Client for $servicename$</summary>\n",
+             "servicename", GetServiceClassName(service));
   out->Print("#pragma warning disable 0618\n");
   out->Print(
       "public class $name$ : ClientBase<$name$>, $interface$\n",
@@ -392,6 +453,7 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
 
     if (method_type == METHODTYPE_NO_STREAMING) {
       // unary calls have an extra synchronous stub method
+      GenerateDocCommentBody(out, method);
       out->Print("public virtual $response$ $methodname$($request$ request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))\n",
           "methodname", method->name(), "request",
           GetClassName(method->input_type()), "response",
@@ -404,6 +466,7 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
       out->Print("}\n");
 
       // overload taking CallOptions as a param
+      GenerateDocCommentBody(out, method);
       out->Print("public virtual $response$ $methodname$($request$ request, CallOptions options)\n",
           "methodname", method->name(), "request",
           GetClassName(method->input_type()), "response",
@@ -420,6 +483,7 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
     if (method_type == METHODTYPE_NO_STREAMING) {
       method_name += "Async";  // prevent name clash with synchronous method.
     }
+    GenerateDocCommentBody(out, method);
     out->Print(
             "public virtual $returntype$ $methodname$($request_maybe$Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))\n",
             "methodname", method_name, "request_maybe",
@@ -435,6 +499,7 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
     out->Print("}\n");
 
     // overload taking CallOptions as a param
+    GenerateDocCommentBody(out, method);
     out->Print(
         "public virtual $returntype$ $methodname$($request_maybe$CallOptions options)\n",
         "methodname", method_name, "request_maybe",
@@ -485,7 +550,7 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
 void GenerateBindServiceMethod(Printer* out, const ServiceDescriptor *service,
                                bool use_server_class) {
   out->Print(
-      "// creates service definition that can be registered with a server\n");
+      "/// <summary>Creates service definition that can be registered with a server</summary>\n");
   out->Print("#pragma warning disable 0618\n");
   out->Print(
       "public static ServerServiceDefinition BindService($interface$ serviceImpl)\n",
@@ -519,7 +584,8 @@ void GenerateBindServiceMethod(Printer* out, const ServiceDescriptor *service,
 }
 
 void GenerateNewStubMethods(Printer* out, const ServiceDescriptor *service) {
-  out->Print("// creates a new client\n");
+  out->Print("/// <summary>Creates a new client for $servicename$</summary>\n",
+             "servicename", GetServiceClassName(service));
   out->Print("public static $classname$ NewClient(Channel channel)\n",
              "classname", GetClientClassName(service));
   out->Print("{\n");
@@ -534,6 +600,7 @@ void GenerateNewStubMethods(Printer* out, const ServiceDescriptor *service) {
 void GenerateService(Printer* out, const ServiceDescriptor *service,
                      bool generate_client, bool generate_server,
                      bool internal_access) {
+  GenerateDocCommentBody(out, service);
   out->Print("$access_level$ static class $classname$\n", "access_level",
              GetAccessLevel(internal_access), "classname",
              GetServiceClassName(service));
@@ -590,6 +657,14 @@ grpc::string GetServices(const FileDescriptor *file, bool generate_client,
     // Write out a file header.
     out.Print("// Generated by the protocol buffer compiler.  DO NOT EDIT!\n");
     out.Print("// source: $filename$\n", "filename", file->name());
+
+    // use C++ style as there are no file-level XML comments in .NET
+    grpc::string leading_comments = GetCppComments(file, true);
+    if (!leading_comments.empty()) {
+      out.Print("// Original file comments:\n");
+      out.Print(leading_comments.c_str());
+    }
+
     out.Print("#region Designer generated code\n");
     out.Print("\n");
     out.Print("using System;\n");
-- 
GitLab