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
21 changes: 21 additions & 0 deletions Source/SwiftLintCore/RuleConfigurations/RegexConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ import SourceKittenFramework
/// A rule configuration used for defining custom rules in yaml.
public struct RegexConfiguration<Parent: Rule>: SeverityBasedRuleConfiguration, Hashable,
CacheDescriptionProvider, InlinableOptionType {
/// The execution mode for this custom rule.
public enum ExecutionMode: String, Codable, Sendable {
/// Uses SwiftSyntax to obtain syntax token kinds.
case swiftsyntax
/// Uses SourceKit to obtain syntax token kinds.
case sourcekit
/// Uses SwiftSyntax by default unless overridden to use SourceKit.
case `default`
}
/// The identifier for this custom rule.
public let identifier: String
/// The name for this custom rule.
Expand All @@ -24,6 +33,8 @@ public struct RegexConfiguration<Parent: Rule>: SeverityBasedRuleConfiguration,
public var severityConfiguration = SeverityConfiguration<Parent>(.warning)
/// The index of the regex capture group to match.
public var captureGroup = 0
/// The execution mode for this rule.
public var executionMode: ExecutionMode = .default

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

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

if let modeString = configurationDict["execution_mode"] as? String {
guard let mode = ExecutionMode(rawValue: modeString) else {
throw Issue.invalidConfiguration(ruleID: Parent.identifier)
}
self.executionMode = mode
}

self.excludedMatchKinds = try self.excludedMatchKinds(from: configurationDict)
}

public func hash(into hasher: inout Hasher) {
hasher.combine(identifier)
hasher.combine(executionMode)
}

package func shouldValidate(filePath: String) -> Bool {
Expand Down
30 changes: 25 additions & 5 deletions Source/SwiftLintFramework/Rules/CustomRules.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,33 @@ struct CustomRulesConfiguration: RuleConfiguration, CacheDescriptionProvider {

var parameterDescription: RuleConfigurationDescription? { RuleConfigurationOption.noOptions }
var cacheDescription: String {
customRuleConfigurations
let configsDescription = customRuleConfigurations
.sorted { $0.identifier < $1.identifier }
.map(\.cacheDescription)
.joined(separator: "\n")

if let defaultMode = defaultExecutionMode {
return "default_execution_mode:\(defaultMode.rawValue)\n\(configsDescription)"
}
return configsDescription
}
var customRuleConfigurations = [RegexConfiguration<Parent>]()
var defaultExecutionMode: RegexConfiguration<Parent>.ExecutionMode?

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

for (key, value) in configurationDict {
// Parse default execution mode if present
if let defaultModeString = configurationDict["default_execution_mode"] as? String {
guard let mode = RegexConfiguration<Parent>.ExecutionMode(rawValue: defaultModeString) else {
throw Issue.invalidConfiguration(ruleID: Parent.identifier)
}
defaultExecutionMode = mode
}

for (key, value) in configurationDict where key != "default_execution_mode" {
var ruleConfiguration = RegexConfiguration<Parent>(identifier: key)

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

var configuration = CustomRulesConfiguration()

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

func validate(file: SwiftLintFile) -> [StyleViolation] {
Expand Down
Loading