Skip to content

Keep lowercase letters as they are in Snake, Kebab and Camel case [patch] - #111

Merged
matt-edmondson merged 1 commit into
mainfrom
fix/84-snake-case-lowercase-round-trip
Oct 6, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
fix/84-snake-case-lowercase-round-trip

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #84

Problem

ToSnakeCase was ToLowerInvariantFull(input.ToMacroCase()). Uppercasing a letter and then lowercasing it doesn't always give back the same letter: ς becomes σ, µ (U+00B5) becomes μ (U+03BC), and ſ becomes s. As a result "λόγος" turned into "λόγοσ", and running ToSnakeCase on text that was already snake_case changed it. Camel case had the same problem with its first letter, which went through the Pascal step's uppercase and was then lowered again.

Change

  • ToMacroCase and ToSnakeCase now share a private JoinWordsWithUnderscores(input, caseMap) that trims, splits, case-maps and joins with underscores. Macro passes the uppercase mapping and snake passes the lowercase one, so snake lowercases the split words directly, and the two can't drift apart. Kebab still builds on snake.
  • ToCamelCase keeps the input's first letter as it is when it is already lowercase and is the letter the Pascal step uppercased. Otherwise it falls back to ToLowercaseFirstChar as before.

Behaviour change worth a look

The dotless ı is a lowercase letter, so it is now kept too: "kırmızı".ToSnakeCase() gives "kırmızı" (it used to give "kirmizi"), and "ılık su".ToCamelCase() gives "ılıkSu" (it used to give "ilıkSu"). That is what the issue's acceptance criterion asks for, since all-lowercase input should come out unchanged apart from separators. Macro and Pascal output for ı is unchanged, and İ is still lowered to i.

Tests

  • ToSnakeCaseAndToKebabCaseShouldKeepLowercaseLetters covers "λόγος", "already_snake_ς", "µs delay", "ſtraße" and "kırmızı", and also checks that snake case is idempotent.
  • ToCamelCaseShouldKeepALowercaseFirstLetter covers "µs delay", "ςx" and "ılık su".
  • With the library change stashed, both new tests fail on the issue's examples. With it, the full suite passes on net10.0: 141 passed. The library builds for every target framework.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JE3qjjjTXN28xUcTq2h6vD


Generated by Claude Code

…tch]

ToSnakeCase lowered the output of ToMacroCase, and that round trip is not
an identity for every lowercase letter: final sigma came back as sigma,
the micro sign as Greek mu, and long s as s. Snake case now lowercases the
split words directly, sharing the split-and-join step with macro case.
Camel case keeps a first letter that is already lowercase instead of
lowering the uppercase Pascal step gave it.

Fixes #84

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

sonarqubecloud Bot commented Oct 6, 2026

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit 52c8d38 into main Oct 6, 2026
14 checks passed
@matt-edmondson
matt-edmondson deleted the fix/84-snake-case-lowercase-round-trip branch October 6, 2026 07:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants