diff --git a/src/Elastic.Markdown/Diagnostics/DiagnosticsChannel.cs b/src/Elastic.Markdown/Diagnostics/DiagnosticsChannel.cs index a698c70..8f7c50f 100644 --- a/src/Elastic.Markdown/Diagnostics/DiagnosticsChannel.cs +++ b/src/Elastic.Markdown/Diagnostics/DiagnosticsChannel.cs @@ -138,4 +138,26 @@ public virtual async Task StopAsync(CancellationToken cancellationToken) await _started; await Channel.Reader.Completion; } + + + public void EmitError(string file, string message) + { + var d = new Diagnostic + { + Severity = Severity.Error, + File = file, + Message = message, + }; + Channel.Write(d); + } + public void EmitWarning(string file, string message) + { + var d = new Diagnostic + { + Severity = Severity.Warning, + File = file, + Message = message, + }; + Channel.Write(d); + } } diff --git a/src/Elastic.Markdown/IO/MarkdownFile.cs b/src/Elastic.Markdown/IO/MarkdownFile.cs index 2782053..43e04ce 100644 --- a/src/Elastic.Markdown/IO/MarkdownFile.cs +++ b/src/Elastic.Markdown/IO/MarkdownFile.cs @@ -2,6 +2,7 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information using System.IO.Abstractions; +using Elastic.Markdown.Diagnostics; using Elastic.Markdown.Myst; using Elastic.Markdown.Myst.Directives; using Elastic.Markdown.Slices; @@ -23,8 +24,11 @@ public MarkdownFile(IFileInfo sourceFile, IDirectoryInfo rootPath, MarkdownParse FileName = sourceFile.Name; UrlPathPrefix = context.UrlPathPrefix; MarkdownParser = parser; + Collector = context.Collector; } + public DiagnosticsCollector Collector { get; } + public string? UrlPathPrefix { get; } private MarkdownParser MarkdownParser { get; } public YamlFrontMatter? YamlFrontMatter { get; private set; } @@ -60,6 +64,8 @@ public async Task ParseFullAsync(Cancel ctx) await MinimalParse(ctx); var document = await MarkdownParser.ParseAsync(SourceFile, YamlFrontMatter, ctx); + if (Title == RelativePath) + Collector.EmitWarning(SourceFile.FullName, "Missing yaml front-matter block defining a title or a level 1 header"); return document; } diff --git a/tests/Elastic.Markdown.Tests/Directives/DirectiveBaseTests.cs b/tests/Elastic.Markdown.Tests/Directives/DirectiveBaseTests.cs index 03ec0f7..56f4e35 100644 --- a/tests/Elastic.Markdown.Tests/Directives/DirectiveBaseTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/DirectiveBaseTests.cs @@ -59,7 +59,16 @@ protected DirectiveTest(ITestOutputHelper output, [LanguageInjection("markdown") var logger = new TestLoggerFactory(output); FileSystem = new MockFileSystem(new Dictionary { - { "docs/source/index.md", new MockFileData(content) } + { "docs/source/index.md", new MockFileData(string.IsNullOrEmpty(content) || content.StartsWith("---") ? content : + // language=markdown +$""" +--- +title: Test Document +--- + +{content} +""" + )} }, new MockFileSystemOptions { CurrentDirectory = Paths.Root.FullName diff --git a/tests/Elastic.Markdown.Tests/Directives/YamlFrontMatterTests.cs b/tests/Elastic.Markdown.Tests/Directives/YamlFrontMatterTests.cs index 18b39a1..c478f37 100644 --- a/tests/Elastic.Markdown.Tests/Directives/YamlFrontMatterTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/YamlFrontMatterTests.cs @@ -32,3 +32,17 @@ public void ReadsSubstitutions() .And.ContainKey("key"); } } + +public class EmptyFileWarnsNeedingATitle(ITestOutputHelper output) : DirectiveTest(output, "") +{ + [Fact] + public void ReadsTitle() => File.Title.Should().Be("index.md"); + + [Fact] + public void ReadsNavigationTitle() => File.NavigationTitle.Should().Be("index.md"); + + [Fact] + public void WarnsOfNoTitle() => + Collector.Diagnostics.Should().NotBeEmpty() + .And.Contain(d=>d.Message.Contains("Missing yaml front-matter block defining a title")); +} diff --git a/tests/Elastic.Markdown.Tests/Inline/AnchorLinkTests.cs b/tests/Elastic.Markdown.Tests/Inline/AnchorLinkTests.cs index 85b621e..6911758 100644 --- a/tests/Elastic.Markdown.Tests/Inline/AnchorLinkTests.cs +++ b/tests/Elastic.Markdown.Tests/Inline/AnchorLinkTests.cs @@ -12,14 +12,14 @@ namespace Elastic.Markdown.Tests.Inline; public abstract class AnchorLinkTestBase(ITestOutputHelper output, [LanguageInjection("markdown")] string content) : InlineTest(output, - $""" - ## Hello world +$""" +## Hello world - A paragraph +A paragraph - {content} +{content} - """) +""") { protected override void AddToFileSystem(MockFileSystem fileSystem) { diff --git a/tests/Elastic.Markdown.Tests/Inline/InlneBaseTests.cs b/tests/Elastic.Markdown.Tests/Inline/InlneBaseTests.cs index b85428c..62ac18a 100644 --- a/tests/Elastic.Markdown.Tests/Inline/InlneBaseTests.cs +++ b/tests/Elastic.Markdown.Tests/Inline/InlneBaseTests.cs @@ -69,7 +69,16 @@ protected InlineTest(ITestOutputHelper output, [LanguageInjection("markdown")]st var logger = new TestLoggerFactory(output); FileSystem = new MockFileSystem(new Dictionary { - { "docs/source/index.md", new MockFileData(content) } + { "docs/source/index.md", new MockFileData(string.IsNullOrEmpty(content) || content.StartsWith("---") ? content : + // language=markdown +$""" +--- +title: Test Document +--- + +{content} +""" + )} }, new MockFileSystemOptions { CurrentDirectory = Paths.Root.FullName