Skip to content
Draft
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
1 change: 1 addition & 0 deletions CODEOWNERS → .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@
* @allevato @hborla @hamishknight @rintaro

.github/ @hborla @shahmishal
.github/CODEOWNERS @allevato @hborla @hamishknight @rintaro
.swiftci/ @hborla @shahmishal
3 changes: 2 additions & 1 deletion Documentation/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,9 @@ too long.
**description:** Configuration for the `OrderedImports` rule.

- `includeConditionalImports` _(boolean)_: Determines whether imports within conditional compilation blocks (`#if`, `#elseif`, `#else`) should be ordered. When `true`, imports inside conditional blocks will be sorted and organized according to the same rules as top-level imports. When `false`, imports within conditional blocks are left in their original order.
- `shouldGroupImports` _(boolean)_: Determines whether different import types should be grouped together. When `true`, imports are grouped into the following order, with a blank line between each section: 1) regular imports, 2) declaration imports, 3) @\_implementationOnly imports, and 4) @testable imports. When `false`, imports are lexicographically ordered by name, regardless of type.

**default:** `{ "includeConditionalImports" : false }`
**default:** `{ "includeConditionalImports" : false, "shouldGroupImports": true }`

---

Expand Down
11 changes: 7 additions & 4 deletions Documentation/RuleDocumentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,19 +406,22 @@ Lint: If a function call with a trailing closure also contains a non-trailing cl

### OrderedImports

Imports must be lexicographically ordered and logically grouped at the top of each source file.
The order of the import groups is 1) regular imports, 2) declaration imports, 3) @_implementationOnly
Imports must be lexicographically ordered and (optionally) logically grouped at the top of each source file.
The order of the import groups is 1) regular imports, 2) declaration imports, 3) @\_implementationOnly
imports, and 4) @testable imports. These groups are separated by a single blank line. Blank lines in
between the import declarations are removed.

Logical grouping is enabled by default but can be disabled via the `orderedImports.shouldGroupImports`
configuration option to limit this rule to lexicographic ordering.

By default, imports within conditional compilation blocks (`#if`, `#elseif`, `#else`) are not ordered.
This behavior can be controlled via the `orderedImports.includeConditionalImports` configuration option.

Lint: If an import appears anywhere other than the beginning of the file it resides in,
not lexicographically ordered, or not in the appropriate import group, a lint error is
not lexicographically ordered, or (optionally) not in the appropriate import group, a lint error is
raised.

Format: Imports will be reordered and grouped at the top of the file.
Format: Imports will be reordered and (optionally) grouped at the top of the file.

`OrderedImports` rule can format your code automatically.

Expand Down
5 changes: 3 additions & 2 deletions Sources/SwiftFormat/API/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,8 @@ public struct NoAssignmentInExpressionsConfiguration: Codable, Equatable {
/// Configuration for the `OrderedImports` rule.
public struct OrderedImportsConfiguration: Codable, Equatable {
/// Determines whether imports within conditional compilation blocks should be ordered.
public var includeConditionalImports: Bool = false

public var includeConditionalImports = false
/// Determines whether imports are separated into groups based on their type.
public var shouldGroupImports = true
public init() {}
}
80 changes: 52 additions & 28 deletions Sources/SwiftFormat/Rules/OrderedImports.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,22 @@

import SwiftSyntax

/// Imports must be lexicographically ordered and logically grouped at the top of each source file.
/// The order of the import groups is 1) regular imports, 2) declaration imports, 3) @_implementationOnly
/// Imports must be lexicographically ordered and (optionally) logically grouped at the top of each source file.
/// The order of the import groups is 1) regular imports, 2) declaration imports, 3) @\_implementationOnly
/// imports, and 4) @testable imports. These groups are separated by a single blank line. Blank lines in
/// between the import declarations are removed.
///
/// Logical grouping is enabled by default but can be disabled via the `orderedImports.shouldGroupImports`
/// configuration option to limit this rule to lexicographic ordering.
///
/// By default, imports within conditional compilation blocks (`#if`, `#elseif`, `#else`) are not ordered.
/// This behavior can be controlled via the `orderedImports.includeConditionalImports` configuration option.
///
/// Lint: If an import appears anywhere other than the beginning of the file it resides in,
/// not lexicographically ordered, or not in the appropriate import group, a lint error is
/// not lexicographically ordered, or (optionally) not in the appropriate import group, a lint error is
/// raised.
///
/// Format: Imports will be reordered and grouped at the top of the file.
/// Format: Imports will be reordered and (optionally) grouped at the top of the file.
@_spi(Rules)
public final class OrderedImports: SyntaxFormatRule {

Expand All @@ -45,6 +48,7 @@ public final class OrderedImports: SyntaxFormatRule {
var declImports: [Line] = []
var implementationOnlyImports: [Line] = []
var testableImports: [Line] = []

var codeBlocks: [Line] = []
var fileHeader: [Line] = []
var atStartOfFile = true
Expand Down Expand Up @@ -152,35 +156,51 @@ public final class OrderedImports: SyntaxFormatRule {
line.syntaxNode = .ifConfigCodeBlock(CodeBlockItemSyntax(item: .decl(DeclSyntax(ifConfigDecl))))
}

// Separate lines into different categories along with any associated comments.
switch line.type {
case .regularImport:
regularImports.append(contentsOf: commentBuffer)
regularImports.append(line)
commentBuffer = []
if context.configuration.orderedImports.shouldGroupImports {
// Separate lines into different categories along with any associated comments.
switch line.type {
case .regularImport:
regularImports.append(contentsOf: commentBuffer)
regularImports.append(line)
commentBuffer = []

case .implementationOnlyImport:
implementationOnlyImports.append(contentsOf: commentBuffer)
implementationOnlyImports.append(line)
commentBuffer = []
case .implementationOnlyImport:
implementationOnlyImports.append(contentsOf: commentBuffer)
implementationOnlyImports.append(line)
commentBuffer = []

case .testableImport:
testableImports.append(contentsOf: commentBuffer)
testableImports.append(line)
commentBuffer = []
case .testableImport:
testableImports.append(contentsOf: commentBuffer)
testableImports.append(line)
commentBuffer = []

case .declImport:
declImports.append(contentsOf: commentBuffer)
declImports.append(line)
commentBuffer = []
case .declImport:
declImports.append(contentsOf: commentBuffer)
declImports.append(line)
commentBuffer = []

case .codeBlock, .blankLine:
codeBlocks.append(contentsOf: commentBuffer)
codeBlocks.append(line)
commentBuffer = []
case .codeBlock, .blankLine:
codeBlocks.append(contentsOf: commentBuffer)
codeBlocks.append(line)
commentBuffer = []

case .comment:
commentBuffer.append(line)
case .comment:
commentBuffer.append(line)
}
} else {
switch line.type {
case .regularImport, .implementationOnlyImport, .testableImport, .declImport:
regularImports.append(contentsOf: commentBuffer)
regularImports.append(line)
commentBuffer = []
case .codeBlock, .blankLine:
codeBlocks.append(contentsOf: commentBuffer)
codeBlocks.append(line)
commentBuffer = []

case .comment:
commentBuffer.append(line)
}
}
}

Expand Down Expand Up @@ -222,6 +242,10 @@ public final class OrderedImports: SyntaxFormatRule {
}
}

guard context.configuration.orderedImports.shouldGroupImports else {
continue
}

if testableGroup {
switch lineType {
case .regularImport, .declImport, .implementationOnlyImport:
Expand Down
107 changes: 107 additions & 0 deletions Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1018,4 +1018,111 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase {
configuration: configuration
)
}

func testOrderingWithGroupingDisabled() {
var configuration = Configuration.forTesting
configuration.orderedImports.shouldGroupImports = false

let code = """
import Core
@testable import func Darwin.C.isatty
import enum Darwin.D.isatty
import Foundation
@_implementationOnly import InternalModule
import MyModuleUnderTest
@testable import SwiftFormat
import SwiftSyntax
import UIKit

let a = 3
"""

assertFormatting(
OrderedImports.self,
input: code,
expected: code,
findings: [],
configuration: configuration
)
}

func testMixedContentOrderingWithGroupingDisabled() {
var configuration = Configuration.forTesting
configuration.orderedImports.shouldGroupImports = false

assertFormatting(
OrderedImports.self,
input: """
// Header comment

import Core
let a = 3
1️⃣@testable import func Darwin.C.isatty
// Third comment
2️⃣import Foundation
let b = 4
// Second comment
3️⃣4️⃣import enum Darwin.D.isatty
""",
expected: """
// Header comment

import Core
@testable import func Darwin.C.isatty
// Second comment
import enum Darwin.D.isatty
// Third comment
import Foundation

let a = 3
let b = 4
""",
findings: [
FindingSpec("1️⃣", message: "place imports at the top of the file"),
FindingSpec("2️⃣", message: "place imports at the top of the file"),
FindingSpec("3️⃣", message: "place imports at the top of the file"),
FindingSpec("4️⃣", message: "sort import statements lexicographically"),
],
configuration: configuration
)
}

func testInvalidOrderingWithGroupingDisabled() {
var configuration = Configuration.forTesting
configuration.orderedImports.shouldGroupImports = false

assertFormatting(
OrderedImports.self,
input: """
import Core
import func Darwin.C.isatty
@testable import SwiftFormat
1️⃣import enum Darwin.D.isatty
@_implementationOnly import InternalModule
@testable import MyModuleUnderTest
import SwiftSyntax
import Foundation
import UIKit

let a = 3
""",
expected: """
import Core
import func Darwin.C.isatty
import enum Darwin.D.isatty
import Foundation
@_implementationOnly import InternalModule
@testable import MyModuleUnderTest
@testable import SwiftFormat
import SwiftSyntax
import UIKit

let a = 3
""",
findings: [
FindingSpec("1️⃣", message: "sort import statements lexicographically")
],
configuration: configuration
)
}
}
Loading