1- import Foundation
2- import SourceKittenFramework
1+ import SwiftLintCore
2+ import SwiftSyntax
33
4- struct ExtensionAccessModifierRule : ASTRule , OptInRule {
4+ @SwiftSyntaxRule
5+ struct ExtensionAccessModifierRule : OptInRule {
56var configuration = SeverityConfiguration < Self > ( . warning)
67
78static let description = RuleDescription (
@@ -34,28 +35,68 @@ struct ExtensionAccessModifierRule: ASTRule, OptInRule {
3435 }
3536""" ) ,
3637Example ( """
38+ extension Foo {
39+ var bar: Int { return 1 }
40+ internal var baz: Int { return 1 }
41+ }
42+ """ ) ,
43+ Example ( """
44+ internal extension Foo {
45+ var bar: Int { return 1 }
46+ var baz: Int { return 1 }
47+ }
48+ """ ) ,
49+ Example ( """
3750 public extension Foo {
3851 var bar: Int { return 1 }
3952 var baz: Int { return 1 }
4053 }
4154""" ) ,
4255Example ( """
56+ public extension Foo {
57+ var bar: Int { return 1 }
58+ internal var baz: Int { return 1 }
59+ }
60+ """ ) ,
61+ Example ( """
4362 extension Foo {
44- private bar: Int { return 1 }
45- private baz: Int { return 1 }
63+ privatevar bar: Int { return 1 }
64+ privatevar baz: Int { return 1 }
4665 }
4766""" ) ,
4867Example ( """
4968 extension Foo {
50- open bar: Int { return 1 }
51- open baz: Int { return 1 }
69+ openvar bar: Int { return 1 }
70+ openvar baz: Int { return 1 }
5271 }
5372""" ) ,
5473Example ( """
5574 extension Foo {
5675 func setup() {}
5776 public func update() {}
5877 }
78+ """ ) ,
79+ Example ( """
80+ private extension Foo {
81+ private var bar: Int { return 1 }
82+ var baz: Int { return 1 }
83+ }
84+ """ ) ,
85+ Example ( """
86+ extension Foo {
87+ internal private(set) var bar: Int {
88+ get { Foo.shared.bar }
89+ set { Foo.shared.bar = newValue }
90+ }
91+ }
92+ """ ) ,
93+ Example ( """
94+ extension Foo {
95+ private(set) internal var bar: Int {
96+ get { Foo.shared.bar }
97+ set { Foo.shared.bar = newValue }
98+ }
99+ }
59100""" )
60101] ,
61102 triggeringExamples: [
@@ -73,100 +114,131 @@ struct ExtensionAccessModifierRule: ASTRule, OptInRule {
73114""" ) ,
74115Example ( """
75116 public extension Foo {
76- public ↓func bar() {}
77- public ↓func baz() {}
117+ ↓public func bar() {}
118+ ↓public func baz() {}
119+ }
120+ """ ) ,
121+ Example ( """
122+ ↓extension Foo {
123+ public var bar: Int {
124+ let value = 1
125+ return value
126+ }
127+
128+ public var baz: Int { return 1 }
129+ }
130+ """ ) ,
131+ Example ( """
132+ ↓extension Array where Element: Equatable {
133+ public var unique: [Element] {
134+ var uniqueValues = [Element]()
135+ for item in self where !uniqueValues.contains(item) {
136+ uniqueValues.append(item)
137+ }
138+ return uniqueValues
139+ }
78140 }
79141""" ) ,
80142Example ( """
81143 ↓extension Foo {
144+ #if DEBUG
82145 public var bar: Int {
83146 let value = 1
84147 return value
85148 }
149+ #endif
86150
87151 public var baz: Int { return 1 }
88152 }
153+ """ ) ,
154+ Example ( """
155+ public extension Foo {
156+ ↓private func bar() {}
157+ ↓private func baz() {}
158+ }
89159""" )
90160]
91161)
162+ }
92163
93- func validate( file: SwiftLintFile , kind: SwiftDeclarationKind ,
94- dictionary: SourceKittenDictionary ) -> [ StyleViolation ] {
95- guard kind== . extension, let offset= dictionary. offset,
96- dictionary. inheritedTypes. isEmpty
97- else {
98- return [ ]
99- }
100-
101- let declarations = dictionary. substructure
102- . compactMap { entry-> ( acl: AccessControlLevel , offset: ByteCount ) ? in
103- guard let kind= entry. declarationKind,
104- kind!= . varLocal, kind!= . varParameter,
105- let offset= entry. offsetelse {
106- return nil
107- }
164+ private extension ExtensionAccessModifierRule {
165+ private enum ACL : Hashable {
166+ case implicit
167+ case explicit( TokenKind )
108168
109- return ( acl: entry. accessibility?? . internal, offset: offset)
169+ static func from( tokenKind: TokenKind ? ) -> ACL {
170+ switch tokenKind{
171+ case nil :
172+ return . implicit
173+ case let value? :
174+ return . explicit( value)
110175}
111-
112- let declarationsACLs = declarations. map { $0. acl} . unique
113- let allowedACLs : Set < AccessControlLevel > = [ . internal, . private, . open]
114- guard declarationsACLs. count== 1 , !allowedACLs. contains ( declarationsACLs [ 0 ] ) else {
115- return [ ]
116176}
117177
118- let syntaxTokens = file. syntaxMap. tokens
119- let parts = syntaxTokens. partitioned { offset<= $0. offset}
120- if let aclToken= parts. first. last, file. isACL ( token: aclToken) {
121- return declarationsViolations ( file: file, acl: declarationsACLs [ 0 ] ,
122- declarationOffsets: declarations. map { $0. offset} ,
123- dictionary: dictionary)
178+ static func isAllowed( _ acl: Self ) -> Bool {
179+ [
180+ . explicit( . keyword( . internal) ) ,
181+ . explicit( . keyword( . private) ) ,
182+ . explicit( . keyword( . open) ) ,
183+ . implicit
184+ ] . contains ( acl)
124185}
125-
126- return [
127- StyleViolation ( ruleDescription: Self . description,
128- severity: configuration. severity,
129- location: Location ( file: file, byteOffset: offset) )
130- ]
131186}
132187
133- private func declarationsViolations( file: SwiftLintFile , acl: AccessControlLevel ,
134- declarationOffsets: [ ByteCount ] ,
135- dictionary: SourceKittenDictionary ) -> [ StyleViolation ] {
136- guard let byteRange= dictionary. byteRange,
137- caselet contents= file. stringView,
138- let range= contents. byteRangeToNSRange ( byteRange) else {
139- return [ ]
140- }
188+ final class Visitor : ViolationsSyntaxVisitor < ConfigurationType > {
189+ override var skippableDeclarations : [ any DeclSyntaxProtocol . Type ] { . all}
141190
142- // find all ACL tokens
143- let allACLRanges = file. match ( pattern: acl. description, with: [ . attributeBuiltin] , range: range) . compactMap {
144- contents. NSRangeToByteRange ( start: $0. location, length: $0. length)
145- }
191+ override func visitPost( _ node: ExtensionDeclSyntax ) {
192+ guard node. inheritanceClause== nil else {
193+ return
194+ }
195+
196+ var areAllACLsEqual = true
197+ var aclTokens = [ ( position: AbsolutePosition, acl: ACL) ] ( )
146198
147- let violationOffsets = declarationOffsets. filter { typeOffsetin
148- // find the last ACL token before the type
149- guard let previousInternalByteRange= lastACLByteRange ( before: typeOffset, in: allACLRanges) else {
150- // didn't find a candidate token, so the ACL is implicit (not a violation)
151- return false
199+ for decl in node. memberBlock. expandingIfConfigs ( ) {
200+ let modifiers = decl. asProtocol ( ( any WithModifiersSyntax ) . self) ? . modifiers
201+ let aclToken = modifiers? . accessLevelModifier? . name
202+ let acl = ACL . from ( tokenKind: aclToken? . tokenKind)
203+ if areAllACLsEqual, acl!= aclTokens. last? . acl, aclTokens. isNotEmpty{
204+ areAllACLsEqual= false
205+ }
206+ aclTokens. append ( ( decl. positionAfterSkippingLeadingTrivia, acl) )
152207}
153208
154- // the ACL token correspond to the type if there're only
155- // attributeBuiltin (`final` for example) tokens between them
156- let length = typeOffset - previousInternalByteRange . location
157- let range = ByteRange ( location : previousInternalByteRange . location , length : length )
158- return Set ( file . syntaxMap . kinds ( inByteRange : range ) ) == [ . attributeBuiltin ]
159- }
209+ guard areAllACLsEqual , let lastACL = aclTokens . last else {
210+ return
211+ }
212+
213+ let isAllowedACL = ACL . isAllowed ( lastACL . acl )
214+ let extensionACL = ACL . from ( tokenKind : node . modifiers . accessLevelModifier ? . name . tokenKind )
160215
161- return violationOffsets. map {
162- StyleViolation ( ruleDescription: Self . description,
163- severity: configuration. severity,
164- location: Location ( file: file, byteOffset: $0) )
216+ if extensionACL!= . implicit{
217+ if !isAllowedACL || lastACL. acl!= extensionACL, lastACL. acl!= . implicit{
218+ violations. append ( contentsOf: aclTokens. map ( \. position) )
219+ }
220+ } else if !isAllowedACL{
221+ violations. append ( node. extensionKeyword. positionAfterSkippingLeadingTrivia)
222+ }
165223}
166224}
225+ }
167226
168- private func lastACLByteRange( before typeOffset: ByteCount , in ranges: [ ByteRange ] ) -> ByteRange ? {
169- let firstPartition = ranges. partitioned ( by: { $0. location> typeOffset} ) . first
170- return firstPartition. last
227+ private extension MemberBlockSyntax {
228+ func expandingIfConfigs( ) -> [ DeclSyntax ] {
229+ members. flatMap { memberin
230+ if let ifConfig= member. decl. as ( IfConfigDeclSyntax . self) {
231+ return ifConfig. clauses. flatMap { clausein
232+ switch clause. elements{
233+ case . decls( let decls) :
234+ return decls. map ( \. decl)
235+ default :
236+ return [ ]
237+ }
238+ }
239+ } else {
240+ return [ member. decl]
241+ }
242+ }
171243}
172244}