Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow more required line breaks #240

Draft
wants to merge 4 commits into
base: swift-5.3-branch
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions Sources/SwiftFormatConfiguration/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`.
///
Expand All @@ -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.
///
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
67 changes: 56 additions & 11 deletions Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -411,12 +417,18 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
attributes: AttributeListSyntax?,
genericWhereClause: GenericWhereClauseSyntax?,
body: Node?,
bodyContentsKeyPath: KeyPath<Node, BodyContents>?
bodyContentsKeyPath: KeyPath<Node, BodyContents>
) 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)
Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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)
Expand All @@ -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
}

Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand Down