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
19 changes: 19 additions & 0 deletions src/Elastic.Markdown/Myst/Renderers/HtmxLinkInlineRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ protected override void Write(HtmlRenderer renderer, LinkInline link)
{
if (renderer.EnableHtmlForInline && !link.IsImage)
{
// Avoid nested <a> 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);
Expand Down Expand Up @@ -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
Expand Down
52 changes: 52 additions & 0 deletions tests/Elastic.Markdown.Tests/Inline/AutoLinkTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,58 @@ public void BothLinksWork() =>
public void HasNoErrors() => Collector.Diagnostics.Should().HaveCount(0);
}

// Regression test for elastic/docs-builder#3317: no nested <a> 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(
"""<a href="https://gist.github.com" target="_blank" rel="noopener noreferrer">https://gist.github.com</a>"""
).And.NotMatchRegex(@"<a\b[^>]*><a\b");

[Fact]
public void HasNoErrors() => 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(
"""<a href="https://docs.test.io" target="_blank" rel="noopener noreferrer">the page at https://example.test.io for details</a>"""
);

[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<LinkInline>(output,
"""
[![alt text](https://example.com/image.png)](https://example.com)
"""
)
{
[Fact]
public void RendersOuterAnchor() =>
Html.Should().Contain("""<a href="https://example.com" target="_blank" rel="noopener noreferrer">""");

[Fact]
public void RendersImage() =>
Html.Should().Contain("<img src=\"https://example.com/image.png\"");

[Fact]
public void HasNoErrors() => 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.
Expand Down
Loading