Add new option to `attributes` rule to influence it for attributes with arguments (#4855)

This commit is contained in:
Danny Mösch 2023-05-05 22:12:01 +02:00 committed by GitHub
parent f0138ea1df
commit 7756793356
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 42 additions and 28 deletions

View File

@ -2,7 +2,14 @@
#### Breaking #### Breaking
* None. * The `attributes` rule now expects attributes with arguments to be placed
on their own line above the declaration they are supposed to influence.
This applies to attributes with any kinds of arguments including single
key path arguments which were previously handled in a different way. This
behavior can be turned off by setting `attributes_with_arguments_always_on_line_above`
to `false.
[SimplyDanny](https://github.com/SimplyDanny)
[#4843](https://github.com/realm/SwiftLint/issues/4843)
#### Experimental #### Experimental
@ -59,6 +66,14 @@
[whiteio](https://github.com/whiteio) [whiteio](https://github.com/whiteio)
[#4923](https://github.com/realm/SwiftLint/issues/4923) [#4923](https://github.com/realm/SwiftLint/issues/4923)
* The `attributes` rule received a new boolean option
`attributes_with_arguments_always_on_line_above` which is `true` by default.
Setting it to `false` ensures that attributes with arguments like
`@Persisted(primaryKey: true)` don't violate the rule if they are on the same
line with the variable declaration.
[SimplyDanny](https://github.com/SimplyDanny)
[#4843](https://github.com/realm/SwiftLint/issues/4843)
#### Bug Fixes #### Bug Fixes
* Fix `lower_acl_than_parent` rule rewriter by preserving leading whitespace. * Fix `lower_acl_than_parent` rule rewriter by preserving leading whitespace.

View File

@ -1,5 +1,6 @@
struct AttributesConfiguration: SeverityBasedRuleConfiguration, Equatable { struct AttributesConfiguration: SeverityBasedRuleConfiguration, Equatable {
var severityConfiguration = SeverityConfiguration(.warning) var severityConfiguration = SeverityConfiguration(.warning)
private(set) var attributesWithArgumentsAlwaysOnNewLine = true
private(set) var alwaysOnSameLine = Set<String>() private(set) var alwaysOnSameLine = Set<String>()
private(set) var alwaysOnNewLine = Set<String>() private(set) var alwaysOnNewLine = Set<String>()
@ -20,6 +21,11 @@ struct AttributesConfiguration: SeverityBasedRuleConfiguration, Equatable {
throw ConfigurationError.unknownConfiguration throw ConfigurationError.unknownConfiguration
} }
if let attributesWithArgumentsAlwaysOnNewLine
= configuration["attributes_with_arguments_always_on_line_above"] as? Bool {
self.attributesWithArgumentsAlwaysOnNewLine = attributesWithArgumentsAlwaysOnNewLine
}
if let alwaysOnSameLine = configuration["always_on_same_line"] as? [String] { if let alwaysOnSameLine = configuration["always_on_same_line"] as? [String] {
self.alwaysOnSameLine = Set(alwaysOnSameLine) self.alwaysOnSameLine = Set(alwaysOnSameLine)
} }

View File

@ -157,7 +157,7 @@ private extension AttributeListSyntax {
return (attribute, .sameLineAsDeclaration) return (attribute, .sameLineAsDeclaration)
} else if configuration.alwaysOnNewLine.contains(atPrefixedName) { } else if configuration.alwaysOnNewLine.contains(atPrefixedName) {
return (attribute, .dedicatedLine) return (attribute, .dedicatedLine)
} else if attribute.argument != nil { } else if attribute.argument != nil, configuration.attributesWithArgumentsAlwaysOnNewLine {
return (attribute, .dedicatedLine) return (attribute, .dedicatedLine)
} }
@ -165,20 +165,9 @@ private extension AttributeListSyntax {
} }
} }
var hasAttributeWithKeypathArgument: Bool {
contains { element in
switch element {
case .attribute(let attribute):
return attribute.hasKeypathArgument
case .ifConfigDecl:
return false
}
}
}
// swiftlint:disable:next cyclomatic_complexity // swiftlint:disable:next cyclomatic_complexity
func makeHelper(locationConverter: SourceLocationConverter) -> RuleHelper? { func makeHelper(locationConverter: SourceLocationConverter) -> RuleHelper? {
guard let parent, !hasAttributeWithKeypathArgument else { guard let parent else {
return nil return nil
} }
@ -226,9 +215,3 @@ private extension AttributeListSyntax {
) )
} }
} }
private extension AttributeSyntax {
var hasKeypathArgument: Bool {
argument?.as(TupleExprElementListSyntax.self)?.first?.expression.is(KeyPathExprSyntax.self) == true
}
}

View File

@ -82,15 +82,15 @@ internal struct AttributesRuleExamples {
final class AppDelegate: NSAppDelegate {} final class AppDelegate: NSAppDelegate {}
"""), """),
Example(#""" Example(#"""
@_spi(Private) import SomeFramework
@_spi(Private)
final class MyView: View { final class MyView: View {
@SwiftUI.Environment(\.colorScheme) var colorScheme: ColorScheme @SwiftUI.Environment(\.colorScheme) var first: ColorScheme
@Environment(\.colorScheme) var second: ColorScheme
@Persisted(primaryKey: true) var id: Int
} }
"""#), """#, configuration: ["attributes_with_arguments_always_on_line_above": false], excludeFromDocumentation: true)
Example(#"""
final class MyView: View {
@Environment(\.colorScheme) var colorScheme: ColorScheme
}
"""#)
] ]
static let triggeringExamples = [ static let triggeringExamples = [
@ -124,6 +124,16 @@ internal struct AttributesRuleExamples {
Example("@GKInspectable\n ↓var maxSpeed: Float"), Example("@GKInspectable\n ↓var maxSpeed: Float"),
Example("@discardableResult ↓func a() -> Int"), Example("@discardableResult ↓func a() -> Int"),
Example("@objc\n @discardableResult ↓func a() -> Int"), Example("@objc\n @discardableResult ↓func a() -> Int"),
Example("@objc\n\n @discardableResult\n ↓func a() -> Int") Example("@objc\n\n @discardableResult\n ↓func a() -> Int"),
Example(#"""
struct S: View {
@Environment(\.colorScheme) var first: ColorScheme
@Persisted var id: Int
@FetchRequest(
animation: nil
)
var entities: FetchedResults
}
"""#, excludeFromDocumentation: true)
] ]
} }