What's wrong
The converters disagree about where a word starts after a digit:
IsWordBoundary (CaseConverter/CaseConverter.cs ~L103–122) breaks on letter→digit ("abc123" → "abc 123") but not on digit→letter, so "123def" stays one token.
ToTitleCase then calls TextInfo.ToTitleCase, which treats a letter that follows a digit as the start of a word and capitalises it.
As a result, Title, Pascal and Camel case put a word boundary after a digit, while Snake, Macro and Kebab case do not. Converting through one form into another changes the words.
Failure scenario (reproduced)
| Input |
ToTitleCase |
ToPascalCase |
ToSnakeCase |
ToPascalCase().ToSnakeCase() |
| "1st place" |
"1St Place" |
"1StPlace" |
"1st_place" |
"1_st_place" |
| "abc123def" |
"Abc 123Def" |
"Abc123Def" |
"abc_123def" |
"abc_123_def" |
| "md5hash" |
"Md 5Hash" |
"Md5Hash" |
"md_5hash" |
"md_5_hash" |
| "3d model" |
"3D Model" |
"3DModel" |
"3d_model" |
"3_d_model" |
"1St Place" is plainly wrong title casing. The round-trip mismatch also means x.ToSnakeCase() and x.ToPascalCase().ToSnakeCase() return different identifiers for the same input.
This is not covered by #75 (spaces before punctuation) or its open PR. That PR still only breaks on letter→digit and does not change how TextInfo capitalises after a digit.
Suggested fix
Choose one rule and apply it in every converter:
- Digit→letter is a boundary: add it to
IsWordBoundary, so ToSnakeCase("abc123def") returns "abc_123_def" and matches Pascal.
- Keep "1st" as one word: stop using
TextInfo.ToTitleCase for capitalisation, and uppercase only the first code point of each space-separated token.
Acceptance criteria
"1st place".ToTitleCase() is either "1st Place" or consistently "1 St Place", whichever rule is chosen.
- For the inputs above,
x.ToSnakeCase() == x.ToPascalCase().ToSnakeCase(), with tests covering it.
What's wrong
The converters disagree about where a word starts after a digit:
IsWordBoundary(CaseConverter/CaseConverter.cs~L103–122) breaks on letter→digit ("abc123" → "abc 123") but not on digit→letter, so "123def" stays one token.ToTitleCasethen callsTextInfo.ToTitleCase, which treats a letter that follows a digit as the start of a word and capitalises it.As a result, Title, Pascal and Camel case put a word boundary after a digit, while Snake, Macro and Kebab case do not. Converting through one form into another changes the words.
Failure scenario (reproduced)
"1St Place" is plainly wrong title casing. The round-trip mismatch also means
x.ToSnakeCase()andx.ToPascalCase().ToSnakeCase()return different identifiers for the same input.This is not covered by #75 (spaces before punctuation) or its open PR. That PR still only breaks on letter→digit and does not change how
TextInfocapitalises after a digit.Suggested fix
Choose one rule and apply it in every converter:
IsWordBoundary, soToSnakeCase("abc123def")returns "abc_123_def" and matches Pascal.TextInfo.ToTitleCasefor capitalisation, and uppercase only the first code point of each space-separated token.Acceptance criteria
"1st place".ToTitleCase()is either "1st Place" or consistently "1 St Place", whichever rule is chosen.x.ToSnakeCase() == x.ToPascalCase().ToSnakeCase(), with tests covering it.