Blazej SLEBODA
Blazej SLEBODA

Reputation: 9925

Xcode Refactor "Convert to Switch"

Xcode Refactor has a function called "Convert to Switch". I am looking for an example of code which can be used by the function and be converted to switch flow statement.

Upvotes: 2

Views: 408

Answers (1)

olha
olha

Reputation: 2272

Update

As you've pointed out, Convert to Switch != Convert To Switch Statement. In order to understand why, we need to know how Xcode refactoring feature works under the hood.

What I've found:

Here's a great article on the topic: https://levelup.gitconnected.com/uncovering-xcode-indexing-8b3f3ff82551

It points us to a swift open-source repo: https://github.com/apple/swift

Xcode refactoring is done with a SourceKit framework, which is inside a swift repo.

Here's a SourceKit protocol for communication with Xcode: https://github.com/apple/swift/blob/main/tools/SourceKit/docs/Protocol.md

Internally, when we choose "Refactor", Xcode somehow (via XPC) sends something like source.request.workspace.refactoring message to a SourceKit. And there's an "indexed file" somewhere, so SourceKit can instantly (not in 5 minutes :)) propose you the options which are adequate/possible for a current snippet of code.

I've loaded swift repo from github (it's about 30MB), navigated into SourceKit code and tried to find stuff related to source.request.workspace.refactoring.

Somewhere in the code is a reference to swift/IDE/RefactoringKinds.def. I'll copy-paste the contents of this file:

#ifndef REFACTORING
#define REFACTORING(KIND, NAME, ID)
#endif

#ifndef SEMANTIC_REFACTORING
#define SEMANTIC_REFACTORING(KIND, NAME, ID) REFACTORING(KIND, NAME, ID)
#endif

#ifndef RANGE_REFACTORING
#define RANGE_REFACTORING(KIND, NAME, ID) SEMANTIC_REFACTORING(KIND, NAME, ID)
#endif

#ifndef INTERNAL_RANGE_REFACTORING
#define INTERNAL_RANGE_REFACTORING(KIND, NAME, ID) RANGE_REFACTORING(KIND, NAME, ID)
#endif

#ifndef CURSOR_REFACTORING
#define CURSOR_REFACTORING(KIND, NAME, ID) SEMANTIC_REFACTORING(KIND, NAME, ID)
#endif

/// Rename and categorise the symbol occurrences at provided locations
/// (syntactically).
REFACTORING(GlobalRename, "Global Rename", rename.global)

/// Categorize source ranges for symbol occurrences at provided locations
/// (syntactically).
REFACTORING(FindGlobalRenameRanges, "Find Global Rename Ranges", rename.global.find-ranges)

/// Find and categorize all occurences of the file-local symbol at a given
/// location.
REFACTORING(FindLocalRenameRanges, "Find Local Rename Ranges", rename.local.find-ranges)

/// Find and rename all occurences of the file-local symbol at a given
/// location.
CURSOR_REFACTORING(LocalRename, "Local Rename", rename.local)

CURSOR_REFACTORING(FillProtocolStub, "Add Missing Protocol Requirements", fillstub)

CURSOR_REFACTORING(ExpandDefault, "Expand Default", expand.default)

CURSOR_REFACTORING(ExpandSwitchCases, "Expand Switch Cases", expand.switch.cases)

CURSOR_REFACTORING(LocalizeString, "Localize String", localize.string)

CURSOR_REFACTORING(SimplifyNumberLiteral, "Simplify Long Number Literal", simplify.long.number.literal)

CURSOR_REFACTORING(CollapseNestedIfStmt, "Collapse Nested If Statements", collapse.nested.ifstmt)

CURSOR_REFACTORING(ConvertToDoCatch, "Convert To Do/Catch", convert.do.catch)

CURSOR_REFACTORING(TrailingClosure, "Convert To Trailing Closure", trailingclosure)

CURSOR_REFACTORING(MemberwiseInitLocalRefactoring, "Generate Memberwise Initializer", memberwise.init.local.refactoring)

CURSOR_REFACTORING(AddEquatableConformance, "Add Equatable Conformance", add.equatable.conformance)

CURSOR_REFACTORING(ConvertCallToAsyncAlternative, "Convert Call to Async Alternative", convert.call-to-async)

CURSOR_REFACTORING(ConvertToAsync, "Convert Function to Async", convert.func-to-async)

CURSOR_REFACTORING(AddAsyncAlternative, "Add Async Alternative", add.async-alternative)

RANGE_REFACTORING(ExtractExpr, "Extract Expression", extract.expr)

RANGE_REFACTORING(ExtractFunction, "Extract Method", extract.function)

RANGE_REFACTORING(ExtractRepeatedExpr, "Extract Repeated Expression", extract.expr.repeated)

RANGE_REFACTORING(MoveMembersToExtension, "Move To Extension", move.members.to.extension)

RANGE_REFACTORING(ConvertStringsConcatenationToInterpolation, "Convert to String Interpolation", convert.string-concatenation.interpolation)

RANGE_REFACTORING(ExpandTernaryExpr, "Expand Ternary Expression", expand.ternary.expr)

RANGE_REFACTORING(ConvertToTernaryExpr, "Convert To Ternary Expression", convert.ternary.expr)

RANGE_REFACTORING(ConvertIfLetExprToGuardExpr, "Convert To Guard Expression", convert.iflet.to.guard.expr)

RANGE_REFACTORING(ConvertGuardExprToIfLetExpr, "Convert To IfLet Expression", convert.to.iflet.expr)

RANGE_REFACTORING(ConvertToComputedProperty, "Convert To Computed Property", convert.to.computed.property)

RANGE_REFACTORING(ConvertToSwitchStmt, "Convert To Switch Statement", convert.switch.stmt)

// These internal refactorings are designed to be helpful for working on
// the compiler/standard library, etc., but are likely to be just confusing and
// noise for general development.

INTERNAL_RANGE_REFACTORING(ReplaceBodiesWithFatalError, "Replace Function Bodies With 'fatalError()'", replace.bodies.with.fatalError)

#undef CURSOR_REFACTORING
#undef INTERNAL_RANGE_REFACTORING
#undef RANGE_REFACTORING
#undef SEMANTIC_REFACTORING
#undef REFACTORING

As you see, there's Convert To Switch Statement here, and Expand Switch Cases, but not Convert To Switch.

So my guess is that what Xcode shows us in UI, is either a bug (they forgot to delete an outdated stuff), or a part of some implementation which is not in a current version of a Swift repo.

Anyway, you may dig even deeper - I've just tried to investigate it in a short period of time. :) If you really need this done, then except of exploring SourceKit protocols, I guess you can open an issue on Github - maybe those who know it will explain a real reason.

Initial answer

Let's consider such an example:

enum SomeEnum {
    case option1, option2, option3
}

func convertToSwitchExampleFunc(_ option: SomeEnum) {
    if option == .option1 {
        print("1")
    }
    if option == .option2 {
        print("2")
    }
    if option == .option3 {
        print("3")
    }
}

func callingFunc() {
    convertToSwitchExampleFunc(.option2)
}

If you select all code inside the body of convertToSwitchExampleFunc, then choose Refactor, Xcode won't show you "Convert to Switch".

But if you change the code such that it's more similar to an ugly "switch" implementation (if-else-if-else chain):

func convertToSwitchExampleFunc(_ option: SomeEnum) {
    if option == .option1 {
        print("1")
    }
    else if option == .option2 {
        print("2")
    }
    else if option == .option3 {
        print("3")
    }
}

Then Xcode will show you "Convert to Switch menu", and an auto-refactored code will look like this:

func convertToSwitchExampleFunc(_ option: SomeEnum) {
    switch option {
    case .option1:
        print("1")
    case .option2:
        print("2")
    case .option3:
        print("3")
    default:
        break
    }
}

Just to give more context: enter image description here

Upvotes: 5

Related Questions