From f397ec393c75ccbafa8d32d9358c08cf32e60b9e Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Mon, 5 Oct 2020 15:27:17 +0200 Subject: [PATCH 1/2] Fix: autofix shouldn't produce template literals with `\8` or `\9` --- lib/rules/utils/ast-utils.js | 16 ++++++++++------ tests/lib/rules/prefer-template.js | 5 +++++ tests/lib/rules/quotes.js | 13 +++++++++++++ tests/lib/rules/utils/ast-utils.js | 25 ++++++++++++++++--------- 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/lib/rules/utils/ast-utils.js b/lib/rules/utils/ast-utils.js index fb8beb25211..248c97adaa9 100644 --- a/lib/rules/utils/ast-utils.js +++ b/lib/rules/utils/ast-utils.js @@ -38,7 +38,9 @@ const LINEBREAKS = new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]); const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "SwitchCase"]); const DECIMAL_INTEGER_PATTERN = /^(?:0|0[0-7]*[89]\d*|[1-9](?:_?\d)*)$/u; -const OCTAL_ESCAPE_PATTERN = /^(?:[^\\]|\\[^0-7]|\\0(?![0-9]))*\\(?:[1-7]|0[0-9])/u; + +// Test for presence of at least one LegacyOctalEscapeSequence or NonOctalDecimalEscapeSequence in a raw string +const OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN = /^(?:[^\\]|\\.)*\\(?:[1-9]|0[0-9])/su; const LOGICAL_ASSIGNMENT_OPERATORS = new Set(["&&=", "||=", "??="]); @@ -1766,17 +1768,19 @@ module.exports = { }, /** - * Determines whether the given raw string contains an octal escape sequence. + * Determines whether the given raw string contains an octal escape sequence + * or a non-octal decimal escape sequence ("\8", "\9"). * - * "\1", "\2" ... "\7" - * "\00", "\01" ... "\09" + * "\1", "\2" ... "\7", "\8", "\9" + * "\00", "\01" ... "\07", "\08", "\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. + * @returns {boolean} `true` if the string contains at least one octal escape sequence + * or at least one non-octal decimal escape sequence. */ hasOctalEscapeSequence(rawString) { - return OCTAL_ESCAPE_PATTERN.test(rawString); + return OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN.test(rawString); }, isLogicalExpression, diff --git a/tests/lib/rules/prefer-template.js b/tests/lib/rules/prefer-template.js index 3ac3879d636..eea0255f1ac 100644 --- a/tests/lib/rules/prefer-template.js +++ b/tests/lib/rules/prefer-template.js @@ -194,6 +194,11 @@ ruleTester.run("prefer-template", rule, { output: null, errors }, + { + code: "foo + 'does not autofix non-octal decimal escape sequence' + '\\8'", + output: null, + errors + }, { code: "foo + '\\n other text \\033'", output: null, diff --git a/tests/lib/rules/quotes.js b/tests/lib/rules/quotes.js index 86dc711f794..a7e223aab9b 100644 --- a/tests/lib/rules/quotes.js +++ b/tests/lib/rules/quotes.js @@ -614,6 +614,19 @@ ruleTester.run("quotes", rule, { type: "Literal" } ] + }, + { + code: "var nonOctalDecimalEscape = '\\8'", + output: null, + options: ["backtick"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "wrongQuotes", + data: { description: "backtick" }, + type: "Literal" + } + ] } ] }); diff --git a/tests/lib/rules/utils/ast-utils.js b/tests/lib/rules/utils/ast-utils.js index 8d60a11d70c..6c15e9f0cd2 100644 --- a/tests/lib/rules/utils/ast-utils.js +++ b/tests/lib/rules/utils/ast-utils.js @@ -1680,19 +1680,24 @@ describe("ast-utils", () => { "\\\\\\1": true, "\\\\\\01": true, "\\\\\\08": true, + "\\8": true, + "\\9": true, + "a\\8a": true, + "\\0\\8": true, + "\\8\\0": true, + "\\80": true, + "\\81": true, + "\\\\\\8": true, + "\\\n\\1": true, + "foo\\\nbar\\2baz": true, + "\\\n\\8": true, + "foo\\\nbar\\9baz": 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, @@ -1700,7 +1705,6 @@ describe("ast-utils", () => { "\\\\1": false, "\\\\12": false, "\\\\\\0": false, - "\\\\\\8": false, "\\0\\\\": false, "0": false, "1": false, @@ -1710,7 +1714,10 @@ describe("ast-utils", () => { "80": false, "12": false, "\\a": false, - "\\n": false + "\\n": false, + "\\\n": false, + "foo\\\nbar": false, + "128\\\n349": false }; /* eslint-enable quote-props */ From 82c54064bf203ae5ea17077c2518e9110733b5c1 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Sun, 18 Oct 2020 00:54:11 +0200 Subject: [PATCH 2/2] Rename functions --- lib/rules/prefer-template.js | 36 ++++++++++++------------------ lib/rules/quotes.js | 7 ++++-- lib/rules/utils/ast-utils.js | 4 ++-- tests/lib/rules/utils/ast-utils.js | 4 ++-- 4 files changed, 23 insertions(+), 28 deletions(-) diff --git a/lib/rules/prefer-template.js b/lib/rules/prefer-template.js index e8f980ebd38..cb967660a62 100644 --- a/lib/rules/prefer-template.js +++ b/lib/rules/prefer-template.js @@ -39,33 +39,25 @@ function getTopConcatBinaryExpression(node) { } /** - * Determines whether a given node is a octal escape sequence + * Checks whether or not a node contains a string literal with an octal or non-octal decimal escape sequence * @param {ASTNode} node A node to check - * @returns {boolean} `true` if the node is an octal escape sequence + * @returns {boolean} `true` if at least one string literal within the node contains + * an octal or non-octal decimal escape sequence */ -function isOctalEscapeSequence(node) { - - // No need to check TemplateLiterals – would throw error with octal escape - const isStringLiteral = node.type === "Literal" && typeof node.value === "string"; - - if (!isStringLiteral) { - return false; +function hasOctalOrNonOctalDecimalEscapeSequence(node) { + if (isConcatenation(node)) { + return ( + hasOctalOrNonOctalDecimalEscapeSequence(node.left) || + hasOctalOrNonOctalDecimalEscapeSequence(node.right) + ); } - return astUtils.hasOctalEscapeSequence(node.raw); -} - -/** - * Checks whether or not a node contains a octal escape sequence - * @param {ASTNode} node A node to check - * @returns {boolean} `true` if the node contains an octal escape sequence - */ -function hasOctalEscapeSequence(node) { - if (isConcatenation(node)) { - return hasOctalEscapeSequence(node.left) || hasOctalEscapeSequence(node.right); + // No need to check TemplateLiterals – would throw parsing error + if (node.type === "Literal" && typeof node.value === "string") { + return astUtils.hasOctalOrNonOctalDecimalEscapeSequence(node.raw); } - return isOctalEscapeSequence(node); + return false; } /** @@ -237,7 +229,7 @@ module.exports = { function fixNonStringBinaryExpression(fixer, node) { const topBinaryExpr = getTopConcatBinaryExpression(node.parent); - if (hasOctalEscapeSequence(topBinaryExpr)) { + if (hasOctalOrNonOctalDecimalEscapeSequence(topBinaryExpr)) { return null; } diff --git a/lib/rules/quotes.js b/lib/rules/quotes.js index d1f4443b903..da7e127493e 100644 --- a/lib/rules/quotes.js +++ b/lib/rules/quotes.js @@ -282,9 +282,12 @@ module.exports = { description: settings.description }, fix(fixer) { - if (quoteOption === "backtick" && astUtils.hasOctalEscapeSequence(rawVal)) { + if (quoteOption === "backtick" && astUtils.hasOctalOrNonOctalDecimalEscapeSequence(rawVal)) { - // An octal escape sequence in a template literal would produce syntax error, even in non-strict mode. + /* + * An octal or non-octal decimal escape sequence in a template literal would + * produce syntax error, even in non-strict mode. + */ return null; } diff --git a/lib/rules/utils/ast-utils.js b/lib/rules/utils/ast-utils.js index 248c97adaa9..c984467ed2a 100644 --- a/lib/rules/utils/ast-utils.js +++ b/lib/rules/utils/ast-utils.js @@ -39,7 +39,7 @@ const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "SwitchCase const DECIMAL_INTEGER_PATTERN = /^(?:0|0[0-7]*[89]\d*|[1-9](?:_?\d)*)$/u; -// Test for presence of at least one LegacyOctalEscapeSequence or NonOctalDecimalEscapeSequence in a raw string +// Tests the presence of at least one LegacyOctalEscapeSequence or NonOctalDecimalEscapeSequence in a raw string const OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN = /^(?:[^\\]|\\.)*\\(?:[1-9]|0[0-9])/su; const LOGICAL_ASSIGNMENT_OPERATORS = new Set(["&&=", "||=", "??="]); @@ -1779,7 +1779,7 @@ module.exports = { * @returns {boolean} `true` if the string contains at least one octal escape sequence * or at least one non-octal decimal escape sequence. */ - hasOctalEscapeSequence(rawString) { + hasOctalOrNonOctalDecimalEscapeSequence(rawString) { return OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN.test(rawString); }, diff --git a/tests/lib/rules/utils/ast-utils.js b/tests/lib/rules/utils/ast-utils.js index 6c15e9f0cd2..86cb4f2d9a9 100644 --- a/tests/lib/rules/utils/ast-utils.js +++ b/tests/lib/rules/utils/ast-utils.js @@ -1645,7 +1645,7 @@ describe("ast-utils", () => { }); }); - describe("hasOctalEscapeSequence", () => { + describe("hasOctalOrNonOctalDecimalEscapeSequence", () => { /* eslint-disable quote-props */ const expectedResults = { @@ -1725,7 +1725,7 @@ describe("ast-utils", () => { it(`should return ${expectedResults[key]} for ${key}`, () => { const ast = espree.parse(`"${key}"`); - assert.strictEqual(astUtils.hasOctalEscapeSequence(ast.body[0].expression.raw), expectedResults[key]); + assert.strictEqual(astUtils.hasOctalOrNonOctalDecimalEscapeSequence(ast.body[0].expression.raw), expectedResults[key]); }); }); });