@@ -21,6 +21,13 @@ import SwiftSyntax
2121/// `internal`, as that is the default access level) have the explicit access level removed.
2222@_spi ( Rules)
2323public 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+
2431 public override func visit( _ node: ExtensionDeclSyntax ) -> DeclSyntax {
2532 guard
2633 let accessKeyword = node. modifiers. accessLevelModifier,
@@ -29,80 +36,169 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule {
2936 return DeclSyntax ( node)
3037 }
3138
32- var result = node
39+ let keywordToAdd : Keyword ?
40+ let message : Finding . Message
3341
3442 switch keyword {
35- // Public, private, fileprivate, or package keywords need to be moved to members
3643 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.
4147 if keyword == . private {
42- accessKeywordToAdd . name . tokenKind = . keyword ( . fileprivate)
48+ keywordToAdd = . fileprivate
4349 message = . moveAccessKeywordAndMakeFileprivate( keyword: accessKeyword. name. text)
4450 } else {
51+ keywordToAdd = keyword
4552 message = . moveAccessKeyword( keyword: accessKeyword. name. text)
4653 }
4754
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
5855 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
6460
6561 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
6779 }
6880
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
6986 return DeclSyntax ( result)
7087 }
7188
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+ }
89120
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) )
98147 )
148+ )
99149
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)
103193 }
104194
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)
106202 }
107203}
108204
@@ -122,81 +218,3 @@ extension Finding.Message {
122218 " add ' \( keyword) ' access modifier to this declaration "
123219 }
124220}
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