Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion Sources/SwiftParser/Languages/MarkdownLanguage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,44 @@ public struct MarkdownLanguage: CodeLanguage {
}
}

public class BareAutoLinkBuilder: CodeElementBuilder {
private static let regex: NSRegularExpression = {
let pattern = #"^((https?|ftp)://[^\s<>]+|www\.[^\s<>]+|[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})"#
return try! NSRegularExpression(pattern: pattern, options: [])
}()

public init() {}

public func accept(context: CodeContext, token: any CodeToken) -> Bool {
guard let tok = token as? Token else { return false }
let start = tok.range.lowerBound
let text = String(context.input[start...])
let range = NSRange(location: 0, length: text.utf16.count)
if let m = Self.regex.firstMatch(in: text, range: range), m.range.location == 0 {
return true
}
return false
}

public func build(context: inout CodeContext) {
guard let tok = context.tokens[context.index] as? Token else { return }
let start = tok.range.lowerBound
let text = String(context.input[start...])
let range = NSRange(location: 0, length: text.utf16.count)
guard let m = Self.regex.firstMatch(in: text, range: range) else { return }
let endPos = context.input.index(start, offsetBy: m.range.length)
let url = String(context.input[start..<endPos])
context.currentNode.addChild(CodeNode(type: Element.autoLink, value: url))
while context.index < context.tokens.count {
if let t = context.tokens[context.index] as? Token, t.range.upperBound <= endPos {
context.index += 1
} else {
break
}
}
}
}

public class TableBuilder: CodeElementBuilder {
public init() {}
public func accept(context: CodeContext, token: any CodeToken) -> Bool {
Expand Down Expand Up @@ -1016,7 +1054,7 @@ public struct MarkdownLanguage: CodeLanguage {

public var tokenizer: CodeTokenizer { Tokenizer() }
public var builders: [CodeElementBuilder] {
[HeadingBuilder(), SetextHeadingBuilder(), CodeBlockBuilder(), IndentedCodeBlockBuilder(), BlockQuoteBuilder(), ThematicBreakBuilder(), OrderedListItemBuilder(), ListItemBuilder(), ImageBuilder(), HTMLBuilder(), EntityBuilder(), StrikethroughBuilder(), AutoLinkBuilder(), TableBuilder(), FootnoteBuilder(), LinkReferenceDefinitionBuilder(), LinkBuilder(), StrongBuilder(), EmphasisBuilder(), InlineCodeBuilder(), ParagraphBuilder()]
[HeadingBuilder(), SetextHeadingBuilder(), CodeBlockBuilder(), IndentedCodeBlockBuilder(), BlockQuoteBuilder(), ThematicBreakBuilder(), OrderedListItemBuilder(), ListItemBuilder(), ImageBuilder(), HTMLBuilder(), EntityBuilder(), StrikethroughBuilder(), AutoLinkBuilder(), BareAutoLinkBuilder(), TableBuilder(), FootnoteBuilder(), LinkReferenceDefinitionBuilder(), LinkBuilder(), StrongBuilder(), EmphasisBuilder(), InlineCodeBuilder(), ParagraphBuilder()]
}
public var expressionBuilders: [CodeExpressionBuilder] { [] }
public var rootElement: any CodeElement { Element.root }
Expand Down
9 changes: 9 additions & 0 deletions Tests/SwiftParserTests/SwiftParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ final class SwiftParserTests: XCTestCase {
XCTAssertEqual(result.root.children.first?.type as? MarkdownLanguage.Element, .link)
}

func testMarkdownAutoLinkWithoutBrackets() {
let parser = SwiftParser()
let source = "https://example.com"
let result = parser.parse(source, language: MarkdownLanguage())
XCTAssertEqual(result.errors.count, 0)
XCTAssertEqual(result.root.children.first?.type as? MarkdownLanguage.Element, .autoLink)
XCTAssertEqual(result.root.children.first?.value, "https://example.com")
}

func testMarkdownReferenceLink() {
let parser = SwiftParser()
let source = "[title][ref]\n[ref]: http://example.com"
Expand Down