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,16 @@ 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 ( fileHeader, regularImports, declImports, implementationOnlyImports , testableImports, codeBlocks)
59
61
formattedLines. append ( contentsOf: joined)
60
62
61
63
regularImports = [ ]
62
64
declImports = [ ]
65
+ implementationOnlyImports = [ ]
63
66
testableImports = [ ]
64
67
codeBlocks = [ ]
65
68
fileHeader = [ ]
@@ -115,6 +118,11 @@ public final class OrderedImports: SyntaxFormatRule {
115
118
regularImports. append ( line)
116
119
commentBuffer = [ ]
117
120
121
+ case . implementationOnlyImport:
122
+ implementationOnlyImports. append ( contentsOf: commentBuffer)
123
+ implementationOnlyImports. append ( line)
124
+ commentBuffer = [ ]
125
+
118
126
case . testableImport:
119
127
testableImports. append ( contentsOf: commentBuffer)
120
128
testableImports. append ( line)
@@ -148,6 +156,7 @@ public final class OrderedImports: SyntaxFormatRule {
148
156
/// statements do not appear at the top of the file.
149
157
private func checkGrouping< C: Collection > ( _ lines: C ) where C. Element == Line {
150
158
var declGroup = false
159
+ var implementationOnlyGroup = false
151
160
var testableGroup = false
152
161
var codeGroup = false
153
162
@@ -157,6 +166,8 @@ public final class OrderedImports: SyntaxFormatRule {
157
166
switch lineType {
158
167
case . declImport:
159
168
declGroup = true
169
+ case . implementationOnlyImport:
170
+ implementationOnlyGroup = true
160
171
case . testableImport:
161
172
testableGroup = true
162
173
case . codeBlock:
@@ -166,15 +177,15 @@ public final class OrderedImports: SyntaxFormatRule {
166
177
167
178
if codeGroup {
168
179
switch lineType {
169
- case . regularImport, . declImport, . testableImport:
180
+ case . regularImport, . declImport, . implementationOnlyImport , . testableImport:
170
181
diagnose ( . placeAtTopOfFile, on: line. firstToken)
171
182
default : ( )
172
183
}
173
184
}
174
185
175
186
if testableGroup {
176
187
switch lineType {
177
- case . regularImport, . declImport:
188
+ case . regularImport, . declImport, . implementationOnlyImport :
178
189
diagnose (
179
190
. groupImports( before: lineType, after: LineType . testableImport) ,
180
191
on: line. firstToken
@@ -183,6 +194,17 @@ public final class OrderedImports: SyntaxFormatRule {
183
194
}
184
195
}
185
196
197
+ if implementationOnlyGroup {
198
+ switch lineType {
199
+ case . regularImport, . declImport:
200
+ diagnose (
201
+ . groupImports( before: lineType, after: LineType . implementationOnlyImport) ,
202
+ on: line. firstToken
203
+ )
204
+ default : ( )
205
+ }
206
+ }
207
+
186
208
if declGroup {
187
209
switch lineType {
188
210
case . regularImport:
@@ -208,7 +230,7 @@ public final class OrderedImports: SyntaxFormatRule {
208
230
209
231
for line in imports {
210
232
switch line. type {
211
- case . regularImport, . declImport, . testableImport:
233
+ case . regularImport, . declImport, . implementationOnlyImport , . testableImport:
212
234
let fullyQualifiedImport = line. fullyQualifiedImport
213
235
// Check for duplicate imports and potentially remove them.
214
236
if let previousMatchingImportIndex = visitedImports [ fullyQualifiedImport] {
@@ -390,6 +412,7 @@ fileprivate func convertToCodeBlockItems(lines: [Line]) -> [CodeBlockItemSyntax]
390
412
public enum LineType : CustomStringConvertible {
391
413
case regularImport
392
414
case declImport
415
+ case implementationOnlyImport
393
416
case testableImport
394
417
case codeBlock
395
418
case comment
@@ -401,6 +424,8 @@ public enum LineType: CustomStringConvertible {
401
424
return " regular "
402
425
case . declImport:
403
426
return " declaration "
427
+ case . implementationOnlyImport:
428
+ return " implementationOnly "
404
429
case . testableImport:
405
430
return " testable "
406
431
case . codeBlock:
@@ -515,12 +540,16 @@ fileprivate class Line {
515
540
516
541
/// Returns a `LineType` the represents the type of import from the given import decl.
517
542
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
- {
543
+
544
+ let importIdentifierTypes = importDecl. attributes. compactMap { $0. as ( AttributeSyntax . self) ? . attributeName }
545
+ let importAttributeNames = importIdentifierTypes. compactMap { $0. as ( IdentifierTypeSyntax . self) ? . name. text }
546
+
547
+ if importAttributeNames. contains ( " testable " ) {
522
548
return . testableImport
523
549
}
550
+ if importAttributeNames. contains ( " _implementationOnly " ) {
551
+ return . implementationOnlyImport
552
+ }
524
553
if importDecl. importKindSpecifier != nil {
525
554
return . declImport
526
555
}
0 commit comments