Repository: DmitrySoshnikov/regexp-tree
Pull request #187 (2019-08-26 21:43:02)

Implement IdentityEscape_U
Edit: rebased.


This PR includes commits from #186. So it is recommended to review #186 before this PR.



In this PR we correct the following behavior compared to master branch, per [IdentityEscape grammar](https://tc39.es/ecma262/#prod-IdentityEscape)

- `/\./u` should not throw syntax error
- `/\-/u` should throw syntax error


Patch:
diff --git a/src/parser/generated/regexp-tree.js b/src/parser/generated/regexp-tree.js
index b713acf..9c89579 100644
--- a/src/parser/generated/regexp-tree.js
+++ b/src/parser/generated/regexp-tree.js
@@ -343,7 +343,6 @@ let tokenizer;
 
 const lexRules = [[/^#[^\n]+/, function() { /* skip comments */ }],
 [/^\s+/, function() { /* skip whitespace */ }],
-[/^\\-/, function() { return 'ESC_CHAR' }],
 [/^-/, function() { return 'DASH' }],
 [/^\//, function() { return 'CHAR' }],
 [/^#/, function() { return 'CHAR' }],
@@ -372,6 +371,7 @@ const lexRules = [[/^#[^\n]+/, function() { /* skip comments */ }],
 [/^\\[tnrdDsSwWvf]/, function() { return 'META_CHAR' }],
 [/^\\\//, function() { return 'ESC_CHAR' }],
 [/^\\[ #]/, function() { return 'ESC_CHAR' }],
+[/^\\[\^\$\.\*\+\?\(\)\\\[\]\{\}\|\/]/, function() { return 'ESC_CHAR' }],
 [/^\\[^*?+\[()\\|]/, function() { 
                                       const s = this.getCurrentState();
                                       if (s === 'u' || s === 'xu' || s === 'u_class') {
@@ -379,7 +379,6 @@ const lexRules = [[/^#[^\n]+/, function() { /* skip comments */ }],
                                       }
                                       return 'ESC_CHAR';
                                      }],
-[/^\\[*?+\[()\\|]/, function() { return 'ESC_CHAR' }],
 [/^\(/, function() { return 'CHAR' }],
 [/^\)/, function() { return 'CHAR' }],
 [/^\(\?=/, function() { return 'POS_LA_ASSERT' }],
@@ -408,7 +407,7 @@ const lexRules = [[/^#[^\n]+/, function() { /* skip comments */ }],
 [/^[^*?+\[()\\|]/, function() { return 'CHAR' }],
 [/^\[\^/, function() {  const s = this.getCurrentState(); this.pushState(s === 'u' || s === 'xu' ? 'u_class' : 'class'); return 'NEG_CLASS'  }],
 [/^\[/, function() {  const s = this.getCurrentState(); this.pushState(s === 'u' || s === 'xu' ? 'u_class' : 'class'); return 'L_BRACKET'  }]];
-const lexRulesByConditions = {"INITIAL":[9,10,11,12,13,14,15,16,17,18,21,23,24,25,27,28,31,32,33,34,35,36,37,38,42,43,44,45,46,47,48,49,50,51,52],"u":[9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,27,28,31,32,33,34,35,36,37,38,42,43,44,45,46,47,48,49,50,51,52],"xu":[0,1,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,31,32,33,34,35,36,37,38,42,43,44,45,46,47,48,49,50,51,52],"x":[0,1,9,10,11,12,13,14,15,16,17,18,21,23,24,25,27,28,31,32,33,34,35,36,37,38,42,43,44,45,46,47,48,49,50,51,52],"u_class":[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52],"class":[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,21,23,24,25,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52]};
+const lexRulesByConditions = {"INITIAL":[8,9,10,11,12,13,14,15,16,17,20,22,23,24,26,27,30,31,32,33,34,35,36,37,41,42,43,44,45,46,47,48,49,50,51],"u":[8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,26,27,30,31,32,33,34,35,36,37,41,42,43,44,45,46,47,48,49,50,51],"xu":[0,1,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,30,31,32,33,34,35,36,37,41,42,43,44,45,46,47,48,49,50,51],"x":[0,1,8,9,10,11,12,13,14,15,16,17,20,22,23,24,26,27,30,31,32,33,34,35,36,37,41,42,43,44,45,46,47,48,49,50,51],"u_class":[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51],"class":[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,20,22,23,24,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51]};
 
 const EOF_TOKEN = {
   type: EOF,
@@ -1119,7 +1118,7 @@ function validateUnicodeGroupName(name, state) {
   if (isUnicodeName && !isUnicodeState) {
     throw new SyntaxError(`invalid group Unicode name "${name}", use \`u\` flag.`);
   }
-  
+
   return name;
 }
 
diff --git a/src/parser/regexp.bnf b/src/parser/regexp.bnf
index 452630c..3031fea 100644
--- a/src/parser/regexp.bnf
+++ b/src/parser/regexp.bnf
@@ -81,7 +81,6 @@ NAME                    \w+
 <x,xu>'#'[^\n]+                      /* skip comments */
 <x,xu>\s+                            /* skip whitespace */
 
-<class,u_class>{ESC}'-'              return 'ESC_CHAR'
 <class,u_class>'-'                   return 'DASH'
 <class,u_class>'/'                   return 'CHAR'
 <class,u_class>'#'                   return 'CHAR'
@@ -125,6 +124,8 @@ NAME                    \w+
 
 <xu>{ESC}[ #]                       return 'ESC_CHAR'
 
+{ESC}[\^\$\.\*\+\?\(\)\\\[\]\{\}\|\/]     return 'ESC_CHAR'
+
 {ESC}{CHAR}                         {
                                       const s = this.getCurrentState();
                                       if (s === 'u' || s === 'xu' || s === 'u_class') {
@@ -133,7 +134,6 @@ NAME                    \w+
                                       return 'ESC_CHAR';
                                     }
 
-{ESC}[*?+\[()\\|]                   return 'ESC_CHAR'
 
 <class,u_class>'('                  return 'CHAR'
 <class,u_class>')'                  return 'CHAR'
@@ -457,7 +457,7 @@ function validateUnicodeGroupName(name, state) {
   if (isUnicodeName && !isUnicodeState) {
     throw new SyntaxError(`invalid group Unicode name "${name}", use \`u\` flag.`);
   }
-  
+
   return name;
 }
 


Test patch:
diff --git a/src/parser/__tests__/parser-basic-test.js b/src/parser/__tests__/parser-basic-test.js
index 468653b..0f7e7d8 100644
--- a/src/parser/__tests__/parser-basic-test.js
+++ b/src/parser/__tests__/parser-basic-test.js
@@ -1042,5 +1042,10 @@ describe('basic', () => {
     expect(() => parser.parse('/\\p/u')).toThrowError(SyntaxError);
     expect(() => parser.parse('/\\e/u')).toThrowError(SyntaxError);
     expect(() => parser.parse('/\\g/u')).toThrowError(SyntaxError);
+    expect(() => parser.parse('/\\-/u')).toThrowError(SyntaxError);
+  });
+
+  it('should not throw when identity escape is a syntax character', () => {
+    expect(() => parser.parse('/\\./u')).not.toThrowError();
   });
 });