Skip to content

Normalize an all-caps word per word, not per string - #73

Merged
matt-edmondson merged 1 commit into
mainfrom
claude/exciting-albattani-007iac
Sep 26, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
claude/exciting-albattani-007iac

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #72

The choice

The issue offers three options and deliberately does not pick one, while stating the acceptance criteria neutrally ("under whichever of the options above is chosen"). This takes option 1 — normalize per word — for three reasons:

  • It is the only option that satisfies the first acceptance criterion across all the affected converters. Option 3 (fix Pascal/camel, leave ToTitleCase) leaves ToTitleCase itself neighbour-dependent: URL → Url but my URL handler → My URL Handler.
  • Option 2 (preserve per word) is consistent but strictly worse at the reported case: it gives MAX_SIZE → MAXSIZE, losing the word boundary the macro/snake path already gets right.
  • Option 1 brings the title-case-derived converters into agreement with the macro/snake path, which never had the dependence, and matches .NET naming guidance (HttpHeader, not HTTPHeader).

There is a fourth argument the issue does not mention, found while measuring: README.md already documents option 1's answer. Its acronym example claims "API_response_URL".ToPascalCase() gives "ApiResponseUrl". Measured at main (5c3fa37), it actually gives "APIResponseURL". The documented contract and the code disagreed, and this change resolves it in the documentation's favour.

The change

ToTitleCase tested IsAllCaps against the whole string, so one lowercase character anywhere stopped every all-caps word from being normalized. LowercaseAllCapsWords now makes that decision per space-separated word before handing the string to TextInfo.ToTitleCase. ToPascalCase and ToCamelCase inherit the fix through ToTitleCase; ToMacroCase/ToSnakeCase/ToKebabCase are untouched because they never had the defect.

Measured at main (5c3fa37) and on this branch, .NET SDK 10.0.401, Linux:

input ToTitleCase ToPascalCase ToCamelCase ToMacroCase
MAX_SIZE Max _ Size MaxSize maxSize MAX_SIZE
set MAX_SIZE Set MAX _ SIZE → Set Max _ Size SetMAXSIZE → SetMaxSize setMAXSIZE → setMaxSize SET_MAX_SIZE
get MAX_SIZE Get MAX _ SIZE → Get Max _ Size GetMAXSIZE → GetMaxSize getMAXSIZE → getMaxSize GET_MAX_SIZE
URL Url Url url URL
my URL handler My URL Handler → My Url Handler MyURLHandler → MyUrlHandler myURLHandler → myUrlHandler MY_URL_HANDLER
HTTP Http Http http HTTP
parse HTTP header Parse HTTP Header → Parse Http Header ParseHTTPHeader → ParseHttpHeader parseHTTPHeader → parseHttpHeader PARSE_HTTP_HEADER
API_response_URL — APIResponseURL → ApiResponseUrl aPIResponseURL → apiResponseUrl —

Rows where nothing changed are the single-word cases, which already normalized. Every column now reads the same for a word alone and beside a lowercase one.

The rule is stated in the XML docs on ToTitleCase, ToPascalCase and ToCamelCase, since it is not inferable from the method names.

This changes output for existing callers

An all-caps word beside a lowercase one is now normalized rather than preserved as an acronym. Two existing tests pinned the old behaviour and are updated, exactly as the issue anticipates for this option:

  • ToTitleCaseShouldHandleMultipleSpaces — " the quick brown FOX " → "The Quick Brown FOX" becomes "The Quick Brown Fox"
  • ToTitleCaseShouldConvertToTitleCase — "the quick Brown FOX" → same change

Tagged [minor] on the commit subject accordingly.

Tests

Five added. Each of the first three measures the word alone and beside a lowercase one, so a regression to whole-string reasoning fails on the second assertion while the first still passes — which is the shape of the defect:

test covers
ToTitleCaseShouldNormalizeAnAllCapsWordIndependentlyOfItsNeighbours HTTP / parse HTTP header
ToPascalCaseShouldNormalizeAnAllCapsWordIndependentlyOfItsNeighbours MAX_SIZE / set MAX_SIZE
ToCamelCaseShouldNormalizeAnAllCapsWordIndependentlyOfItsNeighbours URL / my URL handler
ToPascalCaseShouldMatchTheAcronymExampleInTheReadme the README.md example that had drifted
ToPascalCaseShouldAgreeWithToMacroCaseOnWordBoundaries Pascal and macro agreeing on set MAX_SIZE

Proved failing without the fix. Reverting CaseConverter.cs to main and keeping the new tests: 34 total, 6 failed — all four new tests plus the two updated FOX tests. Each new test failed on its second assertion, confirming the neighbour dependence rather than a blanket wrong answer.

Verification

  • dotnet build CaseConverter.sln -c Release — succeeded, 0 warnings, 0 errors across net10.0, net9.0, net8.0, netstandard2.1, netstandard2.0 (analyzers run as errors in this repo)
  • dotnet test CaseConverter.Test -c Release — 34 total, 34 passed, 0 failed

Not in this change

README.md needs no edit — its acronym example is now correct rather than aspirational, and a test pins it. CLAUDE.md's "Regex-Based String Processing" section is stale for an unrelated reason (the regexes were replaced by code-point walks in the BMP fix), and is left alone rather than folded in here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WDrz1L5y2hSRD2xci1QNDh


Generated by Claude Code

ToTitleCase tested IsAllCaps against the whole string, so an all-caps word was
normalized only when nothing else in the string was lowercase. ToPascalCase and
ToCamelCase inherit that through it, so the same token converted two different
ways depending on its neighbours: "MAX_SIZE" gave "MaxSize" but "set MAX_SIZE"
gave "SetMAXSIZE", and "URL" gave "Url" but "my URL handler" gave "MyURLHandler".
The macro and snake paths never had the dependence.

Lowercase each all-caps word individually before handing the string to
TextInfo.ToTitleCase. A word now converts the same way whatever else is in the
string, and the title-case-derived converters agree with the macro/snake ones on
word boundaries.

This changes output for existing callers: an all-caps word beside a lowercase one
is now normalized rather than preserved as an acronym, so "parse HTTP header"
Pascal-cases to "ParseHttpHeader". That is the direction README.md already
documented - it shows "API_response_URL" giving "ApiResponseUrl", which the
library did not actually produce until now - and it matches .NET naming guidance.
The two tests that pinned "FOX" surviving in a mixed string are updated, and a
test pins the README example.

Fixes #72

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

Whether an all-caps word is normalized or kept as an acronym depends on what else is in the string

2 participants