From 30ebf929f60684520b1201c1adfd86214c19d614 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Sun, 18 Aug 2019 21:53:24 +0200 Subject: [PATCH] Fix: prefer-template autofix produces syntax error with octal escapes (#12085) --- lib/rules/prefer-template.js | 11 +---- lib/rules/utils/ast-utils.js | 16 ++++++ tests/lib/rules/prefer-template.js | 10 ++++ tests/lib/rules/utils/ast-utils.js | 78 ++++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 10 deletions(-) diff --git a/lib/rules/prefer-template.js b/lib/rules/prefer-template.js index f73ac34f837..a2507d452c6 100644 --- a/lib/rules/prefer-template.js +++ b/lib/rules/prefer-template.js @@ -52,16 +52,7 @@ function isOctalEscapeSequence(node) { return false; } - const match = node.raw.match(/^([^\\]|\\[^0-7])*\\([0-7]{1,3})/u); - - if (match) { - - // \0 is actually not considered an octal - if (match[2] !== "0" || typeof match[3] !== "undefined") { - return true; - } - } - return false; + return astUtils.hasOctalEscapeSequence(node.raw); } /** diff --git a/lib/rules/utils/ast-utils.js b/lib/rules/utils/ast-utils.js index ecf7db82ff3..7c17fc2ec0c 100644 --- a/lib/rules/utils/ast-utils.js +++ b/lib/rules/utils/ast-utils.js @@ -38,6 +38,7 @@ const LINEBREAKS = new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]); const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "SwitchCase"]); const DECIMAL_INTEGER_PATTERN = /^(0|[1-9]\d*)$/u; +const OCTAL_ESCAPE_PATTERN = /^(?:[^\\]|\\[^0-7]|\\0(?![0-9]))*\\(?:[1-7]|0[0-9])/u; /** * Checks reference if is non initializer and writable. @@ -1373,5 +1374,20 @@ module.exports = { "/*".length + (match ? match.index + 1 : 0) ); + }, + + /** + * Determines whether the given raw string contains an octal escape sequence. + * + * "\1", "\2" ... "\7" + * "\00", "\01" ... "\09" + * + * "\0", when not followed by a digit, is not an octal escape sequence. + * + * @param {string} rawString A string in its raw representation. + * @returns {boolean} `true` if the string contains at least one octal escape sequence. + */ + hasOctalEscapeSequence(rawString) { + return OCTAL_ESCAPE_PATTERN.test(rawString); } }; diff --git a/tests/lib/rules/prefer-template.js b/tests/lib/rules/prefer-template.js index 9e987a813fa..439b261cc1a 100644 --- a/tests/lib/rules/prefer-template.js +++ b/tests/lib/rules/prefer-template.js @@ -199,6 +199,16 @@ ruleTester.run("prefer-template", rule, { output: null, errors }, + { + code: "foo + '\\0\\1'", + output: null, + errors + }, + { + code: "foo + '\\08'", + output: null, + errors + }, { code: "foo + '\\\\033'", output: "`${foo }\\\\033`", diff --git a/tests/lib/rules/utils/ast-utils.js b/tests/lib/rules/utils/ast-utils.js index 02f57e11618..314deeadfc5 100644 --- a/tests/lib/rules/utils/ast-utils.js +++ b/tests/lib/rules/utils/ast-utils.js @@ -1250,4 +1250,82 @@ describe("ast-utils", () => { assert.strictEqual(astUtils.equalTokens(ast.body[0], ast.body[1], sourceCode), false); }); }); + + describe("hasOctalEscapeSequence", () => { + + /* eslint-disable quote-props */ + const expectedResults = { + "\\1": true, + "\\2": true, + "\\7": true, + "\\00": true, + "\\01": true, + "\\02": true, + "\\07": true, + "\\08": true, + "\\09": true, + "\\10": true, + "\\12": true, + " \\1": true, + "\\1 ": true, + "a\\1": true, + "\\1a": true, + "a\\1a": true, + " \\01": true, + "\\01 ": true, + "a\\01": true, + "\\01a": true, + "a\\01a": true, + "a\\08a": true, + "\\0\\1": true, + "\\0\\01": true, + "\\0\\08": true, + "\\n\\1": true, + "\\n\\01": true, + "\\n\\08": true, + "\\\\\\1": true, + "\\\\\\01": true, + "\\\\\\08": true, + + "\\0": false, + "\\8": false, + "\\9": false, + " \\0": false, + "\\0 ": false, + "a\\0": false, + "\\0a": false, + "a\\8a": false, + "\\0\\8": false, + "\\8\\0": false, + "\\80": false, + "\\81": false, + "\\\\": false, + "\\\\0": false, + "\\\\01": false, + "\\\\08": false, + "\\\\1": false, + "\\\\12": false, + "\\\\\\0": false, + "\\\\\\8": false, + "\\0\\\\": false, + "0": false, + "1": false, + "8": false, + "01": false, + "08": false, + "80": false, + "12": false, + "\\a": false, + "\\n": false + }; + /* eslint-enable quote-props */ + + Object.keys(expectedResults).forEach(key => { + it(`should return ${expectedResults[key]} for ${key}`, () => { + const ast = espree.parse(`"${key}"`); + + assert.strictEqual(astUtils.hasOctalEscapeSequence(ast.body[0].expression.raw), expectedResults[key]); + }); + }); + }); });