Feat(SwiftRefactor): Implement InvertIfCondition refactoring#3263
Feat(SwiftRefactor): Implement InvertIfCondition refactoring#3263rickhohler wants to merge 4 commits intoswiftlang:mainfrom
Conversation
ahoppen
left a comment
There was a problem hiding this comment.
Very nice. Some small comments inline.
| /// ``` | ||
| public struct InvertIfCondition: SyntaxRefactoringProvider { | ||
| public static func refactor(syntax ifExpr: IfExprSyntax, in context: Void) -> IfExprSyntax { | ||
| // 1. Must have an `else` block (and it must be a CodeBlock, not another `if`). |
There was a problem hiding this comment.
Similar to my comment in your other PR, I wouldn’t have these numbered comments that explain exactly what the code blow does.
| guard ifExpr.conditions.count == 1, | ||
| let condition = ifExpr.conditions.first | ||
| else { |
There was a problem hiding this comment.
Fits on one line, same above.
| guard ifExpr.conditions.count == 1, | |
| let condition = ifExpr.conditions.first | |
| else { | |
| guard ifExpr.conditions.count == 1, let condition = ifExpr.conditions.first else { |
| // Preserve trivia: The `!` might have leading trivia (e.g. comments/spaces). | ||
| // Usually standard formatting is `if !cond`. | ||
| // We should probably apply the `PrefixOperatorExpr`'s leading trivia to the inner expression | ||
| // to preserve any comments attached to the negation. |
There was a problem hiding this comment.
If there are things left to address, please address them. Otherwise please don’t talk about things that we should probably do. Either do them or don’t and explain why.
| // Preserve trivia: The `!` might have leading trivia (e.g. comments/spaces). | ||
| // Usually standard formatting is `if !cond`. | ||
| // We should probably apply the `PrefixOperatorExpr`'s leading trivia to the inner expression | ||
| // to preserve any comments attached to the negation. |
There was a problem hiding this comment.
You should be able to use merging(triviaOf:) to make sure we don’t loose trivia from !.
There was a problem hiding this comment.
This is an unrelated change. Please make sure your PR only addresses one issue.
|
|
||
| final class InvertIfConditionTest: XCTestCase { | ||
| func testInvertIfCondition() throws { | ||
| let tests = [ |
There was a problem hiding this comment.
Same comment about writing an assert function as in your other PR here.
|
Looks like you pushed a new commit without addressing all of my comments. Please let me know when this is ready for another review. |
…ents, reformat guards, improve trivia preservation, and add test helper
|
Thank you for the patience. I have now addressed all of your previous comments in the latest push: Styling: Removed the numbered comments and reformatted the guard statements to fit on single lines. Trivia: Implemented merging(triviaOf:) for the unwrapped condition to ensure that any trivia (comments/spacing) attached to the ! operator is correctly preserved. Hygiene: Confirmed that the branch is now clean and free of the unrelated RemoveRedundantParens changes. Tests: Added a private assertInvertIfCondition helper to the test suite to reduce boilerplate and match repository conventions. This should be ready for another review now! |
ahoppen
left a comment
There was a problem hiding this comment.
Looks good, just a few small things left.
| public struct InvertIfCondition: SyntaxRefactoringProvider { | ||
| public static func refactor(syntax ifExpr: IfExprSyntax, in context: Void) -> IfExprSyntax { | ||
| guard let elseBody = ifExpr.elseBody, case .codeBlock(let elseBlock) = elseBody else { | ||
| return ifExpr |
There was a problem hiding this comment.
If there is nothing to refactor we should throw a RefactoringNotApplicableError. This will prevent the refactoring from showing up in SourceKit-LSP when it doesn’t apply.
| return ifExpr | ||
| } | ||
|
|
||
| guard let prefixOpExpr = expr.as(PrefixOperatorExprSyntax.self), prefixOpExpr.operator.text == "!" else { |
There was a problem hiding this comment.
Should we also support adding the ! if you want to invert an if expression that has a normal expression? The tricky part would likely be to decide whether parentheses need to be added around the expression for precedence rules. We can also do that in a follow-up PR.
| let newCondition = condition.with(\.condition, .expression(innerExpr)) | ||
| let newConditions = ifExpr.conditions.with(\.[ifExpr.conditions.startIndex], newCondition) |
There was a problem hiding this comment.
| let newCondition = condition.with(\.condition, .expression(innerExpr)) | |
| let newConditions = ifExpr.conditions.with(\.[ifExpr.conditions.startIndex], newCondition) | |
| let newConditions = ifExpr.conditions.with(\.[ifExpr.conditions.startIndex].condition, .expression(innerExpr)) |
|
|
||
| final class InvertIfConditionTest: XCTestCase { | ||
| func testInvertIfCondition() throws { | ||
| let tests = [ |
| } else { | ||
| a | ||
| } | ||
| """ |
There was a problem hiding this comment.
Could you also add a test that has comments in the bodies?
| } | ||
| """, | ||
| """ | ||
| if (x == y) { |
There was a problem hiding this comment.
Should we strip the parentheses here since they are no longer necessary?
Description
This PR implements the
InvertIfConditionrefactoring action, which supports SourceKit-LSP #2408.Transformation:
Detailed Design
InvertIfConditionprovider toSwiftRefactor.IfExprSyntax.elseblock (CodeBlock).PrefixOperatorExprwith!).!condition->condition).bodyandelseBody.Fixes
Addresses logic for swiftlang/sourcekit-lsp#2408.
Pre-PR Checklist
swift-format.InvertIfConditionTest.swift.