Skip to content

Keep an all-caps word containing ß in one piece, so ToMacroCase is idempotent [patch] - #94

Merged
matt-edmondson merged 1 commit into
mainfrom
fix/uncased-lowercase-letters
Sep 28, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
fix/uncased-lowercase-letters

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #87

What was wrong

ToUpperInvariant leaves ß, fi and ʼn unchanged, so ToMacroCase output such as STRAßE still contains a lowercase (Ll) letter. When that output was converted again:

  • IsWordBoundary read the ß as lowercase. The acronym-tail rule broke before the A, and the lower→upper rule broke before the E, which gave STR_Aß_E.
  • IsAllCaps("STRAßE") returned false, so ToTitleCase did not normalize the word.

Change

A lowercase letter that uppercasing leaves unchanged (IsLowercaseWithNoUppercase) is now handled as follows:

  • Acronym-tail rule: it is skipped when looking ahead for the lowercase letter, so STRAßE does not break before the A.
  • Lower→upper rule: it takes the case of the nearest letter before it in the same word (EndsWordThatIsNotUppercase). STRAßE does not break before the E, while großFoo still breaks before the F. When no cased letter comes before it, as in fiLE, it does not end a word.
  • IsAllCaps: it is ignored. The public behaviour change: "STRAßE".IsAllCaps() is now true. A string whose only letters are such letters, such as "ß", is now vacuously all caps, the same way a string with no letters already was.

This is a different problem from #84, which is about the upper→lower round trip changing lowercase letters. The triage note's possible conflicts, #85 and #86, have both merged, and this branch is on current main.

Tests

  • ToMacroCaseShouldBeIdempotentForLowercaseLettersWithNoUppercaseForm: x.ToMacroCase().ToMacroCase() == x.ToMacroCase() for straße, maßnahmeLimit, MAX_GRÖßE and file.
  • AnAllCapsWordContainingSharpSShouldStayOneWord: every example from the issue (STRAßE → straße, Straße, Straße; MAßNAHME → maßnahme; MAX_GRÖßE → maxGröße; and so on).
  • ALowercaseWordEndingInSharpSShouldStillSplitBeforeACapital: großFoo → groß_foo. This guard passes before and after the change.
  • IsAllCapsShouldIgnoreLowercaseLettersWithNoUppercaseForm

With CaseConverter.cs reverted, 5 of the new tests fail. With the change, all 72 tests pass on net10.0, and the solution builds for every target with 0 warnings.

🤖 Generated with Claude Code

https://claude.ai/code/session_01N3tMVtLPuNwsQF7rUEBTMQ


Generated by Claude Code

…empotent [patch]

ToUpperInvariant leaves ß, fi and ʼn unchanged, so ToMacroCase output such as
"STRAßE" still holds a lowercase letter. Converting it again split it into
"STR_Aß_E", and IsAllCaps did not count it as all caps. A lowercase letter
with no uppercase form now takes the case of the nearest cased letter before
it for word boundaries, is skipped when looking ahead for the lowercase tail
of an acronym, and is ignored by IsAllCaps.

Fixes #87

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3tMVtLPuNwsQF7rUEBTMQ
@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.

An all-caps word containing ß is split into bogus words, so ToMacroCase isn't idempotent: "straße" → "STRAßE" → "STR_Aß_E"

2 participants