LinkInlines created via AutoLinkParser are incorrectly rendered with RoundtripRenderer #766

Open
opened 2026-01-29 14:45:08 +00:00 by claunia · 0 comments
Owner

Originally created by @geffner on GitHub (Dec 19, 2025).

Sample code:

using System;
using System.IO;
using Markdig;
using Markdig.Renderers.Roundtrip;

public class Program
{
	public static void Main()
	{
		MarkdownPipeline Pipeline = new MarkdownPipelineBuilder()
			.DisableHtml()
			.UseAutoLinks()
			.EnableTrackTrivia()
			.Build();
		var markdownDocument = Markdown.Parse("http://example.com/", Pipeline);
		using StringWriter sanitizedStringWriter = new();
        RoundtripRenderer sanitizedTextRenderer = new(sanitizedStringWriter);
        sanitizedTextRenderer.Write(markdownDocument);
        Console.WriteLine(sanitizedStringWriter.ToString());
	}
}

Expected output is one of the following:

  • [http://example.com/](http://example.com/)
  • <http://example.com/>

Actual output:
[http://example.com/]()

The actual output consists of a broken hyperlink for the following reason.

When markdig parses Markdown and identifies a URL as an autolink, it creates a LinkInline for it and sets the .Url field to be the URL for the link.
/src/Markdig/Extensions/AutoLinks/AutoLinkParser.cs:

        var inline = new LinkInline()
        {
            Span =
            {
                Start = processor.GetSourcePosition(startPosition, out int line, out int column),
            },
            Line = line,
            Column = column,
───────▶    Url = c == 'w' ? ((Options.UseHttpsForWWWLinks ? "https://" : "http://") + link) : link,
            IsClosed = true,
            IsAutoLink = true,
        };

However, when this LinkInline object is rendered back to Markdown, only the object's .UnescapedUrl field is used, not the .Url field. And since that .UnescapedUrl field is never assigned a value, it ends up getting rendered as an empty string.
/src/Markdig/Renderers/Roundtrip/Inlines/LinkInlineRenderer.cs:

                renderer.Write('(');
                renderer.Write(link.TriviaBeforeUrl);
                if (link.UrlHasPointyBrackets)
                {
                    renderer.Write('<');
                }
───────────▶    renderer.Write(link.UnescapedUrl);
                if (link.UrlHasPointyBrackets)
                {
                    renderer.Write('>');
                }
                renderer.Write(link.TriviaAfterUrl);

                if (!string.IsNullOrEmpty(link.Title))
                {
                    var open = link.TitleEnclosingCharacter;
                    var close = link.TitleEnclosingCharacter;
                    if (link.TitleEnclosingCharacter == '(')
                    {
                        close = ')';
                    }
                    renderer.Write(open);
                    renderer.Write(link.UnescapedTitle);
                    renderer.Write(close);
                    renderer.Write(link.TriviaAfterTitle);
                }

                renderer.Write(')');
Originally created by @geffner on GitHub (Dec 19, 2025). Sample code: ```c# using System; using System.IO; using Markdig; using Markdig.Renderers.Roundtrip; public class Program { public static void Main() { MarkdownPipeline Pipeline = new MarkdownPipelineBuilder() .DisableHtml() .UseAutoLinks() .EnableTrackTrivia() .Build(); var markdownDocument = Markdown.Parse("http://example.com/", Pipeline); using StringWriter sanitizedStringWriter = new(); RoundtripRenderer sanitizedTextRenderer = new(sanitizedStringWriter); sanitizedTextRenderer.Write(markdownDocument); Console.WriteLine(sanitizedStringWriter.ToString()); } } ``` Expected output is one of the following: - `[http://example.com/](http://example.com/)` - `<http://example.com/>` Actual output: `[http://example.com/]()` The actual output consists of a broken hyperlink for the following reason. When markdig parses Markdown and identifies a URL as an autolink, it creates a `LinkInline` for it and sets the `.Url` field to be the URL for the link. [/src/Markdig/Extensions/AutoLinks/AutoLinkParser.cs](https://github.com/xoofx/markdig/blob/cd7b9ca0ef66cb582232cbceaefbfe4195cf575b/src/Markdig/Extensions/AutoLinks/AutoLinkParser.cs#L160): ```c# var inline = new LinkInline() { Span = { Start = processor.GetSourcePosition(startPosition, out int line, out int column), }, Line = line, Column = column, ───────▶ Url = c == 'w' ? ((Options.UseHttpsForWWWLinks ? "https://" : "http://") + link) : link, IsClosed = true, IsAutoLink = true, }; ``` However, when this `LinkInline` object is rendered back to Markdown, only the object's `.UnescapedUrl` field is used, not the `.Url` field. And since that `.UnescapedUrl` field is never assigned a value, it ends up getting rendered as an empty string. [/src/Markdig/Renderers/Roundtrip/Inlines/LinkInlineRenderer.cs](https://github.com/xoofx/markdig/blob/cd7b9ca0ef66cb582232cbceaefbfe4195cf575b/src/Markdig/Renderers/Roundtrip/Inlines/LinkInlineRenderer.cs#L42): ```c# renderer.Write('('); renderer.Write(link.TriviaBeforeUrl); if (link.UrlHasPointyBrackets) { renderer.Write('<'); } ───────────▶ renderer.Write(link.UnescapedUrl); if (link.UrlHasPointyBrackets) { renderer.Write('>'); } renderer.Write(link.TriviaAfterUrl); if (!string.IsNullOrEmpty(link.Title)) { var open = link.TitleEnclosingCharacter; var close = link.TitleEnclosingCharacter; if (link.TitleEnclosingCharacter == '(') { close = ')'; } renderer.Write(open); renderer.Write(link.UnescapedTitle); renderer.Write(close); renderer.Write(link.TriviaAfterTitle); } renderer.Write(')'); ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/markdig#766