@@ -74,6 +74,20 @@ fileprivate extension Child {
74
74
func hasSameType( as other: Child ) -> Bool {
75
75
return name == other. name && kind. hasSameType ( as: other. kind) && isOptional == other. isOptional
76
76
}
77
+
78
+ func isFollowedByColonToken( in node: LayoutNode ) -> Bool {
79
+ guard let childIndex = node. children. firstIndex ( where: { $0. name == self . name } ) else {
80
+ preconditionFailure ( " \( self . name) is not a child of \( node. kind. syntaxType) " )
81
+ }
82
+ guard childIndex + 2 < node. children. count else {
83
+ return false
84
+ }
85
+ if case . token( choices: [ . token( tokenKind: " ColonToken " ) ] , _, _) = node. children [ childIndex + 2 ] . kind {
86
+ return true
87
+ } else {
88
+ return false
89
+ }
90
+ }
77
91
}
78
92
79
93
class ValidateSyntaxNodes : XCTestCase {
@@ -102,13 +116,13 @@ class ValidateSyntaxNodes: XCTestCase {
102
116
/// Implementation detail of `testSingleTokenChoiceChildNaming`, validating a single child.
103
117
///
104
118
/// - Returns: A failure message if validation failed, otherwise `nil`
105
- private func validateSingleTokenChoiceChild( child: Child , childIndex : Int , in node: LayoutNode ) -> String ? {
119
+ private func validateSingleTokenChoiceChild( child: Child , in node: LayoutNode ) -> String ? {
106
120
guard case . token( choices: let tokenChoices, _, _) = child. kind, let choice = tokenChoices. only else {
107
121
return nil
108
122
}
109
123
switch choice {
110
124
case . keyword( text: let keyword) :
111
- if childIndex + 2 < node . children . count , case . token ( choices : [ . token ( tokenKind : " ColonToken " ) ] , _ , _ ) = node. children [ childIndex + 2 ] . kind {
125
+ if child . isFollowedByColonToken ( in : node) {
112
126
if child. name != " \( keyword. withFirstCharacterUppercased) Label " {
113
127
return
114
128
" child ' \( child. name) ' has a single keyword as its only token choice and is followed by a colon. It should thus be named ' \( keyword. withFirstCharacterUppercased) Label' "
@@ -148,8 +162,8 @@ class ValidateSyntaxNodes: XCTestCase {
148
162
func testSingleTokenChoiceChildNaming( ) {
149
163
var failures : [ ValidationFailure ] = [ ]
150
164
for node in SYNTAX_NODES . compactMap ( \. layoutNode) {
151
- for (childIndex , child) in node. children. enumerated ( ) {
152
- if let failureMessage = validateSingleTokenChoiceChild ( child: child, childIndex : childIndex , in: node) {
165
+ for child in node. children {
166
+ if let failureMessage = validateSingleTokenChoiceChild ( child: child, in: node) {
153
167
failures. append ( ValidationFailure ( node: node. kind, message: failureMessage) )
154
168
}
155
169
}
@@ -189,11 +203,11 @@ class ValidateSyntaxNodes: XCTestCase {
189
203
// If there are two tokens of the same kind in a node, we can't follow the naming rule without conflict
190
204
ValidationFailure (
191
205
node: . differentiableAttributeArguments,
192
- message: " child 'DiffKindComma ' has a comma keyword as its only token choice and should thus be named 'Comma' or 'TrailingComma' "
206
+ message: " child 'KindSpecifierComma ' has a comma keyword as its only token choice and should thus be named 'Comma' or 'TrailingComma' "
193
207
) ,
194
208
ValidationFailure (
195
209
node: . differentiableAttributeArguments,
196
- message: " child 'DiffParamsComma ' has a comma keyword as its only token choice and should thus be named 'Comma' or 'TrailingComma' "
210
+ message: " child 'ParametersComma ' has a comma keyword as its only token choice and should thus be named 'Comma' or 'TrailingComma' "
197
211
) ,
198
212
ValidationFailure (
199
213
node: . poundSourceLocationArgs,
@@ -292,79 +306,75 @@ class ValidateSyntaxNodes: XCTestCase {
292
306
var failures : [ ValidationFailure ] = [ ]
293
307
for node in SYNTAX_NODES . compactMap ( \. layoutNode) {
294
308
for child in node. children {
295
- if case . token( choices: let tokenChoices, _, _) = child. kind,
296
- tokenChoices. count > 1 , // single token choices are handled by `validateSingleTokenChoiceChildNaming`
297
- tokenChoices. allSatisfy ( { $0. isKeyword } ) ,
298
- !child. name. hasSuffix ( " Keyword " )
299
- {
300
- failures. append (
301
- ValidationFailure (
302
- node: node. kind,
303
- message: " child ' \( child. name) ' only has keywords as its token choices and should thus and with 'Keyword' "
304
- )
305
- )
309
+ guard case . token( choices: let tokenChoices, _, _) = child. kind,
310
+ tokenChoices. count > 1 ,
311
+ tokenChoices. allSatisfy ( { $0. isKeyword } )
312
+ else {
313
+ // Not a child with only keyword choices
314
+ // Single token choices are handled by `validateSingleTokenChoiceChildNaming`
315
+ continue
316
+ }
317
+ var failureMessage : String ?
318
+ if child. isFollowedByColonToken ( in: node) {
319
+ if !child. name. hasSuffix ( " Label " ) {
320
+ failureMessage = " child ' \( child. name) ' only has keywords as its token choices, is followed by a colon and should thus end with 'Label' "
321
+ }
322
+ } else {
323
+ if !child. name. hasSuffix ( " Specifier " ) {
324
+ failureMessage = " child ' \( child. name) ' only has keywords as its token choices and should thus end with 'Specifier' "
325
+ }
326
+ }
327
+ if let failureMessage {
328
+ failures. append ( ValidationFailure ( node: node. kind, message: failureMessage) )
306
329
}
307
330
}
308
331
}
309
332
310
333
assertFailuresMatchXFails (
311
334
failures,
312
335
expectedFailures: [
313
- ValidationFailure ( node: . accessorDecl, message: " child 'AccessorKind' only has keywords as its token choices and should thus and with 'Keyword' " ) ,
314
- ValidationFailure ( node: . attributedType, message: " child 'Specifier' only has keywords as its token choices and should thus and with 'Keyword' " ) ,
336
+ // MARK: Only one non-deprecated keyword
315
337
ValidationFailure (
316
- node: . availabilityLabeledArgument,
317
- message: " child 'Label' only has keywords as its token choices and should thus and with 'Keyword' "
338
+ node: . discardStmt,
339
+ message: " child 'DiscardKeyword' only has keywords as its token choices and should thus end with 'Specifier' "
340
+ // DiscardKeyword can be 'discard' or '_forget' and '_forget' is deprecated
318
341
) ,
319
342
ValidationFailure (
320
- node: . booleanLiteralExpr,
321
- message: " child 'BooleanLiteral' only has keywords as its token choices and should thus and with 'Keyword' "
322
- ) ,
323
- ValidationFailure ( node: . canImportVersionInfo, message: " child 'Label' only has keywords as its token choices and should thus and with 'Keyword' " ) ,
324
- ValidationFailure (
325
- node: . closureCaptureItemSpecifier,
326
- message: " child 'Specifier' only has keywords as its token choices and should thus and with 'Keyword' "
327
- ) ,
328
- ValidationFailure (
329
- node: . closureCaptureItemSpecifier,
330
- message: " child 'Detail' only has keywords as its token choices and should thus and with 'Keyword' "
343
+ node: . moveExpr,
344
+ message: " child 'ConsumeKeyword' only has keywords as its token choices and should thus end with 'Specifier' "
345
+ // ConsumeKeyword can be 'consume' or '_move' and '_move' is deprecated
331
346
) ,
347
+
348
+ // MARK: Conceptually a value, not a specifier
332
349
ValidationFailure (
333
- node: . constrainedSugarType,
334
- message: " child 'SomeOrAnySpecifier' only has keywords as its token choices and should thus and with 'Keyword' "
335
- ) ,
336
- ValidationFailure ( node: . declModifier, message: " child 'Name' only has keywords as its token choices and should thus and with 'Keyword' " ) ,
337
- ValidationFailure (
338
- node: . derivativeRegistrationAttributeArguments,
339
- message: " child 'AccessorKind' only has keywords as its token choices and should thus and with 'Keyword' "
350
+ node: . booleanLiteralExpr,
351
+ message: " child 'Literal' only has keywords as its token choices and should thus end with 'Specifier' "
352
+ // TrueOrFalseKeyword would be a stupid name here
340
353
) ,
341
354
ValidationFailure (
342
- node: . differentiableAttributeArguments ,
343
- message: " child 'DiffKind ' only has keywords as its token choices and should thus and with 'Keyword ' "
355
+ node: . precedenceGroupAssignment ,
356
+ message: " child 'Value ' only has keywords as its token choices and should thus end with 'Specifier ' "
344
357
) ,
345
358
ValidationFailure (
346
- node: . documentationAttributeArgument ,
347
- message: " child 'Label ' only has keywords as its token choices and should thus and with 'Keyword ' "
359
+ node: . precedenceGroupAssociativity ,
360
+ message: " child 'Value ' only has keywords as its token choices and should thus end with 'Specifier ' "
348
361
) ,
362
+
363
+ // MARK: Miscellaneous
364
+ // 'weak' or 'unowned' are already the specifier, this is the detail in parens
349
365
ValidationFailure (
350
- node: . functionEffectSpecifiers ,
351
- message: " child 'AsyncSpecifier ' only has keywords as its token choices and should thus and with 'Keyword ' "
366
+ node: . closureCaptureItemSpecifier ,
367
+ message: " child 'Detail ' only has keywords as its token choices and should thus end with 'Specifier ' "
352
368
) ,
369
+ // This really is the modifier name and not a specifier
353
370
ValidationFailure (
354
- node: . functionEffectSpecifiers ,
355
- message: " child 'ThrowsSpecifier ' only has keywords as its token choices and should thus and with 'Keyword ' "
371
+ node: . declModifier ,
372
+ message: " child 'Name ' only has keywords as its token choices and should thus end with 'Specifier ' "
356
373
) ,
357
- ValidationFailure ( node : . importDecl , message : " child 'ImportKind' only has keywords as its token choices and should thus and with 'Keyword' " ) ,
374
+ // Conceptually, this isn't a specifier, it's more like a type inheritance
358
375
ValidationFailure (
359
376
node: . layoutRequirement,
360
- message: " child 'LayoutConstraint' only has keywords as its token choices and should thus and with 'Keyword' "
361
- ) ,
362
- ValidationFailure ( node: . metatypeType, message: " child 'TypeOrProtocol' only has keywords as its token choices and should thus and with 'Keyword' " ) ,
363
- ValidationFailure ( node: . operatorDecl, message: " child 'Fixity' only has keywords as its token choices and should thus and with 'Keyword' " ) ,
364
- ValidationFailure ( node: . precedenceGroupAssignment, message: " child 'Flag' only has keywords as its token choices and should thus and with 'Keyword' " ) ,
365
- ValidationFailure (
366
- node: . precedenceGroupAssociativity,
367
- message: " child 'Value' only has keywords as its token choices and should thus and with 'Keyword' "
377
+ message: " child 'LayoutConstraint' only has keywords as its token choices and should thus end with 'Specifier' "
368
378
) ,
369
379
]
370
380
)
0 commit comments