Consider custom attributes in `attributes` rule (#4616)
This commit is contained in:
parent
5eed8fe91b
commit
325d0ee1e4
|
@ -79,6 +79,9 @@ opt_in_rules:
|
|||
- xct_specific_matcher
|
||||
- yoda_condition
|
||||
|
||||
attributes:
|
||||
always_on_line_above:
|
||||
- "@OptionGroup"
|
||||
identifier_name:
|
||||
excluded:
|
||||
- id
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 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
|
||||
}
|
||||
|
||||
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)
|
||||
-> [(AttributeSyntax, AttributePlacement)] {
|
||||
self
|
||||
.children(viewMode: .sourceAccurate)
|
||||
.compactMap { $0.as(AttributeSyntax.self) }
|
||||
.map { attribute in
|
||||
let atPrefixedName = "@\(attribute.attributeName.text)"
|
||||
-> [(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, .sameLineAsDeclaration)
|
||||
return (attribute.syntaxNode, .sameLineAsDeclaration)
|
||||
} else if configuration.alwaysOnNewLine.contains(atPrefixedName) {
|
||||
return (attribute, .dedicatedLine)
|
||||
} else if attribute.argument != nil {
|
||||
return (attribute, .dedicatedLine)
|
||||
return (attribute.syntaxNode, .dedicatedLine)
|
||||
} else if attribute.hasArguments {
|
||||
return (attribute.syntaxNode, .dedicatedLine)
|
||||
}
|
||||
|
||||
return shouldBeOnSameLine ? (attribute, .sameLineAsDeclaration) : (attribute, .dedicatedLine)
|
||||
return shouldBeOnSameLine
|
||||
? (attribute.syntaxNode, .sameLineAsDeclaration)
|
||||
: (attribute.syntaxNode, .dedicatedLine)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {}
|
||||
""")
|
||||
]
|
||||
|
||||
|
|
|
@ -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 }))
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue