Skip to content

Commit cf70c5b

Browse files
committed
Assert that for each assertParse test without errors, we produce the same tree by formatting it using BasicFormat
1 parent b00a40e commit cf70c5b

File tree

2 files changed

+68
-1
lines changed

2 files changed

+68
-1
lines changed

Sources/_SwiftSyntaxTestSupport/Syntax+Assertions.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,23 @@ public struct SubtreeMatcher {
115115
afterMarker: String? = nil,
116116
_ expected: Syntax,
117117
includeTrivia: Bool = false,
118+
additionalInfo: String? = nil,
118119
file: StaticString = #filePath,
119120
line: UInt = #line
120121
) throws {
121122
if let diff = try findFirstDifference(afterMarker: afterMarker, baseline: expected, includeTrivia: includeTrivia) {
122-
XCTFail(diff.debugDescription, file: file, line: line)
123+
let message: String
124+
if let additionalInfo = additionalInfo {
125+
message = """
126+
\(additionalInfo)
127+
128+
\(diff.debugDescription)
129+
"""
130+
} else {
131+
message = diff.debugDescription
132+
}
133+
134+
XCTFail(message, file: file, line: line)
123135
}
124136
}
125137
}

Tests/SwiftParserTest/Assertions.swift

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,10 @@ func assertParse<S: SyntaxProtocol>(
625625
)
626626
}
627627

628+
if expectedDiagnostics.isEmpty {
629+
assertBasicFormat(source: source, parse: parse, file: file, line: line)
630+
}
631+
628632
#if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
629633
if enableTestCaseMutation {
630634
let mutations: [(offset: Int, replacement: TokenSpec)] = parser.alternativeTokenChoices.flatMap { offset, replacements in
@@ -659,3 +663,54 @@ func assertParse<S: SyntaxProtocol>(
659663
}
660664
#endif
661665
}
666+
667+
class TriviaRemover: SyntaxRewriter {
668+
override func visit(_ token: TokenSyntax) -> TokenSyntax {
669+
var ancestor = Syntax(token)
670+
while let parent = ancestor.parent {
671+
ancestor = parent
672+
if ancestor.is(StringLiteralExprSyntax.self) || ancestor.is(RegexLiteralExprSyntax.self) {
673+
// Don't mess with indentation inside string or regex literals.
674+
// BasicFormat doesn't know where to re-apply newlines and how much to indent the string literal contents.
675+
return token
676+
}
677+
}
678+
if token.parent?.is(StringSegmentSyntax.self) ?? false {
679+
return token
680+
}
681+
return token.with(\.leadingTrivia, []).with(\.trailingTrivia, [])
682+
}
683+
}
684+
685+
func assertBasicFormat<S: SyntaxProtocol>(
686+
source: String,
687+
parse: (inout Parser) -> S,
688+
file: StaticString = #file,
689+
line: UInt = #line
690+
) {
691+
var parser = Parser(source)
692+
let sourceTree = Syntax(parse(&parser))
693+
let withoutTrivia = TriviaRemover().visit(sourceTree)
694+
let formatted = withoutTrivia.formatted()
695+
696+
var formattedParser = Parser(formatted.description)
697+
let formattedReparsed = Syntax(parse(&formattedParser))
698+
699+
do {
700+
let subtreeMatcher = SubtreeMatcher(Syntax(formattedReparsed), markers: [:])
701+
try subtreeMatcher.assertSameStructure(
702+
Syntax(sourceTree),
703+
includeTrivia: false,
704+
additionalInfo: """
705+
Removing trivia, formatting using BasicFormat and re-parsing did not produce the same syntax tree.
706+
707+
Formatted source:
708+
\(formatted)
709+
""",
710+
file: file,
711+
line: line
712+
)
713+
} catch {
714+
XCTFail("Matching for a subtree failed with error: \(error)", file: file, line: line)
715+
}
716+
}

0 commit comments

Comments
 (0)