Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add preserveConsecutiveUppercase option #78

Merged
merged 11 commits into from Oct 28, 2020
16 changes: 16 additions & 0 deletions index.d.ts
Expand Up @@ -6,6 +6,13 @@ declare namespace camelcase {
@default false
*/
readonly pascalCase?: boolean;

/**
Preserve the consecutive uppercase characters: `foo-BAR` → `FooBAR`.

@default false
*/
readonly preserveConsecutiveUppercase?: boolean;
}
}

Expand Down Expand Up @@ -38,6 +45,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'

Expand All @@ -51,6 +64,9 @@ camelCase(['foo', 'bar']);

camelCase(['__foo__', '--bar'], {pascalCase: true});
//=> 'FooBar'

camelCase(['foo', 'BAR'], {pascalCase: true, preserveConsecutiveUppercase: true})
//=> 'FooBAR'
```
*/
declare function camelcase(
Expand Down
30 changes: 22 additions & 8 deletions index.js
Expand Up @@ -29,18 +29,26 @@ const preserveCamelCase = string => {
return string;
};

const preserveConsecutiveUppercase = input => {
return input.replace(/^[\p{Lu}](?![\p{Lu}])/gu, m1 => m1.toLowerCase());
};

const postProcess = input => {
return input.replace(/[_.\- ]+([\p{Alpha}\p{N}_]|$)/gu, (_, p1) => p1.toLocaleUpperCase())
.replace(/\d+([\p{Alpha}\p{N}_]|$)/gu, m => m.toLocaleUpperCase());
};

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() + x.slice(1) : x;

if (Array.isArray(input)) {
input = input.map(x => x.trim())
.filter(x => x.length)
Expand All @@ -63,11 +71,17 @@ const camelCase = (input, options) => {
input = preserveCamelCase(input);
}

input = input
.replace(/^[_.\- ]+/, '')
.toLocaleLowerCase()
.replace(/[_.\- ]+([\p{Alpha}\p{N}_]|$)/gu, (_, p1) => p1.toLocaleUpperCase())
.replace(/\d+([\p{Alpha}\p{N}_]|$)/gu, m => m.toLocaleUpperCase());
input = input.replace(/^[_.\- ]+/, '');

if (options.preserveConsecutiveUppercase) {
input = preserveConsecutiveUppercase(input);
} else {
input = input.toLocaleLowerCase();
}

if (options.pascalCase) {
input = input.charAt(0).toLocaleUpperCase() + input.slice(1);
}

return postProcess(input);
};
Expand Down
16 changes: 16 additions & 0 deletions readme.md
Expand Up @@ -35,6 +35,12 @@ camelCase('Foo-Bar', {pascalCase: true});
camelCase('--foo.bar', {pascalCase: false});
//=> 'fooBar'

camelCase('Foo-Bar', {preserveConsecutiveUppercase: true});
rocktimsaikia marked this conversation as resolved.
Show resolved Hide resolved
//=> 'fooBAR'

camelCase('fooBAR', {pascalCase: true, preserveConsecutiveUppercase: true}));
//=> 'FooBAR'

camelCase('foo bar');
//=> 'fooBar'

Expand All @@ -48,6 +54,9 @@ camelCase(['foo', 'bar']);

camelCase(['__foo__', '--bar'], {pascalCase: true});
//=> 'FooBar'

camelCase(['foo', 'BAR'], {pascalCase: true, preserveConsecutiveUppercase: true})
//=> 'FooBAR'
```

## API
Expand All @@ -71,6 +80,13 @@ Default: `false`

Uppercase the first character: `foo-bar` → `FooBar`

##### preserveConsecutiveUppercase

Type: `boolean`\
Default: `false`

Preserve the consecutive uppercase characters: `foo-BAR` → `FooBAR`
rocktimsaikia marked this conversation as resolved.
Show resolved Hide resolved

## camelcase for enterprise

Available as part of the Tidelift Subscription.
Expand Down
62 changes: 62 additions & 0 deletions test.js
Expand Up @@ -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 => {
sindresorhus marked this conversation as resolved.
Show resolved Hide resolved
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('invalid input', t => {
t.throws(() => {
camelCase(1);
Expand Down