From 7b0e9d90c146ea7940e11d25ca8835f933913851 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Sun, 5 Dec 2021 17:07:13 +0530 Subject: [PATCH] fix: improve fixer --- lib/rules/prefer-template.js | 71 +++++++----------------------- tests/lib/rules/prefer-template.js | 64 +++++++++++++++------------ 2 files changed, 52 insertions(+), 83 deletions(-) diff --git a/lib/rules/prefer-template.js b/lib/rules/prefer-template.js index 446689914746..c8039283e3ad 100644 --- a/lib/rules/prefer-template.js +++ b/lib/rules/prefer-template.js @@ -157,25 +157,6 @@ module.exports = { return allTokens.slice(0, -1).reduce((accumulator, token, index) => accumulator + sourceText.slice(token.range[1], allTokens[index + 1].range[0]), ""); } - /** - * If the current node is a string literal, escape any instances of ${ or ` to prevent them from being interpreted - * as a template placeholder. However, if the code already contains a backslash before the ${ or ` - * for some reason, don't add another backslash, because that would change the meaning of the code (it would cause - * an actual backslash character to appear before the dollar($) sign). - * @param {string} value The string value to check - * @returns {string} The string with escaped instances of ${ or ` - */ - function escapeTemplatePlaceholders(value) { - return value.slice(1, -1).replace(/\\*(\$\{|`)/gu, matched => { - if (matched.lastIndexOf("\\") % 2) { - return `\\${matched}`; - } - return matched; - - // Unescape any quotes that appear in the original Literal that no longer need to be escaped. - }).replace(new RegExp(`\\\\${value[0]}`, "gu"), value[0]); - } - /** * Returns a template literal form of the given node. * @param {ASTNode} currentNode A node that should be converted to a template literal @@ -185,50 +166,28 @@ module.exports = { */ function getTemplateLiteral(currentNode, textBeforeNode, textAfterNode) { if (currentNode.type === "Literal" && typeof currentNode.value === "string") { - return `\`${escapeTemplatePlaceholders(currentNode.raw)}\``; - } - - if (currentNode.type === "TemplateLiteral") { - return sourceCode.getText(currentNode); - } - - if (isConcatenation(currentNode) && !hasNonStringLiteral(currentNode)) { - const firstToken = sourceCode.getFirstToken(currentNode.left); - const currentNodeText = sourceCode.getText(currentNode); - - if (currentNode.range[0] === currentNode.parent.left.range[0]) { - if (isConcatenation(currentNode.right)) { - const openingParenthesisSign = sourceCode.getFirstTokenBetween(currentNode.left, currentNode.right, token => token.value === "("); - const leftNodeText = currentNodeText.slice(0, openingParenthesisSign.range[0] - firstToken.range[0]); - const leftNodeTextBetweenParenthesis = currentNodeText.slice(currentNode.right.left.range[0], currentNode.right.left.range[1]); - return `${leftNodeText}${leftNodeTextBetweenParenthesis} + \`${escapeTemplatePlaceholders(currentNode.right.right.raw)}'`; - } - - const leftNodeText = currentNodeText.slice(0, currentNode.right.range[0] - firstToken.range[0]); - - return `${leftNodeText}\`${escapeTemplatePlaceholders(currentNode.right.raw)}'`; - } - - const nodeBeforeCurrentNode = currentNode.parent.left.right || currentNode.parent.left; - - if (hasNonStringLiteral(nodeBeforeCurrentNode)) { - const nodeAfterCurrentNode = currentNode.parent.parent && currentNode.parent.parent.right; - const firstTokenAfterCurrentNode = nodeAfterCurrentNode && sourceCode.getFirstToken(nodeAfterCurrentNode); - - if (nodeAfterCurrentNode && hasNonStringLiteral(nodeAfterCurrentNode) && firstTokenAfterCurrentNode.type !== "String") { - const leftNodeText = sourceCode.getText(currentNode.left); - - return `\\${escapeTemplatePlaceholders(firstToken.value)}\`${leftNodeText.slice(firstToken.value.length)} + \`${escapeTemplatePlaceholders(currentNode.right.raw)}'`; + /* + * If the current node is a string literal, escape any instances of ${ or ` to prevent them from being interpreted + * as a template placeholder. However, if the code already contains a backslash before the ${ or ` + * for some reason, don't add another backslash, because that would change the meaning of the code (it would cause + * an actual backslash character to appear before the dollar sign). + */ + return `\`${currentNode.raw.slice(1, -1).replace(/\\*(\$\{|`)/gu, matched => { + if (matched.lastIndexOf("\\") % 2) { + return `\\${matched}`; } + return matched; - return `\\${escapeTemplatePlaceholders(firstToken.value)}\`${currentNodeText.slice(firstToken.value.length)}`; - } + // Unescape any quotes that appear in the original Literal that no longer need to be escaped. + }).replace(new RegExp(`\\\\${currentNode.raw[0]}`, "gu"), currentNode.raw[0])}\``; + } + if (currentNode.type === "TemplateLiteral") { return sourceCode.getText(currentNode); } - if (isConcatenation(currentNode) && hasStringLiteral(currentNode) && hasNonStringLiteral(currentNode)) { + if (isConcatenation(currentNode) && hasStringLiteral(currentNode)) { const plusSign = sourceCode.getFirstTokenBetween(currentNode.left, currentNode.right, token => token.value === "+"); const textBeforePlus = getTextBetween(currentNode.left, plusSign); const textAfterPlus = getTextBetween(plusSign, currentNode.right); diff --git a/tests/lib/rules/prefer-template.js b/tests/lib/rules/prefer-template.js index f27879677f56..a89a36a9de2f 100644 --- a/tests/lib/rules/prefer-template.js +++ b/tests/lib/rules/prefer-template.js @@ -230,74 +230,74 @@ ruleTester.run("prefer-template", rule, { code: `"default-src 'self' https://*.google.com;" + "frame-ancestors 'none';" + "report-to " + foo + ";"`, - output: `"default-src 'self' https://*.google.com;" - + "frame-ancestors 'none';" + output: `\`default-src 'self' https://*.google.com;\` + + \`frame-ancestors 'none';\` + \`report-to \${ foo };\``, errors }, { code: "'a' + 'b' + foo", - output: "'a' + `b${ foo}`", + output: "`a` + `b${ foo}`", errors }, { code: "'a' + 'b' + foo + 'c' + 'd'", - output: "'a' + `b${ foo }c` + `d`", + output: "`a` + `b${ foo }c` + `d`", errors }, { code: "'a' + 'b + c' + foo + 'd' + 'e'", - output: "'a' + `b + c${ foo }d` + `e`", + output: "`a` + `b + c${ foo }d` + `e`", errors }, { code: "'a' + 'b' + foo + ('c' + 'd')", - output: "'a' + `b${ foo }c` + 'd'", + output: "`a` + `b${ foo }c` + `d`", errors }, { code: "'a' + 'b' + foo + ('a' + 'b')", - output: "'a' + `b${ foo }a` + 'b'", + output: "`a` + `b${ foo }a` + `b`", errors }, { code: "'a' + 'b' + foo + ('c' + 'd') + ('e' + 'f')", - output: "'a' + `b${ foo }c` + 'd' + 'e' + 'f'", + output: "`a` + `b${ foo }c` + `d` + `e` + `f`", errors }, { code: "foo + ('a' + 'b') + ('c' + 'd')", - output: "`${foo }a` + 'b' + 'c' + 'd'", + output: "`${foo }a` + `b` + `c` + `d`", errors }, { code: "'a' + foo + ('b' + 'c') + ('d' + bar + 'e')", - output: "`a${ foo }b` + 'c' + `d${ bar }e`", + output: "`a${ foo }b` + `c` + `d${ bar }e`", errors }, { code: "foo + ('b' + 'c') + ('d' + bar + 'e')", - output: "`${foo }b` + 'c' + `d${ bar }e`", + output: "`${foo }b` + `c` + `d${ bar }e`", errors }, { code: "'a' + 'b' + foo + ('c' + 'd' + 'e')", - output: "'a' + `b${ foo }c` + 'd' + 'e'", + output: "`a` + `b${ foo }c` + `d` + `e`", errors }, { code: "'a' + 'b' + foo + ('c' + bar + 'd')", - output: "'a' + `b${ foo }c${ bar }d`", + output: "`a` + `b${ foo }c${ bar }d`", errors }, { code: "'a' + 'b' + foo + ('c' + bar + ('d' + 'e') + 'f')", - output: "'a' + `b${ foo }c${ bar }d` + 'e' + `f`", + output: "`a` + `b${ foo }c${ bar }d` + `e` + `f`", errors }, { code: "'a' + 'b' + foo + ('c' + bar + 'e') + 'f' + test", - output: "'a' + `b${ foo }c${ bar }e` + `f${ test}`", + output: "`a` + `b${ foo }c${ bar }e` + `f${ test}`", errors }, { @@ -307,7 +307,7 @@ ruleTester.run("prefer-template", rule, { }, { code: "'a' + foo + ('b' + 'c') + ('d' + bar)", - output: "`a${ foo }b` + 'c' + `d${ bar}`", + output: "`a${ foo }b` + `c` + `d${ bar}`", errors }, { @@ -317,22 +317,22 @@ ruleTester.run("prefer-template", rule, { }, { code: "'a' + '`b`' + c", - output: "'a' + `\\`b\\`${ c}`", + output: "`a` + `\\`b\\`${ c}`", errors }, { code: "'a' + '`b` + `c`' + d", - output: "'a' + `\\`b\\` + \\`c\\`${ d}`", + output: "`a` + `\\`b\\` + \\`c\\`${ d}`", errors }, { code: "'a' + b + ('`c`' + '`d`')", - output: "`a${ b }\\`c\\`` + '`d`'", + output: "`a${ b }\\`c\\`` + `\\`d\\``", errors }, { code: "'`a`' + b + ('`c`' + '`d`')", - output: "`\\`a\\`${ b }\\`c\\`` + '`d`'", + output: "`\\`a\\`${ b }\\`c\\`` + `\\`d\\``", errors }, { @@ -342,12 +342,12 @@ ruleTester.run("prefer-template", rule, { }, { code: "'a' + ('b' + 'c') + d", - output: "'a' + 'b' + `c${ d}`", + output: "`a` + `b` + `c${ d}`", errors }, { code: "'a' + ('`b`' + '`c`') + d", - output: "'a' + '`b`' + `\\`c\\`${ d}`", + output: "`a` + `\\`b\\`` + `\\`c\\`${ d}`", errors }, { @@ -372,27 +372,37 @@ ruleTester.run("prefer-template", rule, { }, { code: "'a' + ('b' + 'c' + 'd') + e", - output: "'a' + 'b' + 'c' + `d${ e}`", + output: "`a` + `b` + `c` + `d${ e}`", errors }, { code: "'a' + ('b' + 'c' + 'd' + (e + 'f') + 'g' +'h' + 'i') + j", - output: "`a` + 'b' + 'c' + `d${ e }fg` +`h` + `i${ j}`", + output: "`a` + `b` + `c` + `d${ e }fg` +`h` + `i${ j}`", + errors + }, + { + code: "a + (('b' + 'c') + 'd')", + output: "`${a }b` + `c` + `d`", + errors + }, + { + code: "(a + 'b') + ('c' + 'd') + e", + output: "`${a }b` + `c` + `d${ e}`", errors }, { code: "var foo = \"Hello \" + \"world \" + \"another \" + test", - output: "var foo = \"Hello \" + \"world \" + `another ${ test}`", + output: "var foo = `Hello ` + `world ` + `another ${ test}`", errors }, { code: "'Hello ' + '\"world\" ' + test", - output: "'Hello ' + `\"world\" ${ test}`", + output: "`Hello ` + `\"world\" ${ test}`", errors }, { code: "\"Hello \" + \"'world' \" + test", - output: "\"Hello \" + `'world' ${ test}`", + output: "`Hello ` + `'world' ${ test}`", errors } ]