Revert recent #if indentation changes related to method chains (#2345)

This commit is contained in:
Cal Stephens
2026-01-29 06:12:15 -08:00
parent 8e36d27eb6
commit dc93e46b76
2 changed files with 16 additions and 1075 deletions
+12 -113
View File
@@ -28,7 +28,6 @@ public extension FormatRule {
var linewrapStack = [false]
var lineIndex = 0
var preserveIfdefDepth = 0
var noIndentIfdefDepth = 0
@discardableResult
func applyIndent(_ indent: String, at index: Int) -> Int {
@@ -58,39 +57,6 @@ public extension FormatRule {
scopeStack.removeLast()
}
// Returns the change in index after applying indent if needed for noIndent ifdef
func applyNoIndentIfdefFix(ifdefIndent: String, at startIndex: Int) -> Int {
let currentIndent = formatter.currentIndentForLine(at: startIndex)
let isNested = noIndentIfdefDepth > 1
if currentIndent.count < ifdefIndent.count ||
(isNested && currentIndent.count > ifdefIndent.count)
{
return applyIndent(ifdefIndent, at: startIndex)
}
return 0
}
func matchingIfdefIndent(forDirectiveAt directiveIndex: Int, fallbackIndent: String) -> String {
var nestedEndifCount = 0
var index = directiveIndex - 1
while index >= 0 {
let token = formatter.tokens[index]
switch token {
case .endOfScope("#endif"):
nestedEndifCount += 1
case .startOfScope("#if"):
if nestedEndifCount == 0 {
return formatter.currentIndentForLine(at: index)
}
nestedEndifCount -= 1
default:
break
}
index -= 1
}
return fallbackIndent
}
var i = i
switch token {
case let .startOfScope(string):
@@ -149,26 +115,7 @@ public extension FormatRule {
i += applyIndent(indent, at: formatter.startOfLine(at: i))
indent += formatter.options.indent
case .noIndent:
let currentIndent = formatter.currentIndentForLine(at: i)
// Check if previous non-blank line starts with #if/#else/#elseif (directly nested)
var directlyNestedInIfdef = false
if noIndentIfdefDepth > 0,
let prevLinebreakIndex = formatter.index(of: .linebreak, before: i)
{
let prevLineStartIndex = formatter.startOfLine(at: prevLinebreakIndex, excludingIndent: true)
if let prevLineStartToken = formatter.token(at: prevLineStartIndex),
[.startOfScope("#if"), .keyword("#else"), .keyword("#elseif")].contains(prevLineStartToken)
{
directlyNestedInIfdef = true
}
}
if currentIndent.count < indent.count || directlyNestedInIfdef {
// Under-indented, or directly nested #if (should be at outer #if level)
i += applyIndent(indent, at: formatter.startOfLine(at: i))
} else {
// At or above expected level (e.g., in method chain), keep it
indent = currentIndent
}
i += applyIndent(indent, at: formatter.startOfLine(at: i))
case .preserve:
indent = formatter.currentIndentForLine(at: i)
case .outdent:
@@ -176,8 +123,6 @@ public extension FormatRule {
}
if formatter.options.ifdefIndent == .preserve {
preserveIfdefDepth += 1
} else if formatter.options.ifdefIndent == .noIndent {
noIndentIfdefDepth += 1
}
case "{" where formatter.isFirstStackedClosureArgument(at: i):
guard var prevIndex = formatter.index(of: .nonSpace, before: i) else {
@@ -281,19 +226,12 @@ public extension FormatRule {
}
let start = formatter.startOfLine(at: i)
switch formatter.options.ifdefIndent {
case .indent:
case .indent, .noIndent:
i += applyIndent(indent, at: start)
case .noIndent:
// #else/#elseif should be at same level as corresponding #if
let fallbackIndent = indentStack.last ?? ""
let targetIndent = matchingIfdefIndent(forDirectiveAt: i, fallbackIndent: fallbackIndent)
if formatter.currentIndentForLine(at: start) != targetIndent {
i += applyIndent(targetIndent, at: start)
}
case .preserve:
break
case .outdent:
i += applyIndent("", at: start)
case .preserve:
break
}
case .keyword("@unknown") where scopeStack.last != .startOfScope("#if"):
var indent = indentStack[indentStack.count - 2]
@@ -359,9 +297,6 @@ public extension FormatRule {
// Handle end of scope
if let scope = scopeStack.last, token.isEndOfScope(scope) {
let indentCount = indentCounts.last! - 1
// Capture #if indent before popScope for noIndent handling
let ifdefIndentBeforePop = (token == .endOfScope("#endif") && formatter.options.ifdefIndent == .noIndent)
? indentStack.last : nil
popScope()
guard !token.isLinebreak, lineIndex > scopeStartLineIndexes.last ?? -1 else {
break
@@ -412,16 +347,6 @@ public extension FormatRule {
if token == .endOfScope("#endif"), formatter.options.ifdefIndent == .outdent {
i += applyIndent("", at: start)
} else if token == .endOfScope("#endif"), formatter.options.ifdefIndent == .noIndent {
// #endif should be at same level as corresponding #if
// Align to the indent of the matching #if when available.
let fallbackIndent = ifdefIndentBeforePop ?? indentStack.last ?? ""
let targetIndent = matchingIfdefIndent(forDirectiveAt: i, fallbackIndent: fallbackIndent)
if formatter.currentIndentForLine(at: start) != targetIndent {
i += applyIndent(targetIndent, at: start)
}
} else if token == .endOfScope("#endif"), formatter.options.ifdefIndent == .preserve {
// Do nothing - preserve current position
} else {
var indent = indentStack.last ?? ""
if token.isSwitchCaseOrDefault,
@@ -442,26 +367,19 @@ public extension FormatRule {
popScope()
}
switch formatter.options.ifdefIndent {
case .indent:
case .indent, .noIndent:
i += applyIndent(indent, at: formatter.startOfLine(at: i))
case .noIndent:
// #endif should be at same level as corresponding #if
i += applyNoIndentIfdefFix(ifdefIndent: indentStack.last ?? indent, at: formatter.startOfLine(at: i))
case .preserve:
break
case .outdent:
i += applyIndent("", at: formatter.startOfLine(at: i))
case .preserve:
break
}
if scopeStack.last == .startOfScope("#if") {
popScope()
}
}
if token == .endOfScope("#endif") {
if formatter.options.ifdefIndent == .preserve {
preserveIfdefDepth = max(preserveIfdefDepth - 1, 0)
} else if formatter.options.ifdefIndent == .noIndent {
noIndentIfdefDepth = max(noIndentIfdefDepth - 1, 0)
}
if token == .endOfScope("#endif"), formatter.options.ifdefIndent == .preserve {
preserveIfdefDepth = max(preserveIfdefDepth - 1, 0)
}
}
switch token {
@@ -539,9 +457,7 @@ public extension FormatRule {
// Determine current indent
var indent = indentStack.last ?? ""
if linewrapped, lineIndex == scopeStartLineIndexes.last,
!(formatter.options.ifdefIndent == .noIndent && noIndentIfdefDepth > 0)
{
if linewrapped, lineIndex == scopeStartLineIndexes.last {
indent = indentStack.count > 1 ? indentStack[indentStack.count - 2] : ""
}
lineIndex += 1
@@ -659,12 +575,6 @@ public extension FormatRule {
} else if linewrapped {
// Don't indent line starting with dot if previous line was just a closing brace
var lastToken = formatter.tokens[lastNonSpaceOrLinebreakIndex]
let isConditionalAssignmentLinebreak =
formatter.options.ifdefIndent == .noIndent &&
noIndentIfdefDepth > 0 &&
scopeStack.last == .operator("=", .infix) &&
lastNonSpaceOrLinebreakIndex > -1 &&
formatter.tokens[lastNonSpaceOrLinebreakIndex] == .operator("=", .infix)
if formatter.options.allmanBraces, nextToken == .startOfScope("{"),
formatter.isStartOfClosure(at: nextNonSpaceIndex)
{
@@ -686,13 +596,6 @@ public extension FormatRule {
[.keyword("#else"), .keyword("#elseif"), .endOfScope("#endif")].contains(startToken)
{
indent = formatter.currentIndentForLine(at: lineStart)
} else if formatter.options.ifdefIndent == .noIndent,
let startToken,
[.startOfScope("#if"), .keyword("#else"), .keyword("#elseif")].contains(startToken)
{
// For noIndent mode, content directly after #if/#else/#elseif should
// stay at the directive's level, not be indented as a method chain
// (indent already set to indentStack.last which is the #if level)
} else if formatter.tokens[lineStart ..< lastNonSpaceOrLinebreakIndex].allSatisfy({
$0.isEndOfScope || $0.isSpaceOrComment
}) {
@@ -709,14 +612,10 @@ public extension FormatRule {
indent += formatter.options.indent
}
} else if !formatter.options.xcodeIndentation || !formatter.isWrappedDeclaration(at: i) {
if !isConditionalAssignmentLinebreak {
indent += formatter.linewrapIndent(at: i)
}
}
} else if !formatter.options.xcodeIndentation || !formatter.isWrappedDeclaration(at: i) {
if !isConditionalAssignmentLinebreak {
indent += formatter.linewrapIndent(at: i)
}
} else if !formatter.options.xcodeIndentation || !formatter.isWrappedDeclaration(at: i) {
indent += formatter.linewrapIndent(at: i)
}
linewrapStack[linewrapStack.count - 1] = true
+4 -962
View File
@@ -3880,13 +3880,13 @@ final class IndentTests: XCTestCase {
Text("Hello, world!")
// Comment above
#if os(macOS)
.padding()
.padding()
#endif
Text("Hello, world!")
#if os(macOS)
// Comment inside
.padding()
// Comment inside
.padding()
#endif
// swiftformat:options --ifdef outdent
@@ -4575,22 +4575,8 @@ final class IndentTests: XCTestCase {
}
}
"""
let output = """
class Bar {
func foo() {
Text("Hello")
#if os(iOS)
.font(.largeTitle)
#elseif os(macOS)
.font(.headline)
#else
.font(.headline)
#endif
}
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options)
testFormatting(for: input, rule: .indent, options: options)
}
func testIfDefPostfixMemberSyntaxNoIndenting2() {
@@ -4659,929 +4645,6 @@ final class IndentTests: XCTestCase {
testFormatting(for: input, rule: .indent, options: options)
}
// MARK: - noIndent spec scenarios
func testNoIndentBasicIfBlock() {
let input = """
func foo() {
#if DEBUG
print("debug")
#endif
}
"""
let output = """
func foo() {
#if DEBUG
print("debug")
#endif
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options)
}
func testNoIndentIfdefFixesOverIndentedElse() {
let input = """
func example(flag: Bool) {
#if os(macOS)
if flag {
return
}
#else
return
#endif
}
"""
let output = """
func example(flag: Bool) {
#if os(macOS)
if flag {
return
}
#else
return
#endif
}
"""
let options = FormatOptions(indent: " ", ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options)
}
func testNoIndentIfdefFixesOverIndentedEndif() {
let input = """
func example() {
#if DEBUG
return
#endif
}
"""
let output = """
func example() {
#if DEBUG
return
#endif
}
"""
let options = FormatOptions(indent: " ", ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options)
}
func testNoIndentIfdefFragmentWithUnmatchedDirectivesInsideScope() {
let input = """
{
#endif
#else
}
"""
let output = """
{
#endif
#else
}
"""
let options = FormatOptions(ifdefIndent: .noIndent, fragment: true)
testFormatting(for: input, output, rule: .indent, options: options)
}
func testNoIndentIfElse() {
let input = """
func foo() {
#if DEBUG
print("debug")
#else
print("release")
#endif
}
"""
let output = """
func foo() {
#if DEBUG
print("debug")
#else
print("release")
#endif
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options)
}
func testNoIndentIfElseIf() {
let input = """
func foo() {
#if os(iOS)
print("iOS")
#elseif os(macOS)
print("macOS")
#else
print("other")
#endif
}
"""
let output = """
func foo() {
#if os(iOS)
print("iOS")
#elseif os(macOS)
print("macOS")
#else
print("other")
#endif
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options)
}
func testNoIndentMethodChainAfterClosingBrace() {
let input = """
var body: some View {
VStack {
Text("Hello")
}
#if os(iOS)
.padding()
#endif
}
"""
let output = """
var body: some View {
VStack {
Text("Hello")
}
#if os(iOS)
.padding()
#endif
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options)
}
func testNoIndentMethodChainInlineWithModifiers() {
let input = """
var body: some View {
Text("Hello")
.font(.title)
#if os(iOS)
.padding()
#endif
}
"""
let output = """
var body: some View {
Text("Hello")
.font(.title)
#if os(iOS)
.padding()
#endif
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options)
}
func testNoIndentAlreadyCorrectLevel() {
let input = """
var body: some View {
Text("Hello")
.font(.title)
#if os(iOS)
.padding()
#endif
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, rule: .indent, options: options)
}
func testNoIndentUnderIndentedIfGetFixed() {
let input = """
func foo() {
#if DEBUG
print("debug")
#endif
}
"""
let output = """
func foo() {
#if DEBUG
print("debug")
#endif
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options)
}
func testNoIndentSwitchCases() {
let input = """
switch value {
case .a:
break
#if DEBUG
case .b:
break
#endif
}
"""
let output = """
switch value {
case .a:
break
#if DEBUG
case .b:
break
#endif
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options)
}
func testNoIndentNestedIfBlocks() {
let input = """
func foo() {
#if os(iOS)
#if DEBUG
print("iOS debug")
#endif
#endif
}
"""
let output = """
func foo() {
#if os(iOS)
#if DEBUG
print("iOS debug")
#endif
#endif
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options)
}
func testNoIndentMultipleStatements() {
let input = """
func foo() {
#if DEBUG
let x = 1
let y = 2
print(x + y)
#endif
}
"""
let output = """
func foo() {
#if DEBUG
let x = 1
let y = 2
print(x + y)
#endif
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options)
}
func testNoIndentMethodChainMultipleModifiers() {
let input = """
var body: some View {
Text("Hello")
#if os(iOS)
.font(.title)
.foregroundColor(.blue)
.padding()
#elseif os(macOS)
.font(.headline)
.padding(.all, 20)
#endif
}
"""
let output = """
var body: some View {
Text("Hello")
#if os(iOS)
.font(.title)
.foregroundColor(.blue)
.padding()
#elseif os(macOS)
.font(.headline)
.padding(.all, 20)
#endif
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options)
}
func testNoIndentMethodChainContinuesAfterEndif() {
let input = """
var body: some View {
Text("Hello")
.font(.title)
#if os(iOS)
.padding()
#endif
.background(Color.white)
}
"""
let output = """
var body: some View {
Text("Hello")
.font(.title)
#if os(iOS)
.padding()
#endif
.background(Color.white)
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options)
}
func testNoIndentFileScope() {
let input = """
#if DEBUG
let debugMode = true
#else
let debugMode = false
#endif
"""
let output = """
#if DEBUG
let debugMode = true
#else
let debugMode = false
#endif
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options)
}
func testNoIndentTypeMembers() {
let input = """
struct Foo {
#if DEBUG
var debugValue: Int
#endif
var normalValue: String
}
"""
let output = """
struct Foo {
#if DEBUG
var debugValue: Int
#endif
var normalValue: String
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options)
}
func testNoIndentWithCommentsInside() {
let input = """
func foo() {
#if DEBUG
// This is a debug comment
print("debug")
#endif
}
"""
let output = """
func foo() {
#if DEBUG
// This is a debug comment
print("debug")
#endif
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options)
}
func testNoIndentPreservesCorrectLinewrapPosition() {
let input = """
var body: some View {
Text("Hello")
#if os(iOS)
.padding()
#endif
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, rule: .indent, options: options)
}
func testNoIndentComplexNestedView() {
let input = """
var body: some View {
HStack {
List {
Text("Item")
}
.listStyle(.plain)
#if os(iOS)
.introspect(.list, on: .iOS(.v15)) { _ in }
#elseif os(macOS)
.introspect(.list, on: .macOS(.v12)) { _ in }
#endif
}
}
"""
let output = """
var body: some View {
HStack {
List {
Text("Item")
}
.listStyle(.plain)
#if os(iOS)
.introspect(.list, on: .iOS(.v15)) { _ in }
#elseif os(macOS)
.introspect(.list, on: .macOS(.v12)) { _ in }
#endif
}
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options)
}
// MARK: - noIndent advanced scenarios (swiftui-introspect patterns)
func testNoIndentFileLevelWrapperWithNestedCode() {
// Pattern: File wrapped in #if with deeply nested struct/function content
let input = """
#if !os(tvOS)
import SwiftUI
@MainActor
struct ListTests {
typealias PlatformList = UIScrollView
@Test func introspect() async throws {
let entity = try await introspection { spy in
HStack {
List {
Text("Item 1")
}
.listStyle(.plain)
}
}
}
}
#endif
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, rule: .indent, options: options, exclude: [.unusedArguments])
}
func testNoIndentFileLevelWrapperWithNestedIfdef() {
// Pattern: File-level #if with nested #if for platform-specific typealias
let input = """
#if !os(tvOS)
import SwiftUI
struct ListTests {
#if canImport(UIKit)
typealias PlatformList = UIScrollView
#elseif canImport(AppKit)
typealias PlatformList = NSTableView
#endif
func test() {
print("test")
}
}
#endif
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, rule: .indent, options: options)
}
func testNoIndentMethodChainWithCommentInsideIfdef() {
// Pattern: #if block with comment before the modifier
let input = """
var body: some View {
NavigationView {
Text("Hello")
}
.navigationViewStyle(.columns)
#if os(iOS)
// NB: this is necessary for introspection to work
.introspect(.navigationView, on: .iOS(.v15)) {
$0.preferredDisplayMode = .oneOverSecondary
}
#endif
}
"""
let output = """
var body: some View {
NavigationView {
Text("Hello")
}
.navigationViewStyle(.columns)
#if os(iOS)
// NB: this is necessary for introspection to work
.introspect(.navigationView, on: .iOS(.v15)) {
$0.preferredDisplayMode = .oneOverSecondary
}
#endif
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options)
}
func testNoIndentMethodChainWithTrailingClosureInsideIfdef() {
// Pattern: #if with modifier containing trailing closure
let input = """
var body: some View {
List {
Text("Item")
}
.listStyle(.plain)
#if os(iOS)
.introspect(.list, on: .iOS(.v15, .v16)) { tableView in
tableView.backgroundColor = .clear
tableView.separatorStyle = .none
}
#endif
}
"""
let output = """
var body: some View {
List {
Text("Item")
}
.listStyle(.plain)
#if os(iOS)
.introspect(.list, on: .iOS(.v15, .v16)) { tableView in
tableView.backgroundColor = .clear
tableView.separatorStyle = .none
}
#endif
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options)
}
func testNoIndentNestedIfdefAtLinewrapLevel() {
// Pattern: #if at linewrap level inside file-level #if wrapper
let input = """
#if !os(tvOS)
struct Tests {
func test() {
let view = HStack {
List {
Text("Item")
#if os(iOS)
.introspect(.list, on: .iOS(.v15), scope: .ancestor) { _ in }
#elseif os(macOS)
.introspect(.list, on: .macOS(.v12), scope: .ancestor) { _ in }
#endif
}
.listStyle(.inset)
}
}
}
#endif
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, rule: .indent, options: options)
}
func testNoIndentMultipleConsecutiveIfdefBlocks() {
// Pattern: Multiple #if blocks in the same method chain
let input = """
var body: some View {
Text("Hello")
.font(.title)
#if os(iOS)
.padding()
#endif
#if DEBUG
.border(Color.red)
#endif
.background(Color.white)
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, rule: .indent, options: options)
}
func testNoIndentIfdefWithMultiplePlatformBranches() {
// Pattern: Complex #if with multiple #elseif branches
let input = """
struct Tests {
func introspect() {
let view = NavigationView {
Text("Content")
}
.navigationViewStyle(.columns)
#if os(iOS)
.introspect(.navigationView, on: .iOS(.v13, .v14, .v15)) { spy in
spy.preferredDisplayMode = .oneOverSecondary
}
#elseif os(tvOS)
.introspect(.navigationView, on: .tvOS(.v13, .v14, .v15)) { spy in
// tvOS specific
}
#elseif os(macOS)
.introspect(.navigationView, on: .macOS(.v10_15, .v11, .v12)) { spy in
// macOS specific
}
#endif
}
}
"""
let output = """
struct Tests {
func introspect() {
let view = NavigationView {
Text("Content")
}
.navigationViewStyle(.columns)
#if os(iOS)
.introspect(.navigationView, on: .iOS(.v13, .v14, .v15)) { spy in
spy.preferredDisplayMode = .oneOverSecondary
}
#elseif os(tvOS)
.introspect(.navigationView, on: .tvOS(.v13, .v14, .v15)) { spy in
// tvOS specific
}
#elseif os(macOS)
.introspect(.navigationView, on: .macOS(.v10_15, .v11, .v12)) { spy in
// macOS specific
}
#endif
}
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options, exclude: [.unusedArguments])
}
func testNoIndentIfdefInsideClosureArgument() {
// Pattern: #if inside a closure that's a function argument
let input = """
func test() {
let result = try await introspection(of: UIScrollView.self) { spy1, spy2 in
HStack {
List {
Text("Item 1")
}
.listStyle(.grouped)
#if os(iOS)
.introspect(.list(style: .grouped), on: .iOS(.v16, .v17)) { spy in
spy1(spy)
}
#endif
List {
Text("Item 2")
}
.listStyle(.grouped)
}
}
}
"""
let output = """
func test() {
let result = try await introspection(of: UIScrollView.self) { spy1, spy2 in
HStack {
List {
Text("Item 1")
}
.listStyle(.grouped)
#if os(iOS)
.introspect(.list(style: .grouped), on: .iOS(.v16, .v17)) { spy in
spy1(spy)
}
#endif
List {
Text("Item 2")
}
.listStyle(.grouped)
}
}
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options, exclude: [.unusedArguments])
}
func testNoIndentIfdefAfterClosingBraceInChain() {
// Pattern: #if immediately after a closing brace in method chain
let input = """
var body: some View {
VStack {
Text("Hello")
}
#if os(iOS)
.padding()
#endif
}
"""
let output = """
var body: some View {
VStack {
Text("Hello")
}
#if os(iOS)
.padding()
#endif
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options)
}
func testNoIndentDeepNestedIfdefInFileLevelWrapper() {
// Pattern: Deeply nested #if inside file-level #if wrapper
let input = """
#if !os(macOS)
import SwiftUI
@MainActor
struct SearchFieldTests {
typealias PlatformSearchField = UISearchBar
@Test func introspect() async throws {
try await introspection(of: PlatformSearchField.self) { spy in
TabView {
NavigationView {
Text("Customized")
.searchable(text: .constant(""))
#if os(iOS)
.introspect(.searchField, on: .iOS(.v15, .v16)) { searchBar in
spy(searchBar)
}
#elseif os(tvOS)
.introspect(.searchField, on: .tvOS(.v15, .v16)) { searchBar in
spy(searchBar)
}
#endif
}
.navigationViewStyle(.stack)
}
}
}
}
#endif
"""
let output = """
#if !os(macOS)
import SwiftUI
@MainActor
struct SearchFieldTests {
typealias PlatformSearchField = UISearchBar
@Test func introspect() async throws {
try await introspection(of: PlatformSearchField.self) { spy in
TabView {
NavigationView {
Text("Customized")
.searchable(text: .constant(""))
#if os(iOS)
.introspect(.searchField, on: .iOS(.v15, .v16)) { searchBar in
spy(searchBar)
}
#elseif os(tvOS)
.introspect(.searchField, on: .tvOS(.v15, .v16)) { searchBar in
spy(searchBar)
}
#endif
}
.navigationViewStyle(.stack)
}
}
}
}
#endif
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options, exclude: [.unusedArguments])
}
func testNoIndentIfdefPreservesCorrectEndifLevel() {
// Pattern: Ensure #endif stays at same level as #if when at linewrap position
let input = """
#if !os(tvOS)
struct ListTests {
func test() {
let view = HStack {
List {
Text("Item 1")
#if os(iOS) || os(visionOS)
.introspect(.list, on: .iOS(.v14, .v15)) { _ in }
.introspect(.list, on: .iOS(.v16, .v17)) { _ in }
#elseif os(macOS)
.introspect(.list, on: .macOS(.v11, .v12)) { _ in }
#endif
}
.listStyle(.inset)
}
}
}
#endif
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, rule: .indent, options: options, exclude: [.unusedArguments])
}
func testNoIndentIfdefWithNestedTypealiasAndFunctions() {
// Pattern: Struct with #if for typealias followed by functions
let input = """
#if !os(tvOS)
struct ListWithInsetStyleTests {
#if canImport(UIKit)
typealias PlatformListWithInsetStyle = UIScrollView
#elseif canImport(AppKit)
typealias PlatformListWithInsetStyle = NSTableView
#endif
@Test func introspect() async throws {
let (entity1, entity2) = try await introspection(of: PlatformListWithInsetStyle.self) { spy1, spy2 in
HStack {
List {
Text("Item 1")
}
.listStyle(.inset)
#if os(iOS) || os(visionOS)
.introspect(.list(style: .inset), on: .iOS(.v14, .v15), customize: spy1)
#elseif os(macOS)
.introspect(.list(style: .inset), on: .macOS(.v11, .v12), customize: spy1)
#endif
}
}
}
}
#endif
"""
let output = """
#if !os(tvOS)
struct ListWithInsetStyleTests {
#if canImport(UIKit)
typealias PlatformListWithInsetStyle = UIScrollView
#elseif canImport(AppKit)
typealias PlatformListWithInsetStyle = NSTableView
#endif
@Test func introspect() async throws {
let (entity1, entity2) = try await introspection(of: PlatformListWithInsetStyle.self) { spy1, spy2 in
HStack {
List {
Text("Item 1")
}
.listStyle(.inset)
#if os(iOS) || os(visionOS)
.introspect(.list(style: .inset), on: .iOS(.v14, .v15), customize: spy1)
#elseif os(macOS)
.introspect(.list(style: .inset), on: .macOS(.v11, .v12), customize: spy1)
#endif
}
}
}
}
#endif
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, output, rule: .indent, options: options, exclude: [.unusedArguments])
}
func testNoIndentIfdefInsideImmediatelyInvokedClosure() {
// Pattern: #if inside a closure that's immediately invoked for conditional values
let input = """
struct ContentView: View {
var body: some View {
Text("Hello")
.toolbar {
ToolbarItem(placement: {
#if os(iOS)
.cancellationAction
#else
.automatic
#endif
}()) {
Button("Cancel") {}
}
}
}
}
"""
let options = FormatOptions(ifdefIndent: .noIndent)
testFormatting(for: input, rule: .indent, options: options)
}
func testIfDefPostfixMemberSyntaxPreserveKeepsAlignment() {
let input = """
struct Example: View {
@@ -6542,27 +5605,6 @@ final class IndentTests: XCTestCase {
testFormatting(for: input, output, rule: .indent, exclude: [.wrapMultilineStatementBraces])
}
func testIndentIfExpressionAssignmentInsideNoIndentIfdef() {
let input = """
#if os(iOS)
func makeRecipients(subtitle: String) {
let recipients: [INPerson] =
if subtitle.isEmpty {
[]
} else {
[
subtitle,
]
}
_ = recipients
}
#endif
"""
let options = FormatOptions(indent: " ", ifdefIndent: .noIndent)
testFormatting(for: input, rule: .indent, options: options)
}
func testIndentIfExpressionAssignmentOnSameLine() {
let input = """
let foo = if let bar {