That line turns HTTPServer into http_server and getHTTP2Client into get_http2_client. The first replace splits a run of capitals before its last capital when a lowercase letter follows, so HTTPServer becomes HTTP_Server. The second puts an underscore between a lowercase letter or digit and the capital after it. Then everything is lowercased.
const toSnake = (s) =>
s.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2').replace(/([a-z\d])([A-Z])/g, '$1_$2').toLowerCase();
for (const name of ['HTTPServer', 'getHTTPResponseCode', 'userID', 'XMLHttpRequest', 'sha256Hash', 'getHTTP2Client']) {
console.log(name.padEnd(20), toSnake(name));
}
HTTPServer http_server
getHTTPResponseCode get_http_response_code
userID user_id
XMLHttpRequest xml_http_request
sha256Hash sha256_hash
getHTTP2Client get_http2_client
The tool below converts the same six names, one per line, and its snake_case row matches.
The naive version
'HTTPServer'.replace(/[A-Z]/g, (c) => '_' + c.toLowerCase()); // '_h_t_t_p_server'
'userID'.replace(/[A-Z]/g, (c) => '_' + c.toLowerCase()); // 'user_i_d'
'HTTPServer'.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase(); // 'httpserver'
An underscore before every capital breaks acronyms apart and adds a leading underscore to PascalCase names. Splitting only where a lowercase letter meets a capital goes wrong the other way and glues HTTPServer into one word. It takes both patterns.
Digits and other letters
[a-z\d] lets a digit end a word, so sha256Hash keeps sha256 together. A digit never starts one: parseHTTPResponse2xx gives parse_http_response2xx, while the tool below treats 2xx as its own word and writes parse_http_response_2xx.
[A-Z] and [a-z] are ASCII only, so caféBar comes out as cafébar. With the u flag, Unicode property classes fix that:
const toSnakeUnicode = (s) =>
s.replace(/(\p{Lu}+)(\p{Lu}\p{Ll})/gu, '$1_$2').replace(/([\p{Ll}\d])(\p{Lu})/gu, '$1_$2').toLowerCase();
toSnakeUnicode('caféBar'); // 'café_bar'
toSnakeUnicode('naïveÉtat'); // 'naïve_état'
Libraries split differently
lodash 4’s _.snakeCase keeps acronyms together but makes every number a word of its own and strips accents. change-case 5’s snakeCase gives the same results as the two patterns for the six names above:
_.snakeCase('getHTTP2Client'); // 'get_http_2_client'
_.snakeCase('sha256Hash'); // 'sha_256_hash'
_.snakeCase('caféBar'); // 'cafe_bar'
snakeCase('getHTTP2Client'); // 'get_http2_client', from change-case
If two systems must agree on column or field names, make them use the same function.
The reverse: snake_case to camelCase
const toCamel = (s) => s.replace(/_([a-z\d])/g, (_, c) => c.toUpperCase());
toCamel('get_http2_client'); // 'getHttp2Client'
toCamel('http_server'); // 'httpServer'
Acronyms don’t survive the round trip: HTTPServer goes to http_server and comes back as httpServer. lodash’s _.camelCase('http_server') gives the same.