Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions lib/utils/identifierMapping.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ function memoize(func) {
// camelCase to snake_case converter that also works with non-ascii characters
// This is needed especially so that aliases containing the `:` character,
// objection uses internally, work.
function snakeCase(str, { upperCase = false, underscoreBeforeDigits = false } = {}) {
function snakeCase(
str,
{
upperCase = false,
underscoreBeforeDigits = false,
underscoreBetweenUppercaseLetters = false
} = {}
) {
if (str.length === 0) {
return str;
}
Expand Down Expand Up @@ -52,12 +59,16 @@ function snakeCase(str, { upperCase = false, underscoreBeforeDigits = false } =
// Test if `char` is an upper-case character and that the character
// actually has different upper and lower case versions.
if (char === upperChar && upperChar !== lowerChar) {
// Multiple consecutive upper case characters shouldn't add underscores.
// For example "fooBAR" should be converted to "foo_bar".
if (prevChar === prevUpperChar && prevUpperChar !== prevLowerChar) {
out += lowerChar;
} else {
const prevCharacterIsUppercase =
prevChar === prevUpperChar && prevUpperChar !== prevLowerChar;

// If underscoreBetweenUppercaseLetters is true, we always place an underscore
// before consecutive uppercase letters (e.g. "fooBAR" becomes "foo_b_a_r").
// Otherwise, we don't (e.g. "fooBAR" becomes "foo_bar").
if (underscoreBetweenUppercaseLetters || !prevCharacterIsUppercase) {
out += '_' + lowerChar;
} else {
out += lowerChar;
}
} else {
out += char;
Expand Down
39 changes: 39 additions & 0 deletions tests/unit/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,35 @@ describe('utils', () => {
testUnderscoreBeforeNumbers('fooBar:spamBaz:troloLolo', 'foo_bar:spam_baz:trolo_lolo');
testUnderscoreBeforeNumbers('fooBar.spamBaz.troloLolo', 'foo_bar.spam_baz.trolo_lolo');

testUnderscoreBetweenUppercaseLetters('*', '*');

testUnderscoreBetweenUppercaseLetters('foo', 'foo');
testUnderscoreBetweenUppercaseLetters('fooBar', 'foo_bar');
testUnderscoreBetweenUppercaseLetters('foo1Bar2', 'foo1_bar2');
testUnderscoreBetweenUppercaseLetters('fooBAR', 'foo_b_a_r');
testUnderscoreBetweenUppercaseLetters('fooBaR', 'foo_ba_r');

testUnderscoreBetweenUppercaseLetters('föö', 'föö');
testUnderscoreBetweenUppercaseLetters('fööBär', 'föö_bär');
testUnderscoreBetweenUppercaseLetters('föö1Bär2', 'föö1_bär2');
testUnderscoreBetweenUppercaseLetters('föö09Bär90', 'föö09_bär90');
testUnderscoreBetweenUppercaseLetters('fööBÄR', 'föö_b_ä_r');
testUnderscoreBetweenUppercaseLetters('fööBäR', 'föö_bä_r');

testUnderscoreBetweenUppercaseLetters('foo1bar2', 'foo1bar2');
testUnderscoreBetweenUppercaseLetters('Foo', 'foo', 'foo');
testUnderscoreBetweenUppercaseLetters('FooBar', 'foo_bar', 'fooBar');
testUnderscoreBetweenUppercaseLetters('märkäLänttiÄäliö', 'märkä_läntti_ääliö');

testUnderscoreBetweenUppercaseLetters(
'fooBar:spamBaz:troloLolo',
'foo_bar:spam_baz:trolo_lolo'
);
testUnderscoreBetweenUppercaseLetters(
'fooBar.spamBaz.troloLolo',
'foo_bar.spam_baz.trolo_lolo'
);

function test(camel, snake, backToCamel) {
backToCamel = backToCamel || camel;

Expand All @@ -162,6 +191,16 @@ describe('utils', () => {
expect(camelCase(snakeCase(camel, opt), opt)).to.equal(backToCamel);
});
}

function testUnderscoreBetweenUppercaseLetters(camel, snake, backToCamel) {
backToCamel = backToCamel || camel;
const opt = { underscoreBetweenUppercaseLetters: true };

it(`${camel} --> ${snake} --> ${backToCamel}`, () => {
expect(snakeCase(camel, opt)).to.equal(snake);
expect(camelCase(snakeCase(camel, opt), opt)).to.equal(backToCamel);
});
}
});
});

Expand Down
1 change: 1 addition & 0 deletions typings/objection/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1285,6 +1285,7 @@ declare namespace Objection {
export interface SnakeCaseMappersOptions {
upperCase?: boolean;
underscoreBeforeDigits?: boolean;
underscoreBetweenUppercaseLetters?: boolean;
}

export interface ColumnNameMappers {
Expand Down