From dc93e46b76dfce6f1cab58de7f58fc436ff71049 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Thu, 29 Jan 2026 06:12:15 -0800 Subject: [PATCH] Revert recent `#if` indentation changes related to method chains (#2345) --- Sources/Rules/Indent.swift | 125 +---- Tests/Rules/IndentTests.swift | 966 +--------------------------------- 2 files changed, 16 insertions(+), 1075 deletions(-) diff --git a/Sources/Rules/Indent.swift b/Sources/Rules/Indent.swift index ca38f7e9..72778961 100644 --- a/Sources/Rules/Indent.swift +++ b/Sources/Rules/Indent.swift @@ -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 diff --git a/Tests/Rules/IndentTests.swift b/Tests/Rules/IndentTests.swift index 2c40db2c..b10f487c 100644 --- a/Tests/Rules/IndentTests.swift +++ b/Tests/Rules/IndentTests.swift @@ -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 {