Skip to content
Merged
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
25 changes: 25 additions & 0 deletions Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,11 @@ private final class TokenStreamCreator: SyntaxVisitor {
}

func visit(_ node: MemberDeclListSyntax) -> SyntaxVisitorContinueKind {
// This is the same as `insertTokens(_:betweenElementsOf:)`, but testing for an extra condition
// on the left-hand element.
for item in node.dropLast() where shouldInsertNewline(basedOn: item.semicolon) {
after(item.lastToken, tokens: .newline)
}
return .visitChildren
}

Expand Down Expand Up @@ -962,6 +967,11 @@ private final class TokenStreamCreator: SyntaxVisitor {
}

func visit(_ node: CodeBlockItemListSyntax) -> SyntaxVisitorContinueKind {
// This is the same as `insertTokens(_:betweenElementsOf:)`, but testing for an extra condition
// on the left-hand element.
for item in node.dropLast() where shouldInsertNewline(basedOn: item.semicolon) {
after(item.lastToken, tokens: .newline)
}
return .visitChildren
}

Expand Down Expand Up @@ -2183,6 +2193,21 @@ private final class TokenStreamCreator: SyntaxVisitor {
return expression is ArrayExprSyntax || expression is DictionaryExprSyntax
|| expression is ClosureExprSyntax
}

/// Returns a value indicating whether a statement or member declaration should have a newline
/// inserted after it, based on the presence of a semicolon and whether or not the formatter is
/// respecting existing newlines.
private func shouldInsertNewline(basedOn semicolon: TokenSyntax?) -> Bool {
if config.respectsExistingLineBreaks {
// If we are respecting existing newlines, then we only want to force a newline at the end of
// statements and declarations that don't have a semicolon (i.e., where they are required).
return semicolon == nil
} else {
// If we are not respecting existing newlines, then we always force a newline (this forces
// even semicolon-delimited statements onto separate lines).
return true
}
}
}

extension Syntax {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import SwiftFormatConfiguration

/// Sanity checks and regression tests for the `respectsExistingLineBreaks` configuration setting
/// in both true and false states.
class RespectsExistingLineBreaksTests: PrettyPrintTestCase {
func testExpressions() {
let input =
"""
a = b + c
+ d
+ e + f + g
+ h + i
"""

let expectedRespecting =
"""
a = b + c
+ d
+ e + f
+ g
+ h + i

"""

assertPrettyPrintEqual(
input: input, expected: expectedRespecting, linelength: 12,
configuration: configuration(respectingExistingLineBreaks: true))

let expectedNotRespecting =
"""
a = b + c + d + e + f + g
+ h + i

"""

assertPrettyPrintEqual(
input: input, expected: expectedNotRespecting, linelength: 25,
configuration: configuration(respectingExistingLineBreaks: false))
}

func testCodeBlocksAndMemberDecls() {
let input =
"""
import Module
import Other

struct FitsOnOneLine {
var x: Int
}

struct Foo {
var storedProperty: Int
= 100
var readOnlyProperty: Int {
return
200
}
var readWriteProperty: Int {
get {
return
somethingElse
}
set {
somethingElse
= newValue
}
}

func oneLiner() -> Int {
return 500
}
func someFunction(
x: Int
) {
foo(x)
bar(x)
}
}
"""

// No changes expected when respecting existing newlines.
assertPrettyPrintEqual(
input: input, expected: input + "\n", linelength: 80,
configuration: configuration(respectingExistingLineBreaks: true))

let expectedNotRespecting =
"""
import Module
import Other

struct FitsOnOneLine { var x: Int }

struct Foo {
var storedProperty: Int = 100
var readOnlyProperty: Int { return 200 }
var readWriteProperty: Int {
get { return somethingElse }
set { somethingElse = newValue }
}

func oneLiner() -> Int { return 500 }
func someFunction(x: Int) {
foo(x)
bar(x)
}
}

"""

assertPrettyPrintEqual(
input: input, expected: expectedNotRespecting, linelength: 80,
configuration: configuration(respectingExistingLineBreaks: false))
}

func testSemicolons() {
let input =
"""
foo(); bar();
baz();

struct Foo {
var a: Int; var b: Int;
var c: Int;
}
"""

// When respecting newlines, we should leave semicolon-delimited statements and declarations on
// the same line if they were originally like that and likewise preserve newlines after
// semicolons if present.
assertPrettyPrintEqual(
input: input, expected: input + "\n", linelength: 80,
configuration: configuration(respectingExistingLineBreaks: true))

let expectedNotRespecting =
"""
foo();
bar();
baz();

struct Foo {
var a: Int;
var b: Int;
var c: Int;
}

"""

// When not respecting newlines every semicolon-delimited statement or declaration should end up
// on its own line.
assertPrettyPrintEqual(
input: input, expected: expectedNotRespecting, linelength: 80,
configuration: configuration(respectingExistingLineBreaks: false))
}

func testInvalidBreaksAreAlwaysRejected() {
// Verify that newlines in places where a break would not be allowed are removed, regardless of
// the configuration setting.
let input =
"""
func foo
(bar
: Int) ->
Int {
return bar * 2
}
"""

let expectedRespecting =
"""
func foo(bar: Int) -> Int {
return bar * 2
}

"""

assertPrettyPrintEqual(
input: input, expected: expectedRespecting, linelength: 80,
configuration: configuration(respectingExistingLineBreaks: true))

let expectedNotRespecting =
"""
func foo(bar: Int) -> Int { return bar * 2 }

"""

assertPrettyPrintEqual(
input: input, expected: expectedNotRespecting, linelength: 80,
configuration: configuration(respectingExistingLineBreaks: false))
}

/// Creates a new configuration with the given value for `respectsExistingLineBreaks` and default
/// values for everything else.
private func configuration(respectingExistingLineBreaks: Bool) -> Configuration {
let config = Configuration()
config.respectsExistingLineBreaks = respectingExistingLineBreaks
return config
}
}
13 changes: 13 additions & 0 deletions Tests/SwiftFormatPrettyPrintTests/XCTestManifests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,18 @@ extension RepeatStmtTests {
]
}

extension RespectsExistingLineBreaksTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__RespectsExistingLineBreaksTests = [
("testCodeBlocksAndMemberDecls", testCodeBlocksAndMemberDecls),
("testExpressions", testExpressions),
("testInvalidBreaksAreAlwaysRejected", testInvalidBreaksAreAlwaysRejected),
("testSemicolons", testSemicolons),
]
}

extension SemiColonTypeTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
Expand Down Expand Up @@ -631,6 +643,7 @@ public func __allTests() -> [XCTestCaseEntry] {
testCase(OperatorDeclTests.__allTests__OperatorDeclTests),
testCase(ProtocolDeclTests.__allTests__ProtocolDeclTests),
testCase(RepeatStmtTests.__allTests__RepeatStmtTests),
testCase(RespectsExistingLineBreaksTests.__allTests__RespectsExistingLineBreaksTests),
testCase(SemiColonTypeTests.__allTests__SemiColonTypeTests),
testCase(SequenceExprFoldingTests.__allTests__SequenceExprFoldingTests),
testCase(SomeTypeTests.__allTests__SomeTypeTests),
Expand Down