Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,18 @@ final class SourceEditorCommand: NSObject, XCSourceEditorCommand {
}

override func visitAny(_ node: Syntax) -> Syntax? {
func withOpenedRefactoringProvider<T: SyntaxRefactoringProvider>(_ providerType: T.Type) -> Syntax? {
func withOpenedRefactoringProvider<T: SyntaxRefactoringProvider>(_ providerType: T.Type) throws -> Syntax? {
guard
let input = node.as(providerType.Input.self),
providerType.Context.self == Void.self
else {
return nil
}
let context = unsafeBitCast(Void(), to: providerType.Context.self)
return providerType.refactor(syntax: input, in: context).map { Syntax($0) }
return try Syntax(providerType.refactor(syntax: input, in: context))
}

return _openExistential(self.provider, do: withOpenedRefactoringProvider)
return try? _openExistential(self.provider, do: withOpenedRefactoringProvider)
}
}

Expand Down
9 changes: 5 additions & 4 deletions Sources/SwiftRefactor/AddSeparatorsToIntegerLiteral.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,12 @@ import SwiftSyntax
/// 0b1_010
/// ```
public struct AddSeparatorsToIntegerLiteral: SyntaxRefactoringProvider {
public static func refactor(syntax lit: IntegerLiteralExprSyntax, in context: Void) -> IntegerLiteralExprSyntax? {
public static func refactor(
syntax lit: IntegerLiteralExprSyntax,
in context: Void
) throws -> IntegerLiteralExprSyntax {
if lit.literal.text.contains("_") {
guard let strippedLiteral = RemoveSeparatorsFromIntegerLiteral.refactor(syntax: lit) else {
return nil
}
let strippedLiteral = try RemoveSeparatorsFromIntegerLiteral.refactor(syntax: lit)
return self.addSeparators(to: strippedLiteral)
} else {
return self.addSeparators(to: lit)
Expand Down
34 changes: 19 additions & 15 deletions Sources/SwiftRefactor/CallToTrailingClosures.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,39 +55,43 @@ public struct CallToTrailingClosures: SyntaxRefactoringProvider {

/// Apply the refactoring to a given syntax node. If either a
/// non-function-like syntax node is passed, or the refactoring fails,
/// `nil` is returned.
// TODO: Rather than returning nil, we should consider throwing errors with
// appropriate messages instead.
/// an error is thrown.
public static func refactor(
syntax: Syntax,
in context: Context = Context()
) -> Syntax? {
guard let call = syntax.asProtocol(CallLikeSyntax.self) else { return nil }
return Syntax(fromProtocol: _refactor(syntax: call, in: context))
) throws -> Syntax {
guard let call = syntax.asProtocol(CallLikeSyntax.self) else {
throw RefactoringNotApplicableError("not a call")
}
return try Syntax(fromProtocol: _refactor(syntax: call, in: context))
}

@available(*, deprecated, message: "Pass a Syntax argument instead of FunctionCallExprSyntax")
public static func refactor(
syntax call: FunctionCallExprSyntax,
in context: Context = Context()
) -> FunctionCallExprSyntax? {
_refactor(syntax: call, in: context)
) throws -> FunctionCallExprSyntax {
try _refactor(syntax: call, in: context)
}

internal static func _refactor<C: CallLikeSyntax>(
syntax call: C,
in context: Context = Context()
) -> C? {
let converted = call.convertToTrailingClosures(from: context.startAtArgument)
return converted?.formatted().as(C.self)
) throws -> C {
let converted = try call.convertToTrailingClosures(from: context.startAtArgument)

guard let formatted = converted.formatted().as(C.self) else {
throw RefactoringNotApplicableError("format error")
}

return formatted
}
}

extension CallLikeSyntax {
fileprivate func convertToTrailingClosures(from startAtArgument: Int) -> Self? {
fileprivate func convertToTrailingClosures(from startAtArgument: Int) throws -> Self {
guard trailingClosure == nil, additionalTrailingClosures.isEmpty, leftParen != nil, rightParen != nil else {
// Already have trailing closures
return nil
throw RefactoringNotApplicableError("call already uses trailing closures")
}

var closures = [(original: LabeledExprSyntax, closure: ClosureExprSyntax)]()
Expand All @@ -106,7 +110,7 @@ extension CallLikeSyntax {
}

guard !closures.isEmpty else {
return nil
throw RefactoringNotApplicableError("no arguments to convert to closures")
}

// First trailing closure won't have label/colon. Transfer their trivia.
Expand Down
16 changes: 10 additions & 6 deletions Sources/SwiftRefactor/ConvertComputedPropertyToStored.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@ import SwiftSyntax
#endif

public struct ConvertComputedPropertyToStored: SyntaxRefactoringProvider {
public static func refactor(syntax: VariableDeclSyntax, in context: ()) -> VariableDeclSyntax? {
guard syntax.bindings.count == 1, let binding = syntax.bindings.first,
let accessorBlock = binding.accessorBlock, case let .getter(body) = accessorBlock.accessors, !body.isEmpty
public static func refactor(syntax: VariableDeclSyntax, in context: ()) throws -> VariableDeclSyntax {
guard syntax.bindings.count == 1, let binding = syntax.bindings.first else {
throw RefactoringNotApplicableError("unsupported variable declaration")
}

guard let accessorBlock = binding.accessorBlock,
case let .getter(body) = accessorBlock.accessors, !body.isEmpty
else {
return nil
throw RefactoringNotApplicableError("getter is missing or empty")
}

let refactored = { (initializer: InitializerClauseSyntax) -> VariableDeclSyntax in
Expand Down Expand Up @@ -55,7 +59,7 @@ public struct ConvertComputedPropertyToStored: SyntaxRefactoringProvider {
}

guard body.count == 1, let item = body.first?.item else {
return nil
throw RefactoringNotApplicableError("getter body is not a single expression")
}

if let item = item.as(ReturnStmtSyntax.self), let expression = item.expression {
Expand All @@ -79,6 +83,6 @@ public struct ConvertComputedPropertyToStored: SyntaxRefactoringProvider {
)
}

return nil
throw RefactoringNotApplicableError("could not extract initial value of stored property")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,25 @@ import SwiftSyntax
#endif

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

var statements: CodeBlockItemListSyntax

guard let typeAnnotation = binding.typeAnnotation,
var accessorBlock = binding.accessorBlock
else { return nil }
else { throw RefactoringNotApplicableError("no type annotation or stored") }

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 }
else { throw RefactoringNotApplicableError("not a getter-only declaration") }
effectSpecifiers = accessor.effectSpecifiers
statements = codeBlock.statements
let accessorSpecifier = accessor.accessorSpecifier
Expand Down
10 changes: 7 additions & 3 deletions Sources/SwiftRefactor/ConvertStoredPropertyToComputed.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,21 @@ import SwiftSyntax
#endif

public struct ConvertStoredPropertyToComputed: SyntaxRefactoringProvider {
public static func refactor(syntax: VariableDeclSyntax, in context: ()) -> VariableDeclSyntax? {
public static func refactor(syntax: VariableDeclSyntax, in context: ()) throws -> VariableDeclSyntax {
guard syntax.bindings.count == 1, let binding = syntax.bindings.first, let initializer = binding.initializer else {
return nil
throw RefactoringNotApplicableError("unsupported variable declaration")
}

var codeBlockSyntax: CodeBlockItemListSyntax

if let functionExpression = initializer.value.as(FunctionCallExprSyntax.self),
let closureExpression = functionExpression.calledExpression.as(ClosureExprSyntax.self)
{
guard functionExpression.arguments.isEmpty else { return nil }
guard functionExpression.arguments.isEmpty else {
throw RefactoringNotApplicableError(
"initializer is a closure that takes arguments"
)
}

codeBlockSyntax = closureExpression.statements
codeBlockSyntax.leadingTrivia =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ import SwiftSyntax
#endif

public struct ConvertZeroParameterFunctionToComputedProperty: SyntaxRefactoringProvider {
public static func refactor(syntax: FunctionDeclSyntax, in context: ()) -> VariableDeclSyntax? {
public static func refactor(syntax: FunctionDeclSyntax, in context: ()) throws -> VariableDeclSyntax {
guard syntax.signature.parameterClause.parameters.isEmpty,
let body = syntax.body
else { return nil }
else { throw RefactoringNotApplicableError("not a zero parameter function") }

let variableName = PatternSyntax(
IdentifierPatternSyntax(
Expand Down
22 changes: 14 additions & 8 deletions Sources/SwiftRefactor/ExpandEditorPlaceholder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -275,13 +275,19 @@ public struct ExpandEditorPlaceholdersToLiteralClosures: SyntaxRefactoringProvid
public static func refactor(
syntax: Syntax,
in context: Context = Context()
) -> Syntax? {
guard let call = syntax.asProtocol(CallLikeSyntax.self) else { return nil }
let expanded = Self.expandClosurePlaceholders(
in: call,
ifIncluded: nil,
context: context
)
) throws -> Syntax {
guard let call = syntax.asProtocol(CallLikeSyntax.self) else {
throw RefactoringNotApplicableError("not a call")
}
guard
let expanded = Self.expandClosurePlaceholders(
in: call,
ifIncluded: nil,
context: context
)
else {
throw RefactoringNotApplicableError("could not expand closure placeholders")
}
return Syntax(fromProtocol: expanded)
}

Expand Down Expand Up @@ -325,7 +331,7 @@ public struct ExpandEditorPlaceholdersToLiteralClosures: SyntaxRefactoringProvid
let callToTrailingContext = CallToTrailingClosures.Context(
startAtArgument: call.arguments.count - expanded.numClosures
)
return CallToTrailingClosures._refactor(syntax: expanded.expr, in: callToTrailingContext)
return try? CallToTrailingClosures._refactor(syntax: expanded.expr, in: callToTrailingContext)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftRefactor/FormatRawStringLiteral.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import SwiftSyntax
/// "Hello World"
/// ```
public struct FormatRawStringLiteral: SyntaxRefactoringProvider {
public static func refactor(syntax lit: StringLiteralExprSyntax, in context: Void) -> StringLiteralExprSyntax? {
public static func refactor(syntax lit: StringLiteralExprSyntax, in context: Void) -> StringLiteralExprSyntax {
var maximumHashes = 0
for segment in lit.segments {
switch segment {
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftRefactor/MigrateToNewIfLetSyntax.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import SwiftSyntax
/// // ...
/// }
public struct MigrateToNewIfLetSyntax: SyntaxRefactoringProvider {
public static func refactor(syntax node: IfExprSyntax, in context: ()) -> IfExprSyntax? {
public static func refactor(syntax node: IfExprSyntax, in context: ()) -> IfExprSyntax {
// Visit all conditions in the node.
let newConditions = node.conditions.enumerated().map { (index, condition) -> ConditionElementListSyntax.Element in
var conditionCopy = condition
Expand Down
10 changes: 5 additions & 5 deletions Sources/SwiftRefactor/OpaqueParameterToGeneric.swift
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ public struct OpaqueParameterToGeneric: SyntaxRefactoringProvider {
public static func refactor(
syntax decl: DeclSyntax,
in context: Void
) -> DeclSyntax? {
) throws -> DeclSyntax {
// Function declaration.
if let funcSyntax = decl.as(FunctionDeclSyntax.self) {
guard
Expand All @@ -188,7 +188,7 @@ public struct OpaqueParameterToGeneric: SyntaxRefactoringProvider {
augmenting: funcSyntax.genericParameterClause
)
else {
return nil
throw RefactoringNotApplicableError("found no parameters to rewrite")
}

return DeclSyntax(
Expand All @@ -206,7 +206,7 @@ public struct OpaqueParameterToGeneric: SyntaxRefactoringProvider {
augmenting: initSyntax.genericParameterClause
)
else {
return nil
throw RefactoringNotApplicableError("found no parameters to rewrite")
}

return DeclSyntax(
Expand All @@ -224,7 +224,7 @@ public struct OpaqueParameterToGeneric: SyntaxRefactoringProvider {
augmenting: subscriptSyntax.genericParameterClause
)
else {
return nil
throw RefactoringNotApplicableError("found no parameters to rewrite")
}

return DeclSyntax(
Expand All @@ -234,6 +234,6 @@ public struct OpaqueParameterToGeneric: SyntaxRefactoringProvider {
)
}

return nil
throw RefactoringNotApplicableError("unsupported declaration")
}
}
43 changes: 27 additions & 16 deletions Sources/SwiftRefactor/RefactoringProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ public import SwiftSyntax
import SwiftSyntax
#endif

/// Error that refactoring actions can throw when the refactoring fails.
///
/// The reason should start with a single lowercase character and
/// should be formatted similarly to compiler error messages.
public struct RefactoringNotApplicableError: Error, CustomStringConvertible {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
public struct RefactoringNotApplicableError: Error, CustomStringConvertible {
/// Error that refactoring actions can throw when the refactoring fails.
///
/// The reason should start with a single character and should be formatted similarly to compiler error messages.
public struct RefactoringNotApplicableError: Error, CustomStringConvertible {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! I was thinking whether I should add a comment there or it's self-evident what this is since none other errors declared in swift-syntax do...

Copy link
Contributor

Choose a reason for hiding this comment

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

What does "The reason should start with a single character" mean?

public var description: String

public init(_ reason: String) {
self.description = reason
}
}

/// A refactoring expressed as textual edits on the original syntax tree. In
/// general clients should prefer `SyntaxRefactoringProvider` where possible.
public protocol EditRefactoringProvider {
Expand All @@ -29,10 +41,10 @@ public protocol EditRefactoringProvider {
/// - Parameters:
/// - syntax: The syntax to transform.
/// - context: Contextual information used by the refactoring action.
/// - Throws: Throws an error if the refactoring action fails or is not applicable.
/// - Returns: Textual edits that describe how to apply the result of the
/// refactoring action on locations within the original tree. An
/// empty array if the refactoring could not be performed.
static func textRefactor(syntax: Input, in context: Context) -> [SourceEdit]
/// refactoring action on locations within the original tree.
static func textRefactor(syntax: Input, in context: Context) throws -> [SourceEdit]
}

extension EditRefactoringProvider where Context == Void {
Expand All @@ -41,9 +53,10 @@ extension EditRefactoringProvider where Context == Void {
///
/// - Parameters:
/// - syntax: The syntax to transform.
/// - Throws: Throws an error if the refactoring action fails or is not applicable.
/// - Returns: Textual edits describing the refactoring to perform.
public static func textRefactor(syntax: Input) -> [SourceEdit] {
return self.textRefactor(syntax: syntax, in: ())
public static func textRefactor(syntax: Input) throws -> [SourceEdit] {
return try self.textRefactor(syntax: syntax, in: ())
}
}

Expand Down Expand Up @@ -84,9 +97,9 @@ public protocol SyntaxRefactoringProvider: EditRefactoringProvider {
/// - Parameters:
/// - syntax: The syntax to transform.
/// - context: Contextual information used by the refactoring action.
/// - Returns: The result of applying the refactoring action, or `nil` if the
/// action could not be performed.
static func refactor(syntax: Input, in context: Context) -> Output?
/// - Throws: Throws an error if the refactoring action fails or is not applicable.
/// - Returns: The result of applying the refactoring action.
static func refactor(syntax: Input, in context: Context) throws -> Output
}

extension SyntaxRefactoringProvider where Context == Void {
Expand All @@ -95,21 +108,19 @@ extension SyntaxRefactoringProvider where Context == Void {
///
/// - Parameters:
/// - syntax: The syntax to transform.
/// - Returns: The result of applying the refactoring action, or `nil` if the
/// action could not be performed.
public static func refactor(syntax: Input) -> Output? {
return self.refactor(syntax: syntax, in: ())
/// - Throws: Throws an error if the refactoring action fails or is not applicable.
/// - Returns: The result of applying the refactoring action.
public static func refactor(syntax: Input) throws -> Output {
return try self.refactor(syntax: syntax, in: ())
}
}

extension SyntaxRefactoringProvider {
/// Provides a default implementation for
/// `EditRefactoringProvider.textRefactor(syntax:in:)` that produces an edit
/// to replace the input of `refactor(syntax:in:)` with its returned output.
public static func textRefactor(syntax: Input, in context: Context) -> [SourceEdit] {
guard let output = refactor(syntax: syntax, in: context) else {
return []
}
public static func textRefactor(syntax: Input, in context: Context) throws -> [SourceEdit] {
let output = try refactor(syntax: syntax, in: context)
return [SourceEdit.replace(syntax, with: output.description)]
}
}
Loading