Repository: DmitrySoshnikov/regexp-tree (js)

Issue:
Wrong classranges optimization
Something is wrong with classranges optimizations, example here:

    $ bin/regexp-tree -e '/[^a-zA-Z0-9_!#$%&*@＠]/' -o
   
    Optimized: /[^\d!#$%&*@-Z_a-z＠]

With `charClassClassrangesMerge` optimization disabled I have better result:

    Expected:  /[^\w!#$%&*@＠]/

Pull request:
Optimizer: CharClassClassrangesMerge: Only merge \w chars and char codes into class ranges
This fixes #156.

EDIT: Somehow I did not get notifications for the discussion that happened in #156 between yesterday evening and this morning. I still think this PR might be relevant though, since we already had a special case to only merge word chars and char codes in a class range [in some cases](https://github.com/DmitrySoshnikov/regexp-tree/pull/157/files#diff-48e74382c584f8474d8aadcdb8af4beeR162).

Patch:
diff --git a/src/optimizer/transforms/char-class-classranges-merge-transform.js b/src/optimizer/transforms/char-class-classranges-merge-transform.js
index e3a2c63..2461c07 100644
--- a/src/optimizer/transforms/char-class-classranges-merge-transform.js
+++ b/src/optimizer/transforms/char-class-classranges-merge-transform.js
@@ -252,8 +252,8 @@ function combinesWithPrecedingClassRange(expression, classRange) {
       return true;
 
     } else if (
-      expression.type === 'Char' &&
-      !isNaN(expression.codePoint) &&
+      // We only want \w chars or char codes to keep readability
+      isMetaWCharOrCode(expression) &&
       classRange.to.codePoint === expression.codePoint - 1
     ) {
       // [a-de] -> [a-e]
@@ -291,8 +291,8 @@ function combinesWithFollowingClassRange(expression, classRange) {
     // there is only one case to handle
     // [ab-e] -> [a-e]
     if (
-      expression.type === 'Char' &&
-      !isNaN(expression.codePoint) &&
+      // We only want \w chars or char codes to keep readability
+      isMetaWCharOrCode(expression) &&
       classRange.from.codePoint === expression.codePoint + 1
     ) {
       classRange.from = expression;
@@ -325,6 +325,7 @@ function fitsInClassRange(expression, classRange) {
  * @returns {number} - Number of characters combined with expression
  */
 function charCombinesWithPrecedingChars(expression, index, expressions) {
+  // We only want \w chars or char codes to keep readability
   if (!isMetaWCharOrCode(expression)) {
     return 0;
   }


Test patch:
diff --git a/src/optimizer/transforms/__tests__/char-class-classranges-merge-transform-test.js b/src/optimizer/transforms/__tests__/char-class-classranges-merge-transform-test.js
index 327a429..520ffb4 100644
--- a/src/optimizer/transforms/__tests__/char-class-classranges-merge-transform-test.js
+++ b/src/optimizer/transforms/__tests__/char-class-classranges-merge-transform-test.js
@@ -73,7 +73,7 @@ describe('char-class-classranges-merge', () => {
     expect(re.toString()).toBe('/[\\S]/');
   });
 
-  it('merges chars in class ranges', () => {
+  it('merges \\w chars and char codes in class ranges', () => {
     let re = transform(/[fb-eg]/, [
       charClassClassrangesMerge,
     ]);
@@ -100,6 +100,13 @@ describe('char-class-classranges-merge', () => {
     expect(re.toString()).toBe('/[\\ud83d\\ude80-\\ud83d\\ude88]/u');
   });
 
+  it('does not merge non \\w chars into class ranges to keep readability', () => {
+    let re = transform(/[@A-E]/, [
+      charClassClassrangesMerge,
+    ]);
+    expect(re.toString()).toBe('/[@A-E]/');
+  });
+
   it('merges ranges together', () => {
     let re = transform(/[a-fc-u0-56-8]/, [
       charClassClassrangesMerge,
@@ -125,7 +132,7 @@ describe('char-class-classranges-merge', () => {
     expect(re.toString()).toBe('/[\\u{1F680}-\\ud83d\\ude9b]/u');
   });
 
-  it('combines sequential chars into class ranges', () => {
+  it('combines \\w sequential chars and char codes into class ranges', () => {
     let re = transform(/[facbdemlpqno]/, [
       charClassClassrangesMerge,
     ]);
@@ -152,7 +159,7 @@ describe('char-class-classranges-merge', () => {
     expect(re.toString()).toBe('/[\\ud83d\\ude88-\\ud83d\\ude8a]/u');
   });
 
-  it('does not combine sequential chars that are nor in \\w nor number-coded', () => {
+  it('does not combine sequential chars that are nor \\w chars nor char codes', () => {
     const re = transform(/[<=>?]/, [
       charClassClassrangesMerge,
     ]);