From 6eb6a5bfd57ba51567ff2fbca782131e5c268646 Mon Sep 17 00:00:00 2001 From: Dongyu Zhao Date: Tue, 15 Jul 2025 21:52:17 +0800 Subject: [PATCH] Trim code block info whitespace and add multiline fenced block test --- .../Languages/MarkdownLanguage.swift | 24 +++++++++++------ .../SwiftParser/Languages/MarkdownNodes.swift | 12 +++++++-- Tests/SwiftParserTests/SwiftParserTests.swift | 26 ++++++++++++++++--- 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/Sources/SwiftParser/Languages/MarkdownLanguage.swift b/Sources/SwiftParser/Languages/MarkdownLanguage.swift index bccef47..b455cae 100644 --- a/Sources/SwiftParser/Languages/MarkdownLanguage.swift +++ b/Sources/SwiftParser/Languages/MarkdownLanguage.swift @@ -706,15 +706,23 @@ public struct MarkdownLanguage: CodeLanguage { fenceLength += 1 context.index += 1 } - // skip info string until end of line + // capture info string until end of line and trim whitespace + var info = "" while context.index < context.tokens.count { - if let tok = context.tokens[context.index] as? Token, case .newline = tok { - context.index += 1 - break + if let tok = context.tokens[context.index] as? Token { + if case .newline = tok { + context.index += 1 + break + } else { + info += tok.text + context.index += 1 + } } else { context.index += 1 } } + info = info.trimmingCharacters(in: .whitespaces) + let lang = info.split(whereSeparator: { $0.isWhitespace }).first.map(String.init) let blockStart = context.index var text = "" @@ -730,7 +738,7 @@ public struct MarkdownLanguage: CodeLanguage { if count >= fenceLength { context.index = idx if context.index < context.tokens.count, let nl = context.tokens[context.index] as? Token, case .newline = nl { context.index += 1 } - context.currentNode.addChild(MarkdownCodeBlockNode(value: text)) + context.currentNode.addChild(MarkdownCodeBlockNode(lang: lang, content: text)) return } } @@ -738,7 +746,7 @@ public struct MarkdownLanguage: CodeLanguage { context.index += 1 } else { context.index += 1 } } - context.currentNode.addChild(MarkdownCodeBlockNode(value: text)) + context.currentNode.addChild(MarkdownCodeBlockNode(lang: lang, content: text)) } } @@ -799,7 +807,7 @@ public struct MarkdownLanguage: CodeLanguage { text += "\n" + String(s.dropFirst(4)) context.index += 1 } else { - context.currentNode.addChild(MarkdownCodeBlockNode(value: text)) + context.currentNode.addChild(MarkdownCodeBlockNode(lang: nil, content: text)) return } case .text(let s, _): @@ -811,7 +819,7 @@ public struct MarkdownLanguage: CodeLanguage { } } else { context.index += 1 } } - context.currentNode.addChild(MarkdownCodeBlockNode(value: text)) + context.currentNode.addChild(MarkdownCodeBlockNode(lang: nil, content: text)) } } diff --git a/Sources/SwiftParser/Languages/MarkdownNodes.swift b/Sources/SwiftParser/Languages/MarkdownNodes.swift index 76b041a..ea8d162 100644 --- a/Sources/SwiftParser/Languages/MarkdownNodes.swift +++ b/Sources/SwiftParser/Languages/MarkdownNodes.swift @@ -88,8 +88,16 @@ public final class MarkdownStrongNode: CodeNode { } public final class MarkdownCodeBlockNode: CodeNode { - public init(value: String = "", range: Range? = nil) { - super.init(type: MarkdownLanguage.Element.codeBlock, value: value, range: range) + public let lang: String? + + public var content: String { + get { value } + set { value = newValue } + } + + public init(lang: String? = nil, content: String = "", range: Range? = nil) { + self.lang = lang + super.init(type: MarkdownLanguage.Element.codeBlock, value: content, range: range) } } diff --git a/Tests/SwiftParserTests/SwiftParserTests.swift b/Tests/SwiftParserTests/SwiftParserTests.swift index 8880daf..8021371 100644 --- a/Tests/SwiftParserTests/SwiftParserTests.swift +++ b/Tests/SwiftParserTests/SwiftParserTests.swift @@ -126,7 +126,10 @@ final class SwiftParserTests: XCTestCase { let source = "```\ncode\n```\ninline `code`" let result = parser.parse(source, language: MarkdownLanguage()) XCTAssertEqual(result.errors.count, 0) - XCTAssertEqual(result.root.children.first?.type as? MarkdownLanguage.Element, .codeBlock) + let block = result.root.children.first as? MarkdownCodeBlockNode + XCTAssertEqual(block?.type as? MarkdownLanguage.Element, .codeBlock) + XCTAssertNil(block?.lang) + XCTAssertEqual(block?.content, "code\n") let para = result.root.children.last XCTAssertEqual(para?.type as? MarkdownLanguage.Element, .paragraph) XCTAssertEqual(para?.children.last?.type as? MarkdownLanguage.Element, .inlineCode) @@ -138,7 +141,10 @@ final class SwiftParserTests: XCTestCase { let result = parser.parse(source, language: MarkdownLanguage()) XCTAssertEqual(result.errors.count, 0) XCTAssertEqual(result.root.children.count, 1) - XCTAssertEqual(result.root.children.first?.type as? MarkdownLanguage.Element, .codeBlock) + let block = result.root.children.first as? MarkdownCodeBlockNode + XCTAssertEqual(block?.type as? MarkdownLanguage.Element, .codeBlock) + XCTAssertEqual(block?.lang, "swift") + XCTAssertEqual(block?.content, "print(\"hi\")\n") } func testMarkdownTildeCodeBlock() { @@ -147,7 +153,21 @@ final class SwiftParserTests: XCTestCase { let result = parser.parse(source, language: MarkdownLanguage()) XCTAssertEqual(result.errors.count, 0) XCTAssertEqual(result.root.children.count, 1) - XCTAssertEqual(result.root.children.first?.type as? MarkdownLanguage.Element, .codeBlock) + let block = result.root.children.first as? MarkdownCodeBlockNode + XCTAssertEqual(block?.type as? MarkdownLanguage.Element, .codeBlock) + XCTAssertNil(block?.lang) + XCTAssertEqual(block?.content, "print(\"hi\")\n") + } + + func testMarkdownMultiLineFencedCodeBlock() { + let parser = SwiftParser() + let source = "```swift\nprint(\"hi\")\nprint(\"bye\")\n```" + let result = parser.parse(source, language: MarkdownLanguage()) + XCTAssertEqual(result.errors.count, 0) + XCTAssertEqual(result.root.children.count, 1) + let block = result.root.children.first as? MarkdownCodeBlockNode + XCTAssertEqual(block?.lang, "swift") + XCTAssertEqual(block?.content, "print(\"hi\")\nprint(\"bye\")\n") } func testMarkdownLink() {