Skip to content

Commit dddf0e4

Browse files
committed
Emit hints for deep-linking virtual files during validation:
- Add checks to identify files with deep paths and children, suggesting the use of `folder` for better structure. - Extend test coverage for virtual file scenarios to validate hint emission and suppression. - Refactor diagnostics in physical docset navigation to include hints while ensuring no errors or warnings occur.
1 parent 22382ff commit dddf0e4

File tree

3 files changed

+136
-2
lines changed

3 files changed

+136
-2
lines changed

src/Elastic.Documentation.Configuration/DocSet/DocumentationSetFile.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,14 @@ private static ITableOfContentsItem ResolveFileRef(
229229
: new FileRef(fullPath, fileRef.Hidden, [], context);
230230
}
231231

232+
// Emit hint if file has children and uses deep-linking (path contains '/')
233+
// This suggests using 'folder' instead of 'file' would be better
234+
if (fileRef.Path.Contains('/') && fileRef.Children.Count > 0)
235+
{
236+
collector.EmitHint(context,
237+
$"File '{fileRef.Path}' uses deep-linking with children. Consider using 'folder' instead of 'file' for better navigation structure. Virtual files are primarily intended to group sibling files together.");
238+
}
239+
232240
// Children of a file should be resolved in the same directory as the parent file.
233241
// Special handling for FolderIndexFileRef (folder+file combinations from YAML):
234242
// - These are created when both folder and file keys exist (e.g., "folder: path/to/dir, file: index.md")

tests/Navigation.Tests/Isolation/PhysicalDocsetTests.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.IO.Abstractions;
66
using Elastic.Documentation.Configuration;
77
using Elastic.Documentation.Configuration.DocSet;
8+
using Elastic.Documentation.Diagnostics;
89
using Elastic.Documentation.Navigation.Isolated;
910
using FluentAssertions;
1011

@@ -118,8 +119,17 @@ public async Task PhysicalDocsetCanBeNavigated()
118119
var folderUrls = folders.Select(f => f.Url).ToList();
119120
folderUrls.Should().Contain("/contribute");
120121

121-
// No errors should be emitted during navigation construction
122-
context.Diagnostics.Should().BeEmpty();
122+
// No errors or warnings should be emitted during navigation construction
123+
// Hints are acceptable for best practice guidance
124+
context.Collector.Errors.Should().Be(0, "no errors should be emitted");
125+
context.Collector.Warnings.Should().Be(0, "no warnings should be emitted");
126+
127+
// Verify that the hint about deep-linking virtual file was emitted
128+
var hints = context.Diagnostics.Where(d => d.Severity == Severity.Hint).ToList();
129+
hints.Should().Contain(d =>
130+
d.Message.Contains("nest-under-index/index.md") &&
131+
d.Message.Contains("deep-linking"),
132+
"should emit hint for deep-linking virtual file");
123133
}
124134

125135
[Fact]

tests/Navigation.Tests/Isolation/ValidationTests.cs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
using System.IO.Abstractions.TestingHelpers;
66
using Elastic.Documentation.Configuration.DocSet;
7+
using Elastic.Documentation.Diagnostics;
78
using Elastic.Documentation.Extensions;
89
using Elastic.Documentation.Navigation.Isolated;
910
using FluentAssertions;
@@ -169,4 +170,119 @@ public async Task ValidationEmitsErrorWhenTocYmlFileNotFound()
169170
d.Message.Contains("Table of contents file not found") &&
170171
d.Message.Contains("api/toc.yml"));
171172
}
173+
174+
[Fact]
175+
public async Task ValidationEmitsHintForDeepLinkingVirtualFiles()
176+
{
177+
// language=yaml
178+
var yaml = """
179+
project: 'test-project'
180+
toc:
181+
- file: a/b/c/getting-started.md
182+
children:
183+
- file: a/b/c/setup.md
184+
- file: a/b/c/install.md
185+
""";
186+
187+
var fileSystem = new MockFileSystem();
188+
fileSystem.AddDirectory("/docs");
189+
var context = CreateContext(fileSystem);
190+
var docSet = DocumentationSetFile.LoadAndResolve(context.Collector, yaml, fileSystem.NewDirInfo("docs"));
191+
_ = context.Collector.StartAsync(TestContext.Current.CancellationToken);
192+
193+
_ = new DocumentationSetNavigation<IDocumentationFile>(docSet, context, GenericDocumentationFileFactory.Instance);
194+
195+
await context.Collector.StopAsync(TestContext.Current.CancellationToken);
196+
197+
context.Collector.Hints.Should().BeGreaterThan(0, "should have emitted a hint for deep-linking virtual file");
198+
var diagnostics = context.Diagnostics;
199+
diagnostics.Should().Contain(d =>
200+
d.Severity == Severity.Hint &&
201+
d.Message.Contains("a/b/c/getting-started.md") &&
202+
d.Message.Contains("deep-linking") &&
203+
d.Message.Contains("folder"));
204+
}
205+
206+
[Fact]
207+
public async Task ValidationEmitsHintForNestedPathVirtualFiles()
208+
{
209+
// language=yaml
210+
var yaml = """
211+
project: 'test-project'
212+
toc:
213+
- file: guides/api/overview.md
214+
children:
215+
- file: guides/api/authentication.md
216+
- file: guides/api/endpoints.md
217+
""";
218+
219+
var fileSystem = new MockFileSystem();
220+
fileSystem.AddDirectory("/docs");
221+
var context = CreateContext(fileSystem);
222+
var docSet = DocumentationSetFile.LoadAndResolve(context.Collector, yaml, fileSystem.NewDirInfo("docs"));
223+
_ = context.Collector.StartAsync(TestContext.Current.CancellationToken);
224+
225+
_ = new DocumentationSetNavigation<IDocumentationFile>(docSet, context, GenericDocumentationFileFactory.Instance);
226+
227+
await context.Collector.StopAsync(TestContext.Current.CancellationToken);
228+
229+
context.Collector.Hints.Should().BeGreaterThan(0);
230+
var diagnostics = context.Diagnostics;
231+
diagnostics.Should().Contain(d =>
232+
d.Severity == Severity.Hint &&
233+
d.Message.Contains("guides/api/overview.md") &&
234+
d.Message.Contains("Virtual files are primarily intended to group sibling files together"));
235+
}
236+
237+
[Fact]
238+
public async Task ValidationDoesNotEmitHintForSimpleVirtualFiles()
239+
{
240+
// language=yaml
241+
var yaml = """
242+
project: 'test-project'
243+
toc:
244+
- file: guide.md
245+
children:
246+
- file: chapter1.md
247+
- file: chapter2.md
248+
""";
249+
250+
var fileSystem = new MockFileSystem();
251+
fileSystem.AddDirectory("/docs");
252+
var context = CreateContext(fileSystem);
253+
var docSet = DocumentationSetFile.LoadAndResolve(context.Collector, yaml, fileSystem.NewDirInfo("docs"));
254+
_ = context.Collector.StartAsync(TestContext.Current.CancellationToken);
255+
256+
_ = new DocumentationSetNavigation<IDocumentationFile>(docSet, context, GenericDocumentationFileFactory.Instance);
257+
258+
await context.Collector.StopAsync(TestContext.Current.CancellationToken);
259+
260+
context.Collector.Hints.Should().Be(0, "simple virtual files without deep-linking should not trigger hints");
261+
context.Diagnostics.Should().BeEmpty();
262+
}
263+
264+
[Fact]
265+
public async Task ValidationDoesNotEmitHintForFilesWithoutChildren()
266+
{
267+
// language=yaml
268+
var yaml = """
269+
project: 'test-project'
270+
toc:
271+
- file: a/b/c/getting-started.md
272+
- file: guides/setup.md
273+
""";
274+
275+
var fileSystem = new MockFileSystem();
276+
fileSystem.AddDirectory("/docs");
277+
var context = CreateContext(fileSystem);
278+
var docSet = DocumentationSetFile.LoadAndResolve(context.Collector, yaml, fileSystem.NewDirInfo("docs"));
279+
_ = context.Collector.StartAsync(TestContext.Current.CancellationToken);
280+
281+
_ = new DocumentationSetNavigation<IDocumentationFile>(docSet, context, GenericDocumentationFileFactory.Instance);
282+
283+
await context.Collector.StopAsync(TestContext.Current.CancellationToken);
284+
285+
context.Collector.Hints.Should().Be(0, "files without children should not trigger hints, even with deep paths");
286+
context.Diagnostics.Should().BeEmpty();
287+
}
172288
}

0 commit comments

Comments
 (0)