Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: autofix shouldn't produce template literals with \8 or \9 #13737

Merged
merged 2 commits into from Oct 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 14 additions & 22 deletions lib/rules/prefer-template.js
Expand Up @@ -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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like how you simplified these two helpers down to a single recursive function. It's easier to follow.

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;
}

/**
Expand Down Expand Up @@ -237,7 +229,7 @@ module.exports = {
function fixNonStringBinaryExpression(fixer, node) {
const topBinaryExpr = getTopConcatBinaryExpression(node.parent);

if (hasOctalEscapeSequence(topBinaryExpr)) {
if (hasOctalOrNonOctalDecimalEscapeSequence(topBinaryExpr)) {
return null;
}

Expand Down
7 changes: 5 additions & 2 deletions lib/rules/quotes.js
Expand Up @@ -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;
}

Expand Down
18 changes: 11 additions & 7 deletions lib/rules/utils/ast-utils.js
Expand Up @@ -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;

// 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(["&&=", "||=", "??="]);

Expand Down Expand Up @@ -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);
hasOctalOrNonOctalDecimalEscapeSequence(rawString) {
return OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN.test(rawString);
},

isLogicalExpression,
Expand Down
5 changes: 5 additions & 0 deletions tests/lib/rules/prefer-template.js
Expand Up @@ -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,
Expand Down
13 changes: 13 additions & 0 deletions tests/lib/rules/quotes.js
Expand Up @@ -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"
}
]
}
]
});
29 changes: 18 additions & 11 deletions tests/lib/rules/utils/ast-utils.js
Expand Up @@ -1645,7 +1645,7 @@ describe("ast-utils", () => {
});
});

describe("hasOctalEscapeSequence", () => {
describe("hasOctalOrNonOctalDecimalEscapeSequence", () => {

/* eslint-disable quote-props */
const expectedResults = {
Expand Down Expand Up @@ -1680,27 +1680,31 @@ 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,
"\\\\08": false,
"\\\\1": false,
"\\\\12": false,
"\\\\\\0": false,
"\\\\\\8": false,
"\\0\\\\": false,
"0": false,
"1": false,
Expand All @@ -1710,15 +1714,18 @@ 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 */

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]);
assert.strictEqual(astUtils.hasOctalOrNonOctalDecimalEscapeSequence(ast.body[0].expression.raw), expectedResults[key]);
});
});
});
Expand Down