Skip to content

A letter after a digit starts a new word in Title/Pascal/Camel but not in Snake/Kebab: "1st place" → "1St Place", "abc123def" → "abc_123def" #78

Description

@matt-edmondson

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions