@@ -21,6 +21,13 @@ import SwiftSyntax
21
21
/// `internal`, as that is the default access level) have the explicit access level removed.
22
22
@_spi ( Rules)
23
23
public final class NoAccessLevelOnExtensionDeclaration : SyntaxFormatRule {
24
+ /// The access level keyword that was attached to the current extension, or nil if we are not
25
+ /// inside an extension.
26
+ private var accessKeyword : Keyword ? = nil
27
+
28
+ /// Findings propagated up to the extension visitor from any members that were rewritten.
29
+ private var notesFromRewrittenMembers : [ Finding . Note ] = [ ]
30
+
24
31
public override func visit( _ node: ExtensionDeclSyntax ) -> DeclSyntax {
25
32
guard
26
33
let accessKeyword = node. modifiers. accessLevelModifier,
@@ -29,80 +36,169 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule {
29
36
return DeclSyntax ( node)
30
37
}
31
38
32
- var result = node
39
+ let keywordToAdd : Keyword ?
40
+ let message : Finding . Message
33
41
34
42
switch keyword {
35
- // Public, private, fileprivate, or package keywords need to be moved to members
36
43
case . public, . private, . fileprivate, . package :
37
- // The effective access level of the members of a `private` extension is `fileprivate`, so
38
- // we have to update the keyword to ensure that the result is correct.
39
- var accessKeywordToAdd = accessKeyword
40
- let message : Finding . Message
44
+ // These access level modifiers need to be moved to members. Additionally, `private` is a
45
+ // special case, because the *effective* access level for a top-level private extension is
46
+ // `fileprivate`, so we need to preserve that when we apply it to the members.
41
47
if keyword == . private {
42
- accessKeywordToAdd . name . tokenKind = . keyword ( . fileprivate)
48
+ keywordToAdd = . fileprivate
43
49
message = . moveAccessKeywordAndMakeFileprivate( keyword: accessKeyword. name. text)
44
50
} else {
51
+ keywordToAdd = keyword
45
52
message = . moveAccessKeyword( keyword: accessKeyword. name. text)
46
53
}
47
54
48
- let ( newMembers, notes) =
49
- addMemberAccessKeyword ( accessKeywordToAdd, toMembersIn: node. memberBlock)
50
- diagnose ( message, on: accessKeyword, notes: notes)
51
-
52
- result. modifiers. remove ( anyOf: [ keyword] )
53
- result. extensionKeyword. leadingTrivia = accessKeyword. leadingTrivia
54
- result. memberBlock. members = newMembers
55
- return DeclSyntax ( result)
56
-
57
- // Internal keyword redundant, delete
58
55
case . internal:
59
- diagnose ( . removeRedundantAccessKeyword, on: accessKeyword)
60
-
61
- result. modifiers. remove ( anyOf: [ keyword] )
62
- result. extensionKeyword. leadingTrivia = accessKeyword. leadingTrivia
63
- return DeclSyntax ( result)
56
+ // If the access level keyword was `internal`, then it's redundant and we can just remove it.
57
+ // We don't need to modify the members at all in this case.
58
+ message = . removeRedundantAccessKeyword
59
+ keywordToAdd = nil
64
60
65
61
default :
66
- break
62
+ // For anything else, just return the extension and its members unchanged.
63
+ return DeclSyntax ( node)
64
+ }
65
+
66
+ // Set the keyword that should be applied to nested decls when we visit them, if any. Since
67
+ // extensions can't nest, we don't have to worry about maintaining a stack here.
68
+ self . accessKeyword = keywordToAdd
69
+ self . notesFromRewrittenMembers = [ ]
70
+ defer { self . accessKeyword = nil }
71
+
72
+ var result : ExtensionDeclSyntax
73
+ if keywordToAdd != nil {
74
+ // Visit the children if we need to add a keyword to the extension members.
75
+ result = super. visit ( node) . as ( ExtensionDeclSyntax . self) !
76
+ } else {
77
+ // We don't need to visit the children in this case.
78
+ result = node
67
79
}
68
80
81
+ // Finally, emit the finding (which includes notes from any rewritten members) and remove the
82
+ // access level keyword from the extension itself.
83
+ diagnose ( message, on: accessKeyword, notes: self . notesFromRewrittenMembers)
84
+ result. modifiers. remove ( anyOf: [ keyword] )
85
+ result. extensionKeyword. leadingTrivia = accessKeyword. leadingTrivia
69
86
return DeclSyntax ( result)
70
87
}
71
88
72
- // Adds given keyword to all members in declaration block
73
- private func addMemberAccessKeyword(
74
- _ modifier: DeclModifierSyntax ,
75
- toMembersIn memberBlock: MemberBlockSyntax
76
- ) -> ( MemberBlockItemListSyntax , [ Finding . Note ] ) {
77
- var newMembers : [ MemberBlockItemSyntax ] = [ ]
78
- var notes : [ Finding . Note ] = [ ]
79
-
80
- for memberItem in memberBlock. members {
81
- let decl = memberItem. decl
82
- guard
83
- let modifiers = decl. asProtocol ( WithModifiersSyntax . self) ? . modifiers,
84
- modifiers. accessLevelModifier == nil
85
- else {
86
- newMembers. append ( memberItem)
87
- continue
88
- }
89
+ public override func visit( _ node: ActorDeclSyntax ) -> DeclSyntax {
90
+ return applyingAccessModifierIfNone ( to: node)
91
+ }
92
+
93
+ public override func visit( _ node: ClassDeclSyntax ) -> DeclSyntax {
94
+ return applyingAccessModifierIfNone ( to: node)
95
+ }
96
+
97
+ public override func visit( _ node: EnumDeclSyntax ) -> DeclSyntax {
98
+ return applyingAccessModifierIfNone ( to: node)
99
+ }
100
+
101
+ public override func visit( _ node: FunctionDeclSyntax ) -> DeclSyntax {
102
+ return applyingAccessModifierIfNone ( to: node)
103
+ }
104
+
105
+ public override func visit( _ node: InitializerDeclSyntax ) -> DeclSyntax {
106
+ return applyingAccessModifierIfNone ( to: node)
107
+ }
108
+
109
+ public override func visit( _ node: StructDeclSyntax ) -> DeclSyntax {
110
+ return applyingAccessModifierIfNone ( to: node)
111
+ }
112
+
113
+ public override func visit( _ node: SubscriptDeclSyntax ) -> DeclSyntax {
114
+ return applyingAccessModifierIfNone ( to: node)
115
+ }
116
+
117
+ public override func visit( _ node: TypeAliasDeclSyntax ) -> DeclSyntax {
118
+ return applyingAccessModifierIfNone ( to: node)
119
+ }
89
120
90
- // Create a note associated with each declaration that needs to have an access level modifier
91
- // added to it.
92
- notes. append (
93
- Finding . Note (
94
- message: . addModifierToExtensionMember( keyword: modifier. name. text) ,
95
- location:
96
- Finding . Location ( decl. startLocation ( converter: context. sourceLocationConverter) )
97
- )
121
+ public override func visit( _ node: VariableDeclSyntax ) -> DeclSyntax {
122
+ return applyingAccessModifierIfNone ( to: node)
123
+ }
124
+
125
+ /// Adds `modifier` to `decl` if it doesn't already have an explicit access level modifier and
126
+ /// returns the new declaration.
127
+ ///
128
+ /// If `decl` already has an access level modifier, it is returned unchanged.
129
+ private func applyingAccessModifierIfNone( to decl: some DeclSyntaxProtocol ) -> DeclSyntax {
130
+ // Only go further if we are applying an access level keyword and if the decl is one that
131
+ // allows modifiers but doesn't already have an access level modifier.
132
+ guard
133
+ let accessKeyword,
134
+ let modifiers = decl. asProtocol ( WithModifiersSyntax . self) ? . modifiers,
135
+ modifiers. accessLevelModifier == nil
136
+ else {
137
+ return DeclSyntax ( decl)
138
+ }
139
+
140
+ // Create a note associated with each declaration that needs to have an access level modifier
141
+ // added to it.
142
+ self . notesFromRewrittenMembers. append (
143
+ Finding . Note (
144
+ message: . addModifierToExtensionMember( keyword: TokenSyntax . keyword ( accessKeyword) . text) ,
145
+ location:
146
+ Finding . Location ( decl. startLocation ( converter: context. sourceLocationConverter) )
98
147
)
148
+ )
99
149
100
- var newItem = memberItem
101
- newItem. decl = applyingAccessModifierIfNone ( modifier, to: decl)
102
- newMembers. append ( newItem)
150
+ switch Syntax ( decl) . as ( SyntaxEnum . self) {
151
+ case . actorDecl( let actorDecl) :
152
+ return applyingAccessModifierIfNone ( accessKeyword, to: actorDecl, declKeywordKeyPath: \. actorKeyword)
153
+ case . classDecl( let classDecl) :
154
+ return applyingAccessModifierIfNone ( accessKeyword, to: classDecl, declKeywordKeyPath: \. classKeyword)
155
+ case . enumDecl( let enumDecl) :
156
+ return applyingAccessModifierIfNone ( accessKeyword, to: enumDecl, declKeywordKeyPath: \. enumKeyword)
157
+ case . initializerDecl( let initDecl) :
158
+ return applyingAccessModifierIfNone ( accessKeyword, to: initDecl, declKeywordKeyPath: \. initKeyword)
159
+ case . functionDecl( let funcDecl) :
160
+ return applyingAccessModifierIfNone ( accessKeyword, to: funcDecl, declKeywordKeyPath: \. funcKeyword)
161
+ case . structDecl( let structDecl) :
162
+ return applyingAccessModifierIfNone ( accessKeyword, to: structDecl, declKeywordKeyPath: \. structKeyword)
163
+ case . subscriptDecl( let subscriptDecl) :
164
+ return applyingAccessModifierIfNone ( accessKeyword, to: subscriptDecl, declKeywordKeyPath: \. subscriptKeyword)
165
+ case . typeAliasDecl( let typeAliasDecl) :
166
+ return applyingAccessModifierIfNone ( accessKeyword, to: typeAliasDecl, declKeywordKeyPath: \. typealiasKeyword)
167
+ case . variableDecl( let varDecl) :
168
+ return applyingAccessModifierIfNone ( accessKeyword, to: varDecl, declKeywordKeyPath: \. bindingSpecifier)
169
+ default :
170
+ return DeclSyntax ( decl)
171
+ }
172
+ }
173
+
174
+ private func applyingAccessModifierIfNone< Decl: DeclSyntaxProtocol & WithModifiersSyntax > (
175
+ _ modifier: Keyword ,
176
+ to decl: Decl ,
177
+ declKeywordKeyPath: WritableKeyPath < Decl , TokenSyntax >
178
+ ) -> DeclSyntax {
179
+ // If there's already an access modifier among the modifier list, bail out.
180
+ guard decl. modifiers. accessLevelModifier == nil else { return DeclSyntax ( decl) }
181
+
182
+ var result = decl
183
+ var modifier = DeclModifierSyntax ( name: . keyword( modifier) )
184
+ modifier. trailingTrivia = [ . spaces( 1 ) ]
185
+
186
+ guard var firstModifier = decl. modifiers. first else {
187
+ // If there are no modifiers at all, add the one being requested, moving the leading trivia
188
+ // from the decl keyword to that modifier (to preserve leading comments, newlines, etc.).
189
+ modifier. leadingTrivia = decl [ keyPath: declKeywordKeyPath] . leadingTrivia
190
+ result [ keyPath: declKeywordKeyPath] . leadingTrivia = [ ]
191
+ result. modifiers = . init( [ modifier] )
192
+ return DeclSyntax ( result)
103
193
}
104
194
105
- return ( MemberBlockItemListSyntax ( newMembers) , notes)
195
+ // Otherwise, insert the modifier at the front of the modifier list, moving the (original) first
196
+ // modifier's leading trivia to the new one (to preserve leading comments, newlines, etc.).
197
+ modifier. leadingTrivia = firstModifier. leadingTrivia
198
+ firstModifier. leadingTrivia = [ ]
199
+ result. modifiers [ result. modifiers. startIndex] = firstModifier
200
+ result. modifiers. insert ( modifier, at: result. modifiers. startIndex)
201
+ return DeclSyntax ( result)
106
202
}
107
203
}
108
204
@@ -122,81 +218,3 @@ extension Finding.Message {
122
218
" add ' \( keyword) ' access modifier to this declaration "
123
219
}
124
220
}
125
-
126
- /// Adds `modifier` to `decl` if it doesn't already have an explicit access level modifier and
127
- /// returns the new declaration.
128
- ///
129
- /// If `decl` already has an access level modifier, it is returned unchanged.
130
- private func applyingAccessModifierIfNone(
131
- _ modifier: DeclModifierSyntax ,
132
- to decl: DeclSyntax
133
- ) -> DeclSyntax {
134
- switch Syntax ( decl) . as ( SyntaxEnum . self) {
135
- case . actorDecl( let actorDecl) :
136
- return applyingAccessModifierIfNone ( modifier, to: actorDecl, declKeywordKeyPath: \. actorKeyword)
137
- case . classDecl( let classDecl) :
138
- return applyingAccessModifierIfNone ( modifier, to: classDecl, declKeywordKeyPath: \. classKeyword)
139
- case . enumDecl( let enumDecl) :
140
- return applyingAccessModifierIfNone ( modifier, to: enumDecl, declKeywordKeyPath: \. enumKeyword)
141
- case . initializerDecl( let initDecl) :
142
- return applyingAccessModifierIfNone ( modifier, to: initDecl, declKeywordKeyPath: \. initKeyword)
143
- case . functionDecl( let funcDecl) :
144
- return applyingAccessModifierIfNone ( modifier, to: funcDecl, declKeywordKeyPath: \. funcKeyword)
145
- case . structDecl( let structDecl) :
146
- return applyingAccessModifierIfNone (
147
- modifier,
148
- to: structDecl,
149
- declKeywordKeyPath: \. structKeyword
150
- )
151
- case . subscriptDecl( let subscriptDecl) :
152
- return applyingAccessModifierIfNone (
153
- modifier,
154
- to: subscriptDecl,
155
- declKeywordKeyPath: \. subscriptKeyword
156
- )
157
- case . typeAliasDecl( let typeAliasDecl) :
158
- return applyingAccessModifierIfNone (
159
- modifier,
160
- to: typeAliasDecl,
161
- declKeywordKeyPath: \. typealiasKeyword
162
- )
163
- case . variableDecl( let varDecl) :
164
- return applyingAccessModifierIfNone (
165
- modifier,
166
- to: varDecl,
167
- declKeywordKeyPath: \. bindingSpecifier
168
- )
169
- default :
170
- return decl
171
- }
172
- }
173
-
174
- private func applyingAccessModifierIfNone< Decl: DeclSyntaxProtocol & WithModifiersSyntax > (
175
- _ modifier: DeclModifierSyntax ,
176
- to decl: Decl ,
177
- declKeywordKeyPath: WritableKeyPath < Decl , TokenSyntax >
178
- ) -> DeclSyntax {
179
- // If there's already an access modifier among the modifier list, bail out.
180
- guard decl. modifiers. accessLevelModifier == nil else { return DeclSyntax ( decl) }
181
-
182
- var result = decl
183
- var modifier = modifier
184
- modifier. trailingTrivia = [ . spaces( 1 ) ]
185
-
186
- guard var firstModifier = decl. modifiers. first else {
187
- // If there are no modifiers at all, add the one being requested, moving the leading trivia
188
- // from the decl keyword to that modifier (to preserve leading comments, newlines, etc.).
189
- modifier. leadingTrivia = decl [ keyPath: declKeywordKeyPath] . leadingTrivia
190
- result [ keyPath: declKeywordKeyPath] . leadingTrivia = [ ]
191
- result. modifiers = . init( [ modifier] )
192
- return DeclSyntax ( result)
193
- }
194
-
195
- // Otherwise, insert the modifier at the front of the modifier list, moving the (original) first
196
- // modifier's leading trivia to the new one (to preserve leading comments, newlines, etc.).
197
- modifier. leadingTrivia = firstModifier. leadingTrivia
198
- firstModifier. leadingTrivia = [ ]
199
- result. modifiers [ result. modifiers. startIndex] = firstModifier
200
- result. modifiers. insert ( modifier, at: result. modifiers. startIndex)
201
- return DeclSyntax ( result)
202
- }
0 commit comments