Files

108 lines
3.5 KiB
Swift

//
// SortImports.swift
// SwiftFormat
//
// Created by Nick Lockwood on 8/13/23.
// Copyright © 2024 Nick Lockwood. All rights reserved.
//
import Foundation
public extension FormatRule {
/// Sort import statements
static let sortImports = FormatRule(
help: "Sort and group import statements.",
options: ["import-grouping"],
sharedOptions: ["linebreaks"]
) { formatter in
for var importRanges in formatter.parseImports().reversed() {
guard importRanges.count > 1 else { continue }
let range: Range = importRanges.first!.range.lowerBound ..< importRanges.last!.range.upperBound
let sortedRanges = formatter.sortRanges(importRanges)
var insertedLinebreak = false
var sortedTokens = sortedRanges.flatMap { inputRange -> [Token] in
var tokens = Array(formatter.tokens[inputRange.range])
if tokens.first?.isLinebreak == false {
insertedLinebreak = true
tokens.insert(formatter.linebreakToken(for: tokens.startIndex), at: tokens.startIndex)
}
return tokens
}
if insertedLinebreak {
sortedTokens.removeFirst()
}
formatter.replaceTokens(in: range, with: sortedTokens)
}
} examples: {
"""
```diff
- import Foo
- import Bar
+ import Bar
+ import Foo
```
```diff
- import B
- import A
- #if os(iOS)
- import Foo-iOS
- import Bar-iOS
- #endif
+ import A
+ import B
+ #if os(iOS)
+ import Bar-iOS
+ import Foo-iOS
+ #endif
```
"""
}
}
extension Formatter {
func sortRanges(_ ranges: [Formatter.ImportRange]) -> [Formatter.ImportRange] {
let grouping = options.importGrouping
let partitions: [[Formatter.ImportRange]]
if grouping.contains(.testableFirst) {
partitions = [ranges.filter(\.isTestable), ranges.filter { !$0.isTestable }]
} else if grouping.contains(.testableLast) {
partitions = [ranges.filter { !$0.isTestable }, ranges.filter(\.isTestable)]
} else {
partitions = [ranges]
}
return partitions.flatMap { partition in
partition.sorted { lhs, rhs in
if grouping.contains(.accessControl) {
let lhsAccessOrder = accessLevelSortOrder(for: lhs)
let rhsAccessOrder = accessLevelSortOrder(for: rhs)
if lhsAccessOrder != rhsAccessOrder {
return lhsAccessOrder > rhsAccessOrder
}
}
if grouping.contains(.length) {
if lhs.module.count != rhs.module.count {
return lhs.module.count < rhs.module.count
}
if grouping.contains(.alpha) {
return lhs < rhs
}
return false
}
// Default to alphabetical
return lhs < rhs
}
}
}
/// Sort order for import access level using aclModifiers (higher index = more visible).
/// Unlabeled imports return -1 (sorted last).
func accessLevelSortOrder(for range: Formatter.ImportRange) -> Int {
guard let level = range.accessLevel else { return -1 }
return _FormatRules.aclModifiers.firstIndex(of: level) ?? -1
}
}