mirror of
https://github.com/realm/SwiftLint.git
synced 2026-05-07 20:12:49 +00:00
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:
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user