diff --git a/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift b/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift index 3bf853d2f..b56192cba 100644 --- a/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift +++ b/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift @@ -26,19 +26,23 @@ public final class DontRepeatTypeInStaticProperties: SyntaxLintRule { /// type name (excluding possible namespace prefixes, like `NS` or `UI`) as a suffix. public override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { guard node.modifiers.contains(anyOf: [.class, .static]), - let typeName = Syntax(node).containingDeclName + let typeName = Syntax(node).containingDeclName, + let variableTypeName = node.typeName, + typeName.hasSuffix(variableTypeName) || variableTypeName == "Self" else { return .visitChildren } - let bareTypeName = removingPossibleNamespacePrefix(from: typeName) + // the final component of the top type `A.B.C.D` is what we want `D`. + let lastTypeName = typeName.components(separatedBy: ".").last! + let bareTypeName = removingPossibleNamespacePrefix(from: lastTypeName) for binding in node.bindings { guard let identifierPattern = binding.pattern.as(IdentifierPatternSyntax.self) else { continue } let varName = identifierPattern.identifier.text - if varName.contains(bareTypeName) { + if varName.hasSuffix(bareTypeName) { diagnose(.removeTypeFromName(name: varName, type: bareTypeName), on: identifierPattern) } } @@ -90,8 +94,7 @@ extension Syntax { case .identifierType(let simpleType): return simpleType.name.text case .memberType(let memberType): - // the final component of the top type `A.B.C.D` is what we want `D`. - return memberType.name.text + return memberType.description.trimmingCharacters(in: .whitespacesAndNewlines) default: // Do nothing for non-nominal types. If Swift adds support for extensions on non-nominals, // we'll need to update this if we need to support some subset of those. @@ -106,3 +109,23 @@ extension Syntax { } } } + +extension VariableDeclSyntax { + fileprivate var typeName: String? { + if let typeAnnotation = bindings.first?.typeAnnotation { + return typeAnnotation.type.description + } else if let initializerCalledExpression = bindings.first?.initializer?.value.as(FunctionCallExprSyntax.self)? + .calledExpression + { + if let memberAccessExprSyntax = initializerCalledExpression.as(MemberAccessExprSyntax.self), + memberAccessExprSyntax.declName.baseName.tokenKind == .keyword(.`init`) + { + return memberAccessExprSyntax.base?.description + } else { + return initializerCalledExpression.description + } + } else { + return nil + } + } +} diff --git a/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift b/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift index 2eb12dd82..d140bdbd2 100644 --- a/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift +++ b/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift @@ -56,8 +56,10 @@ final class DontRepeatTypeInStaticPropertiesTests: LintOrFormatRuleTestCase { extension A { static let b = C() } - """, - findings: [] + extension UIImage { + static let fooImage: Int + } + """ ) } @@ -75,14 +77,84 @@ final class DontRepeatTypeInStaticPropertiesTests: LintOrFormatRuleTestCase { ) } + func testDottedExtendedTypeWithNamespacePrefix() { + assertLint( + DontRepeatTypeInStaticProperties.self, + """ + extension Dotted.RANDThing { + static let 1️⃣defaultThing: Dotted.RANDThing + } + """, + findings: [ + FindingSpec("1️⃣", message: "remove the suffix 'Thing' from the name of the variable 'defaultThing'") + ] + ) + } + + func testSelfType() { + assertLint( + DontRepeatTypeInStaticProperties.self, + """ + extension Dotted.Thing { + static let 1️⃣defaultThing: Self + } + """, + findings: [ + FindingSpec("1️⃣", message: "remove the suffix 'Thing' from the name of the variable 'defaultThing'") + ] + ) + } + + func testDottedExtendedTypeInitializer() { + assertLint( + DontRepeatTypeInStaticProperties.self, + """ + extension Dotted.Thing { + static let 1️⃣defaultThing = Dotted.Thing() + } + """, + findings: [ + FindingSpec("1️⃣", message: "remove the suffix 'Thing' from the name of the variable 'defaultThing'") + ] + ) + } + + func testExplicitInitializer() { + assertLint( + DontRepeatTypeInStaticProperties.self, + """ + struct Foo { + static let 1️⃣defaultFoo = Foo.init() + } + """, + findings: [ + FindingSpec("1️⃣", message: "remove the suffix 'Foo' from the name of the variable 'defaultFoo'") + ] + ) + } + + func testSelfTypeInitializer() { + assertLint( + DontRepeatTypeInStaticProperties.self, + """ + struct Foo { + static let 1️⃣defaultFoo = Self() + } + """, + findings: [ + FindingSpec("1️⃣", message: "remove the suffix 'Foo' from the name of the variable 'defaultFoo'") + ] + ) + } + func testIgnoreSingleDecl() { assertLint( DontRepeatTypeInStaticProperties.self, """ struct Foo { // swift-format-ignore: DontRepeatTypeInStaticProperties - static let defaultFoo: Int - static let 1️⃣alternateFoo: Int + static let defaultFoo: Foo + static let 1️⃣alternateFoo: Foo } """, findings: [ @@ -90,5 +162,4 @@ final class DontRepeatTypeInStaticPropertiesTests: LintOrFormatRuleTestCase { ] ) } - }