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
|
- xct_specific_matcher
|
||||||
- yoda_condition
|
- yoda_condition
|
||||||
|
|
||||||
|
attributes:
|
||||||
|
always_on_line_above:
|
||||||
|
- "@OptionGroup"
|
||||||
identifier_name:
|
identifier_name:
|
||||||
excluded:
|
excluded:
|
||||||
- id
|
- id
|
||||||
|
|
|
@ -49,6 +49,10 @@
|
||||||
[SimplyDanny](https://github.com/SimplyDanny)
|
[SimplyDanny](https://github.com/SimplyDanny)
|
||||||
[#4645](https://github.com/realm/SwiftLint/issues/4645)
|
[#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
|
* Fix false positives on `private_subject` rule when using
|
||||||
subjects inside functions.
|
subjects inside functions.
|
||||||
[Marcelo Fabri](https://github.com/marcelofabri)
|
[Marcelo Fabri](https://github.com/marcelofabri)
|
||||||
|
|
|
@ -121,7 +121,7 @@ private struct RuleHelper {
|
||||||
|
|
||||||
func hasViolation(
|
func hasViolation(
|
||||||
locationConverter: SourceLocationConverter,
|
locationConverter: SourceLocationConverter,
|
||||||
attributesAndPlacements: [(AttributeSyntax, AttributePlacement)]
|
attributesAndPlacements: [(SyntaxProtocol, AttributePlacement)]
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
var linesWithAttributes: Set<Int> = [keywordLine]
|
var linesWithAttributes: Set<Int> = [keywordLine]
|
||||||
for (attribute, placement) in attributesAndPlacements {
|
for (attribute, placement) in attributesAndPlacements {
|
||||||
|
@ -147,23 +147,69 @@ private struct RuleHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension AttributeListSyntax {
|
private enum Attribute {
|
||||||
func attributesAndPlacements(configuration: AttributesConfiguration, shouldBeOnSameLine: Bool)
|
case builtIn(AttributeSyntax)
|
||||||
-> [(AttributeSyntax, AttributePlacement)] {
|
case custom(CustomAttributeSyntax)
|
||||||
self
|
|
||||||
.children(viewMode: .sourceAccurate)
|
static func from(syntax: SyntaxProtocol) -> Self? {
|
||||||
.compactMap { $0.as(AttributeSyntax.self) }
|
if let attribute = syntax.as(AttributeSyntax.self) {
|
||||||
.map { attribute in
|
return .builtIn(attribute)
|
||||||
let atPrefixedName = "@\(attribute.attributeName.text)"
|
}
|
||||||
if configuration.alwaysOnSameLine.contains(atPrefixedName) {
|
if let attribute = syntax.as(CustomAttributeSyntax.self) {
|
||||||
return (attribute, .sameLineAsDeclaration)
|
return .custom(attribute)
|
||||||
} else if configuration.alwaysOnNewLine.contains(atPrefixedName) {
|
}
|
||||||
return (attribute, .dedicatedLine)
|
return nil
|
||||||
} else if attribute.argument != nil {
|
|
||||||
return (attribute, .dedicatedLine)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,13 @@ internal struct AttributesRuleExamples {
|
||||||
func refreshable(action: @escaping @Sendable () async -> Void) -> some View {
|
func refreshable(action: @escaping @Sendable () async -> Void) -> some View {
|
||||||
modifier(RefreshableModifier(action: action))
|
modifier(RefreshableModifier(action: action))
|
||||||
}
|
}
|
||||||
|
"""),
|
||||||
|
Example("""
|
||||||
|
import AppKit
|
||||||
|
|
||||||
|
@NSApplicationMain
|
||||||
|
@MainActor
|
||||||
|
final class AppDelegate: NSAppDelegate {}
|
||||||
""")
|
""")
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,8 @@ extension SwiftLint {
|
||||||
var path = "rule_docs"
|
var path = "rule_docs"
|
||||||
@Option(help: "The path to a SwiftLint configuration file")
|
@Option(help: "The path to a SwiftLint configuration file")
|
||||||
var config: String?
|
var config: String?
|
||||||
@OptionGroup var rulesFilterOptions: RulesFilterOptions
|
@OptionGroup
|
||||||
|
var rulesFilterOptions: RulesFilterOptions
|
||||||
|
|
||||||
func run() throws {
|
func run() throws {
|
||||||
let configuration = Configuration(configurationFiles: [config].compactMap({ $0 }))
|
let configuration = Configuration(configurationFiles: [config].compactMap({ $0 }))
|
||||||
|
|
|
@ -17,7 +17,8 @@ extension SwiftLint {
|
||||||
|
|
||||||
@Option(help: "The path to a SwiftLint configuration file")
|
@Option(help: "The path to a SwiftLint configuration file")
|
||||||
var config: String?
|
var config: String?
|
||||||
@OptionGroup var rulesFilterOptions: RulesFilterOptions
|
@OptionGroup
|
||||||
|
var rulesFilterOptions: RulesFilterOptions
|
||||||
@Flag(name: .shortAndLong, help: "Display full configuration details")
|
@Flag(name: .shortAndLong, help: "Display full configuration details")
|
||||||
var verbose = false
|
var verbose = false
|
||||||
@Argument(help: "The rule identifier to display description for")
|
@Argument(help: "The rule identifier to display description for")
|
||||||
|
|
Loading…
Reference in New Issue