From 080b7f8ec403301e541d5d22bb4558dab61cfa24 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Fri, 20 Jan 2017 14:47:52 -0800 Subject: [PATCH] Swap quotes (#355) - During the first iteration, we printed the unescaped values which let to printing invalid JavaScript characters and bad things like invisible characters. - During the second iteration, we escaped everything, which generated valid JavaScript but you lost your emojis and chinese/cyrillic characters In this iteration, which I hope will be the last one, we maintain the string exactly as encoded and only swap quotes. The swap quotes implementation is a bit convoluted but I think it works. --- src/printer.js | 57 +++++++++++++++---- tests/quotes/__snapshots__/jsfmt.spec.js.snap | 12 ++-- .../strings/__snapshots__/jsfmt.spec.js.snap | 6 +- .../unicode/__snapshots__/jsfmt.spec.js.snap | 4 +- 4 files changed, 56 insertions(+), 23 deletions(-) diff --git a/src/printer.js b/src/printer.js index 014bcfe52..2468bbdba 100644 --- a/src/printer.js +++ b/src/printer.js @@ -4,7 +4,6 @@ var comments = require("./comments"); var FastPath = require("./fast-path"); var util = require("./util"); var isIdentifierName = require("esutils").keyword.isIdentifierNameES6; -var jsesc = require("jsesc"); var docBuilders = require("./doc-builders"); var concat = docBuilders.concat; @@ -2319,6 +2318,13 @@ function nodeStr(node, options) { const str = node.value; isString.assert(str); + // Workaround a bug in the Javascript version of the flow parser where + // astral unicode characters like \uD801\uDC28 are incorrectly parsed as + // a sequence of \uFFFD. + if (options.parser === "flow" && str.indexOf("\uFFFD") !== -1) { + return node.raw; + } + const containsSingleQuote = str.indexOf("'") !== -1; const containsDoubleQuote = str.indexOf('"') !== -1; @@ -2330,19 +2336,48 @@ function nodeStr(node, options) { shouldUseSingleQuote = true; } - const result = jsesc(str, { - quotes: shouldUseSingleQuote ? "single" : "double", - wrap: true - }); + const raw = node.extra ? node.extra.raw : node.raw; + const isSingleQuote = raw.charAt(0) === "'"; - // Workaround a bug in the Javascript version of the flow parser where - // astral unicode characters like \uD801\uDC28 are incorrectly parsed as - // a sequence of \uFFFD. - if (options.parser === "flow" && result.indexOf("\\uFFFD") !== -1) { - return node.raw; + if (isSingleQuote && shouldUseSingleQuote || + !isSingleQuote && !shouldUseSingleQuote) { + return raw; } - return result; + const nextQuote = shouldUseSingleQuote ? "'" : '"'; + + // When we change the outer quotes, we need to change the number of + // backslash before. In order to know how many backslash to put, + // I manually did the first few examples and extracted a general rule + // out of it. + // + // shouldSwitch === true + // + // 3 -> 3 "\\\'" -> '\\\'' + // 2 -> 3 "\\'" -> '\\\'' + // 1 -> 1 "\'" -> '\'' + // 0 -> 1 "\'" -> '\'' + // + // Rule: n -> Math.floor(n / 2) + 1 + // + // shouldSwitch === false + // + // 3 -> 2 '\\\'' -> "\\'" + // 1 -> 0 '\'' -> "'" + // + // Rule: n -> n - 1 + return nextQuote + raw.slice(1, -1) + .replace(/\\*['"]/g, (match) => { + const quote = match[match.length - 1]; + const shouldSwitch = quote === "'" && shouldUseSingleQuote || + quote === '"' && !shouldUseSingleQuote; + const numBackslash = match.length - 1; + const nextNumBackslash = shouldSwitch ? + Math.floor(numBackslash / 2) + 1 : + numBackslash - 1; + + return '\\'.repeat(nextNumBackslash) + quote; + }) + nextQuote; } function isFirstStatement(path) { diff --git a/tests/quotes/__snapshots__/jsfmt.spec.js.snap b/tests/quotes/__snapshots__/jsfmt.spec.js.snap index fe175ae9e..0fd9a45e6 100644 --- a/tests/quotes/__snapshots__/jsfmt.spec.js.snap +++ b/tests/quotes/__snapshots__/jsfmt.spec.js.snap @@ -57,11 +57,11 @@ exports[`test strings.js 1`] = ` \"\'\"; \'\"\'; -\'\"\'; +\'\\\"\'; \'\\\\\"\'; \"\'\"; -\"\'\"; +\"\\\'\"; \"\\\\\'\"; \"\'\\\"\"; @@ -71,7 +71,7 @@ exports[`test strings.js 1`] = ` \"\\\\\"; \"\\0\"; -\"\\uD83D\\uDC36\"; +\"๐Ÿถ\"; " `; @@ -104,11 +104,11 @@ exports[`test strings.js 2`] = ` \"\'\"; \'\"\'; -\'\"\'; +\'\\\"\'; \'\\\\\"\'; \"\'\"; -\"\'\"; +\"\\\'\"; \"\\\\\'\"; \'\\\'\"\'; @@ -118,6 +118,6 @@ exports[`test strings.js 2`] = ` \'\\\\\'; \'\\0\'; -\'\\uD83D\\uDC36\'; +\'๐Ÿถ\'; " `; diff --git a/tests/strings/__snapshots__/jsfmt.spec.js.snap b/tests/strings/__snapshots__/jsfmt.spec.js.snap index 17d59130f..a648fe85a 100644 --- a/tests/strings/__snapshots__/jsfmt.spec.js.snap +++ b/tests/strings/__snapshots__/jsfmt.spec.js.snap @@ -30,17 +30,17 @@ exports[`test strings.js 1`] = ` \"abc\", \"\'\", \'\"\', - \'\"\', + \'\\\"\', \'\\\\\"\', \"\'\", - \"\'\", + \"\\\'\", \"\\\\\'\", \"\'\\\"\", \"\'\\\"\", \"\\\\\", \"\\\\\", \"\\0\", - \"\\uD83D\\uDC36\", + \"๐Ÿถ\", \'\\uD801\\uDC28\' ]; " diff --git a/tests/unicode/__snapshots__/jsfmt.spec.js.snap b/tests/unicode/__snapshots__/jsfmt.spec.js.snap index d64e9df76..84fdb1bb8 100644 --- a/tests/unicode/__snapshots__/jsfmt.spec.js.snap +++ b/tests/unicode/__snapshots__/jsfmt.spec.js.snap @@ -73,8 +73,6 @@ function utf16Length(str, pos) { exports[`test keys.js 1`] = ` "({\'ใ“ใฎไบ‹ใฏใคใ‚‚ใ‚Š็ด ๆ™ดใ‚‰ใ—ใ„ใ“ใจใ•\': \'35jL9V\'}) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -({ - \"\\u3053\\u306E\\u4E8B\\u306F\\u3064\\u3082\\u308A\\u7D20\\u6674\\u3089\\u3057\\u3044\\u3053\\u3068\\u3055\": \"35jL9V\" -}); +({ \"ใ“ใฎไบ‹ใฏใคใ‚‚ใ‚Š็ด ๆ™ดใ‚‰ใ—ใ„ใ“ใจใ•\": \"35jL9V\" }); " `;