diff --git a/docs/_docset.yml b/docs/_docset.yml index ed1e8aa7d..4c87c4e85 100644 --- a/docs/_docset.yml +++ b/docs/_docset.yml @@ -123,6 +123,7 @@ toc: - file: index.md - file: build.md - file: diff-validate.md + - file: format.md - file: index-command.md - file: mv.md - file: serve.md diff --git a/docs/cli/docset/format.md b/docs/cli/docset/format.md new file mode 100644 index 000000000..b5a88154b --- /dev/null +++ b/docs/cli/docset/format.md @@ -0,0 +1,114 @@ +# format + +Format documentation files by fixing common issues like irregular space + +## Usage + +``` +docs-builder format --check [options...] +docs-builder format --write [options...] +``` + +## Options + +`--check` +: Check if files need formatting without modifying them. Exits with code 1 if formatting is needed, 0 if all files are properly formatted. (required, mutually exclusive with --write) + +`--write` +: Write formatting changes to files. (required, mutually exclusive with --check) + +`-p|--path` `` +: Path to the documentation folder, defaults to pwd. (optional) + +## Description + +The `format` command automatically detects and fixes formatting issues in your documentation files. The command only processes Markdown files (`.md`) that are included in your `_docset.yml` table of contents, ensuring that only intentional documentation files are modified. + +You must specify exactly one of `--check` or `--write`: +- `--check` validates formatting without modifying files, useful for CI/CD pipelines +- `--write` applies formatting changes to files + +Currently, it handles irregular space characters that may impair Markdown rendering. + +### Irregular Space Detection + +The format command detects and replaces 24 types of irregular space characters with regular spaces, including: + +- No-Break Space (U+00A0) +- En Space (U+2002) +- Em Space (U+2003) +- Zero Width Space (U+200B) +- Line Separator (U+2028) +- Paragraph Separator (U+2029) +- And 18 other irregular space variants + +These characters can cause unexpected rendering issues in Markdown and are often introduced accidentally through copy-paste operations from other applications. + +## Examples + +### Check if formatting is needed (CI/CD) + +```bash +docs-builder format --check +``` + +Exit codes: +- `0`: All files are properly formatted +- `1`: Some files need formatting + +### Apply formatting changes + +```bash +docs-builder format --write +``` + +### Check specific documentation folder + +```bash +docs-builder format --check --path /path/to/docs +``` + +### Format specific documentation folder + +```bash +docs-builder format --write --path /path/to/docs +``` + +## Output + +### Check mode output + +When using `--check`, the command reports which files need formatting: + +``` +Checking documentation in: /path/to/docs + +Formatting needed: + Files needing formatting: 2 + irregular space fixes needed: 3 + +Run 'docs-builder format --write' to apply changes +``` + +### Write mode output + +When using `--write`, the command reports the changes made: + +``` +Formatting documentation in: /path/to/docs +Formatted index.md (2 change(s)) + +Formatting complete: + Files processed: 155 + Files modified: 1 + irregular space fixes: 2 +``` + +## Future Enhancements + +The format command is designed to be extended with additional formatting capabilities in the future, such as: + +- Line ending normalization +- Trailing whitespace removal +- Consistent heading spacing +- And other formatting fixes diff --git a/docs/index.md b/docs/index.md index 7e999138f..ead514e83 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,7 +4,7 @@ navigation_title: Elastic Docs v3 # Welcome to Elastic Docs v3 -Elastic Docs V3 is our next-generation documentation platform designed to improve the experience of learning, using, and contributing to Elastic products. Built on a foundation of modern authoring tools and scalable infrastructure, V3 offers faster builds, streamlined versioning, and enhanced navigation to guide users through Elastic’s complex ecosystem. +Elastic Docs V3 is our next-generation documentation platform designed to improve the experience of learning, using, and contributing to Elastic products. Built on a foundation of modern authoring tools and scalable infrastructure, V3 offers faster builds, streamlined versioning, and enhanced navigation to guide users through Elastic’s complex ecosystem. ## What do you want to do today? diff --git a/src/Elastic.Markdown/Myst/Linters/SpaceNormalizer.cs b/src/Elastic.Markdown/Myst/Linters/SpaceNormalizer.cs new file mode 100644 index 000000000..5a842a2dc --- /dev/null +++ b/src/Elastic.Markdown/Myst/Linters/SpaceNormalizer.cs @@ -0,0 +1,108 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// 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.Buffers; +using Elastic.Markdown.Diagnostics; +using Markdig; +using Markdig.Helpers; +using Markdig.Parsers; +using Markdig.Parsers.Inlines; +using Markdig.Renderers; +using Markdig.Renderers.Html; +using Markdig.Renderers.Html.Inlines; +using Markdig.Syntax.Inlines; + +namespace Elastic.Markdown.Myst.Linters; + +public static class SpaceNormalizerBuilderExtensions +{ + public static MarkdownPipelineBuilder UseSpaceNormalizer(this MarkdownPipelineBuilder pipeline) + { + pipeline.Extensions.AddIfNotAlready(); + return pipeline; + } +} + +public class SpaceNormalizerBuilderExtension : IMarkdownExtension +{ + public void Setup(MarkdownPipelineBuilder pipeline) => + pipeline.InlineParsers.InsertBefore(new SpaceNormalizerParser()); + + public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) => + renderer.ObjectRenderers.InsertAfter(new SpaceNormalizerRenderer()); +} + +public class SpaceNormalizerParser : InlineParser +{ + // Collection of irregular space characters that may impair Markdown rendering + private static readonly char[] IrregularSpaceChars = + [ + '\u000B', // Line Tabulation (\v) - + '\u000C', // Form Feed (\f) - + '\u00A0', // No-Break Space - + '\u0085', // Next Line + '\u1680', // Ogham Space Mark + '\u180E', // Mongolian Vowel Separator - + '\ufeff', // Zero Width No-Break Space - + '\u2000', // En Quad + '\u2001', // Em Quad + '\u2002', // En Space - + '\u2003', // Em Space - + '\u2004', // Tree-Per-Em + '\u2005', // Four-Per-Em + '\u2006', // Six-Per-Em + '\u2007', // Figure Space + '\u2008', // Punctuation Space - + '\u2009', // Thin Space + '\u200A', // Hair Space + '\u200B', // Zero Width Space - + '\u2028', // Line Separator + '\u2029', // Paragraph Separator + '\u202F', // Narrow No-Break Space + '\u205F', // Medium Mathematical Space + '\u3000' // Ideographic Space + ]; + private static readonly SearchValues SpaceSearchValues = SearchValues.Create(IrregularSpaceChars); + + // Track which files have already had the hint emitted to avoid duplicates + private static readonly HashSet FilesWithHintEmitted = []; + + public SpaceNormalizerParser() => OpeningCharacters = IrregularSpaceChars; + + public override bool Match(InlineProcessor processor, ref StringSlice slice) + { + var span = slice.AsSpan().Slice(0, 1); + if (span.IndexOfAny(SpaceSearchValues) == -1) + return false; + + processor.Inline = IrregularSpace.Instance; + + // Emit a single hint per file on first detection + var context = processor.GetContext(); + var filePath = context.MarkdownSourcePath.FullName; + + lock (FilesWithHintEmitted) + { + if (!FilesWithHintEmitted.Contains(filePath)) + { + _ = FilesWithHintEmitted.Add(filePath); + processor.EmitHint(processor.Inline, 1, "Irregular space detected. Run 'docs-builder format --write' to automatically fix all instances."); + } + } + + slice.SkipChar(); + return true; + } +} + +public class IrregularSpace : LeafInline +{ + public static readonly IrregularSpace Instance = new(); +}; + +public class SpaceNormalizerRenderer : HtmlObjectRenderer +{ + protected override void Write(HtmlRenderer renderer, IrregularSpace obj) => + renderer.Write(' '); +} diff --git a/src/Elastic.Markdown/Myst/Linters/WhiteSpaceNormalizer.cs b/src/Elastic.Markdown/Myst/Linters/WhiteSpaceNormalizer.cs deleted file mode 100644 index 25af91bef..000000000 --- a/src/Elastic.Markdown/Myst/Linters/WhiteSpaceNormalizer.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Licensed to Elasticsearch B.V under one or more agreements. -// 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.Buffers; -using Elastic.Markdown.Diagnostics; -using Markdig; -using Markdig.Helpers; -using Markdig.Parsers; -using Markdig.Parsers.Inlines; -using Markdig.Renderers; -using Markdig.Renderers.Html; -using Markdig.Renderers.Html.Inlines; -using Markdig.Syntax.Inlines; - -namespace Elastic.Markdown.Myst.Linters; - -public static class WhiteSpaceNormalizerBuilderExtensions -{ - public static MarkdownPipelineBuilder UseWhiteSpaceNormalizer(this MarkdownPipelineBuilder pipeline) - { - pipeline.Extensions.AddIfNotAlready(); - return pipeline; - } -} - -public class WhiteSpaceNormalizerBuilderExtension : IMarkdownExtension -{ - public void Setup(MarkdownPipelineBuilder pipeline) => - pipeline.InlineParsers.InsertBefore(new WhiteSpaceNormalizerParser()); - - public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) => - renderer.ObjectRenderers.InsertAfter(new WhiteSpaceNormalizerRenderer()); -} - -public class WhiteSpaceNormalizerParser : InlineParser -{ - // Collection of irregular whitespace characters that may impair Markdown rendering - private static readonly char[] IrregularWhitespaceChars = - [ - '\u000B', // Line Tabulation (\v) - - '\u000C', // Form Feed (\f) - - '\u00A0', // No-Break Space - - '\u0085', // Next Line - '\u1680', // Ogham Space Mark - '\u180E', // Mongolian Vowel Separator - - '\ufeff', // Zero Width No-Break Space - - '\u2000', // En Quad - '\u2001', // Em Quad - '\u2002', // En Space - - '\u2003', // Em Space - - '\u2004', // Tree-Per-Em - '\u2005', // Four-Per-Em - '\u2006', // Six-Per-Em - '\u2007', // Figure Space - '\u2008', // Punctuation Space - - '\u2009', // Thin Space - '\u200A', // Hair Space - '\u200B', // Zero Width Space - - '\u2028', // Line Separator - '\u2029', // Paragraph Separator - '\u202F', // Narrow No-Break Space - '\u205F', // Medium Mathematical Space - '\u3000' // Ideographic Space - ]; - private static readonly SearchValues WhiteSpaceSearchValues = SearchValues.Create(IrregularWhitespaceChars); - - public WhiteSpaceNormalizerParser() => OpeningCharacters = IrregularWhitespaceChars; - - public override bool Match(InlineProcessor processor, ref StringSlice slice) - { - var span = slice.AsSpan().Slice(0, 1); - if (span.IndexOfAny(WhiteSpaceSearchValues) == -1) - return false; - - processor.Inline = IrregularWhiteSpace.Instance; - - var c = span[0]; - var charName = GetCharacterName(c); - - processor.EmitHint(processor.Inline, 1, $"Irregular whitespace character detected: U+{(int)c:X4} ({charName}). This may impair Markdown rendering."); - - slice.SkipChar(); - return true; - } - - // Helper to get a friendly name for the whitespace character - private static string GetCharacterName(char c) => c switch - { - '\u000B' => "Line Tabulation (VT)", - '\u000C' => "Form Feed (FF)", - '\u00A0' => "No-Break Space (NBSP)", - '\u0085' => "Next Line", - '\u1680' => "Ogham Space Mark", - '\u180E' => "Mongolian Vowel Separator (MVS)", - '\ufeff' => "Zero Width No-Break Space (BOM)", - '\u2000' => "En Quad", - '\u2001' => "Em Quad", - '\u2002' => "En Space (ENSP)", - '\u2003' => "Em Space (EMSP)", - '\u2004' => "Tree-Per-Em", - '\u2005' => "Four-Per-Em", - '\u2006' => "Six-Per-Em", - '\u2007' => "Figure Space", - '\u2008' => "Punctuation Space (PUNCSP)", - '\u2009' => "Thin Space", - '\u200A' => "Hair Space", - '\u200B' => "Zero Width Space (ZWSP)", - '\u2028' => "Line Separator", - '\u2029' => "Paragraph Separator", - '\u202F' => "Narrow No-Break Space", - '\u205F' => "Medium Mathematical Space", - '\u3000' => "Ideographic Space", - _ => "Unknown" - }; -} - -public class IrregularWhiteSpace : LeafInline -{ - public static readonly IrregularWhiteSpace Instance = new(); -}; - -public class WhiteSpaceNormalizerRenderer : HtmlObjectRenderer -{ - protected override void Write(HtmlRenderer renderer, IrregularWhiteSpace obj) => - renderer.Write(' '); -} diff --git a/src/Elastic.Markdown/Myst/MarkdownParser.cs b/src/Elastic.Markdown/Myst/MarkdownParser.cs index 8eb3dde01..1c530fba4 100644 --- a/src/Elastic.Markdown/Myst/MarkdownParser.cs +++ b/src/Elastic.Markdown/Myst/MarkdownParser.cs @@ -169,7 +169,7 @@ public static MarkdownPipeline Pipeline .UseEnhancedCodeBlocks() .UseHtmxLinkInlineRenderer() .DisableHtml() - .UseWhiteSpaceNormalizer() + .UseSpaceNormalizer() .UseHardBreaks(); _ = builder.BlockParsers.TryRemove(); PipelineCached = builder.Build(); diff --git a/src/authoring/Elastic.Documentation.Refactor/FormatService.cs b/src/authoring/Elastic.Documentation.Refactor/FormatService.cs new file mode 100644 index 000000000..bd7dbf4b1 --- /dev/null +++ b/src/authoring/Elastic.Documentation.Refactor/FormatService.cs @@ -0,0 +1,141 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// 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.Documentation.Configuration; +using Elastic.Documentation.Diagnostics; +using Elastic.Documentation.Links.CrossLinks; +using Elastic.Documentation.Refactor.Formatters; +using Elastic.Documentation.Services; +using Elastic.Markdown.IO; +using Microsoft.Extensions.Logging; + +namespace Elastic.Documentation.Refactor; + +public class FormatService( + ILoggerFactory logFactory, + IConfigurationContext configurationContext +) : IService +{ + private readonly ILogger _logger = logFactory.CreateLogger(); + + // List of formatters to apply - easily extensible for future formatting operations + private static readonly IFormatter[] Formatters = + [ + new IrregularSpaceFormatter() + // Future formatters can be added here: + // new TrailingWhitespaceFormatter(), + // new LineEndingFormatter(), + // etc. + ]; + + public async Task Format( + IDiagnosticsCollector collector, + string? path, + bool checkOnly, + IFileSystem fs, + Cancel ctx + ) + { + // Create BuildContext to load the documentation set + var context = new BuildContext(collector, fs, fs, configurationContext, ExportOptions.MetadataOnly, path, null); + var set = new DocumentationSet(context, logFactory, NoopCrossLinkResolver.Instance); + + var mode = checkOnly ? "Checking" : "Formatting"; + _logger.LogInformation("{Mode} documentation in: {Path}", mode, set.SourceDirectory.FullName); + + var totalFilesProcessed = 0; + var totalFilesModified = 0; + var formatterStats = new Dictionary(); + + // Initialize stats for each formatter + foreach (var formatter in Formatters) + formatterStats[formatter.Name] = 0; + + // Only process markdown files that are part of the documentation set + foreach (var docFile in set.Files.OfType()) + { + if (ctx.IsCancellationRequested) + break; + + totalFilesProcessed++; + var (modified, changes) = await ProcessFile(docFile.SourceFile, checkOnly, fs, formatterStats); + + if (modified) + totalFilesModified++; + } + + _logger.LogInformation(""); + + if (checkOnly) + { + if (totalFilesModified > 0) + { + _logger.LogInformation("Formatting needed:"); + _logger.LogInformation(" Files needing formatting: {Modified}", totalFilesModified); + + // Log stats for each formatter that would make changes + foreach (var (formatterName, changeCount) in formatterStats.Where(kvp => kvp.Value > 0)) + _logger.LogInformation(" {Formatter} fixes needed: {Count}", formatterName, changeCount); + + _logger.LogInformation(""); + + // Emit error to trigger exit code 1 + collector.EmitError(string.Empty, $"{totalFilesModified} file(s) need formatting. Run 'docs-builder format --write' to apply changes."); + + return false; + } + else + { + _logger.LogInformation("All files are properly formatted"); + return true; + } + } + else + { + _logger.LogInformation("Formatting complete:"); + _logger.LogInformation(" Files processed: {Processed}", totalFilesProcessed); + _logger.LogInformation(" Files modified: {Modified}", totalFilesModified); + + // Log stats for each formatter that made changes + foreach (var (formatterName, changeCount) in formatterStats.Where(kvp => kvp.Value > 0)) + _logger.LogInformation(" {Formatter} fixes: {Count}", formatterName, changeCount); + + return true; + } + } + + private static async Task<(bool modified, int totalChanges)> ProcessFile( + IFileInfo file, + bool checkOnly, + IFileSystem fs, + Dictionary stats + ) + { + var content = await fs.File.ReadAllTextAsync(file.FullName); + var originalContent = content; + var totalChanges = 0; + + // Apply each formatter in sequence + foreach (var formatter in Formatters) + { + var result = formatter.Format(content); + + if (result.Changes > 0) + { + content = result.Content; + totalChanges += result.Changes; + stats[formatter.Name] += result.Changes; + } + } + + var modified = content != originalContent; + + // Only write if content changed and in write mode + if (modified && !checkOnly) + await fs.File.WriteAllTextAsync(file.FullName, content); + + return (modified, totalChanges); + } +} diff --git a/src/authoring/Elastic.Documentation.Refactor/Formatters/IFormatter.cs b/src/authoring/Elastic.Documentation.Refactor/Formatters/IFormatter.cs new file mode 100644 index 000000000..b5304b964 --- /dev/null +++ b/src/authoring/Elastic.Documentation.Refactor/Formatters/IFormatter.cs @@ -0,0 +1,30 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// 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 + +namespace Elastic.Documentation.Refactor.Formatters; + +/// +/// Result of a formatting operation +/// +/// The formatted content +/// The number of changes made +public record FormatResult(string Content, int Changes); + +/// +/// Defines a formatter that can process and modify file content +/// +public interface IFormatter +{ + /// + /// Gets the name of this formatter for logging purposes + /// + string Name { get; } + + /// + /// Formats the content and returns the result + /// + /// The content to format + /// The format result containing the formatted content and number of changes + FormatResult Format(string content); +} diff --git a/src/authoring/Elastic.Documentation.Refactor/Formatters/IrregularSpaceFormatter.cs b/src/authoring/Elastic.Documentation.Refactor/Formatters/IrregularSpaceFormatter.cs new file mode 100644 index 000000000..6d4212e55 --- /dev/null +++ b/src/authoring/Elastic.Documentation.Refactor/Formatters/IrregularSpaceFormatter.cs @@ -0,0 +1,73 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// 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.Buffers; +using System.Text; + +namespace Elastic.Documentation.Refactor.Formatters; + +/// +/// Formatter that replaces irregular space characters with regular spaces +/// +public class IrregularSpaceFormatter : IFormatter +{ + public string Name => "irregular space"; + + // Collection of irregular space characters that may impair Markdown rendering + private static readonly char[] IrregularSpaceChars = + [ + '\u000B', // Line Tabulation (\v) - + '\u000C', // Form Feed (\f) - + '\u00A0', // No-Break Space - + '\u0085', // Next Line + '\u1680', // Ogham Space Mark + '\u180E', // Mongolian Vowel Separator - + '\ufeff', // Zero Width No-Break Space - + '\u2000', // En Quad + '\u2001', // Em Quad + '\u2002', // En Space - + '\u2003', // Em Space - + '\u2004', // Tree-Per-Em + '\u2005', // Four-Per-Em + '\u2006', // Six-Per-Em + '\u2007', // Figure Space + '\u2008', // Punctuation Space - + '\u2009', // Thin Space + '\u200A', // Hair Space + '\u200B', // Zero Width Space - + '\u2028', // Line Separator + '\u2029', // Paragraph Separator + '\u202F', // Narrow No-Break Space + '\u205F', // Medium Mathematical Space + '\u3000' // Ideographic Space + ]; + + private static readonly SearchValues IrregularSpaceSearchValues = SearchValues.Create(IrregularSpaceChars); + + public FormatResult Format(string content) + { + // Quick check - if no irregular space, return original + if (content.AsSpan().IndexOfAny(IrregularSpaceSearchValues) == -1) + return new FormatResult(content, 0); + + // Replace irregular space with regular spaces + var sb = new StringBuilder(content.Length); + var replacements = 0; + + foreach (var c in content) + { + if (IrregularSpaceSearchValues.Contains(c)) + { + _ = sb.Append(' '); + replacements++; + } + else + { + _ = sb.Append(c); + } + } + + return new FormatResult(sb.ToString(), replacements); + } +} diff --git a/src/tooling/docs-builder/Commands/FormatCommand.cs b/src/tooling/docs-builder/Commands/FormatCommand.cs new file mode 100644 index 000000000..7672c7458 --- /dev/null +++ b/src/tooling/docs-builder/Commands/FormatCommand.cs @@ -0,0 +1,53 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// 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 ConsoleAppFramework; +using Elastic.Documentation.Configuration; +using Elastic.Documentation.Diagnostics; +using Elastic.Documentation.Refactor; +using Elastic.Documentation.Services; +using Microsoft.Extensions.Logging; + +namespace Documentation.Builder.Commands; + +internal sealed class FormatCommand( + ILoggerFactory logFactory, + IDiagnosticsCollector collector, + IConfigurationContext configurationContext +) +{ + /// + /// Format documentation files by fixing common issues like irregular space + /// + /// -p, Path to the documentation folder, defaults to pwd + /// Check if files need formatting without modifying them (exits with code 1 if formatting needed) + /// Write formatting changes to files + /// + [Command("")] + public async Task Format( + string? path = null, + bool check = false, + bool write = false, + Cancel ctx = default + ) + { + // Validate that exactly one of --check or --write is specified + if (check == write) + { + collector.EmitError(string.Empty, "Must specify exactly one of --check or --write"); + return 1; + } + + await using var serviceInvoker = new ServiceInvoker(collector); + + var service = new FormatService(logFactory, configurationContext); + var fs = new FileSystem(); + + serviceInvoker.AddCommand(service, (path, check, fs), + async static (s, collector, state, ctx) => await s.Format(collector, state.path, state.check, state.fs, ctx) + ); + return await serviceInvoker.InvokeAsync(ctx); + } +} diff --git a/src/tooling/docs-builder/Program.cs b/src/tooling/docs-builder/Program.cs index 31f6225f4..5e91ca90d 100644 --- a/src/tooling/docs-builder/Program.cs +++ b/src/tooling/docs-builder/Program.cs @@ -38,6 +38,7 @@ app.Add("mv"); app.Add("serve"); app.Add("index"); +app.Add("format"); //assembler commands diff --git a/tests/authoring/Linters/WhiteSpaceNormalizers.fs b/tests/authoring/Linters/SpaceNormalizers.fs similarity index 68% rename from tests/authoring/Linters/WhiteSpaceNormalizers.fs rename to tests/authoring/Linters/SpaceNormalizers.fs index 8d9391148..89dff9870 100644 --- a/tests/authoring/Linters/WhiteSpaceNormalizers.fs +++ b/tests/authoring/Linters/SpaceNormalizers.fs @@ -2,13 +2,13 @@ // 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 -module ``AuthoringTests``.``linters``.``white space normalizers`` +module ``AuthoringTests``.``linters``.``space normalizers`` open Xunit open authoring -type ``white space detection`` () = +type ``space detection`` () = static let markdown = Setup.Markdown $""" not a{'\u000B'}space @@ -20,4 +20,4 @@ not a{'\u000B'}space [] let ``emits a hint when a bad space is used`` () = - markdown |> hasHint "Irregular whitespace character detected: U+000B (Line Tabulation (VT)). This may impair Markdown rendering." + markdown |> hasHint "Irregular space detected. Run 'docs-builder format --write' to automatically fix all instances." diff --git a/tests/authoring/authoring.fsproj b/tests/authoring/authoring.fsproj index dedc4f06f..4bf239acb 100644 --- a/tests/authoring/authoring.fsproj +++ b/tests/authoring/authoring.fsproj @@ -53,7 +53,7 @@ - +