Skip to content

Commit 94959f0

Browse files
authored
Merge pull request #1661 from TTOzzi/fix-newline-parsing-at-trailing-trivia
Fix newline parsing at trailing trivia
2 parents 5ebcc29 + fb134e9 commit 94959f0

File tree

3 files changed

+51
-16
lines changed

3 files changed

+51
-16
lines changed

Sources/SwiftParser/Lexer/Cursor.swift

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,9 @@ extension Lexer {
243243
/// If we have already lexed a token, the kind of the previously lexed token
244244
var previousTokenKind: RawTokenKind?
245245

246+
/// If we have already lexed a token, stores whether the previous lexeme‘s ending contains a newline.
247+
var previousLexemeTrailingNewlinePresence: NewlinePresence?
248+
246249
/// If the `previousTokenKind` is `.keyword`, the keyword kind. Otherwise
247250
/// `nil`.
248251
var previousKeyword: Keyword?
@@ -317,21 +320,25 @@ extension Lexer {
317320
/// If `tokenKind` is `.keyword`, the kind of keyword produced, otherwise
318321
/// `nil`.
319322
let keywordKind: Keyword?
323+
/// Indicates whether the end of the lexed token text contains a newline.
324+
let trailingNewlinePresence: Lexer.Cursor.NewlinePresence
320325

321326
private init(
322327
_ tokenKind: RawTokenKind,
323328
flags: Lexer.Lexeme.Flags,
324329
error: Cursor.LexingDiagnostic?,
325330
stateTransition: StateTransition?,
326331
trailingTriviaLexingMode: Lexer.Cursor.TriviaLexingMode?,
327-
keywordKind: Keyword?
332+
keywordKind: Keyword?,
333+
trailingNewlinePresence: Lexer.Cursor.NewlinePresence
328334
) {
329335
self.tokenKind = tokenKind
330336
self.flags = flags
331337
self.error = error
332338
self.stateTransition = stateTransition
333339
self.trailingTriviaLexingMode = trailingTriviaLexingMode
334340
self.keywordKind = keywordKind
341+
self.trailingNewlinePresence = trailingNewlinePresence
335342
}
336343

337344
/// Create a lexer result. Note that keywords should use `Result.keyword`
@@ -341,7 +348,8 @@ extension Lexer {
341348
flags: Lexer.Lexeme.Flags = [],
342349
error: Cursor.LexingDiagnostic? = nil,
343350
stateTransition: StateTransition? = nil,
344-
trailingTriviaLexingMode: Lexer.Cursor.TriviaLexingMode? = nil
351+
trailingTriviaLexingMode: Lexer.Cursor.TriviaLexingMode? = nil,
352+
trailingNewlinePresence: Lexer.Cursor.NewlinePresence = .absent
345353
) {
346354
precondition(tokenKind != .keyword, "Use Result.keyword instead")
347355
self.init(
@@ -350,7 +358,8 @@ extension Lexer {
350358
error: error,
351359
stateTransition: stateTransition,
352360
trailingTriviaLexingMode: trailingTriviaLexingMode,
353-
keywordKind: nil
361+
keywordKind: nil,
362+
trailingNewlinePresence: trailingNewlinePresence
354363
)
355364
}
356365

@@ -362,7 +371,8 @@ extension Lexer {
362371
error: nil,
363372
stateTransition: nil,
364373
trailingTriviaLexingMode: nil,
365-
keywordKind: kind
374+
keywordKind: kind,
375+
trailingNewlinePresence: .absent
366376
)
367377
}
368378
}
@@ -430,6 +440,16 @@ extension Lexer.Cursor {
430440
result = lexInRegexLiteral(lexemes.pointee[index...], existingPtr: lexemes)
431441
}
432442

443+
var flags = result.flags
444+
if newlineInLeadingTrivia == .present {
445+
flags.insert(.isAtStartOfLine)
446+
}
447+
if let previousLexemeTrailingNewlinePresence, previousLexemeTrailingNewlinePresence == .present {
448+
flags.insert(.isAtStartOfLine)
449+
}
450+
451+
self.previousLexemeTrailingNewlinePresence = result.trailingNewlinePresence
452+
433453
if let stateTransition = result.stateTransition {
434454
self.stateStack.perform(stateTransition: stateTransition, stateAllocator: stateAllocator)
435455
}
@@ -438,18 +458,14 @@ extension Lexer.Cursor {
438458
let trailingTriviaStart = self
439459
if let trailingTriviaMode = result.trailingTriviaLexingMode ?? currentState.trailingTriviaLexingMode(cursor: self) {
440460
let triviaResult = self.lexTrivia(mode: trailingTriviaMode)
461+
self.previousLexemeTrailingNewlinePresence = triviaResult.newlinePresence
441462
diagnostic = TokenDiagnostic(combining: diagnostic, triviaResult.error?.tokenDiagnostic(tokenStart: cursor))
442463
}
443464

444465
if self.currentState.shouldPopStateWhenReachingNewlineInTrailingTrivia && self.is(at: "\r", "\n") {
445466
self.stateStack.perform(stateTransition: .pop, stateAllocator: stateAllocator)
446467
}
447468

448-
var flags = result.flags
449-
if newlineInLeadingTrivia == .present {
450-
flags.insert(.isAtStartOfLine)
451-
}
452-
453469
diagnostic = TokenDiagnostic(combining: diagnostic, result.error?.tokenDiagnostic(tokenStart: cursor))
454470

455471
let lexeme = Lexer.Lexeme(
@@ -1889,7 +1905,7 @@ extension Lexer.Cursor {
18891905
if character == UInt8(ascii: "\r") {
18901906
_ = self.advance(matching: "\n")
18911907
}
1892-
return Lexer.Result(.stringSegment, error: error)
1908+
return Lexer.Result(.stringSegment, error: error, trailingNewlinePresence: .present)
18931909
} else {
18941910
// Single line literals cannot span multiple lines.
18951911
// Terminate the string here and go back to normal lexing (instead of `afterStringLiteral`)

Tests/SwiftParserTest/LexerTests.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,9 +1182,9 @@ public class LexerTests: XCTestCase {
11821182
"""#,
11831183
lexemes: [
11841184
LexemeSpec(.multilineStringQuote, leading: " ", text: #"""""#, trailing: "\n"),
1185-
LexemeSpec(.stringSegment, text: " line 1\n"),
1186-
LexemeSpec(.stringSegment, text: " line 2\n"),
1187-
LexemeSpec(.stringSegment, text: " "),
1185+
LexemeSpec(.stringSegment, text: " line 1\n", flags: .isAtStartOfLine),
1186+
LexemeSpec(.stringSegment, text: " line 2\n", flags: .isAtStartOfLine),
1187+
LexemeSpec(.stringSegment, text: " ", flags: .isAtStartOfLine),
11881188
LexemeSpec(.multilineStringQuote, text: #"""""#),
11891189
]
11901190
)
@@ -1198,9 +1198,9 @@ public class LexerTests: XCTestCase {
11981198
"""#,
11991199
lexemes: [
12001200
LexemeSpec(.multilineStringQuote, leading: " ", text: #"""""#, trailing: "\n"),
1201-
LexemeSpec(.stringSegment, text: " line 1 ", trailing: "\\\n"),
1202-
LexemeSpec(.stringSegment, text: " line 2\n"),
1203-
LexemeSpec(.stringSegment, text: " "),
1201+
LexemeSpec(.stringSegment, text: " line 1 ", trailing: "\\\n", flags: .isAtStartOfLine),
1202+
LexemeSpec(.stringSegment, text: " line 2\n", flags: .isAtStartOfLine),
1203+
LexemeSpec(.stringSegment, text: " ", flags: .isAtStartOfLine),
12041204
LexemeSpec(.multilineStringQuote, text: #"""""#),
12051205
]
12061206
)

Tests/SwiftParserTest/StatementTests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,4 +763,23 @@ final class StatementTests: XCTestCase {
763763
"""
764764
)
765765
}
766+
767+
func testTrailingTriviaIncludesNewline() {
768+
assertParse(
769+
"""
770+
let a = 2/*
771+
*/let b = 3
772+
"""
773+
)
774+
775+
assertParse(
776+
"""
777+
let a = 2/*
778+
779+
780+
781+
*/let b = 3
782+
"""
783+
)
784+
}
766785
}

0 commit comments

Comments
 (0)