Update --self-required to support assignment type names like OSLogMessage (#2448)

Co-authored-by: calda <1811727+calda@users.noreply.github.com>
This commit is contained in:
Copilot
2026-03-15 09:31:51 -07:00
committed by Cal Stephens
parent 9a00c082cc
commit dce393fe93
4 changed files with 156 additions and 2 deletions
+1 -1
View File
@@ -2888,7 +2888,7 @@ Insert/remove explicit `self` where applicable.
Option | Description
--- | ---
`--self` | Explicit self: "insert", "remove" (default) or "init-only"
`--self-required` | Comma-delimited list of functions with @autoclosure arguments
`--self-required` | Comma-delimited list of functions / types with @autoclosure arguments
<details>
<summary>Examples</summary>
+49
View File
@@ -130,10 +130,59 @@ extension Formatter {
}
index = scopeStart
}
if !staticSelf,
isAssignedToSelfRequiredType(at: index)
{
return false
}
removeTokens(in: i ..< nextIndex)
return true
}
/// Whether the expression at the given index is on the RHS of an assignment
/// whose LHS has a type annotation matching a `selfRequired` type.
/// e.g. `let _: OSLogMessage = "\(self.bar)"` or `let _: OSLogMessage = foo(self.bar)`
func isAssignedToSelfRequiredType(at i: Int) -> Bool {
guard !options.selfRequired.isEmpty else { return false }
// Walk backwards from start of expression to find `=` operator
var index = i
while let prevIndex = self.index(of: .nonSpaceOrCommentOrLinebreak, before: index) {
if tokens[prevIndex] == .operator("=", .infix) {
return isSelfRequiredType(beforeAssignment: prevIndex)
}
switch tokens[prevIndex] {
case .identifier, .operator(".", .infix),
.keyword("try"), .keyword("await"),
.operator("?", .postfix), .operator("!", .postfix):
index = prevIndex
default:
return false
}
}
return false
}
/// Whether the type annotation before an `=` operator is a `selfRequired` type.
func isSelfRequiredType(beforeAssignment equalsIndex: Int) -> Bool {
var typeIndex = equalsIndex
while let prevTypeIndex = index(of: .nonSpaceOrCommentOrLinebreak, before: typeIndex) {
if tokens[prevTypeIndex].isUnwrapOperator {
typeIndex = prevTypeIndex
} else if tokens[prevTypeIndex] == .endOfScope(">"),
let matchingStart = startOfScope(at: prevTypeIndex)
{
typeIndex = matchingStart
} else {
typeIndex = prevTypeIndex
break
}
}
guard tokens[typeIndex].isIdentifier else {
return false
}
return options.selfRequired.contains(tokens[typeIndex].unescaped())
}
/// gather declared variable names, starting at index after let/var keyword
func processDeclaredVariables(at index: inout Int, names: inout Set<String>,
removeSelfKeyword: String?, onlyLocal: Bool,
+1 -1
View File
@@ -838,7 +838,7 @@ struct _Descriptors {
let selfRequired = OptionDescriptor(
argumentName: "self-required",
displayName: "Self Required",
help: "Comma-delimited list of functions with @autoclosure arguments",
help: "Comma-delimited list of functions / types with @autoclosure arguments",
keyPath: \FormatOptions.selfRequired
)
let throwCapturing = OptionDescriptor(
+105
View File
@@ -879,6 +879,111 @@ final class RedundantSelfTests: XCTestCase {
testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.propertyTypes])
}
func testNoRemoveSelfInStringInterpolationWithSelfRequiredTypeAnnotation() {
let input = """
class C {
let bar = NSObject()
func f() {
let _: OSLogMessage = "\\(self.bar)"
}
}
"""
let options = FormatOptions(selfRequired: ["OSLogMessage"])
testFormatting(for: input, rule: .redundantSelf, options: options,
exclude: [.propertyTypes])
}
func testNoRemoveSelfInStringInterpolationWithOptionalSelfRequiredTypeAnnotation() {
let input = """
class C {
let bar = NSObject()
func f() {
let x: OSLogMessage? = "\\(self.bar)"
}
}
"""
let options = FormatOptions(selfRequired: ["OSLogMessage"])
testFormatting(for: input, rule: .redundantSelf, options: options,
exclude: [.propertyTypes])
}
func testRemoveSelfInStringInterpolationWithNonSelfRequiredTypeAnnotation() {
let input = """
class C {
let bar = "test"
func f() {
let x: String = "\\(self.bar)"
}
}
"""
let output = """
class C {
let bar = "test"
func f() {
let x: String = "\\(bar)"
}
}
"""
testFormatting(for: input, output, rule: .redundantSelf,
exclude: [.propertyTypes, .redundantType])
}
func testNoRemoveSelfInFunctionCallWithSelfRequiredTypeAnnotation() {
let input = """
class C {
let bar = NSObject()
func f() {
let _: OSLogMessage = foo(self.bar)
}
}
"""
let options = FormatOptions(selfRequired: ["OSLogMessage"])
testFormatting(for: input, rule: .redundantSelf, options: options,
exclude: [.propertyTypes])
}
func testNoRemoveSelfInMethodChainWithSelfRequiredTypeAnnotation() {
let input = """
class C {
let bar = NSObject()
func f() {
let _: OSLogMessage = Foo.bar(self.bar)
}
}
"""
let options = FormatOptions(selfRequired: ["OSLogMessage"])
testFormatting(for: input, rule: .redundantSelf, options: options,
exclude: [.propertyTypes])
}
func testNoRemoveSelfInDirectAssignmentWithSelfRequiredTypeAnnotation() {
let input = """
class C {
let bar = NSObject()
func f() {
let _: OSLogMessage = self.bar
}
}
"""
let options = FormatOptions(selfRequired: ["OSLogMessage"])
testFormatting(for: input, rule: .redundantSelf, options: options,
exclude: [.propertyTypes])
}
func testNoRemoveSelfInSelfRequiredTypeInitializer() {
let input = """
class C {
let bar = NSObject()
func f() {
let msg = OSLogMessage(self.bar)
}
}
"""
let options = FormatOptions(selfRequired: ["OSLogMessage"])
testFormatting(for: input, rule: .redundantSelf, options: options,
exclude: [.propertyTypes])
}
func testSelfRemovedFromSwitchCaseWhere() {
let input = """
class Foo {