From 7397632751a4aab020c2fbbd7013fa4bc1c247b3 Mon Sep 17 00:00:00 2001 From: Dongyu Zhao Date: Mon, 21 Jul 2025 03:16:35 +0800 Subject: [PATCH] Extract language from fenced code blocks --- .../Builders/MarkdownFencedCodeBuilder.swift | 15 ++++++++++++++- .../Consumer/MarkdownBlockElementTests.swift | 6 +++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftParser/Markdown/Builders/MarkdownFencedCodeBuilder.swift b/Sources/SwiftParser/Markdown/Builders/MarkdownFencedCodeBuilder.swift index 7835cde..5b269ad 100644 --- a/Sources/SwiftParser/Markdown/Builders/MarkdownFencedCodeBuilder.swift +++ b/Sources/SwiftParser/Markdown/Builders/MarkdownFencedCodeBuilder.swift @@ -10,7 +10,8 @@ public class MarkdownFencedCodeBuilder: CodeNodeBuilder { isStartOfLine(context) else { return false } context.consuming += 1 let code = trimFence(token.text) - let node = CodeBlockNode(source: code, language: nil) + let language = extractLanguage(token.text) + let node = CodeBlockNode(source: code, language: language) context.current.append(node) if context.consuming < context.tokens.count, let nl = context.tokens[context.consuming] as? MarkdownToken, @@ -30,6 +31,18 @@ public class MarkdownFencedCodeBuilder: CodeNodeBuilder { return lines.joined(separator: "\n") } + private func extractLanguage(_ text: String) -> String? { + guard let firstLine = text.split(separator: "\n", maxSplits: 1).first else { + return nil + } + var cleaned = firstLine.trimmingCharacters(in: .whitespaces) + while cleaned.starts(with: "`") { + cleaned.removeFirst() + } + let lang = cleaned.trimmingCharacters(in: .whitespaces) + return lang.isEmpty ? nil : lang + } + private func isStartOfLine(_ context: CodeContext) -> Bool { if context.consuming == 0 { return true } if let prev = context.tokens[context.consuming - 1] as? MarkdownToken { diff --git a/Tests/SwiftParserTests/Markdown/Consumer/MarkdownBlockElementTests.swift b/Tests/SwiftParserTests/Markdown/Consumer/MarkdownBlockElementTests.swift index 0e59a3d..5c3705d 100644 --- a/Tests/SwiftParserTests/Markdown/Consumer/MarkdownBlockElementTests.swift +++ b/Tests/SwiftParserTests/Markdown/Consumer/MarkdownBlockElementTests.swift @@ -17,7 +17,11 @@ final class MarkdownBlockElementTests: XCTestCase { let (node, context) = parser.parse(input, root: root) XCTAssertTrue(context.errors.isEmpty) XCTAssertEqual(node.children.count, 1) - XCTAssertTrue(node.children.first is CodeBlockNode) + if let code = node.children.first as? CodeBlockNode { + XCTAssertEqual(code.language, "swift") + } else { + XCTFail("Expected CodeBlockNode") + } } func testHorizontalRule() {