Skip to content

Commit bef1b03

Browse files
authored
Enforce _snippets folder rules for includes (#190)
1 parent 2c29f46 commit bef1b03

File tree

8 files changed

+115
-8
lines changed

8 files changed

+115
-8
lines changed
File renamed without changes.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
This snippet is not included anywhere
2+
3+
```{warning}
4+
This snippet is not included anywhere
5+
```

docs/source/syntax/file_inclusion.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ If there are portions of content that are relevant to multiple pages, you can in
1212
### Syntax
1313

1414
```markdown
15-
:::{include} _snippets/my_snippet.md
15+
:::{include} _snippets/reusable-snippet.md
1616
:::
1717
```
1818

19-
:::{include} _snippets/my_snippet.md
19+
:::{include} _snippets/reusable-snippet.md
2020
:::
2121

2222
### Asciidoc syntax

src/Elastic.Markdown/IO/DocumentationFile.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ public record StaticFile(IFileInfo SourceFile, IDirectoryInfo RootPath)
1919

2020
public record ExcludedFile(IFileInfo SourceFile, IDirectoryInfo RootPath)
2121
: DocumentationFile(SourceFile, RootPath);
22+
23+
public record SnippetFile(IFileInfo SourceFile, IDirectoryInfo RootPath)
24+
: DocumentationFile(SourceFile, RootPath);

src/Elastic.Markdown/IO/DocumentationSet.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ private DocumentationFile CreateMarkDownFile(IFileInfo file, BuildContext contex
9090
if (Configuration.Globs.Any(g => g.IsMatch(relativePath)))
9191
return new MarkdownFile(file, SourcePath, MarkdownParser, context);
9292

93+
// we ignore files in folders that start with an underscore
94+
if (relativePath.IndexOf("_snippets", StringComparison.Ordinal) >= 0)
95+
return new SnippetFile(file, SourcePath);
96+
97+
// we ignore files in folders that start with an underscore
9398
if (relativePath.IndexOf("/_", StringComparison.Ordinal) > 0 || relativePath.StartsWith("_"))
9499
return new ExcludedFile(file, SourcePath);
95100

src/Elastic.Markdown/Myst/Directives/IncludeBlock.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,24 @@ private void ExtractInclusionPath(ParserContext context)
7171
Found = true;
7272
else
7373
this.EmitError($"`{IncludePath}` does not exist.");
74+
75+
// literal includes may point to locations other than `_snippets` since they do not
76+
// participate in emitting links
77+
if (Literal)
78+
return;
79+
80+
var file = FileSystem.FileInfo.New(IncludePath);
81+
82+
if (file.Directory != null && file.Directory.FullName.IndexOf("_snippets", StringComparison.Ordinal) < 0)
83+
{
84+
this.EmitError($"{{include}} only supports including snippets from `_snippet` folders. `{IncludePath}` is not a snippet");
85+
Found = false;
86+
}
87+
88+
if (file.FullName == context.Path.FullName)
89+
{
90+
this.EmitError($"{{include}} cyclical include detected `{IncludePath}` points to itself");
91+
Found = false;
92+
}
7493
}
7594
}

tests/Elastic.Markdown.Tests/DocSet/LinkReferenceTests.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,23 @@
1010

1111
namespace Elastic.Markdown.Tests.DocSet;
1212

13-
public class LinkReferenceTests(ITestOutputHelper output) : NavigationTestsBase(output)
13+
public class LinkReferenceTests : NavigationTestsBase
1414
{
15+
public LinkReferenceTests(ITestOutputHelper output) : base(output) => Reference = LinkReference.Create(Set);
16+
17+
private LinkReference Reference { get; }
18+
1519
[Fact]
16-
public void Create()
17-
{
18-
var reference = LinkReference.Create(Set);
20+
public void ShouldNotBeNull() =>
21+
Reference.Should().NotBeNull();
1922

20-
reference.Should().NotBeNull();
21-
}
23+
[Fact]
24+
public void EmitsLinks() =>
25+
Reference.Links.Should().NotBeNullOrEmpty();
26+
27+
[Fact]
28+
public void ShouldNotIncludeSnippets() =>
29+
Reference.Links.Should().NotContain(l => l.Contains("_snippets/"));
2230
}
2331

2432
public class GitCheckoutInformationTests(ITestOutputHelper output) : NavigationTestsBase(output)

tests/Elastic.Markdown.Tests/FileInclusion/IncludeTests.cs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,70 @@ public void EmitsError()
114114
.OnlyContain(d => d.Message.Contains("include requires an argument."));
115115
}
116116
}
117+
118+
public class IncludeNeedsToLiveInSpecialFolder(ITestOutputHelper output) : DirectiveTest<IncludeBlock>(output,
119+
"""
120+
```{include} test.md
121+
```
122+
"""
123+
)
124+
{
125+
126+
protected override void AddToFileSystem(MockFileSystem fileSystem)
127+
{
128+
// language=markdown
129+
var inclusion = "*Hello world*";
130+
fileSystem.AddFile(@"docs/source/test.md", inclusion);
131+
}
132+
133+
[Fact]
134+
public void ParsesBlock() => Block.Should().NotBeNull();
135+
136+
[Fact]
137+
public void IncludesNothing() => Html.Should().Be("");
138+
139+
[Fact]
140+
public void EmitsError()
141+
{
142+
Collector.Diagnostics.Should().NotBeNullOrEmpty().And.HaveCount(1);
143+
Collector.Diagnostics.Should().OnlyContain(d => d.Severity == Severity.Error);
144+
Collector.Diagnostics.Should()
145+
.OnlyContain(d => d.Message.Contains("only supports including snippets from `_snippet` folders."));
146+
}
147+
}
148+
149+
150+
public class CanNotIncludeItself(ITestOutputHelper output) : DirectiveTest<IncludeBlock>(output,
151+
"""
152+
```{include} _snippets/test.md
153+
```
154+
"""
155+
)
156+
{
157+
158+
protected override void AddToFileSystem(MockFileSystem fileSystem)
159+
{
160+
// language=markdown
161+
var inclusion =
162+
"""
163+
:::{include} test.md
164+
:::
165+
""";
166+
fileSystem.AddFile(@"docs/source/_snippets/test.md", inclusion);
167+
}
168+
169+
[Fact]
170+
public void ParsesBlock() => Block.Should().NotBeNull();
171+
172+
[Fact]
173+
public void IncludesNothing() => Html.Should().Be("");
174+
175+
[Fact]
176+
public void EmitsError()
177+
{
178+
Collector.Diagnostics.Should().NotBeNullOrEmpty().And.HaveCount(1);
179+
Collector.Diagnostics.Should().OnlyContain(d => d.Severity == Severity.Error);
180+
Collector.Diagnostics.Should()
181+
.OnlyContain(d => d.Message.Contains("cyclical include detected"));
182+
}
183+
}

0 commit comments

Comments
 (0)