diff --git a/Sources/SwiftFormatConfiguration/Configuration.swift b/Sources/SwiftFormatConfiguration/Configuration.swift index 6429586d8..2a908a9a2 100644 --- a/Sources/SwiftFormatConfiguration/Configuration.swift +++ b/Sources/SwiftFormatConfiguration/Configuration.swift @@ -26,9 +26,13 @@ public struct Configuration: Codable, Equatable { case tabWidth case indentation case respectsExistingLineBreaks + case lineBreakBeforeControlFlowBodies case lineBreakBeforeControlFlowKeywords case lineBreakBeforeEachArgument + case lineBreakBeforeFuncBodies case lineBreakBeforeEachGenericRequirement + case lineBreakBeforeSwitchCaseOrDefaultBodies + case lineBreakBeforeTypeBodies case prioritizeKeepingFunctionOutputTogether case indentConditionalCompilationBlocks case lineBreakAroundMultilineExpressionChainComponents @@ -73,6 +77,14 @@ public struct Configuration: Codable, Equatable { /// MARK: Rule-specific configuration + /// Determines the line-breaking behavior for bodies of control flow keywords, like `if` and + /// `for`. + /// + /// If true, a line break will be added after the opening brace of these bodies, forcing them + /// onto their own lines. If false (the default), these bodies will be laid out on the same line + /// as the keyword, with line breaks only being added when the line length would be exceeded. + public var lineBreakBeforeControlFlowBodies = false + /// Determines the line-breaking behavior for control flow keywords that follow a closing brace, /// like `else` and `catch`. /// @@ -97,6 +109,32 @@ public struct Configuration: Codable, Equatable { /// horizontally first, with line breaks only being fired when the line length would be exceeded. public var lineBreakBeforeEachGenericRequirement = false + /// Determins the line-breaking behavior for the bodies of functions declared with `func`, as + /// well as "`func`-like" declarations: initializers, deinitializers, and subscripts. + /// + /// If true, a line break will be added after the opening brace of function bodies, forcing the + /// body to be on a separate line from the function. If false (the default), these bodies will be + /// laid out on the same line as the declaration, with line breaks only being added when the line + /// length would be exceeded. + public var lineBreakBeforeFuncBodies = false + + /// Determines the line-breaking behavior for the bodies of `case` and `default` items within + /// a `switch` statement. + /// + /// If true, a line break will be added after the colon following `case` or `default`, forcing the + /// body to be on a separate line from the `case` or `default`. If false (the default), these bodies + /// will be laid out on the same line as the `case` or `default`, with line breaks only being added + /// when the line length would be exceeded. + public var lineBreakBeforeSwitchCaseOrDefaultBodies = false + + /// Determines the line-breaking behavior for the bodies of types: `class`, `enum`, `extension`, + /// `protocol`, and `struct`. + /// + /// If true, a line break will be added after the opening brace for all non-empty types. If false + /// (the default), these bodies will be laid out on the same line as the type declaration, with + /// line breaks only being added when the line length would be exceeded. + public var lineBreakBeforeTypeBodies = false + /// Determines if function-like declaration outputs should be prioritized to be together with the /// function signature right (closing) parenthesis. /// @@ -181,12 +219,20 @@ public struct Configuration: Codable, Equatable { = try container.decodeIfPresent(Indent.self, forKey: .indentation) ?? .spaces(2) self.respectsExistingLineBreaks = try container.decodeIfPresent(Bool.self, forKey: .respectsExistingLineBreaks) ?? true + self.lineBreakBeforeControlFlowBodies + = try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeControlFlowBodies) ?? false self.lineBreakBeforeControlFlowKeywords = try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeControlFlowKeywords) ?? false self.lineBreakBeforeEachArgument = try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeEachArgument) ?? false self.lineBreakBeforeEachGenericRequirement = try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeEachGenericRequirement) ?? false + self.lineBreakBeforeFuncBodies + = try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeFuncBodies) ?? false + self.lineBreakBeforeSwitchCaseOrDefaultBodies + = try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeSwitchCaseOrDefaultBodies) ?? false + self.lineBreakBeforeTypeBodies + = try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeTypeBodies) ?? false self.prioritizeKeepingFunctionOutputTogether = try container.decodeIfPresent(Bool.self, forKey: .prioritizeKeepingFunctionOutputTogether) ?? false self.indentConditionalCompilationBlocks @@ -218,9 +264,13 @@ public struct Configuration: Codable, Equatable { try container.encode(tabWidth, forKey: .tabWidth) try container.encode(indentation, forKey: .indentation) try container.encode(respectsExistingLineBreaks, forKey: .respectsExistingLineBreaks) + try container.encode(lineBreakBeforeControlFlowBodies, forKey: .lineBreakBeforeControlFlowBodies) try container.encode(lineBreakBeforeControlFlowKeywords, forKey: .lineBreakBeforeControlFlowKeywords) try container.encode(lineBreakBeforeEachArgument, forKey: .lineBreakBeforeEachArgument) try container.encode(lineBreakBeforeEachGenericRequirement, forKey: .lineBreakBeforeEachGenericRequirement) + try container.encode(lineBreakBeforeFuncBodies, forKey: .lineBreakBeforeFuncBodies) + try container.encode(lineBreakBeforeSwitchCaseOrDefaultBodies, forKey: .lineBreakBeforeSwitchCaseOrDefaultBodies) + try container.encode(lineBreakBeforeTypeBodies, forKey: .lineBreakBeforeTypeBodies) try container.encode(prioritizeKeepingFunctionOutputTogether, forKey: .prioritizeKeepingFunctionOutputTogether) try container.encode(indentConditionalCompilationBlocks, forKey: .indentConditionalCompilationBlocks) try container.encode( diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index c346e6d39..caa673d7a 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -246,7 +246,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(firstTokenAfterAttributes, tokens: .open) after(typeKeyword, tokens: .break) - arrangeBracesAndContents(of: members, contentsKeyPath: \.members) + arrangeBracesAndContents( + of: members, + contentsKeyPath: \.members, + openBraceNewlineBehavior: !areBracesCompletelyEmpty(members, contentsKeyPath: \.members) + && config.lineBreakBeforeTypeBodies + ? .hard : .elective + ) if let genericWhereClause = genericWhereClause { before(genericWhereClause.firstToken, tokens: .break(.same), .open) @@ -411,12 +417,18 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { attributes: AttributeListSyntax?, genericWhereClause: GenericWhereClauseSyntax?, body: Node?, - bodyContentsKeyPath: KeyPath? + bodyContentsKeyPath: KeyPath ) where BodyContents.Element: SyntaxProtocol { before(node.firstToken, tokens: .open) arrangeAttributeList(attributes) - arrangeBracesAndContents(of: body, contentsKeyPath: bodyContentsKeyPath) + arrangeBracesAndContents( + of: body, + contentsKeyPath: bodyContentsKeyPath, + openBraceNewlineBehavior: config.lineBreakBeforeFuncBodies + && body.map { !areBracesCompletelyEmpty($0, contentsKeyPath: bodyContentsKeyPath) } ?? false + ? .hard : .elective + ) if let genericWhereClause = genericWhereClause { before(genericWhereClause.firstToken, tokens: .break(.same), .open) @@ -470,7 +482,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after(condition.lastToken, tokens: .break(.close(mustBreak: false), size: 0)) } - arrangeBracesAndContents(of: node.body, contentsKeyPath: \.statements) + arrangeBracesAndContents( + of: node.body, + contentsKeyPath: \.statements, + openBraceNewlineBehavior: config.lineBreakBeforeControlFlowBodies ? .hard : .elective + ) if let elseKeyword = node.elseKeyword { // Add a token before the else keyword. Breaking before `else` is explicitly allowed when @@ -492,7 +508,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } } - arrangeBracesAndContents(of: node.elseBody?.as(CodeBlockSyntax.self), contentsKeyPath: \.statements) + arrangeBracesAndContents( + of: node.elseBody?.as(CodeBlockSyntax.self), + contentsKeyPath: \.statements, + openBraceNewlineBehavior: config.lineBreakBeforeControlFlowBodies ? .hard : .elective + ) return .visitChildren } @@ -531,7 +551,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after(typeAnnotation.lastToken, tokens: .break(.close(mustBreak: false), size: 0)) } - arrangeBracesAndContents(of: node.body, contentsKeyPath: \.statements) + arrangeBracesAndContents( + of: node.body, + contentsKeyPath: \.statements, + openBraceNewlineBehavior: config.lineBreakBeforeControlFlowBodies ? .hard : .elective + ) return .visitChildren } @@ -553,14 +577,22 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after(condition.lastToken, tokens: .break(.close(mustBreak: false), size: 0)) } - arrangeBracesAndContents(of: node.body, contentsKeyPath: \.statements) + arrangeBracesAndContents( + of: node.body, + contentsKeyPath: \.statements, + openBraceNewlineBehavior: config.lineBreakBeforeControlFlowBodies ? .hard : .elective + ) return .visitChildren } override func visit(_ node: RepeatWhileStmtSyntax) -> SyntaxVisitorContinueKind { after(node.labelColon, tokens: .space) - arrangeBracesAndContents(of: node.body, contentsKeyPath: \.statements) + arrangeBracesAndContents( + of: node.body, + contentsKeyPath: \.statements, + openBraceNewlineBehavior: config.lineBreakBeforeControlFlowBodies ? .hard : .elective + ) if config.lineBreakBeforeControlFlowKeywords { before(node.whileKeyword, tokens: .break(.same), .open) @@ -580,7 +612,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: DoStmtSyntax) -> SyntaxVisitorContinueKind { after(node.labelColon, tokens: .space) - arrangeBracesAndContents(of: node.body, contentsKeyPath: \.statements) + arrangeBracesAndContents( + of: node.body, + contentsKeyPath: \.statements, + openBraceNewlineBehavior: config.lineBreakBeforeControlFlowBodies ? .hard : .elective + ) return .visitChildren } @@ -603,7 +639,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } } - arrangeBracesAndContents(of: node.body, contentsKeyPath: \.statements) + arrangeBracesAndContents( + of: node.body, + contentsKeyPath: \.statements, + openBraceNewlineBehavior: config.lineBreakBeforeControlFlowBodies ? .hard : .elective + ) return .visitChildren } @@ -659,7 +699,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(node.firstToken, tokens: openBreak) after(node.unknownAttr?.lastToken, tokens: .space) - after(node.label.lastToken, tokens: .break(.reset, size: 0), .break(.open), .open) + after( + node.label.lastToken, + tokens: .break(.reset, size: 0), + .break(.open, newlines: config.lineBreakBeforeSwitchCaseOrDefaultBodies ? .hard : .elective), + .open + ) // If switch/case labels were configured to be indented, insert an extra `close` break after the // case body to match the `open` break above