From 417686a8930338f0dab596aa7667936a6719aba6 Mon Sep 17 00:00:00 2001 From: Stefana Dranca Date: Tue, 23 Jan 2024 16:44:52 +0000 Subject: [PATCH 1/2] [CodeGenLib] Render preformatted comments 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 `///`. --- .../Internal/Renderer/TextBasedRenderer.swift | 15 ++++++-- .../StructuredSwiftRepresentation.swift | 8 ++++ .../Translator/ClientCodeTranslator.swift | 11 ++++-- .../Translator/ServerCodeTranslator.swift | 8 ++-- ...lientCodeTranslatorSnippetBasedTests.swift | 38 +++++++++++-------- ...erverCodeTranslatorSnippetBasedTests.swift | 30 +++++++-------- 6 files changed, 68 insertions(+), 42 deletions(-) diff --git a/Sources/GRPCCodeGen/Internal/Renderer/TextBasedRenderer.swift b/Sources/GRPCCodeGen/Internal/Renderer/TextBasedRenderer.swift index f9f8eeba5..3fd683720 100644 --- a/Sources/GRPCCodeGen/Internal/Renderer/TextBasedRenderer.swift +++ b/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. diff --git a/Sources/GRPCCodeGen/Internal/StructuredSwiftRepresentation.swift b/Sources/GRPCCodeGen/Internal/StructuredSwiftRepresentation.swift index e5b513a51..7a91fcd94 100644 --- a/Sources/GRPCCodeGen/Internal/StructuredSwiftRepresentation.swift +++ b/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. diff --git a/Sources/GRPCCodeGen/Internal/Translator/ClientCodeTranslator.swift b/Sources/GRPCCodeGen/Internal/Translator/ClientCodeTranslator.swift index 1b83f991c..7722663df 100644 --- a/Sources/GRPCCodeGen/Internal/Translator/ClientCodeTranslator.swift +++ b/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) ) } diff --git a/Sources/GRPCCodeGen/Internal/Translator/ServerCodeTranslator.swift b/Sources/GRPCCodeGen/Internal/Translator/ServerCodeTranslator.swift index dbf4914d6..feb858a3c 100644 --- a/Sources/GRPCCodeGen/Internal/Translator/ServerCodeTranslator.swift +++ b/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)) ) } diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift b/Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift index b97d3ca2f..20045e7f2 100644 --- a/Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift +++ b/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) { diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift b/Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift index d0a4a5975..3f13ec0c6 100644 --- a/Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift +++ b/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", From b6d3a71e28d4ed03f3edf397972ed5847e43e12e Mon Sep 17 00:00:00 2001 From: Stefana Dranca Date: Tue, 23 Jan 2024 17:10:11 +0000 Subject: [PATCH 2/2] changed leading trivia into pre formatted and added more information in CodegenerationRequest documentation --- Sources/GRPCCodeGen/CodeGenerationRequest.swift | 5 ++++- .../Internal/Translator/IDLToStructuredSwiftTranslator.swift | 2 +- .../GRPCCodeGenTests/Internal/Translator/TestFunctions.swift | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Sources/GRPCCodeGen/CodeGenerationRequest.swift b/Sources/GRPCCodeGen/CodeGenerationRequest.swift index fab71b977..9711b39d2 100644 --- a/Sources/GRPCCodeGen/CodeGenerationRequest.swift +++ b/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. diff --git a/Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift b/Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift index 2f8a81707..b35a85d1b 100644 --- a/Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift +++ b/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 diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/TestFunctions.swift b/Tests/GRPCCodeGenTests/Internal/Translator/TestFunctions.swift index 1250b92d9..800ff7393 100644 --- a/Tests/GRPCCodeGenTests/Internal/Translator/TestFunctions.swift +++ b/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: {