Repository: sindresorhus/camelcase (js)

Issue:
treating none alphabetic characters
I'm using ``camelcase`` to deal with css class names.
I'd like to transform css classes like ``m-grid-col6@md`` to camelCase notation ``mGridCol6@md`` and it works perfectly, but sometime I got input already camelCased(its due to React and its props) and it transform into ``mGridCol6-@md`` which is unwanted from my perspective.

Test case not passing:
```
t.is(m('mGridCol6@md'), 'mGridCol6@md');
```

I've came with two solutions:
1. change the way how ``camecase`` is checking case of character
```
isLastCharLower = c.toLowerCase() === c && c.toUpperCase() !== c;
isLastLastCharUpper = isLastCharUpper;
isLastCharUpper = c.toUpperCase() === c && c.toLowerCase() !== c;
```
2. widen the regexp in te final replace: ``/[_.\- ]+(\w|$)/g`` insted of   ``/[_.\- ]+(\.|$)/g``

But unfortunately that changes the behaviour in the following case:
```
t.is(m('@M'), '@M');
```

I'm happy to provide a PR but need suggestion how would you like to deal with it, if ever.

Pull request description:
Improve upper/lower case character detection
by asserting that a character has an upper and lower case variant, we can be more accurate on determining its case, resolving the issue in #42 

this follows JS convention for asserting case

Adds appropriate test cases

fixes #42 

Patch:
diff --git a/index.d.ts b/index.d.ts
index d4982d3..58f2069 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -1,18 +1,63 @@
-export interface Options {
-	/**
-	 * Uppercase the first character: `foo-bar` → `FooBar`.
-	 *
-	 * @default false
-	 */
-	readonly pascalCase?: boolean;
+declare namespace camelcase {
+	interface Options {
+		/**
+		Uppercase the first character: `foo-bar` → `FooBar`.
+
+		@default false
+		*/
+		readonly pascalCase?: boolean;
+	}
 }
 
-/**
- * Convert a dash/dot/underscore/space separated string to camelCase or PascalCase: `foo-bar` → `fooBar`.
- *
- * @param input - String to convert to camel case.
- */
-export default function camelcase(
-	input: string | ReadonlyArray<string>,
-	options?: Options
-): string;
+declare const camelcase: {
+	/**
+	Convert a dash/dot/underscore/space separated string to camelCase or PascalCase: `foo-bar` → `fooBar`.
+
+	@param input - String to convert to camel case.
+
+	@example
+	```
+	import camelCase = require('camelcase');
+
+	camelCase('foo-bar');
+	//=> 'fooBar'
+
+	camelCase('foo_bar');
+	//=> 'fooBar'
+
+	camelCase('Foo-Bar');
+	//=> 'fooBar'
+
+	camelCase('Foo-Bar', {pascalCase: true});
+	//=> 'FooBar'
+
+	camelCase('--foo.bar', {pascalCase: false});
+	//=> 'fooBar'
+
+	camelCase('foo bar');
+	//=> 'fooBar'
+
+	console.log(process.argv[3]);
+	//=> '--foo-bar'
+	camelCase(process.argv[3]);
+	//=> 'fooBar'
+
+	camelCase(['foo', 'bar']);
+	//=> 'fooBar'
+
+	camelCase(['__foo__', '--bar'], {pascalCase: true});
+	//=> 'FooBar'
+	```
+	*/
+	(input: string | ReadonlyArray<string>, options?: camelcase.Options): string;
+
+	// TODO: Remove this for the next major release, refactor the whole definition to:
+	// declare function camelcase(
+	// 	input: string | ReadonlyArray<string>,
+	// 	options?: camelcase.Options
+	// ): string;
+	// export = camelcase;
+	default: typeof camelcase;
+};
+
+export = camelcase;
diff --git a/index.js b/index.js
index 54398d4..8cbd59e 100644
--- a/index.js
+++ b/index.js
@@ -20,9 +20,9 @@ const preserveCamelCase = string => {
 			isLastCharUpper = false;
 			isLastCharLower = true;
 		} else {
-			isLastCharLower = character.toLowerCase() === character;
+			isLastCharLower = character.toLowerCase() === character && character.toUpperCase() !== character;
 			isLastLastCharUpper = isLastCharUpper;
-			isLastCharUpper = character.toUpperCase() === character;
+			isLastCharUpper = character.toUpperCase() === character && character.toLowerCase() !== character;
 		}
 	}
 
@@ -75,4 +75,5 @@ const camelCase = (input, options) => {
 };
 
 module.exports = camelCase;
+// TODO: Remove this for the next major release
 module.exports.default = camelCase;
diff --git a/package.json b/package.json
index ffd8a1f..0cb8a88 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
 		"node": ">=6"
 	},
 	"scripts": {
-		"test": "xo && ava && tsd-check"
+		"test": "xo && ava && tsd"
 	},
 	"files": [
 		"index.js",
@@ -36,8 +36,8 @@
 		"pascal-case"
 	],
 	"devDependencies": {
-		"ava": "^1.2.1",
-		"tsd-check": "^0.3.0",
+		"ava": "^1.4.1",
+		"tsd": "^0.7.1",
 		"xo": "^0.24.0"
 	}
 }


Test patch:
diff --git a/index.test-d.ts b/index.test-d.ts
index 3e21afa..ed6c78e 100644
--- a/index.test-d.ts
+++ b/index.test-d.ts
@@ -1,5 +1,5 @@
-import {expectType} from 'tsd-check';
-import camelCase from '.';
+import {expectType} from 'tsd';
+import camelCase = require('.');
 
 expectType<string>(camelCase('foo-bar'));
 expectType<string>(camelCase('Foo-Bar', {pascalCase: true}));
diff --git a/test.js b/test.js
index 896bab9..6aab4a3 100644
--- a/test.js
+++ b/test.js
@@ -47,6 +47,8 @@ test('camelCase', t => {
 	t.is(camelCase('AjaxXMLHttpRequest'), 'ajaxXmlHttpRequest');
 	t.is(camelCase('Ajax-XMLHttpRequest'), 'ajaxXmlHttpRequest');
 	t.is(camelCase([]), '');
+	t.is(camelCase('mGridCol6@md'), 'mGridCol6@md');
+	t.is(camelCase('A::a'), 'a::a');
 });
 
 test('camelCase with pascalCase option', t => {
@@ -95,6 +97,8 @@ test('camelCase with pascalCase option', t => {
 	t.is(camelCase('AjaxXMLHttpRequest', {pascalCase: true}), 'AjaxXmlHttpRequest');
 	t.is(camelCase('Ajax-XMLHttpRequest', {pascalCase: true}), 'AjaxXmlHttpRequest');
 	t.is(camelCase([], {pascalCase: true}), '');
+	t.is(camelCase('mGridCol6@md', {pascalCase: true}), 'MGridCol6@md');
+	t.is(camelCase('A::a', {pascalCase: true}), 'A::a');
 });
 
 test('invalid input', t => {