From 85c9757a392332ee0b16162ca04ae68e433f9a88 Mon Sep 17 00:00:00 2001 From: David Brunow <> Date: Wed, 20 Jul 2022 09:29:06 -0500 Subject: [PATCH 1/2] Add fix and tests for postfix pound ifs --- .../TokenStreamCreator.swift | 32 +++- .../IfConfigTests.swift | 160 ++++++++++++++++++ 2 files changed, 190 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 23f53a67d..d85ac660c 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1308,7 +1308,15 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(tokenToOpenWith.nextToken, tokens: .break(breakKindClose, newlines: .soft), .close) } - if let condition = node.condition { + if Syntax(node).isPostfixIfConfig { + before( + node.firstToken, + tokens: [ + .printerControl(kind: .enableBreaking), + .break(.reset), + ] + ) + } else if let condition = node.condition { before(condition.firstToken, tokens: .printerControl(kind: .disableBreaking)) after( condition.lastToken, @@ -3460,7 +3468,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let calledMemberAccessExpr = calledExpression.as(MemberAccessExprSyntax.self) { if calledMemberAccessExpr.base != nil { - before(calledMemberAccessExpr.dot, tokens: [.break(.contextual, size: 0)]) + if Syntax(calledMemberAccessExpr).isPostfixIfConfig { + before(calledMemberAccessExpr.dot, tokens: [.break(.same, size: 0)]) + } else { + before(calledMemberAccessExpr.dot, tokens: [.break(.contextual, size: 0)]) + } } before(calledMemberAccessExpr.dot, tokens: beforeTokens) after(expr.lastToken, tokens: afterTokens) @@ -3485,6 +3497,22 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } } +extension Syntax { + var isPostfixIfConfig: Bool { + var this: Syntax? = self + + while this?.parent != nil { + if this?.parent?.is(PostfixIfConfigExprSyntax.self) == true { + return true + } + + this = this?.parent + } + + return false + } +} + extension Syntax { /// Creates a pretty-printable token stream for the provided Syntax node. func makeTokenStream(configuration: Configuration, operatorContext: OperatorContext) -> [Token] { diff --git a/Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift b/Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift index 71fab97bd..9547816f0 100644 --- a/Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift @@ -230,4 +230,164 @@ final class IfConfigTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 40) } + + func testPostfixPoundIfAfterParentheses() { + let input = + """ + VStack { + Text("something") + #if os(iOS) + .iOSSpecificModifier() + #endif + .commonModifier() + } + """ + + let expected = + """ + VStack { + Text("something") + #if os(iOS) + .iOSSpecificModifier() + #endif + .commonModifier() + } + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) + } + + func testPostfixPoundIfAfterParenthesesMultipleMembers() { + let input = + """ + VStack { + Text("something") + #if os(iOS) + .iOSSpecificModifier() + .anotherModifier() + .anotherAnotherModifier() + #endif + .commonModifier() + .anotherCommonModifier() + } + """ + + let expected = + """ + VStack { + Text("something") + #if os(iOS) + .iOSSpecificModifier() + .anotherModifier() + .anotherAnotherModifier() + #endif + .commonModifier() + .anotherCommonModifier() + } + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) + } + + func testPostfixPoundIfNested() { + let input = + """ + VStack { + Text("something") + #if os(iOS) || os(watchOS) + #if os(iOS) + .iOSModifier() + #else + .watchOSModifier() + #endif + .iOSAndWatchOSModifier() + #endif + } + """ + + let expected = + """ + VStack { + Text("something") + #if os(iOS) || os(watchOS) + #if os(iOS) + .iOSModifier() + #else + .watchOSModifier() + #endif + .iOSAndWatchOSModifier() + #endif + } + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) + } + + + func testPostfixPoundIfAfterVariables() { + let input = + """ + VStack { + textView + #if os(iOS) + .iOSSpecificModifier() + #endif + .commonModifier() + } + """ + + let expected = + """ + VStack { + textView + #if os(iOS) + .iOSSpecificModifier() + #endif + .commonModifier() + } + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) + } + + func testPostfixPoundIfAfterClosingBrace() { + let input = + """ + HStack { + Toggle(isOn: binding) { + Text("Some text") + } + #if !os(tvOS) + .toggleStyle(SwitchToggleStyle(tint: Color.blue)) + #endif + .accessibilityValue( + binding.wrappedValue == true ? "On" : "Off" + ) + } + """ + + let expected = + """ + HStack { + Toggle(isOn: binding) { + Text("Some text") + } + #if !os(tvOS) + .toggleStyle( + SwitchToggleStyle(tint: Color.blue)) + #endif + .accessibilityValue( + binding.wrappedValue == true + ? "On" : "Off" + ) + } + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) + } } From 08e194351c8dd42d8b17827230192ff87f45806a Mon Sep 17 00:00:00 2001 From: David Brunow <> Date: Mon, 22 Aug 2022 09:17:33 -0500 Subject: [PATCH 2/2] Use private function instead of extension on Syntax --- .../SwiftFormatPrettyPrint/TokenStreamCreator.swift | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index d85ac660c..d989e8b40 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1308,7 +1308,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(tokenToOpenWith.nextToken, tokens: .break(breakKindClose, newlines: .soft), .close) } - if Syntax(node).isPostfixIfConfig { + if isNestedInPostfixIfConfig(node: Syntax(node)) { before( node.firstToken, tokens: [ @@ -3468,7 +3468,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let calledMemberAccessExpr = calledExpression.as(MemberAccessExprSyntax.self) { if calledMemberAccessExpr.base != nil { - if Syntax(calledMemberAccessExpr).isPostfixIfConfig { + if isNestedInPostfixIfConfig(node: Syntax(calledMemberAccessExpr)) { before(calledMemberAccessExpr.dot, tokens: [.break(.same, size: 0)]) } else { before(calledMemberAccessExpr.dot, tokens: [.break(.contextual, size: 0)]) @@ -3497,9 +3497,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } } -extension Syntax { - var isPostfixIfConfig: Bool { - var this: Syntax? = self +private func isNestedInPostfixIfConfig(node: Syntax) -> Bool { + var this: Syntax? = node while this?.parent != nil { if this?.parent?.is(PostfixIfConfigExprSyntax.self) == true { @@ -3510,7 +3509,6 @@ extension Syntax { } return false - } } extension Syntax {