From 086ba928216f645136805e4b4b66547a67aa9b2d Mon Sep 17 00:00:00 2001 From: Rick Hohler Date: Mon, 2 Feb 2026 15:00:34 -0600 Subject: [PATCH] Fix(FormatRawStringLiteral): Prevent invalid hash removal from single-line multiline strings (Fixes SourceKit-LSP #2465) --- .../FormatRawStringLiteral.swift | 30 ++++++++++++++++++- .../FormatRawStringLiteral.swift | 2 ++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftRefactor/FormatRawStringLiteral.swift b/Sources/SwiftRefactor/FormatRawStringLiteral.swift index c8208b56ec3..15ecb507a75 100644 --- a/Sources/SwiftRefactor/FormatRawStringLiteral.swift +++ b/Sources/SwiftRefactor/FormatRawStringLiteral.swift @@ -54,7 +54,35 @@ public struct FormatRawStringLiteral: SyntaxRefactoringProvider { } } - guard maximumHashes > 0 else { + var shouldRemoveHashes = maximumHashes == 0 + let quote = lit.openingQuote.text + + if shouldRemoveHashes { + if quote == "\"" { + // Check for the #"""..."""# case which parses as quote=""" and content wrapped in quotes. + // If we remove hashes, we get """...""", which is invalid single-line multiline syntax. + for segment in lit.segments { + if case .stringSegment(let s) = segment { + if s.content.text.hasPrefix("\"") && s.content.text.hasSuffix("\"") { + shouldRemoveHashes = false + break + } + } + } + } else if quote == "\"\"\"" { + // Check for single-line multiline strings. + let containsNewline = + lit.openingQuote.trailingTrivia.description.contains("\n") + || lit.segments.contains(where: { $0.description.contains("\n") }) + || lit.closingQuote.leadingTrivia.description.contains("\n") + + if !containsNewline { + shouldRemoveHashes = false + } + } + } + + if shouldRemoveHashes { return lit .with(\.openingPounds, lit.openingPounds?.with(\.tokenKind, .rawStringPoundDelimiter(""))) diff --git a/Tests/SwiftRefactorTest/FormatRawStringLiteral.swift b/Tests/SwiftRefactorTest/FormatRawStringLiteral.swift index 59d5f3e8012..3e6fce13184 100644 --- a/Tests/SwiftRefactorTest/FormatRawStringLiteral.swift +++ b/Tests/SwiftRefactorTest/FormatRawStringLiteral.swift @@ -31,6 +31,8 @@ final class FormatRawStringLiteralTest: XCTestCase { expectation: ########" #######"###### \####(hello) ##"####### "######## ), (#line, literal: ########" #######"hello \(world) "####### "########, expectation: #" "hello \(world) " "#), + // Test for SourceKit-LSP #2465: Single-line multiline strings MUST have at least one hash. + (#line, literal: ##"#"""hello"""#"##, expectation: ##"#"""hello"""#"##), ] for (line, literal, expectation) in tests {