From b725538d648f4247e1ead3ca7ffd74063c090f9c Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Tue, 15 Jul 2025 12:03:44 +0200 Subject: [PATCH 1/6] Add support for variable operator syntax --- docs/syntax/substitutions.md | 94 ++++++++++++- docs/syntax/version-variables.md | 21 ++- .../Builder/ConfigurationFile.cs | 16 --- .../Substitution/SubstitutionParser.cs | 131 +++++++++++++++++- .../authoring/Inline/SubstitutionMutations.fs | 52 +++++++ tests/authoring/authoring.fsproj | 1 + 6 files changed, 286 insertions(+), 29 deletions(-) create mode 100644 tests/authoring/Inline/SubstitutionMutations.fs diff --git a/docs/syntax/substitutions.md b/docs/syntax/substitutions.md index d7bd928d1..45adc889f 100644 --- a/docs/syntax/substitutions.md +++ b/docs/syntax/substitutions.md @@ -3,6 +3,7 @@ sub: frontmatter_key: "Front Matter Value" a-key-with-dashes: "A key with dashes" version: 7.17.0 + hello-world: "Hello world!" --- # Substitutions @@ -26,7 +27,7 @@ Doing so will result in a build error. To use the variables in your files, surround them in curly brackets (`{{variable}}`). -## Example +### Example Here are some variable substitutions: @@ -36,6 +37,97 @@ Here are some variable substitutions: | {{a-key-with-dashes}} | Front Matter | | {{a-global-variable}} | `docset.yml` | +## Mutations + +Substitutions can be mutated using a chain of operators seperated by a pipe (`|`). + +````markdown +`{{hello-world | trim | lc | tc}}` +```` + +Will trim, lowercase and finally titlecase the contents of the 'hello-world' variable. + +### Operators + + +| Operator | Purpose | +|----------|----------------------------------------------------| +| `lc` | LowerCase, | +| `uc` | UpperCase, | +| `tc` | TitleCase, capitalizes all words, | +| `c` | Capitalize the first letter, | +| `kc` | Convert to KebabCase, | +| `sc` | Convert to SnakeCase, | +| `cc` | Convert to CamelCase, | +| `pc` | Convert to PascalCase, | +| `trim` | Trim common non word characters from start and end | + +For variables declaring a semantic version or `Major.Minor` the following operations are also exposed + +| Operator | Purpose | +|----------|------------------------------------------| +| `M` | Display only the major component | +| `M.x` | Display major component followed by '.x' | +| `M.M` | Display only the major and the minor | +| `M+1` | The next major version | +| `M.M+1` | The next minor version | + +### Example + +Given the following frontmatter: + +```yaml +--- +sub: + hello-world: "Hello world!" +--- +``` + +::::{tab-set} + +:::{tab-item} Output + +* Lowercase: {{hello-world | lc}} +* Uppercase: {{hello-world | uc}} +* TitleCase: {{hello-world | tc}} +* kebab-case: {{hello-world | kc}} +* camelCase: {{hello-world | tc | cc}} +* PascalCase: {{hello-world | pc}} +* SnakeCase: {{hello-world | sc}} +* CapitalCase (chained): {{hello-world | lc | c}} +* Trim: {{hello-world | trim}} +* M.x: {{version.stack | M.x }} +* M.M: {{version.stack | M.M }} +* M: {{version.stack | M }} +* M+1: {{version.stack | M+1 }} +* M+1 | M.M: {{version.stack | M+1 | M.M }} +* M.M+1: {{version.stack | M.M+1 }} + +::: + +:::{tab-item} Markdown + +````markdown +* Lowercase: {{hello-world | lc}} +* Uppercase: {{hello-world | uc}} +* TitleCase: {{hello-world | tc}} +* kebab-case: {{hello-world | kc}} +* camelCase: {{hello-world | tc | cc}} +* PascalCase: {{hello-world | pc}} +* SnakeCase: {{hello-world | sc}} +* CapitalCase (chained): {{hello-world | lc | c}} +* Trim: {{hello-world | trim}} +* M.x: {{version.stack | M.x }} +* M.M: {{version.stack | M.M }} +* M: {{version.stack | M }} +* M+1: {{version.stack | M+1 }} +* M+1 | M.M: {{version.stack | M+1 | M.M }} +* M.M+1: {{version.stack | M.M+1 }} +```` +::: + +:::: + ## Code blocks Substitutions are supported in code blocks but are disabled by default. Enable substitutions by adding `subs=true` to the code block. diff --git a/docs/syntax/version-variables.md b/docs/syntax/version-variables.md index 87cc0cf07..da9a63401 100644 --- a/docs/syntax/version-variables.md +++ b/docs/syntax/version-variables.md @@ -10,14 +10,21 @@ Besides the current version, the following suffixes are available: | Version substitution | result | purpose | |--------------------------------------|-----------------------------------|-----------------------------------------| -| `{{versions.stack}}` | {{version.stack}} | Current version | -| `{{versions.stack.major_minor}}` | {{version.stack.major_minor}} | Current `MAJOR.MINOR` | -| `{{versions.stack.major_x}}` | {{version.stack.major_x}} | Current `MAJOR.X` | -| `{{versions.stack.major_component}}` | {{version.stack.major_component}} | Current major component | -| `{{versions.stack.next_major}}` | {{version.stack.next_major}} | The next major version | -| `{{versions.stack.next_minor}}` | {{version.stack.next_minor}} | The next minor version | -| `{{versions.stack.base}}` | {{version.stack.base}} | The first version on the new doc system | +| `{{version.stack}}` | {{version.stack}} | Current version | +| `{{version.stack.base}}` | {{version.stack.base}} | The first version on the new doc system | +## Formatting + +Using specialized [mutation operators](substitutions.md#mutations) versions +can be printed in any kind of ways. + + +| Version substitution | result | +|------------------------|-----------| +| `{{version.stack| M.M}}` | {{version.stack|M.M}} | +| `{{version.stack.base | M }}` | {{version.stack.base | M }} | +| `{{version.stack | M+1 | M }}` | {{version.stack | M+1 | M }} | +| `{{version.stack.base | M.M+1 }}` | {{version.stack.base | M.M+1 }} | ## Available versioning schemes. diff --git a/src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs b/src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs index 24cbaa5b8..da4cb39de 100644 --- a/src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs +++ b/src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs @@ -163,27 +163,11 @@ public ConfigurationFile(IDocumentationContext context, VersionsConfiguration ve foreach (var (id, system) in versionsConfig.VersioningSystems) { var name = id.ToStringFast(true); - var current = system.Current; var key = $"version.{name}"; _substitutions[key] = system.Current; key = $"version.{name}.base"; _substitutions[key] = system.Base; - - key = $"version.{name}.major_minor"; - _substitutions[key] = $"{current.Major:N0}.{current.Minor:N0}"; - - key = $"version.{name}.major_x"; - _substitutions[key] = $"{current.Major:N0}.x"; - - key = $"version.{name}.major_component"; - _substitutions[key] = $"{current.Major:N0}"; - - key = $"version.{name}.next_minor"; - _substitutions[key] = new SemVersion(current.Major, current.Minor + 1, current.Patch, current.Prerelease, current.Metadata); - - key = $"version.{name}.next_major"; - _substitutions[key] = new SemVersion(current.Major + 1, current.Minor, current.Patch, current.Prerelease, current.Metadata); } var toc = new TableOfContentsConfiguration(this, sourceFile, ScopeDirectory, _context, 0, ""); diff --git a/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs b/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs index 34c20295a..0b7e91deb 100644 --- a/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs +++ b/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs @@ -3,7 +3,12 @@ // See the LICENSE file in the project root for more information using System.Buffers; +using System.ComponentModel.DataAnnotations; using System.Diagnostics; +using System.Globalization; +using System.Text; +using System.Text.Json; +using Elastic.Documentation; using Elastic.Markdown.Diagnostics; using Markdig.Helpers; using Markdig.Parsers; @@ -11,27 +16,117 @@ using Markdig.Renderers.Html; using Markdig.Syntax; using Markdig.Syntax.Inlines; +using NetEscapades.EnumGenerators; namespace Elastic.Markdown.Myst.InlineParsers.Substitution; [DebuggerDisplay("{GetType().Name} Line: {Line}, Found: {Found}, Replacement: {Replacement}")] -public class SubstitutionLeaf(string content, bool found, string replacement) : CodeInline(content) +public class SubstitutionLeaf(string content, bool found, string replacement) + : CodeInline(content) { public bool Found { get; } = found; public string Replacement { get; } = replacement; + public IReadOnlyCollection? Mutations { get; set; } +} + +[EnumExtensions] +public enum SubstitutionMutation +{ + [Display(Name = "M")] MajorComponent, + [Display(Name = "M.x")] MajorX, + [Display(Name = "M.M")] MajorMinor, + [Display(Name = "M+1")] IncreaseMajor, + [Display(Name = "M.M+1")] IncreaseMinor, + [Display(Name = "lc")] LowerCase, + [Display(Name = "uc")] UpperCase, + [Display(Name = "tc")] TitleCase, + [Display(Name = "c")] Capitalize, + [Display(Name = "kc")] KebabCase, + [Display(Name = "sc")] SnakeCase, + [Display(Name = "cc")] CamelCase, + [Display(Name = "pc")] PascalCase, + [Display(Name = "trim")] Trim } public class SubstitutionRenderer : HtmlObjectRenderer { - protected override void Write(HtmlRenderer renderer, SubstitutionLeaf obj) => - renderer.Write(obj.Found ? obj.Replacement : obj.Content); + protected override void Write(HtmlRenderer renderer, SubstitutionLeaf leaf) + { + if (!leaf.Found) + { + _ = renderer.Write(leaf.Content); + return; + } + + var replacement = leaf.Replacement; + if (leaf.Mutations is null or { Count: 0 }) + { + _ = renderer.Write(replacement); + return; + } + + foreach (var mutation in leaf.Mutations) + { + var (success, update) = mutation switch + { + SubstitutionMutation.MajorComponent => TryGetVersion(replacement, v => $"{v.Major}"), + SubstitutionMutation.MajorX => TryGetVersion(replacement, v => $"{v.Major}.x"), + SubstitutionMutation.MajorMinor => TryGetVersion(replacement, v => $"{v.Major}.{v.Minor}"), + SubstitutionMutation.IncreaseMajor => TryGetVersion(replacement, v => $"{v.Major + 1}.0.0"), + SubstitutionMutation.IncreaseMinor => TryGetVersion(replacement, v => $"{v.Major}.{v.Minor + 1}.0"), + SubstitutionMutation.LowerCase => (true, replacement.ToLowerInvariant()), + SubstitutionMutation.UpperCase => (true, replacement.ToUpperInvariant()), + SubstitutionMutation.Capitalize => (true, Capitalize(replacement)), + SubstitutionMutation.KebabCase => (true, ToKebabCase(replacement)), + SubstitutionMutation.CamelCase => (true, ToCamelCase(replacement)), + SubstitutionMutation.PascalCase => (true, ToPascalCase(replacement)), + SubstitutionMutation.SnakeCase => (true, ToSnakeCase(replacement)), + SubstitutionMutation.TitleCase => (true, TitleCase(replacement)), + SubstitutionMutation.Trim => (true, Trim(replacement)), + _ => throw new Exception($"encountered an unknown mutation '{mutation.ToStringFast(true)}'") + }; + if (!success) + { + _ = renderer.Write(leaf.Content); + return; + } + replacement = update; + } + _ = renderer.Write(replacement); + } + + private static string ToCamelCase(string str) => JsonNamingPolicy.CamelCase.ConvertName(str.Replace(" ", string.Empty)); + private static string ToSnakeCase(string str) => JsonNamingPolicy.SnakeCaseLower.ConvertName(str).Replace(" ", string.Empty); + private static string ToKebabCase(string str) => JsonNamingPolicy.KebabCaseLower.ConvertName(str).Replace(" ", string.Empty); + private static string ToPascalCase(string str) => TitleCase(str).Replace(" ", string.Empty); + + private static string TitleCase(string str) => CultureInfo.InvariantCulture.TextInfo.ToTitleCase(str); + + private static string Trim(string str) => + str.AsSpan().Trim(['!', ' ', '\t', '\r', '\n', '.', ',', ')', '(', ':', ';', '<', '>', '[', ']']).ToString(); + + private static string Capitalize(string input) => + input switch + { + null => string.Empty, + "" => string.Empty, + _ => string.Concat(input[0].ToString().ToUpper(), input.AsSpan(1)) + }; + + private (bool, string) TryGetVersion(string version, Func mutate) + { + if (!SemVersion.TryParse(version, out var v) && !SemVersion.TryParse(version + ".0", out v)) + return (false, string.Empty); + + return (true, mutate(v)); + } } public class SubstitutionParser : InlineParser { public SubstitutionParser() => OpeningCharacters = ['{']; - private readonly SearchValues _values = SearchValues.Create(['\r', '\n', ' ', '\t', '}']); + private readonly SearchValues _values = SearchValues.Create(['\r', '\n', '\t', '}']); public override bool Match(InlineProcessor processor, ref StringSlice slice) { @@ -84,6 +179,10 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) var key = content.ToString().Trim(['{', '}']).ToLowerInvariant(); var found = false; var replacement = string.Empty; + var components = key.Split('|'); + if (components.Length > 1) + key = components[0].Trim(); + if (context.Substitutions.TryGetValue(key, out var value)) { found = true; @@ -100,7 +199,6 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) var start = processor.GetSourcePosition(startPosition, out var line, out var column); var end = processor.GetSourcePosition(slice.Start); var sourceSpan = new SourceSpan(start, end); - var substitutionLeaf = new SubstitutionLeaf(content.ToString(), found, replacement) { Delimiter = '{', @@ -109,8 +207,31 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) Column = column, DelimiterCount = openSticks }; + if (!found) processor.EmitError(line + 1, column + 3, substitutionLeaf.Span.Length - 3, $"Substitution key {{{key}}} is undefined"); + else + { + List? mutations = null; + if (components.Length >= 10) + processor.EmitError(line + 1, column + 3, substitutionLeaf.Span.Length - 3, $"Substitution key {{{key}}} defines too many mutations, none will be applied"); + else if (components.Length > 1) + { + foreach (var c in components[1..]) + { + if (SubstitutionMutationExtensions.TryParse(c.Trim(), out var mutation, true, true)) + { + mutations ??= []; + mutations.Add(mutation); + } + else + processor.EmitError(line + 1, column + 3, substitutionLeaf.Span.Length - 3, $"Mutation '{c}' on {{{key}}} is undefined"); + } + } + + substitutionLeaf.Mutations = mutations; + } + if (processor.TrackTrivia) { diff --git a/tests/authoring/Inline/SubstitutionMutations.fs b/tests/authoring/Inline/SubstitutionMutations.fs new file mode 100644 index 000000000..6929efeff --- /dev/null +++ b/tests/authoring/Inline/SubstitutionMutations.fs @@ -0,0 +1,52 @@ +// 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 + +module ``inline elements``.``substitution mutations`` + +open Xunit +open authoring + +type ``read sub from yaml frontmatter`` () = + let markdown = Setup.Document """--- +sub: + hello-world: "Hello world!" + versions.stack: 9.1.0 +--- +* Lowercase: {{hello-world | lc}} +* Uppercase: {{hello-world | uc}} +* TitleCase: {{hello-world | tc}} +* kebab-case: {{hello-world | kc}} +* camelCase: {{hello-world | tc | cc}} +* PascalCase: {{hello-world | pc}} +* SnakeCase: {{hello-world | sc}} +* CapitalCase (chained): {{hello-world | lc | c}} +* Trim: {{hello-world | trim}} +* M.x: {{versions.stack | M.x }} +* M.M: {{versions.stack | M.M }} +* M: {{versions.stack | M }} +* M+1: {{versions.stack | M+1 }} +* M+1 | M.M: {{versions.stack | M+1 | M.M }} +* M.M+1: {{versions.stack | M.M+1 }} +""" + + [] + let ``validate HTML: replace substitution`` () = + markdown |> convertsToHtml """
    +
  • Lowercase: hello world!
  • +
  • Uppercase: HELLO WORLD!
  • +
  • TitleCase: Hello World!
  • +
  • kebab-case: hello-world!
  • +
  • camelCase: helloWorld!
  • +
  • PascalCase: HelloWorld!
  • +
  • SnakeCase: hello_world!
  • +
  • CapitalCase (chained): Hello world!
  • +
  • Trim: Hello world
  • +
  • M.x: 9.x
  • +
  • M.M: 9.1
  • +
  • M: 9
  • +
  • M+1: 10.0.0
  • +
  • M+1 | M.M: 10.0
  • +
  • M.M+1: 9.2.0
  • +
+ """ diff --git a/tests/authoring/authoring.fsproj b/tests/authoring/authoring.fsproj index a58fafc22..a0b659cb4 100644 --- a/tests/authoring/authoring.fsproj +++ b/tests/authoring/authoring.fsproj @@ -40,6 +40,7 @@ + From bf4cb8ccdae4a993a8c750f59d0ee98a6020c237 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Tue, 15 Jul 2025 12:09:10 +0200 Subject: [PATCH 2/6] do not report version variables as unused --- src/Elastic.Markdown/DocumentationGenerator.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Elastic.Markdown/DocumentationGenerator.cs b/src/Elastic.Markdown/DocumentationGenerator.cs index 3a9bd9eed..b956771ea 100644 --- a/src/Elastic.Markdown/DocumentationGenerator.cs +++ b/src/Elastic.Markdown/DocumentationGenerator.cs @@ -180,7 +180,13 @@ private void HintUnusedSubstitutionKeys() { var definedKeys = new HashSet(Context.Configuration.Substitutions.Keys.ToArray()); var inUse = new HashSet(Context.Collector.InUseSubstitutionKeys.Keys); - var keysNotInUse = definedKeys.Except(inUse).ToArray(); + var keysNotInUse = definedKeys.Except(inUse) + // versions keys are injected + .Where(key => !key.StartsWith("version.")) + // reserving context namespace + .Where(key => !key.StartsWith("context.")) + .ToArray(); + // If we have less than 20 unused keys, emit them separately, // Otherwise emit one hint with all of them for brevity if (keysNotInUse.Length >= 20) From 0b2ebeb421b69d833405747f69d803b1e96806ad Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Tue, 15 Jul 2025 12:15:58 +0200 Subject: [PATCH 3/6] Allow a space after {{ --- .../Myst/InlineParsers/Substitution/SubstitutionParser.cs | 2 +- tests/authoring/Inline/Substitutions.fs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs b/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs index 0b7e91deb..ad2623f41 100644 --- a/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs +++ b/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs @@ -176,7 +176,7 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) startPosition -= openSticks; startPosition = Math.Max(startPosition, 0); - var key = content.ToString().Trim(['{', '}']).ToLowerInvariant(); + var key = content.ToString().Trim(['{', '}']).Trim().ToLowerInvariant(); var found = false; var replacement = string.Empty; var components = key.Split('|'); diff --git a/tests/authoring/Inline/Substitutions.fs b/tests/authoring/Inline/Substitutions.fs index ed75ba22b..1a7679952 100644 --- a/tests/authoring/Inline/Substitutions.fs +++ b/tests/authoring/Inline/Substitutions.fs @@ -46,6 +46,7 @@ The following should be subbed: {{hello-world}} not a comment not a {{valid-key}} not a {substitution} +The following should be subbed too: {{ hello-world }} """ [] @@ -58,5 +59,7 @@ not a {substitution}

The following should be subbed: Hello World! not a comment not a {{valid-key}} - not a {substitution}

+ not a {substitution} + The following should be subbed too: Hello World! +

""" From ad4d342ae34eabefe0e651e185d6faffdfb13a81 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Tue, 15 Jul 2025 12:21:17 +0200 Subject: [PATCH 4/6] carry trimming over to split --- .../Myst/InlineParsers/Substitution/SubstitutionParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs b/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs index ad2623f41..ae0f956e6 100644 --- a/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs +++ b/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs @@ -181,7 +181,7 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) var replacement = string.Empty; var components = key.Split('|'); if (components.Length > 1) - key = components[0].Trim(); + key = components[0].Trim(['{', '}']).Trim().ToLowerInvariant(); if (context.Substitutions.TryGetValue(key, out var value)) { From 6e1bee4f22f097de903afcae5f6d7d4cbe4b9283 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Tue, 15 Jul 2025 12:41:51 +0200 Subject: [PATCH 5/6] Temporarily make variables with spaces that are not found hints, we used to not do anything here --- .../ProcessorDiagnosticExtensions.cs | 28 +++++++------------ .../Substitution/SubstitutionParser.cs | 4 ++- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/Elastic.Markdown/Diagnostics/ProcessorDiagnosticExtensions.cs b/src/Elastic.Markdown/Diagnostics/ProcessorDiagnosticExtensions.cs index 24e43c434..abcefd00d 100644 --- a/src/Elastic.Markdown/Diagnostics/ProcessorDiagnosticExtensions.cs +++ b/src/Elastic.Markdown/Diagnostics/ProcessorDiagnosticExtensions.cs @@ -14,32 +14,24 @@ public static class ProcessorDiagnosticExtensions { private static string CreateExceptionMessage(string message, Exception? e) => message + (e != null ? Environment.NewLine + e : string.Empty); - public static void EmitError(this InlineProcessor processor, int line, int column, int length, string message) - { - var context = processor.GetContext(); - if (context.SkipValidation) - return; - var d = new Diagnostic - { - Severity = Severity.Error, - File = processor.GetContext().MarkdownSourcePath.FullName, - Column = column, - Line = line, - Message = message, - Length = length - }; - context.Build.Collector.Write(d); - } + public static void EmitError(this InlineProcessor processor, int line, int column, int length, string message) => + processor.Emit(Severity.Error, line, column, length, message); - public static void EmitWarning(this InlineProcessor processor, int line, int column, int length, string message) + public static void EmitWarning(this InlineProcessor processor, int line, int column, int length, string message) => + processor.Emit(Severity.Warning, line, column, length, message); + + public static void EmitHint(this InlineProcessor processor, int line, int column, int length, string message) => + processor.Emit(Severity.Hint, line, column, length, message); + + public static void Emit(this InlineProcessor processor, Severity severity, int line, int column, int length, string message) { var context = processor.GetContext(); if (context.SkipValidation) return; var d = new Diagnostic { - Severity = Severity.Warning, + Severity = severity, File = processor.GetContext().MarkdownSourcePath.FullName, Column = column, Line = line, diff --git a/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs b/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs index ae0f956e6..c7b9804d4 100644 --- a/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs +++ b/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs @@ -9,6 +9,7 @@ using System.Text; using System.Text.Json; using Elastic.Documentation; +using Elastic.Documentation.Diagnostics; using Elastic.Markdown.Diagnostics; using Markdig.Helpers; using Markdig.Parsers; @@ -209,7 +210,8 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) }; if (!found) - processor.EmitError(line + 1, column + 3, substitutionLeaf.Span.Length - 3, $"Substitution key {{{key}}} is undefined"); + // We temporarily diagnose variable spaces as hints. We used to not read this at all. + processor.Emit(key.Contains(' ') ? Severity.Error : Severity.Hint, line + 1, column + 3, substitutionLeaf.Span.Length - 3, $"Substitution key {{{key}}} is undefined"); else { List? mutations = null; From c7ae71a4b451ff34cd13251a1499492172b4e8f7 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Tue, 15 Jul 2025 12:45:20 +0200 Subject: [PATCH 6/6] flip ternary --- .../Myst/InlineParsers/Substitution/SubstitutionParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs b/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs index c7b9804d4..7b0f33e67 100644 --- a/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs +++ b/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs @@ -211,7 +211,7 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) if (!found) // We temporarily diagnose variable spaces as hints. We used to not read this at all. - processor.Emit(key.Contains(' ') ? Severity.Error : Severity.Hint, line + 1, column + 3, substitutionLeaf.Span.Length - 3, $"Substitution key {{{key}}} is undefined"); + processor.Emit(key.Contains(' ') ? Severity.Hint : Severity.Error, line + 1, column + 3, substitutionLeaf.Span.Length - 3, $"Substitution key {{{key}}} is undefined"); else { List? mutations = null;