Repository: sindresorhus/eslint-plugin-unicorn (JavaScript)

Issue:
(v14): Make character class sorting in `regex-shorthand` optional or at least documented
Hi,

With the latest 14.0.0, I'm getting the following false positive for `regex-shorthand` (besides those reported).

The rule is expecting sorting within character classes, though this is not advertised as being part of the rule in the docs. E.g., `/#([^)'"]+)/` can be optimized to `/#([^"')]+)/` . I think this should be either advertised as such, or, better yet, made into an option, since it is sometimes clearer to make one's own groupings without sorting, e.g., `[AaQqTt]`. 

Thanks!


Discussion:
We are using `regexp-tree` to optimize them since 14.0.0, unfortunately seems there  is no option to avoid sorting character class https://github.com/DmitrySoshnikov/regexp-tree/blob/master/src/optimizer/README.md#transforms

I guess we can document this.
Yes, let's document it.
@fisker : Thank you for the background info.

I've filed https://github.com/DmitrySoshnikov/regexp-tree/issues/199 in the hopes it may be possible to configure in the future.
I agree that this is problematic.

One part of it is that different languages sort characters in different ways. Eg. in Swedish `åäö` is last in the alphabet and in that order and this rule eg. wants to resort `åä` to `äå`. To makes matter even more complicated, German sorts those letters after their dot-less equivalents, so `ä` after `a` and `ö` after `o` – so one universal true rule is not really possible when sorting purely on letters, unless one wants to enforce a "true" sorting upon everyone else.

I've now disabled this rule on our projects, which is a pity, because it has lots of other nice things to it than the sorting.
I did find a way to deactivate character sorting @fisker, you can find it in #468 
May I suggest to disable character class sorting by default ? The optimisation seems unnecessary and it makes regexps less readable.



Patch:
diff --git a/docs/rules/regex-shorthand.md b/docs/rules/regex-shorthand.md
index a00d13abc4..db42628bc5 100644
--- a/docs/rules/regex-shorthand.md
+++ b/docs/rules/regex-shorthand.md
@@ -2,7 +2,6 @@
 
 This rule is fixable.
 
-
 ## Fail
 
 ```js
@@ -15,7 +14,6 @@ const regex = /[^a-z0-9_]/i;
 const regex = /[0-9]\.[a-zA-Z0-9_]\-[^0-9]/i;
 ```
 
-
 ## Pass
 
 ```js
@@ -27,3 +25,12 @@ const regex = /\W/;
 const regex = /\W/i;
 const regex = /\d\.\w\-\D/i;
 ```
+
+## Options
+
+### sortCharacterClasses
+
+Type: `boolean`\
+Default: `true`
+
+Disables optimizations that affect the sorting of character classes. For example, preserves the order of the characters in `[AaQqTt]` rather than sorting it to `[AQTaqt]`.
diff --git a/rules/regex-shorthand.js b/rules/regex-shorthand.js
index 39f4455b82..0c20a74bb0 100644
--- a/rules/regex-shorthand.js
+++ b/rules/regex-shorthand.js
@@ -7,6 +7,14 @@ const quoteString = require('./utils/quote-string');
 const message = 'Use regex shorthands to improve readability.';
 
 const create = context => {
+	const {sortCharacterClasses} = context.options[0] || {};
+
+	const blacklist = [];
+
+	if (sortCharacterClasses === false) {
+		blacklist.push('charClassClassrangesMerge');
+	}
+
 	return {
 		'Literal[regex]': node => {
 			const {raw: original, regex} = node;
@@ -20,7 +28,7 @@ const create = context => {
 			let optimized = original;
 
 			try {
-				optimized = optimize(original).toString();
+				optimized = optimize(original, undefined, {blacklist}).toString();
 			} catch (_) {}
 
 			if (original === optimized) {
@@ -69,6 +77,16 @@ const create = context => {
 	};
 };
 
+const schema = [{
+	type: 'object',
+	properties: {
+		sortCharacterClasses: {
+			type: 'boolean',
+			default: true
+		}
+	}
+}];
+
 module.exports = {
 	create,
 	meta: {
@@ -76,6 +94,7 @@ module.exports = {
 		docs: {
 			url: getDocumentationUrl(__filename)
 		},
-		fixable: 'code'
+		fixable: 'code',
+		schema
 	}
 };


Test patch:
diff --git a/test/regex-shorthand.js b/test/regex-shorthand.js
index dcceeab680..c43a18a6f3 100644
--- a/test/regex-shorthand.js
+++ b/test/regex-shorthand.js
@@ -13,6 +13,10 @@ const error = {
 	message: 'Use regex shorthands to improve readability.'
 };
 
+const disableSortCharacterClassesOptions = [{
+	sortCharacterClasses: false
+}];
+
 ruleTester.run('regex-shorthand', rule, {
 	valid: [
 		'const foo = /\\d/',
@@ -36,7 +40,12 @@ ruleTester.run('regex-shorthand', rule, {
 
 		// Should not suggest wrong regex (#447)
 		'/(\\s|\\.|@|_|-)/u',
-		'/[\\s.@_-]/u'
+		'/[\\s.@_-]/u',
+
+		{
+			code: '/[GgHhIiå.Z:a-f"0-8%A*ä]/',
+			options: disableSortCharacterClassesOptions
+		}
 	],
 	invalid: [
 		{
@@ -250,6 +259,24 @@ ruleTester.run('regex-shorthand', rule, {
 				message: '/^by @([a-zA-Z0-9-]+)/ can be optimized to /^by @([\\d-A-Za-z]+)/'
 			}],
 			output: 'const foo = /^by @([\\d-A-Za-z]+)/'
+		},
+		{
+			code: '/[GgHhIiå.Z:a-f"0-8%A*ä]/',
+			errors: [{
+				...error,
+				message: '/[GgHhIiå.Z:a-f"0-8%A*ä]/ can be optimized to /["%*.0-8:AG-IZa-iäå]/'
+			}],
+			output: '/["%*.0-8:AG-IZa-iäå]/'
+		},
+		// Should still use shorthand when disabling sort character classes
+		{
+			code: '/[a0-9b]/',
+			options: disableSortCharacterClassesOptions,
+			errors: [{
+				...error,
+				message: '/[a0-9b]/ can be optimized to /[a\\db]/'
+			}],
+			output: '/[a\\db]/'
 		}
 	]
 });