Skip to content
Open
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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@

### Enhancements

* Add `check_if_and_switch_expressions` option to the `implicit_return` rule.
When enabled, the rule also triggers on `return` statements in all branches
of an `if` or `switch` (including nested ones) that could be turned into an
`if` or `switch` expression. Closures are only checked when they declare
an explicit return type. Defaults to `false`.
[Andrew Elliott](https://github.com/andrewse02)
[nandhinisubbu](https://github.com/nandhinisubbu)
[#6167](https://github.com/realm/SwiftLint/issues/6167)

* Add autocorrection to the `multiline_call_arguments` rule, expanding single-line
and multi-line calls to one-argument-per-line, including nested calls whose closing
`)` would otherwise be stranded with the last argument.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ struct ImplicitReturnConfiguration: SeverityBasedRuleConfiguration {
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "included")
private(set) var includedKinds = Self.defaultIncludedKinds
@ConfigurationElement(key: "check_if_and_switch_expressions")
private(set) var checkIfAndSwitchExpressions = false

init(includedKinds: Set<ReturnKind> = Self.defaultIncludedKinds) {
init(includedKinds: Set<ReturnKind> = Self.defaultIncludedKinds, checkIfAndSwitchExpressions: Bool = false) {
self.includedKinds = includedKinds
self.checkIfAndSwitchExpressions = checkIfAndSwitchExpressions
}

func isKindIncluded(_ kind: ReturnKind) -> Bool {
Expand Down
86 changes: 80 additions & 6 deletions Source/SwiftLintBuiltInRules/Rules/Style/ImplicitReturnRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ struct ImplicitReturnRule: Rule {
name: "Implicit Return",
description: "Prefer implicit returns in closures, functions and getters",
kind: .style,
nonTriggeringExamples: ImplicitReturnRuleExamples.nonTriggeringExamples,
triggeringExamples: ImplicitReturnRuleExamples.triggeringExamples,
nonTriggeringExamples: ImplicitReturnRuleExamples.nonTriggeringExamples
+ ImplicitReturnRuleExamples.IfAndSwitchExpressionExamples.nonTriggeringExamples,
triggeringExamples: ImplicitReturnRuleExamples.triggeringExamples
+ ImplicitReturnRuleExamples.IfAndSwitchExpressionExamples.triggeringExamples,
corrections: ImplicitReturnRuleExamples.corrections
.merging(ImplicitReturnRuleExamples.IfAndSwitchExpressionExamples.corrections) { _, _ in
preconditionFailure("Duplicate correction in implicit return rule examples.")
}
)
}

Expand All @@ -29,7 +34,12 @@ private extension ImplicitReturnRule {

override func visitPost(_ node: ClosureExprSyntax) {
if configuration.isKindIncluded(.closure) {
collectViolation(in: node.statements)
// Without an explicit return type, the branches of an `if` or `switch` expression are type-checked
// independently, which may fail where the `return` statements compiled fine (e.g. `1` and `nil`).
collectViolation(
in: node.statements,
checkIfAndSwitch: node.signature?.returnClause != nil
)
}
}

Expand Down Expand Up @@ -61,10 +71,16 @@ private extension ImplicitReturnRule {
}
}

private func collectViolation(in itemList: CodeBlockItemListSyntax) {
guard let returnStmt = itemList.onlyElement?.item.as(ReturnStmtSyntax.self) else {
return
private func collectViolation(in itemList: CodeBlockItemListSyntax, checkIfAndSwitch: Bool = true) {
if let returnStmt = itemList.onlyReturnStatement {
collectViolation(for: returnStmt)
} else if checkIfAndSwitch, configuration.checkIfAndSwitchExpressions,
let returnStmts = itemList.ifOrSwitchExpressionReturnStatements {
returnStmts.forEach(collectViolation(for:))
}
}

private func collectViolation(for returnStmt: ReturnStmtSyntax) {
let returnKeyword = returnStmt.returnKeyword
violations.append(
at: returnKeyword.positionAfterSkippingLeadingTrivia,
Expand All @@ -78,3 +94,61 @@ private extension ImplicitReturnRule {
}
}
}

private extension CodeBlockItemListSyntax {
var onlyReturnStatement: ReturnStmtSyntax? {
onlyElement?.item.as(ReturnStmtSyntax.self)
}

/// The `return` statements in all branches of the only statement in this list if it is an `if` or `switch`
/// that could be used as an expression after removing them. `nil` otherwise.
var ifOrSwitchExpressionReturnStatements: [ReturnStmtSyntax]? {
guard let expression = onlyElement?.item.as(ExpressionStmtSyntax.self)?.expression else {
return nil
}
if let ifExpr = expression.as(IfExprSyntax.self) {
return ifExpr.branchReturnStatements
}
if let switchExpr = expression.as(SwitchExprSyntax.self) {
return switchExpr.branchReturnStatements
}
return nil
}

/// The `return` statements that make this branch of an `if` or `switch` produce a value. Either the branch's
/// only statement returning a value or the returns of a nested `if` or `switch` qualifying itself.
var branchReturnStatements: [ReturnStmtSyntax]? {
if let returnStmt = onlyReturnStatement {
return returnStmt.expression == nil ? nil : [returnStmt]
}
return ifOrSwitchExpressionReturnStatements
}
}

private extension IfExprSyntax {
var branchReturnStatements: [ReturnStmtSyntax]? {
guard let thenReturns = body.statements.branchReturnStatements else {
return nil
}
let elseReturns: [ReturnStmtSyntax]? = switch elseBody {
case let .codeBlock(block): block.statements.branchReturnStatements
case let .ifExpr(ifExpr): ifExpr.branchReturnStatements
case nil: nil
}
return elseReturns.map { thenReturns + $0 }
}
}

private extension SwitchExprSyntax {
var branchReturnStatements: [ReturnStmtSyntax]? {
var returns = [ReturnStmtSyntax]()
for element in cases {
guard case let .switchCase(switchCase) = element,
let caseReturns = switchCase.statements.branchReturnStatements else {
return nil
}
returns += caseReturns
}
return returns.isEmpty ? nil : returns
}
}
Loading