Skip to content

Commit 23068d4

Browse files
committed
Merge pull request #554 from allevato/poundif-after-paren
Fix postfix-`#if` formatting when they come after a closing parenthesis.
1 parent 6a8d51a commit 23068d4

File tree

2 files changed

+116
-58
lines changed

2 files changed

+116
-58
lines changed

Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift

Lines changed: 34 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -988,14 +988,22 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
988988

989989
override func visit(_ node: MemberAccessExprSyntax) -> SyntaxVisitorContinueKind {
990990
preVisitInsertingContextualBreaks(node)
991-
992991
return .visitChildren
993992
}
994993

995994
override func visitPost(_ node: MemberAccessExprSyntax) {
996995
clearContextualBreakState(node)
997996
}
998997

998+
override func visit(_ node: PostfixIfConfigExprSyntax) -> SyntaxVisitorContinueKind {
999+
preVisitInsertingContextualBreaks(node)
1000+
return .visitChildren
1001+
}
1002+
1003+
override func visitPost(_ node: PostfixIfConfigExprSyntax) {
1004+
clearContextualBreakState(node)
1005+
}
1006+
9991007
override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind {
10001008
preVisitInsertingContextualBreaks(node)
10011009

@@ -1456,29 +1464,10 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
14561464
before(tokenToOpenWith.nextToken(viewMode: .all), tokens: .break(breakKindClose, newlines: .soft), .close)
14571465
}
14581466

1459-
if isNestedInPostfixIfConfig(node: Syntax(node)) {
1460-
let breakToken: Token
1461-
let currentIfConfigDecl = node.parent?.parent?.as(IfConfigDeclSyntax.self)
1462-
1463-
if let currentIfConfigDecl = currentIfConfigDecl,
1464-
let tokenBeforeCurrentIfConfigDecl = currentIfConfigDecl.previousToken(viewMode: .sourceAccurate),
1465-
isNestedInIfConfig(node: Syntax(tokenBeforeCurrentIfConfigDecl)) ||
1466-
tokenBeforeCurrentIfConfigDecl.text == "}" {
1467-
breakToken = .break(.reset)
1468-
} else {
1469-
breakToken = .break
1470-
before(currentIfConfigDecl?.poundEndif, tokens: [.break])
1471-
}
1472-
1467+
if !isNestedInPostfixIfConfig(node: Syntax(node)), let condition = node.condition {
14731468
before(
1474-
node.firstToken(viewMode: .sourceAccurate),
1475-
tokens: [
1476-
.printerControl(kind: .enableBreaking),
1477-
breakToken,
1478-
]
1479-
)
1480-
} else if let condition = node.condition {
1481-
before(condition.firstToken(viewMode: .sourceAccurate), tokens: .printerControl(kind: .disableBreaking(allowDiscretionary: true)))
1469+
condition.firstToken(viewMode: .sourceAccurate),
1470+
tokens: .printerControl(kind: .disableBreaking(allowDiscretionary: true)))
14821471
after(
14831472
condition.lastToken(viewMode: .sourceAccurate),
14841473
tokens: .printerControl(kind: .enableBreaking), .break(.reset, size: 0))
@@ -3287,7 +3276,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
32873276
private func mustBreakBeforeClosingDelimiter<T: ExprSyntaxProtocol>(
32883277
of expr: T, argumentListPath: KeyPath<T, TupleExprElementListSyntax>
32893278
) -> Bool {
3290-
guard let parent = expr.parent, parent.is(MemberAccessExprSyntax.self) else { return false }
3279+
guard
3280+
let parent = expr.parent,
3281+
parent.is(MemberAccessExprSyntax.self) || parent.is(PostfixIfConfigExprSyntax.self)
3282+
else { return false }
3283+
32913284
let argumentList = expr[keyPath: argumentListPath]
32923285

32933286
// When there's a single compact argument, there is no extra indentation for the argument and
@@ -3722,6 +3715,23 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
37223715
after(expr.lastToken(viewMode: .sourceAccurate), tokens: .contextualBreakingEnd)
37233716
}
37243717
return (hasCompoundExpression, true)
3718+
} else if let postfixIfExpr = expr.as(PostfixIfConfigExprSyntax.self),
3719+
let base = postfixIfExpr.base
3720+
{
3721+
// For postfix-if expressions with bases (i.e., they aren't the first `#if` nested inside
3722+
// another `#if`), add contextual breaks before the top-level clauses (and the terminating
3723+
// `#endif`) so that they nest or line-up properly based on the preceding node. We don't do
3724+
// this for initial nested `#if`s because they will already get open/close breaks to control
3725+
// their indentation from their parent clause.
3726+
before(postfixIfExpr.firstToken(viewMode: .sourceAccurate), tokens: .contextualBreakingStart)
3727+
after(postfixIfExpr.lastToken(viewMode: .sourceAccurate), tokens: .contextualBreakingEnd)
3728+
3729+
for clause in postfixIfExpr.config.clauses {
3730+
before(clause.poundKeyword, tokens: .break(.contextual, size: 0))
3731+
}
3732+
before(postfixIfExpr.config.poundEndif, tokens: .break(.contextual, size: 0))
3733+
3734+
return insertContextualBreaks(base, isTopLevel: false)
37253735
} else if let callingExpr = expr.asProtocol(CallingExprSyntaxProtocol.self) {
37263736
let calledExpression = callingExpr.calledExpression
37273737
let (hasCompoundExpression, hasMemberAccess) =
@@ -3788,20 +3798,6 @@ private func isNestedInPostfixIfConfig(node: Syntax) -> Bool {
37883798
return false
37893799
}
37903800

3791-
private func isNestedInIfConfig(node: Syntax) -> Bool {
3792-
var this: Syntax? = node
3793-
3794-
while this?.parent != nil {
3795-
if this?.is(IfConfigClauseSyntax.self) == true {
3796-
return true
3797-
}
3798-
3799-
this = this?.parent
3800-
}
3801-
3802-
return false
3803-
}
3804-
38053801
extension Syntax {
38063802
/// Creates a pretty-printable token stream for the provided Syntax node.
38073803
func makeTokenStream(configuration: Configuration, operatorTable: OperatorTable) -> [Token] {

Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift

Lines changed: 82 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -163,25 +163,25 @@ final class IfConfigTests: PrettyPrintTestCase {
163163

164164
func testInvalidDiscretionaryLineBreaksRemoved() {
165165
let input =
166-
"""
167-
#if (canImport(SwiftUI) &&
168-
!(os(iOS) &&
169-
arch(arm)) &&
170-
((canImport(AppKit) ||
171-
canImport(UIKit)) && !os(watchOS)))
172-
conditionalFunc(foo, bar, baz)
173-
#endif
174-
"""
175-
176-
let expected =
177-
"""
178-
#if (canImport(SwiftUI) && !(os(iOS) && arch(arm)) && ((canImport(AppKit) || canImport(UIKit)) && !os(watchOS)))
179-
conditionalFunc(foo, bar, baz)
180-
#endif
181-
182-
"""
183-
184-
assertPrettyPrintEqual(input: input, expected: expected, linelength: 40)
166+
"""
167+
#if (canImport(SwiftUI) &&
168+
!(os(iOS) &&
169+
arch(arm)) &&
170+
((canImport(AppKit) ||
171+
canImport(UIKit)) && !os(watchOS)))
172+
conditionalFunc(foo, bar, baz)
173+
#endif
174+
"""
175+
176+
let expected =
177+
"""
178+
#if (canImport(SwiftUI) && !(os(iOS) && arch(arm)) && ((canImport(AppKit) || canImport(UIKit)) && !os(watchOS)))
179+
conditionalFunc(foo, bar, baz)
180+
#endif
181+
182+
"""
183+
184+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 40)
185185
}
186186

187187
func testValidDiscretionaryLineBreaksRetained() {
@@ -299,6 +299,8 @@ final class IfConfigTests: PrettyPrintTestCase {
299299
#if os(iOS) || os(watchOS)
300300
#if os(iOS)
301301
.iOSModifier()
302+
#elseif os(tvOS)
303+
.tvOSModifier()
302304
#else
303305
.watchOSModifier()
304306
#endif
@@ -314,6 +316,8 @@ final class IfConfigTests: PrettyPrintTestCase {
314316
#if os(iOS) || os(watchOS)
315317
#if os(iOS)
316318
.iOSModifier()
319+
#elseif os(tvOS)
320+
.tvOSModifier()
317321
#else
318322
.watchOSModifier()
319323
#endif
@@ -326,7 +330,6 @@ final class IfConfigTests: PrettyPrintTestCase {
326330
assertPrettyPrintEqual(input: input, expected: expected, linelength: 45)
327331
}
328332

329-
330333
func testPostfixPoundIfAfterVariables() {
331334
let input =
332335
"""
@@ -398,6 +401,7 @@ final class IfConfigTests: PrettyPrintTestCase {
398401
.padding([.vertical])
399402
#if os(iOS)
400403
.iOSSpecificModifier()
404+
.anotherIOSSpecificModifier()
401405
#endif
402406
.commonModifier()
403407
"""
@@ -408,6 +412,7 @@ final class IfConfigTests: PrettyPrintTestCase {
408412
.padding([.vertical])
409413
#if os(iOS)
410414
.iOSSpecificModifier()
415+
.anotherIOSSpecificModifier()
411416
#endif
412417
.commonModifier()
413418
@@ -454,4 +459,61 @@ final class IfConfigTests: PrettyPrintTestCase {
454459

455460
assertPrettyPrintEqual(input: input, expected: expected, linelength: 45)
456461
}
462+
463+
func testPostfixPoundIfNotIndentedIfClosingParenOnOwnLine() {
464+
let input =
465+
"""
466+
SomeFunction(
467+
foo,
468+
bar
469+
)
470+
#if os(iOS)
471+
.iOSSpecificModifier()
472+
#endif
473+
.commonModifier()
474+
"""
475+
476+
let expected =
477+
"""
478+
SomeFunction(
479+
foo,
480+
bar
481+
)
482+
#if os(iOS)
483+
.iOSSpecificModifier()
484+
#endif
485+
.commonModifier()
486+
487+
"""
488+
489+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 45)
490+
}
491+
492+
func testPostfixPoundIfForcesPrecedingClosingParenOntoNewLine() {
493+
let input =
494+
"""
495+
SomeFunction(
496+
foo,
497+
bar)
498+
#if os(iOS)
499+
.iOSSpecificModifier()
500+
#endif
501+
.commonModifier()
502+
"""
503+
504+
let expected =
505+
"""
506+
SomeFunction(
507+
foo,
508+
bar
509+
)
510+
#if os(iOS)
511+
.iOSSpecificModifier()
512+
#endif
513+
.commonModifier()
514+
515+
"""
516+
517+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 45)
518+
}
457519
}

0 commit comments

Comments
 (0)