diff --git a/Sources/SwiftRefactor/ConvertZeroParameterFunctionToComputedProperty.swift b/Sources/SwiftRefactor/ConvertZeroParameterFunctionToComputedProperty.swift index 4c54672abfd..14033e8e014 100644 --- a/Sources/SwiftRefactor/ConvertZeroParameterFunctionToComputedProperty.swift +++ b/Sources/SwiftRefactor/ConvertZeroParameterFunctionToComputedProperty.swift @@ -12,8 +12,10 @@ #if compiler(>=6) public import SwiftSyntax +public import SwiftBasicFormat #else import SwiftSyntax +import SwiftBasicFormat #endif public struct ConvertZeroParameterFunctionToComputedProperty: SyntaxRefactoringProvider { @@ -52,11 +54,46 @@ public struct ConvertZeroParameterFunctionToComputedProperty: SyntaxRefactoringP ) } - let accessorBlock = AccessorBlockSyntax( - leftBrace: body.leftBrace, - accessors: .getter(body.statements), - rightBrace: body.rightBrace - ) + let accessorEffectSpecifiers: AccessorEffectSpecifiersSyntax? + if let fnEffectSpecifiers = syntax.signature.effectSpecifiers { + accessorEffectSpecifiers = AccessorEffectSpecifiersSyntax( + asyncSpecifier: fnEffectSpecifiers.asyncSpecifier, + throwsClause: fnEffectSpecifiers.throwsClause + ) + } else { + accessorEffectSpecifiers = nil + } + + let indentation = BasicFormat.inferIndentation(of: syntax) ?? .spaces(2) + + let accessorBlock: AccessorBlockSyntax + + if let accessorEffectSpecifiers { + let indentedStatements = body.statements.indented(by: indentation) + let getterBody = CodeBlockSyntax( + leftBrace: body.leftBrace, + statements: indentedStatements, + rightBrace: .rightBraceToken(leadingTrivia: .newline + indentation) + ) + + let getAccessor = AccessorDeclSyntax( + accessorSpecifier: .keyword(.get, trailingTrivia: .space), + effectSpecifiers: accessorEffectSpecifiers, + body: getterBody + ).with(\.leadingTrivia, indentation) + + accessorBlock = AccessorBlockSyntax( + leftBrace: .leftBraceToken(trailingTrivia: .newline), + accessors: .accessors(AccessorDeclListSyntax([getAccessor])), + rightBrace: .rightBraceToken(leadingTrivia: .newline) + ) + } else { + accessorBlock = AccessorBlockSyntax( + leftBrace: body.leftBrace, + accessors: .getter(body.statements), + rightBrace: body.rightBrace + ) + } let bindingSpecifier = syntax.funcKeyword.detached.with(\.tokenKind, .keyword(.var)) diff --git a/Tests/SwiftRefactorTest/ConvertZeroParameterFunctionToComputedPropertyTests.swift b/Tests/SwiftRefactorTest/ConvertZeroParameterFunctionToComputedPropertyTests.swift index 0394032312c..dd533cd2cf5 100644 --- a/Tests/SwiftRefactorTest/ConvertZeroParameterFunctionToComputedPropertyTests.swift +++ b/Tests/SwiftRefactorTest/ConvertZeroParameterFunctionToComputedPropertyTests.swift @@ -191,6 +191,120 @@ final class ConvertZeroParameterFunctionToComputedPropertyTests: XCTestCase { try assertRefactorConvert(baseline, expected: expected) } + + func testAsyncThrowsFunction() throws { + let baseline: DeclSyntax = """ + func foo() async throws -> Int { + try await someCall() + } + """ + + let expected: DeclSyntax = """ + var foo: Int { + get async throws { + try await someCall() + } + } + """ + + try assertRefactorConvert(baseline, expected: expected) + } + + func testAsyncOnlyFunction() throws { + let baseline: DeclSyntax = """ + func bar() async -> String { + await getValue() + } + """ + + let expected: DeclSyntax = """ + var bar: String { + get async { + await getValue() + } + } + """ + + try assertRefactorConvert(baseline, expected: expected) + } + + func testThrowsOnlyFunction() throws { + let baseline: DeclSyntax = """ + func baz() throws -> Bool { + try riskyOperation() + } + """ + + let expected: DeclSyntax = """ + var baz: Bool { + get throws { + try riskyOperation() + } + } + """ + + try assertRefactorConvert(baseline, expected: expected) + } + + func testSynchronousFunction() throws { + let baseline: DeclSyntax = """ + func qux() -> Int { + return 42 + } + """ + + let expected: DeclSyntax = """ + var qux: Int { + return 42 + } + """ + + try assertRefactorConvert(baseline, expected: expected) + } + + func testAsyncFunctionWithMultiLineStatement() throws { + let baseline: DeclSyntax = """ + func foo() async { + bar( + 1 + ) + } + """ + + let expected: DeclSyntax = """ + var foo: Void { + get async { + bar( + 1 + ) + } + } + """ + + try assertRefactorConvert(baseline, expected: expected) + } + + func testAsyncThrowsFunctionWithMultipleStatements() throws { + let baseline: DeclSyntax = """ + func complex() async throws -> String { + let x = try await fetch() + let y = process(x) + return y + } + """ + + let expected: DeclSyntax = """ + var complex: String { + get async throws { + let x = try await fetch() + let y = process(x) + return y + } + } + """ + + try assertRefactorConvert(baseline, expected: expected) + } } private func assertRefactorConvert(