@@ -289,9 +289,9 @@ struct DiagnosticSpec {
289
289
}
290
290
291
291
/// Assert that `location` is the same as that of `locationMarker` in `tree`.
292
- func assertLocation< T : SyntaxProtocol > (
292
+ func assertLocation(
293
293
_ location: SourceLocation ,
294
- in tree: T ,
294
+ in tree: some SyntaxProtocol ,
295
295
markerLocations: [ String : Int ] ,
296
296
expectedLocationMarker locationMarker: String ,
297
297
file: StaticString = #filePath,
@@ -315,9 +315,9 @@ func assertLocation<T: SyntaxProtocol>(
315
315
316
316
/// Assert that the diagnostic `note` produced in `tree` matches `spec`,
317
317
/// using `markerLocations` to translate markers to actual source locations.
318
- func assertNote< T : SyntaxProtocol > (
318
+ func assertNote(
319
319
_ note: Note ,
320
- in tree: T ,
320
+ in tree: some SyntaxProtocol ,
321
321
markerLocations: [ String : Int ] ,
322
322
expected spec: NoteSpec
323
323
) {
@@ -335,9 +335,9 @@ func assertNote<T: SyntaxProtocol>(
335
335
336
336
/// Assert that the diagnostic `diag` produced in `tree` matches `spec`,
337
337
/// using `markerLocations` to translate markers to actual source locations.
338
- func assertDiagnostic< T : SyntaxProtocol > (
338
+ func assertDiagnostic(
339
339
_ diag: Diagnostic ,
340
- in tree: T ,
340
+ in tree: some SyntaxProtocol ,
341
341
markerLocations: [ String : Int ] ,
342
342
expected spec: DiagnosticSpec
343
343
) {
@@ -491,9 +491,9 @@ public struct AssertParseOptions: OptionSet, Sendable {
491
491
extension ParserTestCase {
492
492
/// After a test case has been mutated, assert that the mutated source
493
493
/// round-trips and doesn’t hit any assertion failures in the parser.
494
- fileprivate static func assertMutationRoundTrip< S : SyntaxProtocol > (
494
+ fileprivate static func assertMutationRoundTrip(
495
495
source: [ UInt8 ] ,
496
- _ parse: ( inout Parser ) -> S ,
496
+ _ parse: ( inout Parser ) -> some SyntaxProtocol ,
497
497
swiftVersion: Parser . SwiftVersion ? ,
498
498
experimentalFeatures: Parser . ExperimentalFeatures ,
499
499
file: StaticString ,
@@ -524,6 +524,39 @@ extension ParserTestCase {
524
524
}
525
525
}
526
526
527
+ enum FixItsApplication {
528
+ /// Apply only the fix-its whose messages are included in `applyFixIts` to generate `fixedSource`.
529
+ case optIn( applyFixIts: [ String ] , fixedSource: String )
530
+ /// Apply all fix-its to generate `fixedSource`.
531
+ case all( fixedSource: String )
532
+
533
+ init ? ( applyFixIts: [ String ] ? , expectedFixedSource: String ? ) {
534
+ if let applyFixIts {
535
+ self = . optIn( applyFixIts: applyFixIts, fixedSource: expectedFixedSource ?? " " )
536
+ } else if let expectedFixedSource {
537
+ self = . all( fixedSource: expectedFixedSource)
538
+ } else {
539
+ return nil
540
+ }
541
+ }
542
+
543
+ var applyFixIts : [ String ] ? {
544
+ switch self {
545
+ case . optIn( let applyFixIts, _) :
546
+ return applyFixIts
547
+ case . all:
548
+ return nil
549
+ }
550
+ }
551
+
552
+ var expectedFixedSource : String {
553
+ switch self {
554
+ case . optIn( _, let fixedSource) , . all( let fixedSource) :
555
+ return fixedSource
556
+ }
557
+ }
558
+ }
559
+
527
560
/// Verifies that parsing of `markedSource` produces expected results using a
528
561
/// combination of various testing techniques:
529
562
///
@@ -558,16 +591,16 @@ extension ParserTestCase {
558
591
/// - expectedDiagnostics: Asserts the given diagnostics were output, by default it
559
592
/// asserts the parse was successful (ie. it has no diagnostics). Note
560
593
/// that `DiagnosticsSpec` uses the location marked by `1️⃣` by default.
561
- /// - applyFixIts: Applies only the fix-its with these messages.
594
+ /// - applyFixIts: Applies only the fix-its with these messages. Nil means applying all fix-its.
562
595
/// - expectedFixedSource: Asserts that the source after applying fix-its matches
563
596
/// this string.
564
597
/// - swiftVersion: The version of Swift using which the file should be parsed.
565
598
/// Defaults to the latest version.
566
599
/// - experimentalFeatures: A list of experimental features to enable, or
567
600
/// `nil` to enable the default set of features provided by the test case.
568
- func assertParse< S : SyntaxProtocol > (
601
+ func assertParse(
569
602
_ markedSource: String ,
570
- _ parse: @Sendable ( inout Parser ) -> S = { SourceFileSyntax . parse ( from: & $0) } ,
603
+ _ parse: @Sendable ( inout Parser ) -> some SyntaxProtocol = { SourceFileSyntax . parse ( from: & $0) } ,
571
604
substructure expectedSubstructure: ( some SyntaxProtocol ) ? = Optional< Syntax> . none,
572
605
substructureAfterMarker: String = " START " ,
573
606
diagnostics expectedDiagnostics: [ DiagnosticSpec ] = [ ] ,
@@ -578,6 +611,76 @@ extension ParserTestCase {
578
611
experimentalFeatures: Parser . ExperimentalFeatures ? = nil ,
579
612
file: StaticString = #filePath,
580
613
line: UInt = #line
614
+ ) {
615
+ assertParse (
616
+ markedSource,
617
+ parse,
618
+ substructure: expectedSubstructure,
619
+ substructureAfterMarker: substructureAfterMarker,
620
+ diagnostics: expectedDiagnostics,
621
+ fixItsApplications: FixItsApplication ( applyFixIts: applyFixIts, expectedFixedSource: expectedFixedSource) . map {
622
+ [ $0]
623
+ } ?? [ ] ,
624
+ options: options,
625
+ swiftVersion: swiftVersion,
626
+ experimentalFeatures: experimentalFeatures,
627
+ file: file,
628
+ line: line
629
+ )
630
+ }
631
+
632
+ /// Verifies that parsing of `markedSource` produces expected results using a
633
+ /// combination of various testing techniques:
634
+ ///
635
+ /// 1. Asserts that parsing of `markedSource` round-trips, ie. that the printed
636
+ /// parsed tree is the same as the input.
637
+ /// 2. Checks that parsing produces the expected list of diagnostics. If no
638
+ /// diagnostics are passed, asserts that the input parses without any errors.
639
+ /// 3. Checks that applying fix-its of the source code results in the
640
+ /// expected fixed source, effectively testing the Fix-Its.
641
+ /// 4. If a substructure is passed, asserts that the parsed tree contains a
642
+ /// subtree of that structure.
643
+ /// 5. Mutates the test input by flipping each token's presence (ie. for every
644
+ /// token, either remove it from the input if it is present in the parsed
645
+ /// tree or synthesize it if it was missing) and verifies that this
646
+ /// mutated input round-trips. This test is disabled if the
647
+ /// `SKIP_LONG_TESTS` environment variable is set.
648
+ /// 6. If swift-syntax is compiled with the
649
+ /// `SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION` environment variable
650
+ /// set, mutates the input based on tokens the parse is trying to parse.
651
+ /// See the *Test Case Mutation* section in CONTRIBUTING.md.
652
+ ///
653
+ ///
654
+ /// - Parameters:
655
+ /// - markedSource: source that can include markers of the form `1️⃣`,
656
+ /// to be used as locations in the following parameters.
657
+ /// - parse: The function with which the source code should be parsed.
658
+ /// Defaults to parsing as a source file.
659
+ /// - expectedSubstructure: Asserts the parsed syntax tree contains this structure.
660
+ /// - substructureAfterMarker: Changes the position to start the structure
661
+ /// assertion from, ie. allows matching a particular substructure rather
662
+ /// than the whole source.
663
+ /// - expectedDiagnostics: Asserts the given diagnostics were output, by default it
664
+ /// asserts the parse was successful (ie. it has no diagnostics). Note
665
+ /// that `DiagnosticsSpec` uses the location marked by `1️⃣` by default.
666
+ /// - fixItsApplications: A list of `FixItsApplication` to check for whether applying certain fix-its to the source
667
+ /// will generate a certain expected fixed source. An empty list means no fix-its are expected.
668
+ /// - swiftVersion: The version of Swift using which the file should be parsed.
669
+ /// Defaults to the latest version.
670
+ /// - experimentalFeatures: A list of experimental features to enable, or
671
+ /// `nil` to enable the default set of features provided by the test case.
672
+ func assertParse(
673
+ _ markedSource: String ,
674
+ _ parse: @Sendable ( inout Parser ) -> some SyntaxProtocol = { SourceFileSyntax . parse ( from: & $0) } ,
675
+ substructure expectedSubstructure: ( some SyntaxProtocol ) ? = Optional< Syntax> . none,
676
+ substructureAfterMarker: String = " START " ,
677
+ diagnostics expectedDiagnostics: [ DiagnosticSpec ] = [ ] ,
678
+ fixItsApplications: [ FixItsApplication ] = [ ] ,
679
+ options: AssertParseOptions = [ ] ,
680
+ swiftVersion: Parser . SwiftVersion ? = nil ,
681
+ experimentalFeatures: Parser . ExperimentalFeatures ? = nil ,
682
+ file: StaticString = #filePath,
683
+ line: UInt = #line
581
684
) {
582
685
let experimentalFeatures = experimentalFeatures ?? self . experimentalFeatures
583
686
@@ -591,7 +694,7 @@ extension ParserTestCase {
591
694
parser. enableAlternativeTokenChoices ( )
592
695
}
593
696
#endif
594
- let tree : S = parse ( & parser)
697
+ let tree = parse ( & parser)
595
698
596
699
// Round-trip
597
700
assertStringsEqualWithDiff (
@@ -640,27 +743,30 @@ extension ParserTestCase {
640
743
}
641
744
642
745
// Applying Fix-Its
643
- if expectedDiagnostics. contains ( where: { !$0. fixIts. isEmpty } ) && expectedFixedSource == nil {
746
+ if expectedDiagnostics. contains ( where: { !$0. fixIts. isEmpty } ) && fixItsApplications . isEmpty {
644
747
XCTFail ( " Expected a fixed source if the test case produces diagnostics with Fix-Its " , file: file, line: line)
645
- } else if let expectedFixedSource = expectedFixedSource {
646
- let fixedTree = FixItApplier . applyFixes ( from: diags, filterByMessages: applyFixIts, to: tree)
647
- var fixedTreeDescription = fixedTree. description
648
- if options. contains ( . normalizeNewlinesInFixedSource) {
649
- fixedTreeDescription =
650
- fixedTreeDescription
651
- . replacingOccurrences ( of: " \r \n " , with: " \n " )
652
- . replacingOccurrences ( of: " \r " , with: " \n " )
748
+ } else {
749
+ for fixItsApplication in fixItsApplications {
750
+ let applyFixIts = fixItsApplication. applyFixIts
751
+ let fixedTree = FixItApplier . applyFixes ( from: diags, filterByMessages: applyFixIts, to: tree)
752
+ var fixedTreeDescription = fixedTree. description
753
+ if options. contains ( . normalizeNewlinesInFixedSource) {
754
+ fixedTreeDescription =
755
+ fixedTreeDescription
756
+ . replacingOccurrences ( of: " \r \n " , with: " \n " )
757
+ . replacingOccurrences ( of: " \r " , with: " \n " )
758
+ }
759
+ assertStringsEqualWithDiff (
760
+ fixedTreeDescription. trimmingTrailingWhitespace ( ) ,
761
+ fixItsApplication. expectedFixedSource. trimmingTrailingWhitespace ( ) ,
762
+ " Applying \( applyFixIts? . description ?? " all Fix-Its " ) didn’t produce the expected fixed source " ,
763
+ file: file,
764
+ line: line
765
+ )
653
766
}
654
- assertStringsEqualWithDiff (
655
- fixedTreeDescription. trimmingTrailingWhitespace ( ) ,
656
- expectedFixedSource. trimmingTrailingWhitespace ( ) ,
657
- " Applying all Fix-Its didn’t produce the expected fixed source " ,
658
- file: file,
659
- line: line
660
- )
661
767
}
662
768
663
- if expectedDiagnostics. allSatisfy ( { $0. fixIts. isEmpty } ) && expectedFixedSource != nil {
769
+ if expectedDiagnostics. allSatisfy ( { $0. fixIts. isEmpty } ) && !fixItsApplications . isEmpty {
664
770
XCTFail (
665
771
" Fixed source was provided but the test case produces no diagnostics with Fix-Its " ,
666
772
file: file,
@@ -747,9 +853,9 @@ class TriviaRemover: SyntaxRewriter {
747
853
}
748
854
}
749
855
750
- func assertBasicFormat< S : SyntaxProtocol > (
856
+ func assertBasicFormat(
751
857
source: String ,
752
- parse: ( inout Parser ) -> S ,
858
+ parse: ( inout Parser ) -> some SyntaxProtocol ,
753
859
swiftVersion: Parser . SwiftVersion ? ,
754
860
experimentalFeatures: Parser . ExperimentalFeatures ,
755
861
file: StaticString = #filePath,
0 commit comments