Files
SwiftFormat/Sources/Rules/PreferSwiftStringAPI.swift

57 lines
2.2 KiB
Swift

//
// PreferSwiftStringAPI.swift
// SwiftFormat
//
// Created by Sutheesh Sukumaran on 05/04/2026.
// Copyright © 2026 Nick Lockwood. All rights reserved.
//
import Foundation
public extension FormatRule {
/// Replace Objective-C bridged String methods with their Swift equivalents
/// Disabled by default since `replacing(_:with:)` is only available in iOS 16+ / macOS 13+.
static let preferSwiftStringAPI = FormatRule(
help: "Replace Objective-C bridged String methods with Swift equivalents.",
disabledByDefault: true
) { formatter in
// replacing(_:with:) was introduced in Swift 5.7
guard formatter.options.swiftVersion >= "5.7" else { return }
formatter.forEach(.identifier("replacingOccurrences")) { i, _ in
// Must be a method call (preceded by a dot)
guard let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i),
formatter.tokens[prevIndex] == .operator(".", .infix)
else { return }
// Must be followed immediately by a `(` argument list
guard let openParenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i),
formatter.tokens[openParenIndex] == .startOfScope("(")
else { return }
// Only transform the two-argument form: `replacingOccurrences(of:with:)`
let args = formatter.parseFunctionCallArguments(startOfScope: openParenIndex)
guard args.count == 2,
args[0].label == "of",
args[1].label == "with",
let ofLabelIndex = args[0].labelIndex
else { return }
// Remove the `of:` label and any whitespace up to the value.
// Since ofLabelIndex > i, this does not invalidate index i.
let valueStart = args[0].valueRange.lowerBound
formatter.removeTokens(in: ofLabelIndex ..< valueStart)
// Rename `replacingOccurrences` `replacing`
formatter.replaceToken(at: i, with: .identifier("replacing"))
}
} examples: {
"""
```diff
- str.replacingOccurrences(of: "foo", with: "bar")
+ str.replacing("foo", with: "bar")
```
"""
}
}