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
23 changes: 23 additions & 0 deletions Sources/SwiftFormat/Rules/OrderedImports.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@ public final class OrderedImports: SyntaxFormatRule {
public override func visit(_ node: SourceFileSyntax) -> SourceFileSyntax {
var newNode = node
newNode.statements = orderImports(in: node.statements)

// A shebang line ends with a newline that is stored as leading trivia on
// the first statement. Reordering can treat that newline (and any blank
// lines right after it) as leading blank lines of the file header and drop
// them, which pulls the following comment or import up onto the shebang
// line. Put the leading newlines back so the shebang keeps its own line.
if node.shebang != nil, !newNode.statements.isEmpty {
let original = leadingNewlineCount(of: node.statements)
let updated = leadingNewlineCount(of: newNode.statements)
if updated < original {
newNode.statements.leadingTrivia =
.newlines(original - updated) + newNode.statements.leadingTrivia
}
}
return newNode
}

Expand Down Expand Up @@ -373,6 +387,15 @@ private func joinLines(_ inputLineLists: [Line]...) -> [Line] {
return output
}

/// Returns the number of newlines at the very start of the statement list's
/// leading trivia, or zero if it does not start with one.
private func leadingNewlineCount(of statements: CodeBlockItemListSyntax) -> Int {
if case .newlines(let count)? = statements.leadingTrivia.pieces.first {
return count
}
return 0
}

/// This function transforms the statements in a CodeBlockItemListSyntax object into a list of Line
/// objects. Blank lines and standalone comments are represented by their own Line object. Code with
/// a trailing comment are represented together in the same Line.
Expand Down
78 changes: 78 additions & 0 deletions Tests/SwiftFormatTests/PrettyPrint/GarbageTextTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
//
//===----------------------------------------------------------------------===//

import SwiftFormat
import SwiftOperators
import SwiftParser
import SwiftSyntax
import XCTest

private let bom: Unicode.Scalar = "\u{feff}"
private let unknownScalar: Unicode.Scalar = "\u{fffe}"

Expand All @@ -32,6 +38,78 @@ final class GarbageTextTests: PrettyPrintTestCase {
assertPrettyPrintEqual(input: input, expected: expected, linelength: 20)
}

func testHashBangFollowedByLineComment() {
let input =
"""
#!/usr/bin/env swift
// (c) Acme Inc.

print("Hello world!")
"""

let expected =
"""
#!/usr/bin/env swift
// (c) Acme Inc.

print("Hello world!")

"""

assertPrettyPrintEqual(input: input, expected: expected, linelength: 80)

// Also exercise the full formatter pipeline, which is the path the CLI
// takes and the one the original bug report exercised.
assertFormatted(input: input, expected: expected, linelength: 80)
}

private func assertFormatted(
input: String,
expected: String,
linelength: Int,
file: StaticString = #file,
line: UInt = #line
) {
var configuration = Configuration.forTesting
configuration.lineLength = linelength
let formatter = SwiftFormatter(configuration: configuration)
var output = ""
let tree = Parser.parse(source: input)
let folded = try! OperatorTable.standardOperators.foldAll(tree).as(SourceFileSyntax.self)!
try! formatter.format(
syntax: folded,
source: input,
operatorTable: .standardOperators,
assumingFileURL: nil,
selection: .infinite,
to: &output
)
XCTAssertEqual(output, expected, file: file, line: line)
}

func testHashBangFollowedByBlankLineAndComment() {
let input =
"""
#!/usr/bin/env swift

// (c) Acme Inc.

print("Hello world!")
"""

let expected =
"""
#!/usr/bin/env swift

// (c) Acme Inc.

print("Hello world!")

"""

assertPrettyPrintEqual(input: input, expected: expected, linelength: 80)
}

func testBOM() {
let input =
"""
Expand Down
53 changes: 53 additions & 0 deletions Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,59 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase {
)
}

func testShebangWithFileHeaderAndImport() {
// The newline that ends a shebang line is leading trivia on the first
// statement; reordering must not pull the file header up onto the shebang.
assertFormatting(
OrderedImports.self,
input: """
#!/usr/bin/swift
//
// some file comment

import Foundation

someCode()
""",
expected: """
#!/usr/bin/swift
//
// some file comment

import Foundation

someCode()
"""
)
}

func testShebangWithReorderedImports() {
assertFormatting(
OrderedImports.self,
input: """
#!/usr/bin/swift
// File header.

import Zebra
1️⃣import Apple

someCode()
""",
expected: """
#!/usr/bin/swift
// File header.

import Apple
import Zebra

someCode()
""",
findings: [
FindingSpec("1️⃣", message: "sort import statements lexicographically")
]
)
}

func testNonHeaderComment() {
let input =
"""
Expand Down
Loading