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 99a990d commit d75d058
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 0 deletions.
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ disabled_rules:
- no_grouping_extension
- no_magic_numbers
- one_declaration_per_file
- prefer_key_path
- prefer_nimble
- prefer_self_in_static_references
- prefixed_toplevel_constant
Expand Down
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,71 @@
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 }"),
Example("f ↓{ $0.0.a }"),
Example("f(a: ↓{ $0.b })"),
Example("f { 1 } a: ↓{ $0.b }"),
Example("let f: (Int) -> Int = ↓{ $0.bigEndian }")
]
)
}

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

private extension ExprSyntax {
func accesses(identifier: String?) -> Bool {
if let base = `as`(MemberAccessExprSyntax.self)?.base {
if let declRef = base.as(DeclReferenceExprSyntax.self) {
return declRef.baseName.text == identifier ?? "$0"
}
return base.accesses(identifier: identifier)
}
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
2 changes: 2 additions & 0 deletions Tests/IntegrationTests/default_rule_configurations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,8 @@ pattern_matching_keywords:
severity: warning
period_spacing:
severity: warning
prefer_key_path:
severity: warning
prefer_nimble:
severity: warning
prefer_self_in_static_references:
Expand Down

0 comments on commit d75d058

Please sign in to comment.