diff --git a/Iceshrimp.Parsing/Mfm.fs b/Iceshrimp.Parsing/Mfm.fs index 08b26b53..cb4cc081 100644 --- a/Iceshrimp.Parsing/Mfm.fs +++ b/Iceshrimp.Parsing/Mfm.fs @@ -134,24 +134,20 @@ module private MfmParser = let aggregateTextInline nodes = nodes |> aggregateText |> List.map (fun x -> x :?> MfmInlineNode) - let domainFirstComponent = - many1Chars (satisfy isAsciiLetter <|> satisfy isDigit <|> anyOf "_-") - let domainComponent = - many1Chars (satisfy isAsciiLetter <|> satisfy isDigit <|> anyOf "._-") + many1Chars ( + satisfy isAsciiLetterOrNumber + <|> pchar '_' + <|> attempt ( + pchar '-' + .>> (previousCharSatisfies isAsciiLetterOrNumber + <|> nextCharSatisfies isAsciiLetterOrNumber) + ) + ) - let domainStart = (satisfy isAsciiLetter <|> satisfy isDigit) - - let domainFull = - domainStart .>>. domainFirstComponent .>>. pchar '.' .>>. many1 domainComponent - - let domainAggregate1 (a: char, b: string) = string a + b - let domainAggregate2 (a: char * string, b: char) = (domainAggregate1 a) + string b - - let domainAggregate (x: (char * string) * char, y: string list) = - domainAggregate2 x + (String.concat "" y) - - let domain = domainFull |>> domainAggregate + let domain = + domainComponent .>>. (many <| attempt (skipChar '.' >>. domainComponent)) + |>> fun (a, b) -> String.concat "." <| Seq.append [ a ] b let acct (user: string, host: string option) = match host with @@ -230,7 +226,10 @@ module private MfmParser = let mentionNode = previousCharSatisfiesNot isNotWhitespace >>. skipString "@" - >>. many1Chars (satisfy isAsciiLetter <|> satisfy isDigit <|> anyOf "._-") + >>. many1Chars ( + satisfy isAsciiLetterOrNumber + <|> attempt (anyOf "._-" .>> nextCharSatisfies isAsciiLetterOrNumber) + ) .>>. opt (skipChar '@' >>. domain) .>> (lookAhead <| choice diff --git a/Iceshrimp.Tests/Parsing/MfmTests.cs b/Iceshrimp.Tests/Parsing/MfmTests.cs index 2ce55e88..cf17a4f4 100644 --- a/Iceshrimp.Tests/Parsing/MfmTests.cs +++ b/Iceshrimp.Tests/Parsing/MfmTests.cs @@ -66,10 +66,13 @@ public class MfmTests [TestMethod] public void TestMention() { - const string input = "test @test test"; + const string input = "test @test test @test@instance.tld"; List expected = [ - new MfmTextNode("test "), new MfmMentionNode("test", "test", null), new MfmTextNode(" test") + new MfmTextNode("test "), + new MfmMentionNode("test", "test", null), + new MfmTextNode(" test "), + new MfmMentionNode("test@instance.tld", "test", "instance.tld") ]; var res = Mfm.parse(input); @@ -90,6 +93,30 @@ public class MfmTests MfmSerializer.Serialize(res).Should().BeEquivalentTo(input); } + [TestMethod] + public void TestMentionTrailingDot() + { + const string input = "@test@asdf.com."; + List expected = [new MfmMentionNode("test@asdf.com", "test", "asdf.com"), new MfmTextNode(".")]; + var res = Mfm.parse(input); + + AssertionOptions.FormattingOptions.MaxDepth = 100; + res.ToList().Should().Equal(expected, MfmNodeEqual); + MfmSerializer.Serialize(res).Should().BeEquivalentTo(input); + } + + [TestMethod] + public void TestMentionTrailingDotLocal() + { + const string input = "@test."; + List expected = [new MfmMentionNode("test", "test", null), new MfmTextNode(".")]; + var res = Mfm.parse(input); + + AssertionOptions.FormattingOptions.MaxDepth = 100; + res.ToList().Should().Equal(expected, MfmNodeEqual); + MfmSerializer.Serialize(res).Should().BeEquivalentTo(input); + } + [TestMethod] public void TestCodeBlock() {