From 1c48cd19c7adaabba6da869d0508e6f5a6d8758d Mon Sep 17 00:00:00 2001 From: "exe.dev user" Date: Tue, 10 Feb 2026 21:46:14 +0000 Subject: [PATCH 1/7] Make conditional_returns_on_newline rule autocorrectable Added a Rewriter class to automatically fix violations where return statements are on the same line as conditional keywords (if/guard). The autocorrection: - Adds a newline before the return statement - Preserves proper indentation (4 spaces from the conditional's indentation) - Handles both if/else and guard statements - Preserves preceding statements on the same line (e.g., XCTFail()) - Respects the if_only configuration option Added test for guard statement corrections. Co-authored-by: Shelley --- .../ConditionalReturnsOnNewlineRule.swift | 98 ++++++++++++++++++- ...ConditionalReturnsOnNewlineRuleTests.swift | 13 +++ 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/ConditionalReturnsOnNewlineRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/ConditionalReturnsOnNewlineRule.swift index 92d28780d2..e9b925f7c6 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/ConditionalReturnsOnNewlineRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/ConditionalReturnsOnNewlineRule.swift @@ -1,6 +1,6 @@ import SwiftSyntax -@SwiftSyntaxRule(optIn: true) +@SwiftSyntaxRule(explicitRewriter: true, optIn: true) struct ConditionalReturnsOnNewlineRule: Rule { var configuration = ConditionalReturnsOnNewlineConfiguration() @@ -30,6 +30,11 @@ struct ConditionalReturnsOnNewlineRule: Rule { Example(""" ↓guard condition else { XCTFail(); return } """), + ], + corrections: [ + Example("↓if true { return }"): Example("if true {\n return\n}"), + Example("↓if true { break } else { return }"): Example("if true { break } else {\n return\n}"), + Example("↓if true { return \"YES\" } else { return \"NO\" }"): Example("if true {\n return \"YES\"\n} else { return \"NO\" }"), ] ) } @@ -67,6 +72,97 @@ private extension ConditionalReturnsOnNewlineRule { locationConverter.location(for: token.positionAfterSkippingLeadingTrivia).line } } + + final class Rewriter: ViolationsSyntaxRewriter { + override func visit(_ node: IfExprSyntax) -> ExprSyntax { + // Match the visitor's logic: if body has violation, only fix body (and return early) + // If body is fine, check else body + if isReturn(node.body.statements.lastReturn, onTheSameLineAs: node.ifKeyword) { + numberOfCorrections += 1 + let modifiedNode = node.with(\.body, fixCodeBlock(node.body, baseToken: node.ifKeyword)) + return super.visit(modifiedNode) + } + + // Check if else body's return is on the same line as the else keyword + if let elseBody = node.elseBody?.as(CodeBlockSyntax.self), let elseKeyword = node.elseKeyword, + isReturn(elseBody.statements.lastReturn, onTheSameLineAs: elseKeyword) { + numberOfCorrections += 1 + let fixedElseBody = fixCodeBlock(elseBody, baseToken: node.ifKeyword) + let modifiedNode = node.with(\.elseBody, .codeBlock(fixedElseBody)) + return super.visit(modifiedNode) + } + + return super.visit(node) + } + + override func visit(_ node: GuardStmtSyntax) -> StmtSyntax { + if configuration.ifOnly { + return super.visit(node) + } + + guard isReturn(node.body.statements.lastReturn, onTheSameLineAs: node.guardKeyword) else { + return super.visit(node) + } + + numberOfCorrections += 1 + let fixedNode = node.with(\.body, fixCodeBlock(node.body, baseToken: node.guardKeyword)) + return super.visit(fixedNode) + } + + private func isReturn(_ returnStmt: ReturnStmtSyntax?, onTheSameLineAs token: TokenSyntax) -> Bool { + guard let returnStmt else { + return false + } + + return locationConverter.location(for: returnStmt.returnKeyword.positionAfterSkippingLeadingTrivia).line == + locationConverter.location(for: token.positionAfterSkippingLeadingTrivia).line + } + + private func fixCodeBlock(_ block: CodeBlockSyntax, baseToken: TokenSyntax) -> CodeBlockSyntax { + // Get the indentation of the base token (e.g., `if` or `guard`) + let baseIndentation = baseToken.leadingTrivia.indentation(isOnNewline: true) ?? Trivia() + let innerIndentation = Trivia(pieces: baseIndentation.pieces + [.spaces(4)]) + + // Check if the first statement is the return (i.e., return is the only statement) + let returnIsFirst = block.statements.first?.item.is(ReturnStmtSyntax.self) == true + + // Fix the left brace: only remove trailing whitespace if return is the first statement + let fixedLeftBrace = returnIsFirst + ? block.leftBrace.with(\.trailingTrivia, Trivia()) + : block.leftBrace + + // Fix the statements: add newline + inner indentation before the return, + // and remove trailing whitespace from the return value and the preceding statement + var statements = Array(block.statements) + for i in statements.indices { + if statements[i].item.is(ReturnStmtSyntax.self) { + // Remove trailing whitespace from the previous statement if there is one + if i > 0 { + statements[i - 1] = statements[i - 1].with(\.trailingTrivia, Trivia()) + } + // Add newline + indentation before return and remove its trailing whitespace + statements[i] = statements[i] + .with( + \.leadingTrivia, + Trivia(pieces: [.newlines(1)] + innerIndentation.pieces) + ) + .with(\.trailingTrivia, Trivia()) + } + } + let fixedStatements = CodeBlockItemListSyntax(statements) + + // Fix the closing brace: add newline + base indentation before it + let fixedRightBrace = block.rightBrace.with( + \.leadingTrivia, + Trivia(pieces: [.newlines(1)] + baseIndentation.pieces) + ) + + return block + .with(\.leftBrace, fixedLeftBrace) + .with(\.statements, fixedStatements) + .with(\.rightBrace, fixedRightBrace) + } + } } private extension CodeBlockItemListSyntax { diff --git a/Tests/BuiltInRulesTests/ConditionalReturnsOnNewlineRuleTests.swift b/Tests/BuiltInRulesTests/ConditionalReturnsOnNewlineRuleTests.swift index 96a31dbd7e..b3fa06e8b9 100644 --- a/Tests/BuiltInRulesTests/ConditionalReturnsOnNewlineRuleTests.swift +++ b/Tests/BuiltInRulesTests/ConditionalReturnsOnNewlineRuleTests.swift @@ -27,4 +27,17 @@ final class ConditionalReturnsOnNewlineRuleTests: SwiftLintTestCase { verifyRule(description, ruleConfiguration: ["if_only": true]) } + + func testGuardCorrection() { + // Test guard correction with default configuration + let corrections = [ + Example("↓guard true else { return }"): Example("guard true else {\n return\n}"), + Example("↓guard condition else { XCTFail(); return }"): Example("guard condition else { XCTFail();\n return\n}"), + ] + + let description = ConditionalReturnsOnNewlineRule.description + .with(corrections: corrections) + + verifyRule(description) + } } From 2591ad2f5c92daaeea3665639b735ac4c9979a02 Mon Sep 17 00:00:00 2001 From: "exe.dev user" Date: Wed, 11 Feb 2026 00:34:47 +0000 Subject: [PATCH 2/7] Add missing SwiftBasicFormat import Co-authored-by: Shelley --- .../Rules/Style/ConditionalReturnsOnNewlineRule.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/ConditionalReturnsOnNewlineRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/ConditionalReturnsOnNewlineRule.swift index e9b925f7c6..0edd509217 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/ConditionalReturnsOnNewlineRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/ConditionalReturnsOnNewlineRule.swift @@ -1,3 +1,4 @@ +import SwiftBasicFormat import SwiftSyntax @SwiftSyntaxRule(explicitRewriter: true, optIn: true) From 678ca006282f46d2e0955d95bf11785282510c17 Mon Sep 17 00:00:00 2001 From: "exe.dev user" Date: Wed, 11 Feb 2026 04:06:37 +0000 Subject: [PATCH 3/7] Update rule configuration to mark conditional_returns_on_newline as correctable Co-authored-by: Shelley --- .../IntegrationTests/Resources/default_rule_configurations.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/IntegrationTests/Resources/default_rule_configurations.yml b/Tests/IntegrationTests/Resources/default_rule_configurations.yml index 474a656955..43d33ed082 100644 --- a/Tests/IntegrationTests/Resources/default_rule_configurations.yml +++ b/Tests/IntegrationTests/Resources/default_rule_configurations.yml @@ -134,7 +134,7 @@ conditional_returns_on_newline: if_only: false meta: opt-in: true - correctable: false + correctable: true contains_over_filter_count: severity: warning meta: From 975967e2828d5545f11e2822baeb3c0f61a36ca9 Mon Sep 17 00:00:00 2001 From: "exe.dev user" Date: Wed, 11 Feb 2026 14:46:53 +0000 Subject: [PATCH 4/7] Fix linting errors: line length, identifier name, for_where Co-authored-by: Shelley --- .../ConditionalReturnsOnNewlineRule.swift | 30 +++++++++---------- ...ConditionalReturnsOnNewlineRuleTests.swift | 6 ++-- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/ConditionalReturnsOnNewlineRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/ConditionalReturnsOnNewlineRule.swift index 0edd509217..7587dd4de7 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/ConditionalReturnsOnNewlineRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/ConditionalReturnsOnNewlineRule.swift @@ -34,8 +34,10 @@ struct ConditionalReturnsOnNewlineRule: Rule { ], corrections: [ Example("↓if true { return }"): Example("if true {\n return\n}"), - Example("↓if true { break } else { return }"): Example("if true { break } else {\n return\n}"), - Example("↓if true { return \"YES\" } else { return \"NO\" }"): Example("if true {\n return \"YES\"\n} else { return \"NO\" }"), + Example("↓if true { break } else { return }"): + Example("if true { break } else {\n return\n}"), + Example("↓if true { return \"YES\" } else { return \"NO\" }"): + Example("if true {\n return \"YES\"\n} else { return \"NO\" }"), ] ) } @@ -135,20 +137,18 @@ private extension ConditionalReturnsOnNewlineRule { // Fix the statements: add newline + inner indentation before the return, // and remove trailing whitespace from the return value and the preceding statement var statements = Array(block.statements) - for i in statements.indices { - if statements[i].item.is(ReturnStmtSyntax.self) { - // Remove trailing whitespace from the previous statement if there is one - if i > 0 { - statements[i - 1] = statements[i - 1].with(\.trailingTrivia, Trivia()) - } - // Add newline + indentation before return and remove its trailing whitespace - statements[i] = statements[i] - .with( - \.leadingTrivia, - Trivia(pieces: [.newlines(1)] + innerIndentation.pieces) - ) - .with(\.trailingTrivia, Trivia()) + for index in statements.indices where statements[index].item.is(ReturnStmtSyntax.self) { + // Remove trailing whitespace from the previous statement if there is one + if index > 0 { + statements[index - 1] = statements[index - 1].with(\.trailingTrivia, Trivia()) } + // Add newline + indentation before return and remove its trailing whitespace + statements[index] = statements[index] + .with( + \.leadingTrivia, + Trivia(pieces: [.newlines(1)] + innerIndentation.pieces) + ) + .with(\.trailingTrivia, Trivia()) } let fixedStatements = CodeBlockItemListSyntax(statements) diff --git a/Tests/BuiltInRulesTests/ConditionalReturnsOnNewlineRuleTests.swift b/Tests/BuiltInRulesTests/ConditionalReturnsOnNewlineRuleTests.swift index b3fa06e8b9..5d2b37f2bc 100644 --- a/Tests/BuiltInRulesTests/ConditionalReturnsOnNewlineRuleTests.swift +++ b/Tests/BuiltInRulesTests/ConditionalReturnsOnNewlineRuleTests.swift @@ -31,8 +31,10 @@ final class ConditionalReturnsOnNewlineRuleTests: SwiftLintTestCase { func testGuardCorrection() { // Test guard correction with default configuration let corrections = [ - Example("↓guard true else { return }"): Example("guard true else {\n return\n}"), - Example("↓guard condition else { XCTFail(); return }"): Example("guard condition else { XCTFail();\n return\n}"), + Example("↓guard true else { return }"): + Example("guard true else {\n return\n}"), + Example("↓guard condition else { XCTFail(); return }"): + Example("guard condition else { XCTFail();\n return\n}"), ] let description = ConditionalReturnsOnNewlineRule.description From 546f771dd5467ea89fac782ec8febaa586f3c418 Mon Sep 17 00:00:00 2001 From: "exe.dev user" Date: Fri, 13 Feb 2026 00:46:36 +0000 Subject: [PATCH 5/7] Add indented if example to correction tests Co-authored-by: Shelley --- .../Rules/Style/ConditionalReturnsOnNewlineRule.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/ConditionalReturnsOnNewlineRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/ConditionalReturnsOnNewlineRule.swift index 7587dd4de7..c338e6d51a 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/ConditionalReturnsOnNewlineRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/ConditionalReturnsOnNewlineRule.swift @@ -34,6 +34,17 @@ struct ConditionalReturnsOnNewlineRule: Rule { ], corrections: [ Example("↓if true { return }"): Example("if true {\n return\n}"), + Example(""" + func f() { + ↓if true { return } + } + """): Example(""" + func f() { + if true { + return + } + } + """), Example("↓if true { break } else { return }"): Example("if true { break } else {\n return\n}"), Example("↓if true { return \"YES\" } else { return \"NO\" }"): From 48d2a641a0424e2f21fe03c7390baf98d8fff7a4 Mon Sep 17 00:00:00 2001 From: "exe.dev user" Date: Fri, 13 Feb 2026 01:44:59 +0000 Subject: [PATCH 6/7] Move guard corrections to rule description - Added guard correction examples to the rule's corrections array - Updated if_only test to override corrections (excluding guard) - Removed separate testGuardCorrection since it's now tested via RuleDescription Co-authored-by: Shelley --- .../ConditionalReturnsOnNewlineRule.swift | 9 +++++++ ...ConditionalReturnsOnNewlineRuleTests.swift | 25 ++++++++----------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/ConditionalReturnsOnNewlineRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/ConditionalReturnsOnNewlineRule.swift index c338e6d51a..06d58e6b88 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/ConditionalReturnsOnNewlineRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/ConditionalReturnsOnNewlineRule.swift @@ -49,6 +49,15 @@ struct ConditionalReturnsOnNewlineRule: Rule { Example("if true { break } else {\n return\n}"), Example("↓if true { return \"YES\" } else { return \"NO\" }"): Example("if true {\n return \"YES\"\n} else { return \"NO\" }"), + Example("↓guard true else { return }"): + Example("guard true else {\n return\n}"), + Example(""" + ↓guard condition else { XCTFail(); return } + """): Example(""" + guard condition else { XCTFail(); + return + } + """), ] ) } diff --git a/Tests/BuiltInRulesTests/ConditionalReturnsOnNewlineRuleTests.swift b/Tests/BuiltInRulesTests/ConditionalReturnsOnNewlineRuleTests.swift index 5d2b37f2bc..1887c07dfc 100644 --- a/Tests/BuiltInRulesTests/ConditionalReturnsOnNewlineRuleTests.swift +++ b/Tests/BuiltInRulesTests/ConditionalReturnsOnNewlineRuleTests.swift @@ -4,6 +4,7 @@ import TestHelpers final class ConditionalReturnsOnNewlineRuleTests: SwiftLintTestCase { func testConditionalReturnsOnNewlineWithIfOnly() { // Test with `if_only` set to true + // guard statements should not trigger or be corrected let nonTriggeringExamples = [ Example("guard true else {\n return true\n}"), Example("guard true,\n let x = true else {\n return true\n}"), @@ -20,26 +21,20 @@ final class ConditionalReturnsOnNewlineRuleTests: SwiftLintTestCase { Example("↓if true { break } else { return }"), Example("↓if true { return \"YES\" } else { return \"NO\" }"), ] - - let description = ConditionalReturnsOnNewlineRule.description - .with(triggeringExamples: triggeringExamples) - .with(nonTriggeringExamples: nonTriggeringExamples) - - verifyRule(description, ruleConfiguration: ["if_only": true]) - } - - func testGuardCorrection() { - // Test guard correction with default configuration + // Only include `if` corrections - guard corrections should not apply with if_only let corrections = [ - Example("↓guard true else { return }"): - Example("guard true else {\n return\n}"), - Example("↓guard condition else { XCTFail(); return }"): - Example("guard condition else { XCTFail();\n return\n}"), + Example("↓if true { return }"): Example("if true {\n return\n}"), + Example("↓if true { break } else { return }"): + Example("if true { break } else {\n return\n}"), + Example("↓if true { return \"YES\" } else { return \"NO\" }"): + Example("if true {\n return \"YES\"\n} else { return \"NO\" }"), ] let description = ConditionalReturnsOnNewlineRule.description + .with(triggeringExamples: triggeringExamples) + .with(nonTriggeringExamples: nonTriggeringExamples) .with(corrections: corrections) - verifyRule(description) + verifyRule(description, ruleConfiguration: ["if_only": true]) } } From 5140533c217cec1a63b31de773a523dbe357270e Mon Sep 17 00:00:00 2001 From: "exe.dev user" Date: Fri, 13 Feb 2026 16:53:04 +0000 Subject: [PATCH 7/7] Improve test readability: move comment, rename variable Co-authored-by: Shelley --- .../ConditionalReturnsOnNewlineRuleTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/BuiltInRulesTests/ConditionalReturnsOnNewlineRuleTests.swift b/Tests/BuiltInRulesTests/ConditionalReturnsOnNewlineRuleTests.swift index 1887c07dfc..e1c18a8a98 100644 --- a/Tests/BuiltInRulesTests/ConditionalReturnsOnNewlineRuleTests.swift +++ b/Tests/BuiltInRulesTests/ConditionalReturnsOnNewlineRuleTests.swift @@ -4,7 +4,6 @@ import TestHelpers final class ConditionalReturnsOnNewlineRuleTests: SwiftLintTestCase { func testConditionalReturnsOnNewlineWithIfOnly() { // Test with `if_only` set to true - // guard statements should not trigger or be corrected let nonTriggeringExamples = [ Example("guard true else {\n return true\n}"), Example("guard true,\n let x = true else {\n return true\n}"), @@ -13,6 +12,7 @@ final class ConditionalReturnsOnNewlineRuleTests: SwiftLintTestCase { Example("if textField.returnKeyType == .Next {"), Example("if true { // return }"), Example("/*if true { */ return }"), + // guard statements should not trigger or be corrected Example("guard true else { return }"), ] let triggeringExamples = [ @@ -22,7 +22,7 @@ final class ConditionalReturnsOnNewlineRuleTests: SwiftLintTestCase { Example("↓if true { return \"YES\" } else { return \"NO\" }"), ] // Only include `if` corrections - guard corrections should not apply with if_only - let corrections = [ + let expectedCorrections = [ Example("↓if true { return }"): Example("if true {\n return\n}"), Example("↓if true { break } else { return }"): Example("if true { break } else {\n return\n}"), @@ -33,7 +33,7 @@ final class ConditionalReturnsOnNewlineRuleTests: SwiftLintTestCase { let description = ConditionalReturnsOnNewlineRule.description .with(triggeringExamples: triggeringExamples) .with(nonTriggeringExamples: nonTriggeringExamples) - .with(corrections: corrections) + .with(corrections: expectedCorrections) verifyRule(description, ruleConfiguration: ["if_only": true]) }