@@ -16,122 +16,71 @@ import SwiftSyntax
16
16
17
17
/// Use caseless `enum`s for namespacing.
18
18
///
19
- /// In practice, this means that any `class` or ` struct` that consists of only `static let`s and
20
- /// `static func`s should be converted to an `enum`.
19
+ /// In practice, this means that any `struct` that consists of only `static let`s and `static func`s
20
+ /// should be converted to an `enum`.
21
21
///
22
- /// Lint: `class`es or `struct`s consisting of only `static let/func`s will yield a lint error.
22
+ /// This is **not** a safe transformation for `class` types because the user might pass the metatype
23
+ /// as an `AnyClass` argument to an API, and changing the declaration to a non-`class` type breaks
24
+ /// that (see https://bugs.swift.org/browse/SR-11111).
23
25
///
24
- /// Format: Rewrite the `class` or `struct` as an `enum` .
25
- /// TODO(abl): This can get complicated to pattern-match correctly.
26
- // . TODO(b/78286392): Give this formatting pass a category that makes it not run on save .
26
+ /// Lint: `struct`s consisting of only `static let/func`s will yield a lint error .
27
+ ///
28
+ /// Format: Rewrite the `struct` as an `enum` .
27
29
///
28
30
/// - SeeAlso: https://google.github.io/swift#nesting-and-namespacing
29
31
public final class UseEnumForNamespacing : SyntaxFormatRule {
32
+
30
33
public override func visit( _ node: StructDeclSyntax ) -> DeclSyntax {
31
- guard let newDecls = declsIfUsedAsNamespace ( node. members. members) ,
32
- node. genericParameterClause == nil ,
33
- node. inheritanceClause == nil
34
+ guard node. genericParameterClause == nil , node. inheritanceClause == nil ,
35
+ let memberDecls = membersToKeepIfUsedAsNamespace ( node. members. members)
34
36
else {
35
37
return node
36
38
}
37
39
38
40
diagnose ( . convertToEnum( kind: " struct " , name: node. identifier) , on: node)
39
41
40
- return makeEnum (
41
- declarationKeyword: node. structKeyword,
42
- modifiers: node. modifiers,
43
- name: node. identifier,
44
- members: node. members. withMembers ( newDecls)
45
- )
46
- }
47
-
48
- public override func visit( _ node: ClassDeclSyntax ) -> DeclSyntax {
49
- guard let newDecls = declsIfUsedAsNamespace ( node. members. members) ,
50
- node. genericParameterClause == nil ,
51
- node. inheritanceClause == nil
52
- else {
53
- return node
54
- }
55
-
56
- diagnose ( . convertToEnum( kind: " class " , name: node. identifier) , on: node)
57
-
58
- return makeEnum (
59
- declarationKeyword: node. classKeyword,
60
- modifiers: node. modifiers,
61
- name: node. identifier,
62
- members: node. members. withMembers ( newDecls)
63
- )
64
- }
65
-
66
- func makeEnum(
67
- declarationKeyword: TokenSyntax ,
68
- modifiers: ModifierListSyntax ? ,
69
- name: TokenSyntax ,
70
- members: MemberDeclBlockSyntax
71
- ) -> EnumDeclSyntax {
72
- // Since we remove the "final" modifier, we need to preserve its trivia if it is the first
73
- // modifier.
74
- var newLeadingTrivia : Trivia ? = nil
75
- if let firstMod = modifiers? . first, firstMod. name. text == " final " {
76
- newLeadingTrivia = firstMod. leadingTrivia
77
- }
78
-
79
- let newModifiers = modifiers? . remove ( name: " final " )
80
-
81
- let outputEnum = EnumDeclSyntax {
82
- if let mods = newModifiers {
83
- for mod in mods { $0. addModifier ( mod) }
84
- }
85
- $0. useEnumKeyword ( declarationKeyword. withKind ( . enumKeyword) )
86
- $0. useIdentifier ( name)
87
- $0. useMembers ( members)
88
- }
89
-
90
- if let trivia = newLeadingTrivia {
91
- return replaceTrivia (
92
- on: outputEnum,
93
- token: outputEnum. firstToken,
94
- leadingTrivia: trivia
95
- ) as! EnumDeclSyntax
96
- } else {
97
- return outputEnum
42
+ return EnumDeclSyntax { builder in
43
+ node. modifiers? . forEach { builder. addModifier ( $0) }
44
+ builder. useEnumKeyword ( node. structKeyword. withKind ( . enumKeyword) )
45
+ builder. useIdentifier ( node. identifier)
46
+ builder. useMembers ( node. members. withMembers ( memberDecls) )
98
47
}
99
48
}
100
49
101
- /// Determines if the set of declarations is consistent with a class or struct being used
102
- /// solely as a namespace for static functions. If there is a non-static private initializer
103
- /// with no arguments, that does not count against possibly being a namespace.
104
- func declsIfUsedAsNamespace( _ members: MemberDeclListSyntax ) -> MemberDeclListSyntax ? {
50
+ /// Returns the list of members that should be retained if all of them satisfy conditions that
51
+ /// make them effectively a namespace.
52
+ ///
53
+ /// If there is a non-static private initializer with no arguments, that does not count against
54
+ /// possibly being a namespace, since the user probably added it to prevent instantiation.
55
+ ///
56
+ /// If any of the members causes the type to disqualify as a namespace, this method returns nil.
57
+ func membersToKeepIfUsedAsNamespace( _ members: MemberDeclListSyntax ) -> MemberDeclListSyntax ? {
105
58
if members. count == 0 { return nil }
106
59
var declList = [ MemberDeclListItemSyntax] ( )
60
+
107
61
for member in members {
108
62
switch member. decl {
109
63
case let decl as FunctionDeclSyntax :
110
- guard let modifiers = decl. modifiers,
111
- modifiers. has ( modifier: " static " )
112
- else {
113
- return nil
114
- }
64
+ guard let modifiers = decl. modifiers, modifiers. has ( modifier: " static " ) else { return nil }
115
65
declList. append ( member)
66
+
116
67
case let decl as VariableDeclSyntax :
117
- guard let modifiers = decl. modifiers,
118
- modifiers. has ( modifier: " static " )
119
- else {
120
- return nil
121
- }
68
+ guard let modifiers = decl. modifiers, modifiers. has ( modifier: " static " ) else { return nil }
122
69
declList. append ( member)
70
+
123
71
case let decl as InitializerDeclSyntax :
124
- guard let modifiers = decl. modifiers,
125
- modifiers. has ( modifier: " private " ) ,
126
- decl. parameters. parameterList. count == 0
72
+ guard let modifiers = decl. modifiers, modifiers. has ( modifier: " private " ) ,
73
+ decl. parameters. parameterList. isEmpty
127
74
else {
128
75
return nil
129
76
}
130
- // Do not append private initializer
77
+ // Do not append private initializer.
78
+
131
79
default :
132
80
declList. append ( member)
133
81
}
134
82
}
83
+
135
84
return SyntaxFactory . makeMemberDeclList ( declList)
136
85
}
137
86
}
0 commit comments