Reputation: 9925
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
Reputation: 2272
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.
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
}
}
Upvotes: 5