Skip to content

Commit 816d691

Browse files
authored
Merge pull request swiftlang#690 from omochi/missing-string-closer
Generate missing string closer quote and delimiter
2 parents cb71972 + f5dd267 commit 816d691

File tree

3 files changed

+64
-10
lines changed

3 files changed

+64
-10
lines changed

Sources/SwiftParser/Expressions.swift

+28-8
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,9 @@ extension Parser {
907907
at: openDelimiter != nil ? .leadingRaw : .leading,
908908
text: text
909909
) ?? RawTokenSyntax(missing: .stringQuote, arena: arena)
910-
text = text.dropFirst(openQuote.tokenText.count)
910+
if !openQuote.isMissing {
911+
text = text.dropFirst(openQuote.tokenText.count)
912+
}
911913

912914
/// Parse segments.
913915
let (segments, closeStart) = self.parseStringLiteralSegments(
@@ -917,16 +919,31 @@ extension Parser {
917919
/// Parse close quote.
918920
let closeQuote = self.parseStringLiteralQuote(
919921
at: openDelimiter != nil ? .trailingRaw : .trailing,
920-
text: text[closeStart...]
922+
text: text
921923
) ?? RawTokenSyntax(missing: openQuote.tokenKind, arena: arena)
922-
text = text.dropFirst(closeQuote.byteLength)
923-
924+
if !closeQuote.isMissing {
925+
text = text.dropFirst(closeQuote.tokenText.count)
926+
}
924927
/// Parse closing raw string delimiter if exist.
925-
let closeDelimiter = self.parseStringLiteralDelimiter(at: .trailing, text: text)
928+
let closeDelimiter: RawTokenSyntax?
929+
if let delimiter = self.parseStringLiteralDelimiter(
930+
at: .trailing,
931+
text: text
932+
) {
933+
closeDelimiter = delimiter
934+
} else if let openDelimiter = openDelimiter {
935+
closeDelimiter = RawTokenSyntax(
936+
missing: .rawStringDelimiter,
937+
text: openDelimiter.tokenText,
938+
arena: arena
939+
)
940+
} else {
941+
closeDelimiter = nil
942+
}
926943
assert((openDelimiter == nil) == (closeDelimiter == nil),
927944
"existence of open/close delimiter should match")
928-
if let closeDelimiter = closeDelimiter {
929-
text = text.dropFirst(closeDelimiter.tokenText.count)
945+
if let closeDelimiter = closeDelimiter, !closeDelimiter.isMissing {
946+
text = text.dropFirst(closeDelimiter.byteLength)
930947
}
931948

932949
assert(text.isEmpty,
@@ -1060,7 +1077,10 @@ extension Parser {
10601077
// position == .leadingRaw implies that we saw a `#` before the quote.
10611078
// A multiline string literal must always start its contents on a new line.
10621079
// Thus we are parsing somethign like #"""#, which is not a multiline string literal but a raw literal containing a single quote.
1063-
if position == .leadingRaw && text[index] == UInt8(ascii: "#") {
1080+
if position == .leadingRaw,
1081+
index < text.endIndex,
1082+
text[index] == UInt8(ascii: "#")
1083+
{
10641084
quoteCount = 1
10651085
index = text.index(text.startIndex, offsetBy: quoteCount)
10661086
}

Sources/SwiftSyntax/Raw/RawSyntaxNodeProtocol.swift

+6-2
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,14 @@ public struct RawTokenSyntax: RawSyntaxNodeProtocol {
158158
/// If `text` is passed, it will be used to represent the missing token's text.
159159
/// If `text` is `nil`, the `kind`'s default text will be used.
160160
/// If that is also `nil`, the token will have empty text.
161-
public init(missing kind: RawTokenKind, arena: __shared SyntaxArena) {
161+
public init(
162+
missing kind: RawTokenKind,
163+
text: SyntaxText? = nil,
164+
arena: __shared SyntaxArena
165+
) {
162166
self.init(
163167
kind: kind,
164-
text: kind.defaultText ?? "",
168+
text: text ?? kind.defaultText ?? "",
165169
leadingTriviaPieces: [],
166170
trailingTriviaPieces: [],
167171
presence: .missing,

Tests/SwiftParserTest/Expressions.swift

+30
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,36 @@ final class ExpressionTests: XCTestCase {
310310
""""""#^DIAG^#
311311
"""##
312312
)
313+
314+
AssertParse(
315+
##"""
316+
#"#^DIAG^#
317+
"""##,
318+
diagnostics: [
319+
DiagnosticSpec(message: #"Expected '"' in string literal"#),
320+
DiagnosticSpec(message: #"Expected '#' in string literal"#)
321+
]
322+
)
323+
324+
AssertParse(
325+
##"""
326+
#"""#^DIAG^#
327+
"""##,
328+
diagnostics: [
329+
DiagnosticSpec(message: #"Expected '"""' in string literal"#),
330+
DiagnosticSpec(message: #"Expected '#' in string literal"#)
331+
]
332+
)
333+
334+
AssertParse(
335+
##"""
336+
#"""a#^DIAG^#
337+
"""##,
338+
diagnostics: [
339+
DiagnosticSpec(message: #"Expected '"""' in string literal"#),
340+
DiagnosticSpec(message: #"Expected '#' in string literal"#)
341+
]
342+
)
313343
}
314344

315345
func testSingleQuoteStringLiteral() {

0 commit comments

Comments
 (0)