@@ -21,88 +21,197 @@ 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+   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+ 
2439  public  override func  visit( _ node:  ExtensionDeclSyntax )  ->  DeclSyntax  { 
2540    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.
2644      let  accessKeyword =  node. modifiers. accessLevelModifier, 
2745      case . keyword( let  keyword)  =  accessKeyword. name. tokenKind
2846    else  { 
2947      return  DeclSyntax ( node) 
3048    } 
3149
32-     var  result  =  node
50+     self . notesFromRewrittenMembers =  [ ] 
51+ 
52+     let  keywordToAdd :  Keyword ? 
53+     let  message :  Finding . Message 
3354
3455    switch  keyword { 
35-     // Public, private, fileprivate, or package keywords need to be moved to members
3656    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.
4160      if  keyword ==  . private { 
42-         accessKeywordToAdd . name . tokenKind  =  . keyword ( . fileprivate) 
61+         keywordToAdd  =  . fileprivate
4362        message =  . moveAccessKeywordAndMakeFileprivate( keyword:  accessKeyword. name. text) 
4463      }  else  { 
64+         keywordToAdd =  keyword
4565        message =  . moveAccessKeyword( keyword:  accessKeyword. name. text) 
4666      } 
4767
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
5868    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 
6473
6574    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
6792    } 
6893
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
6999    return  DeclSyntax ( result) 
70100  } 
71101
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+   } 
89129
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) ) 
98160      ) 
161+     ) 
99162
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) 
103206    } 
104207
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) 
106215  } 
107216} 
108217
@@ -122,81 +231,3 @@ extension Finding.Message {
122231    " add ' \( keyword) ' access modifier to this declaration " 
123232  } 
124233} 
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