Consider custom attributes in `attributes` rule (#4616)

This commit is contained in:
Danny Mösch 2023-01-31 22:34:11 +01:00 committed by GitHub
parent 5eed8fe91b
commit 325d0ee1e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 76 additions and 14 deletions

View File

@ -79,6 +79,9 @@ opt_in_rules:
- xct_specific_matcher
- yoda_condition
attributes:
always_on_line_above:
- "@OptionGroup"
identifier_name:
excluded:
- id

View File

@ -49,6 +49,10 @@
[SimplyDanny](https://github.com/SimplyDanny)
[#4645](https://github.com/realm/SwiftLint/issues/4645)
* Consider custom attributes in `attributes` rule.
[SimplyDanny](https://github.com/SimplyDanny)
[#4599](https://github.com/realm/SwiftLint/issues/4599)
* Fix false positives on `private_subject` rule when using
subjects inside functions.
[Marcelo Fabri](https://github.com/marcelofabri)

View File

@ -121,7 +121,7 @@ private struct RuleHelper {
func hasViolation(
locationConverter: SourceLocationConverter,
attributesAndPlacements: [(AttributeSyntax, AttributePlacement)]
attributesAndPlacements: [(SyntaxProtocol, AttributePlacement)]
) -> Bool {
var linesWithAttributes: Set<Int> = [keywordLine]
for (attribute, placement) in attributesAndPlacements {
@ -147,23 +147,69 @@ private struct RuleHelper {
}
}
private extension AttributeListSyntax {
func attributesAndPlacements(configuration: AttributesConfiguration, shouldBeOnSameLine: Bool)
-> [(AttributeSyntax, AttributePlacement)] {
self
.children(viewMode: .sourceAccurate)
.compactMap { $0.as(AttributeSyntax.self) }
.map { attribute in
let atPrefixedName = "@\(attribute.attributeName.text)"
if configuration.alwaysOnSameLine.contains(atPrefixedName) {
return (attribute, .sameLineAsDeclaration)
} else if configuration.alwaysOnNewLine.contains(atPrefixedName) {
return (attribute, .dedicatedLine)
} else if attribute.argument != nil {
return (attribute, .dedicatedLine)
private enum Attribute {
case builtIn(AttributeSyntax)
case custom(CustomAttributeSyntax)
static func from(syntax: SyntaxProtocol) -> Self? {
if let attribute = syntax.as(AttributeSyntax.self) {
return .builtIn(attribute)
}
if let attribute = syntax.as(CustomAttributeSyntax.self) {
return .custom(attribute)
}
return nil
}
return shouldBeOnSameLine ? (attribute, .sameLineAsDeclaration) : (attribute, .dedicatedLine)
var hasArguments: Bool {
switch self {
case let .builtIn(attribute):
return attribute.argument != nil
case let .custom(attribute):
return attribute.argumentList != nil
}
}
var name: String? {
switch self {
case let .builtIn(attribute):
return attribute.attributeName.text
case let .custom(attribute):
return attribute.attributeName.as(SimpleTypeIdentifierSyntax.self)?.typeName
}
}
var syntaxNode: SyntaxProtocol {
switch self {
case let .builtIn(attribute):
return attribute
case let .custom(attribute):
return attribute
}
}
}
private extension AttributeListSyntax {
func attributesAndPlacements(configuration: AttributesConfiguration, shouldBeOnSameLine: Bool)
-> [(SyntaxProtocol, AttributePlacement)] {
self.children(viewMode: .sourceAccurate)
.compactMap { Attribute.from(syntax: $0) }
.compactMap { attribute in
guard let attributeName = attribute.name else {
return nil
}
let atPrefixedName = "@\(attributeName)"
if configuration.alwaysOnSameLine.contains(atPrefixedName) {
return (attribute.syntaxNode, .sameLineAsDeclaration)
} else if configuration.alwaysOnNewLine.contains(atPrefixedName) {
return (attribute.syntaxNode, .dedicatedLine)
} else if attribute.hasArguments {
return (attribute.syntaxNode, .dedicatedLine)
}
return shouldBeOnSameLine
? (attribute.syntaxNode, .sameLineAsDeclaration)
: (attribute.syntaxNode, .dedicatedLine)
}
}

View File

@ -73,6 +73,13 @@ internal struct AttributesRuleExamples {
func refreshable(action: @escaping @Sendable () async -> Void) -> some View {
modifier(RefreshableModifier(action: action))
}
"""),
Example("""
import AppKit
@NSApplicationMain
@MainActor
final class AppDelegate: NSAppDelegate {}
""")
]

View File

@ -12,7 +12,8 @@ extension SwiftLint {
var path = "rule_docs"
@Option(help: "The path to a SwiftLint configuration file")
var config: String?
@OptionGroup var rulesFilterOptions: RulesFilterOptions
@OptionGroup
var rulesFilterOptions: RulesFilterOptions
func run() throws {
let configuration = Configuration(configurationFiles: [config].compactMap({ $0 }))

View File

@ -17,7 +17,8 @@ extension SwiftLint {
@Option(help: "The path to a SwiftLint configuration file")
var config: String?
@OptionGroup var rulesFilterOptions: RulesFilterOptions
@OptionGroup
var rulesFilterOptions: RulesFilterOptions
@Flag(name: .shortAndLong, help: "Display full configuration details")
var verbose = false
@Argument(help: "The rule identifier to display description for")