From e2c4891ad67796e1dd81eb8f9cc39952e8694592 Mon Sep 17 00:00:00 2001 From: Rocktim Saikia Date: Thu, 29 Oct 2020 03:34:31 +0530 Subject: [PATCH] Add `preserveConsecutiveUppercase` option (#78) Co-authored-by: Sindre Sorhus --- index.d.ts | 19 ++++++++++++++--- index.js | 32 ++++++++++++++++++++-------- readme.md | 16 ++++++++++++++ test.js | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+), 12 deletions(-) diff --git a/index.d.ts b/index.d.ts index 90a208a..169b634 100644 --- a/index.d.ts +++ b/index.d.ts @@ -7,6 +7,13 @@ declare namespace camelcase { */ readonly pascalCase?: boolean; + /** + Preserve the consecutive uppercase characters: `foo-BAR` → `FooBAR`. + + @default false + */ + readonly preserveConsecutiveUppercase?: boolean; + /** The locale parameter indicates the locale to be used to convert to upper/lower case according to any locale-specific case mappings. If multiple locales are given in an array, the best available locale is used. @@ -18,13 +25,10 @@ declare namespace camelcase { camelCase('lorem-ipsum', {locale: 'en-US'}); //=> 'loremIpsum' - camelCase('lorem-ipsum', {locale: 'tr-TR'}); //=> 'loremİpsum' - camelCase('lorem-ipsum', {locale: ['en-US', 'en-GB']}); //=> 'loremIpsum' - camelCase('lorem-ipsum', {locale: ['tr', 'TR', 'tr-TR']}); //=> 'loremİpsum' ``` @@ -62,6 +66,12 @@ camelCase('Foo-Bar', {pascalCase: true}); camelCase('--foo.bar', {pascalCase: false}); //=> 'fooBar' +camelCase('Foo-BAR', {preserveConsecutiveUppercase: true}); +//=> 'fooBAR' + +camelCase('fooBAR', {pascalCase: true, preserveConsecutiveUppercase: true})); +//=> 'FooBAR' + camelCase('foo bar'); //=> 'fooBar' @@ -76,6 +86,9 @@ camelCase(['foo', 'bar']); camelCase(['__foo__', '--bar'], {pascalCase: true}); //=> 'FooBar' +camelCase(['foo', 'BAR'], {pascalCase: true, preserveConsecutiveUppercase: true}) +//=> 'FooBAR' + camelCase('lorem-ipsum', {locale: 'en-US'}); //=> 'loremIpsum' ``` diff --git a/index.js b/index.js index 3c7ae42..cdb5511 100644 --- a/index.js +++ b/index.js @@ -29,18 +29,26 @@ const preserveCamelCase = (string, locale) => { return string; }; +const preserveConsecutiveUppercase = input => { + return input.replace(/^[\p{Lu}](?![\p{Lu}])/gu, m1 => m1.toLowerCase()); +}; + +const postProcess = (input, options) => { + return input.replace(/[_.\- ]+([\p{Alpha}\p{N}_]|$)/gu, (_, p1) => p1.toLocaleUpperCase(options.locale)) + .replace(/\d+([\p{Alpha}\p{N}_]|$)/gu, m => m.toLocaleUpperCase(options.locale)); +}; + const camelCase = (input, options) => { if (!(typeof input === 'string' || Array.isArray(input))) { throw new TypeError('Expected the input to be `string | string[]`'); } options = { - ...{pascalCase: false}, + pascalCase: false, + preserveConsecutiveUppercase: false, ...options }; - const postProcess = x => options.pascalCase ? x.charAt(0).toLocaleUpperCase(options.locale) + x.slice(1) : x; - if (Array.isArray(input)) { input = input.map(x => x.trim()) .filter(x => x.length) @@ -63,13 +71,19 @@ const camelCase = (input, options) => { input = preserveCamelCase(input, options.locale); } - input = input - .replace(/^[_.\- ]+/, '') - .toLocaleLowerCase(options.locale) - .replace(/[_.\- ]+([\p{Alpha}\p{N}_]|$)/gu, (_, p1) => p1.toLocaleUpperCase(options.locale)) - .replace(/\d+([\p{Alpha}\p{N}_]|$)/gu, m => m.toLocaleUpperCase(options.locale)); + input = input.replace(/^[_.\- ]+/, ''); + + if (options.preserveConsecutiveUppercase) { + input = preserveConsecutiveUppercase(input); + } else { + input = input.toLocaleLowerCase(); + } + + if (options.pascalCase) { + input = input.charAt(0).toLocaleUpperCase(options.locale) + input.slice(1); + } - return postProcess(input); + return postProcess(input, options); }; module.exports = camelCase; diff --git a/readme.md b/readme.md index c9b4e08..dbfc0de 100644 --- a/readme.md +++ b/readme.md @@ -35,6 +35,12 @@ camelCase('Foo-Bar', {pascalCase: true}); camelCase('--foo.bar', {pascalCase: false}); //=> 'fooBar' +camelCase('Foo-BAR', {preserveConsecutiveUppercase: true}); +//=> 'fooBAR' + +camelCase('fooBAR', {pascalCase: true, preserveConsecutiveUppercase: true})); +//=> 'FooBAR' + camelCase('foo bar'); //=> 'fooBar' @@ -49,6 +55,9 @@ camelCase(['foo', 'bar']); camelCase(['__foo__', '--bar'], {pascalCase: true}); //=> 'FooBar' +camelCase(['foo', 'BAR'], {pascalCase: true, preserveConsecutiveUppercase: true}) +//=> 'FooBAR' + camelCase('lorem-ipsum', {locale: 'en-US'}); //=> 'loremIpsum' ``` @@ -74,6 +83,13 @@ Default: `false` Uppercase the first character: `foo-bar` → `FooBar` +##### preserveConsecutiveUppercase + +Type: `boolean`\ +Default: `false` + +Preserve the consecutive uppercase characters: `foo-BAR` → `FooBAR`. + ##### locale Type: `string | string[]`\ diff --git a/test.js b/test.js index 6de410c..e7de55b 100644 --- a/test.js +++ b/test.js @@ -130,6 +130,68 @@ test('camelCase with pascalCase option', t => { t.is(camelCase('桑德_在这里。', {pascalCase: true}), '桑德在这里。'); }); +test('camelCase with preserveConsecutiveUppercase option', t => { + t.is(camelCase('foo-BAR', {preserveConsecutiveUppercase: true}), 'fooBAR'); + t.is(camelCase('Foo-BAR', {preserveConsecutiveUppercase: true}), 'fooBAR'); + t.is(camelCase('fooBAR', {preserveConsecutiveUppercase: true}), 'fooBAR'); + t.is(camelCase('fooBaR', {preserveConsecutiveUppercase: true}), 'fooBaR'); + t.is(camelCase('FOÈ-BAR', {preserveConsecutiveUppercase: true}), 'FOÈBAR'); + t.is(camelCase(['foo', 'BAR'], {preserveConsecutiveUppercase: true}), 'fooBAR'); + t.is(camelCase(['foo', '-BAR'], {preserveConsecutiveUppercase: true}), 'fooBAR'); + t.is(camelCase(['foo', '-BAR', 'baz'], {preserveConsecutiveUppercase: true}), 'fooBARBaz'); + t.is(camelCase(['', ''], {preserveConsecutiveUppercase: true}), ''); + t.is(camelCase('--', {preserveConsecutiveUppercase: true}), ''); + t.is(camelCase('', {preserveConsecutiveUppercase: true}), ''); + t.is(camelCase('--__--_--_', {preserveConsecutiveUppercase: true}), ''); + t.is(camelCase(['---_', '--', '', '-_- '], {preserveConsecutiveUppercase: true}), ''); + t.is(camelCase('foo BAR?', {preserveConsecutiveUppercase: true}), 'fooBAR?'); + t.is(camelCase('foo BAR!', {preserveConsecutiveUppercase: true}), 'fooBAR!'); + t.is(camelCase('foo BAR$', {preserveConsecutiveUppercase: true}), 'fooBAR$'); + t.is(camelCase('foo-BAR#', {preserveConsecutiveUppercase: true}), 'fooBAR#'); + t.is(camelCase('XMLHttpRequest', {preserveConsecutiveUppercase: true}), 'XMLHttpRequest'); + t.is(camelCase('AjaxXMLHttpRequest', {preserveConsecutiveUppercase: true}), 'ajaxXMLHttpRequest'); + t.is(camelCase('Ajax-XMLHttpRequest', {preserveConsecutiveUppercase: true}), 'ajaxXMLHttpRequest'); + t.is(camelCase([], {preserveConsecutiveUppercase: true}), ''); + t.is(camelCase('mGridCOl6@md', {preserveConsecutiveUppercase: true}), 'mGridCOl6@md'); + t.is(camelCase('A::a', {preserveConsecutiveUppercase: true}), 'a::a'); + t.is(camelCase('Hello1WORLD', {preserveConsecutiveUppercase: true}), 'hello1WORLD'); + t.is(camelCase('Hello11WORLD', {preserveConsecutiveUppercase: true}), 'hello11WORLD'); + t.is(camelCase('РозовыйПушистыйFOOдинорогиf', {preserveConsecutiveUppercase: true}), 'розовыйПушистыйFOOдинорогиf'); + t.is(camelCase('桑德在这里。', {preserveConsecutiveUppercase: true}), '桑德在这里。'); + t.is(camelCase('桑德_在这里。', {preserveConsecutiveUppercase: true}), '桑德在这里。'); +}); + +test('camelCase with both pascalCase and preserveConsecutiveUppercase option', t => { + t.is(camelCase('foo-BAR', {pascalCase: true, preserveConsecutiveUppercase: true}), 'FooBAR'); + t.is(camelCase('fooBAR', {pascalCase: true, preserveConsecutiveUppercase: true}), 'FooBAR'); + t.is(camelCase('fooBaR', {pascalCase: true, preserveConsecutiveUppercase: true}), 'FooBaR'); + t.is(camelCase('fOÈ-BAR', {pascalCase: true, preserveConsecutiveUppercase: true}), 'FOÈBAR'); + t.is(camelCase('--foo.BAR', {pascalCase: true, preserveConsecutiveUppercase: true}), 'FooBAR'); + t.is(camelCase(['Foo', 'BAR'], {pascalCase: true, preserveConsecutiveUppercase: true}), 'FooBAR'); + t.is(camelCase(['foo', '-BAR'], {pascalCase: true, preserveConsecutiveUppercase: true}), 'FooBAR'); + t.is(camelCase(['foo', '-BAR', 'baz'], {pascalCase: true, preserveConsecutiveUppercase: true}), 'FooBARBaz'); + t.is(camelCase(['', ''], {pascalCase: true, preserveConsecutiveUppercase: true}), ''); + t.is(camelCase('--', {pascalCase: true, preserveConsecutiveUppercase: true}), ''); + t.is(camelCase('', {pascalCase: true, preserveConsecutiveUppercase: true}), ''); + t.is(camelCase('--__--_--_', {pascalCase: true, preserveConsecutiveUppercase: true}), ''); + t.is(camelCase(['---_', '--', '', '-_- '], {pascalCase: true, preserveConsecutiveUppercase: true}), ''); + t.is(camelCase('foo BAR?', {pascalCase: true, preserveConsecutiveUppercase: true}), 'FooBAR?'); + t.is(camelCase('foo BAR!', {pascalCase: true, preserveConsecutiveUppercase: true}), 'FooBAR!'); + t.is(camelCase('Foo BAR$', {pascalCase: true, preserveConsecutiveUppercase: true}), 'FooBAR$'); + t.is(camelCase('foo-BAR#', {pascalCase: true, preserveConsecutiveUppercase: true}), 'FooBAR#'); + t.is(camelCase('xMLHttpRequest', {pascalCase: true, preserveConsecutiveUppercase: true}), 'XMLHttpRequest'); + t.is(camelCase('ajaxXMLHttpRequest', {pascalCase: true, preserveConsecutiveUppercase: true}), 'AjaxXMLHttpRequest'); + t.is(camelCase('Ajax-XMLHttpRequest', {pascalCase: true, preserveConsecutiveUppercase: true}), 'AjaxXMLHttpRequest'); + t.is(camelCase([], {pascalCase: true, preserveConsecutiveUppercase: true}), ''); + t.is(camelCase('mGridCOl6@md', {pascalCase: true, preserveConsecutiveUppercase: true}), 'MGridCOl6@md'); + t.is(camelCase('A::a', {pascalCase: true, preserveConsecutiveUppercase: true}), 'A::a'); + t.is(camelCase('Hello1WORLD', {pascalCase: true, preserveConsecutiveUppercase: true}), 'Hello1WORLD'); + t.is(camelCase('Hello11WORLD', {pascalCase: true, preserveConsecutiveUppercase: true}), 'Hello11WORLD'); + t.is(camelCase('pозовыйПушистыйFOOдинорогиf', {pascalCase: true, preserveConsecutiveUppercase: true}), 'PозовыйПушистыйFOOдинорогиf'); + t.is(camelCase('桑德在这里。', {pascalCase: true, preserveConsecutiveUppercase: true}), '桑德在这里。'); + t.is(camelCase('桑德_在这里。', {pascalCase: true, preserveConsecutiveUppercase: true}), '桑德在这里。'); +}); + test('camelCase with locale option', t => { t.is(camelCase('lorem-ipsum', {locale: 'tr-TR'}), 'loremİpsum'); t.is(camelCase('lorem-ipsum', {locale: 'en-EN'}), 'loremIpsum');