From 60a71f41409ab76fa031402962aa1b226313d583 Mon Sep 17 00:00:00 2001 From: "fabien.menager" Date: Thu, 3 Aug 2023 21:16:25 +0200 Subject: [PATCH] Escape invalid identifiers for file constants --- src/ThisAssembly.Constants/Model.cs | 34 ++++++++++++++++--- .../Content/Docs/12. Readme (copy).txt | 1 + src/ThisAssembly.Tests/Tests.cs | 4 +++ 3 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 src/ThisAssembly.Tests/Content/Docs/12. Readme (copy).txt diff --git a/src/ThisAssembly.Constants/Model.cs b/src/ThisAssembly.Constants/Model.cs index 25ec0b69..f8664985 100644 --- a/src/ThisAssembly.Constants/Model.cs +++ b/src/ThisAssembly.Constants/Model.cs @@ -3,6 +3,8 @@ using System.Diagnostics; using System.Linq; using System.Reflection; +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis.CSharp; [DebuggerDisplay("Values = {RootArea.Values.Count}")] record Model(Area RootArea) @@ -16,6 +18,27 @@ record Area(string Name, string Prefix) { public List NestedAreas { get; init; } = new(); public List Values { get; init; } = new(); + + private static string EscapeIdentifier(string identifier) + { + if (string.IsNullOrWhiteSpace(identifier)) + { + return "_"; + } + + var replaced = identifier + .Select(c => SyntaxFacts.IsIdentifierPartCharacter(c) ? c : '_') + .ToArray(); + + var result = Regex.Replace(new string(replaced), "(_)+", "_"); + + if (!SyntaxFacts.IsIdentifierStartCharacter(result[0])) + { + result = "_" + result; + } + + return result; + } public static Area Load(List constants, string rootArea = "Constants") { @@ -24,15 +47,18 @@ public static Area Load(List constants, string rootArea = "Constants") foreach (var constant in constants) { // Splits: ([area].)*[name] - var parts = constant.Name.Split(new[] { "." }, StringSplitOptions.RemoveEmptyEntries); + var parts = constant.Name.Split(new[] { "." }, StringSplitOptions.RemoveEmptyEntries) + .Select(EscapeIdentifier) + .ToArray(); + if (parts.Length <= 1) { - root.Values.Add(new Constant(constant.Name, constant.Value, constant.Comment)); + root.Values.Add(constant with { Name = EscapeIdentifier(constant.Name) }); } else { var area = GetArea(root, parts.Take(parts.Length - 1)); - var value = new Constant(parts.Skip(parts.Length - 1).First(), constant.Value, constant.Comment); + var value = constant with { Name = parts.Skip(parts.Length - 1).First() }; area.Values.Add(value); } @@ -74,4 +100,4 @@ static Area GetArea(Area area, IEnumerable areaPath) } [DebuggerDisplay("{Name} = {Value}")] -record Constant(string Name, string? Value, string? Comment); \ No newline at end of file +record Constant(string Name, string? Value, string? Comment); diff --git a/src/ThisAssembly.Tests/Content/Docs/12. Readme (copy).txt b/src/ThisAssembly.Tests/Content/Docs/12. Readme (copy).txt new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/src/ThisAssembly.Tests/Content/Docs/12. Readme (copy).txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ThisAssembly.Tests/Tests.cs b/src/ThisAssembly.Tests/Tests.cs index 7a11d414..d9944274 100644 --- a/src/ThisAssembly.Tests/Tests.cs +++ b/src/ThisAssembly.Tests/Tests.cs @@ -35,6 +35,10 @@ public void CanUseConstants() public void CanUseFileConstants() => Assert.Equal(ThisAssembly.Constants.Content.Docs.License, Path.Combine("Content", "Docs", "License.md")); + [Fact] + public void CanUseFileConstantInvalidIdentifier() + => Assert.Equal(ThisAssembly.Constants.Content.Docs._12._Readme_copy_, Path.Combine("Content", "Docs", "12. Readme (copy).txt")); + [Fact] public void CanUseFileConstantLinkedFile() => Assert.Equal(ThisAssembly.Constants.Included.Readme, Path.Combine("Included", "Readme.txt"));