Files
2025-07-13 11:32:06 -07:00

90 lines
3.5 KiB
Swift

//
// PreferKeyPath.swift
// SwiftFormat
//
// Created by Nick Lockwood on 7/29/20.
// Copyright © 2024 Nick Lockwood. All rights reserved.
//
import Foundation
public extension FormatRule {
static let preferKeyPath = FormatRule(
help: "Convert trivial `map { $0.foo }` closures to keyPath-based syntax."
) { formatter in
formatter.forEach(.startOfScope("{")) { i, _ in
guard formatter.options.swiftVersion >= "5.2",
var prevIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i)
else {
return
}
var prevToken = formatter.tokens[prevIndex]
var label: String?
if prevToken == .delimiter(":"),
let labelIndex = formatter.index(of: .nonSpace, before: prevIndex),
case let .identifier(name) = formatter.tokens[labelIndex],
let prevIndex2 = formatter.index(of: .nonSpaceOrLinebreak, before: labelIndex)
{
label = name
prevToken = formatter.tokens[prevIndex2]
prevIndex = prevIndex2
}
let parenthesized = prevToken == .startOfScope("(")
if parenthesized {
prevToken = formatter.last(.nonSpaceOrLinebreak, before: prevIndex) ?? prevToken
}
guard case let .identifier(name) = prevToken,
["map", "flatMap", "compactMap", "allSatisfy", "filter", "contains"].contains(name),
let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: {
$0 == .identifier("$0")
}),
let endIndex = formatter.endOfScope(at: i),
let lastIndex = formatter.index(of: .nonSpaceOrLinebreak, before: endIndex)
else {
return
}
if let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endIndex),
formatter.isLabel(at: nextIndex)
{
return
}
if name == "contains" {
if label != "where" {
return
}
} else if label != nil {
return
}
var replacementTokens: [Token]
if nextIndex == lastIndex {
// https://bugs.swift.org/browse/SR-12897
guard formatter.options.swiftVersion >= "6" else { return }
replacementTokens = tokenize("\\.self")
} else {
let tokens = formatter.tokens[nextIndex + 1 ... lastIndex]
guard tokens.allSatisfy({ $0.isSpace || $0.isIdentifier || $0.isOperator(".") }) else {
return
}
replacementTokens = [.operator("\\", .prefix)] + tokens
}
if let label {
replacementTokens = [.identifier(label), .delimiter(":"), .space(" ")] + replacementTokens
}
if !parenthesized {
replacementTokens = [.startOfScope("(")] + replacementTokens + [.endOfScope(")")]
}
formatter.replaceTokens(in: prevIndex + 1 ... endIndex, with: replacementTokens)
}
} examples: {
"""
```diff
- let barArray = fooArray.map { $0.bar }
+ let barArray = fooArray.map(\\.bar)
- let barArray = fooArray.compactMap { $0.optionalBar }
+ let barArray = fooArray.compactMap(\\.optionalBar)
```
"""
}
}