Problem
Actually I needed a function which converts underscore to camel-case.
But then I’ve had the idea: Why not extend the function so, that is works the other way around?
Finally I recalled that there is a third case which programmers use: Pascal-Case. So I included that too.
The function doesn’t work perfect but as long as the different cases are used as expected it works alright.
Any hints and suggestions concerning improvements are welcomed.
/**
* Converts a string to either underscore-case or camel-case or
* to pascal-case.
* @param {string} toChange
* @param {string} [toCase='toUnderscoreCase'] - Options:
* 'toUnderscoreCase' | 'toCamelCase' | 'toPascalCase'
* @returns {string} according toCase-param.
* Returns an empty string in case of error.
*/
function changeCase(toChange, toCase) {
var needle = null;
var funct = null;
var needleChangingUpperLower = /[a-z0-9]_[a-z0-9]/g;
var needleToUnderscore = /(([a-zA-Z0-9]*?)(([a-z0-9][A-Z]))|([a-zA-Z0-9]*)$)/g;
toCase = toCase || 'toUnderscoreCase'
if ( !toChange ||
typeof toChange !== 'string' ||
toChange.length < 2 ) {
return '';
}
if (toChange.search(/[^w]/g) > -1) {
return '';
}
function toChangingUpperLower(match) {
var chars = match.split('_');
return chars[0] + chars[1].toUpperCase();
}
function toUnderscore(match, wordComplete, wordTruncated, wordChange) {
var ret = '';
if (wordChange) {
var chars = wordChange.split('');
ret += wordTruncated.toLowerCase() + chars[0] + '_' + chars[1].toLowerCase();
} else {
ret = wordComplete.toLowerCase();
}
return ret;
}
switch (toCase) {
case 'toCamelCase':
needle = needleChangingUpperLower;
toChange = toChange.slice(0, 1).toLowerCase() + toChange.slice(1);
funct = toChangingUpperLower;
break;
case 'toPascalCase':
needle = needleChangingUpperLower;
toChange = toChange.slice(0, 1).toUpperCase() + toChange.slice(1);
funct = toChangingUpperLower;
break;
case 'toUnderscoreCase':
needle = needleToUnderscore;
funct = toUnderscore;
break;
default:
return '';
}
return toChange.replace(needle, funct);
}
// --- Usage-examples and test-cases --------
var tests = [ {
toChange: 'get_amount_of_taxes',
toCase: ''
},
{
toChange: 'ThisIsTheSoCalledPascalCase',
toCase: 'toCamelCase'
},
{
toChange: 'thisIsTheSoCalledCamelCase',
toCase: 'toPascalCase'
},
{
toChange: 'set_sum_of_articles',
toCase: 'toCamelCase'
},
{
toChange: 'getAmountOfTaxes',
toCase: 'toUnderscoreCase'
},
{
toChange: 'XMLHttpRequest',
toCase: 'toUnderscoreCase'
},
{
toChange: 'theXMLconnector1ForABC',
toCase: 'toUnderscoreCase'
},
{
toChange: 'aB',
toCase: 'toUnderscoreCase'
},
{
toChange: 'c',
toCase: 'toUnderscoreCase'
},
{
toChange: [2, 3],
toCase: 'toUnderscoreCase'
},
{
toChange: 'abc#123Spec',
toCase: 'toUnderscoreCase'
},
{
toChange: 'abc_890-spec',
toCase: 'toCamelCase'
},
{
toChange: 'get_amount_of_taxes',
toCase: 'abc123'
} ];
tests.forEach(function(test) {
var result = changeCase(test.toChange, test.toCase);
var log = console.log;
var err = console.error;
!result
? err( '%sn%s',
'Something has gone wrong.',
JSON.stringify(test, null, 2))
: log( '%s - %s',
result,
JSON.stringify(test, null, 2));
});
Solution
Tests
Your test code isn’t the clearest. Assuming the rest of the code works as it should, then you should look into making your tests clearer and more reliable.
As it stands, you don’t give any expected value for the test results. Having that would enable you to throw an error if the result is wrong as well as if the method throws. Consider something like this:
var tests = [
{
'toChange': 'ThisIsPascalCase',
'toCase': 'toUnderscoreCase',
'expected': 'this_is_pascal_case'
}
];
tests.forEach(function(test) {
var result = changeCase(test.toChange, test.toCase);
if(!result) {
console.error("Something has gone wrong!");
// log extra error details if you want them
}
else if(result !== test.expected) {
console.error("Result doesn't match expected result!");
// log extra error and environment/stack details
}
else {
console.log("Test passed successfully.");
}
});
That makes your test code clearer, and provides you with accuracy checking.
Instead of writing one changeCase
function that takes a parameter for what type of transformation to do, you may want consider writing a module that has three separate functions:
var Case = {
toCamelCase : function (string) {
},
toPascalCase : function (string) {
},
toUnderscoreCase : function (string) {
}
}
This would make your code more cohesive, since each part will focus on one part of the code.
For example, this would make it so the code for underscore case is in the toUnderscoreCase
function, and the code for camel case is in the toCamelCase
function. That way if you have a bug where and underscore case test isn’t working right, you can edit the toUnderscoreCase
without the risk of inadvertently breaking your other function’s test cases. Code for a different case just acts as clutter in these situations.
I particularly bring this up since you assign which function to use with the funct
variable during the execution of the changeCase
in a switch statement. Why not just call separate the separate functions directly?
One problem with breaking up the code is that you will end up with some duplicate code, like when you validate the input string. (by the way, single character strings are perfectly valid: ‘A’ is pascal case and ‘a’ is in both camel and underscore case) You can overcome this by also breaking the duplicate code out it’s own function and then calling those functions inside your case conversion functions.