@@ -454,6 +454,39 @@ func assertDiagnostic<T: SyntaxProtocol>(
454454 }
455455}
456456
457+ class MutatedTreePrinter : SyntaxVisitor {
458+ private var mutations : [ Int : TokenSpec ] = [ : ]
459+ private var printedSource : [ UInt8 ] = [ ]
460+
461+ /// Prints `tree` by replacing the tokens whose offset is in `mutations` by
462+ /// a token that matches the corresponding `TokenSpec`.
463+ static func print( tree: Syntax , mutations: [ Int : TokenSpec ] ) -> [ UInt8 ] {
464+ let printer = MutatedTreePrinter ( mutations: mutations)
465+ printer. walk ( tree)
466+ return printer. printedSource
467+ }
468+
469+ private init ( mutations: [ Int : TokenSpec ] ) {
470+ self . mutations = mutations
471+ super. init ( viewMode: . sourceAccurate)
472+ }
473+
474+ override func visit( _ node: TokenSyntax ) -> SyntaxVisitorContinueKind {
475+ if let mutation = mutations [ node. positionAfterSkippingLeadingTrivia. utf8Offset] {
476+ let token = TokenSyntax (
477+ mutation. synthesizedTokenKind,
478+ leadingTrivia: node. leadingTrivia,
479+ trailingTrivia: node. trailingTrivia,
480+ presence: . present
481+ )
482+ printedSource. append ( contentsOf: token. syntaxTextBytes)
483+ return . skipChildren
484+ }
485+ printedSource. append ( contentsOf: node. syntaxTextBytes)
486+ return . skipChildren
487+ }
488+ }
489+
457490public struct AssertParseOptions : OptionSet {
458491 public var rawValue : UInt8
459492
@@ -497,38 +530,6 @@ func assertParse(
497530 )
498531}
499532
500- /// Same as `assertParse` overload with a `(String) -> S` `parse`,
501- /// constructing a `Parser` from the given `String` and passing that to
502- /// `parse` instead.
503- func assertParse< S: SyntaxProtocol > (
504- _ markedSource: String ,
505- _ parse: ( inout Parser ) -> S ,
506- substructure expectedSubstructure: Syntax ? = nil ,
507- substructureAfterMarker: String = " START " ,
508- diagnostics expectedDiagnostics: [ DiagnosticSpec ] = [ ] ,
509- applyFixIts: [ String ] ? = nil ,
510- fixedSource expectedFixedSource: String ? = nil ,
511- options: AssertParseOptions = [ ] ,
512- file: StaticString = #file,
513- line: UInt = #line
514- ) {
515- assertParse (
516- markedSource,
517- { ( source: String ) -> S in
518- var parser = Parser ( source)
519- return parse ( & parser)
520- } ,
521- substructure: expectedSubstructure,
522- substructureAfterMarker: substructureAfterMarker,
523- diagnostics: expectedDiagnostics,
524- applyFixIts: applyFixIts,
525- fixedSource: expectedFixedSource,
526- options: options,
527- file: file,
528- line: line
529- )
530- }
531-
532533/// Removes any test markers from `markedSource` (1) and parses the result
533534/// using `parse`. By default it only checks if the parsed syntax tree is
534535/// printable back to the origin source, ie. it round trips.
@@ -549,7 +550,7 @@ func assertParse<S: SyntaxProtocol>(
549550/// this string.
550551func assertParse< S: SyntaxProtocol > (
551552 _ markedSource: String ,
552- _ parse: ( String ) -> S ,
553+ _ parse: ( inout Parser ) -> S ,
553554 substructure expectedSubstructure: Syntax ? = nil ,
554555 substructureAfterMarker: String = " START " ,
555556 diagnostics expectedDiagnostics: [ DiagnosticSpec ] = [ ] ,
@@ -563,7 +564,15 @@ func assertParse<S: SyntaxProtocol>(
563564 var ( markerLocations, source) = extractMarkers ( markedSource)
564565 markerLocations [ " START " ] = 0
565566
566- let tree : S = parse ( source)
567+ var parser = Parser ( source)
568+ #if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
569+ let enableTestCaseMutation = ProcessInfo . processInfo. environment [ " SKIP_LONG_TESTS " ] != " 1 "
570+
571+ if enableTestCaseMutation {
572+ parser. enableAlternativeTokenChoices ( )
573+ }
574+ #endif
575+ let tree : S = parse ( & parser)
567576
568577 // Round-trip
569578 assertStringsEqualWithDiff (
@@ -623,4 +632,38 @@ func assertParse<S: SyntaxProtocol>(
623632 line: line
624633 )
625634 }
635+
636+ #if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
637+ if enableTestCaseMutation {
638+ let mutations : [ ( offset: Int , replacement: TokenSpec ) ] = parser. alternativeTokenChoices. flatMap { offset, replacements in
639+ return replacements. map { ( offset, $0) }
640+ }
641+ DispatchQueue . concurrentPerform ( iterations: mutations. count) { index in
642+ let mutation = mutations [ index]
643+ let alternateSource = MutatedTreePrinter . print ( tree: Syntax ( tree) , mutations: [ mutation. offset: mutation. replacement] )
644+ alternateSource. withUnsafeBufferPointer { buf in
645+ let mutatedSource = String ( decoding: buf, as: UTF8 . self)
646+ // Check that we don't hit any assertions in the parser while parsing
647+ // the mutated source and that it round-trips
648+ var mutatedParser = Parser ( buf)
649+ let mutatedTree = parse ( & mutatedParser)
650+ assertStringsEqualWithDiff (
651+ " \( mutatedTree) " ,
652+ mutatedSource,
653+ additionalInfo: """
654+ Mutated source failed to round-trip.
655+
656+ Mutated source:
657+ \( mutatedSource)
658+
659+ Actual syntax tree:
660+ \( mutatedTree. debugDescription)
661+ """ ,
662+ file: file,
663+ line: line
664+ )
665+ }
666+ }
667+ }
668+ #endif
626669}
0 commit comments