diff --git a/Sources/SwiftFormat/Rules/OrderedImports.swift b/Sources/SwiftFormat/Rules/OrderedImports.swift index 93abebd2..257894a4 100644 --- a/Sources/SwiftFormat/Rules/OrderedImports.swift +++ b/Sources/SwiftFormat/Rules/OrderedImports.swift @@ -26,7 +26,16 @@ import SwiftSyntax public final class OrderedImports: SyntaxFormatRule { public override func visit(_ node: SourceFileSyntax) -> SourceFileSyntax { - let lines = generateLines(codeBlockItemList: node.statements, context: context) + var newNode = node + newNode.statements = orderImports(in: node.statements, atStartOfFile: true) + return newNode + } + + private func orderImports( + in codeBlockItemList: CodeBlockItemListSyntax, + atStartOfFile: Bool + ) -> CodeBlockItemListSyntax { + let lines = generateLines(codeBlockItemList: codeBlockItemList, context: context) // Stores the formatted and sorted lines that will be used to reconstruct the list of code block // items later. @@ -38,7 +47,7 @@ public final class OrderedImports: SyntaxFormatRule { var testableImports: [Line] = [] var codeBlocks: [Line] = [] var fileHeader: [Line] = [] - var atStartOfFile = true + var atStartOfFile = atStartOfFile var commentBuffer: [Line] = [] func formatAndAppend(linesSection: ArraySlice) { @@ -118,6 +127,24 @@ public final class OrderedImports: SyntaxFormatRule { } } + if let syntaxNode = line.syntaxNode, case .ifConfigCodeBlock(let ifConfigCodeBlock) = syntaxNode { + var ifConfigDecl = ifConfigCodeBlock.item.cast(IfConfigDeclSyntax.self) + + let newClauses = ifConfigDecl.clauses.map { clause in + guard case .statements(let codeBlockItemList) = clause.elements else { + return clause + } + var newClause = clause + var newCodeBlockItemList = orderImports(in: codeBlockItemList, atStartOfFile: false) + newCodeBlockItemList.leadingTrivia = .newline + newCodeBlockItemList.leadingTrivia + newClause.elements = .statements(newCodeBlockItemList) + return newClause + } + + ifConfigDecl.clauses = IfConfigClauseListSyntax(newClauses) + line.syntaxNode = .ifConfigCodeBlock(CodeBlockItemSyntax(item: .decl(DeclSyntax(ifConfigDecl)))) + } + // Separate lines into different categories along with any associated comments. switch line.type { case .regularImport: @@ -154,9 +181,7 @@ public final class OrderedImports: SyntaxFormatRule { formatAndAppend(linesSection: lines[lastSliceStartIndex.. [CodeBlockItemSyntax] { switch syntaxNode { case .importCodeBlock(let codeBlock, _): append(codeBlockItem: codeBlock) + case .ifConfigCodeBlock(let ifConfigCodeBlock): + append(codeBlockItem: ifConfigCodeBlock) case .nonImportCodeBlocks(let codeBlocks): codeBlocks.forEach(append(codeBlockItem:)) } @@ -458,6 +490,9 @@ private class Line { case nonImportCodeBlocks([CodeBlockItemSyntax]) /// A single code block item whose content must be an import decl. case importCodeBlock(CodeBlockItemSyntax, sortable: Bool) + /// A single code block item whose content must be an if config decl. + /// This is used to sort conditional imports. + case ifConfigCodeBlock(CodeBlockItemSyntax) } /// Stores line comments. `syntaxNode` need not be defined, since a comment can exist by itself on @@ -478,7 +513,7 @@ private class Line { var type: LineType { if let syntaxNode = syntaxNode { switch syntaxNode { - case .nonImportCodeBlocks: + case .nonImportCodeBlocks, .ifConfigCodeBlock: return .codeBlock case .importCodeBlock(let importCodeBlock, _): guard let importDecl = importCodeBlock.item.as(ImportDeclSyntax.self) else { @@ -542,6 +577,8 @@ private class Line { return codeBlock.firstToken(viewMode: .sourceAccurate) case .nonImportCodeBlocks(let codeBlocks): return codeBlocks.first?.firstToken(viewMode: .sourceAccurate) + case .ifConfigCodeBlock(let codeBlock): + return codeBlock.firstToken(viewMode: .sourceAccurate) } } @@ -592,6 +629,8 @@ extension Line: CustomStringConvertible { description += "\(codeBlocks.count) code blocks " case .importCodeBlock(_, let sortable): description += "\(sortable ? "sorted" : "unsorted") import \(importName) " + case .ifConfigCodeBlock: + description += "if config code block " } } diff --git a/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift b/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift index 9a3c51d6..1d0bb7fd 100644 --- a/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift +++ b/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift @@ -616,11 +616,13 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase { import Zebras 1️⃣import Apples #if canImport(Darwin) - import Darwin + import Foundation + 2️⃣import Darwin #elseif canImport(Glibc) import Glibc + 3️⃣import Foundation #endif - 2️⃣import Aardvarks + 4️⃣import Aardvarks foo() bar() @@ -633,7 +635,9 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase { #if canImport(Darwin) import Darwin + import Foundation #elseif canImport(Glibc) + import Foundation import Glibc #endif @@ -643,7 +647,9 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase { """, findings: [ FindingSpec("1️⃣", message: "sort import statements lexicographically"), - FindingSpec("2️⃣", message: "place imports at the top of the file"), + FindingSpec("2️⃣", message: "sort import statements lexicographically"), + FindingSpec("3️⃣", message: "sort import statements lexicographically"), + FindingSpec("4️⃣", message: "place imports at the top of the file"), ] ) }