diff --git a/src/Elastic.Markdown/Myst/Renderers/HtmxLinkInlineRenderer.cs b/src/Elastic.Markdown/Myst/Renderers/HtmxLinkInlineRenderer.cs index 873ae1cd2..4b77432aa 100644 --- a/src/Elastic.Markdown/Myst/Renderers/HtmxLinkInlineRenderer.cs +++ b/src/Elastic.Markdown/Myst/Renderers/HtmxLinkInlineRenderer.cs @@ -18,6 +18,13 @@ protected override void Write(HtmlRenderer renderer, LinkInline link) { if (renderer.EnableHtmlForInline && !link.IsImage) { + // Avoid nested tags when a URL inside link text was autolinked (elastic/docs-builder#3317). + if (IsNestedInsideLink(link)) + { + renderer.WriteChildren(link); + return; + } + if (link.GetData(nameof(ParserContext.CurrentUrlPath)) is not string) { base.Write(renderer, link); @@ -107,6 +114,18 @@ private static void WriteImage(HtmlRenderer renderer, LinkInline link) private static IHtmxAttributeProvider? GetHtmxProvider(LinkInline link) => link.GetData(nameof(IHtmxAttributeProvider)) as IHtmxAttributeProvider; + + private static bool IsNestedInsideLink(LinkInline link) + { + var parent = link.Parent; + while (parent != null) + { + if (parent is LinkInline) + return true; + parent = parent.Parent; + } + return false; + } } public static class CustomLinkInlineRendererExtensions diff --git a/tests/Elastic.Markdown.Tests/Inline/AutoLinkTests.cs b/tests/Elastic.Markdown.Tests/Inline/AutoLinkTests.cs index cf327efef..5f31a380f 100644 --- a/tests/Elastic.Markdown.Tests/Inline/AutoLinkTests.cs +++ b/tests/Elastic.Markdown.Tests/Inline/AutoLinkTests.cs @@ -245,6 +245,58 @@ public void BothLinksWork() => public void HasNoErrors() => Collector.Diagnostics.Should().HaveCount(0); } +// Regression test for elastic/docs-builder#3317: no nested when a URL is the link text. +public class AutoLinkInsideLinkTextTests(ITestOutputHelper output) : AutoLinkTestBase(output, +""" +Upload to a service like [https://gist.github.com](https://gist.github.com). +""" +) +{ + [Fact] + public void DoesNotCreateNestedAnchor() => + Html.Should().Contain( + """https://gist.github.com""" + ).And.NotMatchRegex(@"]*> Collector.Diagnostics.Should().HaveCount(0); +} + +public class AutoLinkInsideLinkTextWithSurroundingTextTests(ITestOutputHelper output) : AutoLinkTestBase(output, +""" +See [the page at https://example.test.io for details](https://docs.test.io). +""" +) +{ + [Fact] + public void DoesNotAutolinkUrlInsideLinkText() => + Html.Should().Contain( + """the page at https://example.test.io for details""" + ); + + [Fact] + public void HasNoErrors() => Collector.Diagnostics.Should().HaveCount(0); +} + +// Verify that image-inside-link is unaffected by the IsNestedInsideLink guard (images bypass it via the IsImage branch). +public class ImageInsideLinkTests(ITestOutputHelper output) : InlineTest(output, +""" +[![alt text](https://example.com/image.png)](https://example.com) +""" +) +{ + [Fact] + public void RendersOuterAnchor() => + Html.Should().Contain(""""""); + + [Fact] + public void RendersImage() => + Html.Should().Contain(" Collector.Diagnostics.Should().HaveCount(0); +} + public class MultipleAutoLinksTests(ITestOutputHelper output) : AutoLinkTestBase(output, """ First https://first.com then https://second.com and finally https://third.com are all linked.