diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/AttributesRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/AttributesRule.swift index 8930adf09..672f3a61d 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/AttributesRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/AttributesRule.swift @@ -46,12 +46,30 @@ private extension AttributesRule { let hasViolation = helper.hasViolation( locationConverter: locationConverter, - attributesAndPlacements: attributesAndPlacements + attributesAndPlacements: attributesAndPlacements, + attributesWithArgumentsAlwaysOnNewLine: configuration.attributesWithArgumentsAlwaysOnNewLine ) - if hasViolation { + switch hasViolation { + case .argumentsAlwaysOnNewLineViolation: + let reason = """ + Attributes with arguments or inside always_on_line_above must be on a new line \ + instead of the same line + """ + + violations.append( + ReasonedRuleViolation( + position: helper.violationPosition, + reason: reason, + severity: configuration.severityConfiguration.severity + ) + ) + return + case .violation: violations.append(helper.violationPosition) return + case .noViolation: + break } let linesForAttributes = attributesAndPlacements @@ -112,6 +130,12 @@ private enum AttributePlacement { case dedicatedLine } +private enum Violation { + case argumentsAlwaysOnNewLineViolation + case noViolation + case violation +} + private struct RuleHelper { let violationPosition: AbsolutePosition let keywordLine: Int @@ -119,8 +143,9 @@ private struct RuleHelper { func hasViolation( locationConverter: SourceLocationConverter, - attributesAndPlacements: [(AttributeSyntax, AttributePlacement)] - ) -> Bool { + attributesAndPlacements: [(AttributeSyntax, AttributePlacement)], + attributesWithArgumentsAlwaysOnNewLine: Bool + ) -> (Violation) { var linesWithAttributes: Set = [keywordLine] for (attribute, placement) in attributesAndPlacements { guard let attributeStartLine = attribute.startLine(locationConverter: locationConverter) else { @@ -130,18 +155,22 @@ private struct RuleHelper { switch placement { case .sameLineAsDeclaration: if attributeStartLine != keywordLine { - return true + return .violation } case .dedicatedLine: let hasViolation = attributeStartLine == keywordLine || linesWithAttributes.contains(attributeStartLine) linesWithAttributes.insert(attributeStartLine) if hasViolation { - return true + if attributesWithArgumentsAlwaysOnNewLine && shouldBeOnSameLine { + return .argumentsAlwaysOnNewLineViolation + } else { + return .violation + } } } } - return false + return .noViolation } } diff --git a/Tests/SwiftLintFrameworkTests/AttributesRuleTests.swift b/Tests/SwiftLintFrameworkTests/AttributesRuleTests.swift index aae95a6d0..29d4306c6 100644 --- a/Tests/SwiftLintFrameworkTests/AttributesRuleTests.swift +++ b/Tests/SwiftLintFrameworkTests/AttributesRuleTests.swift @@ -95,4 +95,39 @@ class AttributesRuleTests: SwiftLintTestCase { ruleConfiguration: ["always_on_same_line": ["@discardableResult", "@objc", "@IBAction", "@IBDesignable"]]) } + + func testAttributesWithArgumentsAlwaysOnLineAboveFalse() { + let nonTriggeringExamples = [ + Example("@Environment(\\.presentationMode) private var presentationMode") + ] + let triggeringExamples = [ + Example(""" + @Environment(\\.presentationMode) + private ↓var presentationMode + """) + ] + + let argumentsAlwaysOnLineDescription = AttributesRule.description + .with(triggeringExamples: triggeringExamples) + .with(nonTriggeringExamples: nonTriggeringExamples) + + verifyRule(argumentsAlwaysOnLineDescription, + ruleConfiguration: ["attributes_with_arguments_always_on_line_above": false]) + } + + func testAttributesWithArgumentsAlwaysOnLineAboveTrue() { + let nonTriggeringExamples = [ + Example("@Environment(\\.presentationMode)\nprivate var presentationMode") + ] + let triggeringExamples = [ + Example("@Environment(\\.presentationMode) private ↓var presentationMode") + ] + + let argumentsAlwaysOnLineDescription = AttributesRule.description + .with(triggeringExamples: triggeringExamples) + .with(nonTriggeringExamples: nonTriggeringExamples) + + verifyRule(argumentsAlwaysOnLineDescription, + ruleConfiguration: ["attributes_with_arguments_always_on_line_above": true]) + } }