@@ -19,11 +19,8 @@ import SwiftSyntax
19
19
/// Format: The access level is removed from the extension declaration and is added to each
20
20
/// declaration in the extension; declarations with redundant access levels (e.g.
21
21
/// `internal`, as that is the default access level) have the explicit access level removed.
22
- ///
23
- /// TODO: Find a better way to access modifiers and keyword tokens besides casting each declaration
24
22
@_spi ( Rules)
25
23
public final class NoAccessLevelOnExtensionDeclaration : SyntaxFormatRule {
26
-
27
24
public override func visit( _ node: ExtensionDeclSyntax ) -> DeclSyntax {
28
25
guard !node. modifiers. isEmpty else { return DeclSyntax ( node) }
29
26
guard let accessKeyword = node. modifiers. accessLevelModifier else { return DeclSyntax ( node) }
@@ -32,21 +29,26 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule {
32
29
switch keywordKind {
33
30
// Public, private, or fileprivate keywords need to be moved to members
34
31
case . keyword( . public) , . keyword( . private) , . keyword( . fileprivate) :
35
- diagnose ( . moveAccessKeyword( keyword: accessKeyword. name. text) , on: accessKeyword)
36
-
37
32
// The effective access level of the members of a `private` extension is `fileprivate`, so
38
33
// we have to update the keyword to ensure that the result is correct.
39
34
let accessKeywordToAdd : DeclModifierSyntax
35
+ let message : Finding . Message
40
36
if keywordKind == . keyword( . private) {
41
37
accessKeywordToAdd
42
38
= accessKeyword. with ( \. name, accessKeyword. name. with ( \. tokenKind, . keyword( . fileprivate) ) )
39
+ message = . moveAccessKeywordAndMakeFileprivate( keyword: accessKeyword. name. text)
43
40
} else {
44
41
accessKeywordToAdd = accessKeyword
42
+ message = . moveAccessKeyword( keyword: accessKeyword. name. text)
45
43
}
46
44
45
+ let ( newMemberBlock, notes) = addMemberAccessKeywords (
46
+ memDeclBlock: node. memberBlock, keyword: accessKeywordToAdd)
47
+ diagnose ( message, on: accessKeyword, notes: notes)
48
+
47
49
let newMembers = MemberBlockSyntax (
48
50
leftBrace: node. memberBlock. leftBrace,
49
- members: addMemberAccessKeywords ( memDeclBlock : node . memberBlock , keyword : accessKeywordToAdd ) ,
51
+ members: newMemberBlock ,
50
52
rightBrace: node. memberBlock. rightBrace)
51
53
var newKeyword = node. extensionKeyword
52
54
newKeyword. leadingTrivia = accessKeyword. leadingTrivia
@@ -57,9 +59,7 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule {
57
59
58
60
// Internal keyword redundant, delete
59
61
case . keyword( . internal) :
60
- diagnose (
61
- . removeRedundantAccessKeyword( name: node. extendedType. description) ,
62
- on: accessKeyword)
62
+ diagnose ( . removeRedundantAccessKeyword, on: accessKeyword)
63
63
var newKeyword = node. extensionKeyword
64
64
newKeyword. leadingTrivia = accessKeyword. leadingTrivia
65
65
let result = node. with ( \. modifiers, node. modifiers. remove ( name: accessKeyword. name. text) )
@@ -76,33 +76,56 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule {
76
76
private func addMemberAccessKeywords(
77
77
memDeclBlock: MemberBlockSyntax ,
78
78
keyword: DeclModifierSyntax
79
- ) -> MemberBlockItemListSyntax {
79
+ ) -> ( MemberBlockItemListSyntax , [ Finding . Note ] ) {
80
80
var newMembers : [ MemberBlockItemSyntax ] = [ ]
81
+ var notes : [ Finding . Note ] = [ ]
81
82
82
83
var formattedKeyword = keyword
83
84
formattedKeyword. leadingTrivia = [ ]
84
85
85
86
for memberItem in memDeclBlock. members {
86
87
let member = memberItem. decl
87
88
guard
89
+ let modifiers = member. asProtocol ( WithModifiersSyntax . self) ? . modifiers,
88
90
// addModifier relocates trivia for any token(s) displaced by the new modifier.
89
91
let newDecl = addModifier ( declaration: member, modifierKeyword: formattedKeyword)
90
92
. as ( DeclSyntax . self)
91
- else { continue }
93
+ else {
94
+ newMembers. append ( memberItem)
95
+ continue
96
+ }
97
+
92
98
newMembers. append ( memberItem. with ( \. decl, newDecl) )
99
+
100
+ // If it already had an explicit access modifier, don't leave a note.
101
+ if modifiers. accessLevelModifier == nil {
102
+ notes. append ( Finding . Note (
103
+ message: . addModifierToExtensionMember( keyword: formattedKeyword. name. text) ,
104
+ location: Finding . Location ( member. startLocation ( converter: context. sourceLocationConverter) )
105
+ ) )
106
+ }
93
107
}
94
- return MemberBlockItemListSyntax ( newMembers)
108
+ return ( MemberBlockItemListSyntax ( newMembers) , notes )
95
109
}
96
110
}
97
111
98
112
extension Finding . Message {
99
113
@_spi ( Rules)
100
- public static func removeRedundantAccessKeyword( name: String ) -> Finding . Message {
101
- " remove redundant 'internal' access keyword from ' \( name) ' "
102
- }
114
+ public static let removeRedundantAccessKeyword : Finding . Message =
115
+ " remove this redundant 'internal' access modifier from this extension "
103
116
104
117
@_spi ( Rules)
105
118
public static func moveAccessKeyword( keyword: String ) -> Finding . Message {
106
- " move the ' \( keyword) ' access keyword to precede each member inside the extension "
119
+ " move this ' \( keyword) ' access modifier to precede each member inside this extension "
120
+ }
121
+
122
+ @_spi ( Rules)
123
+ public static func moveAccessKeywordAndMakeFileprivate( keyword: String ) -> Finding . Message {
124
+ " remove this ' \( keyword) ' access modifier and declare each member inside this extension as 'fileprivate' "
125
+ }
126
+
127
+ @_spi ( Rules)
128
+ public static func addModifierToExtensionMember( keyword: String ) -> Finding . Message {
129
+ " add ' \( keyword) ' access modifier to this declaration "
107
130
}
108
131
}
0 commit comments