Skip to content

Commit

Permalink
Handle Unicode character casing and require Node.js 10 (#62)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
sverweij and sindresorhus committed Apr 7, 2020
1 parent adfe007 commit 878bc44
Show file tree
Hide file tree
Showing 7 changed files with 37 additions and 16 deletions.
2 changes: 0 additions & 2 deletions .travis.yml
Expand Up @@ -2,5 +2,3 @@ language: node_js
node_js:
- '12'
- '10'
- '8'
- '6'
5 changes: 5 additions & 0 deletions index.d.ts
Expand Up @@ -13,6 +13,8 @@ declare const camelcase: {
/**
Convert a dash/dot/underscore/space separated string to camelCase or PascalCase: `foo-bar` → `fooBar`.
Correctly handles Unicode strings.
@param input - String to convert to camel case.
@example
Expand All @@ -27,6 +29,9 @@ declare const camelcase: {
camelCase('Foo-Bar');
//=> 'fooBar'
camelCase('розовый_пушистый_единороги');
//=> 'розовыйПушистыйЕдинороги'
camelCase('Foo-Bar', {pascalCase: true});
//=> 'FooBar'
Expand Down
27 changes: 14 additions & 13 deletions index.js
Expand Up @@ -8,21 +8,21 @@ const preserveCamelCase = string => {
for (let i = 0; i < string.length; i++) {
const character = string[i];

if (isLastCharLower && /[a-zA-Z]/.test(character) && character.toUpperCase() === character) {
if (isLastCharLower && /[\p{Lu}]/u.test(character)) {
string = string.slice(0, i) + '-' + string.slice(i);
isLastCharLower = false;
isLastLastCharUpper = isLastCharUpper;
isLastCharUpper = true;
i++;
} else if (isLastCharUpper && isLastLastCharUpper && /[a-zA-Z]/.test(character) && character.toLowerCase() === character) {
} else if (isLastCharUpper && isLastLastCharUpper && /[\p{Ll}]/u.test(character)) {
string = string.slice(0, i - 1) + '-' + string.slice(i - 1);
isLastLastCharUpper = isLastCharUpper;
isLastCharUpper = false;
isLastCharLower = true;
} else {
isLastCharLower = character.toLowerCase() === character && character.toUpperCase() !== character;
isLastCharLower = character.toLocaleLowerCase() === character && character.toLocaleUpperCase() !== character;
isLastLastCharUpper = isLastCharUpper;
isLastCharUpper = character.toUpperCase() === character && character.toLowerCase() !== character;
isLastCharUpper = character.toLocaleUpperCase() === character && character.toLocaleLowerCase() !== character;
}
}

Expand All @@ -34,11 +34,12 @@ const camelCase = (input, options) => {
throw new TypeError('Expected the input to be `string | string[]`');
}

options = Object.assign({
pascalCase: false
}, options);
options = {
...{pascalCase: false},
...options
};

const postProcess = x => options.pascalCase ? x.charAt(0).toUpperCase() + x.slice(1) : x;
const postProcess = x => options.pascalCase ? x.charAt(0).toLocaleUpperCase() + x.slice(1) : x;

if (Array.isArray(input)) {
input = input.map(x => x.trim())
Expand All @@ -53,20 +54,20 @@ const camelCase = (input, options) => {
}

if (input.length === 1) {
return options.pascalCase ? input.toUpperCase() : input.toLowerCase();
return options.pascalCase ? input.toLocaleUpperCase() : input.toLocaleLowerCase();
}

const hasUpperCase = input !== input.toLowerCase();
const hasUpperCase = input !== input.toLocaleLowerCase();

if (hasUpperCase) {
input = preserveCamelCase(input);
}

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

return postProcess(input);
};
Expand Down
1 change: 1 addition & 0 deletions index.test-d.ts
Expand Up @@ -2,6 +2,7 @@ import {expectType} from 'tsd';
import camelCase = require('.');

expectType<string>(camelCase('foo-bar'));
expectType<string>(camelCase('розовый_пушистый_единороги'));
expectType<string>(camelCase('Foo-Bar', {pascalCase: true}));
expectType<string>(camelCase(['foo', 'bar']));
expectType<string>(camelCase(['__foo__', '--bar'], {pascalCase: true}));
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -10,7 +10,7 @@
"url": "sindresorhus.com"
},
"engines": {
"node": ">=6"
"node": ">=10"
},
"scripts": {
"test": "xo && ava && tsd"
Expand Down
5 changes: 5 additions & 0 deletions readme.md
Expand Up @@ -2,6 +2,8 @@

> Convert a dash/dot/underscore/space separated string to camelCase or PascalCase: `foo-bar``fooBar`
Correctly handles Unicode strings.


## Install

Expand All @@ -24,6 +26,9 @@ camelCase('foo_bar');
camelCase('Foo-Bar');
//=> 'fooBar'

camelCase('розовый_пушистый_единороги');
//=> 'розовыйПушистыйЕдинороги'

camelCase('Foo-Bar', {pascalCase: true});
//=> 'FooBar'

Expand Down
11 changes: 11 additions & 0 deletions test.js
Expand Up @@ -58,6 +58,12 @@ test('camelCase', t => {
t.is(camelCase('1Hello'), '1Hello');
t.is(camelCase('1hello'), '1Hello');
t.is(camelCase('h2w'), 'h2W');
t.is(camelCase('розовый_пушистый-единороги'), 'розовыйПушистыйЕдинороги');
t.is(camelCase('розовый_пушистый-единороги'), 'розовыйПушистыйЕдинороги');
t.is(camelCase('РОЗОВЫЙ_ПУШИСТЫЙ-ЕДИНОРОГИ'), 'розовыйПушистыйЕдинороги');
t.is(camelCase('桑德在这里。'), '桑德在这里。');
t.is(camelCase('桑德在这里。'), '桑德在这里。');
t.is(camelCase('桑德_在这里。'), '桑德在这里。');
});

test('camelCase with pascalCase option', t => {
Expand Down Expand Up @@ -117,6 +123,11 @@ test('camelCase with pascalCase option', t => {
t.is(camelCase('1hello', {pascalCase: true}), '1Hello');
t.is(camelCase('1Hello', {pascalCase: true}), '1Hello');
t.is(camelCase('h1W', {pascalCase: true}), 'H1W');
t.is(camelCase('РозовыйПушистыйЕдинороги', {pascalCase: true}), 'РозовыйПушистыйЕдинороги');
t.is(camelCase('розовый_пушистый-единороги', {pascalCase: true}), 'РозовыйПушистыйЕдинороги');
t.is(camelCase('РОЗОВЫЙ_ПУШИСТЫЙ-ЕДИНОРОГИ', {pascalCase: true}), 'РозовыйПушистыйЕдинороги');
t.is(camelCase('桑德在这里。', {pascalCase: true}), '桑德在这里。');
t.is(camelCase('桑德_在这里。', {pascalCase: true}), '桑德在这里。');
});

test('invalid input', t => {
Expand Down

0 comments on commit 878bc44

Please sign in to comment.