Allow to configure only severity in a short form for every rule with a severity (#5509)

The README states that a configuration like `attributes: error` is
valid to only set a different severity level for a rule (the
`attributes` rule here). This was previously only possible for rules
that were accompanied by a plain severity configuration.

I don't think this broke with the automatic parsing code generation.
For some rules it might have worked before, for others not. This change
makes it consistently working for all rules.
This commit is contained in:
Danny Mösch
2024-03-25 21:40:56 +01:00
committed by GitHub
parent 5075cc073c
commit 299042a233
6 changed files with 135 additions and 23 deletions
+5
View File
@@ -31,6 +31,11 @@
[Julien Baillon](https://github.com/julien-baillon)
[#5372](https://github.com/realm/SwiftLint/issues/5372)
* Allow to set the severity of rules (if they have one) in the short form
`rule_name: warning|error` provided that no other attributes need to be
configured.
[SimplyDanny](https://github.com/SimplyDanny)
* Add new `ignore_one_liners` option to `switch_case_alignment`
rule to ignore switch statements written in a single line.
[tonell-m](https://github.com/tonell-m)
@@ -35,9 +35,27 @@ enum AutoApply: MemberMacro {
let elementNames = annotatedVarDecls.compactMap {
$0.0.bindings.first?.pattern.as(IdentifierPatternSyntax.self)?.identifier.text
}
let nonInlinedOptions = elementNames[..<firstIndexWithoutKey]
var inlinedOptions = elementNames[firstIndexWithoutKey...]
let isSeverityBased = configuration.inheritanceClause?.inheritedTypes.contains {
$0.type.as(IdentifierTypeSyntax.self)?.name.text == "SeverityBasedRuleConfiguration"
}
if isSeverityBased == true {
if nonInlinedOptions.contains("severityConfiguration") {
inlinedOptions.append("severityConfiguration")
} else {
context.diagnose(SwiftLintCoreMacroError.severityBasedWithoutProperty.diagnose(at: configuration.name))
}
}
return [
DeclSyntax(try FunctionDeclSyntax("mutating func apply(configuration: Any) throws") {
let inlinedOptions = elementNames[firstIndexWithoutKey...]
for option in nonInlinedOptions {
"""
if $\(raw: option).key.isEmpty {
$\(raw: option).key = "\(raw: option.snakeCased)"
}
"""
}
for option in inlinedOptions {
"""
do {
@@ -51,16 +69,11 @@ enum AutoApply: MemberMacro {
"""
guard let configuration = configuration as? [String: Any] else {
\(raw: inlinedOptions.isEmpty
? "throw Issue.invalidConfiguration(ruleID: Parent.description.identifier)"
? "throw Issue.invalidConfiguration(ruleID: Parent.identifier)"
: "return")
}
"""
for option in elementNames[..<firstIndexWithoutKey] {
"""
if $\(raw: option).key.isEmpty {
$\(raw: option).key = "\(raw: option.snakeCased)"
}
"""
for option in nonInlinedOptions {
"""
try \(raw: option).apply(configuration[$\(raw: option).key], ruleID: Parent.identifier)
"""
@@ -14,6 +14,9 @@ struct SwiftLintCoreMacros: CompilerPlugin {
enum SwiftLintCoreMacroError: String, DiagnosticMessage {
case notStruct = "Attribute can only be applied to structs"
case severityBasedWithoutProperty = """
Severity-based configuration without a 'severityConfiguration' property is invalid
"""
case notEnum = "Attribute can only be applied to enums"
case noStringRawType = "Attribute can only be applied to enums with a 'String' raw type"
case noBooleanLiteral = "Macro argument must be a boolean literal"
+98 -14
View File
@@ -38,7 +38,7 @@ final class AutoApplyTests: XCTestCase {
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.invalidConfiguration(ruleID: Parent.description.identifier)
throw Issue.invalidConfiguration(ruleID: Parent.identifier)
}
if !supportedKeys.isSuperset(of: configuration.keys) {
let unknownKeys = Set(configuration.keys).subtracting(supportedKeys)
@@ -58,7 +58,7 @@ final class AutoApplyTests: XCTestCase {
struct S {
@ConfigurationElement
var eA = 1
@ConfigurationElement(value: 7)
@ConfigurationElement(key: "name")
var eB = 2
}
""",
@@ -67,21 +67,21 @@ final class AutoApplyTests: XCTestCase {
struct S {
@ConfigurationElement
var eA = 1
@ConfigurationElement(value: 7)
@ConfigurationElement(key: "name")
var eB = 2
mutating func apply(configuration: Any) throws {
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"
}
guard let configuration = configuration as? [String: Any] else {
throw Issue.invalidConfiguration(ruleID: Parent.identifier)
}
try eA.apply(configuration[$eA.key], ruleID: Parent.identifier)
try $eA.performAfterParseOperations()
try eB.apply(configuration[$eB.key], ruleID: Parent.identifier)
try $eB.performAfterParseOperations()
if !supportedKeys.isSuperset(of: configuration.keys) {
@@ -119,6 +119,12 @@ final class AutoApplyTests: XCTestCase {
var eC = 3
mutating func apply(configuration: Any) throws {
if $eA.key.isEmpty {
$eA.key = "e_a"
}
if $eC.key.isEmpty {
$eC.key = "e_c"
}
do {
try eB.apply(configuration, ruleID: Parent.identifier)
try $eB.performAfterParseOperations()
@@ -128,14 +134,8 @@ final class AutoApplyTests: XCTestCase {
guard let configuration = configuration as? [String: Any] else {
return
}
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) {
@@ -148,4 +148,88 @@ final class AutoApplyTests: XCTestCase {
macros: macros
)
}
func testSeverityBasedConfigurationWithoutSeverityProperty() {
assertMacroExpansion(
"""
@AutoApply
struct S: SeverityBasedRuleConfiguration {
}
""",
expandedSource:
"""
struct S: SeverityBasedRuleConfiguration {
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.invalidConfiguration(ruleID: Parent.identifier)
}
if !supportedKeys.isSuperset(of: configuration.keys) {
let unknownKeys = Set(configuration.keys).subtracting(supportedKeys)
throw Issue.invalidConfigurationKeys(ruleID: Parent.identifier, keys: unknownKeys)
}
}
}
""",
diagnostics: [
DiagnosticSpec(
message: SwiftLintCoreMacroError.severityBasedWithoutProperty.message,
line: 2,
column: 8
)
],
macros: macros)
}
func testSeverityAppliedTwice() {
// swiftlint:disable line_length
assertMacroExpansion(
"""
@AutoApply
struct S: SeverityBasedRuleConfiguration {
@ConfigurationElement
var severityConfiguration = .warning
@ConfigurationElement
var foo = 2
}
""",
expandedSource:
"""
struct S: SeverityBasedRuleConfiguration {
@ConfigurationElement
var severityConfiguration = .warning
@ConfigurationElement
var foo = 2
mutating func apply(configuration: Any) throws {
if $severityConfiguration.key.isEmpty {
$severityConfiguration.key = "severity_configuration"
}
if $foo.key.isEmpty {
$foo.key = "foo"
}
do {
try severityConfiguration.apply(configuration, ruleID: Parent.identifier)
try $severityConfiguration.performAfterParseOperations()
} catch let issue as Issue where issue == Issue.nothingApplied(ruleID: Parent.identifier) {
// Acceptable. Continue.
}
guard let configuration = configuration as? [String: Any] else {
return
}
try severityConfiguration.apply(configuration[$severityConfiguration.key], ruleID: Parent.identifier)
try $severityConfiguration.performAfterParseOperations()
try foo.apply(configuration[$foo.key], ruleID: Parent.identifier)
try $foo.performAfterParseOperations()
if !supportedKeys.isSuperset(of: configuration.keys) {
let unknownKeys = Set(configuration.keys).subtracting(supportedKeys)
throw Issue.invalidConfigurationKeys(ruleID: Parent.identifier, keys: unknownKeys)
}
}
}
""",
macros: macros
)
// swiftlint:enable line_length
}
}
@@ -46,7 +46,7 @@ class RuleConfigurationDescriptionTests: XCTestCase {
// swiftlint:disable:next function_body_length
func testDescriptionFromConfiguration() throws {
var configuration = TestConfiguration()
try configuration.apply(configuration: [:])
try configuration.apply(configuration: Void()) // Configure to set keys.
let description = RuleConfigurationDescription.from(configuration: configuration)
XCTAssertEqual(description.oneLiner(), """
@@ -42,6 +42,13 @@ class RuleConfigurationTests: SwiftLintTestCase {
}
}
func testSeverityWorksAsOnlyParameter() throws {
var config = AttributesConfiguration()
XCTAssertEqual(config.severity, .warning)
try config.apply(configuration: "error")
XCTAssertEqual(config.severity, .error)
}
func testSeverityConfigurationFromString() {
let config = "Warning"
let comp = SeverityConfiguration<RuleMock>(.warning)