Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,32 @@ import SwiftSyntax
#endif

public struct ConvertZeroParameterFunctionToComputedProperty: SyntaxRefactoringProvider {
public static func refactor(syntax: FunctionDeclSyntax, in context: ()) throws -> VariableDeclSyntax {
guard syntax.signature.parameterClause.parameters.isEmpty,
public static func refactor(
syntax: FunctionDeclSyntax,
in context: ()
) throws -> VariableDeclSyntax {

guard
syntax.signature.parameterClause.parameters.isEmpty,
let body = syntax.body
else { throw RefactoringNotApplicableError("not a zero parameter function") }
else {
throw RefactoringNotApplicableError("not a zero parameter function")
}

// MARK: - Pattern

let variableName = PatternSyntax(
IdentifierPatternSyntax(
identifier: syntax.name
)
IdentifierPatternSyntax(identifier: syntax.name)
)

// MARK: - Type annotation

let triviaFromParameters =
(syntax.signature.parameterClause.leftParen.trivia + syntax.signature.parameterClause.rightParen.trivia)
(syntax.signature.parameterClause.leftParen.trivia +
syntax.signature.parameterClause.rightParen.trivia)
.droppingTrailingWhitespace

var variableType: TypeAnnotationSyntax?
let variableType: TypeAnnotationSyntax

if let returnClause = syntax.signature.returnClause {
variableType = TypeAnnotationSyntax(
Expand All @@ -52,11 +62,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 accessorBlock: AccessorBlockSyntax

if let accessorEffectSpecifiers {
let indentedStatements = body.statements.map { $0.with(\.leadingTrivia, .spaces(4)) }
let getterBody = CodeBlockSyntax(
leftBrace: .leftBraceToken(trailingTrivia: .newline),
statements: CodeBlockItemListSyntax(indentedStatements),
rightBrace: .rightBraceToken(leadingTrivia: .newline + .spaces(2))
)

let getAccessor = AccessorDeclSyntax(
accessorSpecifier: .keyword(.get, trailingTrivia: .space),
effectSpecifiers: accessorEffectSpecifiers,
body: getterBody
).with(\.leadingTrivia, .spaces(2))

accessorBlock = AccessorBlockSyntax(
leftBrace: .leftBraceToken(trailingTrivia: .newline),
accessors: .accessors(
AccessorDeclListSyntax([getAccessor])
),
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick: Fits on one line

Suggested change
accessors: .accessors(
AccessorDeclListSyntax([getAccessor])
),
accessors: .accessors(AccessorDeclListSyntax([getAccessor])),

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done!

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))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,76 @@ 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)
}
}

private func assertRefactorConvert(
Expand Down