I've built a SyntaxRewriter as part of transitioning to 5.10 concurrency. It finds makeBody
methods that need a MainActor.assumeIsolated
wrapper, and rewrites them with the wrapper (for backward compatibility to 15.2, I've called it runUnsafely
here). This code works well, except that I don't yet check if the change has already been applied. However, I'm wondering if it's working too hard. In particular, do I need to be reassembling the statements, then the body, then the decl, then MemberBlockItemSyntax, and finally re-inserting it into members? Or is there a more straightforward way? Is the double-reverse-prefix algorithm an appropriate way to get the indentation?
(inherits(from)
is a custom extension)
class RewriteMakeBodyRewriter: SyntaxRewriter {
override func visit(_ node: StructDeclSyntax) -> DeclSyntax {
guard
node.inherits(from: "ButtonStyle"),
let makeBody = node.memberBlock.members
.first(where: { $0.decl.as(FunctionDeclSyntax.self)?.name.text == "makeBody" }),
let decl = makeBody.decl.as(FunctionDeclSyntax.self),
let statements = decl.body?.statements
else { return DeclSyntax(node) }
let leadingSpaces = Trivia(pieces: statements.leadingTrivia.reversed().prefix { $0.isSpaceOrTab }.reversed())
let newStatements = statements.with(\.leadingTrivia, leadingSpaces).with(\.trailingTrivia, [])
let newBody = ExprSyntax("""
MainActor.runUnsafely {
\(newStatements)
\(leadingSpaces)}
""")
.with(\.leadingTrivia, statements.leadingTrivia)
.with(\.trailingTrivia, statements.trailingTrivia)
let newCodeBlock = decl.body?.with(\.statements, [CodeBlockItemSyntax(item: .expr(newBody))])
let newDecl = decl.with(\.body, newCodeBlock)
let newMakeBody = makeBody.with(\.decl, DeclSyntax(newDecl))
let index = node.memberBlock.members.index(of: makeBody)!
var newMembers = node.memberBlock
newMembers.members[index] = newMakeBody
return DeclSyntax(node.with(\.memberBlock, newMembers))
}
}
The following test case shows the function:
func testMakeBody() throws {
let source = """
public struct LinkButtonStyle: ButtonStyle {
public func makeBody(configuration: Configuration) -> some View {
// Comment inside body
VStack {
EmptyView()
}
}
}
"""
let expected = """
public struct LinkButtonStyle: ButtonStyle {
public func makeBody(configuration: Configuration) -> some View {
// Comment inside body
MainActor.runUnsafely {
VStack {
EmptyView()
}
}
}
}
"""
let result = automain().process(source: source)
XCTAssertEqual(result.description, expected)
}