diff --git a/docs/src/rules/quotes.md b/docs/src/rules/quotes.md index 38d21b428f9..1b876ebdd3e 100644 --- a/docs/src/rules/quotes.md +++ b/docs/src/rules/quotes.md @@ -15,7 +15,7 @@ var single = 'single'; var backtick = `backtick`; // ES6 only ``` -Each of these lines creates a string and, in some cases, can be used interchangeably. The choice of how to define strings in a codebase is a stylistic one outside of template literals (which allow embedded of expressions to be interpreted). +Each of these lines creates a string and, in some cases, can be used interchangeably. The choice of how to define strings in a codebase is a stylistic one outside of template literals (which allow embedded expressions to be interpreted). Many codebases require strings to be defined in a consistent manner. @@ -23,6 +23,8 @@ Many codebases require strings to be defined in a consistent manner. This rule enforces the consistent use of either backticks, double, or single quotes. +This rule is aware of directive prologues such as `"use strict"` and will not flag or autofix them if doing so will change how the directive prologue is interpreted. + ## Options This rule has two options, a string option and an object option. @@ -125,7 +127,9 @@ Examples of **correct** code for this rule with the `"backtick"` option: /*eslint quotes: ["error", "backtick"]*/ /*eslint-env es6*/ +"use strict"; // directives must use single or double quotes var backtick = `backtick`; +var obj = { 'prop-name': `value` }; // backticks not allowed for property names ``` ::: diff --git a/lib/rules/quotes.js b/lib/rules/quotes.js index 9efb9809aa3..6eda7c0b40a 100644 --- a/lib/rules/quotes.js +++ b/lib/rules/quotes.js @@ -157,7 +157,8 @@ module.exports = { /** * Checks whether or not a given node is a directive. - * The directive is a `ExpressionStatement` which has only a string literal. + * The directive is a `ExpressionStatement` which has only a string literal not surrounded by + * parentheses. * @param {ASTNode} node A node to check. * @returns {boolean} Whether or not the node is a directive. * @private @@ -166,23 +167,23 @@ module.exports = { return ( node.type === "ExpressionStatement" && node.expression.type === "Literal" && - typeof node.expression.value === "string" + typeof node.expression.value === "string" && + !astUtils.isParenthesised(sourceCode, node.expression) ); } /** - * Checks whether or not a given node is a part of directive prologues. - * See also: http://www.ecma-international.org/ecma-262/6.0/#sec-directive-prologues-and-the-use-strict-directive + * Checks whether a specified node is either part of, or immediately follows a (possibly empty) directive prologue. + * @see {@link http://www.ecma-international.org/ecma-262/6.0/#sec-directive-prologues-and-the-use-strict-directive} * @param {ASTNode} node A node to check. - * @returns {boolean} Whether or not the node is a part of directive prologues. + * @returns {boolean} Whether a specified node is either part of, or immediately follows a (possibly empty) directive prologue. * @private */ - function isPartOfDirectivePrologue(node) { - const block = node.parent.parent; - - if (block.type !== "Program" && (block.type !== "BlockStatement" || !astUtils.isFunction(block.parent))) { + function isExpressionInOrJustAfterDirectivePrologue(node) { + if (!astUtils.isTopLevelExpressionStatement(node.parent)) { return false; } + const block = node.parent.parent; // Check the node is at a prologue. for (let i = 0; i < block.body.length; ++i) { @@ -212,7 +213,7 @@ module.exports = { // Directive Prologues. case "ExpressionStatement": - return isPartOfDirectivePrologue(node); + return !astUtils.isParenthesised(sourceCode, node) && isExpressionInOrJustAfterDirectivePrologue(node); // LiteralPropertyName. case "Property": @@ -328,12 +329,11 @@ module.exports = { description: settings.description }, fix(fixer) { - if (isPartOfDirectivePrologue(node)) { + if (astUtils.isTopLevelExpressionStatement(node.parent) && !astUtils.isParenthesised(sourceCode, node)) { /* - * TemplateLiterals in a directive prologue aren't actually directives, but if they're - * in the directive prologue, then fixing them might turn them into directives and change - * the behavior of the code. + * TemplateLiterals aren't actually directives, but fixing them might turn + * them into directives and change the behavior of the code. */ return null; } diff --git a/tests/lib/rules/quotes.js b/tests/lib/rules/quotes.js index d1c42b73e84..11ae90248b7 100644 --- a/tests/lib/rules/quotes.js +++ b/tests/lib/rules/quotes.js @@ -440,7 +440,7 @@ ruleTester.run("quotes", rule, { }, { code: "() => { foo(); `use strict`; }", - output: "() => { foo(); \"use strict\"; }", + output: null, // no autofix parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "wrongQuotes", @@ -450,7 +450,7 @@ ruleTester.run("quotes", rule, { }, { code: "foo(); `use strict`;", - output: "foo(); \"use strict\";", + output: null, // no autofix parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "wrongQuotes", @@ -725,6 +725,62 @@ ruleTester.run("quotes", rule, { type: "Literal" } ] + }, + + // https://github.com/eslint/eslint/pull/17022 + { + code: "() => { foo(); (`use strict`); }", + output: "() => { foo(); (\"use strict\"); }", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "wrongQuotes", + data: { description: "doublequote" }, + type: "TemplateLiteral" + }] + }, + { + code: "('foo'); \"bar\";", + output: "(`foo`); `bar`;", + options: ["backtick"], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "wrongQuotes", + data: { description: "backtick" }, + type: "Literal" + }, { + messageId: "wrongQuotes", + data: { description: "backtick" }, + type: "Literal" + }] + }, + { + code: "; 'use asm';", + output: "; \"use asm\";", + errors: [{ + messageId: "wrongQuotes", + data: { description: "doublequote" }, + type: "Literal" + }] + }, + { + code: "{ `foobar`; }", + output: "{ \"foobar\"; }", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "wrongQuotes", + data: { description: "doublequote" }, + type: "TemplateLiteral" + }] + }, + { + code: "foo(() => `bar`);", + output: "foo(() => \"bar\");", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "wrongQuotes", + data: { description: "doublequote" }, + type: "TemplateLiteral" + }] } ] });