Skip to content

Commit

Permalink
Add new prefer_key_path rule
Browse files Browse the repository at this point in the history
  • Loading branch information
SimplyDanny committed Apr 29, 2024
1 parent 108b1fc commit 247038c
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@
[SimplyDanny](https://github.com/SimplyDanny)
[#70](https://github.com/realm/SwiftLint/issues/70)

* Add new `prefer_key_path` rule that triggers when a trailing closure on a function
call is only hosting a (chained) member access expression since the closure can be
replaced with a key path argument.
[SimplyDanny](https://github.com/SimplyDanny)

* Warn when `--fix` comes together with `--strict` or `--lenient` as only `--fix`
takes effect then.
[SimplyDanny](https://github.com/SimplyDanny)
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 @@ -146,6 +146,7 @@ public let builtInRules: [any Rule.Type] = [
OverrideInExtensionRule.self,
PatternMatchingKeywordsRule.self,
PeriodSpacingRule.self,
PreferKeyPathRule.self,
PreferNimbleRule.self,
PreferSelfInStaticReferencesRule.self,
PreferSelfTypeOverTypeOfSelfRule.self,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import SwiftLintCore
import SwiftSyntax

@SwiftSyntaxRule
struct PreferKeyPathRule: OptInRule {
var configuration = SeverityConfiguration<Self>(.warning)

static var description = RuleDescription(
identifier: "prefer_key_path",
name: "Prefer Key Path",
description: "Use a key path argument instead of a closure with property access",
kind: .idiomatic,
minSwiftVersion: .fiveDotTwo,
nonTriggeringExamples: [
Example("f {}"),
Example("f { $0 }"),
Example("f() { g() }"),
Example("f { a.b.c }"),
Example("f { a in a }"),
Example("f { a, b in a.b }"),
Example("f { (a, b) in a.b }")
],
triggeringExamples: [
Example("f ↓{ $0.a }"),
Example("f ↓{ a in a.b }"),
Example("f ↓{ a in a.b.c }"),
Example("f ↓{ (a: A) in a.b }"),
Example("f ↓{ (a b: A) in b.c }")
]
)
}

private extension PreferKeyPathRule {
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
override func visitPost(_ node: FunctionCallExprSyntax) {
if let closure = node.trailingClosure,
case let .expr(expr) = closure.statements.onlyElement?.item,
expr.accesses(identifier: closure.onlyParameter) {
violations.append(closure.positionAfterSkippingLeadingTrivia)
}
}
}
}

private extension ExprSyntax {
func accesses(identifier: String?) -> Bool {
if let access = `as`(MemberAccessExprSyntax.self) {
if let declRef = access.base?.as(DeclReferenceExprSyntax.self) {
return declRef.baseName.text == identifier ?? "$0"
}
return false
}
return false
}
}

private extension ClosureExprSyntax {
var onlyParameter: String? {
switch signature?.parameterClause {
case let .simpleInput(params):
return params.onlyElement?.name.text
case let .parameterClause(params):
let param = params.parameters.onlyElement
return param?.secondName?.text ?? param?.firstName.text
case nil: return nil
}
}
}
6 changes: 6 additions & 0 deletions Tests/GeneratedTests/GeneratedTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -865,6 +865,12 @@ final class PeriodSpacingRuleGeneratedTests: SwiftLintTestCase {
}
}

final class PreferKeyPathRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(PreferKeyPathRule.description)
}
}

final class PreferNimbleRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(PreferNimbleRule.description)
Expand Down

0 comments on commit 247038c

Please sign in to comment.