Skip to content

Commit

Permalink
Merge pull request eslint#3154 from mysticatea/quotes/backtick-allows…
Browse files Browse the repository at this point in the history
…-directive-prologue

Fix: `quotes` with `"backtick"` allows directive prologues
  • Loading branch information
nzakas committed Jul 24, 2015
2 parents c73093a + 53a99d6 commit 3f4b862
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 2 deletions.
51 changes: 50 additions & 1 deletion lib/rules/quotes.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ var QUOTE_SETTINGS = {

var AVOID_ESCAPE = "avoid-escape";

var FUNCTION_TYPE = /^(?:Arrow)?Function(?:Declaration|Expression)$/;

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
Expand Down Expand Up @@ -56,6 +58,53 @@ module.exports = function(context) {
return node.type.indexOf("JSX") === 0;
}

/**
* Checks whether or not a given node is a directive.
* The directive is a `ExpressionStatement` which has only a string literal.
* @param {ASTNode} node - A node to check.
* @returns {boolean} Whether or not the node is a directive.
* @private
*/
function isDirective(node) {
return (
node.type === "ExpressionStatement" &&
node.expression.type === "Literal" &&
typeof node.expression.value === "string"
);
}

/**
* 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
* @param {ASTNode} node - A node to check.
* @returns {boolean} Whether or not the node is a part of directive prologues.
* @private
*/
function isPartOfDirectivePrologue(node) {
if (!isDirective(node.parent)) {
return false;
}

var block = node.parent.parent;
if (block.type !== "Program" && (block.type !== "BlockStatement" || !FUNCTION_TYPE.test(block.parent.type))) {
return false;
}

// Check the node is at a prologue.
for (var i = 0; i < block.body.length; ++i) {
var statement = block.body[i];

if (statement === node.parent) {
return true;
}
if (!isDirective(statement)) {
break;
}
}

return false;
}

return {

"Literal": function(node) {
Expand All @@ -67,7 +116,7 @@ module.exports = function(context) {
isValid;

if (settings && typeof val === "string") {
isValid = isJSXElement(node.parent) || isSurroundedBy(rawVal, settings.quote);
isValid = (quoteOption === "backtick" && isPartOfDirectivePrologue(node)) || isJSXElement(node.parent) || isSurroundedBy(rawVal, settings.quote);

if (!isValid && avoidEscape) {
isValid = isSurroundedBy(rawVal, settings.alternateQuote) && rawVal.indexOf(settings.quote) >= 0;
Expand Down
23 changes: 22 additions & 1 deletion tests/lib/rules/quotes.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,14 @@ eslintTester.addRuleTest("lib/rules/quotes", {
{ code: "var foo = <div id=\"foo\"></div>;", args: [1, "backtick"], ecmaFeatures: { jsx: true } },
{ code: "var foo = <div>Hello world</div>;", args: [1, "backtick"], ecmaFeatures: { jsx: true }},
{ code: "var foo = `backtick`;", args: [1, "single"], ecmaFeatures: { templateStrings: true }},
{ code: "var foo = `backtick`;", args: [1, "double"], ecmaFeatures: { templateStrings: true }}
{ code: "var foo = `backtick`;", args: [1, "double"], ecmaFeatures: { templateStrings: true }},

// `backtick` should not warn the directive prologues.
{ code: "\"use strict\"; var foo = `backtick`;", options: ["backtick"], ecmaFeatures: { templateStrings: true }},
{ code: "\"use strict\"; 'use strong'; \"use asm\"; var foo = `backtick`;", options: ["backtick"], ecmaFeatures: { templateStrings: true }},
{ code: "function foo() { \"use strict\"; \"use strong\"; \"use asm\"; var foo = `backtick`; }", options: ["backtick"], ecmaFeatures: { templateStrings: true }},
{ code: "(function() { 'use strict'; 'use strong'; 'use asm'; var foo = `backtick`; })();", options: ["backtick"], ecmaFeatures: { templateStrings: true }},
{ code: "(() => { \"use strict\"; \"use strong\"; \"use asm\"; var foo = `backtick`; })();", options: ["backtick"], ecmaFeatures: { arrowFunctions: true, templateStrings: true }}
],
invalid: [
{ code: "var foo = 'bar';",
Expand Down Expand Up @@ -62,6 +69,20 @@ eslintTester.addRuleTest("lib/rules/quotes", {
errors: [{ message: "Strings must use backtick.", type: "Literal" }]},
{ code: "var foo = 'bar';",
args: [1, "backtick", "avoid-escape"],
errors: [{ message: "Strings must use backtick.", type: "Literal" }]},

// below are not the directive prologues.
{ code: "var foo = `backtick`; \"use strict\";",
options: ["backtick"],
ecmaFeatures: { templateStrings: true },
errors: [{ message: "Strings must use backtick.", type: "Literal" }]},
{ code: "{ \"use strict\"; var foo = `backtick`; }",
options: ["backtick"],
ecmaFeatures: { templateStrings: true },
errors: [{ message: "Strings must use backtick.", type: "Literal" }]},
{ code: "if (1) { \"use strict\"; var foo = `backtick`; }",
options: ["backtick"],
ecmaFeatures: { templateStrings: true },
errors: [{ message: "Strings must use backtick.", type: "Literal" }]}
]
});

0 comments on commit 3f4b862

Please sign in to comment.