From 6047278f2703fc2e8d4b140046575e1debecf6b6 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 31 Oct 2024 00:53:15 -0400 Subject: [PATCH 1/6] fix: correct regexp slash escapes --- grammar.pegjs | 10 ++++++++-- parser.js | 10 ++++++++-- tests/fixtures/literalSlash.js | 10 ++++++++++ tests/queryAttribute.js | 29 +++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 tests/fixtures/literalSlash.js diff --git a/grammar.pegjs b/grammar.pegjs index a4410a1..cc87d31 100644 --- a/grammar.pegjs +++ b/grammar.pegjs @@ -14,6 +14,9 @@ } }); } + + // https://github.com/estools/esquery/issues/68 + input = input.replaceAll("\\/", "\\\\x2F"); } start @@ -97,8 +100,11 @@ attr path = i:identifierName { return { type: 'literal', value: i }; } type = "type(" _ t:[^ )]+ _ ")" { return { type: 'type', value: t.join('') }; } flags = [imsu]+ - regex = "/" d:[^/]+ "/" flgs:flags? { return { - type: 'regexp', value: new RegExp(d.join(''), flgs ? flgs.join('') : '') }; + regex = "/" d:[^/]+ "/" flgs:flags? { + const text = d.join('').replaceAll("\\\\x2F", "\\/"); + return { + type: 'regexp', value: new RegExp(text, flgs ? flgs.join('') : '') + }; } field = "." i:identifierName is:("." identifierName)* { diff --git a/parser.js b/parser.js index 7ebcabf..90552fa 100644 --- a/parser.js +++ b/parser.js @@ -250,8 +250,11 @@ peg$c75 = peg$literalExpectation("/", false), peg$c76 = /^[^\/]/, peg$c77 = peg$classExpectation(["/"], true, false), - peg$c78 = function(d, flgs) { return { - type: 'regexp', value: new RegExp(d.join(''), flgs ? flgs.join('') : '') }; + peg$c78 = function(d, flgs) { + const text = d.join('').replaceAll("\\\\x2F", "\\/"); + return { + type: 'regexp', value: new RegExp(text, flgs ? flgs.join('') : '') + }; }, peg$c79 = function(i, is) { return { type: 'field', name: is.reduce(function(memo, p){ return memo + p[0] + p[1]; }, i)}; @@ -2736,6 +2739,9 @@ }); } + // https://github.com/estools/esquery/issues/68 + input = input.replaceAll("\\/", "\\\\x2F"); + peg$result = peg$startRuleFunction(); diff --git a/tests/fixtures/literalSlash.js b/tests/fixtures/literalSlash.js new file mode 100644 index 0000000..a5ef922 --- /dev/null +++ b/tests/fixtures/literalSlash.js @@ -0,0 +1,10 @@ +import * as esprima from 'esprima'; + +const parsed = esprima.parse(` + var s1 = "foo/bar"; + var s2 = "foo//bar"; +`); + +export default parsed; + + diff --git a/tests/queryAttribute.js b/tests/queryAttribute.js index 6b5be0a..bb96e01 100644 --- a/tests/queryAttribute.js +++ b/tests/queryAttribute.js @@ -2,6 +2,7 @@ import esquery from '../esquery.js'; import literal from './fixtures/literal.js'; import conditional from './fixtures/conditional.js'; import forLoop from './fixtures/forLoop.js'; +import literalSlash from './fixtures/literalSlash.js'; import simpleFunction from './fixtures/simpleFunction.js'; import simpleProgram from './fixtures/simpleProgram.js'; @@ -160,6 +161,34 @@ describe('Attribute query', function () { ]); }); + it('single backslash-escaped slash in a regexp', function () { + const matches = esquery(literalSlash, '[value=/foo\\/bar/]'); + assert.includeMembers(matches, [ + literalSlash.body[0].declarations[0].init + ]); + }); + + it('single encoded slash in a regexp', function () { + const matches = esquery(literalSlash, '[value=/foo\\x2Fbar/]'); + assert.includeMembers(matches, [ + literalSlash.body[0].declarations[0].init + ]); + }); + + it('double backslash-escaped slash in a regexp', function () { + const matches = esquery(literalSlash, '[value=/foo\\/\\/bar/]'); + assert.includeMembers(matches, [ + literalSlash.body[1].declarations[0].init + ]); + }); + + it('double backslash-escaped slash in a regexp', function () { + const matches = esquery(literalSlash, '[value=/foo\\x2F\\x2Fbar/]'); + assert.includeMembers(matches, [ + literalSlash.body[1].declarations[0].init + ]); + }); + it('multiple regexp flags (i and u)', function () { const matches = esquery(simpleProgram, '[name=/\\u{61}|[SDFY]/iu]'); assert.includeMembers(matches, [ From 7d99050559bf5b9c53d9507d133cce3dfd649422 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 31 Oct 2024 01:20:56 -0400 Subject: [PATCH 2/6] Failing test cases for non-regex slashes --- tests/fixtures/literalSlash.js | 3 +++ tests/queryAttribute.js | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/tests/fixtures/literalSlash.js b/tests/fixtures/literalSlash.js index a5ef922..effce87 100644 --- a/tests/fixtures/literalSlash.js +++ b/tests/fixtures/literalSlash.js @@ -3,6 +3,9 @@ import * as esprima from 'esprima'; const parsed = esprima.parse(` var s1 = "foo/bar"; var s2 = "foo//bar"; + + var b1 = "foo\\/bar"; + var b2 = "foo\\/\\/bar"; `); export default parsed; diff --git a/tests/queryAttribute.js b/tests/queryAttribute.js index bb96e01..55fb2b0 100644 --- a/tests/queryAttribute.js +++ b/tests/queryAttribute.js @@ -161,6 +161,13 @@ describe('Attribute query', function () { ]); }); + it('single backslash-escaped slash in a literal', function () { + const matches = esquery(literalSlash, '[value="foo\\/bar"]'); + assert.includeMembers(matches, [ + literalSlash.body[2].declarations[0].init + ]); + }); + it('single backslash-escaped slash in a regexp', function () { const matches = esquery(literalSlash, '[value=/foo\\/bar/]'); assert.includeMembers(matches, [ @@ -175,6 +182,13 @@ describe('Attribute query', function () { ]); }); + it('single backslash-escaped slash in a literal', function () { + const matches = esquery(literalSlash, '[value="foo\\/\\/bar"]'); + assert.includeMembers(matches, [ + literalSlash.body[3].declarations[0].init + ]); + }); + it('double backslash-escaped slash in a regexp', function () { const matches = esquery(literalSlash, '[value=/foo\\/\\/bar/]'); assert.includeMembers(matches, [ From d6092de2023533383bfa22e1166363660783476c Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 4 Nov 2024 14:25:49 -0500 Subject: [PATCH 3/6] account for slashes outside of regex --- grammar.pegjs | 5 ++++- parser.js | 5 ++++- tests/queryAttribute.js | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/grammar.pegjs b/grammar.pegjs index cc87d31..7e2c865 100644 --- a/grammar.pegjs +++ b/grammar.pegjs @@ -16,7 +16,9 @@ } // https://github.com/estools/esquery/issues/68 - input = input.replaceAll("\\/", "\\\\x2F"); + input = input.replaceAll(/\/((?:[^\/\\]|\\.)*?)\//g, (match) => { + return match.replaceAll("\\/", "\\\\x2F"); + }); } start @@ -101,6 +103,7 @@ attr type = "type(" _ t:[^ )]+ _ ")" { return { type: 'type', value: t.join('') }; } flags = [imsu]+ regex = "/" d:[^/]+ "/" flgs:flags? { + // https://github.com/estools/esquery/issues/68 const text = d.join('').replaceAll("\\\\x2F", "\\/"); return { type: 'regexp', value: new RegExp(text, flgs ? flgs.join('') : '') diff --git a/parser.js b/parser.js index 90552fa..6009be0 100644 --- a/parser.js +++ b/parser.js @@ -251,6 +251,7 @@ peg$c76 = /^[^\/]/, peg$c77 = peg$classExpectation(["/"], true, false), peg$c78 = function(d, flgs) { + // https://github.com/estools/esquery/issues/68 const text = d.join('').replaceAll("\\\\x2F", "\\/"); return { type: 'regexp', value: new RegExp(text, flgs ? flgs.join('') : '') @@ -2740,7 +2741,9 @@ } // https://github.com/estools/esquery/issues/68 - input = input.replaceAll("\\/", "\\\\x2F"); + input = input.replaceAll(/\/((?:[^\/\\]|\\.)*?)\//g, (match) => { + return match.replaceAll("\\/", "\\\\x2F"); + }); peg$result = peg$startRuleFunction(); diff --git a/tests/queryAttribute.js b/tests/queryAttribute.js index 55fb2b0..2dc2d29 100644 --- a/tests/queryAttribute.js +++ b/tests/queryAttribute.js @@ -182,7 +182,7 @@ describe('Attribute query', function () { ]); }); - it('single backslash-escaped slash in a literal', function () { + it('double backslash-escaped slash in a literal', function () { const matches = esquery(literalSlash, '[value="foo\\/\\/bar"]'); assert.includeMembers(matches, [ literalSlash.body[3].declarations[0].init From 8af2dfb447789238bb638386e25cea52178d894d Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 4 Nov 2024 14:28:50 -0500 Subject: [PATCH 4/6] A bit of docs --- grammar.pegjs | 1 + parser.js | 1 + 2 files changed, 2 insertions(+) diff --git a/grammar.pegjs b/grammar.pegjs index 7e2c865..b9db0d5 100644 --- a/grammar.pegjs +++ b/grammar.pegjs @@ -16,6 +16,7 @@ } // https://github.com/estools/esquery/issues/68 + // Inside all /regexp/ literals, we replace escaped-backslashes with the \x2F equivalent. input = input.replaceAll(/\/((?:[^\/\\]|\\.)*?)\//g, (match) => { return match.replaceAll("\\/", "\\\\x2F"); }); diff --git a/parser.js b/parser.js index 6009be0..69c9853 100644 --- a/parser.js +++ b/parser.js @@ -2741,6 +2741,7 @@ } // https://github.com/estools/esquery/issues/68 + // Inside all /regexp/ literals, we replace escaped-backslashes with the \x2F equivalent. input = input.replaceAll(/\/((?:[^\/\\]|\\.)*?)\//g, (match) => { return match.replaceAll("\\/", "\\\\x2F"); }); From 0ace1a36888c01d9e87356ce1d0183d7dff41251 Mon Sep 17 00:00:00 2001 From: Michael Ficarra Date: Tue, 30 Dec 2025 16:34:14 -0700 Subject: [PATCH 5/6] better fix --- grammar.pegjs | 19 +- parser.js | 488 +++++++++++++++++++++++++-------- tests/fixtures/literalSlash.js | 1 - tests/queryAttribute.js | 28 +- 4 files changed, 393 insertions(+), 143 deletions(-) diff --git a/grammar.pegjs b/grammar.pegjs index b9db0d5..b396a31 100644 --- a/grammar.pegjs +++ b/grammar.pegjs @@ -14,12 +14,6 @@ } }); } - - // https://github.com/estools/esquery/issues/68 - // Inside all /regexp/ literals, we replace escaped-backslashes with the \x2F equivalent. - input = input.replaceAll(/\/((?:[^\/\\]|\\.)*?)\//g, (match) => { - return match.replaceAll("\\/", "\\\\x2F"); - }); } start @@ -103,13 +97,18 @@ attr path = i:identifierName { return { type: 'literal', value: i }; } type = "type(" _ t:[^ )]+ _ ")" { return { type: 'type', value: t.join('') }; } flags = [imsu]+ - regex = "/" d:[^/]+ "/" flgs:flags? { - // https://github.com/estools/esquery/issues/68 - const text = d.join('').replaceAll("\\\\x2F", "\\/"); + regex = "/" pattern:(regex_cc / regex_hex_escape / regex_single_char_escape / regex_chars)+ "/" flgs:flags? { return { - type: 'regexp', value: new RegExp(text, flgs ? flgs.join('') : '') + type: 'regexp', value: new RegExp(pattern.join(''), flgs ? flgs.join('') : '') }; } + regex_cc = "[" cs:([^\]\\] / regex_hex_escape / regex_single_char_escape)+ "]" { return '[' + cs.join('') + ']'; } + regex_hex_escape = "\\x" a:[A-Fa-f0-9] b:[A-Fa-f0-9] { return '\\x' + a + b; } + regex_single_char_escape = "\\" a:. { + let hex = a.charCodeAt(0).toString(16); + return '\\x' + (hex.length > 1 ? hex : '0' + hex) ; + } + regex_chars = cs:[^/\\\[]+ { return cs.join(''); } field = "." i:identifierName is:("." identifierName)* { return { type: 'field', name: is.reduce(function(memo, p){ return memo + p[0] + p[1]; }, i)}; diff --git a/parser.js b/parser.js index 69c9853..62586d9 100644 --- a/parser.js +++ b/parser.js @@ -248,44 +248,55 @@ peg$c73 = peg$classExpectation(["i", "m", "s", "u"], false, false), peg$c74 = "/", peg$c75 = peg$literalExpectation("/", false), - peg$c76 = /^[^\/]/, - peg$c77 = peg$classExpectation(["/"], true, false), - peg$c78 = function(d, flgs) { - // https://github.com/estools/esquery/issues/68 - const text = d.join('').replaceAll("\\\\x2F", "\\/"); + peg$c76 = function(pattern, flgs) { return { - type: 'regexp', value: new RegExp(text, flgs ? flgs.join('') : '') + type: 'regexp', value: new RegExp(pattern.join(''), flgs ? flgs.join('') : '') }; }, - peg$c79 = function(i, is) { + peg$c77 = /^[^\]\\]/, + peg$c78 = peg$classExpectation(["]", "\\"], true, false), + peg$c79 = function(cs) { return '[' + cs.join('') + ']'; }, + peg$c80 = "\\x", + peg$c81 = peg$literalExpectation("\\x", false), + peg$c82 = /^[A-Fa-f0-9]/, + peg$c83 = peg$classExpectation([["A", "F"], ["a", "f"], ["0", "9"]], false, false), + peg$c84 = function(a, b) { return '\\x' + a + b; }, + peg$c85 = function(a) { + let hex = a.charCodeAt(0).toString(16); + return '\\x' + (hex.length > 1 ? hex : '0' + hex) ; + }, + peg$c86 = /^[^\/\\[]/, + peg$c87 = peg$classExpectation(["/", "\\", "["], true, false), + peg$c88 = function(cs) { return cs.join(''); }, + peg$c89 = function(i, is) { return { type: 'field', name: is.reduce(function(memo, p){ return memo + p[0] + p[1]; }, i)}; }, - peg$c80 = ":not(", - peg$c81 = peg$literalExpectation(":not(", false), - peg$c82 = function(ss) { return { type: 'not', selectors: ss }; }, - peg$c83 = ":matches(", - peg$c84 = peg$literalExpectation(":matches(", false), - peg$c85 = function(ss) { return { type: 'matches', selectors: ss }; }, - peg$c86 = ":is(", - peg$c87 = peg$literalExpectation(":is(", false), - peg$c88 = ":has(", - peg$c89 = peg$literalExpectation(":has(", false), - peg$c90 = function(ss) { return { type: 'has', selectors: ss }; }, - peg$c91 = ":first-child", - peg$c92 = peg$literalExpectation(":first-child", false), - peg$c93 = function() { return nth(1); }, - peg$c94 = ":last-child", - peg$c95 = peg$literalExpectation(":last-child", false), - peg$c96 = function() { return nthLast(1); }, - peg$c97 = ":nth-child(", - peg$c98 = peg$literalExpectation(":nth-child(", false), - peg$c99 = function(n) { return nth(parseInt(n.join(''), 10)); }, - peg$c100 = ":nth-last-child(", - peg$c101 = peg$literalExpectation(":nth-last-child(", false), - peg$c102 = function(n) { return nthLast(parseInt(n.join(''), 10)); }, - peg$c103 = ":", - peg$c104 = peg$literalExpectation(":", false), - peg$c105 = function(c) { + peg$c90 = ":not(", + peg$c91 = peg$literalExpectation(":not(", false), + peg$c92 = function(ss) { return { type: 'not', selectors: ss }; }, + peg$c93 = ":matches(", + peg$c94 = peg$literalExpectation(":matches(", false), + peg$c95 = function(ss) { return { type: 'matches', selectors: ss }; }, + peg$c96 = ":is(", + peg$c97 = peg$literalExpectation(":is(", false), + peg$c98 = ":has(", + peg$c99 = peg$literalExpectation(":has(", false), + peg$c100 = function(ss) { return { type: 'has', selectors: ss }; }, + peg$c101 = ":first-child", + peg$c102 = peg$literalExpectation(":first-child", false), + peg$c103 = function() { return nth(1); }, + peg$c104 = ":last-child", + peg$c105 = peg$literalExpectation(":last-child", false), + peg$c106 = function() { return nthLast(1); }, + peg$c107 = ":nth-child(", + peg$c108 = peg$literalExpectation(":nth-child(", false), + peg$c109 = function(n) { return nth(parseInt(n.join(''), 10)); }, + peg$c110 = ":nth-last-child(", + peg$c111 = peg$literalExpectation(":nth-last-child(", false), + peg$c112 = function(n) { return nthLast(parseInt(n.join(''), 10)); }, + peg$c113 = ":", + peg$c114 = peg$literalExpectation(":", false), + peg$c115 = function(c) { return { type: 'class', name: c }; }, @@ -430,7 +441,7 @@ function peg$parsestart() { var s0, s1, s2, s3; - var key = peg$currPos * 33 + 0, + var key = peg$currPos * 37 + 0, cached = peg$resultsCache[key]; if (cached) { @@ -479,7 +490,7 @@ function peg$parse_() { var s0, s1; - var key = peg$currPos * 33 + 1, + var key = peg$currPos * 37 + 1, cached = peg$resultsCache[key]; if (cached) { @@ -515,7 +526,7 @@ function peg$parseidentifierName() { var s0, s1, s2; - var key = peg$currPos * 33 + 2, + var key = peg$currPos * 37 + 2, cached = peg$resultsCache[key]; if (cached) { @@ -561,7 +572,7 @@ function peg$parsebinaryOp() { var s0, s1, s2, s3; - var key = peg$currPos * 33 + 3, + var key = peg$currPos * 37 + 3, cached = peg$resultsCache[key]; if (cached) { @@ -691,7 +702,7 @@ function peg$parsehasSelectors() { var s0, s1, s2, s3, s4, s5, s6, s7; - var key = peg$currPos * 33 + 4, + var key = peg$currPos * 37 + 4, cached = peg$resultsCache[key]; if (cached) { @@ -794,7 +805,7 @@ function peg$parseselectors() { var s0, s1, s2, s3, s4, s5, s6, s7; - var key = peg$currPos * 33 + 5, + var key = peg$currPos * 37 + 5, cached = peg$resultsCache[key]; if (cached) { @@ -897,7 +908,7 @@ function peg$parsehasSelector() { var s0, s1, s2; - var key = peg$currPos * 33 + 6, + var key = peg$currPos * 37 + 6, cached = peg$resultsCache[key]; if (cached) { @@ -934,7 +945,7 @@ function peg$parseselector() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 33 + 7, + var key = peg$currPos * 37 + 7, cached = peg$resultsCache[key]; if (cached) { @@ -1001,7 +1012,7 @@ function peg$parsesequence() { var s0, s1, s2, s3; - var key = peg$currPos * 33 + 8, + var key = peg$currPos * 37 + 8, cached = peg$resultsCache[key]; if (cached) { @@ -1053,7 +1064,7 @@ function peg$parseatom() { var s0; - var key = peg$currPos * 33 + 9, + var key = peg$currPos * 37 + 9, cached = peg$resultsCache[key]; if (cached) { @@ -1108,7 +1119,7 @@ function peg$parsewildcard() { var s0, s1; - var key = peg$currPos * 33 + 10, + var key = peg$currPos * 37 + 10, cached = peg$resultsCache[key]; if (cached) { @@ -1139,7 +1150,7 @@ function peg$parseidentifier() { var s0, s1, s2; - var key = peg$currPos * 33 + 11, + var key = peg$currPos * 37 + 11, cached = peg$resultsCache[key]; if (cached) { @@ -1182,7 +1193,7 @@ function peg$parseattr() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 33 + 12, + var key = peg$currPos * 37 + 12, cached = peg$resultsCache[key]; if (cached) { @@ -1246,7 +1257,7 @@ function peg$parseattrOps() { var s0, s1, s2; - var key = peg$currPos * 33 + 13, + var key = peg$currPos * 37 + 13, cached = peg$resultsCache[key]; if (cached) { @@ -1304,7 +1315,7 @@ function peg$parseattrEqOps() { var s0, s1, s2; - var key = peg$currPos * 33 + 14, + var key = peg$currPos * 37 + 14, cached = peg$resultsCache[key]; if (cached) { @@ -1353,7 +1364,7 @@ function peg$parseattrName() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 33 + 15, + var key = peg$currPos * 37 + 15, cached = peg$resultsCache[key]; if (cached) { @@ -1432,7 +1443,7 @@ function peg$parseattrValue() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 33 + 16, + var key = peg$currPos * 37 + 16, cached = peg$resultsCache[key]; if (cached) { @@ -1538,7 +1549,7 @@ function peg$parsestring() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 33 + 17, + var key = peg$currPos * 37 + 17, cached = peg$resultsCache[key]; if (cached) { @@ -1780,7 +1791,7 @@ function peg$parsenumber() { var s0, s1, s2, s3; - var key = peg$currPos * 33 + 18, + var key = peg$currPos * 37 + 18, cached = peg$resultsCache[key]; if (cached) { @@ -1875,7 +1886,7 @@ function peg$parsepath() { var s0, s1; - var key = peg$currPos * 33 + 19, + var key = peg$currPos * 37 + 19, cached = peg$resultsCache[key]; if (cached) { @@ -1900,7 +1911,7 @@ function peg$parsetype() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 33 + 20, + var key = peg$currPos * 37 + 20, cached = peg$resultsCache[key]; if (cached) { @@ -1985,7 +1996,7 @@ function peg$parseflags() { var s0, s1; - var key = peg$currPos * 33 + 21, + var key = peg$currPos * 37 + 21, cached = peg$resultsCache[key]; if (cached) { @@ -2025,7 +2036,7 @@ function peg$parseregex() { var s0, s1, s2, s3, s4; - var key = peg$currPos * 33 + 22, + var key = peg$currPos * 37 + 22, cached = peg$resultsCache[key]; if (cached) { @@ -2044,22 +2055,28 @@ } if (s1 !== peg$FAILED) { s2 = []; - if (peg$c76.test(input.charAt(peg$currPos))) { - s3 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c77); } + s3 = peg$parseregex_cc(); + if (s3 === peg$FAILED) { + s3 = peg$parseregex_hex_escape(); + if (s3 === peg$FAILED) { + s3 = peg$parseregex_single_char_escape(); + if (s3 === peg$FAILED) { + s3 = peg$parseregex_chars(); + } + } } if (s3 !== peg$FAILED) { while (s3 !== peg$FAILED) { s2.push(s3); - if (peg$c76.test(input.charAt(peg$currPos))) { - s3 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c77); } + s3 = peg$parseregex_cc(); + if (s3 === peg$FAILED) { + s3 = peg$parseregex_hex_escape(); + if (s3 === peg$FAILED) { + s3 = peg$parseregex_single_char_escape(); + if (s3 === peg$FAILED) { + s3 = peg$parseregex_chars(); + } + } } } } else { @@ -2080,7 +2097,7 @@ } if (s4 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c78(s2, s4); + s1 = peg$c76(s2, s4); s0 = s1; } else { peg$currPos = s0; @@ -2104,10 +2121,245 @@ return s0; } + function peg$parseregex_cc() { + var s0, s1, s2, s3; + + var key = peg$currPos * 37 + 23, + cached = peg$resultsCache[key]; + + if (cached) { + peg$currPos = cached.nextPos; + + return cached.result; + } + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 91) { + s1 = peg$c31; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c32); } + } + if (s1 !== peg$FAILED) { + s2 = []; + if (peg$c77.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c78); } + } + if (s3 === peg$FAILED) { + s3 = peg$parseregex_hex_escape(); + if (s3 === peg$FAILED) { + s3 = peg$parseregex_single_char_escape(); + } + } + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + if (peg$c77.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c78); } + } + if (s3 === peg$FAILED) { + s3 = peg$parseregex_hex_escape(); + if (s3 === peg$FAILED) { + s3 = peg$parseregex_single_char_escape(); + } + } + } + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 93) { + s3 = peg$c33; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c34); } + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c79(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; + + return s0; + } + + function peg$parseregex_hex_escape() { + var s0, s1, s2, s3; + + var key = peg$currPos * 37 + 24, + cached = peg$resultsCache[key]; + + if (cached) { + peg$currPos = cached.nextPos; + + return cached.result; + } + + s0 = peg$currPos; + if (input.substr(peg$currPos, 2) === peg$c80) { + s1 = peg$c80; + peg$currPos += 2; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c81); } + } + if (s1 !== peg$FAILED) { + if (peg$c82.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c83); } + } + if (s2 !== peg$FAILED) { + if (peg$c82.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c83); } + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c84(s2, s3); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; + + return s0; + } + + function peg$parseregex_single_char_escape() { + var s0, s1, s2; + + var key = peg$currPos * 37 + 25, + cached = peg$resultsCache[key]; + + if (cached) { + peg$currPos = cached.nextPos; + + return cached.result; + } + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 92) { + s1 = peg$c52; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c53); } + } + if (s1 !== peg$FAILED) { + if (input.length > peg$currPos) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c54); } + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c85(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; + + return s0; + } + + function peg$parseregex_chars() { + var s0, s1, s2; + + var key = peg$currPos * 37 + 26, + cached = peg$resultsCache[key]; + + if (cached) { + peg$currPos = cached.nextPos; + + return cached.result; + } + + s0 = peg$currPos; + s1 = []; + if (peg$c86.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c87); } + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + if (peg$c86.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c87); } + } + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c88(s1); + } + s0 = s1; + + peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; + + return s0; + } + function peg$parsefield() { var s0, s1, s2, s3, s4, s5, s6; - var key = peg$currPos * 33 + 23, + var key = peg$currPos * 37 + 27, cached = peg$resultsCache[key]; if (cached) { @@ -2175,7 +2427,7 @@ } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c79(s2, s3); + s1 = peg$c89(s2, s3); s0 = s1; } else { peg$currPos = s0; @@ -2198,7 +2450,7 @@ function peg$parsenegation() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 33 + 24, + var key = peg$currPos * 37 + 28, cached = peg$resultsCache[key]; if (cached) { @@ -2208,12 +2460,12 @@ } s0 = peg$currPos; - if (input.substr(peg$currPos, 5) === peg$c80) { - s1 = peg$c80; + if (input.substr(peg$currPos, 5) === peg$c90) { + s1 = peg$c90; peg$currPos += 5; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c81); } + if (peg$silentFails === 0) { peg$fail(peg$c91); } } if (s1 !== peg$FAILED) { s2 = peg$parse_(); @@ -2231,7 +2483,7 @@ } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c82(s3); + s1 = peg$c92(s3); s0 = s1; } else { peg$currPos = s0; @@ -2262,7 +2514,7 @@ function peg$parsematches() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 33 + 25, + var key = peg$currPos * 37 + 29, cached = peg$resultsCache[key]; if (cached) { @@ -2272,12 +2524,12 @@ } s0 = peg$currPos; - if (input.substr(peg$currPos, 9) === peg$c83) { - s1 = peg$c83; + if (input.substr(peg$currPos, 9) === peg$c93) { + s1 = peg$c93; peg$currPos += 9; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c84); } + if (peg$silentFails === 0) { peg$fail(peg$c94); } } if (s1 !== peg$FAILED) { s2 = peg$parse_(); @@ -2295,7 +2547,7 @@ } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c85(s3); + s1 = peg$c95(s3); s0 = s1; } else { peg$currPos = s0; @@ -2326,7 +2578,7 @@ function peg$parseis() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 33 + 26, + var key = peg$currPos * 37 + 30, cached = peg$resultsCache[key]; if (cached) { @@ -2336,12 +2588,12 @@ } s0 = peg$currPos; - if (input.substr(peg$currPos, 4) === peg$c86) { - s1 = peg$c86; + if (input.substr(peg$currPos, 4) === peg$c96) { + s1 = peg$c96; peg$currPos += 4; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c87); } + if (peg$silentFails === 0) { peg$fail(peg$c97); } } if (s1 !== peg$FAILED) { s2 = peg$parse_(); @@ -2359,7 +2611,7 @@ } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c85(s3); + s1 = peg$c95(s3); s0 = s1; } else { peg$currPos = s0; @@ -2390,7 +2642,7 @@ function peg$parsehas() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 33 + 27, + var key = peg$currPos * 37 + 31, cached = peg$resultsCache[key]; if (cached) { @@ -2400,12 +2652,12 @@ } s0 = peg$currPos; - if (input.substr(peg$currPos, 5) === peg$c88) { - s1 = peg$c88; + if (input.substr(peg$currPos, 5) === peg$c98) { + s1 = peg$c98; peg$currPos += 5; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c89); } + if (peg$silentFails === 0) { peg$fail(peg$c99); } } if (s1 !== peg$FAILED) { s2 = peg$parse_(); @@ -2423,7 +2675,7 @@ } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c90(s3); + s1 = peg$c100(s3); s0 = s1; } else { peg$currPos = s0; @@ -2454,7 +2706,7 @@ function peg$parsefirstChild() { var s0, s1; - var key = peg$currPos * 33 + 28, + var key = peg$currPos * 37 + 32, cached = peg$resultsCache[key]; if (cached) { @@ -2464,16 +2716,16 @@ } s0 = peg$currPos; - if (input.substr(peg$currPos, 12) === peg$c91) { - s1 = peg$c91; + if (input.substr(peg$currPos, 12) === peg$c101) { + s1 = peg$c101; peg$currPos += 12; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c92); } + if (peg$silentFails === 0) { peg$fail(peg$c102); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c93(); + s1 = peg$c103(); } s0 = s1; @@ -2485,7 +2737,7 @@ function peg$parselastChild() { var s0, s1; - var key = peg$currPos * 33 + 29, + var key = peg$currPos * 37 + 33, cached = peg$resultsCache[key]; if (cached) { @@ -2495,16 +2747,16 @@ } s0 = peg$currPos; - if (input.substr(peg$currPos, 11) === peg$c94) { - s1 = peg$c94; + if (input.substr(peg$currPos, 11) === peg$c104) { + s1 = peg$c104; peg$currPos += 11; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c95); } + if (peg$silentFails === 0) { peg$fail(peg$c105); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c96(); + s1 = peg$c106(); } s0 = s1; @@ -2516,7 +2768,7 @@ function peg$parsenthChild() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 33 + 30, + var key = peg$currPos * 37 + 34, cached = peg$resultsCache[key]; if (cached) { @@ -2526,12 +2778,12 @@ } s0 = peg$currPos; - if (input.substr(peg$currPos, 11) === peg$c97) { - s1 = peg$c97; + if (input.substr(peg$currPos, 11) === peg$c107) { + s1 = peg$c107; peg$currPos += 11; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c98); } + if (peg$silentFails === 0) { peg$fail(peg$c108); } } if (s1 !== peg$FAILED) { s2 = peg$parse_(); @@ -2570,7 +2822,7 @@ } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c99(s3); + s1 = peg$c109(s3); s0 = s1; } else { peg$currPos = s0; @@ -2601,7 +2853,7 @@ function peg$parsenthLastChild() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 33 + 31, + var key = peg$currPos * 37 + 35, cached = peg$resultsCache[key]; if (cached) { @@ -2611,12 +2863,12 @@ } s0 = peg$currPos; - if (input.substr(peg$currPos, 16) === peg$c100) { - s1 = peg$c100; + if (input.substr(peg$currPos, 16) === peg$c110) { + s1 = peg$c110; peg$currPos += 16; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c101); } + if (peg$silentFails === 0) { peg$fail(peg$c111); } } if (s1 !== peg$FAILED) { s2 = peg$parse_(); @@ -2655,7 +2907,7 @@ } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c102(s3); + s1 = peg$c112(s3); s0 = s1; } else { peg$currPos = s0; @@ -2686,7 +2938,7 @@ function peg$parseclass() { var s0, s1, s2; - var key = peg$currPos * 33 + 32, + var key = peg$currPos * 37 + 36, cached = peg$resultsCache[key]; if (cached) { @@ -2697,17 +2949,17 @@ s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 58) { - s1 = peg$c103; + s1 = peg$c113; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c104); } + if (peg$silentFails === 0) { peg$fail(peg$c114); } } if (s1 !== peg$FAILED) { s2 = peg$parseidentifierName(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c105(s2); + s1 = peg$c115(s2); s0 = s1; } else { peg$currPos = s0; @@ -2740,12 +2992,6 @@ }); } - // https://github.com/estools/esquery/issues/68 - // Inside all /regexp/ literals, we replace escaped-backslashes with the \x2F equivalent. - input = input.replaceAll(/\/((?:[^\/\\]|\\.)*?)\//g, (match) => { - return match.replaceAll("\\/", "\\\\x2F"); - }); - peg$result = peg$startRuleFunction(); diff --git a/tests/fixtures/literalSlash.js b/tests/fixtures/literalSlash.js index effce87..52c9d18 100644 --- a/tests/fixtures/literalSlash.js +++ b/tests/fixtures/literalSlash.js @@ -10,4 +10,3 @@ const parsed = esprima.parse(` export default parsed; - diff --git a/tests/queryAttribute.js b/tests/queryAttribute.js index 2dc2d29..f4a39e8 100644 --- a/tests/queryAttribute.js +++ b/tests/queryAttribute.js @@ -163,43 +163,49 @@ describe('Attribute query', function () { it('single backslash-escaped slash in a literal', function () { const matches = esquery(literalSlash, '[value="foo\\/bar"]'); - assert.includeMembers(matches, [ + assert.sameMembers(matches, [ + literalSlash.body[0].declarations[0].init, literalSlash.body[2].declarations[0].init ]); }); - it('single backslash-escaped slash in a regexp', function () { + it('single backslash-escaped slash in a literal', function () { const matches = esquery(literalSlash, '[value=/foo\\/bar/]'); - assert.includeMembers(matches, [ - literalSlash.body[0].declarations[0].init + assert.sameMembers(matches, [ + literalSlash.body[0].declarations[0].init, + literalSlash.body[2].declarations[0].init ]); }); it('single encoded slash in a regexp', function () { const matches = esquery(literalSlash, '[value=/foo\\x2Fbar/]'); - assert.includeMembers(matches, [ - literalSlash.body[0].declarations[0].init + assert.sameMembers(matches, [ + literalSlash.body[0].declarations[0].init, + literalSlash.body[2].declarations[0].init ]); }); it('double backslash-escaped slash in a literal', function () { const matches = esquery(literalSlash, '[value="foo\\/\\/bar"]'); - assert.includeMembers(matches, [ + assert.sameMembers(matches, [ + literalSlash.body[1].declarations[0].init, literalSlash.body[3].declarations[0].init ]); }); it('double backslash-escaped slash in a regexp', function () { const matches = esquery(literalSlash, '[value=/foo\\/\\/bar/]'); - assert.includeMembers(matches, [ - literalSlash.body[1].declarations[0].init + assert.sameMembers(matches, [ + literalSlash.body[1].declarations[0].init, + literalSlash.body[3].declarations[0].init ]); }); it('double backslash-escaped slash in a regexp', function () { const matches = esquery(literalSlash, '[value=/foo\\x2F\\x2Fbar/]'); - assert.includeMembers(matches, [ - literalSlash.body[1].declarations[0].init + assert.sameMembers(matches, [ + literalSlash.body[1].declarations[0].init, + literalSlash.body[3].declarations[0].init ]); }); From 261e905d4ff96d21b6bc0f7577276703426f8298 Mon Sep 17 00:00:00 2001 From: Michael Ficarra Date: Wed, 31 Dec 2025 08:23:47 -0700 Subject: [PATCH 6/6] simplify and add more tests --- grammar.pegjs | 12 +- parser.js | 318 +++++++++++++++------------------------- tests/queryAttribute.js | 36 ++++- 3 files changed, 158 insertions(+), 208 deletions(-) diff --git a/grammar.pegjs b/grammar.pegjs index b396a31..7d6639f 100644 --- a/grammar.pegjs +++ b/grammar.pegjs @@ -97,18 +97,14 @@ attr path = i:identifierName { return { type: 'literal', value: i }; } type = "type(" _ t:[^ )]+ _ ")" { return { type: 'type', value: t.join('') }; } flags = [imsu]+ - regex = "/" pattern:(regex_cc / regex_hex_escape / regex_single_char_escape / regex_chars)+ "/" flgs:flags? { + regex = "/" pattern:(re_character_class / re_escape / re_chars)+ "/" flgs:flags? { return { type: 'regexp', value: new RegExp(pattern.join(''), flgs ? flgs.join('') : '') }; } - regex_cc = "[" cs:([^\]\\] / regex_hex_escape / regex_single_char_escape)+ "]" { return '[' + cs.join('') + ']'; } - regex_hex_escape = "\\x" a:[A-Fa-f0-9] b:[A-Fa-f0-9] { return '\\x' + a + b; } - regex_single_char_escape = "\\" a:. { - let hex = a.charCodeAt(0).toString(16); - return '\\x' + (hex.length > 1 ? hex : '0' + hex) ; - } - regex_chars = cs:[^/\\\[]+ { return cs.join(''); } + re_character_class = "[" cs:([^\]\\] / re_escape)+ "]" { return '[' + cs.join('') + ']'; } + re_escape = "\\" a:. { return '\\' + a; } + re_chars = cs:[^/\\[]+ { return cs.join(''); } field = "." i:identifierName is:("." identifierName)* { return { type: 'field', name: is.reduce(function(memo, p){ return memo + p[0] + p[1]; }, i)}; diff --git a/parser.js b/parser.js index 62586d9..c6fd741 100644 --- a/parser.js +++ b/parser.js @@ -256,47 +256,39 @@ peg$c77 = /^[^\]\\]/, peg$c78 = peg$classExpectation(["]", "\\"], true, false), peg$c79 = function(cs) { return '[' + cs.join('') + ']'; }, - peg$c80 = "\\x", - peg$c81 = peg$literalExpectation("\\x", false), - peg$c82 = /^[A-Fa-f0-9]/, - peg$c83 = peg$classExpectation([["A", "F"], ["a", "f"], ["0", "9"]], false, false), - peg$c84 = function(a, b) { return '\\x' + a + b; }, - peg$c85 = function(a) { - let hex = a.charCodeAt(0).toString(16); - return '\\x' + (hex.length > 1 ? hex : '0' + hex) ; - }, - peg$c86 = /^[^\/\\[]/, - peg$c87 = peg$classExpectation(["/", "\\", "["], true, false), - peg$c88 = function(cs) { return cs.join(''); }, - peg$c89 = function(i, is) { + peg$c80 = function(a) { return '\\' + a; }, + peg$c81 = /^[^\/\\[]/, + peg$c82 = peg$classExpectation(["/", "\\", "["], true, false), + peg$c83 = function(cs) { return cs.join(''); }, + peg$c84 = function(i, is) { return { type: 'field', name: is.reduce(function(memo, p){ return memo + p[0] + p[1]; }, i)}; }, - peg$c90 = ":not(", - peg$c91 = peg$literalExpectation(":not(", false), - peg$c92 = function(ss) { return { type: 'not', selectors: ss }; }, - peg$c93 = ":matches(", - peg$c94 = peg$literalExpectation(":matches(", false), - peg$c95 = function(ss) { return { type: 'matches', selectors: ss }; }, - peg$c96 = ":is(", - peg$c97 = peg$literalExpectation(":is(", false), - peg$c98 = ":has(", - peg$c99 = peg$literalExpectation(":has(", false), - peg$c100 = function(ss) { return { type: 'has', selectors: ss }; }, - peg$c101 = ":first-child", - peg$c102 = peg$literalExpectation(":first-child", false), - peg$c103 = function() { return nth(1); }, - peg$c104 = ":last-child", - peg$c105 = peg$literalExpectation(":last-child", false), - peg$c106 = function() { return nthLast(1); }, - peg$c107 = ":nth-child(", - peg$c108 = peg$literalExpectation(":nth-child(", false), - peg$c109 = function(n) { return nth(parseInt(n.join(''), 10)); }, - peg$c110 = ":nth-last-child(", - peg$c111 = peg$literalExpectation(":nth-last-child(", false), - peg$c112 = function(n) { return nthLast(parseInt(n.join(''), 10)); }, - peg$c113 = ":", - peg$c114 = peg$literalExpectation(":", false), - peg$c115 = function(c) { + peg$c85 = ":not(", + peg$c86 = peg$literalExpectation(":not(", false), + peg$c87 = function(ss) { return { type: 'not', selectors: ss }; }, + peg$c88 = ":matches(", + peg$c89 = peg$literalExpectation(":matches(", false), + peg$c90 = function(ss) { return { type: 'matches', selectors: ss }; }, + peg$c91 = ":is(", + peg$c92 = peg$literalExpectation(":is(", false), + peg$c93 = ":has(", + peg$c94 = peg$literalExpectation(":has(", false), + peg$c95 = function(ss) { return { type: 'has', selectors: ss }; }, + peg$c96 = ":first-child", + peg$c97 = peg$literalExpectation(":first-child", false), + peg$c98 = function() { return nth(1); }, + peg$c99 = ":last-child", + peg$c100 = peg$literalExpectation(":last-child", false), + peg$c101 = function() { return nthLast(1); }, + peg$c102 = ":nth-child(", + peg$c103 = peg$literalExpectation(":nth-child(", false), + peg$c104 = function(n) { return nth(parseInt(n.join(''), 10)); }, + peg$c105 = ":nth-last-child(", + peg$c106 = peg$literalExpectation(":nth-last-child(", false), + peg$c107 = function(n) { return nthLast(parseInt(n.join(''), 10)); }, + peg$c108 = ":", + peg$c109 = peg$literalExpectation(":", false), + peg$c110 = function(c) { return { type: 'class', name: c }; }, @@ -441,7 +433,7 @@ function peg$parsestart() { var s0, s1, s2, s3; - var key = peg$currPos * 37 + 0, + var key = peg$currPos * 36 + 0, cached = peg$resultsCache[key]; if (cached) { @@ -490,7 +482,7 @@ function peg$parse_() { var s0, s1; - var key = peg$currPos * 37 + 1, + var key = peg$currPos * 36 + 1, cached = peg$resultsCache[key]; if (cached) { @@ -526,7 +518,7 @@ function peg$parseidentifierName() { var s0, s1, s2; - var key = peg$currPos * 37 + 2, + var key = peg$currPos * 36 + 2, cached = peg$resultsCache[key]; if (cached) { @@ -572,7 +564,7 @@ function peg$parsebinaryOp() { var s0, s1, s2, s3; - var key = peg$currPos * 37 + 3, + var key = peg$currPos * 36 + 3, cached = peg$resultsCache[key]; if (cached) { @@ -702,7 +694,7 @@ function peg$parsehasSelectors() { var s0, s1, s2, s3, s4, s5, s6, s7; - var key = peg$currPos * 37 + 4, + var key = peg$currPos * 36 + 4, cached = peg$resultsCache[key]; if (cached) { @@ -805,7 +797,7 @@ function peg$parseselectors() { var s0, s1, s2, s3, s4, s5, s6, s7; - var key = peg$currPos * 37 + 5, + var key = peg$currPos * 36 + 5, cached = peg$resultsCache[key]; if (cached) { @@ -908,7 +900,7 @@ function peg$parsehasSelector() { var s0, s1, s2; - var key = peg$currPos * 37 + 6, + var key = peg$currPos * 36 + 6, cached = peg$resultsCache[key]; if (cached) { @@ -945,7 +937,7 @@ function peg$parseselector() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 37 + 7, + var key = peg$currPos * 36 + 7, cached = peg$resultsCache[key]; if (cached) { @@ -1012,7 +1004,7 @@ function peg$parsesequence() { var s0, s1, s2, s3; - var key = peg$currPos * 37 + 8, + var key = peg$currPos * 36 + 8, cached = peg$resultsCache[key]; if (cached) { @@ -1064,7 +1056,7 @@ function peg$parseatom() { var s0; - var key = peg$currPos * 37 + 9, + var key = peg$currPos * 36 + 9, cached = peg$resultsCache[key]; if (cached) { @@ -1119,7 +1111,7 @@ function peg$parsewildcard() { var s0, s1; - var key = peg$currPos * 37 + 10, + var key = peg$currPos * 36 + 10, cached = peg$resultsCache[key]; if (cached) { @@ -1150,7 +1142,7 @@ function peg$parseidentifier() { var s0, s1, s2; - var key = peg$currPos * 37 + 11, + var key = peg$currPos * 36 + 11, cached = peg$resultsCache[key]; if (cached) { @@ -1193,7 +1185,7 @@ function peg$parseattr() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 37 + 12, + var key = peg$currPos * 36 + 12, cached = peg$resultsCache[key]; if (cached) { @@ -1257,7 +1249,7 @@ function peg$parseattrOps() { var s0, s1, s2; - var key = peg$currPos * 37 + 13, + var key = peg$currPos * 36 + 13, cached = peg$resultsCache[key]; if (cached) { @@ -1315,7 +1307,7 @@ function peg$parseattrEqOps() { var s0, s1, s2; - var key = peg$currPos * 37 + 14, + var key = peg$currPos * 36 + 14, cached = peg$resultsCache[key]; if (cached) { @@ -1364,7 +1356,7 @@ function peg$parseattrName() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 37 + 15, + var key = peg$currPos * 36 + 15, cached = peg$resultsCache[key]; if (cached) { @@ -1443,7 +1435,7 @@ function peg$parseattrValue() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 37 + 16, + var key = peg$currPos * 36 + 16, cached = peg$resultsCache[key]; if (cached) { @@ -1549,7 +1541,7 @@ function peg$parsestring() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 37 + 17, + var key = peg$currPos * 36 + 17, cached = peg$resultsCache[key]; if (cached) { @@ -1791,7 +1783,7 @@ function peg$parsenumber() { var s0, s1, s2, s3; - var key = peg$currPos * 37 + 18, + var key = peg$currPos * 36 + 18, cached = peg$resultsCache[key]; if (cached) { @@ -1886,7 +1878,7 @@ function peg$parsepath() { var s0, s1; - var key = peg$currPos * 37 + 19, + var key = peg$currPos * 36 + 19, cached = peg$resultsCache[key]; if (cached) { @@ -1911,7 +1903,7 @@ function peg$parsetype() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 37 + 20, + var key = peg$currPos * 36 + 20, cached = peg$resultsCache[key]; if (cached) { @@ -1996,7 +1988,7 @@ function peg$parseflags() { var s0, s1; - var key = peg$currPos * 37 + 21, + var key = peg$currPos * 36 + 21, cached = peg$resultsCache[key]; if (cached) { @@ -2036,7 +2028,7 @@ function peg$parseregex() { var s0, s1, s2, s3, s4; - var key = peg$currPos * 37 + 22, + var key = peg$currPos * 36 + 22, cached = peg$resultsCache[key]; if (cached) { @@ -2055,27 +2047,21 @@ } if (s1 !== peg$FAILED) { s2 = []; - s3 = peg$parseregex_cc(); + s3 = peg$parsere_character_class(); if (s3 === peg$FAILED) { - s3 = peg$parseregex_hex_escape(); + s3 = peg$parsere_escape(); if (s3 === peg$FAILED) { - s3 = peg$parseregex_single_char_escape(); - if (s3 === peg$FAILED) { - s3 = peg$parseregex_chars(); - } + s3 = peg$parsere_chars(); } } if (s3 !== peg$FAILED) { while (s3 !== peg$FAILED) { s2.push(s3); - s3 = peg$parseregex_cc(); + s3 = peg$parsere_character_class(); if (s3 === peg$FAILED) { - s3 = peg$parseregex_hex_escape(); + s3 = peg$parsere_escape(); if (s3 === peg$FAILED) { - s3 = peg$parseregex_single_char_escape(); - if (s3 === peg$FAILED) { - s3 = peg$parseregex_chars(); - } + s3 = peg$parsere_chars(); } } } @@ -2121,10 +2107,10 @@ return s0; } - function peg$parseregex_cc() { + function peg$parsere_character_class() { var s0, s1, s2, s3; - var key = peg$currPos * 37 + 23, + var key = peg$currPos * 36 + 23, cached = peg$resultsCache[key]; if (cached) { @@ -2151,10 +2137,7 @@ if (peg$silentFails === 0) { peg$fail(peg$c78); } } if (s3 === peg$FAILED) { - s3 = peg$parseregex_hex_escape(); - if (s3 === peg$FAILED) { - s3 = peg$parseregex_single_char_escape(); - } + s3 = peg$parsere_escape(); } if (s3 !== peg$FAILED) { while (s3 !== peg$FAILED) { @@ -2167,10 +2150,7 @@ if (peg$silentFails === 0) { peg$fail(peg$c78); } } if (s3 === peg$FAILED) { - s3 = peg$parseregex_hex_escape(); - if (s3 === peg$FAILED) { - s3 = peg$parseregex_single_char_escape(); - } + s3 = peg$parsere_escape(); } } } else { @@ -2206,68 +2186,10 @@ return s0; } - function peg$parseregex_hex_escape() { - var s0, s1, s2, s3; - - var key = peg$currPos * 37 + 24, - cached = peg$resultsCache[key]; - - if (cached) { - peg$currPos = cached.nextPos; - - return cached.result; - } - - s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c80) { - s1 = peg$c80; - peg$currPos += 2; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c81); } - } - if (s1 !== peg$FAILED) { - if (peg$c82.test(input.charAt(peg$currPos))) { - s2 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c83); } - } - if (s2 !== peg$FAILED) { - if (peg$c82.test(input.charAt(peg$currPos))) { - s3 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c83); } - } - if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c84(s2, s3); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; - - return s0; - } - - function peg$parseregex_single_char_escape() { + function peg$parsere_escape() { var s0, s1, s2; - var key = peg$currPos * 37 + 25, + var key = peg$currPos * 36 + 24, cached = peg$resultsCache[key]; if (cached) { @@ -2294,7 +2216,7 @@ } if (s2 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c85(s2); + s1 = peg$c80(s2); s0 = s1; } else { peg$currPos = s0; @@ -2310,10 +2232,10 @@ return s0; } - function peg$parseregex_chars() { + function peg$parsere_chars() { var s0, s1, s2; - var key = peg$currPos * 37 + 26, + var key = peg$currPos * 36 + 25, cached = peg$resultsCache[key]; if (cached) { @@ -2324,22 +2246,22 @@ s0 = peg$currPos; s1 = []; - if (peg$c86.test(input.charAt(peg$currPos))) { + if (peg$c81.test(input.charAt(peg$currPos))) { s2 = input.charAt(peg$currPos); peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c87); } + if (peg$silentFails === 0) { peg$fail(peg$c82); } } if (s2 !== peg$FAILED) { while (s2 !== peg$FAILED) { s1.push(s2); - if (peg$c86.test(input.charAt(peg$currPos))) { + if (peg$c81.test(input.charAt(peg$currPos))) { s2 = input.charAt(peg$currPos); peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c87); } + if (peg$silentFails === 0) { peg$fail(peg$c82); } } } } else { @@ -2347,7 +2269,7 @@ } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c88(s1); + s1 = peg$c83(s1); } s0 = s1; @@ -2359,7 +2281,7 @@ function peg$parsefield() { var s0, s1, s2, s3, s4, s5, s6; - var key = peg$currPos * 37 + 27, + var key = peg$currPos * 36 + 26, cached = peg$resultsCache[key]; if (cached) { @@ -2427,7 +2349,7 @@ } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c89(s2, s3); + s1 = peg$c84(s2, s3); s0 = s1; } else { peg$currPos = s0; @@ -2450,7 +2372,7 @@ function peg$parsenegation() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 37 + 28, + var key = peg$currPos * 36 + 27, cached = peg$resultsCache[key]; if (cached) { @@ -2460,12 +2382,12 @@ } s0 = peg$currPos; - if (input.substr(peg$currPos, 5) === peg$c90) { - s1 = peg$c90; + if (input.substr(peg$currPos, 5) === peg$c85) { + s1 = peg$c85; peg$currPos += 5; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c91); } + if (peg$silentFails === 0) { peg$fail(peg$c86); } } if (s1 !== peg$FAILED) { s2 = peg$parse_(); @@ -2483,7 +2405,7 @@ } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c92(s3); + s1 = peg$c87(s3); s0 = s1; } else { peg$currPos = s0; @@ -2514,7 +2436,7 @@ function peg$parsematches() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 37 + 29, + var key = peg$currPos * 36 + 28, cached = peg$resultsCache[key]; if (cached) { @@ -2524,12 +2446,12 @@ } s0 = peg$currPos; - if (input.substr(peg$currPos, 9) === peg$c93) { - s1 = peg$c93; + if (input.substr(peg$currPos, 9) === peg$c88) { + s1 = peg$c88; peg$currPos += 9; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c94); } + if (peg$silentFails === 0) { peg$fail(peg$c89); } } if (s1 !== peg$FAILED) { s2 = peg$parse_(); @@ -2547,7 +2469,7 @@ } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c95(s3); + s1 = peg$c90(s3); s0 = s1; } else { peg$currPos = s0; @@ -2578,7 +2500,7 @@ function peg$parseis() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 37 + 30, + var key = peg$currPos * 36 + 29, cached = peg$resultsCache[key]; if (cached) { @@ -2588,12 +2510,12 @@ } s0 = peg$currPos; - if (input.substr(peg$currPos, 4) === peg$c96) { - s1 = peg$c96; + if (input.substr(peg$currPos, 4) === peg$c91) { + s1 = peg$c91; peg$currPos += 4; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c97); } + if (peg$silentFails === 0) { peg$fail(peg$c92); } } if (s1 !== peg$FAILED) { s2 = peg$parse_(); @@ -2611,7 +2533,7 @@ } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c95(s3); + s1 = peg$c90(s3); s0 = s1; } else { peg$currPos = s0; @@ -2642,7 +2564,7 @@ function peg$parsehas() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 37 + 31, + var key = peg$currPos * 36 + 30, cached = peg$resultsCache[key]; if (cached) { @@ -2652,12 +2574,12 @@ } s0 = peg$currPos; - if (input.substr(peg$currPos, 5) === peg$c98) { - s1 = peg$c98; + if (input.substr(peg$currPos, 5) === peg$c93) { + s1 = peg$c93; peg$currPos += 5; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c99); } + if (peg$silentFails === 0) { peg$fail(peg$c94); } } if (s1 !== peg$FAILED) { s2 = peg$parse_(); @@ -2675,7 +2597,7 @@ } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c100(s3); + s1 = peg$c95(s3); s0 = s1; } else { peg$currPos = s0; @@ -2706,7 +2628,7 @@ function peg$parsefirstChild() { var s0, s1; - var key = peg$currPos * 37 + 32, + var key = peg$currPos * 36 + 31, cached = peg$resultsCache[key]; if (cached) { @@ -2716,16 +2638,16 @@ } s0 = peg$currPos; - if (input.substr(peg$currPos, 12) === peg$c101) { - s1 = peg$c101; + if (input.substr(peg$currPos, 12) === peg$c96) { + s1 = peg$c96; peg$currPos += 12; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c102); } + if (peg$silentFails === 0) { peg$fail(peg$c97); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c103(); + s1 = peg$c98(); } s0 = s1; @@ -2737,7 +2659,7 @@ function peg$parselastChild() { var s0, s1; - var key = peg$currPos * 37 + 33, + var key = peg$currPos * 36 + 32, cached = peg$resultsCache[key]; if (cached) { @@ -2747,16 +2669,16 @@ } s0 = peg$currPos; - if (input.substr(peg$currPos, 11) === peg$c104) { - s1 = peg$c104; + if (input.substr(peg$currPos, 11) === peg$c99) { + s1 = peg$c99; peg$currPos += 11; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c105); } + if (peg$silentFails === 0) { peg$fail(peg$c100); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c106(); + s1 = peg$c101(); } s0 = s1; @@ -2768,7 +2690,7 @@ function peg$parsenthChild() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 37 + 34, + var key = peg$currPos * 36 + 33, cached = peg$resultsCache[key]; if (cached) { @@ -2778,12 +2700,12 @@ } s0 = peg$currPos; - if (input.substr(peg$currPos, 11) === peg$c107) { - s1 = peg$c107; + if (input.substr(peg$currPos, 11) === peg$c102) { + s1 = peg$c102; peg$currPos += 11; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c108); } + if (peg$silentFails === 0) { peg$fail(peg$c103); } } if (s1 !== peg$FAILED) { s2 = peg$parse_(); @@ -2822,7 +2744,7 @@ } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c109(s3); + s1 = peg$c104(s3); s0 = s1; } else { peg$currPos = s0; @@ -2853,7 +2775,7 @@ function peg$parsenthLastChild() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 37 + 35, + var key = peg$currPos * 36 + 34, cached = peg$resultsCache[key]; if (cached) { @@ -2863,12 +2785,12 @@ } s0 = peg$currPos; - if (input.substr(peg$currPos, 16) === peg$c110) { - s1 = peg$c110; + if (input.substr(peg$currPos, 16) === peg$c105) { + s1 = peg$c105; peg$currPos += 16; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c111); } + if (peg$silentFails === 0) { peg$fail(peg$c106); } } if (s1 !== peg$FAILED) { s2 = peg$parse_(); @@ -2907,7 +2829,7 @@ } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c112(s3); + s1 = peg$c107(s3); s0 = s1; } else { peg$currPos = s0; @@ -2938,7 +2860,7 @@ function peg$parseclass() { var s0, s1, s2; - var key = peg$currPos * 37 + 36, + var key = peg$currPos * 36 + 35, cached = peg$resultsCache[key]; if (cached) { @@ -2949,17 +2871,17 @@ s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 58) { - s1 = peg$c113; + s1 = peg$c108; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c114); } + if (peg$silentFails === 0) { peg$fail(peg$c109); } } if (s1 !== peg$FAILED) { s2 = peg$parseidentifierName(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c115(s2); + s1 = peg$c110(s2); s0 = s1; } else { peg$currPos = s0; diff --git a/tests/queryAttribute.js b/tests/queryAttribute.js index f4a39e8..e72854f 100644 --- a/tests/queryAttribute.js +++ b/tests/queryAttribute.js @@ -177,7 +177,15 @@ describe('Attribute query', function () { ]); }); - it('single encoded slash in a regexp', function () { + it('single slash in a regexp character class', function () { + const matches = esquery(literalSlash, '[value=/foo[/]bar/]'); + assert.sameMembers(matches, [ + literalSlash.body[0].declarations[0].init, + literalSlash.body[2].declarations[0].init + ]); + }); + + it('single encoded slash in a regexp (\\x2F)', function () { const matches = esquery(literalSlash, '[value=/foo\\x2Fbar/]'); assert.sameMembers(matches, [ literalSlash.body[0].declarations[0].init, @@ -185,6 +193,14 @@ describe('Attribute query', function () { ]); }); + it('single encoded slash in a regexp (\\u002F)', function () { + const matches = esquery(literalSlash, '[value=/foo\\u002Fbar/]'); + assert.sameMembers(matches, [ + literalSlash.body[0].declarations[0].init, + literalSlash.body[2].declarations[0].init + ]); + }); + it('double backslash-escaped slash in a literal', function () { const matches = esquery(literalSlash, '[value="foo\\/\\/bar"]'); assert.sameMembers(matches, [ @@ -201,7 +217,15 @@ describe('Attribute query', function () { ]); }); - it('double backslash-escaped slash in a regexp', function () { + it('double slash in a regexp character class', function () { + const matches = esquery(literalSlash, '[value=/foo[/][/]bar/]'); + assert.sameMembers(matches, [ + literalSlash.body[1].declarations[0].init, + literalSlash.body[3].declarations[0].init + ]); + }); + + it('double backslash-escaped slash in a regexp (\\x2F)', function () { const matches = esquery(literalSlash, '[value=/foo\\x2F\\x2Fbar/]'); assert.sameMembers(matches, [ literalSlash.body[1].declarations[0].init, @@ -209,6 +233,14 @@ describe('Attribute query', function () { ]); }); + it('double backslash-escaped slash in a regexp (\\u002F)', function () { + const matches = esquery(literalSlash, '[value=/foo\\u002F\\u002Fbar/]'); + assert.sameMembers(matches, [ + literalSlash.body[1].declarations[0].init, + literalSlash.body[3].declarations[0].init + ]); + }); + it('multiple regexp flags (i and u)', function () { const matches = esquery(simpleProgram, '[name=/\\u{61}|[SDFY]/iu]'); assert.includeMembers(matches, [