@@ -446,6 +446,39 @@ func assertDiagnostic<T: SyntaxProtocol>(
446446 }
447447}
448448
449+ class MutatedTreePrinter : SyntaxVisitor {
450+ private var mutations : [ Int : TokenSpec ] = [ : ]
451+ private var printedSource : [ UInt8 ] = [ ]
452+
453+ /// Prints `tree` by replacing the tokens whose offset is in `mutations` by
454+ /// a token that matches the corresponding `TokenSpec`.
455+ static func print( tree: Syntax , mutations: [ Int : TokenSpec ] ) -> [ UInt8 ] {
456+ let printer = MutatedTreePrinter ( mutations: mutations)
457+ printer. walk ( tree)
458+ return printer. printedSource
459+ }
460+
461+ private init ( mutations: [ Int : TokenSpec ] ) {
462+ self . mutations = mutations
463+ super. init ( viewMode: . sourceAccurate)
464+ }
465+
466+ override func visit( _ node: TokenSyntax ) -> SyntaxVisitorContinueKind {
467+ if let mutation = mutations [ node. positionAfterSkippingLeadingTrivia. utf8Offset] {
468+ let token = TokenSyntax (
469+ mutation. synthesizedTokenKind,
470+ leadingTrivia: node. leadingTrivia,
471+ trailingTrivia: node. trailingTrivia,
472+ presence: . present
473+ )
474+ printedSource. append ( contentsOf: token. syntaxTextBytes)
475+ return . skipChildren
476+ }
477+ printedSource. append ( contentsOf: node. syntaxTextBytes)
478+ return . skipChildren
479+ }
480+ }
481+
449482public struct AssertParseOptions : OptionSet {
450483 public var rawValue : UInt8
451484
@@ -489,38 +522,6 @@ func assertParse(
489522 )
490523}
491524
492- /// Same as `assertParse` overload with a `(String) -> S` `parse`,
493- /// constructing a `Parser` from the given `String` and passing that to
494- /// `parse` instead.
495- func assertParse< S: SyntaxProtocol > (
496- _ markedSource: String ,
497- _ parse: ( inout Parser ) -> S ,
498- substructure expectedSubstructure: Syntax ? = nil ,
499- substructureAfterMarker: String = " START " ,
500- diagnostics expectedDiagnostics: [ DiagnosticSpec ] = [ ] ,
501- applyFixIts: [ String ] ? = nil ,
502- fixedSource expectedFixedSource: String ? = nil ,
503- options: AssertParseOptions = [ ] ,
504- file: StaticString = #file,
505- line: UInt = #line
506- ) {
507- assertParse (
508- markedSource,
509- { ( source: String ) -> S in
510- var parser = Parser ( source)
511- return parse ( & parser)
512- } ,
513- substructure: expectedSubstructure,
514- substructureAfterMarker: substructureAfterMarker,
515- diagnostics: expectedDiagnostics,
516- applyFixIts: applyFixIts,
517- fixedSource: expectedFixedSource,
518- options: options,
519- file: file,
520- line: line
521- )
522- }
523-
524525/// Removes any test markers from `markedSource` (1) and parses the result
525526/// using `parse`. By default it only checks if the parsed syntax tree is
526527/// printable back to the origin source, ie. it round trips.
@@ -541,7 +542,7 @@ func assertParse<S: SyntaxProtocol>(
541542/// this string.
542543func assertParse< S: SyntaxProtocol > (
543544 _ markedSource: String ,
544- _ parse: ( String ) -> S ,
545+ _ parse: ( inout Parser ) -> S ,
545546 substructure expectedSubstructure: Syntax ? = nil ,
546547 substructureAfterMarker: String = " START " ,
547548 diagnostics expectedDiagnostics: [ DiagnosticSpec ] = [ ] ,
@@ -555,7 +556,15 @@ func assertParse<S: SyntaxProtocol>(
555556 var ( markerLocations, source) = extractMarkers ( markedSource)
556557 markerLocations [ " START " ] = 0
557558
558- let tree : S = parse ( source)
559+ var parser = Parser ( source)
560+ #if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
561+ let enableTestCaseMutation = ProcessInfo . processInfo. environment [ " SKIP_LONG_TESTS " ] != " 1 "
562+
563+ if enableTestCaseMutation {
564+ parser. enableAlternativeTokenChoices ( )
565+ }
566+ #endif
567+ let tree : S = parse ( & parser)
559568
560569 // Round-trip
561570 assertStringsEqualWithDiff (
@@ -615,4 +624,38 @@ func assertParse<S: SyntaxProtocol>(
615624 line: line
616625 )
617626 }
627+
628+ #if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
629+ if enableTestCaseMutation {
630+ let mutations : [ ( offset: Int , replacement: TokenSpec ) ] = parser. alternativeTokenChoices. flatMap { offset, replacements in
631+ return replacements. map { ( offset, $0) }
632+ }
633+ DispatchQueue . concurrentPerform ( iterations: mutations. count) { index in
634+ let mutation = mutations [ index]
635+ let alternateSource = MutatedTreePrinter . print ( tree: Syntax ( tree) , mutations: [ mutation. offset: mutation. replacement] )
636+ alternateSource. withUnsafeBufferPointer { buf in
637+ let mutatedSource = String ( decoding: buf, as: UTF8 . self)
638+ // Check that we don't hit any assertions in the parser while parsing
639+ // the mutated source and that it round-trips
640+ var mutatedParser = Parser ( buf)
641+ let mutatedTree = parse ( & mutatedParser)
642+ assertStringsEqualWithDiff (
643+ " \( mutatedTree) " ,
644+ mutatedSource,
645+ additionalInfo: """
646+ Mutated source failed to round-trip.
647+
648+ Mutated source:
649+ \( mutatedSource)
650+
651+ Actual syntax tree:
652+ \( mutatedTree. debugDescription)
653+ """ ,
654+ file: file,
655+ line: line
656+ )
657+ }
658+ }
659+ }
660+ #endif
618661}
0 commit comments