Skip to content

Commit cb0ddcf

Browse files
committed
Add an AssertLexemes method to simplify LexerTests
1 parent acc1e43 commit cb0ddcf

File tree

3 files changed

+656
-822
lines changed

3 files changed

+656
-822
lines changed

Sources/SwiftParser/Lexer.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public struct Lexer {
2424
/// represents a fully identified, meaningful part of the input text that
2525
/// will can be consumed by a ``Parser``.
2626
public struct Lexeme: CustomDebugStringConvertible {
27-
public struct Flags: OptionSet {
27+
public struct Flags: OptionSet, CustomDebugStringConvertible {
2828
public var rawValue: UInt8
2929

3030
public init(rawValue: UInt8) {
@@ -34,6 +34,20 @@ public struct Lexer {
3434
public static let isAtStartOfLine = Flags(rawValue: 1 << 0)
3535
public static let isMultilineStringLiteral = Flags(rawValue: 1 << 1)
3636
public static let isErroneous = Flags(rawValue: 1 << 2)
37+
38+
public var debugDescription: String {
39+
var descriptionComponents: [String] = []
40+
if self.contains(.isAtStartOfLine) {
41+
descriptionComponents.append("isAtStartOfLine")
42+
}
43+
if self.contains(.isMultilineStringLiteral) {
44+
descriptionComponents.append("isMultilineStringLiteral")
45+
}
46+
if self.contains(.isErroneous) {
47+
descriptionComponents.append("isErroneous")
48+
}
49+
return "[\(descriptionComponents.joined(separator: ", "))]"
50+
}
3751
}
3852

3953
@_spi(RawSyntax)

Tests/SwiftParserTest/Assertions.swift

Lines changed: 99 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,36 @@ import _SwiftSyntaxTestSupport
1919

2020
// MARK: Lexing Assertions
2121

22+
struct LexemeSpec {
23+
let tokenKind: RawTokenKind
24+
let leadingTrivia: SyntaxText
25+
let tokenText: SyntaxText
26+
let trailingTrivia: SyntaxText
27+
let flags: Lexer.Lexeme.Flags
28+
29+
/// The file and line at which this `LexemeSpec` was created, so that assertion failures can be reported at its location.
30+
let file: StaticString
31+
let line: UInt
32+
33+
init(
34+
_ tokenKind: RawTokenKind,
35+
leading: SyntaxText = "",
36+
text: SyntaxText,
37+
trailing: SyntaxText = "",
38+
flags: Lexer.Lexeme.Flags = [],
39+
file: StaticString = #file,
40+
line: UInt = #line
41+
) {
42+
self.tokenKind = tokenKind
43+
self.leadingTrivia = leading
44+
self.tokenText = text
45+
self.trailingTrivia = trailing
46+
self.flags = flags
47+
self.file = file
48+
self.line = line
49+
}
50+
}
51+
2252
/// Asserts that two lexical streams are structurally equal, including their trivia and any
2353
/// text.
2454
///
@@ -29,45 +59,85 @@ import _SwiftSyntaxTestSupport
2959
/// which this function was called.
3060
/// - line: The line number on which failure occurred. Defaults to the line number on which this
3161
/// function was called.
32-
func AssertEqualTokens(_ actual: [Lexer.Lexeme], _ expected: [Lexer.Lexeme], file: StaticString = #file, line: UInt = #line) {
62+
private func AssertTokens(_ actual: [Lexer.Lexeme], _ expected: [LexemeSpec], file: StaticString = #file, line: UInt = #line) {
3363
guard actual.count == expected.count else {
34-
return XCTFail("Number of tokens does not match! \(actual.count) != \(expected.count)", file: file, line: line)
64+
return XCTFail(
65+
"Expected \(expected.count) tokens but got \(actual.count)",
66+
file: file,
67+
line: line
68+
)
3569
}
3670

37-
for (idx, (l, r)) in zip(actual, expected).enumerated() {
38-
guard l.tokenKind == r.tokenKind else {
39-
return XCTFail("Token at index \(idx) does not match! \(l.tokenKind) != \(r.tokenKind)", file: file, line: line)
71+
for (actualLexeme, expectedLexeme) in zip(actual, expected) {
72+
if actualLexeme.tokenKind != expectedLexeme.tokenKind {
73+
XCTFail(
74+
"Expected token kind \(expectedLexeme.tokenKind) but got \(actualLexeme.tokenKind)",
75+
file: expectedLexeme.file,
76+
line: expectedLexeme.line
77+
)
4078
}
4179

42-
guard l.leadingTriviaText == r.leadingTriviaText else {
43-
return FailStringsEqualWithDiff(
44-
String(syntaxText: l.leadingTriviaText),
45-
String(syntaxText: r.leadingTriviaText),
46-
"Token at index \(idx) does not have matching leading trivia",
47-
file: file,
48-
line: line
80+
if actualLexeme.leadingTriviaText != expectedLexeme.leadingTrivia {
81+
FailStringsEqualWithDiff(
82+
String(syntaxText: actualLexeme.leadingTriviaText),
83+
String(syntaxText: expectedLexeme.leadingTrivia),
84+
"Leading trivia does not match",
85+
file: expectedLexeme.file,
86+
line: expectedLexeme.line
4987
)
5088
}
5189

52-
guard l.tokenText.debugDescription == r.tokenText.debugDescription else {
53-
return FailStringsEqualWithDiff(
54-
l.tokenText.debugDescription,
55-
r.tokenText.debugDescription,
56-
"Text at index \(idx) does not have matching text",
57-
file: file,
58-
line: line
90+
if actualLexeme.tokenText.debugDescription != expectedLexeme.tokenText.debugDescription {
91+
FailStringsEqualWithDiff(
92+
actualLexeme.tokenText.debugDescription,
93+
expectedLexeme.tokenText.debugDescription,
94+
"Token text does not match",
95+
file: expectedLexeme.file,
96+
line: expectedLexeme.line
5997
)
6098
}
6199

62-
guard l.trailingTriviaText == r.trailingTriviaText else {
63-
return FailStringsEqualWithDiff(
64-
String(syntaxText: l.trailingTriviaText),
65-
String(syntaxText: r.trailingTriviaText),
66-
"Token at index \(idx) does not have matching trailing trivia",
67-
file: file,
68-
line: line
100+
if actualLexeme.trailingTriviaText != expectedLexeme.trailingTrivia {
101+
FailStringsEqualWithDiff(
102+
String(syntaxText: actualLexeme.trailingTriviaText),
103+
String(syntaxText: expectedLexeme.trailingTrivia),
104+
"Trailing trivia does not match",
105+
file: expectedLexeme.file,
106+
line: expectedLexeme.line
69107
)
70108
}
109+
110+
if actualLexeme.flags != expectedLexeme.flags {
111+
XCTFail(
112+
"Expected flags \(expectedLexeme.flags.debugDescription) but got \(actualLexeme.flags.debugDescription)",
113+
file: expectedLexeme.file,
114+
line: expectedLexeme.line
115+
)
116+
}
117+
}
118+
}
119+
120+
func AssertLexemes(
121+
_ source: String,
122+
lexemes expectedLexemes: [LexemeSpec],
123+
file: StaticString = #file,
124+
line: UInt = #line
125+
) {
126+
var expectedLexemes = expectedLexemes
127+
if expectedLexemes.last?.tokenKind != .eof {
128+
expectedLexemes.append(LexemeSpec(.eof, text: ""))
129+
}
130+
var source = source
131+
source.withUTF8 { buf in
132+
var lexemes = [Lexer.Lexeme]()
133+
for token in Lexer.tokenize(buf, from: 0) {
134+
lexemes.append(token)
135+
136+
guard token.tokenKind != .eof else {
137+
break
138+
}
139+
}
140+
AssertTokens(lexemes, expectedLexemes, file: file, line: line)
71141
}
72142
}
73143

@@ -271,7 +341,9 @@ func AssertDiagnostic<T: SyntaxProtocol>(
271341
"""
272342
Diagnostic message should only span a single line. Message was:
273343
\(diag.message)
274-
"""
344+
""",
345+
file: file,
346+
line: line
275347
)
276348
}
277349
if let highlight = spec.highlight {

0 commit comments

Comments
 (0)