Skip to content

Commit 222c41a

Browse files
authored
Allow substitutions in external links (#715)
* Allow substitutions in external links * Better error messages * Fix typo
1 parent 6b629c3 commit 222c41a

File tree

3 files changed

+68
-17
lines changed

3 files changed

+68
-17
lines changed

src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.IO.Abstractions;
88
using System.Text.RegularExpressions;
99
using Elastic.Markdown.Diagnostics;
10+
using Elastic.Markdown.Helpers;
1011
using Elastic.Markdown.IO;
1112
using Elastic.Markdown.Myst.Comments;
1213
using Markdig;
@@ -100,6 +101,26 @@ private static void ValidateAndProcessLink(LinkInline link, InlineProcessor proc
100101
{
101102
var url = link.Url;
102103

104+
if (url?.Contains("{{") == true)
105+
{
106+
var replacedUrl = url.ReplaceSubstitutions(processor.GetContext());
107+
if (replacedUrl.Contains("{{"))
108+
{
109+
processor.EmitError(link,
110+
$"The url contains unresolved template expressions: '{replacedUrl}'. Please check if there is an appropriate global or frontmatter subs variable."
111+
);
112+
return;
113+
}
114+
115+
if (!replacedUrl.StartsWith("http"))
116+
{
117+
processor.EmitError(link, $"Link is resolved to '{replacedUrl}'. Only external links are allowed to be resolved from template expressions.");
118+
return;
119+
}
120+
url = replacedUrl;
121+
link.Url = replacedUrl;
122+
}
123+
103124
if (!ValidateBasicUrl(link, processor, url))
104125
return;
105126

@@ -124,15 +145,6 @@ private static bool ValidateBasicUrl(LinkInline link, InlineProcessor processor,
124145
processor.EmitError(link, "Found empty url");
125146
return false;
126147
}
127-
128-
if (url.Contains("{{") || url.Contains("}}"))
129-
{
130-
processor.EmitWarning(link,
131-
"The url contains a template expression. Please do not use template expressions in links. " +
132-
"See https://github.com/elastic/docs-builder/issues/182 for further information.");
133-
return false;
134-
}
135-
136148
return true;
137149
}
138150

tests/Elastic.Markdown.Tests/Inline/InlineLinkTests.cs

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,15 @@
1111
namespace Elastic.Markdown.Tests.Inline;
1212

1313
public abstract class LinkTestBase(ITestOutputHelper output, [LanguageInjection("markdown")] string content)
14-
: InlineTest<LinkInline>(output, content)
14+
: InlineTest<LinkInline>(
15+
output,
16+
content,
17+
new Dictionary<string, string>
18+
{
19+
{ "some-url-with-a-version", "https://github.com/elastic/fake-repo/tree/v1.17.0" },
20+
{ "some-url-path-prefix", "/something" },
21+
}
22+
)
1523
{
1624
[Fact]
1725
public void ParsesBlock() => Block.Should().NotBeNull();
@@ -161,28 +169,59 @@ public void EmitsCrossLink()
161169
}
162170
}
163171

164-
public class LinksWithInterpolationWarning(ITestOutputHelper output) : LinkTestBase(output,
172+
public class LinkWithUnresolvedInterpolationError(ITestOutputHelper output) : LinkTestBase(output,
165173
"""
166-
[global search field]({{kibana-ref}}/introduction.html#kibana-navigation-search)
174+
[global search field]({{this-variable-does-not-exist}}/introduction.html#kibana-navigation-search)
175+
"""
176+
)
177+
{
178+
[Fact]
179+
public void HasErrors()
180+
{
181+
Collector.Diagnostics.Should().HaveCount(1);
182+
Collector.Diagnostics.First().Severity.Should().Be(Severity.Error);
183+
Collector.Diagnostics.First().Message.Should().Contain("he url contains unresolved template expressions: '{{this-variable-does-not-exist}}/introduction.html#kibana-navigation-search'. Please check if there is an appropriate global or frontmatter subs variable.");
184+
}
185+
}
186+
187+
public class ExternalLinksWithInterpolationSuccess(ITestOutputHelper output) : LinkTestBase(output,
188+
"""
189+
[link to app]({{some-url-with-a-version}})
167190
"""
168191
)
169192
{
170193
[Fact]
171194
public void GeneratesHtml() =>
172195
// language=html
173196
Html.Should().Contain(
174-
"""<p><a href="%7B%7Bkibana-ref%7D%7D/introduction.html#kibana-navigation-search">global search field</a></p>"""
197+
"""<p><a href="https://github.com/elastic/fake-repo/tree/v1.17.0">link to app</a></p>"""
175198
);
176199

200+
[Fact]
201+
public void HasNoWarningsOrErrors()
202+
{
203+
Collector.Diagnostics.Should().HaveCount(0);
204+
}
205+
}
206+
207+
public class InternalLinksWithInterpolationWarning(ITestOutputHelper output) : LinkTestBase(output,
208+
"""
209+
[link to app]({{some-url-path-prefix}}/hello-world)
210+
"""
211+
)
212+
{
177213
[Fact]
178214
public void HasWarnings()
179215
{
180216
Collector.Diagnostics.Should().HaveCount(1);
181-
Collector.Diagnostics.First().Severity.Should().Be(Severity.Warning);
182-
Collector.Diagnostics.First().Message.Should().Contain("The url contains a template expression. Please do not use template expressions in links. See https://github.com/elastic/docs-builder/issues/182 for further information.");
217+
Collector.Diagnostics.First().Severity.Should().Be(Severity.Error);
218+
Collector.Diagnostics.First().Message.Should().Contain("Link is resolved to '/something/hello-world'. Only external links are allowed to be resolved from template expressions.");
183219
}
184220
}
185221

222+
223+
224+
186225
public class NonExistingLinks(ITestOutputHelper output) : LinkTestBase(output,
187226
"""
188227
[Non Existing Link](/non-existing.md)

tests/Elastic.Markdown.Tests/Inline/InlneBaseTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ public override async ValueTask InitializeAsync()
4848

4949
}
5050

51-
public abstract class InlineTest<TDirective>(ITestOutputHelper output, [LanguageInjection("markdown")] string content)
52-
: InlineTest(output, content)
51+
public abstract class InlineTest<TDirective>(ITestOutputHelper output, [LanguageInjection("markdown")] string content, Dictionary<string, string>? globalVariables = null)
52+
: InlineTest(output, content, globalVariables)
5353
where TDirective : ContainerInline
5454
{
5555
protected TDirective? Block { get; private set; }

0 commit comments

Comments
 (0)