Skip to content

Commit

Permalink
Improve string escaping in template-literals
Browse files Browse the repository at this point in the history
I didn't like how escape sequences would be converted to the actual
characters after running this transform, so I looked into how that is
represented in string literals and noticed that we have access to a
`raw` property that contains the escaped string that we need, with some
additional quotation marks around it. After stripping off these
quotation marks and escaping ${ to prevent new interpolation, we have
much better string escaping.
  • Loading branch information
lencioni committed Mar 13, 2016
1 parent 3c10136 commit 334835f
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 16 deletions.
8 changes: 8 additions & 0 deletions test/template-literals-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ test = 'a\'b' + c; // escaped quote
test = 'a\"b' + c; // escaped quote
test = "a\'b" + c; // escaped quote
test = "a\"b" + c; // escaped quote
test = 'a\'b' + 'c'; // escaped quote
test = 'a\'b' + "c\"d"; // escaped quotes of different kinds
test = 'a\\"b' + c; // non-escaped quote

test = 'a\tb' + c; // tab
test = 'a\tb' + 'c'; // tab
test = 'a\u00A9' + b; // unicode escape

test = 'hi\nhello' + foo; // line break
test = 'hi' + // comment in the middle
Expand Down Expand Up @@ -59,6 +66,7 @@ test = `hi${foo}` + bar;

test = '${hi}' + foo; // escaping a string
test = '${hi}${hello}' + foo; // escaping a string
test = '${hi}' + '${hello}'; // escaping a string

test = foo + 'hi';
test = foo + 'hi' + bar;
Expand Down
21 changes: 14 additions & 7 deletions test/template-literals-test.output.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,19 @@ test = `hi${foo}`; // double
test = `hi${foo}`; // template literal
test = foo`hi` + bar; // tagged template literal

test = `a'b${c}`; // escaped quote
test = `a"b${c}`; // escaped quote
test = `a'b${c}`; // escaped quote
test = `a"b${c}`; // escaped quote

test = `hi
hello${foo}`; // line break
test = `a\'b${c}`; // escaped quote
test = `a\"b${c}`; // escaped quote
test = `a\'b${c}`; // escaped quote
test = `a\"b${c}`; // escaped quote
test = 'a\'bc'; // escaped quote
test = 'a\'bc"d'; // escaped quotes of different kinds
test = `a\\"b${c}`; // non-escaped quote

test = `a\tb${c}`; // tab
test = 'a\tbc'; // tab
test = `a\u00A9${b}`; // unicode escape

test = `hi\nhello${foo}`; // line break
test = // comment in the middle
`hi${foo}`;
test = // comment in the middle
Expand Down Expand Up @@ -60,6 +66,7 @@ test = `hi${foo}${bar}`;

test = `\${hi}${foo}`; // escaping a string
test = `\${hi}\${hello}${foo}`; // escaping a string
test = '${hi}${hello}'; // escaping a string

test = `${foo}hi`;
test = `${foo}hi${bar}`;
Expand Down
33 changes: 24 additions & 9 deletions transforms/template-literals.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,22 @@
*
* Areas of improvement:
*
* - Better handling of comments when they are in the middle of string
* concatenation. Currently, those are added before the string but after the
* assignment. Perhaps in these situations, the string concatenation should be
* preserved as-is.
* - Comments in the middle of string concatenation are currently added before
* the string but after the assignment. Perhaps in these situations, the
* string concatenation should be preserved as-is.
*
* - Better handling of nested string concatenation inside template literals.
* - Nested concatenation inside template literals is not currently simplified.
* Currently, a + `b${'c' + d}` becomes `${a}b${'c' + d}` but it would ideally
* become `${a}b${`c${d}`}`.
*
* - Unnecessary escaping of quotes from the resulting template literals is
* currently not removed. This is possibly the domain of a different
* transform.
*
* - Unicode escape sequences are converted to unicode characters when
* the simplified concatenation results in a string literal instead of a
* template literal. It would be nice to perserve the original--whether it be
* a unicode escape sequence or a unicode character.
*/
module.exports = function templateLiterals(file, api, options) {
const j = api.jscodeshift;
Expand Down Expand Up @@ -92,9 +100,16 @@ module.exports = function templateLiterals(file, api, options) {

if (node.type === 'Literal') {
const cooked = node.value.toString();
// For the raw string, we need to escape \ and ${ so that we don't
// introduce new interpolation.
const raw = cooked.replace(/(\$\{|\\)/g, '\\$1');
let raw = node.raw.toString();
if (typeof node.value === 'string') {
// We need to remove the opening and trailing quote from the raw value
// of the string.
raw = raw.slice(1, -1);

// We need to escape ${ to prevent new interpolation.
raw = raw.replace(/\$\{/g, '\\${');
}

const newQuasi = j.templateElement({ cooked, raw }, false);
const newQuasis = joinQuasis(quasis, [newQuasi]);
return buildTL(rest, newQuasis, expressions, newComments);
Expand Down Expand Up @@ -140,7 +155,7 @@ module.exports = function templateLiterals(file, api, options) {

// There are no expressions, so let's use a regular string instead of a
// template literal.
const str = tl.quasis.map(q => q.value.raw).join('');
const str = tl.quasis.map(q => q.value.cooked).join('');
const strLiteral = j.literal(str);
strLiteral.comments = tlOptions.comments;
return strLiteral;
Expand Down

0 comments on commit 334835f

Please sign in to comment.