Skip to content

Keep punctuation attached to its word in ToTitleCase - #77

Merged
matt-edmondson merged 1 commit into
mainfrom
fix/title-case-punctuation
Sep 26, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
fix/title-case-punctuation

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #75

What was wrong

ToTitleCase ran SplitOnCaseChange on the raw input. That function inserts a word boundary between a letter and any non-letter that follows it, so punctuation ended up as a word of its own. TextInfo.ToTitleCase then capitalized the letter after it:

Input Before After
hello, world Hello , World Hello, World
don't stop Don 'T Stop Don't Stop
foo_bar Foo _Bar Foo Bar

Change

  • SplitOnCaseChange / IsWordBoundary take a breakBeforeAnyNonLetter flag. The existing single-argument overload passes true, so ToPascalCase and ToMacroCase still get it. ToTitleCase passes false, which means that after a letter it breaks only before a digit (abc123 → Abc 123 still works).
  • In ToTitleCase, _ is mapped to a space first, as the issue suggests. - is left for TextInfo.ToTitleCase, which already treats it as a separator, so well-known becomes Well-Known.
  • A case change (fooBar) now starts a new word only when a letter or digit comes before it. Without this, (Hello split into ( Hello. ToPascalCase and ToMacroCase replace every non-alphanumeric with a space before splitting, so all this removes for them is a redundant double space that was already collapsed. Their existing tests pass unchanged.
  • The three tests from the issue are added, along with coverage for :, ? and ( and a guard that the case-change and digit splits still work in title case.

Verification

  • dotnet test: 39/39 pass.
  • With the source change reverted, the four punctuation tests fail and everything else passes.
  • dotnet build CaseConverter/CaseConverter.csproj succeeds for every target framework with no warnings.

This PR and #76 edit different functions in the same file, so either can merge first.

🤖 Generated with Claude Code

https://claude.ai/code/session_018AEYBNCRr6chFLiZDv1MVj


Generated by Claude Code

ToTitleCase split before every non-letter, so punctuation became a word of
its own and TextInfo.ToTitleCase capitalized the letter after it:
"hello, world" came out as "Hello , World" and "don't stop" as
"Don 'T Stop". In title case, break after a letter only before a digit,
and treat an underscore as a word separator. A case change now starts a
new word only after a letter or digit, so "(Hello" stays together.
ToPascalCase and ToMacroCase strip punctuation before splitting, so their
output is unchanged.

Fixes #75

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AEYBNCRr6chFLiZDv1MVj
@sonarqubecloud

Copy link
Copy Markdown

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ToTitleCase inserts a space before punctuation: "hello, world" → "Hello , World", "don't stop" → "Don 'T Stop"

2 participants