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