Browse Source

[CodeGenLib] Render preformatted comments (#1770)

Motivation:

Some IDL representations (SwiftProtobuf) store the methods and services documentation as preformatted strings containing the "///" and new lines. So we need a case in the SwiftStructuredRepresentation and in the TextRenderer. The responsability of providing already formatted comments for the services and methods should be of the user and not of the CodeGenLibrary.

Modifications:

- created the new case in StructuredSwiiftrepresentation
- handled the rendering of this case
- modified tests

Result:

Documentation for methoda and services should pe preformatted in any CodeGenerationRequest object so we avoid generating commenyts with double `///`.
Stefana-Ioana Dranca 1 year ago
parent
commit
f7f3d2df3b

+ 4 - 1
Sources/GRPCCodeGen/CodeGenerationRequest.swift

@@ -21,7 +21,8 @@ public struct CodeGenerationRequest {
   public var fileName: String
 
   /// Any comments at the top of the file such as documentation and copyright headers.
-  /// They will be placed at the top of the generated file.
+  /// They will be placed at the top of the generated file. They are already formatted,
+  /// meaning they contain  "///" and new lines.
   public var leadingTrivia: String
 
   /// The Swift imports that the generated file depends on. The gRPC specific imports aren't required
@@ -218,6 +219,7 @@ public struct CodeGenerationRequest {
   /// Represents a service described in an IDL file.
   public struct ServiceDescriptor: Hashable {
     /// Documentation from comments above the IDL service description.
+    /// It is already formatted, meaning it contains  "///" and new lines.
     public var documentation: String
 
     /// The service name in different formats.
@@ -252,6 +254,7 @@ public struct CodeGenerationRequest {
     /// Represents a method described in an IDL file.
     public struct MethodDescriptor: Hashable {
       /// Documentation from comments above the IDL method description.
+      /// It is already formatted, meaning it contains  "///" and new lines.
       public var documentation: String
 
       /// Method name in different formats.

+ 11 - 4
Sources/GRPCCodeGen/Internal/Renderer/TextBasedRenderer.swift

@@ -161,12 +161,19 @@ struct TextBasedRenderer: RendererProtocol {
     case .mark(let string, sectionBreak: false):
       prefix = "// MARK:"
       commentString = string
+    case .preFormatted(let string):
+      prefix = ""
+      commentString = string
     }
-    let lines = commentString.transformingLines { line in
-      if line.isEmpty { return prefix }
-      return "\(prefix) \(line)"
+    if prefix.isEmpty {
+      writer.writeLine(commentString)
+    } else {
+      let lines = commentString.transformingLines { line in
+        if line.isEmpty { return prefix }
+        return "\(prefix) \(line)"
+      }
+      lines.forEach(writer.writeLine)
     }
-    lines.forEach(writer.writeLine)
   }
 
   /// Renders the specified import statements.

+ 8 - 0
Sources/GRPCCodeGen/Internal/StructuredSwiftRepresentation.swift

@@ -130,6 +130,14 @@ enum Comment: Equatable, Codable {
   /// For example: `// MARK: - Public methods`, with the optional
   /// section break (`-`).
   case mark(String, sectionBreak: Bool)
+
+  /// A comment that is already formatted,
+  /// meaning that it already has the `///` and
+  /// can contain multiple lines
+  ///
+  /// For example both the string and the comment
+  /// can look like `/// Important type`.
+  case preFormatted(String)
 }
 
 /// A description of a literal.

+ 7 - 4
Sources/GRPCCodeGen/Internal/Translator/ClientCodeTranslator.swift

@@ -77,7 +77,7 @@ struct ClientCodeTranslator: SpecializedTranslator {
       codeBlocks.append(
         .declaration(
           .commentable(
-            .doc(service.documentation),
+            .preFormatted(service.documentation),
             self.makeClientProtocol(for: service, in: codeGenerationRequest)
           )
         )
@@ -88,7 +88,7 @@ struct ClientCodeTranslator: SpecializedTranslator {
       codeBlocks.append(
         .declaration(
           .commentable(
-            .doc(service.documentation),
+            .preFormatted(service.documentation),
             self.makeClientStruct(for: service, in: codeGenerationRequest)
           )
         )
@@ -179,7 +179,10 @@ extension ClientCodeTranslator {
       )
       return .function(signature: functionSignature, body: body)
     } else {
-      return .commentable(.doc(method.documentation), .function(signature: functionSignature))
+      return .commentable(
+        .preFormatted(method.documentation),
+        .function(signature: functionSignature)
+      )
     }
   }
 
@@ -324,7 +327,7 @@ extension ClientCodeTranslator {
     let initializer = self.makeClientVariable()
     let methods = service.methods.map {
       Declaration.commentable(
-        .doc($0.documentation),
+        .preFormatted($0.documentation),
         self.makeClientMethod(for: $0, in: service, from: codeGenerationRequest)
       )
     }

+ 1 - 1
Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift

@@ -31,7 +31,7 @@ struct IDLToStructuredSwiftTranslator: Translator {
       accessLevel: accessLevel
     )
 
-    let topComment = Comment.doc(codeGenerationRequest.leadingTrivia)
+    let topComment = Comment.preFormatted(codeGenerationRequest.leadingTrivia)
     let imports = try codeGenerationRequest.dependencies.reduce(
       into: [ImportDescription(moduleName: "GRPCCore")]
     ) { partialResult, newDependency in

+ 4 - 4
Sources/GRPCCodeGen/Internal/Translator/ServerCodeTranslator.swift

@@ -107,7 +107,7 @@ extension ServerCodeTranslator {
   ) -> Declaration {
     let methods = service.methods.compactMap {
       Declaration.commentable(
-        .doc($0.documentation),
+        .preFormatted($0.documentation),
         .function(
           FunctionDescription(
             signature: self.makeStreamingMethodSignature(for: $0, in: service)
@@ -125,7 +125,7 @@ extension ServerCodeTranslator {
       )
     )
 
-    return .commentable(.doc(service.documentation), streamingProtocol)
+    return .commentable(.preFormatted(service.documentation), streamingProtocol)
   }
 
   private func makeStreamingMethodSignature(
@@ -290,7 +290,7 @@ extension ServerCodeTranslator {
     let streamingProtocol = self.protocolNameTypealias(service: service, streaming: true)
 
     return .commentable(
-      .doc(service.documentation),
+      .preFormatted(service.documentation),
       .protocol(
         ProtocolDescription(
           accessModifier: self.accessModifier,
@@ -344,7 +344,7 @@ extension ServerCodeTranslator {
     )
 
     return .commentable(
-      .doc(method.documentation),
+      .preFormatted(method.documentation),
       .function(FunctionDescription(signature: functionSignature))
     )
   }

+ 23 - 15
Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift

@@ -27,7 +27,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
 
   func testClientCodeTranslatorUnaryMethod() throws {
     let method = MethodDescriptor(
-      documentation: "Documentation for MethodA",
+      documentation: "/// Documentation for MethodA",
       name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"),
       isInputStreaming: false,
       isOutputStreaming: false,
@@ -35,7 +35,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
       outputType: "NamespaceA_ServiceAResponse"
     )
     let service = ServiceDescriptor(
-      documentation: "Documentation for ServiceA",
+      documentation: "/// Documentation for ServiceA",
       name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: ""),
       namespace: Name(base: "namespaceA", generatedUpperCase: "NamespaceA", generatedLowerCase: ""),
       methods: [method]
@@ -98,7 +98,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
 
   func testClientCodeTranslatorClientStreamingMethod() throws {
     let method = MethodDescriptor(
-      documentation: "Documentation for MethodA",
+      documentation: "/// Documentation for MethodA",
       name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"),
       isInputStreaming: true,
       isOutputStreaming: false,
@@ -106,7 +106,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
       outputType: "NamespaceA_ServiceAResponse"
     )
     let service = ServiceDescriptor(
-      documentation: "Documentation for ServiceA",
+      documentation: "/// Documentation for ServiceA",
       name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: ""),
       namespace: Name(base: "namespaceA", generatedUpperCase: "NamespaceA", generatedLowerCase: ""),
       methods: [method]
@@ -169,7 +169,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
 
   func testClientCodeTranslatorServerStreamingMethod() throws {
     let method = MethodDescriptor(
-      documentation: "Documentation for MethodA",
+      documentation: "/// Documentation for MethodA",
       name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"),
       isInputStreaming: false,
       isOutputStreaming: true,
@@ -177,7 +177,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
       outputType: "NamespaceA_ServiceAResponse"
     )
     let service = ServiceDescriptor(
-      documentation: "Documentation for ServiceA",
+      documentation: "/// Documentation for ServiceA",
       name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: ""),
       namespace: Name(base: "namespaceA", generatedUpperCase: "NamespaceA", generatedLowerCase: ""),
       methods: [method]
@@ -240,7 +240,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
 
   func testClientCodeTranslatorBidirectionalStreamingMethod() throws {
     let method = MethodDescriptor(
-      documentation: "Documentation for MethodA",
+      documentation: "/// Documentation for MethodA",
       name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"),
       isInputStreaming: true,
       isOutputStreaming: true,
@@ -248,7 +248,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
       outputType: "NamespaceA_ServiceAResponse"
     )
     let service = ServiceDescriptor(
-      documentation: "Documentation for ServiceA",
+      documentation: "/// Documentation for ServiceA",
       name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: ""),
       namespace: Name(base: "namespaceA", generatedUpperCase: "NamespaceA", generatedLowerCase: ""),
       methods: [method]
@@ -311,7 +311,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
 
   func testClientCodeTranslatorMultipleMethods() throws {
     let methodA = MethodDescriptor(
-      documentation: "Documentation for MethodA",
+      documentation: "/// Documentation for MethodA",
       name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"),
       isInputStreaming: true,
       isOutputStreaming: false,
@@ -319,7 +319,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
       outputType: "NamespaceA_ServiceAResponse"
     )
     let methodB = MethodDescriptor(
-      documentation: "Documentation for MethodB",
+      documentation: "/// Documentation for MethodB",
       name: Name(base: "MethodB", generatedUpperCase: "MethodB", generatedLowerCase: "methodB"),
       isInputStreaming: false,
       isOutputStreaming: true,
@@ -327,7 +327,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
       outputType: "NamespaceA_ServiceAResponse"
     )
     let service = ServiceDescriptor(
-      documentation: "Documentation for ServiceA",
+      documentation: "/// Documentation for ServiceA",
       name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: ""),
       namespace: Name(base: "namespaceA", generatedUpperCase: "NamespaceA", generatedLowerCase: ""),
       methods: [methodA, methodB]
@@ -423,7 +423,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
 
   func testClientCodeTranslatorNoNamespaceService() throws {
     let method = MethodDescriptor(
-      documentation: "Documentation for MethodA",
+      documentation: "/// Documentation for MethodA",
       name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"),
       isInputStreaming: false,
       isOutputStreaming: false,
@@ -431,7 +431,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
       outputType: "ServiceAResponse"
     )
     let service = ServiceDescriptor(
-      documentation: "Documentation for ServiceA",
+      documentation: "/// Documentation for ServiceA",
       name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: ""),
       namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""),
       methods: [method]
@@ -494,7 +494,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
 
   func testClientCodeTranslatorMultipleServices() throws {
     let serviceA = ServiceDescriptor(
-      documentation: "Documentation for ServiceA",
+      documentation: "/// Documentation for ServiceA",
       name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: ""),
       namespace: Name(
         base: "nammespaceA",
@@ -504,7 +504,11 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
       methods: []
     )
     let serviceB = ServiceDescriptor(
-      documentation: "Documentation for ServiceB",
+      documentation: """
+        /// Documentation for ServiceB
+        ///
+        /// Line 2
+        """,
       name: Name(base: "ServiceB", generatedUpperCase: "ServiceB", generatedLowerCase: ""),
       namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""),
       methods: []
@@ -523,10 +527,14 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
           }
       }
       /// Documentation for ServiceB
+      ///
+      /// Line 2
       public protocol ServiceBClientProtocol: Sendable {}
       extension ServiceB.ClientProtocol {
       }
       /// Documentation for ServiceB
+      ///
+      /// Line 2
       public struct ServiceBClient: ServiceB.ClientProtocol {
           private let client: GRPCCore.GRPCClient
           public init(client: GRPCCore.GRPCClient) {

+ 15 - 15
Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift

@@ -27,7 +27,7 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase {
 
   func testServerCodeTranslatorUnaryMethod() throws {
     let method = MethodDescriptor(
-      documentation: "Documentation for unaryMethod",
+      documentation: "/// Documentation for unaryMethod",
       name: Name(base: "UnaryMethod", generatedUpperCase: "Unary", generatedLowerCase: "unary"),
       isInputStreaming: false,
       isOutputStreaming: false,
@@ -35,7 +35,7 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase {
       outputType: "NamespaceA_ServiceAResponse"
     )
     let service = ServiceDescriptor(
-      documentation: "Documentation for ServiceA",
+      documentation: "/// Documentation for ServiceA",
       name: Name(
         base: "AlongNameForServiceA",
         generatedUpperCase: "ServiceA",
@@ -91,7 +91,7 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase {
 
   func testServerCodeTranslatorInputStreamingMethod() throws {
     let method = MethodDescriptor(
-      documentation: "Documentation for inputStreamingMethod",
+      documentation: "/// Documentation for inputStreamingMethod",
       name: Name(
         base: "InputStreamingMethod",
         generatedUpperCase: "InputStreaming",
@@ -103,7 +103,7 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase {
       outputType: "NamespaceA_ServiceAResponse"
     )
     let service = ServiceDescriptor(
-      documentation: "Documentation for ServiceA",
+      documentation: "/// Documentation for ServiceA",
       name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"),
       namespace: Name(
         base: "namespaceA",
@@ -155,7 +155,7 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase {
 
   func testServerCodeTranslatorOutputStreamingMethod() throws {
     let method = MethodDescriptor(
-      documentation: "Documentation for outputStreamingMethod",
+      documentation: "/// Documentation for outputStreamingMethod",
       name: Name(
         base: "OutputStreamingMethod",
         generatedUpperCase: "OutputStreaming",
@@ -167,7 +167,7 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase {
       outputType: "NamespaceA_ServiceAResponse"
     )
     let service = ServiceDescriptor(
-      documentation: "Documentation for ServiceA",
+      documentation: "/// Documentation for ServiceA",
       name: Name(
         base: "ServiceATest",
         generatedUpperCase: "ServiceA",
@@ -223,7 +223,7 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase {
 
   func testServerCodeTranslatorBidirectionalStreamingMethod() throws {
     let method = MethodDescriptor(
-      documentation: "Documentation for bidirectionalStreamingMethod",
+      documentation: "/// Documentation for bidirectionalStreamingMethod",
       name: Name(
         base: "BidirectionalStreamingMethod",
         generatedUpperCase: "BidirectionalStreaming",
@@ -235,7 +235,7 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase {
       outputType: "NamespaceA_ServiceAResponse"
     )
     let service = ServiceDescriptor(
-      documentation: "Documentation for ServiceA",
+      documentation: "/// Documentation for ServiceA",
       name: Name(
         base: "ServiceATest",
         generatedUpperCase: "ServiceA",
@@ -287,7 +287,7 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase {
 
   func testServerCodeTranslatorMultipleMethods() throws {
     let inputStreamingMethod = MethodDescriptor(
-      documentation: "Documentation for inputStreamingMethod",
+      documentation: "/// Documentation for inputStreamingMethod",
       name: Name(
         base: "InputStreamingMethod",
         generatedUpperCase: "InputStreaming",
@@ -299,7 +299,7 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase {
       outputType: "NamespaceA_ServiceAResponse"
     )
     let outputStreamingMethod = MethodDescriptor(
-      documentation: "Documentation for outputStreamingMethod",
+      documentation: "/// Documentation for outputStreamingMethod",
       name: Name(
         base: "outputStreamingMethod",
         generatedUpperCase: "OutputStreaming",
@@ -311,7 +311,7 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase {
       outputType: "NamespaceA_ServiceAResponse"
     )
     let service = ServiceDescriptor(
-      documentation: "Documentation for ServiceA",
+      documentation: "/// Documentation for ServiceA",
       name: Name(
         base: "ServiceATest",
         generatedUpperCase: "ServiceA",
@@ -383,7 +383,7 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase {
 
   func testServerCodeTranslatorNoNamespaceService() throws {
     let method = MethodDescriptor(
-      documentation: "Documentation for MethodA",
+      documentation: "/// Documentation for MethodA",
       name: Name(base: "methodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"),
       isInputStreaming: false,
       isOutputStreaming: false,
@@ -391,7 +391,7 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase {
       outputType: "NamespaceA_ServiceAResponse"
     )
     let service = ServiceDescriptor(
-      documentation: "Documentation for ServiceA",
+      documentation: "/// Documentation for ServiceA",
       name: Name(
         base: "ServiceATest",
         generatedUpperCase: "ServiceA",
@@ -443,7 +443,7 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase {
 
   func testServerCodeTranslatorMoreServicesOrder() throws {
     let serviceA = ServiceDescriptor(
-      documentation: "Documentation for ServiceA",
+      documentation: "/// Documentation for ServiceA",
       name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"),
       namespace: Name(
         base: "namespaceA",
@@ -453,7 +453,7 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase {
       methods: []
     )
     let serviceB = ServiceDescriptor(
-      documentation: "Documentation for ServiceB",
+      documentation: "/// Documentation for ServiceB",
       name: Name(base: "ServiceB", generatedUpperCase: "ServiceB", generatedLowerCase: "serviceB"),
       namespace: Name(
         base: "namespaceA",

+ 2 - 2
Tests/GRPCCodeGenTests/Internal/Translator/TestFunctions.swift

@@ -76,7 +76,7 @@ internal func makeCodeGenerationRequest(
 ) -> CodeGenerationRequest {
   return CodeGenerationRequest(
     fileName: "test.grpc",
-    leadingTrivia: "Some really exciting license header 2023.",
+    leadingTrivia: "/// Some really exciting license header 2023.",
     dependencies: [],
     services: services,
     lookupSerializer: {
@@ -93,7 +93,7 @@ internal func makeCodeGenerationRequest(
 ) -> CodeGenerationRequest {
   return CodeGenerationRequest(
     fileName: "test.grpc",
-    leadingTrivia: "Some really exciting license header 2023.",
+    leadingTrivia: "/// Some really exciting license header 2023.",
     dependencies: dependencies,
     services: [],
     lookupSerializer: {