blakeembrey / blakeembrey/change-case

Case functions are not idempotent for digit→letter boundaries

Open
#367 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
2.4k
Forks
106
PR merge metrics
No merged PRs in 30d

Description

### Summary

Several case transformers are not idempotent — applying a function to its own output changes the result — for inputs where a digit is adjacent to a letter. `split()` treats a **digit → UPPERCASE** boundary as a word split but a **digit → lowercase** boundary as not, so re-casing already-cased text (e.g. `CONSTANT_CASE`) inserts new word breaks.

### Reproduction

```js
import { constantCase } from "change-case"; // v5, default options

constantCase("foo123bar"); // "FOO123BAR"
constantCase(constantCase("foo123bar")); // "FOO123_BAR" ← extra "_"

constantCase("a1b2c3"); // "A1B2C3"
constantCase(constantCase("a1b2c3")); // "A1_B2_C3"

constantCase("3dModel"); // "3D_MODEL"
constantCase(constantCase("3dModel")); // "3_D_MODEL"
```

### Root cause

```js
const SPLIT_LOWER_UPPER_RE = /([\p{Ll}\d])(\p{Lu})/gu;
```

`\d` is in the "before" class, so a digit followed by an UPPERCASE letter is split (`"FOO123BAR"` → `FOO123` | `BAR`), while a digit followed by a lowercase letter is not (`"foo123bar"` stays one word). Because `constantCase` uppercases its output, the second pass now matches and splits where the first didn't → `f(f(x)) !== f(x)`. The same asymmetry can surface in other transforms when re-casing already-uppercased text.

### Expected

Idempotency for the case transformers: `f(f(x)) === f(x)`.

### Possible fix

Make digit boundaries symmetric — e.g. drop `\d` from the before-class of `SPLIT_LOWER_UPPER_RE` so a digit→UPPERCASE boundary isn't split when `separateNumbers` is off (numbers already have the opt-in `splitSeparateNumbers` path). This does change output for some digit-adjacent inputs, so it's a judgement call. Happy to open a PR with tests in whichever direction you'd prefer — wanted to surface the inconsistency first.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start at the SPLIT_LOWER_UPPER_RE expression and the constantCase transform described in the issue. Reproduce the listed digit-to-letter cases, decide the intended symmetric boundary behavior, and add regression coverage showing that applying each affected transformer twice produces the same result.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.