@@ -112,7 +112,7 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase {
112112
113113 let formatter = formatType. init ( context: context)
114114 let actual = formatter. visit ( sourceFileSyntax)
115- assertStringsEqualWithDiff ( actual. description , expected, file: file, line: line)
115+ assertStringsEqualWithDiff ( " \( actual) " , expected, file: file, line: line)
116116
117117 assertFindings (
118118 expected: findings,
@@ -123,15 +123,22 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase {
123123 line: line)
124124
125125 // Verify that the pretty printer can consume the transformed tree (e.g., it does not contain
126- // any unfolded `SequenceExpr`s). We don't need to check the actual output here (we don't want
127- // the rule tests to be pretty-printer dependent), but this will catch invariants that aren't
128- // satisfied.
129- _ = PrettyPrinter (
126+ // any unfolded `SequenceExpr`s). Then do a whitespace-insensitive comparison of the two trees
127+ // to verify that the format rule didn't transform the tree in such a way that it caused the
128+ // pretty-printer to drop important information (the most likely case is a format rule
129+ // misplacing trivia in a way that the pretty-printer isn't able to handle).
130+ let prettyPrintedSource = PrettyPrinter (
130131 context: context,
131132 node: Syntax ( actual) ,
132133 printTokenStream: false ,
133134 whitespaceOnly: false
134135 ) . prettyPrint ( )
136+ let prettyPrintedTree = Parser . parse ( source: prettyPrintedSource)
137+ XCTAssertEqual (
138+ whitespaceInsensitiveText ( of: actual) ,
139+ whitespaceInsensitiveText ( of: prettyPrintedTree) ,
140+ " After pretty-printing and removing fluid whitespace, the files did not match " ,
141+ file: file, line: line)
135142
136143 var emittedPipelineFindings = [ Finding] ( )
137144 // Disable default rules, so only select rule runs in pipeline
@@ -149,3 +156,34 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase {
149156 emittedFindings: emittedPipelineFindings, context: context, file: file, line: line)
150157 }
151158}
159+
160+ /// Returns a string containing a whitespace-insensitive representation of the given source file.
161+ private func whitespaceInsensitiveText( of file: SourceFileSyntax ) -> String {
162+ var result = " "
163+ for token in file. tokens ( viewMode: . sourceAccurate) {
164+ appendNonspaceTrivia ( token. leadingTrivia, to: & result)
165+ result. append ( token. text)
166+ appendNonspaceTrivia ( token. trailingTrivia, to: & result)
167+ }
168+ return result
169+ }
170+
171+ /// Appends any non-whitespace trivia pieces from the given trivia collection to the output string.
172+ private func appendNonspaceTrivia( _ trivia: Trivia , to string: inout String ) {
173+ for piece in trivia {
174+ switch piece {
175+ case . carriageReturnLineFeeds, . carriageReturns, . formfeeds, . newlines, . spaces, . tabs:
176+ break
177+ case . lineComment( let comment) , . docLineComment( let comment) :
178+ // A tree transforming rule might leave whitespace at the end of a line comment, which the
179+ // pretty printer will remove, so we should ignore that.
180+ if let lastNonWhitespaceIndex = comment. lastIndex ( where: { !$0. isWhitespace } ) {
181+ string. append ( contentsOf: comment [ ... lastNonWhitespaceIndex] )
182+ } else {
183+ string. append ( comment)
184+ }
185+ default :
186+ piece. write ( to: & string)
187+ }
188+ }
189+ }
0 commit comments