Allow to infer option names (#5505)

This allows to infer names of options from their names in a configuration. CamelCase is translated into snake_case automatically when `apply` is triggered.

* Don't have all `RuleConfiguration`s conform to `InlinableOptionType`. Mark types that must have this capability explicitly. Same for `AcceptableByConfigurationElement`.
* A type being an `InlinableOptionType` doesn't mean it's automatically inlined. This also doesn't depend on the fact of having a name for its key configured any longer. Instead, an `inline` attribute must explicitly be set to `true` in `@ConfigurationElement`.
* Key name inference is optional and can be overwritten by specifying a key name in the attribute.
* Inlined configurations only fail in `apply` when they are really sure that something is odd. Otherwise, they accept to not being updated.
This commit is contained in:
Danny Mösch
2024-03-23 18:54:39 +01:00
committed by GitHub
parent 082adfaed7
commit 82cad0bfff
19 changed files with 214 additions and 121 deletions
@@ -5,7 +5,7 @@ import SwiftLintCore
struct CyclomaticComplexityConfiguration: RuleConfiguration {
typealias Parent = CyclomaticComplexityRule
@ConfigurationElement
@ConfigurationElement(inline: true)
private(set) var length = SeverityLevelsConfiguration<Parent>(warning: 10, error: 20)
@ConfigurationElement(key: "ignores_case_statements")
private(set) var ignoresCaseStatements = false
@@ -4,7 +4,7 @@ import SwiftLintCore
struct FileLengthConfiguration: RuleConfiguration {
typealias Parent = FileLengthRule
@ConfigurationElement
@ConfigurationElement(inline: true)
private(set) var severityConfiguration = SeverityLevelsConfiguration<Parent>(warning: 400, error: 1000)
@ConfigurationElement(key: "ignore_comment_only_lines")
private(set) var ignoreCommentOnlyLines = false
@@ -4,7 +4,7 @@ import SwiftLintCore
struct FunctionParameterCountConfiguration: RuleConfiguration {
typealias Parent = FunctionParameterCountRule
@ConfigurationElement
@ConfigurationElement(inline: true)
private(set) var severityConfiguration = SeverityLevelsConfiguration<Parent>(warning: 5, error: 8)
@ConfigurationElement(key: "ignores_default_parameters")
private(set) var ignoresDefaultParameters = true
@@ -6,7 +6,7 @@ struct IdentifierNameConfiguration: RuleConfiguration {
private static let defaultOperators = ["/", "=", "-", "+", "!", "*", "|", "^", "~", "?", ".", "%", "<", ">", "&"]
@ConfigurationElement
@ConfigurationElement(inline: true)
private(set) var nameConfiguration = NameConfiguration<Parent>(minLengthWarning: 3,
minLengthError: 2,
maxLengthWarning: 40,
@@ -4,7 +4,7 @@ import SwiftLintCore
struct LineLengthConfiguration: RuleConfiguration {
typealias Parent = LineLengthRule
@ConfigurationElement
@ConfigurationElement(inline: true)
private(set) var length = SeverityLevelsConfiguration<Parent>(warning: 120, error: 200)
@ConfigurationElement(key: "ignores_urls")
private(set) var ignoresURLs = false
@@ -1,7 +1,7 @@
import Foundation
import SwiftLintCore
struct NameConfiguration<Parent: Rule>: RuleConfiguration {
struct NameConfiguration<Parent: Rule>: RuleConfiguration, InlinableOptionType {
typealias Severity = SeverityConfiguration<Parent>
typealias SeverityLevels = SeverityLevelsConfiguration<Parent>
typealias StartWithLowercaseConfiguration = ChildOptionSeverityConfiguration<Parent>
@@ -4,7 +4,7 @@ import SwiftLintCore
struct TypeNameConfiguration: RuleConfiguration {
typealias Parent = TypeNameRule
@ConfigurationElement
@ConfigurationElement(inline: true)
private(set) var nameConfiguration = NameConfiguration<Parent>(minLengthWarning: 3,
minLengthError: 0,
maxLengthWarning: 40,
@@ -1,6 +1,6 @@
/// A rule configuration that allows to disable (`off`) an option of a rule or specify its severity level in which
/// case it's active.
public struct ChildOptionSeverityConfiguration<Parent: Rule>: RuleConfiguration {
public struct ChildOptionSeverityConfiguration<Parent: Rule>: RuleConfiguration, AcceptableByConfigurationElement {
/// Configuration with a warning severity.
public static var error: Self { Self(optionSeverity: .error) }
/// Configuration with an error severity.
@@ -332,7 +332,7 @@ public protocol AcceptableByConfigurationElement {
func asDescription(with key: String) -> RuleConfigurationDescription
/// Update the object.
///
///
/// - Parameter value: New underlying data for the object.
mutating func apply(_ value: Any?, ruleID: String) throws
}
@@ -350,45 +350,69 @@ public extension AcceptableByConfigurationElement {
}
}
/// An option type that does not need a key when used in a ``ConfigurationElement``. Its value will be inlined.
/// An option type that can appear inlined into its using configuration.
///
/// The ``ConfigurationElement`` must opt into this behavior. In this case, the option does not have a key. This is
/// almost exclusively useful for common ``RuleConfiguration``s that are used in many other rules as child
/// configurations.
///
/// > Warning: A type conforming to this protocol is assumed to throw an issue in its `apply` method only when it's
/// absolutely clear that there is an error in the YAML configuration passed in. Since it may be used in a nested
/// context and doesn't know about the outer configuration, it's not always clear if a certain key-value is really
/// unacceptable.
public protocol InlinableOptionType: AcceptableByConfigurationElement {}
/// A single parameter of a rule configuration.
///
/// Apply it to a simple (e.g. boolean) property like
/// ```swift
/// @ConfigurationElement(key: "name")
/// @ConfigurationElement
/// var property = true
/// ```
/// If the wrapped element is an ``InlinableOptionType``, there are two options for its representation
/// in the documentation:
/// to add a (boolean) option to a configuration. The name of the option will be inferred from the name of the property.
/// In this case, it's just `property`. CamelCase names will translated into snake_case, i.e. `myOption` is going to be
/// translated into `my_option` in the `.swiftlint.yml` configuration file.
///
/// 1. It can be inlined into the parent configuration. For that, do not provide a name as an argument. E.g.
/// This mechanism may be overwritten with an explicitly set key:
/// ```swift
/// @ConfigurationElement(key: "foo_bar")
/// var property = true
/// ```
///
/// If the wrapped element is an ``InlinableOptionType``, there are three ways to represent it in the documentation:
///
/// 1. It can be inlined into the parent configuration. For that, add the parameter `inline: true`. E.g.
/// ```swift
/// @ConfigurationElement(key: "name")
/// var property = true
/// @ConfigurationElement
/// @ConfigurationElement(inline: true)
/// var levels = SeverityLevelsConfiguration(warning: 1, error: 2)
/// ```
/// will be documented as a linear list:
/// ```
/// name: true
/// warning: 1
/// error: 2
/// ```
/// 2. It can be represented as a separate nested configuration. In this case, it must have a name. E.g.
/// 2. It can be represented as a separate nested configuration. In this case, it must not have set the `inline` flag to
/// `true`. E.g.
/// ```swift
/// @ConfigurationElement(key: "name")
/// var property = true
/// @ConfigurationElement(key: "levels")
/// @ConfigurationElement
/// var levels = SeverityLevelsConfiguration(warning: 1, error: 2)
/// ```
/// will have a nested configuration section:
/// ```
/// name: true
/// levels: warning: 1
/// error: 2
/// ```
/// 3. As mentioned in the beginning, the implict key inference meachnism can be overruled by specifying a `key` as in:
/// ```swift
/// @ConfigurationElement(key: "foo")
/// var levels = SeverityLevelsConfiguration(warning: 1, error: 2)
/// ```
/// It will appear in the documentation as:
/// ```
/// foo: warning: 1
/// error: 2
/// ```
///
@propertyWrapper
public struct ConfigurationElement<T: AcceptableByConfigurationElement & Equatable>: Equatable {
/// Wrapped option value.
@@ -402,7 +426,10 @@ public struct ConfigurationElement<T: AcceptableByConfigurationElement & Equatab
}
/// Name of this configuration entry.
public let key: String
public var key: String
/// Whether this configuration element will be inlined into its description.
public let inline: Bool
private let postprocessor: (inout T) throws -> Void
@@ -410,12 +437,12 @@ public struct ConfigurationElement<T: AcceptableByConfigurationElement & Equatab
///
/// - Parameters:
/// - value: Value to be wrapped.
/// - key: Name of the option.
/// - key: Optional name of the option. If not specified, it will be inferred from the attributed property.
/// - postprocessor: Function to be applied to the wrapped value after parsing to validate and modify it.
public init(wrappedValue value: T, key: String, postprocessor: @escaping (inout T) throws -> Void = { _ in }) {
self.wrappedValue = value
self.key = key
self.postprocessor = postprocessor
public init(wrappedValue value: T,
key: String = "",
postprocessor: @escaping (inout T) throws -> Void = { _ in }) {
self.init(wrappedValue: value, key: key, inline: false, postprocessor: postprocessor)
// Validate and modify the set value immediately. An exception means invalid defaults.
try! performAfterParseOperations() // swiftlint:disable:this force_try
@@ -423,22 +450,42 @@ public struct ConfigurationElement<T: AcceptableByConfigurationElement & Equatab
/// Constructor for optional values.
///
/// It allows to skip explicit initialization with `nil` of the property.
/// It allows to skip explicit initialization of the property with `nil`.
///
/// - Parameter value: Value to be wrapped.
public init<Wrapped>(key: String) where T == Wrapped? {
self.init(wrappedValue: nil, key: key)
/// - Parameters:
/// - key: Optional name of the option. If not specified, it will be inferred from the attributed property.
public init<Wrapped>(key: String = "") where T == Wrapped? {
self.init(wrappedValue: nil, key: key, inline: false)
}
/// Constructor for a ``ConfigurationElement`` without a key.
///
/// ``InlinableOptionType``s are allowed to have an empty key. The configuration will be inlined into its
/// parent configuration in this specific case.
/// Constructor for an ``InlinableOptionType`` without a key.
///
/// - Parameters:
/// - value: Value to be wrapped.
public init(wrappedValue value: T) where T: InlinableOptionType {
self.init(wrappedValue: value, key: "")
/// - inline: If `true`, the option will be handled as it would be part of its parent. All of its options
/// will be inlined. Otherwise, it will be treated as a normal nested configuration with its name
/// inferred from the name of the attributed property.
public init(wrappedValue value: T, inline: Bool) where T: InlinableOptionType {
self.init(wrappedValue: value, key: "", inline: inline)
}
/// Constructor for an ``InlinableOptionType`` with a name. The configuration will explicitly not be inlined.
///
/// - Parameters:
/// - value: Value to be wrapped.
/// - key: Name of the option.
public init(wrappedValue value: T, key: String) where T: InlinableOptionType {
self.init(wrappedValue: value, key: key, inline: false)
}
private init(wrappedValue: T,
key: String,
inline: Bool,
postprocessor: @escaping (inout T) throws -> Void = { _ in }) {
self.wrappedValue = wrappedValue
self.key = key
self.inline = inline
self.postprocessor = postprocessor
}
/// Run operations to validate and modify the parsed value.
@@ -575,7 +622,7 @@ extension RegularExpression: AcceptableByConfigurationElement {
// MARK: RuleConfiguration conformances
public extension RuleConfiguration {
public extension AcceptableByConfigurationElement where Self: RuleConfiguration {
func asOption() -> OptionType {
.nested(.from(configuration: self))
}
@@ -1,5 +1,5 @@
/// A rule configuration that allows specifying the desired severity level for violations.
public struct SeverityConfiguration<Parent: Rule>: SeverityBasedRuleConfiguration {
public struct SeverityConfiguration<Parent: Rule>: SeverityBasedRuleConfiguration, InlinableOptionType {
/// Configuration with a warning severity.
public static var error: Self { Self(.error) }
/// Configuration with an error severity.
@@ -22,10 +22,12 @@ public struct SeverityConfiguration<Parent: Rule>: SeverityBasedRuleConfiguratio
public mutating func apply(configuration: Any) throws {
let configString = configuration as? String
let configDict = configuration as? [String: Any]
guard let severityString: String = configString ?? configDict?[$severity.key] as? String,
let severity = ViolationSeverity(rawValue: severityString.lowercased()) else {
throw Issue.unknownConfiguration(ruleID: Parent.description.identifier)
if let severityString: String = configString ?? configDict?[$severity.key] as? String {
if let severity = ViolationSeverity(rawValue: severityString.lowercased()) {
self.severity = severity
} else {
throw Issue.unknownConfiguration(ruleID: Parent.description.identifier)
}
}
self.severity = severity
}
}
@@ -1,5 +1,5 @@
/// A configuration value for a rule to allow users to modify its behavior.
public protocol RuleConfiguration: InlinableOptionType, Equatable {
public protocol RuleConfiguration: Equatable {
/// The type of the rule that's using this configuration.
associatedtype Parent: Rule
@@ -2,7 +2,8 @@ import Foundation
import SourceKittenFramework
/// A rule configuration used for defining custom rules in yaml.
public struct RegexConfiguration<Parent: Rule>: SeverityBasedRuleConfiguration, Hashable, CacheDescriptionProvider {
public struct RegexConfiguration<Parent: Rule>: SeverityBasedRuleConfiguration, Hashable,
CacheDescriptionProvider, InlinableOptionType {
/// The identifier for this custom rule.
public let identifier: String
/// The name for this custom rule.
@@ -1,5 +1,5 @@
/// A rule configuration that allows specifying thresholds for `warning` and `error` severities.
public struct SeverityLevelsConfiguration<Parent: Rule>: RuleConfiguration {
public struct SeverityLevelsConfiguration<Parent: Rule>: RuleConfiguration, InlinableOptionType {
/// The threshold for a violation to be a warning.
@ConfigurationElement(key: "warning")
public var warning: Int = 12
@@ -48,8 +48,6 @@ public struct SeverityLevelsConfiguration<Parent: Rule>: RuleConfiguration {
} else {
self.error = nil
}
} else {
throw Issue.invalidConfiguration(ruleID: Parent.description.identifier)
}
}
}
@@ -1,3 +1,4 @@
import Foundation
import SwiftSyntax
import SwiftSyntaxMacros
@@ -22,37 +23,41 @@ enum AutoApply: MemberMacro {
let firstIndexWithoutKey = annotatedVarDecls
.partition { _, annotation in
if case let .argumentList(arguments) = annotation.arguments {
return arguments.contains { $0.label?.text == "key" } == true
return arguments.contains {
$0.label?.text == "inline"
&& $0.expression.as(BooleanLiteralExprSyntax.self)?.literal.text == "true"
}
}
return false
}
let elementNames = annotatedVarDecls.compactMap {
$0.0.bindings.first?.pattern.as(IdentifierPatternSyntax.self)?.identifier.text
}
let elementsWithoutKeyUpdate = elementNames[..<firstIndexWithoutKey]
.map {
"""
try \($0).apply(configuration, ruleID: Parent.identifier)
"""
let inlinedOptionsUpdate = elementNames[firstIndexWithoutKey...].map {
"""
try \($0).apply(configuration, ruleID: Parent.identifier)
try $\($0).performAfterParseOperations()
"""
}
let nonInlinedOptionsUpdate = elementNames[..<firstIndexWithoutKey].map {
"""
if $\($0).key.isEmpty {
$\($0).key = "\($0.snakeCased)"
}
let elementsWithKeyUpdate = elementNames[firstIndexWithoutKey...]
.map {
"""
try \($0).apply(configuration[$\($0).key], ruleID: Parent.identifier)
try $\($0).performAfterParseOperations()
"""
}
let configBinding = elementsWithKeyUpdate.isEmpty ? "_" : "configuration"
try \($0).apply(configuration[$\($0).key], ruleID: Parent.identifier)
try $\($0).performAfterParseOperations()
"""
}
return [
"""
mutating func apply(configuration: Any) throws {
\(raw: elementsWithoutKeyUpdate.joined(separator: "\n"))
guard let \(raw: configBinding) = configuration as? [String: Any] else {
\(raw: elementsWithoutKeyUpdate.isEmpty
\(raw: inlinedOptionsUpdate.joined())
guard let configuration = configuration as? [String: Any] else {
\(raw: inlinedOptionsUpdate.isEmpty
? "throw Issue.invalidConfiguration(ruleID: Parent.description.identifier)"
: "return")
}
\(raw: elementsWithKeyUpdate.joined(separator: "\n"))
\(raw: nonInlinedOptionsUpdate.joined())
if !supportedKeys.isSuperset(of: configuration.keys) {
let unknownKeys = Set(configuration.keys).subtracting(supportedKeys)
throw Issue.invalidConfigurationKeys(ruleID: Parent.identifier, keys: unknownKeys)
@@ -130,3 +135,16 @@ private extension EnumDeclSyntax {
}.first ?? ""
}
}
private extension String {
// swiftlint:disable:next force_try
static let regex = try! NSRegularExpression(pattern: "(?<!^)(?=[A-Z])")
var snakeCased: Self {
Self.regex.stringByReplacingMatches(
in: self,
range: NSRange(location: 0, length: utf16.count),
withTemplate: "_"
).lowercased()
}
}
+44 -25
View File
@@ -38,7 +38,7 @@ final class AutoApplyTests: XCTestCase {
mutating func apply(configuration: Any) throws {
guard let _ = configuration as? [String: Any] else {
guard let configuration = configuration as? [String: Any] else {
throw Issue.invalidConfiguration(ruleID: Parent.description.identifier)
}
@@ -59,26 +59,34 @@ final class AutoApplyTests: XCTestCase {
@AutoApply
struct S {
@ConfigurationElement
var e1 = 1
var eA = 1
@ConfigurationElement(value: 7)
var e2 = 2
var eB = 2
}
""",
expandedSource:
"""
struct S {
@ConfigurationElement
var e1 = 1
var eA = 1
@ConfigurationElement(value: 7)
var e2 = 2
var eB = 2
mutating func apply(configuration: Any) throws {
try e1.apply(configuration, ruleID: Parent.identifier)
try e2.apply(configuration, ruleID: Parent.identifier)
guard let _ = configuration as? [String: Any] else {
return
}
guard let configuration = configuration as? [String: Any] else {
throw Issue.invalidConfiguration(ruleID: Parent.description.identifier)
}
if $eA.key.isEmpty {
$eA.key = "e_a"
}
try eA.apply(configuration[$eA.key], ruleID: Parent.identifier)
try $eA.performAfterParseOperations()
if $eB.key.isEmpty {
$eB.key = "e_b"
}
try eB.apply(configuration[$eB.key], ruleID: Parent.identifier)
try $eB.performAfterParseOperations()
if !supportedKeys.isSuperset(of: configuration.keys) {
let unknownKeys = Set(configuration.keys).subtracting(supportedKeys)
throw Issue.invalidConfigurationKeys(ruleID: Parent.identifier, keys: unknownKeys)
@@ -90,34 +98,45 @@ final class AutoApplyTests: XCTestCase {
)
}
func testConfigurationElementsWithKeys() {
func testInlinedConfigurationElements() {
assertMacroExpansion(
"""
@AutoApply
struct S {
@ConfigurationElement(key: "e1")
var e1 = 1
@ConfigurationElement(key: "e2", other: 7)
var e2 = 2
@ConfigurationElement(key: "eD")
var eA = 1
@ConfigurationElement(inline: true)
var eB = 2
@ConfigurationElement(inline: false)
var eC = 3
}
""",
expandedSource:
"""
struct S {
@ConfigurationElement(key: "e1")
var e1 = 1
@ConfigurationElement(key: "e2", other: 7)
var e2 = 2
@ConfigurationElement(key: "eD")
var eA = 1
@ConfigurationElement(inline: true)
var eB = 2
@ConfigurationElement(inline: false)
var eC = 3
mutating func apply(configuration: Any) throws {
try eB.apply(configuration, ruleID: Parent.identifier)
try $eB.performAfterParseOperations()
guard let configuration = configuration as? [String: Any] else {
throw Issue.invalidConfiguration(ruleID: Parent.description.identifier)
return
}
try e1.apply(configuration[$e1.key], ruleID: Parent.identifier)
try $e1.performAfterParseOperations()
try e2.apply(configuration[$e2.key], ruleID: Parent.identifier)
try $e2.performAfterParseOperations()
if $eA.key.isEmpty {
$eA.key = "e_a"
}
try eA.apply(configuration[$eA.key], ruleID: Parent.identifier)
try $eA.performAfterParseOperations()
if $eC.key.isEmpty {
$eC.key = "e_c"
}
try eC.apply(configuration[$eC.key], ruleID: Parent.identifier)
try $eC.performAfterParseOperations()
if !supportedKeys.isSuperset(of: configuration.keys) {
let unknownKeys = Set(configuration.keys).subtracting(supportedKeys)
throw Issue.invalidConfigurationKeys(ruleID: Parent.identifier, keys: unknownKeys)
@@ -36,7 +36,7 @@ class ExplicitTypeInterfaceConfigurationTests: SwiftLintTestCase {
func testInvalidTypeOfValueInCustomConfiguration() {
var config = ExplicitTypeInterfaceConfiguration()
checkError(Issue.unknownConfiguration(ruleID: ExplicitTypeInterfaceRule.description.identifier)) {
try config.apply(configuration: ["severity": 1])
try config.apply(configuration: ["severity": "foo"])
}
}
@@ -69,7 +69,7 @@ class LineLengthConfigurationTests: SwiftLintTestCase {
}
func testLineLengthConfigurationThrowsOnBadConfig() {
let config = "unknown"
let config = ["warning": "unknown"]
var configuration = LineLengthConfiguration(length: severityLevels)
checkError(Issue.invalidConfiguration(ruleID: LineLengthRule.description.identifier)) {
try configuration.apply(configuration: config)
@@ -12,7 +12,7 @@ class RuleConfigurationDescriptionTests: XCTestCase {
@ConfigurationElement(key: "flag")
var flag = true
@ConfigurationElement(key: "string")
@ConfigurationElement
var string = "value"
@ConfigurationElement(key: "symbol")
var symbol = try! Symbol(fromAny: "value", context: "rule") // swiftlint:disable:this force_try
@@ -20,8 +20,8 @@ class RuleConfigurationDescriptionTests: XCTestCase {
var integer = 2
@ConfigurationElement(key: "null")
var null: Int?
@ConfigurationElement(key: "double")
var double = 2.1
@ConfigurationElement
var myDouble = 2.1
@ConfigurationElement(key: "severity")
var severity = ViolationSeverity.warning
@ConfigurationElement(
@@ -31,36 +31,37 @@ class RuleConfigurationDescriptionTests: XCTestCase {
var list = ["string1", "string2"]
@ConfigurationElement(key: "set")
var set: Set<Int> = [1, 2, 3]
@ConfigurationElement
@ConfigurationElement(inline: true)
var severityConfig = SeverityConfiguration<Parent>(.error)
@ConfigurationElement(key: "SEVERITY")
var renamedSeverityConfig = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement
var inlinedSeverityLevels = SeverityLevelsConfiguration<Parent>(warning: 1, error: 2)
@ConfigurationElement(inline: true)
var inlinedSeverityLevels = SeverityLevelsConfiguration<Parent>(warning: 1, error: nil)
@ConfigurationElement(key: "levels")
var nestedSeverityLevels = SeverityLevelsConfiguration<Parent>(warning: 3, error: nil)
var nestedSeverityLevels = SeverityLevelsConfiguration<Parent>(warning: 3, error: 2)
func isEqualTo(_ ruleConfiguration: some RuleConfiguration) -> Bool { false }
}
// swiftlint:disable:next function_body_length
func testDescriptionFromConfiguration() {
let description = RuleConfigurationDescription.from(configuration: TestConfiguration())
func testDescriptionFromConfiguration() throws {
var configuration = TestConfiguration()
try configuration.apply(configuration: [:])
let description = RuleConfigurationDescription.from(configuration: configuration)
XCTAssertEqual(description.oneLiner(), """
flag: true; \
string: "value"; \
symbol: value; \
integer: 2; \
double: 2.1; \
my_double: 2.1; \
severity: warning; \
list: ["STRING1", "STRING2"]; \
set: [1, 2, 3]; \
severity: error; \
SEVERITY: warning; \
warning: 1; \
error: 2; \
levels: warning: 3
levels: warning: 3, error: 2
""")
XCTAssertEqual(description.markdown(), """
@@ -103,7 +104,7 @@ class RuleConfigurationDescriptionTests: XCTestCase {
</tr>
<tr>
<td>
double
my_double
</td>
<td>
2.1
@@ -159,14 +160,6 @@ class RuleConfigurationDescriptionTests: XCTestCase {
</tr>
<tr>
<td>
error
</td>
<td>
2
</td>
</tr>
<tr>
<td>
levels
</td>
<td>
@@ -183,6 +176,14 @@ class RuleConfigurationDescriptionTests: XCTestCase {
3
</td>
</tr>
<tr>
<td>
error
</td>
<td>
2
</td>
</tr>
</tbody>
</table>
</td>
@@ -196,16 +197,16 @@ class RuleConfigurationDescriptionTests: XCTestCase {
string: "value"
symbol: value
integer: 2
double: 2.1
my_double: 2.1
severity: warning
list: ["STRING1", "STRING2"]
set: [1, 2, 3]
severity: error
SEVERITY: warning
warning: 1
error: 2
levels:
warning: 3
error: 2
""")
}
@@ -465,7 +466,7 @@ class RuleConfigurationDescriptionTests: XCTestCase {
"symbol": "new symbol",
"integer": 5,
"null": 0,
"double": 5.1,
"my_double": 5.1,
"severity": "error",
"list": ["string3", "string4"],
"set": [4, 5, 6],
@@ -479,7 +480,7 @@ class RuleConfigurationDescriptionTests: XCTestCase {
XCTAssertEqual(configuration.symbol, try Symbol(fromAny: "new symbol", context: "rule"))
XCTAssertEqual(configuration.integer, 5)
XCTAssertEqual(configuration.null, 0)
XCTAssertEqual(configuration.double, 5.1)
XCTAssertEqual(configuration.myDouble, 5.1)
XCTAssertEqual(configuration.severity, .error)
XCTAssertEqual(configuration.list, ["STRING3", "STRING4"])
XCTAssertEqual(configuration.set, [4, 5, 6])
@@ -66,8 +66,15 @@ class RuleConfigurationTests: SwiftLintTestCase {
}
}
func testSeverityConfigurationThrowsOnBadConfig() {
func testSeverityConfigurationDoesNotChangeOnBadConfig() throws {
let config = 17
var severityConfig = SeverityConfiguration<RuleMock>(.error)
try severityConfig.apply(configuration: config)
XCTAssertEqual(severityConfig.severity, .error)
}
func testSeverityConfigurationThrowsOnBadConfig() {
let config = "foo"
var severityConfig = SeverityConfiguration<RuleMock>(.warning)
checkError(Issue.unknownConfiguration(ruleID: RuleMock.description.identifier)) {
try severityConfig.apply(configuration: config)