@@ -289,9 +289,9 @@ struct DiagnosticSpec {
289289}
290290
291291/// Assert that `location` is the same as that of `locationMarker` in `tree`.
292- func assertLocation< T : SyntaxProtocol > (
292+ func assertLocation(
293293 _ location: SourceLocation ,
294- in tree: T ,
294+ in tree: some SyntaxProtocol ,
295295 markerLocations: [ String : Int ] ,
296296 expectedLocationMarker locationMarker: String ,
297297 file: StaticString = #filePath,
@@ -315,9 +315,9 @@ func assertLocation<T: SyntaxProtocol>(
315315
316316/// Assert that the diagnostic `note` produced in `tree` matches `spec`,
317317/// using `markerLocations` to translate markers to actual source locations.
318- func assertNote< T : SyntaxProtocol > (
318+ func assertNote(
319319 _ note: Note ,
320- in tree: T ,
320+ in tree: some SyntaxProtocol ,
321321 markerLocations: [ String : Int ] ,
322322 expected spec: NoteSpec
323323) {
@@ -335,9 +335,9 @@ func assertNote<T: SyntaxProtocol>(
335335
336336/// Assert that the diagnostic `diag` produced in `tree` matches `spec`,
337337/// using `markerLocations` to translate markers to actual source locations.
338- func assertDiagnostic< T : SyntaxProtocol > (
338+ func assertDiagnostic(
339339 _ diag: Diagnostic ,
340- in tree: T ,
340+ in tree: some SyntaxProtocol ,
341341 markerLocations: [ String : Int ] ,
342342 expected spec: DiagnosticSpec
343343) {
@@ -491,9 +491,9 @@ public struct AssertParseOptions: OptionSet, Sendable {
491491extension ParserTestCase {
492492 /// After a test case has been mutated, assert that the mutated source
493493 /// round-trips and doesn’t hit any assertion failures in the parser.
494- fileprivate static func assertMutationRoundTrip< S : SyntaxProtocol > (
494+ fileprivate static func assertMutationRoundTrip(
495495 source: [ UInt8 ] ,
496- _ parse: ( inout Parser ) -> S ,
496+ _ parse: ( inout Parser ) -> some SyntaxProtocol ,
497497 swiftVersion: Parser . SwiftVersion ? ,
498498 experimentalFeatures: Parser . ExperimentalFeatures ,
499499 file: StaticString ,
@@ -524,14 +524,84 @@ extension ParserTestCase {
524524 }
525525 }
526526
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+
560+ /// Convenient version of `assertParse` that allows checking a single configuration of applied Fix-Its.
561+ ///
562+ /// - Parameters:
563+ /// - applyFixIts: Applies only the fix-its with these messages. Nil means applying all fix-its.
564+ /// - expectedFixedSource: Asserts that the source after applying fix-its matches
565+ /// this string.
566+ func assertParse(
567+ _ markedSource: String ,
568+ _ parse: @Sendable ( inout Parser ) -> some SyntaxProtocol = { SourceFileSyntax . parse ( from: & $0) } ,
569+ substructure expectedSubstructure: ( some SyntaxProtocol ) ? = Optional< Syntax> . none,
570+ substructureAfterMarker: String = " START " ,
571+ diagnostics expectedDiagnostics: [ DiagnosticSpec ] = [ ] ,
572+ applyFixIts: [ String ] ? = nil ,
573+ fixedSource expectedFixedSource: String ? = nil ,
574+ options: AssertParseOptions = [ ] ,
575+ swiftVersion: Parser . SwiftVersion ? = nil ,
576+ experimentalFeatures: Parser . ExperimentalFeatures ? = nil ,
577+ file: StaticString = #filePath,
578+ line: UInt = #line
579+ ) {
580+ assertParse (
581+ markedSource,
582+ parse,
583+ substructure: expectedSubstructure,
584+ substructureAfterMarker: substructureAfterMarker,
585+ diagnostics: expectedDiagnostics,
586+ fixItsApplications: FixItsApplication ( applyFixIts: applyFixIts, expectedFixedSource: expectedFixedSource) . map {
587+ [ $0]
588+ } ?? [ ] ,
589+ options: options,
590+ swiftVersion: swiftVersion,
591+ experimentalFeatures: experimentalFeatures,
592+ file: file,
593+ line: line
594+ )
595+ }
596+
527597 /// Verifies that parsing of `markedSource` produces expected results using a
528598 /// combination of various testing techniques:
529599 ///
530600 /// 1. Asserts that parsing of `markedSource` round-trips, ie. that the printed
531601 /// parsed tree is the same as the input.
532602 /// 2. Checks that parsing produces the expected list of diagnostics. If no
533603 /// diagnostics are passed, asserts that the input parses without any errors.
534- /// 3. Checks that applying all Fix-Its of the source code results in the
604+ /// 3. Checks that applying fix-its of the source code results in the
535605 /// expected fixed source, effectively testing the Fix-Its.
536606 /// 4. If a substructure is passed, asserts that the parsed tree contains a
537607 /// subtree of that structure.
@@ -558,21 +628,19 @@ extension ParserTestCase {
558628 /// - expectedDiagnostics: Asserts the given diagnostics were output, by default it
559629 /// asserts the parse was successful (ie. it has no diagnostics). Note
560630 /// that `DiagnosticsSpec` uses the location marked by `1️⃣` by default.
561- /// - applyFixIts: Applies only the fix-its with these messages.
562- /// - expectedFixedSource: Asserts that the source after applying fix-its matches
563- /// this string.
631+ /// - fixItsApplications: A list of `FixItsApplication` to check for whether applying certain fix-its to the source
632+ /// will generate a certain expected fixed source. An empty list means no fix-its are expected.
564633 /// - swiftVersion: The version of Swift using which the file should be parsed.
565634 /// Defaults to the latest version.
566635 /// - experimentalFeatures: A list of experimental features to enable, or
567636 /// `nil` to enable the default set of features provided by the test case.
568- func assertParse< S : SyntaxProtocol > (
637+ func assertParse(
569638 _ markedSource: String ,
570- _ parse: @Sendable ( inout Parser ) -> S = { SourceFileSyntax . parse ( from: & $0) } ,
639+ _ parse: @Sendable ( inout Parser ) -> some SyntaxProtocol = { SourceFileSyntax . parse ( from: & $0) } ,
571640 substructure expectedSubstructure: ( some SyntaxProtocol ) ? = Optional< Syntax> . none,
572641 substructureAfterMarker: String = " START " ,
573642 diagnostics expectedDiagnostics: [ DiagnosticSpec ] = [ ] ,
574- applyFixIts: [ String ] ? = nil ,
575- fixedSource expectedFixedSource: String ? = nil ,
643+ fixItsApplications: [ FixItsApplication ] = [ ] ,
576644 options: AssertParseOptions = [ ] ,
577645 swiftVersion: Parser . SwiftVersion ? = nil ,
578646 experimentalFeatures: Parser . ExperimentalFeatures ? = nil ,
@@ -591,7 +659,7 @@ extension ParserTestCase {
591659 parser. enableAlternativeTokenChoices ( )
592660 }
593661 #endif
594- let tree : S = parse ( & parser)
662+ let tree = parse ( & parser)
595663
596664 // Round-trip
597665 assertStringsEqualWithDiff (
@@ -640,27 +708,30 @@ extension ParserTestCase {
640708 }
641709
642710 // Applying Fix-Its
643- if expectedDiagnostics. contains ( where: { !$0. fixIts. isEmpty } ) && expectedFixedSource == nil {
711+ if expectedDiagnostics. contains ( where: { !$0. fixIts. isEmpty } ) && fixItsApplications . isEmpty {
644712 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 " )
713+ } else {
714+ for fixItsApplication in fixItsApplications {
715+ let applyFixIts = fixItsApplication. applyFixIts
716+ let fixedTree = FixItApplier . applyFixes ( from: diags, filterByMessages: applyFixIts, to: tree)
717+ var fixedTreeDescription = fixedTree. description
718+ if options. contains ( . normalizeNewlinesInFixedSource) {
719+ fixedTreeDescription =
720+ fixedTreeDescription
721+ . replacingOccurrences ( of: " \r \n " , with: " \n " )
722+ . replacingOccurrences ( of: " \r " , with: " \n " )
723+ }
724+ assertStringsEqualWithDiff (
725+ fixedTreeDescription. trimmingTrailingWhitespace ( ) ,
726+ fixItsApplication. expectedFixedSource. trimmingTrailingWhitespace ( ) ,
727+ " Applying \( applyFixIts? . description ?? " all Fix-Its " ) didn’t produce the expected fixed source " ,
728+ file: file,
729+ line: line
730+ )
653731 }
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- )
661732 }
662733
663- if expectedDiagnostics. allSatisfy ( { $0. fixIts. isEmpty } ) && expectedFixedSource != nil {
734+ if expectedDiagnostics. allSatisfy ( { $0. fixIts. isEmpty } ) && !fixItsApplications . isEmpty {
664735 XCTFail (
665736 " Fixed source was provided but the test case produces no diagnostics with Fix-Its " ,
666737 file: file,
@@ -747,9 +818,9 @@ class TriviaRemover: SyntaxRewriter {
747818 }
748819}
749820
750- func assertBasicFormat< S : SyntaxProtocol > (
821+ func assertBasicFormat(
751822 source: String ,
752- parse: ( inout Parser ) -> S ,
823+ parse: ( inout Parser ) -> some SyntaxProtocol ,
753824 swiftVersion: Parser . SwiftVersion ? ,
754825 experimentalFeatures: Parser . ExperimentalFeatures ,
755826 file: StaticString = #filePath,
0 commit comments