Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions Sources/GRPCCodeGen/Internal/Renderer/TextBasedRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,14 @@ struct TextBasedRenderer: RendererProtocol {

/// Renders the specified Swift file.
func renderFile(_ description: FileDescription) {
if let topComment = description.topComment { renderComment(topComment) }
if let imports = description.imports { renderImports(imports) }
if let topComment = description.topComment {
renderComment(topComment)
writer.writeLine("")
}
if let imports = description.imports {
renderImports(imports)
writer.writeLine("")
}
for codeBlock in description.codeBlocks {
renderCodeBlock(codeBlock)
writer.writeLine("")
Expand Down Expand Up @@ -165,15 +171,18 @@ struct TextBasedRenderer: RendererProtocol {
prefix = ""
commentString = string
}
if prefix.isEmpty {
writer.writeLine(commentString)
} else {
let lines = commentString.transformingLines { line in
if line.isEmpty { return prefix }
return "\(prefix) \(line)"

let lines = commentString.transformingLines { line, isLast in
// The last line of a comment that is blank should be dropped.
// Pre formatted documentation might contain such lines.
if line.isEmpty && prefix.isEmpty && isLast {
return nil
} else {
let formattedPrefix = !prefix.isEmpty && !line.isEmpty ? "\(prefix) " : prefix
return "\(formattedPrefix)\(line)"
}
lines.forEach(writer.writeLine)
}
lines.forEach(writer.writeLine)
}

/// Renders the specified import statements.
Expand Down Expand Up @@ -1095,7 +1104,9 @@ extension String {
/// The closure takes a string representing one line as a parameter.
/// - Parameter work: The closure that transforms each line.
/// - Returns: A new string where each line has been transformed using the given closure.
fileprivate func transformingLines(_ work: (String) -> String) -> [String] { asLines().map(work) }
fileprivate func transformingLines(_ work: (String, Bool) -> String?) -> [String] {
asLines().enumeratedWithLastMarker().compactMap(work)
}
}

extension TextBasedRenderer {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ struct IDLToStructuredSwiftTranslator: Translator {
try partialResult.append(translateImport(dependency: newDependency))
}

var codeBlocks: [CodeBlock] = []
var codeBlocks = [CodeBlock]()
codeBlocks.append(
contentsOf: try typealiasTranslator.translate(from: codeGenerationRequest)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ struct TypealiasTranslator: SpecializedTranslator {
}

func translate(from codeGenerationRequest: CodeGenerationRequest) throws -> [CodeBlock] {
var codeBlocks: [CodeBlock] = []
var codeBlocks = [CodeBlock]()
let services = codeGenerationRequest.services
let servicesByNamespace = Dictionary(
grouping: services,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,22 @@ final class Test_TextBasedRenderer: XCTestCase {
// Also, bar
"""#
)
try _test(
.preFormatted("/// Lorem ipsum\n"),
renderedBy: TextBasedRenderer.renderComment,
rendersAs: """
/// Lorem ipsum
"""
)
try _test(
.preFormatted("/// Lorem ipsum\n\n/// Lorem ipsum\n"),
renderedBy: TextBasedRenderer.renderComment,
rendersAs: """
/// Lorem ipsum

/// Lorem ipsum
"""
)
}

func testImports() throws {
Expand Down Expand Up @@ -825,7 +841,9 @@ final class Test_TextBasedRenderer: XCTestCase {
renderedBy: TextBasedRenderer.renderFile,
rendersAs: #"""
// hi

import Foo

struct Bar {}

"""#
Expand All @@ -847,7 +865,9 @@ final class Test_TextBasedRenderer: XCTestCase {
renderedBy: TextBasedRenderer.renderFile,
rendersAs: #"""
// hi

import Foo

struct Bar {
struct Baz {}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase {
let expectedSwift =
"""
/// Some really exciting license header 2023.

import GRPCCore
import Foo
import typealias Foo.Bar
Expand All @@ -66,6 +67,7 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase {
import let Foo.Baq
import var Foo.Bag
import func Foo.Bak

"""
try self.assertIDLToStructuredSwiftTranslation(
codeGenerationRequest: makeCodeGenerationRequest(dependencies: dependencies),
Expand Down Expand Up @@ -93,6 +95,7 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase {
let expectedSwift =
"""
/// Some really exciting license header 2023.

import GRPCCore
@preconcurrency import Foo
@preconcurrency import enum Foo.Bar
Expand All @@ -101,6 +104,7 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase {
#else
import Baz
#endif

"""
try self.assertIDLToStructuredSwiftTranslation(
codeGenerationRequest: makeCodeGenerationRequest(dependencies: dependencies),
Expand All @@ -123,9 +127,11 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase {
let expectedSwift =
"""
/// Some really exciting license header 2023.

import GRPCCore
@_spi(Secret) import Foo
@_spi(Secret) import enum Foo.Bar

"""
try self.assertIDLToStructuredSwiftTranslation(
codeGenerationRequest: makeCodeGenerationRequest(dependencies: dependencies),
Expand All @@ -134,17 +140,84 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase {
)
}

func testGeneration() throws {
var dependencies = [CodeGenerationRequest.Dependency]()
dependencies.append(CodeGenerationRequest.Dependency(module: "Foo", spi: "Secret"))
dependencies.append(
CodeGenerationRequest.Dependency(
item: .init(kind: .enum, name: "Bar"),
module: "Foo",
spi: "Secret"
)
)

let serviceA = ServiceDescriptor(
documentation: "/// Documentation for AService\n",
name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"),
namespace: Name(
base: "namespaceA",
generatedUpperCase: "NamespaceA",
generatedLowerCase: "namespaceA"
),
methods: []
)

let expectedSwift =
"""
/// Some really exciting license header 2023.

import GRPCCore
@_spi(Secret) import Foo
@_spi(Secret) import enum Foo.Bar

public enum NamespaceA {
public enum ServiceA {
public enum Methods {}
public static let methods: [MethodDescriptor] = []
public typealias StreamingServiceProtocol = NamespaceA_ServiceAServiceStreamingProtocol
public typealias ServiceProtocol = NamespaceA_ServiceAServiceProtocol
}
}

/// Documentation for AService
public protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService {}

/// Conformance to `GRPCCore.RegistrableRPCService`.
extension NamespaceA.ServiceA.StreamingServiceProtocol {
public func registerMethods(with router: inout GRPCCore.RPCRouter) {}
}

/// Documentation for AService
public protocol NamespaceA_ServiceAServiceProtocol: NamespaceA.ServiceA.StreamingServiceProtocol {}

/// Partial conformance to `NamespaceA_ServiceAStreamingServiceProtocol`.
extension NamespaceA.ServiceA.ServiceProtocol {
}

"""
try self.assertIDLToStructuredSwiftTranslation(
codeGenerationRequest: makeCodeGenerationRequest(
services: [serviceA],
dependencies: dependencies
),
expectedSwift: expectedSwift,
accessLevel: .public,
server: true
)
}

private func assertIDLToStructuredSwiftTranslation(
codeGenerationRequest: CodeGenerationRequest,
expectedSwift: String,
accessLevel: SourceGenerator.Configuration.AccessLevel
accessLevel: SourceGenerator.Configuration.AccessLevel,
server: Bool = false
) throws {
let translator = IDLToStructuredSwiftTranslator()
let structuredSwift = try translator.translate(
codeGenerationRequest: codeGenerationRequest,
accessLevel: accessLevel,
client: false,
server: false
server: server
)
let renderer = TextBasedRenderer.default
let sourceFile = try renderer.render(structured: structuredSwift)
Expand Down Expand Up @@ -262,7 +335,7 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase {

func testSameGeneratedNameServicesSameNamespaceError() throws {
let serviceA = ServiceDescriptor(
documentation: "Documentation for AService",
documentation: "/// Documentation for AService\n",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the service or method docs should be pre-formatted. We should only pre-format the leading trivia at the top of the proto file.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are also extracted from the Location objects associated with the methods and services, so they are pre-formatted. I remember we've decided to have all the docs / comments from the proto file as pre-formatted and only the ones we generate as .docs

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we decided that only the copyright header would be pre-formatted because those are the only docs we should copy verbatim. I should've picked this up in the PR which added it.

The other docs are formatted by protoSourceComments() which inserts the leading ///, which isn't actually necessary as the renderer can (and should) do it instead.

Either way we can just leave it as is for now, it's not particularly important.

name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"),
namespace: Name(
base: "namespacea",
Expand All @@ -272,7 +345,7 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase {
methods: []
)
let serviceB = ServiceDescriptor(
documentation: "Documentation for BService",
documentation: "/// Documentation for BService\n",
name: Name(base: "BService", generatedUpperCase: "AService", generatedLowerCase: "aService"),
namespace: Name(
base: "namespacea",
Expand Down
24 changes: 4 additions & 20 deletions Tests/GRPCCodeGenTests/Internal/Translator/TestFunctions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,30 +72,14 @@ internal func XCTAssertEqualWithDiff(
}

internal func makeCodeGenerationRequest(
services: [CodeGenerationRequest.ServiceDescriptor]
services: [CodeGenerationRequest.ServiceDescriptor] = [],
dependencies: [CodeGenerationRequest.Dependency] = []
) -> CodeGenerationRequest {
return CodeGenerationRequest(
fileName: "test.grpc",
leadingTrivia: "/// Some really exciting license header 2023.",
dependencies: [],
services: services,
lookupSerializer: {
"ProtobufSerializer<\($0)>()"
},
lookupDeserializer: {
"ProtobufDeserializer<\($0)>()"
}
)
}

internal func makeCodeGenerationRequest(
dependencies: [CodeGenerationRequest.Dependency]
) -> CodeGenerationRequest {
return CodeGenerationRequest(
fileName: "test.grpc",
leadingTrivia: "/// Some really exciting license header 2023.",
leadingTrivia: "/// Some really exciting license header 2023.\n",
dependencies: dependencies,
services: [],
services: services,
lookupSerializer: {
"ProtobufSerializer<\($0)>()"
},
Expand Down