Skip to content

Commit

Permalink
fix: improve fixer
Browse files Browse the repository at this point in the history
  • Loading branch information
snitin315 committed Dec 5, 2021
1 parent 8b107c2 commit 7b0e9d9
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 83 deletions.
71 changes: 15 additions & 56 deletions lib/rules/prefer-template.js
Expand Up @@ -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
Expand All @@ -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);
Expand Down
64 changes: 37 additions & 27 deletions tests/lib/rules/prefer-template.js
Expand Up @@ -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
},
{
Expand All @@ -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
},
{
Expand All @@ -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
},
{
Expand All @@ -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
},
{
Expand All @@ -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
}
]
Expand Down

0 comments on commit 7b0e9d9

Please sign in to comment.