@@ -32,69 +32,87 @@ public struct DontRepeatTypeInStaticProperties: SyntaxLintRule {
32
32
}
33
33
34
34
public func visit( _ node: ClassDeclSyntax ) -> SyntaxVisitorContinueKind {
35
- determinePropertyNameViolations ( members : node. members. members, nodeId : node. identifier. text)
35
+ diagnoseStaticMembers ( node. members. members, endingWith : node. identifier. text)
36
36
return . skipChildren
37
37
}
38
38
39
39
public func visit( _ node: EnumDeclSyntax ) -> SyntaxVisitorContinueKind {
40
- determinePropertyNameViolations ( members : node. members. members, nodeId : node. identifier. text)
40
+ diagnoseStaticMembers ( node. members. members, endingWith : node. identifier. text)
41
41
return . skipChildren
42
42
}
43
43
44
44
public func visit( _ node: ProtocolDeclSyntax ) -> SyntaxVisitorContinueKind {
45
- determinePropertyNameViolations ( members : node. members. members, nodeId : node. identifier. text)
45
+ diagnoseStaticMembers ( node. members. members, endingWith : node. identifier. text)
46
46
return . skipChildren
47
47
}
48
48
49
49
public func visit( _ node: StructDeclSyntax ) -> SyntaxVisitorContinueKind {
50
- determinePropertyNameViolations ( members : node. members. members, nodeId : node. identifier. text)
50
+ diagnoseStaticMembers ( node. members. members, endingWith : node. identifier. text)
51
51
return . skipChildren
52
52
}
53
53
54
54
public func visit( _ node: ExtensionDeclSyntax ) -> SyntaxVisitorContinueKind {
55
- determinePropertyNameViolations (
56
- members: node. members. members,
57
- nodeId: node. extendedType. description)
55
+ let members = node. members. members
56
+
57
+ switch node. extendedType {
58
+ case let simpleType as SimpleTypeIdentifierSyntax :
59
+ diagnoseStaticMembers ( members, endingWith: simpleType. name. text)
60
+ case let memberType as MemberTypeIdentifierSyntax :
61
+ // We don't need to drill recursively into this structure because types with more than two
62
+ // components are constructed left-heavy; that is, `A.B.C.D` is structured as `((A.B).C).D`,
63
+ // and the final component of the top type is what we want.
64
+ diagnoseStaticMembers ( members, endingWith: memberType. name. text)
65
+ default :
66
+ // Do nothing for non-nominal types. If Swift adds support for extensions on non-nominals,
67
+ // we'll need to update this if we need to support some subset of those.
68
+ break
69
+ }
70
+
58
71
return . skipChildren
59
72
}
60
73
61
- func determinePropertyNameViolations( members: MemberDeclListSyntax , nodeId: String ) {
74
+ /// Iterates over the static/class properties in the given member list and diagnoses any where the
75
+ /// name has the containing type name (excluding possible namespace prefixes, like `NS` or `UI`)
76
+ /// as a suffix.
77
+ private func diagnoseStaticMembers( _ members: MemberDeclListSyntax , endingWith typeName: String ) {
62
78
for member in members {
63
- guard let decl = member. decl as? VariableDeclSyntax else { continue }
64
- guard let modifiers = decl. modifiers else { continue }
65
- guard modifiers. has ( modifier: " static " ) || modifiers. has ( modifier: " class " ) else { continue }
79
+ guard
80
+ let varDecl = member. decl as? VariableDeclSyntax ,
81
+ let modifiers = varDecl. modifiers,
82
+ modifiers. has ( modifier: " static " ) || modifiers. has ( modifier: " class " )
83
+ else { continue }
66
84
67
- let typeName = withoutPrefix ( name : nodeId )
85
+ let bareTypeName = removingPossibleNamespacePrefix ( from : typeName )
68
86
69
- for id in decl. identifiers {
70
- let varName = id. identifier. text
71
- guard varName. contains ( typeName) else { continue }
72
- diagnose ( . removeTypeFromName( name: varName, type: typeName) , on: decl)
87
+ for pattern in varDecl. identifiers {
88
+ let varName = pattern. identifier. text
89
+ if varName. contains ( bareTypeName) {
90
+ diagnose ( . removeTypeFromName( name: varName, type: bareTypeName) , on: varDecl)
91
+ }
73
92
}
74
93
}
75
94
}
76
95
77
- // Returns the given string without capitalized prefix in the beginning
78
- func withoutPrefix( name: String ) -> String {
79
- let formattedName = name. trimmingCharacters ( in: CharacterSet . whitespaces)
80
- let upperCase = Array ( formattedName. uppercased ( ) )
81
- let original = Array ( formattedName)
82
- guard original [ 0 ] == upperCase [ 0 ] else { return name }
83
-
84
- var prefixEndsAt = 0
85
- var idx = 0
86
- while idx <= name. count - 2 {
87
- if original [ idx] == upperCase [ idx] && original [ idx + 1 ] != upperCase [ idx + 1 ] {
88
- prefixEndsAt = idx
96
+ /// Returns the portion of the given string that excludes a possible Objective-C-style capitalized
97
+ /// namespace prefix (a leading sequence of more than one uppercase letter).
98
+ ///
99
+ /// If the name has zero or one leading uppercase letters, the entire name is returned.
100
+ private func removingPossibleNamespacePrefix( from name: String ) -> Substring {
101
+ guard let first = name. first, first. isUppercase else { return name [ ... ] }
102
+
103
+ for index in name. indices. dropLast ( ) {
104
+ let nextIndex = name. index ( after: index)
105
+ if name [ index] . isUppercase && !name[ nextIndex] . isUppercase {
106
+ return name [ index... ]
89
107
}
90
- idx += 1
91
108
}
92
- return String ( formattedName. dropFirst ( prefixEndsAt) )
109
+
110
+ return name [ ... ]
93
111
}
94
112
}
95
113
96
114
extension Diagnostic . Message {
97
- static func removeTypeFromName( name: String , type: String ) -> Diagnostic . Message {
115
+ static func removeTypeFromName( name: String , type: Substring ) -> Diagnostic . Message {
98
116
return . init( . warning, " remove ' \( type) ' from ' \( name) ' " )
99
117
}
100
118
}
0 commit comments