Skip to content

Commit f6c9633

Browse files
authored
Add RegexConfiguration.ExecutionMode (#6128)
* Add `RegexConfiguration.ExecutionMode` To help migrate custom rules to SwiftSyntax. Not wired up yet, just the configuration parsing and defaults. Will wire it up in the next PR. The diff looks big, but it's 500+ lines of tests, with ~45 lines of actually new code. * Docs * Address PR feedback - Add `default` case to ExecutionMode enum instead of using optional - Change configuration key from `mode` to `execution_mode` for consistency - Move default execution mode logic to runtime instead of configuration time - Refactor test functions to use throws instead of do-catch
1 parent c22de52 commit f6c9633

File tree

3 files changed

+545
-5
lines changed

3 files changed

+545
-5
lines changed

Source/SwiftLintCore/RuleConfigurations/RegexConfiguration.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ import SourceKittenFramework
44
/// A rule configuration used for defining custom rules in yaml.
55
public struct RegexConfiguration<Parent: Rule>: SeverityBasedRuleConfiguration, Hashable,
66
CacheDescriptionProvider, InlinableOptionType {
7+
/// The execution mode for this custom rule.
8+
public enum ExecutionMode: String, Codable, Sendable {
9+
/// Uses SwiftSyntax to obtain syntax token kinds.
10+
case swiftsyntax
11+
/// Uses SourceKit to obtain syntax token kinds.
12+
case sourcekit
13+
/// Uses SwiftSyntax by default unless overridden to use SourceKit.
14+
case `default`
15+
}
716
/// The identifier for this custom rule.
817
public let identifier: String
918
/// The name for this custom rule.
@@ -24,6 +33,8 @@ public struct RegexConfiguration<Parent: Rule>: SeverityBasedRuleConfiguration,
2433
public var severityConfiguration = SeverityConfiguration<Parent>(.warning)
2534
/// The index of the regex capture group to match.
2635
public var captureGroup = 0
36+
/// The execution mode for this rule.
37+
public var executionMode: ExecutionMode = .default
2738

2839
public var cacheDescription: String {
2940
let jsonObject: [String] = [
@@ -36,6 +47,7 @@ public struct RegexConfiguration<Parent: Rule>: SeverityBasedRuleConfiguration,
3647
SyntaxKind.allKinds.subtracting(excludedMatchKinds)
3748
.map(\.rawValue).sorted(by: <).joined(separator: ","),
3849
severity.rawValue,
50+
executionMode.rawValue,
3951
]
4052
if let jsonData = try? JSONSerialization.data(withJSONObject: jsonObject),
4153
let jsonString = String(data: jsonData, encoding: .utf8) {
@@ -57,6 +69,7 @@ public struct RegexConfiguration<Parent: Rule>: SeverityBasedRuleConfiguration,
5769
self.identifier = identifier
5870
}
5971

72+
// swiftlint:disable:next cyclomatic_complexity
6073
public mutating func apply(configuration: Any) throws {
6174
guard let configurationDict = configuration as? [String: Any],
6275
let regexString = configurationDict[$regex.key] as? String else {
@@ -97,11 +110,19 @@ public struct RegexConfiguration<Parent: Rule>: SeverityBasedRuleConfiguration,
97110
self.captureGroup = captureGroup
98111
}
99112

113+
if let modeString = configurationDict["execution_mode"] as? String {
114+
guard let mode = ExecutionMode(rawValue: modeString) else {
115+
throw Issue.invalidConfiguration(ruleID: Parent.identifier)
116+
}
117+
self.executionMode = mode
118+
}
119+
100120
self.excludedMatchKinds = try self.excludedMatchKinds(from: configurationDict)
101121
}
102122

103123
public func hash(into hasher: inout Hasher) {
104124
hasher.combine(identifier)
125+
hasher.combine(executionMode)
105126
}
106127

107128
package func shouldValidate(filePath: String) -> Bool {

Source/SwiftLintFramework/Rules/CustomRules.swift

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,33 @@ struct CustomRulesConfiguration: RuleConfiguration, CacheDescriptionProvider {
77

88
var parameterDescription: RuleConfigurationDescription? { RuleConfigurationOption.noOptions }
99
var cacheDescription: String {
10-
customRuleConfigurations
10+
let configsDescription = customRuleConfigurations
1111
.sorted { $0.identifier < $1.identifier }
1212
.map(\.cacheDescription)
1313
.joined(separator: "\n")
14+
15+
if let defaultMode = defaultExecutionMode {
16+
return "default_execution_mode:\(defaultMode.rawValue)\n\(configsDescription)"
17+
}
18+
return configsDescription
1419
}
1520
var customRuleConfigurations = [RegexConfiguration<Parent>]()
21+
var defaultExecutionMode: RegexConfiguration<Parent>.ExecutionMode?
1622

1723
mutating func apply(configuration: Any) throws {
1824
guard let configurationDict = configuration as? [String: Any] else {
1925
throw Issue.invalidConfiguration(ruleID: Parent.identifier)
2026
}
2127

22-
for (key, value) in configurationDict {
28+
// Parse default execution mode if present
29+
if let defaultModeString = configurationDict["default_execution_mode"] as? String {
30+
guard let mode = RegexConfiguration<Parent>.ExecutionMode(rawValue: defaultModeString) else {
31+
throw Issue.invalidConfiguration(ruleID: Parent.identifier)
32+
}
33+
defaultExecutionMode = mode
34+
}
35+
36+
for (key, value) in configurationDict where key != "default_execution_mode" {
2337
var ruleConfiguration = RegexConfiguration<Parent>(identifier: key)
2438

2539
do {
@@ -50,15 +64,21 @@ struct CustomRules: Rule, CacheDescriptionProvider, ConditionallySourceKitFree {
5064
name: "Custom Rules",
5165
description: """
5266
Create custom rules by providing a regex string. Optionally specify what syntax kinds to match against, \
53-
the severity level, and what message to display.
67+
the severity level, and what message to display. Rules default to SwiftSyntax mode for improved \
68+
performance. Use `execution_mode: sourcekit` or `default_execution_mode: sourcekit` for SourceKit mode.
5469
""",
5570
kind: .style)
5671

5772
var configuration = CustomRulesConfiguration()
5873

74+
/// Returns true if all configured custom rules use SwiftSyntax mode, making this rule effectively SourceKit-free.
5975
var isEffectivelySourceKitFree: Bool {
60-
// Just a stub, will be implemented in a follow-up PR
61-
false
76+
configuration.customRuleConfigurations.allSatisfy { config in
77+
let effectiveMode = config.executionMode == .default
78+
? (configuration.defaultExecutionMode ?? .swiftsyntax)
79+
: config.executionMode
80+
return effectiveMode == .swiftsyntax
81+
}
6282
}
6383

6484
func validate(file: SwiftLintFile) -> [StyleViolation] {

0 commit comments

Comments
 (0)