Permalink
Browse files

Support escaped "]" in regexp literal character classes (ex: "/[\]/]/")

https://code.google.com/p/esprima/issues/detail?id=442

The RegExp expression parser did not handle the escape sequence "\]" within
character classes, or any escape sequence for that matter. Once in a character
class, the parser consumed all characters until reaching "]".

This diff makes the parser always consider escape sequences, whether it is
currently inside of a character class or not. This more closely matches the
grammar, which looks like:

  RegularExpressionChar ::=
    RegularExpressionNonTerminator but not \ or / or [
    RegularExpressionBackslashSequence
    RegularExpressionClass

  ...

  RegularExpressionClassChar ::=
    RegularExpressionNonTerminator but not ] or \
    RegularExpressionBackslashSequence

http://es5.github.io/#x7.8.5

That is, the parser can derive RegularExpressionBackslashSequence both inside
and outside of character classes.

Test plan: Added test case to test.js and loaded it in Chrome 30 and Firefox 25.
These browsers produce different results for `new RegExp('[\\]/]').toString()`
so the test case dynamically computes that value.
  • Loading branch information...
1 parent c543522 commit c68864d6a2873679fe3ef686276130209a298866 James Ide committed Jul 31, 2013
Showing with 87 additions and 9 deletions.
  1. +9 −9 esprima.js
  2. +78 −0 test/test.js
View
@@ -1020,19 +1020,19 @@ parseStatement: true, parseSourceElement: true */
while (index < length) {
ch = source[index++];
str += ch;
- if (classMarker) {
+ if (ch === '\\') {
+ ch = source[index++];
+ // ECMA-262 7.8.5
+ if (isLineTerminator(ch.charCodeAt(0))) {
+ throwError({}, Messages.UnterminatedRegExp);
+ }
+ str += ch;
+ } else if (classMarker) {
if (ch === ']') {
classMarker = false;
}
} else {
- if (ch === '\\') {
- ch = source[index++];
- // ECMA-262 7.8.5
- if (isLineTerminator(ch.charCodeAt(0))) {
- throwError({}, Messages.UnterminatedRegExp);
- }
- str += ch;
- } else if (ch === '/') {
+ if (ch === '/') {
terminated = true;
break;
} else if (ch === '[') {
View
@@ -5119,6 +5119,84 @@ var testFixture = {
}]
},
+ 'var x = /[\\]/]/': {
+ type: 'Program',
+ body: [{
+ type: 'VariableDeclaration',
+ declarations: [{
+ type: 'VariableDeclarator',
+ id: {
+ type: 'Identifier',
+ name: 'x',
+ range: [4, 5],
+ loc: {
+ start: { line: 1, column: 4 },
+ end: { line: 1, column: 5 }
+ }
+ },
+ init: {
+ type: 'Literal',
+ value: new RegExp('[\\]/]').toString(),
+ raw: '/[\\]/]/',
+ range: [8, 15],
+ loc: {
+ start: { line: 1, column: 8 },
+ end: { line: 1, column: 15 }
+ }
+ },
+ range: [4, 15],
+ loc: {
+ start: { line: 1, column: 4 },
+ end: { line: 1, column: 15 }
+ }
+ }],
+ kind: 'var',
+ range: [0, 15],
+ loc: {
+ start: { line: 1, column: 0 },
+ end: { line: 1, column: 15 }
+ }
+ }],
+ range: [0, 15],
+ loc: {
+ start: { line: 1, column: 0 },
+ end: { line: 1, column: 15 }
+ },
+ tokens: [{
+ type: 'Keyword',
+ value: 'var',
+ range: [0, 3],
+ loc: {
+ start: { line: 1, column: 0 },
+ end: { line: 1, column: 3 }
+ }
+ }, {
+ type: 'Identifier',
+ value: 'x',
+ range: [4, 5],
+ loc: {
+ start: { line: 1, column: 4 },
+ end: { line: 1, column: 5 }
+ }
+ }, {
+ type: 'Punctuator',
+ value: '=',
+ range: [6, 7],
+ loc: {
+ start: { line: 1, column: 6 },
+ end: { line: 1, column: 7 }
+ }
+ }, {
+ type: 'RegularExpression',
+ value: '/[\\]/]/',
+ range: [8, 15],
+ loc: {
+ start: { line: 1, column: 8 },
+ end: { line: 1, column: 15 }
+ }
+ }]
+ },
+
'var x = /foo\\/bar/': {
type: 'Program',
body: [{

0 comments on commit c68864d

Please sign in to comment.