Files
SwiftLint/Tests/CoreTests/RuleConfigurationDescriptionTests.swift
T
2025-12-01 14:15:00 -05:00

541 lines
14 KiB
Swift

@testable import SwiftLintCore
import TestHelpers
import XCTest
// swiftlint:disable file_length
// swiftlint:disable:next type_body_length
final class RuleConfigurationDescriptionTests: SwiftLintTestCase {
@AutoConfigParser
private struct MockConfiguration: RuleConfiguration {
@ConfigurationElement(key: "flag")
var flag = true
@ConfigurationElement(key: "string")
var string = "value"
@ConfigurationElement(key: "symbol")
var symbol = try! Symbol(fromAny: "value", context: "rule") // swiftlint:disable:this force_try
@ConfigurationElement(key: "integer")
var integer = 2
@ConfigurationElement(key: "null")
var null: Int?
@ConfigurationElement(key: "my_double")
var myDouble = 2.1
@ConfigurationElement(key: "severity")
var severity = ViolationSeverity.warning
@ConfigurationElement(
key: "list",
postprocessor: { list in list = list.map { $0.uppercased() } }
)
var list = ["string1", "string2"]
@ConfigurationElement(key: "set", deprecationNotice: .suggestAlternative(ruleID: "my_rule", name: "other_opt"))
var set: Set<Int> = [1, 2, 3]
@ConfigurationElement(key: "set_of_doubles")
var setOfDoubles: Set<Double> = [1, 2, 3, 4.7]
@ConfigurationElement(inline: true)
var severityConfig = SeverityConfiguration<Parent>(.error)
@ConfigurationElement(key: "SEVERITY")
var renamedSeverityConfig = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(inline: true)
var inlinedSeverityLevels = SeverityLevelsConfiguration<Parent>(warning: 1, error: nil)
@ConfigurationElement(key: "levels")
var nestedSeverityLevels = SeverityLevelsConfiguration<Parent>(warning: 3, error: 2)
func isEqualTo(_: some RuleConfiguration) -> Bool { false }
}
// swiftlint:disable:next function_body_length
func testDescriptionFromConfiguration() throws {
var configuration = MockConfiguration()
try configuration.apply(configuration: Void()) // Configure to set keys.
let description = RuleConfigurationDescription.from(configuration: configuration)
XCTAssertEqual(description.oneLiner(), """
flag: true; \
string: "value"; \
symbol: value; \
integer: 2; \
my_double: 2.1; \
severity: warning; \
list: ["STRING1", "STRING2"]; \
set: [1, 2, 3]; \
set_of_doubles: [1.0, 2.0, 3.0, 4.7]; \
severity: error; \
SEVERITY: warning; \
warning: 1; \
levels: warning: 3, error: 2
""")
XCTAssertEqual(description.markdown(), """
<table>
<thead>
<tr><th>Key</th><th>Value</th></tr>
</thead>
<tbody>
<tr>
<td>
flag
</td>
<td>
true
</td>
</tr>
<tr>
<td>
string
</td>
<td>
&quot;value&quot;
</td>
</tr>
<tr>
<td>
symbol
</td>
<td>
value
</td>
</tr>
<tr>
<td>
integer
</td>
<td>
2
</td>
</tr>
<tr>
<td>
my_double
</td>
<td>
2.1
</td>
</tr>
<tr>
<td>
severity
</td>
<td>
warning
</td>
</tr>
<tr>
<td>
list
</td>
<td>
[&quot;STRING1&quot;, &quot;STRING2&quot;]
</td>
</tr>
<tr>
<td>
set
</td>
<td>
[1, 2, 3]
</td>
</tr>
<tr>
<td>
set_of_doubles
</td>
<td>
[1.0, 2.0, 3.0, 4.7]
</td>
</tr>
<tr>
<td>
severity
</td>
<td>
error
</td>
</tr>
<tr>
<td>
SEVERITY
</td>
<td>
warning
</td>
</tr>
<tr>
<td>
warning
</td>
<td>
1
</td>
</tr>
<tr>
<td>
levels
</td>
<td>
<table>
<thead>
<tr><th>Key</th><th>Value</th></tr>
</thead>
<tbody>
<tr>
<td>
warning
</td>
<td>
3
</td>
</tr>
<tr>
<td>
error
</td>
<td>
2
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
""")
XCTAssertEqual(description.yaml(), """
flag: true
string: "value"
symbol: value
integer: 2
my_double: 2.1
severity: warning
list: ["STRING1", "STRING2"]
set: [1, 2, 3]
set_of_doubles: [1.0, 2.0, 3.0, 4.7]
severity: error
SEVERITY: warning
warning: 1
levels:
warning: 3
error: 2
""")
}
func testPrefersParameterDescription() {
struct MockConfiguration: RuleConfiguration {
typealias Parent = MockRule // swiftlint:disable:this nesting
var parameterDescription: RuleConfigurationDescription? {
"visible" => .flag(true)
}
@ConfigurationElement(key: "invisible")
var invisible = true
mutating func apply(configuration _: Any) { /* conformance for test */ }
func isEqualTo(_: some RuleConfiguration) -> Bool { false }
}
let description = RuleConfigurationDescription.from(configuration: MockConfiguration())
XCTAssertEqual(description.oneLiner(), "visible: true")
XCTAssertEqual(description.markdown(), """
<table>
<thead>
<tr><th>Key</th><th>Value</th></tr>
</thead>
<tbody>
<tr>
<td>
visible
</td>
<td>
true
</td>
</tr>
</tbody>
</table>
""")
XCTAssertEqual(description.yaml(), "visible: true")
}
func testEmptyDescription() {
let description = description { RuleConfigurationOption.noOptions }
XCTAssertTrue(description.oneLiner().isEmpty)
XCTAssertTrue(description.markdown().isEmpty)
XCTAssertTrue(description.yaml().isEmpty)
}
// swiftlint:disable:next function_body_length
func testBasicTypes() {
let description = description {
"flag" => .flag(true)
"string" => .string("value")
"symbol" => .symbol("value")
"integer" => .integer(-12)
"float" => .float(42.0)
"severity" => .severity(.error)
"list" => .list([.symbol("value"), .string("value"), .float(12.8)])
}
XCTAssertEqual(description.markdown(), """
<table>
<thead>
<tr><th>Key</th><th>Value</th></tr>
</thead>
<tbody>
<tr>
<td>
flag
</td>
<td>
true
</td>
</tr>
<tr>
<td>
string
</td>
<td>
&quot;value&quot;
</td>
</tr>
<tr>
<td>
symbol
</td>
<td>
value
</td>
</tr>
<tr>
<td>
integer
</td>
<td>
-12
</td>
</tr>
<tr>
<td>
float
</td>
<td>
42.0
</td>
</tr>
<tr>
<td>
severity
</td>
<td>
error
</td>
</tr>
<tr>
<td>
list
</td>
<td>
[value, &quot;value&quot;, 12.8]
</td>
</tr>
</tbody>
</table>
""")
XCTAssertEqual(description.oneLiner(), """
flag: true; string: "value"; symbol: value; integer: -12; float: 42.0; \
severity: error; list: [value, "value", 12.8]
""")
XCTAssertEqual(description.yaml(), """
flag: true
string: "value"
symbol: value
integer: -12
float: 42.0
severity: error
list: [value, "value", 12.8]
""")
}
// swiftlint:disable:next function_body_length
func testNestedDescription() {
let description = description {
"flag" => .flag(true)
"nested 1" => .nest {
"integer" => .integer(2)
"nested 2" => .nest {
"float" => .float(42.1)
}
"symbol" => .symbol("value")
}
"string" => .string("value")
}
XCTAssertEqual(description.markdown(), """
<table>
<thead>
<tr><th>Key</th><th>Value</th></tr>
</thead>
<tbody>
<tr>
<td>
flag
</td>
<td>
true
</td>
</tr>
<tr>
<td>
nested 1
</td>
<td>
<table>
<thead>
<tr><th>Key</th><th>Value</th></tr>
</thead>
<tbody>
<tr>
<td>
integer
</td>
<td>
2
</td>
</tr>
<tr>
<td>
nested 2
</td>
<td>
<table>
<thead>
<tr><th>Key</th><th>Value</th></tr>
</thead>
<tbody>
<tr>
<td>
float
</td>
<td>
42.1
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td>
symbol
</td>
<td>
value
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td>
string
</td>
<td>
&quot;value&quot;
</td>
</tr>
</tbody>
</table>
""")
XCTAssertEqual(description.oneLiner(), """
flag: true; nested 1: integer: 2, nested 2: float: 42.1, symbol: value; string: "value"
""")
XCTAssertEqual(description.yaml(), """
flag: true
nested 1:
integer: 2
nested 2:
float: 42.1
symbol: value
string: "value"
""")
}
func testUpdate() throws {
var configuration = MockConfiguration()
try configuration.apply(configuration: [
"flag": false,
"string": "new value",
"symbol": "new symbol",
"integer": 5,
"null": 0,
"my_double": 5.1,
"severity": "error",
"list": ["string3", "string4"],
"set": [4, 5, 6],
"SEVERITY": "error",
"warning": 12,
"levels": ["warning": 6, "error": 7],
])
XCTAssertFalse(configuration.flag)
XCTAssertEqual(configuration.string, "new value")
XCTAssertEqual(configuration.symbol, try Symbol(fromAny: "new symbol", context: "rule"))
XCTAssertEqual(configuration.integer, 5)
XCTAssertEqual(configuration.null, 0)
XCTAssertEqual(configuration.myDouble, 5.1)
XCTAssertEqual(configuration.severity, .error)
XCTAssertEqual(configuration.list, ["STRING3", "STRING4"])
XCTAssertEqual(configuration.set, [4, 5, 6])
XCTAssertEqual(configuration.severityConfig, .error)
XCTAssertEqual(configuration.renamedSeverityConfig, .error)
XCTAssertEqual(configuration.inlinedSeverityLevels, SeverityLevelsConfiguration(warning: 12))
XCTAssertEqual(configuration.nestedSeverityLevels, SeverityLevelsConfiguration(warning: 6, error: 7))
}
func testDeprecationWarning() async throws {
let console = try await Issue.captureConsole {
var configuration = MockConfiguration()
try configuration.apply(configuration: ["set": [6, 7]])
}
XCTAssertEqual(
console,
"warning: Configuration option 'set' in 'my_rule' rule is deprecated. Use the option 'other_opt' instead."
)
}
func testNoDeprecationWarningIfNoDeprecatedPropertySet() async throws {
let console = try await Issue.captureConsole {
var configuration = MockConfiguration()
try configuration.apply(configuration: ["flag": false])
}
XCTAssertTrue(console.isEmpty)
}
func testInvalidKeys() async throws {
let console = try await Issue.captureConsole {
var configuration = MockConfiguration()
try configuration.apply(configuration: [
"severity": "error",
"warning": 3,
"unknown": 1,
"unsupported": true,
])
}
XCTAssertEqual(
console,
"warning: Configuration for 'MockRule' rule contains the invalid key(s) 'unknown', 'unsupported'."
)
}
private func description(@RuleConfigurationDescriptionBuilder _ content: () -> RuleConfigurationDescription)
-> RuleConfigurationDescription { content() }
}