I like your solution quite a bit. It's clear, easy to read and I don't see any bugs.
There are many ways to condense the replace
calls as you mention, but I think you're at a point where such changes can easily have a disproportionate impact on readability. That's good--it means the code is already pretty optimal from that standpoint.
For example, here's a one-shot replace
using alternation, but its merit is debatable:
const splitCamelCase = s => s.replace(
/^[a-z]|^([A-Z]+)(?=[A-Z]|$)|([A-Z])+(?=[A-Z]|$)|([A-Z])(?=[a-z]+)/g,
m => " " + m.toUpperCase()
).trim()
;
The idea here is to enumerate each scenario, join the patterns with |
s, and provide an arrow function to handle the addition of a space and a capital letter for each match.
With the two extremes in mind, I prefer a balanced approach such as:
const splitCamelCase = s =>
s.replace(/([A-Z][a-z])/g, " $1")
.replace(/\s*([A-Z]+)/g, " $1")
.replace(/./, m => m.toUpperCase())
.trim()
;
or perhaps
const splitCamelCase = s =>
s.replace(/([A-Z][a-z]|[A-Z]+(?=[A-Z]|$))/g, " $1")
.replace(/./, m => m.toUpperCase())
.trim()
;
These should offer ideas as far as how far you want to go in making the succinctness versus readability tradeoff. But, failing the possibility of a shortcut I might have overlooked, keeping your code basically as-is seems like a fine option to me.
If it's performance you're after in reducing replace
calls, there's no guarantee that fewer calls will translate into better performance. Under the hood, the regex engine may make more passes to compensate; you can benchmark and tweak using a debugger like regex101. For performance, it's likely best to avoid regex entirely and write a single-pass loop by hand.
Here's a test runner:
const splitCamelCase = s =>
s.replace(/([A-Z][a-z]|[A-Z]+(?=[A-Z]|$))/g, " $1")
.replace(/./, m => m.toUpperCase())
.trim()
;
[
"AAABbbbbCcDddEEFffGGHhIiJ",
"AaBbCcDDEeFGgHHHH",
"CDBoomBoxAAAABbbbCCC",
"CDBoomBox",
"camelCase",
"camel",
"Camel",
"c",
"C",
"Aa",
"AA",
"aa",
"AAA",
"aB",
"aBC",
"aBCc",
"",
].forEach(test =>
console.log(
splitCamelCaseOriginal(test) === splitCamelCase(test)
? `'${test}' -> '${splitCamelCase(test)}'`
: "TEST FAILED"
)
);
function splitCamelCaseOriginal(camelCaseString) {
const result = camelCaseString
.replace(/([A-Z][a-z])/g, " $1")
.replace(/([A-Z]+)/g, " $1")
.replace(/ +/g, " ")
.replace(/^ +/g, "");
return result.charAt(0).toUpperCase() + result.slice(1);
}