// // SortImportsTests.swift // SwiftFormatTests // // Created by Nick Lockwood on 8/13/23. // Copyright © 2024 Nick Lockwood. All rights reserved. // import XCTest @testable import SwiftFormat final class SortImportsTests: XCTestCase { func testSortImportsSimpleCase() { let input = """ import Foo import Bar """ let output = """ import Bar import Foo """ testFormatting(for: input, output, rule: .sortImports) } func testDefaultGroupingBehaviorIsAccessControlThenAlpha() { let input = """ @testable import Foo import Zed public import Alpha import Bar public import Beta @testable import Ace """ let output = """ public import Alpha public import Beta @testable import Ace import Bar @testable import Foo import Zed """ testFormatting(for: input, output, rule: .sortImports) } func testSortImportsKeepsPreviousCommentWithImport() { let input = """ import Foo // important comment // (very important) import Bar """ let output = """ // important comment // (very important) import Bar import Foo """ testFormatting(for: input, output, rule: .sortImports, exclude: [.blankLineAfterImports]) } func testSortImportsKeepsPreviousCommentWithImport2() { let input = """ // important comment // (very important) import Foo import Bar """ let output = """ import Bar // important comment // (very important) import Foo """ testFormatting(for: input, output, rule: .sortImports, exclude: [.blankLineAfterImports]) } func testSortImportsDoesntMoveHeaderComment() { let input = """ // header comment import Foo import Bar """ let output = """ // header comment import Bar import Foo """ testFormatting(for: input, output, rule: .sortImports) } func testSortImportsDoesntMoveHeaderCommentFollowedByImportComment() { let input = """ // header comment // important comment import Foo import Bar """ let output = """ // header comment import Bar // important comment import Foo """ testFormatting(for: input, output, rule: .sortImports, exclude: [.blankLineAfterImports]) } func testSortImportsOnSameLine() { let input = """ import Foo; import Bar import Baz """ let output = """ import Baz import Foo; import Bar """ testFormatting(for: input, output, rule: .sortImports) } func testSortImportsWithSemicolonAndCommentOnSameLine() { let input = """ import Foo; // foobar import Bar import Baz """ let output = """ import Bar import Baz import Foo; // foobar """ testFormatting(for: input, output, rule: .sortImports, exclude: [.semicolons]) } func testSortImportEnum() { let input = """ import enum Foo.baz import Foo.bar """ let output = """ import Foo.bar import enum Foo.baz """ testFormatting(for: input, output, rule: .sortImports) } func testSortImportFunc() { let input = """ import func Foo.baz import Foo.bar """ let output = """ import Foo.bar import func Foo.baz """ testFormatting(for: input, output, rule: .sortImports) } func testAlreadySortImportsDoesNothing() { let input = """ import Bar import Foo """ testFormatting(for: input, rule: .sortImports) } func testPreprocessorSortImports() { let input = """ #if os(iOS) import Foo2 import Bar2 #else import Foo1 import Bar1 #endif import Foo3 import Bar3 """ let output = """ #if os(iOS) import Bar2 import Foo2 #else import Bar1 import Foo1 #endif import Bar3 import Foo3 """ testFormatting(for: input, output, rule: .sortImports) } func testTestableSortImports() { let input = """ @testable import Foo3 import Bar3 """ let output = """ import Bar3 @testable import Foo3 """ testFormatting(for: input, output, rule: .sortImports) } func testLengthSortImports() { let input = """ import Foo import Module import Bar3 """ let output = """ import Foo import Bar3 import Module """ let options = FormatOptions(importGrouping: [.length]) testFormatting(for: input, output, rule: .sortImports, options: options) } func testTestableImportsWithTestableOnPreviousLine() { let input = """ @testable import Foo3 import Bar3 """ let output = """ import Bar3 @testable import Foo3 """ testFormatting(for: input, output, rule: .sortImports) } func testTestableImportsWithGroupingTestableBottom() { let input = """ @testable import Bar import Foo @testable import UIKit """ let output = """ import Foo @testable import Bar @testable import UIKit """ let options = FormatOptions(importGrouping: [.alpha, .testableLast]) testFormatting(for: input, output, rule: .sortImports, options: options) } func testTestableImportsWithGroupingTestableTop() { let input = """ @testable import Bar import Foo @testable import UIKit """ let output = """ @testable import Bar @testable import UIKit import Foo """ let options = FormatOptions(importGrouping: [.alpha, .testableFirst]) testFormatting(for: input, output, rule: .sortImports, options: options) } func testCaseInsensitiveSortImports() { let input = """ import Zlib import lib """ let output = """ import lib import Zlib """ testFormatting(for: input, output, rule: .sortImports) } func testCaseInsensitiveCaseDifferingSortImports() { let input = """ import c import B import A.a import A.A """ let output = """ import A.A import A.a import B import c """ testFormatting(for: input, output, rule: .sortImports) } func testNoDeleteCodeBetweenImports() { let input = """ import Foo func bar() {} import Bar """ testFormatting(for: input, rule: .sortImports, exclude: [.blankLineAfterImports]) } func testNoDeleteCodeBetweenImports2() { let input = """ import Foo import Bar foo = bar import Bar """ let output = """ import Bar import Foo foo = bar import Bar """ testFormatting(for: input, output, rule: .sortImports, exclude: [.blankLineAfterImports]) } func testNoDeleteCodeBetweenImports3() { let input = """ import Z // one #if FLAG print("hi") #endif import A """ testFormatting(for: input, rule: .sortImports) } func testSortContiguousImports() { let input = """ import Foo import Bar func bar() {} import Quux import Baz """ let output = """ import Bar import Foo func bar() {} import Baz import Quux """ testFormatting(for: input, output, rule: .sortImports, exclude: [.blankLineAfterImports]) } func testNoMangleImportsPrecededByComment() { let input = """ // evil comment #if canImport(Foundation) import Foundation #if canImport(UIKit) && canImport(AVFoundation) import UIKit import AVFoundation #endif #endif """ let output = """ // evil comment #if canImport(Foundation) import Foundation #if canImport(UIKit) && canImport(AVFoundation) import AVFoundation import UIKit #endif #endif """ testFormatting(for: input, output, rule: .sortImports) } func testNoMangleFileHeaderNotFollowedByLinebreak() { let input = """ // // Code.swift // Module // // Created by Someone on 4/30/20. // import AModuleUI import AModule import AModuleHelper import SomeOtherModule """ let output = """ // // Code.swift // Module // // Created by Someone on 4/30/20. // import AModule import AModuleHelper import AModuleUI import SomeOtherModule """ testFormatting(for: input, output, rule: .sortImports) } func testNoMangleConditionalImportsFollowedByPrivateDeclaration() { let input = """ #if canImport(UIKit) import UIKit private struct Foo {} #elseif canImport(AppKit) import AppKit private struct Foo {} #endif """ let options = FormatOptions(swiftVersion: "6.1") testFormatting(for: input, rule: .sortImports, options: options, exclude: [.blankLineAfterImports, .indent]) } func testNoMoveSwiftToolsVersionLine() { let input = """ // swift-tools-version: 6.2 import PackageDescription import CompilerPluginSupport """ let output = """ // swift-tools-version: 6.2 import CompilerPluginSupport import PackageDescription """ testFormatting(for: input, output, rule: .sortImports) } // MARK: - Access control sorting func testAccessControlSortImports() { let input = """ import Foo private import Bar public import Baz """ let output = """ public import Baz private import Bar import Foo """ let options = FormatOptions(importGrouping: [.alpha, .accessControl]) testFormatting(for: input, output, rule: .sortImports, options: options) } func testAccessControlSortAlphaWithinLevel() { let input = """ public import Zebra public import Alpha public import Middle """ let output = """ public import Alpha public import Middle public import Zebra """ let options = FormatOptions(importGrouping: [.alpha, .accessControl]) testFormatting(for: input, output, rule: .sortImports, options: options) } func testAccessControlSortLengthWithinLevel() { let input = """ public import Zebra public import Al public import Middle """ let output = """ public import Al public import Zebra public import Middle """ let options = FormatOptions(importGrouping: [.length, .accessControl]) testFormatting(for: input, output, rule: .sortImports, options: options) } func testAccessControlSortLengthWithMultipleACLs() { let input = """ private import LongPrivate public import Baz private import Al public import LongPublic import Foo """ let output = """ public import Baz public import LongPublic private import Al private import LongPrivate import Foo """ let options = FormatOptions(importGrouping: [.length, .accessControl]) testFormatting(for: input, output, rule: .sortImports, options: options) } func testAccessControlWithTestableFirst() { let input = """ import Foo @testable import Bar public import Baz """ let output = """ @testable import Bar public import Baz import Foo """ let options = FormatOptions(importGrouping: [.alpha, .accessControl, .testableFirst]) testFormatting(for: input, output, rule: .sortImports, options: options) } func testAccessControlWithTestableLast() { let input = """ public import Baz @testable import Bar import Foo """ let output = """ public import Baz import Foo @testable import Bar """ let options = FormatOptions(importGrouping: [.alpha, .accessControl, .testableLast]) testFormatting(for: input, output, rule: .sortImports, options: options) } func testUnlabeledImportsSortLast() { let input = """ import Foo public import Bar internal import Baz import Qux """ let output = """ public import Bar internal import Baz import Foo import Qux """ let options = FormatOptions(importGrouping: [.alpha, .accessControl]) testFormatting(for: input, output, rule: .sortImports, options: options) } func testTestableImportsSortedByACLAndAlpha() { let input = """ @testable import DModule @testable public import CModule @testable import AModule @testable public import BModule import ZModule public import UModule import YModule public import TModule """ let output = """ public import TModule public import UModule import YModule import ZModule @testable public import BModule @testable public import CModule @testable import AModule @testable import DModule """ let options = FormatOptions(importGrouping: [.alpha, .accessControl, .testableLast]) testFormatting(for: input, output, rule: .sortImports, options: options) } // MARK: - Length + testable combinations func testLengthSortWithTestableTop() { let input = """ import Foo @testable import LongModule import Ba @testable import Az """ let output = """ @testable import Az @testable import LongModule import Ba import Foo """ let options = FormatOptions(importGrouping: [.length, .testableFirst]) testFormatting(for: input, output, rule: .sortImports, options: options) } func testLengthSortWithTestableBottom() { let input = """ @testable import LongModule import Foo import Ba @testable import Az """ let output = """ import Ba import Foo @testable import Az @testable import LongModule """ let options = FormatOptions(importGrouping: [.length, .testableLast]) testFormatting(for: input, output, rule: .sortImports, options: options) } func testLengthThenAlphaSortImports() { let input = """ import Module import Foo import Bar import Ab """ let output = """ import Ab import Bar import Foo import Module """ let options = FormatOptions(importGrouping: [.length, .alpha]) testFormatting(for: input, output, rule: .sortImports, options: options) } func testLengthThenAlphaSortImportsWithSameLength() { let input = """ import Zed import Foo import Bar """ let output = """ import Bar import Foo import Zed """ let options = FormatOptions(importGrouping: [.length, .alpha]) testFormatting(for: input, output, rule: .sortImports, options: options) } }