13
13
import SwiftSyntax
14
14
15
15
/// Imports must be lexicographically ordered and logically grouped at the top of each source file.
16
- /// The order of the import groups is 1) regular imports, 2) declaration imports, and 3) @testable
17
- /// imports. These groups are separated by a single blank line. Blank lines in between the import
18
- /// declarations are removed.
16
+ /// The order of the import groups is 1) regular imports, 2) declaration imports, 3) @_implementationOnly
17
+ /// imports, and 4) @testable imports . These groups are separated by a single blank line. Blank lines in
18
+ /// between the import declarations are removed.
19
19
///
20
20
/// Lint: If an import appears anywhere other than the beginning of the file it resides in,
21
21
/// not lexicographically ordered, or not in the appropriate import group, a lint error is
@@ -34,6 +34,7 @@ public final class OrderedImports: SyntaxFormatRule {
34
34
35
35
var regularImports : [ Line ] = [ ]
36
36
var declImports : [ Line ] = [ ]
37
+ var implementationOnlyImports : [ Line ] = [ ]
37
38
var testableImports : [ Line ] = [ ]
38
39
var codeBlocks : [ Line ] = [ ]
39
40
var fileHeader : [ Line ] = [ ]
@@ -52,14 +53,23 @@ public final class OrderedImports: SyntaxFormatRule {
52
53
53
54
regularImports = formatImports ( regularImports)
54
55
declImports = formatImports ( declImports)
56
+ implementationOnlyImports = formatImports ( implementationOnlyImports)
55
57
testableImports = formatImports ( testableImports)
56
58
formatCodeblocks ( & codeBlocks)
57
59
58
- let joined = joinLines ( fileHeader, regularImports, declImports, testableImports, codeBlocks)
60
+ let joined = joinLines (
61
+ fileHeader,
62
+ regularImports,
63
+ declImports,
64
+ implementationOnlyImports,
65
+ testableImports,
66
+ codeBlocks
67
+ )
59
68
formattedLines. append ( contentsOf: joined)
60
69
61
70
regularImports = [ ]
62
71
declImports = [ ]
72
+ implementationOnlyImports = [ ]
63
73
testableImports = [ ]
64
74
codeBlocks = [ ]
65
75
fileHeader = [ ]
@@ -115,6 +125,11 @@ public final class OrderedImports: SyntaxFormatRule {
115
125
regularImports. append ( line)
116
126
commentBuffer = [ ]
117
127
128
+ case . implementationOnlyImport:
129
+ implementationOnlyImports. append ( contentsOf: commentBuffer)
130
+ implementationOnlyImports. append ( line)
131
+ commentBuffer = [ ]
132
+
118
133
case . testableImport:
119
134
testableImports. append ( contentsOf: commentBuffer)
120
135
testableImports. append ( line)
@@ -148,6 +163,7 @@ public final class OrderedImports: SyntaxFormatRule {
148
163
/// statements do not appear at the top of the file.
149
164
private func checkGrouping< C: Collection > ( _ lines: C ) where C. Element == Line {
150
165
var declGroup = false
166
+ var implementationOnlyGroup = false
151
167
var testableGroup = false
152
168
var codeGroup = false
153
169
@@ -157,6 +173,8 @@ public final class OrderedImports: SyntaxFormatRule {
157
173
switch lineType {
158
174
case . declImport:
159
175
declGroup = true
176
+ case . implementationOnlyImport:
177
+ implementationOnlyGroup = true
160
178
case . testableImport:
161
179
testableGroup = true
162
180
case . codeBlock:
@@ -166,15 +184,15 @@ public final class OrderedImports: SyntaxFormatRule {
166
184
167
185
if codeGroup {
168
186
switch lineType {
169
- case . regularImport, . declImport, . testableImport:
187
+ case . regularImport, . declImport, . implementationOnlyImport , . testableImport:
170
188
diagnose ( . placeAtTopOfFile, on: line. firstToken)
171
189
default : ( )
172
190
}
173
191
}
174
192
175
193
if testableGroup {
176
194
switch lineType {
177
- case . regularImport, . declImport:
195
+ case . regularImport, . declImport, . implementationOnlyImport :
178
196
diagnose (
179
197
. groupImports( before: lineType, after: LineType . testableImport) ,
180
198
on: line. firstToken
@@ -183,6 +201,17 @@ public final class OrderedImports: SyntaxFormatRule {
183
201
}
184
202
}
185
203
204
+ if implementationOnlyGroup {
205
+ switch lineType {
206
+ case . regularImport, . declImport:
207
+ diagnose (
208
+ . groupImports( before: lineType, after: LineType . implementationOnlyImport) ,
209
+ on: line. firstToken
210
+ )
211
+ default : ( )
212
+ }
213
+ }
214
+
186
215
if declGroup {
187
216
switch lineType {
188
217
case . regularImport:
@@ -208,7 +237,7 @@ public final class OrderedImports: SyntaxFormatRule {
208
237
209
238
for line in imports {
210
239
switch line. type {
211
- case . regularImport, . declImport, . testableImport:
240
+ case . regularImport, . declImport, . implementationOnlyImport , . testableImport:
212
241
let fullyQualifiedImport = line. fullyQualifiedImport
213
242
// Check for duplicate imports and potentially remove them.
214
243
if let previousMatchingImportIndex = visitedImports [ fullyQualifiedImport] {
@@ -390,6 +419,7 @@ fileprivate func convertToCodeBlockItems(lines: [Line]) -> [CodeBlockItemSyntax]
390
419
public enum LineType : CustomStringConvertible {
391
420
case regularImport
392
421
case declImport
422
+ case implementationOnlyImport
393
423
case testableImport
394
424
case codeBlock
395
425
case comment
@@ -401,6 +431,8 @@ public enum LineType: CustomStringConvertible {
401
431
return " regular "
402
432
case . declImport:
403
433
return " declaration "
434
+ case . implementationOnlyImport:
435
+ return " implementationOnly "
404
436
case . testableImport:
405
437
return " testable "
406
438
case . codeBlock:
@@ -515,12 +547,16 @@ fileprivate class Line {
515
547
516
548
/// Returns a `LineType` the represents the type of import from the given import decl.
517
549
private func importType( of importDecl: ImportDeclSyntax ) -> LineType {
518
- if let attr = importDecl. attributes. firstToken ( viewMode: . sourceAccurate) ,
519
- attr. tokenKind == . atSign,
520
- attr. nextToken ( viewMode: . sourceAccurate) ? . text == " testable "
521
- {
550
+
551
+ let importIdentifierTypes = importDecl. attributes. compactMap { $0. as ( AttributeSyntax . self) ? . attributeName }
552
+ let importAttributeNames = importIdentifierTypes. compactMap { $0. as ( IdentifierTypeSyntax . self) ? . name. text }
553
+
554
+ if importAttributeNames. contains ( " testable " ) {
522
555
return . testableImport
523
556
}
557
+ if importAttributeNames. contains ( " _implementationOnly " ) {
558
+ return . implementationOnlyImport
559
+ }
524
560
if importDecl. importKindSpecifier != nil {
525
561
return . declImport
526
562
}
0 commit comments