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/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/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/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", 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: {