Files
SwiftFormat/Sources/Rules/RedundantSwiftTestingSuite.swift

110 lines
4.1 KiB
Swift

//
// RedundantSwiftTestingSuite.swift
// SwiftFormat
//
// Created by Cal Stephens on 2/18/26.
// Copyright © 2026 Nick Lockwood. All rights reserved.
//
import Foundation
public extension FormatRule {
static let redundantSwiftTestingSuite = FormatRule(
help: "Remove redundant @Suite attribute with no arguments.",
orderAfter: [.swiftTestingTestCaseNames]
) { formatter in
guard formatter.hasImport("Testing") else { return }
formatter.forEach(.attribute) { attrIndex, token in
guard token.string == "@Suite" else { return }
// Check what comes after @Suite
guard let nextTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: attrIndex)
else { return }
let nextToken = formatter.tokens[nextTokenIndex]
// If there's a parenthesis after @Suite, check if it has arguments
if nextToken == .startOfScope("(") {
guard let endOfScope = formatter.endOfScope(at: nextTokenIndex) else { return }
// Check if there are any non-whitespace/comment tokens between the parens
if let firstTokenInParens = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: nextTokenIndex),
firstTokenInParens < endOfScope
{
// Has arguments - keep it
return
}
}
// Remove the @Suite attribute
var attributeEndIndex = attrIndex
// Check if there are parentheses after @Suite
if let nextTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: attrIndex),
formatter.tokens[nextTokenIndex] == .startOfScope("("),
let endOfScope = formatter.endOfScope(at: nextTokenIndex)
{
attributeEndIndex = endOfScope
}
var startIndex = attrIndex
var endIndex = attributeEndIndex
// Check if there's a leading space (between another attribute and this one)
let hasLeadingSpace = attrIndex > 0 && formatter.tokens[attrIndex - 1].isSpace
let leadingSpaceIsAfterAttribute = hasLeadingSpace && attrIndex > 1 && !formatter.tokens[attrIndex - 2].isLinebreak
let nextNonSpaceIndex = formatter.index(of: .nonSpace, after: attributeEndIndex)
let hasTrailingLinebreak = nextNonSpaceIndex != nil && formatter.tokens[nextNonSpaceIndex!].isLinebreak
let hasTrailingSpace = attributeEndIndex + 1 < formatter.tokens.count && formatter.tokens[attributeEndIndex + 1].isSpace
if leadingSpaceIsAfterAttribute {
// Remove the space before @Suite (space between attributes)
startIndex = attrIndex - 1
// Don't remove trailing linebreak - preserve the line structure
// Don't remove trailing space - it separates from the next token
} else if hasTrailingLinebreak, let nextIndex = nextNonSpaceIndex {
// @Suite is at the start of the line (possibly with indentation)
endIndex = nextIndex
// Also remove leading indentation
if hasLeadingSpace, attrIndex > 1, formatter.tokens[attrIndex - 2].isLinebreak {
startIndex = attrIndex - 1
}
} else if hasTrailingSpace {
endIndex = attributeEndIndex + 1
}
formatter.removeTokens(in: startIndex ... endIndex)
}
} examples: {
"""
```diff
import Testing
- @Suite
struct MyFeatureTests {
@Test func myFeature() {
#expect(true)
}
}
- @Suite()
struct OtherTests {
@Test func otherFeature() {
#expect(true)
}
}
// Not redundant - @Suite has arguments
@Suite(.serialized)
struct SerializedTests {
@Test func feature() {
#expect(true)
}
}
```
"""
}
}