diff --git a/src/ThisAssembly.Constants/Model.cs b/src/ThisAssembly.Constants/Model.cs
index 25ec0b69..b48f7631 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)
@@ -17,6 +19,27 @@ record Area(string Name, string Prefix)
public List NestedAreas { get; init; } = new();
public List Values { get; init; } = new();
+ 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")
{
var root = new Area(rootArea, "");
@@ -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"));