Skip to content

Code actions to convert between computed properties and zero-parameter functions #2721

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions Sources/SwiftRefactor/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ add_swift_syntax_library(SwiftRefactor
AddSeparatorsToIntegerLiteral.swift
CallToTrailingClosures.swift
ConvertComputedPropertyToStored.swift
ConvertComputedPropertyToZeroParameterFunction.swift
ConvertStoredPropertyToComputed.swift
ConvertZeroParameterFunctionToComputedProperty.swift
ExpandEditorPlaceholder.swift
FormatRawStringLiteral.swift
IntegerLiteralUtilities.swift
MigrateToNewIfLetSyntax.swift
OpaqueParameterToGeneric.swift
RefactoringProvider.swift
RemoveSeparatorsFromIntegerLiteral.swift
SyntaxUtils.swift
)

target_link_swift_syntax_libraries(SwiftRefactor PUBLIC
Expand Down
6 changes: 0 additions & 6 deletions Sources/SwiftRefactor/CallToTrailingClosures.swift
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,6 @@ extension FunctionCallExprSyntax {
}
}

fileprivate extension Trivia {
var droppingLeadingWhitespace: Trivia {
return Trivia(pieces: self.drop(while: \.isWhitespace))
}
}

fileprivate extension Sequence {
func dropSuffix(while predicate: (Element) -> Bool) -> [Element] {
self.reversed().drop(while: predicate).reversed()
Expand Down
12 changes: 0 additions & 12 deletions Sources/SwiftRefactor/ConvertComputedPropertyToStored.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,3 @@ public struct ConvertComputedPropertyToStored: SyntaxRefactoringProvider {
return nil
}
}

fileprivate extension TokenSyntax {
var trivia: Trivia {
return leadingTrivia + trailingTrivia
}
}

fileprivate extension Trivia {
var droppingTrailingWhitespace: Trivia {
return Trivia(pieces: self.reversed().drop(while: \.isWhitespace).reversed())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#if swift(>=6)
public import SwiftSyntax
#else
import SwiftSyntax
#endif

public struct ConvertComputedPropertyToZeroParameterFunction: SyntaxRefactoringProvider {
public static func refactor(syntax: VariableDeclSyntax, in context: Void) -> FunctionDeclSyntax? {
guard syntax.bindings.count == 1,
let binding = syntax.bindings.first,
let identifierPattern = binding.pattern.as(IdentifierPatternSyntax.self)
else { return nil }

var statements: CodeBlockItemListSyntax

guard let typeAnnotation = binding.typeAnnotation,
var accessorBlock = binding.accessorBlock
else { return nil }

var effectSpecifiers: AccessorEffectSpecifiersSyntax?

switch accessorBlock.accessors {
case .accessors(let accessors):
guard accessors.count == 1, let accessor = accessors.first,
accessor.accessorSpecifier.tokenKind == .keyword(.get), let codeBlock = accessor.body
else { return nil }
effectSpecifiers = accessor.effectSpecifiers
statements = codeBlock.statements
let accessorSpecifier = accessor.accessorSpecifier
statements.leadingTrivia =
accessorSpecifier.leadingTrivia + accessorSpecifier.trailingTrivia.droppingLeadingWhitespace
+ codeBlock.leftBrace.leadingTrivia.droppingLeadingWhitespace
+ codeBlock.leftBrace.trailingTrivia.droppingLeadingWhitespace
+ statements.leadingTrivia
statements.trailingTrivia += codeBlock.rightBrace.trivia.droppingLeadingWhitespace
statements.trailingTrivia = statements.trailingTrivia.droppingTrailingWhitespace
case .getter(let codeBlock):
statements = codeBlock
}

let returnType = typeAnnotation.type

var returnClause: ReturnClauseSyntax?
let triviaAfterSignature: Trivia

if !returnType.isVoid {
triviaAfterSignature = .space
returnClause = ReturnClauseSyntax(
arrow: .arrowToken(
leadingTrivia: typeAnnotation.colon.leadingTrivia,
trailingTrivia: typeAnnotation.colon.trailingTrivia
),
type: returnType
)
} else {
triviaAfterSignature = typeAnnotation.colon.leadingTrivia + typeAnnotation.colon.trailingTrivia
}

accessorBlock.leftBrace.leadingTrivia = accessorBlock.leftBrace.leadingTrivia.droppingLeadingWhitespace
accessorBlock.rightBrace.trailingTrivia = accessorBlock.rightBrace.trailingTrivia.droppingTrailingWhitespace

let body = CodeBlockSyntax(
leftBrace: accessorBlock.leftBrace,
statements: statements,
rightBrace: accessorBlock.rightBrace
)

var parameterClause = FunctionParameterClauseSyntax(parameters: [])
parameterClause.trailingTrivia = identifierPattern.identifier.trailingTrivia + triviaAfterSignature

let functionEffectSpecifiers = FunctionEffectSpecifiersSyntax(
asyncSpecifier: effectSpecifiers?.asyncSpecifier,
throwsClause: effectSpecifiers?.throwsClause
)
let functionSignature = FunctionSignatureSyntax(
parameterClause: parameterClause,
effectSpecifiers: functionEffectSpecifiers,
returnClause: returnClause
)

return FunctionDeclSyntax(
modifiers: syntax.modifiers,
funcKeyword: .keyword(
.func,
leadingTrivia: syntax.bindingSpecifier.leadingTrivia,
trailingTrivia: syntax.bindingSpecifier.trailingTrivia
),
name: identifierPattern.identifier.with(\.trailingTrivia, []),
signature: functionSignature,
body: body
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#if swift(>=6)
public import SwiftSyntax
#else
import SwiftSyntax
#endif

public struct ConvertZeroParameterFunctionToComputedProperty: SyntaxRefactoringProvider {
public static func refactor(syntax: FunctionDeclSyntax, in context: ()) -> VariableDeclSyntax? {
guard syntax.signature.parameterClause.parameters.isEmpty,
let body = syntax.body
else { return nil }

let variableName = PatternSyntax(
IdentifierPatternSyntax(
leadingTrivia: syntax.funcKeyword.trailingTrivia,
identifier: syntax.name
)
)

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

var variableType: TypeAnnotationSyntax?

if let returnClause = syntax.signature.returnClause {
variableType = TypeAnnotationSyntax(
colon: .colonToken(
leadingTrivia: triviaFromParameters + returnClause.arrow.leadingTrivia,
trailingTrivia: returnClause.arrow.trailingTrivia
),
type: returnClause.type
)
} else {
variableType = TypeAnnotationSyntax(
colon: .colonToken(
leadingTrivia: triviaFromParameters,
trailingTrivia: .space
),
type: TypeSyntax("Void").with(\.trailingTrivia, .space)
)
}

let accessorBlock = AccessorBlockSyntax(
leftBrace: body.leftBrace,
accessors: .getter(body.statements),
rightBrace: body.rightBrace
)

return VariableDeclSyntax(
modifiers: syntax.modifiers,
.var,
name: variableName,
type: variableType,
accessorBlock: accessorBlock
)
}
}
43 changes: 43 additions & 0 deletions Sources/SwiftRefactor/SyntaxUtils.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#if swift(>=6)
public import SwiftSyntax
#else
import SwiftSyntax
#endif

extension TokenSyntax {
var trivia: Trivia {
return leadingTrivia + trailingTrivia
}
}

extension Trivia {
var droppingLeadingWhitespace: Trivia {
return Trivia(pieces: self.drop(while: \.isWhitespace))
}

var droppingTrailingWhitespace: Trivia {
return Trivia(pieces: self.reversed().drop(while: \.isWhitespace).reversed())
}
}

extension TypeSyntax {
var isVoid: Bool {
switch self.as(TypeSyntaxEnum.self) {
case .identifierType(let identifierType) where identifierType.name.text == "Void": return true
case .tupleType(let tupleType) where tupleType.elements.isEmpty: return true
default: return false
}
}
}
6 changes: 4 additions & 2 deletions Sources/SwiftSyntaxBuilder/ConvenienceInitializers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,8 @@ extension VariableDeclSyntax {
_ bindingSpecifier: Keyword,
name: PatternSyntax,
type: TypeAnnotationSyntax? = nil,
initializer: InitializerClauseSyntax? = nil
initializer: InitializerClauseSyntax? = nil,
accessorBlock: AccessorBlockSyntax? = nil
) {
self.init(
leadingTrivia: leadingTrivia,
Expand All @@ -414,7 +415,8 @@ extension VariableDeclSyntax {
PatternBindingSyntax(
pattern: name,
typeAnnotation: type,
initializer: initializer
initializer: initializer,
accessorBlock: accessorBlock
)
}
}
Expand Down
Loading