mirror of
https://github.com/realm/SwiftLint.git
synced 2026-05-07 20:12:49 +00:00
Allow to set configuration elements as deprecated (#5540)
Automatically print an appropriate warning to the console.
This commit is contained in:
@@ -5,6 +5,9 @@ public enum Issue: LocalizedError, Equatable {
|
||||
/// The configuration didn't match internal expectations.
|
||||
case invalidConfiguration(ruleID: String)
|
||||
|
||||
/// Issued when an option is deprecated. Suggests an alternative optionally.
|
||||
case deprecatedConfigurationOption(ruleID: String, key: String, alternative: String? = nil)
|
||||
|
||||
/// Used in configuration parsing when no changes have been applied. Use only internally!
|
||||
case nothingApplied(ruleID: String)
|
||||
|
||||
@@ -71,6 +74,23 @@ public enum Issue: LocalizedError, Equatable {
|
||||
/// Flag to enable warnings for deprecations being printed to the console. Printing is enabled by default.
|
||||
public static var printDeprecationWarnings = true
|
||||
|
||||
/// Hook used to capture all messages normally printed to stdout and return them back to the caller.
|
||||
///
|
||||
/// > Warning: Shall only be used in tests to verify console output.
|
||||
///
|
||||
/// - parameter runner: The code to run. Messages printed during the execution are collected.
|
||||
///
|
||||
/// - returns: The collected messages produced while running the code in the runner.
|
||||
static func captureConsole(runner: () throws -> Void) rethrows -> String {
|
||||
var console = ""
|
||||
messageConsumer = { console += $0 }
|
||||
defer { messageConsumer = nil }
|
||||
try runner()
|
||||
return console
|
||||
}
|
||||
|
||||
private static var messageConsumer: ((String) -> Void)?
|
||||
|
||||
/// Wraps any `Error` into a `SwiftLintError.genericWarning` if it is not already a `SwiftLintError`.
|
||||
///
|
||||
/// - parameter error: Any `Error`.
|
||||
@@ -102,13 +122,23 @@ public enum Issue: LocalizedError, Equatable {
|
||||
if case .ruleDeprecated = self, !Self.printDeprecationWarnings {
|
||||
return
|
||||
}
|
||||
queuedPrintError(errorDescription)
|
||||
if let consumer = Self.messageConsumer {
|
||||
consumer(errorDescription)
|
||||
} else {
|
||||
queuedPrintError(errorDescription)
|
||||
}
|
||||
}
|
||||
|
||||
private var message: String {
|
||||
switch self {
|
||||
case let .invalidConfiguration(id):
|
||||
return "Invalid configuration for '\(id)' rule. Falling back to default."
|
||||
case let .deprecatedConfigurationOption(id, key, alternative):
|
||||
let baseMessage = "Configuration option '\(key)' in '\(id)' rule is deprecated."
|
||||
if let alternative {
|
||||
return baseMessage + " Use the option '\(alternative)' instead."
|
||||
}
|
||||
return baseMessage
|
||||
case let .nothingApplied(ruleID: id):
|
||||
return Self.invalidConfiguration(ruleID: id).message
|
||||
case let .listedMultipleTime(id, times):
|
||||
|
||||
@@ -415,9 +415,18 @@ public protocol InlinableOptionType: AcceptableByConfigurationElement {}
|
||||
///
|
||||
@propertyWrapper
|
||||
public struct ConfigurationElement<T: AcceptableByConfigurationElement & Equatable>: Equatable {
|
||||
/// A deprecation notice.
|
||||
public enum DeprecationNotice {
|
||||
/// Warning suggesting an alternative option.
|
||||
case suggestAlternative(ruleID: String, name: String)
|
||||
}
|
||||
|
||||
/// Wrapped option value.
|
||||
public var wrappedValue: T {
|
||||
didSet {
|
||||
if case let .suggestAlternative(id, name) = deprecationNotice {
|
||||
Issue.deprecatedConfigurationOption(ruleID: id, key: key, alternative: name).print()
|
||||
}
|
||||
if wrappedValue != oldValue {
|
||||
postprocessor(&wrappedValue)
|
||||
}
|
||||
@@ -437,6 +446,7 @@ public struct ConfigurationElement<T: AcceptableByConfigurationElement & Equatab
|
||||
/// Whether this configuration element will be inlined into its description.
|
||||
public let inline: Bool
|
||||
|
||||
private let deprecationNotice: DeprecationNotice?
|
||||
private let postprocessor: (inout T) -> Void
|
||||
|
||||
/// Default constructor.
|
||||
@@ -444,11 +454,20 @@ public struct ConfigurationElement<T: AcceptableByConfigurationElement & Equatab
|
||||
/// - Parameters:
|
||||
/// - value: Value to be wrapped.
|
||||
/// - key: Optional name of the option. If not specified, it will be inferred from the attributed property.
|
||||
/// - deprecationNotice: An optional deprecation notice in case an option is outdated and/or has been replaced by
|
||||
/// an alternative.
|
||||
/// - postprocessor: Function to be applied to the wrapped value after parsing to validate and modify it.
|
||||
public init(wrappedValue value: T,
|
||||
key: String,
|
||||
deprecationNotice: DeprecationNotice? = nil,
|
||||
postprocessor: @escaping (inout T) -> Void = { _ in }) {
|
||||
self.init(wrappedValue: value, key: key, inline: false, postprocessor: postprocessor)
|
||||
self.init(
|
||||
wrappedValue: value,
|
||||
key: key,
|
||||
inline: false,
|
||||
deprecationNotice: deprecationNotice,
|
||||
postprocessor: postprocessor
|
||||
)
|
||||
|
||||
// Modify the set value immediately.
|
||||
postprocessor(&wrappedValue)
|
||||
@@ -488,10 +507,12 @@ public struct ConfigurationElement<T: AcceptableByConfigurationElement & Equatab
|
||||
private init(wrappedValue: T,
|
||||
key: String,
|
||||
inline: Bool,
|
||||
deprecationNotice: DeprecationNotice? = nil,
|
||||
postprocessor: @escaping (inout T) -> Void = { _ in }) {
|
||||
self.wrappedValue = wrappedValue
|
||||
self.key = key
|
||||
self.inline = inline
|
||||
self.deprecationNotice = deprecationNotice
|
||||
self.postprocessor = postprocessor
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@testable import SwiftLintBuiltInRules
|
||||
@testable import SwiftLintCore
|
||||
import SwiftLintTestHelpers
|
||||
import XCTest
|
||||
|
||||
@@ -6,9 +7,12 @@ class IndentationWidthRuleTests: SwiftLintTestCase {
|
||||
func testInvalidIndentation() throws {
|
||||
var testee = IndentationWidthConfiguration()
|
||||
let defaultValue = testee.indentationWidth
|
||||
for indentation in [0, -1, -5] {
|
||||
try testee.apply(configuration: ["indentation_width": indentation])
|
||||
|
||||
for indentation in [0, -1, -5] {
|
||||
XCTAssertEqual(
|
||||
try Issue.captureConsole { try testee.apply(configuration: ["indentation_width": indentation]) },
|
||||
"warning: Invalid configuration for 'indentation_width' rule. Falling back to default."
|
||||
)
|
||||
// Value remains the default.
|
||||
XCTAssertEqual(testee.indentationWidth, defaultValue)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ class RuleConfigurationDescriptionTests: XCTestCase {
|
||||
postprocessor: { list in list = list.map { $0.uppercased() } }
|
||||
)
|
||||
var list = ["string1", "string2"]
|
||||
@ConfigurationElement(key: "set")
|
||||
@ConfigurationElement(key: "set", deprecationNotice: .suggestAlternative(ruleID: "my_rule", name: "other_opt"))
|
||||
var set: Set<Int> = [1, 2, 3]
|
||||
@ConfigurationElement(inline: true)
|
||||
var severityConfig = SeverityConfiguration<Parent>(.error)
|
||||
@@ -490,6 +490,15 @@ class RuleConfigurationDescriptionTests: XCTestCase {
|
||||
XCTAssertEqual(configuration.nestedSeverityLevels, SeverityLevelsConfiguration(warning: 6, error: 7))
|
||||
}
|
||||
|
||||
func testDeprecationWarning() throws {
|
||||
var configuration = TestConfiguration()
|
||||
|
||||
XCTAssertEqual(
|
||||
try Issue.captureConsole { try configuration.apply(configuration: ["set": [6, 7]]) },
|
||||
"warning: Configuration option 'set' in 'my_rule' rule is deprecated. Use the option 'other_opt' instead."
|
||||
)
|
||||
}
|
||||
|
||||
func testInvalidKeys() throws {
|
||||
var configuration = TestConfiguration()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user