Skip to content
Open
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@

### Enhancements

* Add the opt-in `imports_at_top` rule to require file-scope imports to appear
before other declarations.
[udithaAnuranjana](https://github.com/udithaAnuranjana)
[#6892](https://github.com/realm/SwiftLint/issues/6892)

* Add autocorrection to the `multiline_call_arguments` rule, expanding single-line
and multi-line calls to one-argument-per-line, including nested calls whose closing
`)` would otherwise be stranded with the last argument.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import Foundation
import PackagePlugin

#if canImport(XcodeProjectPlugin)
import XcodeProjectPlugin
#endif

@main
struct SwiftLintBuildToolPlugin: BuildToolPlugin {
func createBuildCommands(
Expand Down Expand Up @@ -89,8 +93,6 @@ private func addedToEnvironment(_ new: [String: String]) -> [String: String] {

#if canImport(XcodeProjectPlugin)

import XcodeProjectPlugin

// swiftlint:disable:next no_grouping_extension
extension SwiftLintBuildToolPlugin: XcodeBuildToolPlugin {
func createBuildCommands(
Expand Down
6 changes: 4 additions & 2 deletions Plugins/SwiftLintCommandPlugin/CommandContext.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import PackagePlugin

#if canImport(XcodeProjectPlugin)
import XcodeProjectPlugin
#endif

protocol CommandContext {
var tool: String { get throws }

Expand Down Expand Up @@ -54,8 +58,6 @@ extension PluginContext: CommandContext {

#if canImport(XcodeProjectPlugin)

import XcodeProjectPlugin

extension XcodePluginContext: CommandContext {
var tool: String {
get throws {
Expand Down
6 changes: 4 additions & 2 deletions Plugins/SwiftLintCommandPlugin/SwiftLintCommandPlugin.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import Foundation
import PackagePlugin

#if canImport(XcodeProjectPlugin)
import XcodeProjectPlugin
#endif

private let commandsNotExpectingPaths: Set<String> = [
"docs",
"generate-docs",
Expand All @@ -23,8 +27,6 @@ struct SwiftLintCommandPlugin: CommandPlugin {

#if canImport(XcodeProjectPlugin)

import XcodeProjectPlugin

extension SwiftLintCommandPlugin: XcodeCommandPlugin {
func performCommand(context: XcodePluginContext, arguments: [String]) throws {
try lintFiles(context: context, arguments: arguments)
Expand Down
1 change: 1 addition & 0 deletions Source/SwiftLintBuiltInRules/Models/BuiltInRules.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public let builtInRules: [any Rule.Type] = [
ImplicitOptionalInitializationRule.self,
ImplicitReturnRule.self,
ImplicitlyUnwrappedOptionalRule.self,
ImportsAtTopRule.self,
InclusiveLanguageRule.self,
IncompatibleConcurrencyAnnotationRule.self,
IndentationWidthRule.self,
Expand Down
155 changes: 155 additions & 0 deletions Source/SwiftLintBuiltInRules/Rules/Style/ImportsAtTopRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import SwiftLintCore
import SwiftSyntax

@SwiftSyntaxRule(optIn: true)
struct ImportsAtTopRule: Rule {
var configuration = SeverityConfiguration<Self>(.warning)

static let description = RuleDescription(
identifier: "imports_at_top",
name: "Imports at Top",
description: "Imports should appear at the beginning of the file",
kind: .style,
nonTriggeringExamples: #examples([
"""
struct Foo {}
""",
"""
import Foundation
import UIKit
@testable import MyModule

struct Foo {}
""",
"""
// File header comment

import Foundation
import UIKit

struct Foo {}
""",
"""
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif

import Foundation

struct Foo {}
""",
"""
#if canImport(UIKit)
#if DEBUG
import UIKit
#endif
#endif

import Foundation

struct Foo {}
""",
]),
triggeringExamples: #examples([
"""
import Foundation

struct Foo {}

↓import UIKit
""",
"""
struct FooTests {}

@testable ↓import MyModule
""",
"""
struct Foo {}

#if canImport(UIKit)
↓import UIKit
#else
↓import AppKit
#endif
""",
"""
#if DEBUG
struct DebugHelper {}

↓import Testing
#endif
""",
"""
#if DEBUG
struct DebugHelper {}

#if canImport(UIKit)
↓import UIKit
#endif
#endif
""",
])
)
}

private extension ImportsAtTopRule {
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
override var skippableDeclarations: [any DeclSyntaxProtocol.Type] { .all }

override func visitPost(_ node: SourceFileSyntax) {
var hasSeenNonImportItem = false
checkImportPlacement(
in: node.statements,
hasSeenNonImportItem: &hasSeenNonImportItem
)
}

private func checkImportPlacement(in statements: CodeBlockItemListSyntax, hasSeenNonImportItem: inout Bool) {
for statement in statements {
if let importDecl = statement.item.as(ImportDeclSyntax.self) {
if hasSeenNonImportItem {
violations.append(
importDecl.importKeyword.positionAfterSkippingLeadingTrivia
)
}
continue
}

if let ifConfig = statement.item.as(IfConfigDeclSyntax.self) {
checkImportPlacement(
in: ifConfig,
hasSeenNonImportItem: &hasSeenNonImportItem
)
continue
}

hasSeenNonImportItem = true
}
}

private func checkImportPlacement(in ifConfig: IfConfigDeclSyntax, hasSeenNonImportItem: inout Bool) {
let hadSeenNonImportItemBeforeIf = hasSeenNonImportItem
var anyClauseHasSeenNonImportItem = false

for clause in ifConfig.clauses {
guard let statements = clause.elements?.as(CodeBlockItemListSyntax.self) else {
continue
}

var clauseHasSeenNonImportItem = hadSeenNonImportItemBeforeIf

checkImportPlacement(
in: statements,
hasSeenNonImportItem: &clauseHasSeenNonImportItem
)

if clauseHasSeenNonImportItem {
anyClauseHasSeenNonImportItem = true
}
}
hasSeenNonImportItem = hadSeenNonImportItemBeforeIf || anyClauseHasSeenNonImportItem
}
}
}
16 changes: 8 additions & 8 deletions Tests/GeneratedTests/GeneratedTests_04.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ struct ImplicitlyUnwrappedOptionalRuleGeneratedTests {
}
}

@Suite(.rulesRegistered)
struct ImportsAtTopRuleGeneratedTests {
@Test
func withDefaultConfiguration() {
verifyRule(ImportsAtTopRule.description)
}
}

@Suite(.rulesRegistered)
struct InclusiveLanguageRuleGeneratedTests {
@Test
Expand Down Expand Up @@ -200,11 +208,3 @@ struct LastWhereRuleGeneratedTests {
verifyRule(LastWhereRule.description)
}
}

@Suite(.rulesRegistered)
struct LeadingWhitespaceRuleGeneratedTests {
@Test
func withDefaultConfiguration() {
verifyRule(LeadingWhitespaceRule.description)
}
}
16 changes: 8 additions & 8 deletions Tests/GeneratedTests/GeneratedTests_05.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ import Testing
@testable import SwiftLintBuiltInRules
@testable import SwiftLintCore

@Suite(.rulesRegistered)
struct LeadingWhitespaceRuleGeneratedTests {
@Test
func withDefaultConfiguration() {
verifyRule(LeadingWhitespaceRule.description)
}
}

@Suite(.rulesRegistered)
struct LegacyCGGeometryFunctionsRuleGeneratedTests {
@Test
Expand Down Expand Up @@ -200,11 +208,3 @@ struct MultilineParametersBracketsRuleGeneratedTests {
verifyRule(MultilineParametersBracketsRule.description)
}
}

@Suite(.rulesRegistered)
struct MultilineParametersRuleGeneratedTests {
@Test
func withDefaultConfiguration() {
verifyRule(MultilineParametersRule.description)
}
}
16 changes: 8 additions & 8 deletions Tests/GeneratedTests/GeneratedTests_06.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ import Testing
@testable import SwiftLintBuiltInRules
@testable import SwiftLintCore

@Suite(.rulesRegistered)
struct MultilineParametersRuleGeneratedTests {
@Test
func withDefaultConfiguration() {
verifyRule(MultilineParametersRule.description)
}
}

@Suite(.rulesRegistered)
struct MultipleClosuresWithTrailingClosureRuleGeneratedTests {
@Test
Expand Down Expand Up @@ -200,11 +208,3 @@ struct OrphanedDocCommentRuleGeneratedTests {
verifyRule(OrphanedDocCommentRule.description)
}
}

@Suite(.rulesRegistered)
struct OverriddenSuperCallRuleGeneratedTests {
@Test
func withDefaultConfiguration() {
verifyRule(OverriddenSuperCallRule.description)
}
}
16 changes: 8 additions & 8 deletions Tests/GeneratedTests/GeneratedTests_07.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ import Testing
@testable import SwiftLintBuiltInRules
@testable import SwiftLintCore

@Suite(.rulesRegistered)
struct OverriddenSuperCallRuleGeneratedTests {
@Test
func withDefaultConfiguration() {
verifyRule(OverriddenSuperCallRule.description)
}
}

@Suite(.rulesRegistered)
struct OverrideInExtensionRuleGeneratedTests {
@Test
Expand Down Expand Up @@ -200,11 +208,3 @@ struct QuickDiscouragedPendingTestRuleGeneratedTests {
verifyRule(QuickDiscouragedPendingTestRule.description)
}
}

@Suite(.rulesRegistered)
struct RawValueForCamelCasedCodableEnumRuleGeneratedTests {
@Test
func withDefaultConfiguration() {
verifyRule(RawValueForCamelCasedCodableEnumRule.description)
}
}
16 changes: 8 additions & 8 deletions Tests/GeneratedTests/GeneratedTests_08.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ import Testing
@testable import SwiftLintBuiltInRules
@testable import SwiftLintCore

@Suite(.rulesRegistered)
struct RawValueForCamelCasedCodableEnumRuleGeneratedTests {
@Test
func withDefaultConfiguration() {
verifyRule(RawValueForCamelCasedCodableEnumRule.description)
}
}

@Suite(.rulesRegistered)
struct ReduceBooleanRuleGeneratedTests {
@Test
Expand Down Expand Up @@ -200,11 +208,3 @@ struct SortedFirstLastRuleGeneratedTests {
verifyRule(SortedFirstLastRule.description)
}
}

@Suite(.rulesRegistered)
struct SortedImportsRuleGeneratedTests {
@Test
func withDefaultConfiguration() {
verifyRule(SortedImportsRule.description)
}
}
16 changes: 8 additions & 8 deletions Tests/GeneratedTests/GeneratedTests_09.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ import Testing
@testable import SwiftLintBuiltInRules
@testable import SwiftLintCore

@Suite(.rulesRegistered)
struct SortedImportsRuleGeneratedTests {
@Test
func withDefaultConfiguration() {
verifyRule(SortedImportsRule.description)
}
}

@Suite(.rulesRegistered)
struct StatementPositionRuleGeneratedTests {
@Test
Expand Down Expand Up @@ -200,11 +208,3 @@ struct UnhandledThrowingTaskRuleGeneratedTests {
verifyRule(UnhandledThrowingTaskRule.description)
}
}

@Suite(.rulesRegistered)
struct UnneededBreakInSwitchRuleGeneratedTests {
@Test
func withDefaultConfiguration() {
verifyRule(UnneededBreakInSwitchRule.description)
}
}
Loading