Skip to content

Commit 8ac3470

Browse files
Update renderer-blank line after imports, delete blank line from docs (#1782)
Motivation: We want to have a blank line between the imports and the generated code and no blank line between the docs and the protocols/methods declarations. Modifications: Updated the TextBasedRenderer and the tests. Result: The generated code will have the right format.
1 parent 2c27ad5 commit 8ac3470

File tree

6 files changed

+124
-36
lines changed

6 files changed

+124
-36
lines changed

Sources/GRPCCodeGen/Internal/Renderer/TextBasedRenderer.swift

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,14 @@ struct TextBasedRenderer: RendererProtocol {
136136

137137
/// Renders the specified Swift file.
138138
func renderFile(_ description: FileDescription) {
139-
if let topComment = description.topComment { renderComment(topComment) }
140-
if let imports = description.imports { renderImports(imports) }
139+
if let topComment = description.topComment {
140+
renderComment(topComment)
141+
writer.writeLine("")
142+
}
143+
if let imports = description.imports {
144+
renderImports(imports)
145+
writer.writeLine("")
146+
}
141147
for codeBlock in description.codeBlocks {
142148
renderCodeBlock(codeBlock)
143149
writer.writeLine("")
@@ -165,15 +171,18 @@ struct TextBasedRenderer: RendererProtocol {
165171
prefix = ""
166172
commentString = string
167173
}
168-
if prefix.isEmpty {
169-
writer.writeLine(commentString)
170-
} else {
171-
let lines = commentString.transformingLines { line in
172-
if line.isEmpty { return prefix }
173-
return "\(prefix) \(line)"
174+
175+
let lines = commentString.transformingLines { line, isLast in
176+
// The last line of a comment that is blank should be dropped.
177+
// Pre formatted documentation might contain such lines.
178+
if line.isEmpty && prefix.isEmpty && isLast {
179+
return nil
180+
} else {
181+
let formattedPrefix = !prefix.isEmpty && !line.isEmpty ? "\(prefix) " : prefix
182+
return "\(formattedPrefix)\(line)"
174183
}
175-
lines.forEach(writer.writeLine)
176184
}
185+
lines.forEach(writer.writeLine)
177186
}
178187

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

11011112
extension TextBasedRenderer {

Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ struct IDLToStructuredSwiftTranslator: Translator {
3838
try partialResult.append(translateImport(dependency: newDependency))
3939
}
4040

41-
var codeBlocks: [CodeBlock] = []
41+
var codeBlocks = [CodeBlock]()
4242
codeBlocks.append(
4343
contentsOf: try typealiasTranslator.translate(from: codeGenerationRequest)
4444
)

Sources/GRPCCodeGen/Internal/Translator/TypealiasTranslator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ struct TypealiasTranslator: SpecializedTranslator {
6464
}
6565

6666
func translate(from codeGenerationRequest: CodeGenerationRequest) throws -> [CodeBlock] {
67-
var codeBlocks: [CodeBlock] = []
67+
var codeBlocks = [CodeBlock]()
6868
let services = codeGenerationRequest.services
6969
let servicesByNamespace = Dictionary(
7070
grouping: services,

Tests/GRPCCodeGenTests/Internal/Renderer/TextBasedRendererTests.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,22 @@ final class Test_TextBasedRenderer: XCTestCase {
8989
// Also, bar
9090
"""#
9191
)
92+
try _test(
93+
.preFormatted("/// Lorem ipsum\n"),
94+
renderedBy: TextBasedRenderer.renderComment,
95+
rendersAs: """
96+
/// Lorem ipsum
97+
"""
98+
)
99+
try _test(
100+
.preFormatted("/// Lorem ipsum\n\n/// Lorem ipsum\n"),
101+
renderedBy: TextBasedRenderer.renderComment,
102+
rendersAs: """
103+
/// Lorem ipsum
104+
105+
/// Lorem ipsum
106+
"""
107+
)
92108
}
93109

94110
func testImports() throws {
@@ -825,7 +841,9 @@ final class Test_TextBasedRenderer: XCTestCase {
825841
renderedBy: TextBasedRenderer.renderFile,
826842
rendersAs: #"""
827843
// hi
844+
828845
import Foo
846+
829847
struct Bar {}
830848

831849
"""#
@@ -847,7 +865,9 @@ final class Test_TextBasedRenderer: XCTestCase {
847865
renderedBy: TextBasedRenderer.renderFile,
848866
rendersAs: #"""
849867
// hi
868+
850869
import Foo
870+
851871
struct Bar {
852872
struct Baz {}
853873
}

Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase {
5656
let expectedSwift =
5757
"""
5858
/// Some really exciting license header 2023.
59+
5960
import GRPCCore
6061
import Foo
6162
import typealias Foo.Bar
@@ -66,6 +67,7 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase {
6667
import let Foo.Baq
6768
import var Foo.Bag
6869
import func Foo.Bak
70+
6971
"""
7072
try self.assertIDLToStructuredSwiftTranslation(
7173
codeGenerationRequest: makeCodeGenerationRequest(dependencies: dependencies),
@@ -93,6 +95,7 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase {
9395
let expectedSwift =
9496
"""
9597
/// Some really exciting license header 2023.
98+
9699
import GRPCCore
97100
@preconcurrency import Foo
98101
@preconcurrency import enum Foo.Bar
@@ -101,6 +104,7 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase {
101104
#else
102105
import Baz
103106
#endif
107+
104108
"""
105109
try self.assertIDLToStructuredSwiftTranslation(
106110
codeGenerationRequest: makeCodeGenerationRequest(dependencies: dependencies),
@@ -123,9 +127,11 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase {
123127
let expectedSwift =
124128
"""
125129
/// Some really exciting license header 2023.
130+
126131
import GRPCCore
127132
@_spi(Secret) import Foo
128133
@_spi(Secret) import enum Foo.Bar
134+
129135
"""
130136
try self.assertIDLToStructuredSwiftTranslation(
131137
codeGenerationRequest: makeCodeGenerationRequest(dependencies: dependencies),
@@ -134,17 +140,84 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase {
134140
)
135141
}
136142

143+
func testGeneration() throws {
144+
var dependencies = [CodeGenerationRequest.Dependency]()
145+
dependencies.append(CodeGenerationRequest.Dependency(module: "Foo", spi: "Secret"))
146+
dependencies.append(
147+
CodeGenerationRequest.Dependency(
148+
item: .init(kind: .enum, name: "Bar"),
149+
module: "Foo",
150+
spi: "Secret"
151+
)
152+
)
153+
154+
let serviceA = ServiceDescriptor(
155+
documentation: "/// Documentation for AService\n",
156+
name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"),
157+
namespace: Name(
158+
base: "namespaceA",
159+
generatedUpperCase: "NamespaceA",
160+
generatedLowerCase: "namespaceA"
161+
),
162+
methods: []
163+
)
164+
165+
let expectedSwift =
166+
"""
167+
/// Some really exciting license header 2023.
168+
169+
import GRPCCore
170+
@_spi(Secret) import Foo
171+
@_spi(Secret) import enum Foo.Bar
172+
173+
public enum NamespaceA {
174+
public enum ServiceA {
175+
public enum Methods {}
176+
public static let methods: [MethodDescriptor] = []
177+
public typealias StreamingServiceProtocol = NamespaceA_ServiceAServiceStreamingProtocol
178+
public typealias ServiceProtocol = NamespaceA_ServiceAServiceProtocol
179+
}
180+
}
181+
182+
/// Documentation for AService
183+
public protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService {}
184+
185+
/// Conformance to `GRPCCore.RegistrableRPCService`.
186+
extension NamespaceA.ServiceA.StreamingServiceProtocol {
187+
public func registerMethods(with router: inout GRPCCore.RPCRouter) {}
188+
}
189+
190+
/// Documentation for AService
191+
public protocol NamespaceA_ServiceAServiceProtocol: NamespaceA.ServiceA.StreamingServiceProtocol {}
192+
193+
/// Partial conformance to `NamespaceA_ServiceAStreamingServiceProtocol`.
194+
extension NamespaceA.ServiceA.ServiceProtocol {
195+
}
196+
197+
"""
198+
try self.assertIDLToStructuredSwiftTranslation(
199+
codeGenerationRequest: makeCodeGenerationRequest(
200+
services: [serviceA],
201+
dependencies: dependencies
202+
),
203+
expectedSwift: expectedSwift,
204+
accessLevel: .public,
205+
server: true
206+
)
207+
}
208+
137209
private func assertIDLToStructuredSwiftTranslation(
138210
codeGenerationRequest: CodeGenerationRequest,
139211
expectedSwift: String,
140-
accessLevel: SourceGenerator.Configuration.AccessLevel
212+
accessLevel: SourceGenerator.Configuration.AccessLevel,
213+
server: Bool = false
141214
) throws {
142215
let translator = IDLToStructuredSwiftTranslator()
143216
let structuredSwift = try translator.translate(
144217
codeGenerationRequest: codeGenerationRequest,
145218
accessLevel: accessLevel,
146219
client: false,
147-
server: false
220+
server: server
148221
)
149222
let renderer = TextBasedRenderer.default
150223
let sourceFile = try renderer.render(structured: structuredSwift)
@@ -262,7 +335,7 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase {
262335

263336
func testSameGeneratedNameServicesSameNamespaceError() throws {
264337
let serviceA = ServiceDescriptor(
265-
documentation: "Documentation for AService",
338+
documentation: "/// Documentation for AService\n",
266339
name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"),
267340
namespace: Name(
268341
base: "namespacea",
@@ -272,7 +345,7 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase {
272345
methods: []
273346
)
274347
let serviceB = ServiceDescriptor(
275-
documentation: "Documentation for BService",
348+
documentation: "/// Documentation for BService\n",
276349
name: Name(base: "BService", generatedUpperCase: "AService", generatedLowerCase: "aService"),
277350
namespace: Name(
278351
base: "namespacea",

Tests/GRPCCodeGenTests/Internal/Translator/TestFunctions.swift

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -72,30 +72,14 @@ internal func XCTAssertEqualWithDiff(
7272
}
7373

7474
internal func makeCodeGenerationRequest(
75-
services: [CodeGenerationRequest.ServiceDescriptor]
75+
services: [CodeGenerationRequest.ServiceDescriptor] = [],
76+
dependencies: [CodeGenerationRequest.Dependency] = []
7677
) -> CodeGenerationRequest {
7778
return CodeGenerationRequest(
7879
fileName: "test.grpc",
79-
leadingTrivia: "/// Some really exciting license header 2023.",
80-
dependencies: [],
81-
services: services,
82-
lookupSerializer: {
83-
"ProtobufSerializer<\($0)>()"
84-
},
85-
lookupDeserializer: {
86-
"ProtobufDeserializer<\($0)>()"
87-
}
88-
)
89-
}
90-
91-
internal func makeCodeGenerationRequest(
92-
dependencies: [CodeGenerationRequest.Dependency]
93-
) -> CodeGenerationRequest {
94-
return CodeGenerationRequest(
95-
fileName: "test.grpc",
96-
leadingTrivia: "/// Some really exciting license header 2023.",
80+
leadingTrivia: "/// Some really exciting license header 2023.\n",
9781
dependencies: dependencies,
98-
services: [],
82+
services: services,
9983
lookupSerializer: {
10084
"ProtobufSerializer<\($0)>()"
10185
},

0 commit comments

Comments
 (0)