Skip to content

Commit c70d1aa

Browse files
authored
Merge pull request #2320 from kimdv/kimdv/2307-macrosystem-should-add-newline-at-the-end-of-added-members-for-an-empty-type
Fix missing newline in member macro
2 parents 4e7d61e + a331a74 commit c70d1aa

File tree

8 files changed

+201
-9
lines changed

8 files changed

+201
-9
lines changed

Examples/Tests/MacroExamples/Implementation/Peer/AddAsyncMacroTests.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ final class AddAsyncMacroTests: XCTestCase {
4343
}
4444
}
4545
}
46+
4647
}
4748
"""#,
4849
macros: macros,
@@ -70,6 +71,7 @@ final class AddAsyncMacroTests: XCTestCase {
7071
continuation.resume(returning: returnValue)
7172
}
7273
}
74+
7375
}
7476
""",
7577
macros: macros,

Examples/Tests/MacroExamples/Implementation/Peer/AddCompletionHandlerMacroTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ final class AddCompletionHandlerMacroTests: XCTestCase {
3535
Task {
3636
completionHandler(await f(a: a, for: b, value))
3737
}
38+
3839
}
3940
""",
4041
macros: macros,

Sources/SwiftParser/ParseSourceFile.swift

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,9 @@ extension Parser {
152152
}
153153

154154
let remainingTokens = self.consumeRemainingTokens()
155+
155156
if remainingTokens.isEmpty {
156-
return into
157+
return R.init(transferTrailingTrivaFromEndOfFileIfPresent(raw: into.raw))!
157158
}
158159

159160
let existingUnexpected: [RawSyntax]
@@ -166,6 +167,33 @@ extension Parser {
166167
let unexpected = RawUnexpectedNodesSyntax(elements: existingUnexpected + remainingTokens, arena: self.arena)
167168

168169
let withUnexpected = layout.replacingChild(at: layout.children.count - 1, with: unexpected.raw, arena: self.arena)
169-
return R.init(withUnexpected)!
170+
171+
return R.init(transferTrailingTrivaFromEndOfFileIfPresent(raw: withUnexpected))!
172+
}
173+
174+
/// Parses the end-of-file token and appends its leading trivia to the provided `RawSyntax`.
175+
/// - Parameter raw: The raw syntax node to which the leading trivia of the end-of-file token will be appended.
176+
/// - Returns: A new `RawSyntax` instance with trailing trivia transferred from the end-of-file token if present, otherwise it will return the raw parameter..
177+
private mutating func transferTrailingTrivaFromEndOfFileIfPresent(raw: RawSyntax) -> RawSyntax {
178+
guard let endOfFileToken = self.consume(if: .endOfFile),
179+
!endOfFileToken.leadingTriviaPieces.isEmpty,
180+
let raw = raw.withTrailingTrivia(
181+
Trivia(
182+
rawPieces: (raw.trailingTriviaPieces ?? []) + endOfFileToken.leadingTriviaPieces
183+
),
184+
arena: self.arena
185+
)
186+
else {
187+
return raw
188+
}
189+
190+
return raw
191+
}
192+
}
193+
194+
private extension Trivia {
195+
init(rawPieces: [RawTriviaPiece]) {
196+
let pieces = rawPieces.map(TriviaPiece.init(raw:))
197+
self.init(pieces: pieces)
170198
}
171199
}

Sources/SwiftSyntax/Raw/RawSyntax.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,8 @@ extension RawSyntax {
302302
/// - Parameters:
303303
/// - leadingTrivia: The trivia to attach.
304304
/// - arena: SyntaxArena to the result node data resides.
305-
func withLeadingTrivia(_ leadingTrivia: Trivia, arena: SyntaxArena) -> RawSyntax? {
305+
@_spi(RawSyntax)
306+
public func withLeadingTrivia(_ leadingTrivia: Trivia, arena: SyntaxArena) -> RawSyntax? {
306307
switch view {
307308
case .token(let tokenView):
308309
return .makeMaterializedToken(
@@ -328,7 +329,8 @@ extension RawSyntax {
328329
/// - Parameters:
329330
/// - trailingTrivia: The trivia to attach.
330331
/// - arena: SyntaxArena to the result node data resides.
331-
func withTrailingTrivia(_ trailingTrivia: Trivia, arena: SyntaxArena) -> RawSyntax? {
332+
@_spi(RawSyntax)
333+
public func withTrailingTrivia(_ trailingTrivia: Trivia, arena: SyntaxArena) -> RawSyntax? {
332334
switch view {
333335
case .token(let tokenView):
334336
return .makeMaterializedToken(

Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -762,10 +762,8 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
762762
return CodeBlockItemListSyntax(newItems)
763763
}
764764

765-
override func visit(_ node: MemberBlockItemListSyntax) -> MemberBlockItemListSyntax {
765+
override func visit(_ node: MemberBlockSyntax) -> MemberBlockSyntax {
766766
let parentDeclGroup = node
767-
.parent?
768-
.as(MemberBlockSyntax.self)?
769767
.parent?
770768
.as(DeclSyntax.self)
771769
var newItems: [MemberBlockItemSyntax] = []
@@ -792,7 +790,7 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
792790
extensions += expandExtensions(of: node.decl)
793791
}
794792

795-
for var item in node {
793+
for var item in node.members {
796794
// Expand member attribute members attached to the declaration context.
797795
// Note that MemberAttribute macros are _not_ applied to generated members
798796
if let parentDeclGroup, let decl = item.decl.asProtocol(WithAttributesSyntax.self) {
@@ -825,7 +823,29 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
825823
}
826824
}
827825

828-
return .init(newItems)
826+
/// Returns an leading trivia for the member blocks closing brace.
827+
/// It will add a leading newline, if there is none.
828+
var leadingTriviaForClosingBrace: Trivia {
829+
if newItems.isEmpty {
830+
return node.rightBrace.leadingTrivia
831+
}
832+
833+
if node.rightBrace.leadingTrivia.contains(where: { $0.isNewline }) {
834+
return node.rightBrace.leadingTrivia
835+
}
836+
837+
if newItems.last?.trailingTrivia.pieces.last?.isNewline ?? false {
838+
return node.rightBrace.leadingTrivia
839+
} else {
840+
return .newline + node.rightBrace.leadingTrivia
841+
}
842+
}
843+
844+
return MemberBlockSyntax(
845+
leftBrace: node.leftBrace,
846+
members: MemberBlockItemListSyntax(newItems),
847+
rightBrace: node.rightBrace.with(\.leadingTrivia, leadingTriviaForClosingBrace)
848+
)
829849
}
830850

831851
override func visit(_ node: VariableDeclSyntax) -> DeclSyntax {

Tests/SwiftBasicFormatTest/BasicFormatTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,7 @@ final class BasicFormatTest: XCTestCase {
588588
func test() {
589589
Task {
590590
}
591+
591592
}
592593
"""
593594
)

Tests/SwiftParserTest/DeclarationTests.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3141,4 +3141,17 @@ final class DeclarationTests: ParserTestCase {
31413141
experimentalFeatures: .nonescapableTypes
31423142
)
31433143
}
3144+
3145+
func testDeclarationEndingWithNewline() {
3146+
let inputs: [UInt: String] = [
3147+
#line: "var x = 0\n",
3148+
#line: "var x = 0 garbage\n",
3149+
#line: "var x = 0 \n",
3150+
]
3151+
3152+
for (line, input) in inputs {
3153+
let decl = DeclSyntax(stringLiteral: input)
3154+
XCTAssertEqual(decl.description, input, line: line)
3155+
}
3156+
}
31443157
}

Tests/SwiftSyntaxMacroExpansionTest/MemberMacroTests.swift

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,4 +299,129 @@ final class MemberMacroTests: XCTestCase {
299299
]
300300
)
301301
}
302+
303+
func testAddMemberToEmptyDeclaration() {
304+
struct TestMacro: MemberMacro {
305+
static func expansion(
306+
of node: AttributeSyntax,
307+
providingMembersOf declaration: some DeclGroupSyntax,
308+
conformingTo protocols: [TypeSyntax],
309+
in context: some MacroExpansionContext
310+
) throws -> [DeclSyntax] {
311+
return [DeclSyntax("var x = 0")]
312+
}
313+
}
314+
315+
assertMacroExpansion(
316+
"""
317+
@Test
318+
struct Foo {}
319+
""",
320+
expandedSource: """
321+
struct Foo {
322+
323+
var x = 0
324+
}
325+
""",
326+
macros: [
327+
"Test": TestMacro.self
328+
],
329+
indentationWidth: indentationWidth
330+
)
331+
}
332+
333+
func testAddTwoMembersToEmptyDeclaration() {
334+
struct TestMacro: MemberMacro {
335+
static func expansion(
336+
of node: AttributeSyntax,
337+
providingMembersOf declaration: some DeclGroupSyntax,
338+
conformingTo protocols: [TypeSyntax],
339+
in context: some MacroExpansionContext
340+
) throws -> [DeclSyntax] {
341+
return [DeclSyntax("var x = 0"), DeclSyntax("var x = 0")]
342+
}
343+
}
344+
345+
assertMacroExpansion(
346+
"""
347+
@Test
348+
struct Foo {}
349+
""",
350+
expandedSource: """
351+
struct Foo {
352+
353+
var x = 0
354+
355+
var x = 0
356+
}
357+
""",
358+
macros: [
359+
"Test": TestMacro.self
360+
],
361+
indentationWidth: indentationWidth
362+
)
363+
}
364+
365+
func testAddMemberToEmptyDeclarationWithEndingNewline() {
366+
struct TestMacro: MemberMacro {
367+
static func expansion(
368+
of node: AttributeSyntax,
369+
providingMembersOf declaration: some DeclGroupSyntax,
370+
conformingTo protocols: [TypeSyntax],
371+
in context: some MacroExpansionContext
372+
) throws -> [DeclSyntax] {
373+
return [DeclSyntax("var x = 0\n")]
374+
}
375+
}
376+
377+
assertMacroExpansion(
378+
"""
379+
@Test
380+
struct Foo {}
381+
""",
382+
expandedSource: """
383+
struct Foo {
384+
385+
var x = 0
386+
}
387+
""",
388+
macros: [
389+
"Test": TestMacro.self
390+
],
391+
indentationWidth: indentationWidth
392+
)
393+
}
394+
395+
func testAddMemberToDeclarationWithASingleVariable() {
396+
struct TestMacro: MemberMacro {
397+
static func expansion(
398+
of node: AttributeSyntax,
399+
providingMembersOf declaration: some DeclGroupSyntax,
400+
conformingTo protocols: [TypeSyntax],
401+
in context: some MacroExpansionContext
402+
) throws -> [DeclSyntax] {
403+
return [DeclSyntax("var x = 0\n")]
404+
}
405+
}
406+
407+
assertMacroExpansion(
408+
"""
409+
@Test
410+
struct Foo {
411+
var y = 0
412+
}
413+
""",
414+
expandedSource: """
415+
struct Foo {
416+
var y = 0
417+
418+
var x = 0
419+
}
420+
""",
421+
macros: [
422+
"Test": TestMacro.self
423+
],
424+
indentationWidth: indentationWidth
425+
)
426+
}
302427
}

0 commit comments

Comments
 (0)