Skip to content

fix(orm): consistent number-word boundary in toSnakeCase/toCamelCase - #6048

Open
g1mn wants to merge 1 commit into
drizzle-team:mainfrom
g1mn:fix/snakecase-number-word-boundary
Open

fix(orm): consistent number-word boundary in toSnakeCase/toCamelCase#6048
g1mn wants to merge 1 commit into
drizzle-team:mainfrom
g1mn:fix/snakecase-number-word-boundary

Conversation

@g1mn

@g1mn g1mn commented Jul 22, 2026

Copy link
Copy Markdown

Summary

toSnakeCase/toCamelCase share a tokenizer regex whose abbreviation alternative, [A-Z]+(?![a-z]), stops matching before a trailing digit and leaves it as a separate word — unlike the other two alternatives, which already absorb trailing digits into the word. So whether a digit stays attached to the preceding word depended inconsistently on that word's casing.

Input Before After
testStuffM3 test_stuff_m_3 test_stuff_m3
totalUsageM3PerA total_usage_m_3_per_a total_usage_m3_per_a
parentEN1Proof parent_en_1_proof parent_en1_proof
userID2 user_id_2 user_id2
foo2Bar foo2_bar foo2_bar (unchanged)
parentEn1Proof parent_en1_proof parent_en1_proof (unchanged)
drizzleORM drizzle_orm drizzle_orm (unchanged)
drizzleORMAndKit drizzle_orm_and_kit drizzle_orm_and_kit (unchanged)

Fix: [A-Z]+(?![a-z])[A-Z]+\d*(?![a-z]), and extracted the shared regex into a single WORD_BOUNDARY constant used by both toSnakeCase and toCamelCase (previously duplicated verbatim in each), so any future change stays in sync between the two.

Closes #6037

Test plan

  • Added regression tests to tests/casing/casing.test.ts covering every row in the table above plus symmetric toCamelCase round-trips.
  • pnpm vitest run tests/casing — 92/92 passing (all dialect casing suites: mysql/pg/sqlite × snake/camel, plus casing.test.ts).
  • Manually verified the backtracking case that a fix like this risks breaking — an all-caps abbreviation immediately followed by another capitalized word, e.g. drizzleORMAndKit — stays unaffected (no digit follows ORM, so \d* matches zero digits).
  • Checked drizzle-kit's introspection code (also imports toCamelCase/toSnakeCase) for any snapshot/golden output depending on the old digit-splitting behavior — found none.

The abbreviation alternative in the shared tokenizer regex,
`[A-Z]+(?![a-z])`, stopped matching before a trailing digit, splitting
it into its own word (e.g. `userID2` -> `user_id_2`). The lowercase
and mixed-case alternatives already absorbed trailing digits, so
number attachment depended inconsistently on the casing of the
preceding letter.

Change the alternative to `[A-Z]+\d*(?![a-z])` so digits attach to an
abbreviation the same way they do elsewhere, and extract the regex
into a shared WORD_BOUNDARY constant used by both toSnakeCase and
toCamelCase.

Closes drizzle-team#6037
@g1mn
g1mn force-pushed the fix/snakecase-number-word-boundary branch from dc91062 to 8eb6216 Compare July 22, 2026 16:12
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.

[BUG]: snakeCase handling of numbers

1 participant