Skip to content

Commit 3a922d4

Browse files
authored
Add ConditionallySourceKitFree to migrate custom rules to SwiftSyntax (#6127)
The protocol will be used to tag rules that may or may not require SourceKit depending on its configuration. I only expect this to be used for custom rules as utility to help transition to a fully SwiftSyntax based approach.
1 parent d22e733 commit 3a922d4

File tree

7 files changed

+133
-5
lines changed

7 files changed

+133
-5
lines changed

Source/SwiftLintCore/Extensions/Request+SwiftLint.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ public extension Request {
1919
}
2020

2121
// Check if the current rule is a SourceKitFreeRule
22-
if ruleType is any SourceKitFreeRule.Type {
22+
// Skip check for ConditionallySourceKitFree rules since we can't determine
23+
// at the type level if they're effectively SourceKit-free
24+
if ruleType is any SourceKitFreeRule.Type &&
25+
!(ruleType is any ConditionallySourceKitFree.Type) {
2326
queuedFatalError("""
2427
'\(ruleID)' is a SourceKitFreeRule and should not be making requests to SourceKit.
2528
""")

Source/SwiftLintCore/Protocols/Rule.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,32 @@ public extension SubstitutionCorrectableRule {
240240
/// A rule that does not need SourceKit to operate and can still operate even after SourceKit has crashed.
241241
public protocol SourceKitFreeRule: Rule {}
242242

243+
/// A rule that may or may not require SourceKit depending on its configuration.
244+
public protocol ConditionallySourceKitFree: Rule {
245+
/// Whether this rule is currently configured in a way that doesn't require SourceKit.
246+
var isEffectivelySourceKitFree: Bool { get }
247+
}
248+
249+
public extension Rule {
250+
/// Whether this rule requires SourceKit to operate.
251+
/// Returns false if the rule conforms to SourceKitFreeRule or if it conforms to
252+
/// ConditionallySourceKitFree and is currently configured to not require SourceKit.
253+
var requiresSourceKit: Bool {
254+
// Check if rule conforms to SourceKitFreeRule
255+
if self is any SourceKitFreeRule {
256+
return false
257+
}
258+
259+
// Check if rule is conditionally SourceKit-free and currently doesn't need SourceKit
260+
if let conditionalRule = self as? any ConditionallySourceKitFree {
261+
return !conditionalRule.isEffectivelySourceKitFree
262+
}
263+
264+
// All other rules require SourceKit
265+
return true
266+
}
267+
}
268+
243269
/// A rule that can operate on the post-typechecked AST using compiler arguments. Performs rules that are more like
244270
/// static analysis than syntactic checks.
245271
public protocol AnalyzerRule: OptInRule {}

Source/SwiftLintFramework/Documentation/RuleDocumentation.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ struct RuleDocumentation {
1212
var isLinterRule: Bool { !isAnalyzerRule }
1313

1414
/// If this rule uses SourceKit.
15-
var usesSourceKit: Bool { !(ruleType is any SourceKitFreeRule.Type) }
15+
/// Note: For ConditionallySourceKitFree rules, this returns true since we can't
16+
/// determine at the type level if they're effectively SourceKit-free.
17+
var usesSourceKit: Bool {
18+
!(ruleType is any SourceKitFreeRule.Type) ||
19+
(ruleType is any ConditionallySourceKitFree.Type)
20+
}
1621

1722
/// If this rule is disabled by default.
1823
var isDisabledByDefault: Bool { ruleType is any OptInRule.Type }

Source/SwiftLintFramework/Models/Linter.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ private extension Rule {
7878
return false
7979
}
8080

81-
if !(self is any SourceKitFreeRule) && file.sourcekitdFailed {
81+
// Only check sourcekitdFailed if the rule requires SourceKit.
82+
// This avoids triggering SourceKit initialization for SourceKit-free rules.
83+
if requiresSourceKit && file.sourcekitdFailed {
8284
warnSourceKitFailedOnce()
8385
return false
8486
}

Source/SwiftLintFramework/Rules/CustomRules.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ struct CustomRulesConfiguration: RuleConfiguration, CacheDescriptionProvider {
3636

3737
// MARK: - CustomRules
3838

39-
struct CustomRules: Rule, CacheDescriptionProvider {
39+
struct CustomRules: Rule, CacheDescriptionProvider, ConditionallySourceKitFree {
4040
var cacheDescription: String {
4141
configuration.cacheDescription
4242
}
@@ -56,6 +56,11 @@ struct CustomRules: Rule, CacheDescriptionProvider {
5656

5757
var configuration = CustomRulesConfiguration()
5858

59+
var isEffectivelySourceKitFree: Bool {
60+
// Just a stub, will be implemented in a follow-up PR
61+
false
62+
}
63+
5964
func validate(file: SwiftLintFile) -> [StyleViolation] {
6065
var configurations = configuration.customRuleConfigurations
6166

Source/swiftlint/Commands/Rules.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ private extension TextTable {
132132
configuredRule != nil ? "yes" : "no",
133133
ruleType.description.kind.rawValue,
134134
(rule is any AnalyzerRule) ? "yes" : "no",
135-
(rule is any SourceKitFreeRule) ? "no" : "yes",
135+
rule.requiresSourceKit ? "yes" : "no",
136136
truncate((defaultConfig ? rule : configuredRule ?? rule).createConfigurationDescription().oneLiner()),
137137
])
138138
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
@testable import SwiftLintCore
2+
@testable import SwiftLintFramework
3+
import XCTest
4+
5+
final class ConditionallySourceKitFreeTests: XCTestCase {
6+
// Mock rule for testing ConditionallySourceKitFree protocol
7+
private struct MockConditionalRule: Rule, ConditionallySourceKitFree {
8+
static let description = RuleDescription(
9+
identifier: "mock_conditional",
10+
name: "Mock Conditional Rule",
11+
description: "A mock rule for testing ConditionallySourceKitFree",
12+
kind: .style
13+
)
14+
15+
var configuration = SeverityConfiguration<Self>(.warning)
16+
var isEffectivelySourceKitFree = true
17+
18+
func validate(file _: SwiftLintFile) -> [StyleViolation] {
19+
[]
20+
}
21+
}
22+
23+
private struct MockSourceKitFreeRule: Rule, SourceKitFreeRule {
24+
static let description = RuleDescription(
25+
identifier: "mock_sourcekit_free",
26+
name: "Mock SourceKit Free Rule",
27+
description: "A mock rule that is always SourceKit-free",
28+
kind: .style
29+
)
30+
31+
var configuration = SeverityConfiguration<Self>(.warning)
32+
33+
func validate(file _: SwiftLintFile) -> [StyleViolation] {
34+
[]
35+
}
36+
}
37+
38+
private struct MockRegularRule: Rule {
39+
static let description = RuleDescription(
40+
identifier: "mock_regular",
41+
name: "Mock Regular Rule",
42+
description: "A mock rule that requires SourceKit",
43+
kind: .style
44+
)
45+
46+
var configuration = SeverityConfiguration<Self>(.warning)
47+
48+
func validate(file _: SwiftLintFile) -> [StyleViolation] {
49+
[]
50+
}
51+
}
52+
53+
func testRequiresSourceKitForDifferentRuleTypes() {
54+
// SourceKitFreeRule should not require SourceKit
55+
let sourceKitFreeRule = MockSourceKitFreeRule()
56+
XCTAssertFalse(sourceKitFreeRule.requiresSourceKit)
57+
58+
// ConditionallySourceKitFree rule that is effectively SourceKit-free
59+
var conditionalRuleFree = MockConditionalRule()
60+
conditionalRuleFree.isEffectivelySourceKitFree = true
61+
XCTAssertFalse(conditionalRuleFree.requiresSourceKit)
62+
63+
// ConditionallySourceKitFree rule that requires SourceKit
64+
var conditionalRuleRequires = MockConditionalRule()
65+
conditionalRuleRequires.isEffectivelySourceKitFree = false
66+
XCTAssertTrue(conditionalRuleRequires.requiresSourceKit)
67+
68+
// Regular rule should require SourceKit
69+
let regularRule = MockRegularRule()
70+
XCTAssertTrue(regularRule.requiresSourceKit)
71+
}
72+
73+
func testTypeCheckingBehavior() {
74+
// Verify instance-level checks work correctly
75+
let sourceKitFreeRule: any Rule = MockSourceKitFreeRule()
76+
XCTAssertTrue(sourceKitFreeRule is any SourceKitFreeRule)
77+
XCTAssertFalse(sourceKitFreeRule is any ConditionallySourceKitFree)
78+
79+
let conditionalRule: any Rule = MockConditionalRule()
80+
XCTAssertFalse(conditionalRule is any SourceKitFreeRule)
81+
XCTAssertTrue(conditionalRule is any ConditionallySourceKitFree)
82+
83+
let regularRule: any Rule = MockRegularRule()
84+
XCTAssertFalse(regularRule is any SourceKitFreeRule)
85+
XCTAssertFalse(regularRule is any ConditionallySourceKitFree)
86+
}
87+
}

0 commit comments

Comments
 (0)