What's hand-rolled
NameStyles.Spell takes a space-separated run of words and spells it as Pascal, Camel, or Snake case, by upper/lower-casing the first character of each word with char.ToUpper/char.ToLower:
|
internal static class NameStyles |
|
{ |
|
/// <summary> |
|
/// Spells a name made of space-separated words. |
|
/// </summary> |
|
/// <param name="words">The name, as words separated by spaces.</param> |
|
/// <param name="style">How the target spells a member's name.</param> |
|
/// <returns>The name.</returns> |
|
/// <remarks> |
|
/// The words come in separated because the caller knows where they are and this cannot: a name |
|
/// already written as <c>setValue</c> or <c>set_value</c> would have to be taken apart first, |
|
/// and every rule for doing that is wrong about something. |
|
/// </remarks> |
|
internal static string Spell(string words, NamingStyle style) |
|
{ |
|
string[] parts = [.. words.Split(' ').Where(part => part.Length > 0)]; |
|
|
|
if (parts.Length == 0) |
|
{ |
|
return string.Empty; |
|
} |
|
|
|
return style switch |
|
{ |
|
NamingStyle.Snake => string.Join("_", parts.Select(part => part.ToLowerInvariant())), |
|
NamingStyle.Camel => Lower(parts[0]) + string.Concat(parts.Skip(1).Select(Upper)), |
|
_ => string.Concat(parts.Select(Upper)), |
|
}; |
|
} |
|
|
|
private static string Upper(string word) => |
|
char.ToUpper(word[0], CultureInfo.InvariantCulture) + word[1..]; |
|
|
|
private static string Lower(string word) => |
|
char.ToLower(word[0], CultureInfo.InvariantCulture) + word[1..]; |
|
} |
It has one call site, PropertyDeclaration.GetterName/SetterName, which builds an invented accessor name (e.g. "set " + Name) and spells it in the target's naming convention:
|
public string GetterName(NamingStyle style) => NameStyles.Spell(Name ?? "value", style); |
|
public string SetterName(NamingStyle style) => NameStyles.Spell($"set {Name ?? "value"}", style); |
What ktsu.CaseConverter provides
ktsu.CaseConverter ships the same three conversions (plus kebab/macro) as string extension methods, and — because its regex-based word splitter treats spaces as word boundaries the same way it treats case changes — they produce identical output when called on an already space-separated string:
"set value".ToPascalCase() → "SetValue", .ToCamelCase() → "setValue", .ToSnakeCase() → "set_value" — the same three outputs NameStyles.Spell produces for NamingStyle.Pascal/Camel/Snake given the same input.
Why it's worth it
Mostly a line-count win for the call sites — Spell is short and currently correct for the ASCII identifiers this AST generates. The one place CaseConverter gets more right: it title-cases through CultureInfo.InvariantCulture.TextInfo.ToTitleCase and matches word/case boundaries against \p{L} (any Unicode letter), where Spell's char.ToUpper(word[0], ...) only touches the first UTF-16 code unit of each word — a word beginning with a character outside the Basic Multilingual Plane (a supplementary-plane letter, e.g. some CJK-B or historic-script identifiers) would have only half its leading surrogate pair touched. Property names sourced from arbitrary input (rather than written directly in an AST-construction call) could hit this.
Compatibility
- Subject targets (Coder core project):
net10.0;net9.0
- ktsu.CaseConverter targets: multi-targets down to
netstandard2.0 (via ktsu.Sdk), so it covers Coder's frameworks
- Dependency direction:
ktsu.CaseConverter has zero ktsu.* package references (confirmed via its Directory.Packages.props), so no cycle with ktsu.Coder
Sketch
- internal static string Spell(string words, NamingStyle style)
- {
- string[] parts = [.. words.Split(' ').Where(part => part.Length > 0)];
- if (parts.Length == 0) return string.Empty;
- return style switch
- {
- NamingStyle.Snake => string.Join("_", parts.Select(part => part.ToLowerInvariant())),
- NamingStyle.Camel => Lower(parts[0]) + string.Concat(parts.Skip(1).Select(Upper)),
- _ => string.Concat(parts.Select(Upper)),
- };
- }
+ internal static string Spell(string words, NamingStyle style) => style switch
+ {
+ NamingStyle.Snake => words.ToSnakeCase(),
+ NamingStyle.Camel => words.ToCamelCase(),
+ _ => words.ToPascalCase(),
+ };
Caveats
- Adds a package reference (
ktsu.CaseConverter) for a currently-internal, single-call-site helper — purely an implementation swap, no public API change.
CaseConverter's splitter treats any non-alphanumeric character as a word boundary (not just spaces), so it's slightly more permissive than Spell's literal ' ' split. Not observable at the current call site, since both inputs are always plain space-joined words, but worth noting if Spell ever gets a caller that passes punctuation.
NamingStyle has no Kebab/Macro members, so only three of CaseConverter's six conversions would be used.
What's hand-rolled
NameStyles.Spelltakes a space-separated run of words and spells it as Pascal, Camel, or Snake case, by upper/lower-casing the first character of each word withchar.ToUpper/char.ToLower:Coder/Coder/Ast/NamingStyle.cs
Lines 33 to 68 in ed20ffd
It has one call site,
PropertyDeclaration.GetterName/SetterName, which builds an invented accessor name (e.g."set " + Name) and spells it in the target's naming convention:Coder/Coder/Ast/PropertyDeclaration.cs
Line 141 in ed20ffd
Coder/Coder/Ast/PropertyDeclaration.cs
Line 153 in ed20ffd
What ktsu.CaseConverter provides
ktsu.CaseConverterships the same three conversions (plus kebab/macro) asstringextension methods, and — because its regex-based word splitter treats spaces as word boundaries the same way it treats case changes — they produce identical output when called on an already space-separated string:public static string ToPascalCase(this string input)— https://github.com/ktsu-dev/CaseConverter/blob/f5cf1509ae734f66533f5111496905166a3d2c2f/CaseConverter/CaseConverter.cs#L145public static string ToCamelCase(this string input)— https://github.com/ktsu-dev/CaseConverter/blob/f5cf1509ae734f66533f5111496905166a3d2c2f/CaseConverter/CaseConverter.cs#L167public static string ToSnakeCase(this string input)— https://github.com/ktsu-dev/CaseConverter/blob/f5cf1509ae734f66533f5111496905166a3d2c2f/CaseConverter/CaseConverter.cs#L181"set value".ToPascalCase()→"SetValue",.ToCamelCase()→"setValue",.ToSnakeCase()→"set_value"— the same three outputsNameStyles.Spellproduces forNamingStyle.Pascal/Camel/Snakegiven the same input.Why it's worth it
Mostly a line-count win for the call sites —
Spellis short and currently correct for the ASCII identifiers this AST generates. The one placeCaseConvertergets more right: it title-cases throughCultureInfo.InvariantCulture.TextInfo.ToTitleCaseand matches word/case boundaries against\p{L}(any Unicode letter), whereSpell'schar.ToUpper(word[0], ...)only touches the first UTF-16 code unit of each word — a word beginning with a character outside the Basic Multilingual Plane (a supplementary-plane letter, e.g. some CJK-B or historic-script identifiers) would have only half its leading surrogate pair touched. Property names sourced from arbitrary input (rather than written directly in an AST-construction call) could hit this.Compatibility
net10.0;net9.0netstandard2.0(viaktsu.Sdk), so it covers Coder's frameworksktsu.CaseConverterhas zeroktsu.*package references (confirmed via itsDirectory.Packages.props), so no cycle withktsu.CoderSketch
Caveats
ktsu.CaseConverter) for a currently-internal, single-call-site helper — purely an implementation swap, no public API change.CaseConverter's splitter treats any non-alphanumeric character as a word boundary (not just spaces), so it's slightly more permissive thanSpell's literal' 'split. Not observable at the current call site, since both inputs are always plain space-joined words, but worth noting ifSpellever gets a caller that passes punctuation.NamingStylehas noKebab/Macromembers, so only three ofCaseConverter's six conversions would be used.