Ignore no_magic_numbers violations if they are in an extension of a test class (#5146)

Only works in the same file.
This commit is contained in:
Martin Redington
2023-08-05 22:07:40 +02:00
committed by GitHub
parent 85c4a3a03d
commit e9c22852e5
3 changed files with 85 additions and 8 deletions
+7
View File
@@ -71,6 +71,13 @@
[Martin Redington](https://github.com/mildm8nnered)
[#4876](https://github.com/realm/SwiftLint/issues/4876)
* The `no_magic_numbers` rule will not trigger for violations in an
extension, if the extended class inherits from one of the specified
`test_parent_classes`, as long as the class declaration and the
extension are in the same source file.
[Martin Redington](https://github.com/mildm8nnered)
[#5137](https://github.com/realm/SwiftLint/issues/5137)
## 0.52.4: Lid Switch
#### Breaking
@@ -56,6 +56,18 @@ struct NoMagicNumbersRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderRule
let bar = array[42]
}
}
"""),
Example("""
class MyTest: XCTestCase {}
extension MyTest {
let a = Int(3)
}
"""),
Example("""
extension MyTest {
let a = Int(3)
}
class MyTest: XCTestCase {}
""")
],
triggeringExamples: [
@@ -64,7 +76,13 @@ struct NoMagicNumbersRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderRule
Example("array[↓42]"),
Example("let box = array[↓12 + ↓14]"),
Example("let a = b + ↓2.0"),
Example("Color.primary.opacity(isAnimate ? ↓0.1 : ↓1.5)")
Example("Color.primary.opacity(isAnimate ? ↓0.1 : ↓1.5)"),
Example("""
class MyTest: XCTestCase {}
extension NSObject {
let a = Int(↓3)
}
""")
]
)
@@ -76,22 +94,63 @@ struct NoMagicNumbersRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderRule
private extension NoMagicNumbersRule {
final class Visitor: ViolationsSyntaxVisitor {
private let testParentClasses: Set<String>
private var testClasses: Set<String> = []
private var nonTestClasses: Set<String> = []
private var possibleViolations: [String: Set<AbsolutePosition>] = [:]
init(viewMode: SyntaxTreeViewMode, testParentClasses: Set<String>) {
self.testParentClasses = testParentClasses
super.init(viewMode: viewMode)
}
override func visitPost(_ node: FloatLiteralExprSyntax) {
if node.isMemberOfATestClass(testParentClasses) == false, node.floatingDigits.isMagicNumber {
violations.append(node.floatingDigits.positionAfterSkippingLeadingTrivia)
override func visitPost(_ node: ClassDeclSyntax) {
let className = node.identifier.text
if node.isXCTestCase(testParentClasses) {
testClasses.insert(className)
removeViolations(forClassName: className)
} else {
nonTestClasses.insert(className)
}
}
override func visitPost(_ node: IntegerLiteralExprSyntax) {
if node.isMemberOfATestClass(testParentClasses) == false, node.digits.isMagicNumber {
violations.append(node.digits.positionAfterSkippingLeadingTrivia)
override func visitPost(_ node: FloatLiteralExprSyntax) {
guard node.floatingDigits.isMagicNumber else {
return
}
collectViolation(forNode: node)
}
override func visitPost(_ node: IntegerLiteralExprSyntax) {
guard node.digits.isMagicNumber else {
return
}
collectViolation(forNode: node)
}
private func collectViolation(forNode node: ExprSyntaxProtocol) {
if node.isMemberOfATestClass(testParentClasses) {
return
}
let violation = node.positionAfterSkippingLeadingTrivia
if let extendedTypeName = node.extendedTypeName() {
if !testClasses.contains(extendedTypeName) {
violations.append(violation)
if !nonTestClasses.contains(extendedTypeName) {
possibleViolations[extendedTypeName, default: []].insert(violation)
}
}
} else {
violations.append(violation)
}
}
private func removeViolations(forClassName className: String) {
guard let possibleViolationsForClass = possibleViolations[className] else {
return
}
let violationsToRemove = Set(possibleViolationsForClass.map { ReasonedRuleViolation(position: $0) })
violations.removeAll { violationsToRemove.contains($0) }
possibleViolations.removeValue(forKey: className)
}
}
}
@@ -126,4 +185,15 @@ private extension ExprSyntaxProtocol {
}
return false
}
func extendedTypeName() -> String? {
var parent = parent
while parent != nil {
if let extensionDecl = parent?.as(ExtensionDeclSyntax.self) {
return extensionDecl.extendedType.trimmedDescription
}
parent = parent?.parent
}
return nil
}
}
@@ -86,7 +86,7 @@ public extension SwiftSyntaxRule {
}
/// A violation produced by `ViolationsSyntaxVisitor`s.
public struct ReasonedRuleViolation: Comparable {
public struct ReasonedRuleViolation: Comparable, Hashable {
/// The violation's position.
public let position: AbsolutePosition
/// A specific reason for the violation.