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
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ disabled_rules:
- discouraged_optional_collection
- explicit_acl
- explicit_enum_raw_value
- explicit_return
- explicit_top_level_acl
- explicit_type_interface
- file_types_order
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@
built-in rules in accordance with the SARIF specification.
[ahmadalfy](https://github.com/ahmadalfy)
[#6499](https://github.com/realm/SwiftLint/issues/6499)

* Add `explicit_return` opt-in rule that warns against omitting the `return`
keyword inside closures, functions and getters.
[m-chojnacki](https://github.com/m-chojnacki)
[talanov](https://github.com/talanov)
[#3632](https://github.com/realm/SwiftLint/issues/3632)
[#3859](https://github.com/realm/SwiftLint/issues/3859)

* Add `allow_underscore_prefixed_names` option to `unused_parameter` so
underscore-prefixed parameter names can be treated as intentionally
Expand Down
1 change: 1 addition & 0 deletions Source/SwiftLintBuiltInRules/Models/BuiltInRules.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public let builtInRules: [any Rule.Type] = [
ExplicitACLRule.self,
ExplicitEnumRawValueRule.self,
ExplicitInitRule.self,
ExplicitReturnRule.self,
ExplicitSelfRule.self,
ExplicitTopLevelACLRule.self,
ExplicitTypeInterfaceRule.self,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import SwiftLintCore

@AutoConfigParser
struct ExplicitReturnConfiguration: SeverityBasedRuleConfiguration {
@AcceptableByConfigurationElement
enum ReturnKind: String, CaseIterable, Comparable {
case closure
case function
case getter
case `subscript`
case initializer

static func < (lhs: Self, rhs: Self) -> Bool {
lhs.rawValue < rhs.rawValue
}
}

static let defaultIncludedKinds: Set<ReturnKind> = [.function, .getter, .subscript, .initializer]

@ConfigurationElement(key: "severity")
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "included")
private(set) var includedKinds = Self.defaultIncludedKinds

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

func isKindIncluded(_ kind: ReturnKind) -> Bool {
includedKinds.contains(kind)
}
}
115 changes: 115 additions & 0 deletions Source/SwiftLintBuiltInRules/Rules/Style/ExplicitReturnRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import Foundation
import SwiftSyntax

@SwiftSyntaxRule(correctable: true, optIn: true)
struct ExplicitReturnRule: Rule {
var configuration = ExplicitReturnConfiguration()

static let description = RuleDescription(
identifier: "explicit_return",
name: "Explicit Return",
description: "Prefer explicit returns in closures, functions and getters",
kind: .style,
nonTriggeringExamples: ExplicitReturnRuleExamples.nonTriggeringExamples,
triggeringExamples: ExplicitReturnRuleExamples.triggeringExamples,
corrections: ExplicitReturnRuleExamples.corrections
)
}

private extension ExplicitReturnRule {
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
override var skippableDeclarations: [any DeclSyntaxProtocol.Type] { [ProtocolDeclSyntax.self] }

override func visitPost(_ node: AccessorDeclSyntax) {
if configuration.isKindIncluded(.getter),
node.accessorSpecifier.tokenKind == .keyword(.get),
let body = node.body {
collectViolation(in: body.statements)
}
}

override func visitPost(_ node: ClosureExprSyntax) {
if configuration.isKindIncluded(.closure) {
collectViolation(in: node.statements, isInsideClosure: true)
}
}

override func visitPost(_ node: FunctionDeclSyntax) {
if configuration.isKindIncluded(.function),
node.signature.allowsImplicitReturns,
let body = node.body {
collectViolation(in: body.statements)
}
}

override func visitPost(_ node: InitializerDeclSyntax) {
if configuration.isKindIncluded(.initializer),
node.optionalMark != nil,
let body = node.body {
collectViolation(in: body.statements)
}
}

override func visitPost(_ node: PatternBindingSyntax) {
if configuration.isKindIncluded(.getter),
case let .getter(itemList) = node.accessorBlock?.accessors {
collectViolation(in: itemList)
}
}

override func visitPost(_ node: SubscriptDeclSyntax) {
if configuration.isKindIncluded(.subscript),
case let .getter(itemList) = node.accessorBlock?.accessors {
collectViolation(in: itemList)
}
}

private func collectViolation(in itemList: CodeBlockItemListSyntax, isInsideClosure: Bool = false) {
guard let onlyItem = itemList.onlyElement?.item,
!onlyItem.is(ReturnStmtSyntax.self),
Syntax(onlyItem).isProtocol((any ExprSyntaxProtocol).self) else {
return
}
if isInsideClosure, Syntax(onlyItem).isFunctionCallExpr {
return
}
let position = onlyItem.positionAfterSkippingLeadingTrivia
violations.append(
at: position,
correction: .init(
start: position,
end: position,
replacement: "return "
)
)
}
}
}

private extension Syntax {
var isFunctionCallExpr: Bool {
if `is`(FunctionCallExprSyntax.self) {
return true
}
if let tryExpr = `as`(TryExprSyntax.self) {
return Syntax(tryExpr.expression).isFunctionCallExpr
}
if let awaitExpr = `as`(AwaitExprSyntax.self) {
return Syntax(awaitExpr.expression).isFunctionCallExpr
}
return false
}
}

private extension FunctionSignatureSyntax {
var allowsImplicitReturns: Bool {
guard let returnClause else { return false }
if let identifierType = returnClause.type.as(IdentifierTypeSyntax.self) {
return identifierType.name.text != "Void" && identifierType.name.text != "Never"
}
if let tupleType = returnClause.type.as(TupleTypeSyntax.self) {
return !tupleType.elements.isEmpty
}
return true
}
}
Loading