Skip to content

[Bug] Case helpers (strCamelCase, strKebabCase, strSnakeCase, strCapitalizeWords): case not normalised, inconsistent word boundaries, non-ASCII letters dropped #634

Open

Description

@nev21

Version: @nevware21/ts-utils 0.16.0 (0.15.0 gives the same output for the inputs checked)
Environment: Node v22.23.4, the package's dist/es5/mod build

Summary

All four helpers are built on two regular expressions:

  • _convertCase: /((_|\W)+(\w){0,1}|([a-z])([A-Z]))/g
  • strLetterCase: /(_|\b)\w/g

Without the u flag, \w, \W and \b cover ASCII only, and the conversion only rewrites the character after a boundary. That causes the five behaviours below. Sections 1, 3 and 4 look like bugs; section 2 is a word-boundary rule worth deciding; section 5 is a question. Every "Actual" value comes from running 0.16.0. The "Suggested" column is a proposal, not a claim about intended behaviour.

1. Bug: strCamelCase does not normalise the case inside a word

Call Actual Suggested
strCamelCase("hELLo dARKness-my old_friend") hELLoDARKnessMyOldFriend helloDarknessMyOldFriend
strCamelCase("ABC def") aBCDef abcDef
strCamelCase("HELLO WORLD") hELLOWORLD helloWorld
strCamelCase("EFTPOS PURCHASE 1234") eFTPOSPURCHASE1234 eftposPurchase1234

Only the first character is lower-cased (/^\w/) and each later word's first letter upper-cased. The rest of each word keeps its case, so all-caps input loses its word boundaries entirely (hELLOWORLD).

2. Word boundaries inside a word are found inconsistently (kebab and snake case)

Call Actual Suggested
strKebabCase("hELLo dARKness-my old_friend") h-ello-darkness-my-old-friend hello-darkness-my-old-friend
strKebabCase("ABCDef") abcdef abc-def
strKebabCase("XMLHttpRequest") xmlhttp-request xml-http-request
strKebabCase("fooBar2Baz") foo-bar2baz foo-bar2-baz
  • ([a-z])([A-Z]) splits after a lone lower-case letter (h + ELLo).
  • It never splits an acronym from the word after it (ABCDef, XMLHttp).
  • It never splits a digit from an upper-case letter (2Baz).
  • strCamelCase on the same hELLo input does not split there at all, so camel case and kebab case disagree about where the words are.

3. Bug: letters outside ASCII are treated as separators and dropped

Call Actual Suggested
strCamelCase("café crème") cafCrMe caféCrème
strKebabCase("café crème") caf-cr-me café-crème
strCamelCase("über straße") berStraE überStraße
strCapitalizeWords("café crème") Café CrèMe Café Crème

é, è, ü and ß match \W, so camel, kebab and snake case replace them with the separator and lose them. \b sees a word boundary after them, so capitalise-words capitalises the next letter (CrèMe). Unicode property escapes ([\p{L}\p{N}] with the u flag) would keep them. They need ES2018, which may not fit the ES5 build, so a character-class alternative or an option may be needed there.

4. Bug: leading and trailing separators become a dangling separator

Call Actual Suggested
strKebabCase("__init__") -init- init
strKebabCase("--foo--") -foo- foo
strSnakeCase("O'Brien & Sons, Ltd.") o_brien_sons_ltd_ o_brien_sons_ltd
strCamelCase("O'Brien & Sons, Ltd.") oBrienSonsLtd (as is)

Only whitespace is trimmed before conversion, so any other leading or trailing punctuation becomes a separator at the edge. Camel case drops it, so camel case and kebab/snake case disagree here too.

5. Question: should strCapitalizeWords keep camel-case word boundaries?

Call Actual
strCapitalizeWords("fooBarBaz") Foobarbaz
strCapitalizeWords("foo_bar-baz qux") Foo_Bar-Baz Qux

It lower-cases everything before capitalising, so camel-case input becomes a single word. This may be intended; if so, it would help to document it.

Repro

const { strCamelCase, strKebabCase, strSnakeCase, strCapitalizeWords } = require("@nevware21/ts-utils");

console.log(strCamelCase("hELLo dARKness-my old_friend")); // hELLoDARKnessMyOldFriend
console.log(strCamelCase("HELLO WORLD"));                  // hELLOWORLD
console.log(strKebabCase("hELLo dARKness-my old_friend")); // h-ello-darkness-my-old-friend
console.log(strKebabCase("XMLHttpRequest"));               // xmlhttp-request
console.log(strKebabCase("fooBar2Baz"));                   // foo-bar2baz
console.log(strCamelCase("café crème"));                   // cafCrMe
console.log(strCapitalizeWords("café crème"));             // Café CrèMe
console.log(strKebabCase("__init__"));                     // -init-
console.log(strSnakeCase("O'Brien & Sons, Ltd."));         // o_brien_sons_ltd_
console.log(strCapitalizeWords("fooBarBaz"));              // Foobarbaz

Compatibility

Any of these fixes changes output that existing callers may rely on, including code that ports these helpers and must match them output for output. A changelog entry, or an option on each helper that selects the new rules, would let callers move deliberately.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions