Skip to content

Commit 64032a7

Browse files
committed
Order conditionally-compiled imports
1 parent 80bcc35 commit 64032a7

File tree

2 files changed

+55
-10
lines changed

2 files changed

+55
-10
lines changed

Sources/SwiftFormat/Rules/OrderedImports.swift

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,16 @@ import SwiftSyntax
2626
public final class OrderedImports: SyntaxFormatRule {
2727

2828
public override func visit(_ node: SourceFileSyntax) -> SourceFileSyntax {
29-
let lines = generateLines(codeBlockItemList: node.statements, context: context)
29+
var newNode = node
30+
newNode.statements = orderImports(in: node.statements, atStartOfFile: true)
31+
return newNode
32+
}
33+
34+
private func orderImports(
35+
in codeBlockItemList: CodeBlockItemListSyntax,
36+
atStartOfFile: Bool
37+
) -> CodeBlockItemListSyntax {
38+
let lines = generateLines(codeBlockItemList: codeBlockItemList, context: context)
3039

3140
// Stores the formatted and sorted lines that will be used to reconstruct the list of code block
3241
// items later.
@@ -38,7 +47,7 @@ public final class OrderedImports: SyntaxFormatRule {
3847
var testableImports: [Line] = []
3948
var codeBlocks: [Line] = []
4049
var fileHeader: [Line] = []
41-
var atStartOfFile = true
50+
var atStartOfFile = atStartOfFile
4251
var commentBuffer: [Line] = []
4352

4453
func formatAndAppend(linesSection: ArraySlice<Line>) {
@@ -118,6 +127,24 @@ public final class OrderedImports: SyntaxFormatRule {
118127
}
119128
}
120129

130+
if let syntaxNode = line.syntaxNode, case .ifConfigCodeBlock(let ifConfigCodeBlock) = syntaxNode {
131+
var ifConfigDecl = ifConfigCodeBlock.item.cast(IfConfigDeclSyntax.self)
132+
133+
let newClauses = ifConfigDecl.clauses.map { clause in
134+
guard case .statements(let codeBlockItemList) = clause.elements else {
135+
return clause
136+
}
137+
var newClause = clause
138+
var newCodeBlockItemList = orderImports(in: codeBlockItemList, atStartOfFile: false)
139+
newCodeBlockItemList.leadingTrivia = .newline + newCodeBlockItemList.leadingTrivia
140+
newClause.elements = .statements(newCodeBlockItemList)
141+
return newClause
142+
}
143+
144+
ifConfigDecl.clauses = IfConfigClauseListSyntax(newClauses)
145+
line.syntaxNode = .ifConfigCodeBlock(CodeBlockItemSyntax(item: .decl(DeclSyntax(ifConfigDecl))))
146+
}
147+
121148
// Separate lines into different categories along with any associated comments.
122149
switch line.type {
123150
case .regularImport:
@@ -154,9 +181,7 @@ public final class OrderedImports: SyntaxFormatRule {
154181
formatAndAppend(linesSection: lines[lastSliceStartIndex..<lines.endIndex])
155182
}
156183

157-
var newNode = node
158-
newNode.statements = CodeBlockItemListSyntax(convertToCodeBlockItems(lines: formattedLines))
159-
return newNode
184+
return CodeBlockItemListSyntax(convertToCodeBlockItems(lines: formattedLines))
160185
}
161186

162187
/// Raise lint errors if the different import types appear in the wrong order, and if import
@@ -354,11 +379,16 @@ private func generateLines(
354379
var blockWithoutTrailingTrivia = block
355380
blockWithoutTrailingTrivia.trailingTrivia = []
356381
currentLine.syntaxNode = .importCodeBlock(blockWithoutTrailingTrivia, sortable: sortable)
382+
} else if block.item.is(IfConfigDeclSyntax.self) {
383+
if currentLine.syntaxNode != nil {
384+
appendNewLine()
385+
}
386+
currentLine.syntaxNode = .ifConfigCodeBlock(block)
357387
} else {
358388
if let syntaxNode = currentLine.syntaxNode {
359389
// Multiple code blocks can be merged, as long as there isn't an import statement.
360390
switch syntaxNode {
361-
case .importCodeBlock:
391+
case .importCodeBlock, .ifConfigCodeBlock:
362392
appendNewLine()
363393
currentLine.syntaxNode = .nonImportCodeBlocks([block])
364394
case .nonImportCodeBlocks(let existingCodeBlocks):
@@ -400,6 +430,8 @@ private func convertToCodeBlockItems(lines: [Line]) -> [CodeBlockItemSyntax] {
400430
switch syntaxNode {
401431
case .importCodeBlock(let codeBlock, _):
402432
append(codeBlockItem: codeBlock)
433+
case .ifConfigCodeBlock(let ifConfigCodeBlock):
434+
append(codeBlockItem: ifConfigCodeBlock)
403435
case .nonImportCodeBlocks(let codeBlocks):
404436
codeBlocks.forEach(append(codeBlockItem:))
405437
}
@@ -458,6 +490,9 @@ private class Line {
458490
case nonImportCodeBlocks([CodeBlockItemSyntax])
459491
/// A single code block item whose content must be an import decl.
460492
case importCodeBlock(CodeBlockItemSyntax, sortable: Bool)
493+
/// A single code block item whose content must be an if config decl.
494+
/// This is used to sort conditional imports.
495+
case ifConfigCodeBlock(CodeBlockItemSyntax)
461496
}
462497

463498
/// Stores line comments. `syntaxNode` need not be defined, since a comment can exist by itself on
@@ -478,7 +513,7 @@ private class Line {
478513
var type: LineType {
479514
if let syntaxNode = syntaxNode {
480515
switch syntaxNode {
481-
case .nonImportCodeBlocks:
516+
case .nonImportCodeBlocks, .ifConfigCodeBlock:
482517
return .codeBlock
483518
case .importCodeBlock(let importCodeBlock, _):
484519
guard let importDecl = importCodeBlock.item.as(ImportDeclSyntax.self) else {
@@ -542,6 +577,8 @@ private class Line {
542577
return codeBlock.firstToken(viewMode: .sourceAccurate)
543578
case .nonImportCodeBlocks(let codeBlocks):
544579
return codeBlocks.first?.firstToken(viewMode: .sourceAccurate)
580+
case .ifConfigCodeBlock(let codeBlock):
581+
return codeBlock.firstToken(viewMode: .sourceAccurate)
545582
}
546583
}
547584

@@ -592,6 +629,8 @@ extension Line: CustomStringConvertible {
592629
description += "\(codeBlocks.count) code blocks "
593630
case .importCodeBlock(_, let sortable):
594631
description += "\(sortable ? "sorted" : "unsorted") import \(importName) "
632+
case .ifConfigCodeBlock:
633+
description += "if config code block "
595634
}
596635
}
597636

Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -616,11 +616,13 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase {
616616
import Zebras
617617
1️⃣import Apples
618618
#if canImport(Darwin)
619-
import Darwin
619+
import Foundation
620+
2️⃣import Darwin
620621
#elseif canImport(Glibc)
621622
import Glibc
623+
3️⃣import Foundation
622624
#endif
623-
2️⃣import Aardvarks
625+
4️⃣import Aardvarks
624626
625627
foo()
626628
bar()
@@ -633,7 +635,9 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase {
633635
634636
#if canImport(Darwin)
635637
import Darwin
638+
import Foundation
636639
#elseif canImport(Glibc)
640+
import Foundation
637641
import Glibc
638642
#endif
639643
@@ -643,7 +647,9 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase {
643647
""",
644648
findings: [
645649
FindingSpec("1️⃣", message: "sort import statements lexicographically"),
646-
FindingSpec("2️⃣", message: "place imports at the top of the file"),
650+
FindingSpec("2️⃣", message: "sort import statements lexicographically"),
651+
FindingSpec("3️⃣", message: "sort import statements lexicographically"),
652+
FindingSpec("4️⃣", message: "place imports at the top of the file"),
647653
]
648654
)
649655
}

0 commit comments

Comments
 (0)