Improve handling of acronyms in swiftTestingTestCaseNames rule (#2413)

Co-authored-by: calda <1811727+calda@users.noreply.github.com>
This commit is contained in:
Copilot
2026-02-23 09:36:57 -08:00
committed by Cal Stephens
parent d2027b4095
commit b7e7bce7f1
2 changed files with 116 additions and 6 deletions
+35 -6
View File
@@ -213,18 +213,47 @@ extension String {
words.append(contentsOf: String(segment).splitCamelCase())
}
return words.joined(separator: " ").lowercased()
// Merge a lone single lowercase leading character with a following all-uppercase word.
// This handles acronym-first names after test prefix removal, e.g. "uUID" (from "testUUID") "UUID".
if words.count >= 2,
words[0].count == 1,
words[0].first?.isLowercase == true,
words[1].allSatisfy(\.isUppercase)
{
words = [words[0].uppercased() + words[1]] + Array(words.dropFirst(2))
}
// Lowercase each word, but preserve all-uppercase words (acronyms like UUID, URL, ABC).
return words.map { $0.allSatisfy(\.isUppercase) ? $0 : $0.lowercased() }.joined(separator: " ")
}
/// Splits a camelCase string into individual words.
/// Splits a camelCase string into individual words, treating consecutive uppercase letters as acronyms.
/// For example: "UUIDIsValid" ["UUID", "Is", "Valid"], "alphabetStartsWithABC" ["alphabet", "Starts", "With", "ABC"].
func splitCamelCase() -> [String] {
var words: [String] = []
var currentWord = ""
let chars = Array(self)
for char in self {
if char.isUppercase, !currentWord.isEmpty {
words.append(currentWord)
currentWord = String(char)
for i in 0 ..< chars.count {
let char = chars[i]
let nextChar = i + 1 < chars.count ? chars[i + 1] : nil
if char.isUppercase {
if currentWord.isEmpty {
currentWord.append(char)
} else if currentWord.last!.isLowercase {
// LowerUpper transition: start a new word
words.append(currentWord)
currentWord = String(char)
} else if let next = nextChar, next.isLowercase {
// Uppercase sequence followed by lowercase: this char starts a new word
// e.g. "UUIDIs" "UUID" + "Is"
words.append(currentWord)
currentWord = String(char)
} else {
// Continue accumulating the uppercase sequence (acronym)
currentWord.append(char)
}
} else {
currentWord.append(char)
}
@@ -1100,4 +1100,85 @@ final class SwiftTestingTestCaseNamesTests: XCTestCase {
swiftVersion: "6.2"),
exclude: [.enumNamespaces])
}
func testConvertsAcronymAtStartToRawIdentifier() {
let input = """
import Testing
struct MyFeatureTests {
@Test
func testUUIDIsValid() {
#expect(true)
}
}
"""
let output = """
import Testing
struct MyFeatureTests {
@Test
func `UUID is valid`() {
#expect(true)
}
}
"""
testFormatting(for: input, output, rule: .swiftTestingTestCaseNames,
options: FormatOptions(swiftVersion: "6.2"))
}
func testConvertsTrailingAcronymToRawIdentifier() {
let input = """
import Testing
struct MyFeatureTests {
@Test
func testAlphabetStartsWithABC() {
#expect(true)
}
}
"""
let output = """
import Testing
struct MyFeatureTests {
@Test
func `alphabet starts with ABC`() {
#expect(true)
}
}
"""
testFormatting(for: input, output, rule: .swiftTestingTestCaseNames,
options: FormatOptions(swiftVersion: "6.2"))
}
func testConvertsMiddleAcronymToRawIdentifier() {
let input = """
import Testing
struct MyFeatureTests {
@Test
func testMyURLIsValid() {
#expect(true)
}
}
"""
let output = """
import Testing
struct MyFeatureTests {
@Test
func `my URL is valid`() {
#expect(true)
}
}
"""
testFormatting(for: input, output, rule: .swiftTestingTestCaseNames,
options: FormatOptions(swiftVersion: "6.2"))
}
}