diff --git a/CHANGELOG.md b/CHANGELOG.md index a85984f73d..519619ac6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ ### 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 @@ -12,7 +14,12 @@ ### 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 diff --git a/Source/SwiftLintBuiltInRules/Rules/Lint/QuickDiscouragedCallRule.swift b/Source/SwiftLintBuiltInRules/Rules/Lint/QuickDiscouragedCallRule.swift index 5e39294c8a..6c35c2e6b6 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Lint/QuickDiscouragedCallRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Lint/QuickDiscouragedCallRule.swift @@ -1,5 +1,6 @@ import SourceKittenFramework +@DisabledWithoutSourceKit struct QuickDiscouragedCallRule: OptInRule { var configuration = SeverityConfiguration(.warning) diff --git a/Source/SwiftLintBuiltInRules/Rules/Lint/UnusedImportRule.swift b/Source/SwiftLintBuiltInRules/Rules/Lint/UnusedImportRule.swift index 59aefbb84c..6541f275f3 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Lint/UnusedImportRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Lint/UnusedImportRule.swift @@ -3,6 +3,7 @@ import SourceKittenFramework private let moduleToLog = ProcessInfo.processInfo.environment["SWIFTLINT_LOG_MODULE_USAGE"] +@DisabledWithoutSourceKit struct UnusedImportRule: CorrectableRule, AnalyzerRule { var configuration = UnusedImportConfiguration() diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/FileTypesOrderRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/FileTypesOrderRule.swift index 0b39623f2a..8d5e930817 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/FileTypesOrderRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/FileTypesOrderRule.swift @@ -3,6 +3,7 @@ import SourceKittenFramework private typealias FileTypeOffset = (fileType: FileTypesOrderConfiguration.FileType, offset: ByteCount) +@DisabledWithoutSourceKit struct FileTypesOrderRule: OptInRule { var configuration = FileTypesOrderConfiguration() diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/IndentationWidthRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/IndentationWidthRule.swift index 77c0d2f5aa..2c8f304de7 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/IndentationWidthRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/IndentationWidthRule.swift @@ -1,6 +1,7 @@ import Foundation import SourceKittenFramework +@DisabledWithoutSourceKit struct IndentationWidthRule: OptInRule { // MARK: - Subtypes private enum Indentation: Equatable { diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/LiteralExpressionEndIndentationRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/LiteralExpressionEndIndentationRule.swift index 842a6c42c3..bbe7066125 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/LiteralExpressionEndIndentationRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/LiteralExpressionEndIndentationRule.swift @@ -1,6 +1,7 @@ import Foundation import SourceKittenFramework +@DisabledWithoutSourceKit struct LiteralExpressionEndIndentationRule: Rule, OptInRule { var configuration = SeverityConfiguration(.warning) diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/ModifierOrderRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/ModifierOrderRule.swift index 6861f197ba..5f721964d0 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/ModifierOrderRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/ModifierOrderRule.swift @@ -1,6 +1,7 @@ import Foundation import SourceKittenFramework +@DisabledWithoutSourceKit struct ModifierOrderRule: ASTRule, OptInRule, CorrectableRule { var configuration = ModifierOrderConfiguration() diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/MultilineFunctionChainsRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/MultilineFunctionChainsRule.swift index 859368544b..c6a9bbd4f3 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/MultilineFunctionChainsRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/MultilineFunctionChainsRule.swift @@ -1,6 +1,7 @@ import Foundation import SourceKittenFramework +@DisabledWithoutSourceKit struct MultilineFunctionChainsRule: ASTRule, OptInRule { var configuration = SeverityConfiguration(.warning) diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/MultilineParametersBracketsRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/MultilineParametersBracketsRule.swift index a31c9c2746..84a8d388f4 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/MultilineParametersBracketsRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/MultilineParametersBracketsRule.swift @@ -1,6 +1,7 @@ import Foundation import SourceKittenFramework +@DisabledWithoutSourceKit struct MultilineParametersBracketsRule: OptInRule { var configuration = SeverityConfiguration(.warning) diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/SortedImportsRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/SortedImportsRule.swift index 94408f4709..014368b08b 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/SortedImportsRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/SortedImportsRule.swift @@ -52,6 +52,7 @@ private extension Sequence where Element == Line { } } +@DisabledWithoutSourceKit struct SortedImportsRule: CorrectableRule, OptInRule { var configuration = SortedImportsConfiguration() diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/StatementPositionRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/StatementPositionRule.swift index 891bb036e3..11cbf7dd71 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/StatementPositionRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/StatementPositionRule.swift @@ -1,6 +1,7 @@ import Foundation import SourceKittenFramework +@DisabledWithoutSourceKit struct StatementPositionRule: CorrectableRule { var configuration = StatementPositionConfiguration() diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/VerticalWhitespaceBetweenCasesRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/VerticalWhitespaceBetweenCasesRule.swift index c7cd724404..5939d82804 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/VerticalWhitespaceBetweenCasesRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/VerticalWhitespaceBetweenCasesRule.swift @@ -7,6 +7,7 @@ private extension SwiftLintFile { } } +@DisabledWithoutSourceKit struct VerticalWhitespaceBetweenCasesRule: Rule { var configuration = SeverityConfiguration(.warning) diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/VerticalWhitespaceClosingBracesRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/VerticalWhitespaceClosingBracesRule.swift index d23484f20f..d62271ce98 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/VerticalWhitespaceClosingBracesRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/VerticalWhitespaceClosingBracesRule.swift @@ -1,6 +1,7 @@ import Foundation import SourceKittenFramework +@DisabledWithoutSourceKit struct VerticalWhitespaceClosingBracesRule: CorrectableRule, OptInRule { var configuration = VerticalWhitespaceClosingBracesConfiguration() diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/VerticalWhitespaceOpeningBracesRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/VerticalWhitespaceOpeningBracesRule.swift index 585a99aa1b..c0deffb9aa 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/VerticalWhitespaceOpeningBracesRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/VerticalWhitespaceOpeningBracesRule.swift @@ -7,6 +7,7 @@ private extension SwiftLintFile { } } +@DisabledWithoutSourceKit struct VerticalWhitespaceOpeningBracesRule: Rule { var configuration = SeverityConfiguration(.warning) diff --git a/Source/SwiftLintCore/Extensions/Request+SwiftLint.swift b/Source/SwiftLintCore/Extensions/Request+SwiftLint.swift index d012b9adba..cb0ce29405 100644 --- a/Source/SwiftLintCore/Extensions/Request+SwiftLint.swift +++ b/Source/SwiftLintCore/Extensions/Request+SwiftLint.swift @@ -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) @@ -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() } diff --git a/Source/SwiftLintCore/Helpers/Macros.swift b/Source/SwiftLintCore/Helpers/Macros.swift index a6e099764c..3bf08c99b6 100644 --- a/Source/SwiftLintCore/Helpers/Macros.swift +++ b/Source/SwiftLintCore/Helpers/Macros.swift @@ -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( diff --git a/Source/SwiftLintCore/Models/CurrentRule.swift b/Source/SwiftLintCore/Models/CurrentRule.swift index 81cd169d0c..6f21a3e565 100644 --- a/Source/SwiftLintCore/Models/CurrentRule.swift +++ b/Source/SwiftLintCore/Models/CurrentRule.swift @@ -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. diff --git a/Source/SwiftLintCore/Protocols/Rule.swift b/Source/SwiftLintCore/Protocols/Rule.swift index 3e31915f28..3d79341147 100644 --- a/Source/SwiftLintCore/Protocols/Rule.swift +++ b/Source/SwiftLintCore/Protocols/Rule.swift @@ -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 { @@ -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 { diff --git a/Source/SwiftLintCoreMacros/DisabledWithoutSourceKit.swift b/Source/SwiftLintCoreMacros/DisabledWithoutSourceKit.swift new file mode 100644 index 0000000000..5c346f6991 --- /dev/null +++ b/Source/SwiftLintCoreMacros/DisabledWithoutSourceKit.swift @@ -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 + } + } + """), + ] + } +} diff --git a/Source/SwiftLintCoreMacros/SwiftLintCoreMacros.swift b/Source/SwiftLintCoreMacros/SwiftLintCoreMacros.swift index fdea5b5c42..3c1d24823d 100644 --- a/Source/SwiftLintCoreMacros/SwiftLintCoreMacros.swift +++ b/Source/SwiftLintCoreMacros/SwiftLintCoreMacros.swift @@ -8,6 +8,7 @@ struct SwiftLintCoreMacros: CompilerPlugin { let providingMacros: [any Macro.Type] = [ AutoConfigParser.self, AcceptableByConfigurationElement.self, + DisabledWithoutSourceKit.self, SwiftSyntaxRule.self, ] } diff --git a/Source/SwiftLintFramework/Models/Linter.swift b/Source/SwiftLintFramework/Models/Linter.swift index 525d25b18c..f0a3c12feb 100644 --- a/Source/SwiftLintFramework/Models/Linter.swift +++ b/Source/SwiftLintFramework/Models/Linter.swift @@ -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 } diff --git a/Source/SwiftLintFramework/Rules/CustomRules.swift b/Source/SwiftLintFramework/Rules/CustomRules.swift index 302261ceb3..c95c005948 100644 --- a/Source/SwiftLintFramework/Rules/CustomRules.swift +++ b/Source/SwiftLintFramework/Rules/CustomRules.swift @@ -50,6 +50,7 @@ struct CustomRulesConfiguration: RuleConfiguration, CacheDescriptionProvider { // MARK: - CustomRules +@DisabledWithoutSourceKit struct CustomRules: Rule, CacheDescriptionProvider, ConditionallySourceKitFree { var cacheDescription: String { configuration.cacheDescription