Introduce a "rule registry" concept
This will allow for registering rules that aren't compiled as part of SwiftLintFramework. Specifically this will allow us to split the built-in and extra rules into separate modules, leading to faster incremental compilation when working on rules since the rest of the framework won't need to be rebuilt on every compilation.
This commit is contained in:
parent
3f52acd0a2
commit
165172e0fa
|
@ -3,6 +3,3 @@
|
|||
let builtInRules: [Rule.Type] = [
|
||||
{% for rule in types.structs where rule.name|hasSuffix:"Rule" or rule.name|hasSuffix:"Rules" %} {{ rule.name }}.self{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}]
|
||||
|
||||
/// The rule list containing all available rules built into SwiftLint as well as native custom rules.
|
||||
public let primaryRuleList = RuleList(rules: builtInRules + extraRules())
|
8
Makefile
8
Makefile
|
@ -30,11 +30,11 @@ VERSION_STRING=$(shell ./tools/get-version)
|
|||
|
||||
all: build
|
||||
|
||||
sourcery: Source/SwiftLintFramework/Models/PrimaryRuleList.swift Source/SwiftLintFramework/Models/ReportersList.swift Tests/GeneratedTests/GeneratedTests.swift
|
||||
sourcery: Source/SwiftLintFramework/Models/BuiltInRules.swift Source/SwiftLintFramework/Models/ReportersList.swift Tests/GeneratedTests/GeneratedTests.swift
|
||||
|
||||
Source/SwiftLintFramework/Models/PrimaryRuleList.swift: Source/SwiftLintFramework/Rules/**/*.swift .sourcery/PrimaryRuleList.stencil
|
||||
./tools/sourcery --sources Source/SwiftLintFramework/Rules --templates .sourcery/PrimaryRuleList.stencil --output .sourcery
|
||||
mv .sourcery/PrimaryRuleList.generated.swift Source/SwiftLintFramework/Models/PrimaryRuleList.swift
|
||||
Source/SwiftLintFramework/Models/BuiltInRules.swift: Source/SwiftLintFramework/Rules/**/*.swift .sourcery/BuiltInRules.stencil
|
||||
./tools/sourcery --sources Source/SwiftLintFramework/Rules --templates .sourcery/BuiltInRules.stencil --output .sourcery
|
||||
mv .sourcery/BuiltInRules.generated.swift Source/SwiftLintFramework/Models/BuiltInRules.swift
|
||||
|
||||
Source/SwiftLintFramework/Models/ReportersList.swift: Source/SwiftLintFramework/Reporters/*.swift .sourcery/ReportersList.stencil
|
||||
./tools/sourcery --sources Source/SwiftLintFramework/Reporters --templates .sourcery/ReportersList.stencil --output .sourcery
|
||||
|
|
|
@ -39,7 +39,7 @@ extension Configuration {
|
|||
/// - parameter cachePath: The location of the persisted cache on disk.
|
||||
public init(
|
||||
dict: [String: Any],
|
||||
ruleList: RuleList = primaryRuleList,
|
||||
ruleList: RuleList = RuleRegistry.shared.list,
|
||||
enableAllRules: Bool = false,
|
||||
cachePath: String? = nil
|
||||
) throws {
|
||||
|
|
|
@ -64,7 +64,7 @@ public extension Configuration {
|
|||
|
||||
let effectiveOptInRules: [String]
|
||||
if optInRules.contains(RuleIdentifier.all.stringRepresentation) {
|
||||
let allOptInRules = primaryRuleList.list.compactMap { ruleID, ruleType in
|
||||
let allOptInRules = RuleRegistry.shared.list.list.compactMap { ruleID, ruleType in
|
||||
ruleType is OptInRule.Type && !(ruleType is AnalyzerRule.Type) ? ruleID : nil
|
||||
}
|
||||
effectiveOptInRules = Array(Set(allOptInRules + optInRules))
|
||||
|
|
|
@ -231,6 +231,3 @@ let builtInRules: [Rule.Type] = [
|
|||
XCTSpecificMatcherRule.self,
|
||||
YodaConditionRule.self
|
||||
]
|
||||
|
||||
/// The rule list containing all available rules built into SwiftLint as well as native custom rules.
|
||||
public let primaryRuleList = RuleList(rules: builtInRules + extraRules())
|
|
@ -121,7 +121,7 @@ public struct Configuration {
|
|||
public init(
|
||||
rulesMode: RulesMode = .default(disabled: [], optIn: []),
|
||||
allRulesWrapped: [ConfigurationRuleWrapper]? = nil,
|
||||
ruleList: RuleList = primaryRuleList,
|
||||
ruleList: RuleList = RuleRegistry.shared.list,
|
||||
fileGraph: FileGraph? = nil,
|
||||
includedPaths: [String] = [],
|
||||
excludedPaths: [String] = [],
|
||||
|
|
|
@ -330,7 +330,7 @@ public struct CollectedLinter {
|
|||
let allCustomIdentifiers =
|
||||
(configuration.rules.first { $0 is CustomRules } as? CustomRules)?
|
||||
.configuration.customRuleConfigurations.map { RuleIdentifier($0.identifier) } ?? []
|
||||
let allRuleIdentifiers = primaryRuleList.allValidIdentifiers().map { RuleIdentifier($0) }
|
||||
let allRuleIdentifiers = RuleRegistry.shared.list.allValidIdentifiers().map { RuleIdentifier($0) }
|
||||
let allValidIdentifiers = Set(allCustomIdentifiers + allRuleIdentifiers + [.all])
|
||||
let superfluousRuleIdentifier = RuleIdentifier(SuperfluousDisableCommandRule.description.identifier)
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
private let _registerAllRulesOnceImpl: Void = {
|
||||
RuleRegistry.shared.register(rules: builtInRules + extraRules())
|
||||
}()
|
||||
|
||||
/// Container to register and look up SwiftLint rules.
|
||||
public final class RuleRegistry {
|
||||
private var registeredRules = [Rule.Type]()
|
||||
|
||||
/// Shared rule registry instance.
|
||||
public static let shared = RuleRegistry()
|
||||
|
||||
/// Rule list associated with this registry. Lazily created, and
|
||||
/// immutable once looked up.
|
||||
///
|
||||
/// - note: Adding registering more rules after this was first
|
||||
/// accessed will not work.
|
||||
public private(set) lazy var list = RuleList(rules: registeredRules)
|
||||
|
||||
private init() {}
|
||||
|
||||
/// Register rules.
|
||||
///
|
||||
/// - parameter rules: The rules to register.
|
||||
public func register(rules: [Rule.Type]) {
|
||||
registeredRules.append(contentsOf: rules)
|
||||
}
|
||||
|
||||
/// Look up a rule for a given ID.
|
||||
///
|
||||
/// - parameter id: The ID for the rule to look up.
|
||||
///
|
||||
/// - returns: The rule matching the specified ID, if one was found.
|
||||
public func rule(forID id: String) -> Rule.Type? {
|
||||
return list.list[id]
|
||||
}
|
||||
|
||||
/// Register all rules. Should only be called once before any SwiftLint code is executed.
|
||||
public static func registerAllRulesOnce() {
|
||||
_ = _registerAllRulesOnceImpl
|
||||
}
|
||||
}
|
|
@ -56,7 +56,7 @@ private extension TextTable {
|
|||
continue
|
||||
}
|
||||
|
||||
let rule = primaryRuleList.list[ruleIdentifier]
|
||||
let rule = RuleRegistry.shared.rule(forID: ruleIdentifier)
|
||||
let violations = ruleIdentifiersToViolationsMap[ruleIdentifier]
|
||||
let numberOfWarnings = violations?.filter { $0.severity == .warning }.count ?? 0
|
||||
let numberOfErrors = violations?.filter { $0.severity == .error }.count ?? 0
|
||||
|
|
|
@ -14,7 +14,7 @@ extension SwiftLint {
|
|||
func run() throws {
|
||||
var subPage = ""
|
||||
if let ruleID {
|
||||
if primaryRuleList.list[ruleID] == nil {
|
||||
if RuleRegistry.shared.rule(forID: ruleID) == nil {
|
||||
queuedPrintError("There is no rule named '\(ruleID)'. Opening rule directory instead.")
|
||||
subPage = "rule-directory.html"
|
||||
} else {
|
||||
|
|
|
@ -26,7 +26,7 @@ extension SwiftLint {
|
|||
|
||||
func run() throws {
|
||||
if let ruleID {
|
||||
guard let rule = primaryRuleList.list[ruleID] else {
|
||||
guard let rule = RuleRegistry.shared.rule(forID: ruleID) else {
|
||||
throw SwiftLintError.usageError(description: "No rule with identifier: \(ruleID)")
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import ArgumentParser
|
||||
import Foundation
|
||||
import SwiftLintFramework
|
||||
|
||||
@main
|
||||
struct SwiftLint: AsyncParsableCommand {
|
||||
|
@ -8,6 +9,8 @@ struct SwiftLint: AsyncParsableCommand {
|
|||
FileManager.default.changeCurrentDirectoryPath(directory)
|
||||
}
|
||||
|
||||
RuleRegistry.registerAllRulesOnce()
|
||||
|
||||
return CommandConfiguration(
|
||||
commandName: "swiftlint",
|
||||
abstract: "A tool to enforce Swift style and conventions.",
|
||||
|
|
|
@ -15,7 +15,7 @@ class RulesFilter {
|
|||
private let allRules: RuleList
|
||||
private let enabledRules: [Rule]
|
||||
|
||||
init(allRules: RuleList = primaryRuleList, enabledRules: [Rule]) {
|
||||
init(allRules: RuleList = RuleRegistry.shared.list, enabledRules: [Rule]) {
|
||||
self.allRules = allRules
|
||||
self.enabledRules = enabledRules
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@ extension ConfigurationTests {
|
|||
func testWarningThresholdMerging() {
|
||||
func configuration(forWarningThreshold warningThreshold: Int?) -> Configuration {
|
||||
return Configuration(
|
||||
ruleList: primaryRuleList,
|
||||
warningThreshold: warningThreshold,
|
||||
reporter: XcodeReporter.identifier
|
||||
)
|
||||
|
|
|
@ -6,7 +6,7 @@ import XCTest
|
|||
|
||||
// swiftlint:disable file_length
|
||||
|
||||
private let optInRules = primaryRuleList.list.filter({ $0.1.init() is OptInRule }).map({ $0.0 })
|
||||
private let optInRules = RuleRegistry.shared.list.list.filter({ $0.1.init() is OptInRule }).map({ $0.0 })
|
||||
|
||||
class ConfigurationTests: XCTestCase {
|
||||
// MARK: Setup & Teardown
|
||||
|
@ -74,12 +74,11 @@ class ConfigurationTests: XCTestCase {
|
|||
func testEnableAllRulesConfiguration() throws {
|
||||
let configuration = try Configuration(
|
||||
dict: [:],
|
||||
ruleList: primaryRuleList,
|
||||
enableAllRules: true,
|
||||
cachePath: nil
|
||||
)
|
||||
|
||||
XCTAssertEqual(configuration.rules.count, primaryRuleList.list.count)
|
||||
XCTAssertEqual(configuration.rules.count, RuleRegistry.shared.list.list.count)
|
||||
}
|
||||
|
||||
func testOnlyRules() throws {
|
||||
|
@ -146,7 +145,7 @@ class ConfigurationTests: XCTestCase {
|
|||
XCTAssertEqual(disabledConfig.rulesWrapper.disabledRuleIdentifiers,
|
||||
["nesting", "todo"],
|
||||
"initializing Configuration with valid rules in Dictionary should succeed")
|
||||
let expectedIdentifiers = Set(primaryRuleList.list.keys
|
||||
let expectedIdentifiers = Set(RuleRegistry.shared.list.list.keys
|
||||
.filter({ !(["nesting", "todo"] + optInRules).contains($0) }))
|
||||
let configuredIdentifiers = Set(disabledConfig.rules.map {
|
||||
type(of: $0).description.identifier
|
||||
|
@ -163,7 +162,7 @@ class ConfigurationTests: XCTestCase {
|
|||
XCTAssertEqual(configuration.rulesWrapper.disabledRuleIdentifiers,
|
||||
[validRule],
|
||||
"initializing Configuration with valid rules in YAML string should succeed")
|
||||
let expectedIdentifiers = Set(primaryRuleList.list.keys
|
||||
let expectedIdentifiers = Set(RuleRegistry.shared.list.list.keys
|
||||
.filter({ !([validRule] + optInRules).contains($0) }))
|
||||
let configuredIdentifiers = Set(configuration.rules.map {
|
||||
type(of: $0).description.identifier
|
||||
|
|
|
@ -48,7 +48,7 @@ public extension String {
|
|||
}
|
||||
}
|
||||
|
||||
public let allRuleIdentifiers = Set(primaryRuleList.list.keys)
|
||||
public let allRuleIdentifiers = Set(RuleRegistry.shared.list.list.keys)
|
||||
|
||||
public extension Configuration {
|
||||
func applyingConfiguration(from example: Example) -> Configuration {
|
||||
|
@ -272,7 +272,7 @@ public func makeConfig(_ ruleConfiguration: Any?, _ identifier: String,
|
|||
let identifiers: Set<String> = skipDisableCommandTests ? [identifier]
|
||||
: [identifier, superfluousDisableCommandRuleIdentifier]
|
||||
|
||||
if let ruleConfiguration, let ruleType = primaryRuleList.list[identifier] {
|
||||
if let ruleConfiguration, let ruleType = RuleRegistry.shared.rule(forID: identifier) {
|
||||
// The caller has provided a custom configuration for the rule under test
|
||||
return (try? ruleType.init(configuration: ruleConfiguration)).flatMap { configuredRule in
|
||||
let rules = skipDisableCommandTests ? [configuredRule] : [configuredRule, SuperfluousDisableCommandRule()]
|
||||
|
@ -330,6 +330,7 @@ public extension XCTestCase {
|
|||
testShebang: Bool = true,
|
||||
file: StaticString = #file,
|
||||
line: UInt = #line) {
|
||||
RuleRegistry.registerAllRulesOnce()
|
||||
guard ruleDescription.minSwiftVersion <= .current else {
|
||||
return
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue