Skip to content
Merged
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
11 changes: 9 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@

### Breaking

* None.
* If `SWIFTLINT_DISABLE_SOURCEKIT` is set to prohibit loading `libsourcekitdInProc` at runtime,
rules requiring SourceKit will be disabled and a warning will be printed once per rule.
[SimplyDanny](https://github.com/SimplyDanny)

### Experimental

* None.

### Enhancements

* None.
* A fully statically linked Linux binary can now be built with the Swift SDK and
the compiler options `-Xswiftc -DSWIFTLINT_DISABLE_SOURCEKIT`. This binary does not
require `libsourcekitdInProc.so` or any other dynamic libraries to be present on the
system at runtime. Rules requiring SourceKit will be disabled and reported to the console
when running this binary.
[SimplyDanny](https://github.com/SimplyDanny)

### Bug Fixes

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import SourceKittenFramework

@DisabledWithoutSourceKit
struct QuickDiscouragedCallRule: OptInRule {
var configuration = SeverityConfiguration<Self>(.warning)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import SourceKittenFramework

private let moduleToLog = ProcessInfo.processInfo.environment["SWIFTLINT_LOG_MODULE_USAGE"]

@DisabledWithoutSourceKit
struct UnusedImportRule: CorrectableRule, AnalyzerRule {
var configuration = UnusedImportConfiguration()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import SourceKittenFramework

private typealias FileTypeOffset = (fileType: FileTypesOrderConfiguration.FileType, offset: ByteCount)

@DisabledWithoutSourceKit
struct FileTypesOrderRule: OptInRule {
var configuration = FileTypesOrderConfiguration()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Foundation
import SourceKittenFramework

@DisabledWithoutSourceKit
struct IndentationWidthRule: OptInRule {
// MARK: - Subtypes
private enum Indentation: Equatable {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Foundation
import SourceKittenFramework

@DisabledWithoutSourceKit
struct LiteralExpressionEndIndentationRule: Rule, OptInRule {
var configuration = SeverityConfiguration<Self>(.warning)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Foundation
import SourceKittenFramework

@DisabledWithoutSourceKit
struct ModifierOrderRule: ASTRule, OptInRule, CorrectableRule {
var configuration = ModifierOrderConfiguration()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Foundation
import SourceKittenFramework

@DisabledWithoutSourceKit
struct MultilineFunctionChainsRule: ASTRule, OptInRule {
var configuration = SeverityConfiguration<Self>(.warning)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Foundation
import SourceKittenFramework

@DisabledWithoutSourceKit
struct MultilineParametersBracketsRule: OptInRule {
var configuration = SeverityConfiguration<Self>(.warning)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ private extension Sequence where Element == Line {
}
}

@DisabledWithoutSourceKit
struct SortedImportsRule: CorrectableRule, OptInRule {
var configuration = SortedImportsConfiguration()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Foundation
import SourceKittenFramework

@DisabledWithoutSourceKit
struct StatementPositionRule: CorrectableRule {
var configuration = StatementPositionConfiguration()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ private extension SwiftLintFile {
}
}

@DisabledWithoutSourceKit
struct VerticalWhitespaceBetweenCasesRule: Rule {
var configuration = SeverityConfiguration<Self>(.warning)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Foundation
import SourceKittenFramework

@DisabledWithoutSourceKit
struct VerticalWhitespaceClosingBracesRule: CorrectableRule, OptInRule {
var configuration = VerticalWhitespaceClosingBracesConfiguration()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ private extension SwiftLintFile {
}
}

@DisabledWithoutSourceKit
struct VerticalWhitespaceOpeningBracesRule: Rule {
var configuration = SeverityConfiguration<Self>(.warning)

Expand Down
12 changes: 10 additions & 2 deletions Source/SwiftLintCore/Extensions/Request+SwiftLint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@ import Foundation
import SourceKittenFramework

public extension Request {
static let disableSourceKit = ProcessInfo.processInfo.environment["SWIFTLINT_DISABLE_SOURCEKIT"] != nil
static let disableSourceKit = {
#if SWIFTLINT_DISABLE_SOURCEKIT
// Compile-time
true
#else
// Runtime
ProcessInfo.processInfo.environment["SWIFTLINT_DISABLE_SOURCEKIT"] != nil
#endif
}()

func sendIfNotDisabled() throws -> [String: any SourceKitRepresentable] {
// Skip safety checks if explicitly allowed (e.g., for testing or specific operations)
Expand Down Expand Up @@ -37,7 +45,7 @@ public extension Request {
}

guard !Self.disableSourceKit else {
throw Self.Error.connectionInterrupted("SourceKit is disabled by `SWIFTLINT_DISABLE_SOURCEKIT`.")
queuedFatalError("SourceKit is disabled by `SWIFTLINT_DISABLE_SOURCEKIT`.")
}
return try send()
}
Expand Down
9 changes: 9 additions & 0 deletions Source/SwiftLintCore/Helpers/Macros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ public macro AcceptableByConfigurationElement() = #externalMacro(
type: "AcceptableByConfigurationElement"
)

@attached(
extension,
names: named(notifyRuleDisabledOnce), named(postMessage)
)
public macro DisabledWithoutSourceKit() = #externalMacro(
module: "SwiftLintCoreMacros",
type: "DisabledWithoutSourceKit"
)

/// Deprecated. Use `AcceptableByConfigurationElement` instead.
@available(*, deprecated, renamed: "AcceptableByConfigurationElement")
@attached(
Expand Down
8 changes: 4 additions & 4 deletions Source/SwiftLintCore/Models/CurrentRule.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/// A task-local value that holds the identifier of the currently executing rule.
/// This allows SourceKit request handling to determine if the current rule
/// is a SourceKitFreeRule without modifying function signatures throughout the codebase.
/// This allows SourceKit request handling to determine certain properties without
/// modifying function signatures throughout the codebase.
public enum CurrentRule {
/// The Rule ID for the currently executing rule.
/// A task-local value that holds the identifier of the currently executing rule, e.g., to check whether the rule
/// is allowed to make SourceKit requests.
@TaskLocal public static var identifier: String?

/// Allows specific SourceKit requests to be made outside of rule execution context.
Expand Down
8 changes: 8 additions & 0 deletions Source/SwiftLintCore/Protocols/Rule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ public protocol Rule: Sendable {
///
/// - Returns: A boolean value indicating whether the rule is enabled in the given region.
func isEnabled(in region: Region, for ruleID: String) -> Bool

/// Prints a warning to the console once about the rule being disabled due to SourceKit being unavailable. The
/// default implementation does nothing. Rules that depend on SourceKit should override this appropriately.
func notifyRuleDisabledOnce()
}

public extension Rule {
Expand Down Expand Up @@ -148,6 +152,10 @@ public extension Rule {
func isEnabled(in region: Region, for ruleID: String) -> Bool {
!Self.description.allIdentifiers.contains(ruleID) || region.isRuleEnabled(self)
}

func notifyRuleDisabledOnce() {
// Intentionally empty by default.
}
}

public extension Rule {
Expand Down
34 changes: 34 additions & 0 deletions Source/SwiftLintCoreMacros/DisabledWithoutSourceKit.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros

enum DisabledWithoutSourceKit: ExtensionMacro {
static func expansion(
of _: AttributeSyntax,
attachedTo declaration: some DeclGroupSyntax,
providingExtensionsOf type: some TypeSyntaxProtocol,
conformingTo _: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [ExtensionDeclSyntax] {
guard declaration.is(StructDeclSyntax.self) else {
context.diagnose(SwiftLintCoreMacroError.notStruct.diagnose(at: declaration))
return []
}
let message = #"""
"Skipping enabled rule '\(Self.identifier)' because it requires SourceKit and SourceKit access is prohibited."
"""#
return [
try ExtensionDeclSyntax("""
extension \(type) {
private static let postMessage: Void = {
Issue.genericWarning(\(raw: message)).print()
}()

func notifyRuleDisabledOnce() {
_ = Self.postMessage
}
}
"""),
]
}
}
1 change: 1 addition & 0 deletions Source/SwiftLintCoreMacros/SwiftLintCoreMacros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ struct SwiftLintCoreMacros: CompilerPlugin {
let providingMacros: [any Macro.Type] = [
AutoConfigParser.self,
AcceptableByConfigurationElement.self,
DisabledWithoutSourceKit.self,
SwiftSyntaxRule.self,
]
}
Expand Down
19 changes: 13 additions & 6 deletions Source/SwiftLintFramework/Models/Linter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,20 @@ private extension Rule {
return false
}

// Only check sourcekitdFailed if the rule requires SourceKit.
// This avoids triggering SourceKit initialization for SourceKit-free rules.
if requiresSourceKit, file.sourcekitdFailed {
warnSourceKitFailedOnce()
return false
if requiresSourceKit {
// If SourceKit is disabled, we skip running the rule and post a warning once.
if Request.disableSourceKit {
notifyRuleDisabledOnce()
return false
}
// Only check `sourcekitdFailed` if the rule requires SourceKit. This avoids triggering SourceKit
// initialization for effectively SourceKit-free rules.
if file.sourcekitdFailed {
warnSourceKitFailedOnce()
return false
}
return true
}

return true
}

Expand Down
1 change: 1 addition & 0 deletions Source/SwiftLintFramework/Rules/CustomRules.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ struct CustomRulesConfiguration: RuleConfiguration, CacheDescriptionProvider {

// MARK: - CustomRules

@DisabledWithoutSourceKit
struct CustomRules: Rule, CacheDescriptionProvider, ConditionallySourceKitFree {
var cacheDescription: String {
configuration.cacheDescription
Expand Down