Repository: jlongster/prettier
Pull request #522 (2017-01-30 19:28:18)

Print \x and \u escapes in strings and regexes lowercase
I went with lowercase since with did so for hex numbers in #498. Tell me if you'd rather want uppercase.

Feel free to close this if prettier shouldn't care. :)


lydell: I forgot template literals. Will update.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4266a27b5..806d8a970 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+# 0.14.1
+
+[link](https://github.com/jlongster/prettier/compare/0.14.0...0.14.1)
+
+* Fix range for object newline detection (#520)
+  * a bugfix for "Keep expanded objects expanded" (#495)
+
 # 0.14.0
 
 [link](https://github.com/jlongster/prettier/compare/0.13.0...0.14.0)
diff --git a/README.md b/README.md
index f17b8664f..c9226cc28 100644
--- a/README.md
+++ b/README.md
@@ -145,7 +145,7 @@ prettier.format(source, {
   // Controls the printing of trailing commas wherever possible
   trailingComma: false,
 
-  // Controls the printing of spaces inside array and objects
+  // Controls the printing of spaces inside object literals
   bracketSpacing: true,
 
   // Which parser to use. Valid options are 'flow' and 'babylon'
diff --git a/src/printer.js b/src/printer.js
index 75472aab8..465b47291 100644
--- a/src/printer.js
+++ b/src/printer.js
@@ -588,11 +588,14 @@ function genericPrintNoParens(path, options, print) {
       );
 
       if (props.length === 0) {
-        return concat([
-          "{",
-          comments.printDanglingComments(path, options),
-          "}"
-        ]);
+        return group(
+          concat([
+            "{",
+            comments.printDanglingComments(path, options),
+            softline,
+            "}"
+          ])
+        );
       } else {
         return group(
           concat([
@@ -726,7 +729,7 @@ function genericPrintNoParens(path, options, print) {
     case "NullLiteral":
       return "null"; // Babel 6 Literal split
     case "RegExpLiteral":
-      return n.extra.raw;
+      return printRegex(n);
     // Babel 6 Literal split
     case "NumericLiteral":
       return printNumber(n.extra.raw);
@@ -736,6 +739,7 @@ function genericPrintNoParens(path, options, print) {
     case "StringLiteral":
     case "Literal":
       if (typeof n.value === "number") return printNumber(n.raw);
+      if (n.regex) return printRegex(n.regex);
       if (typeof n.value !== "string") return "" + n.value;
 
       return nodeStr(n, options); // Babel 6
@@ -1230,7 +1234,10 @@ function genericPrintNoParens(path, options, print) {
     case "ClassExpression":
       return concat(printClass(path, options, print));
     case "TemplateElement":
-      return join(literalline, n.value.raw.split("\n"));
+      return join(
+        literalline,
+        n.value.raw.split("\n").map(line => normalizeEscapes(line))
+      );
     case "TemplateLiteral":
       var expressions = path.map(print, "expressions");
 
@@ -1708,17 +1715,18 @@ function printArgumentsList(path, options, print) {
   // This is just an optimization; I think we could return the
   // conditional group for all function calls, but it's more expensive
   // so only do it for specific forms.
-  const groupLastArg = lastArg.type === "ObjectExpression" ||
-    lastArg.type === "ArrayExpression" ||
-    lastArg.type === "FunctionExpression" ||
-    lastArg.type === "ArrowFunctionExpression" &&
-      (lastArg.body.type === "BlockStatement" ||
-        lastArg.body.type === "ArrowFunctionExpression" ||
-        lastArg.body.type === "ObjectExpression" ||
-        lastArg.body.type === "ArrayExpression" ||
-        lastArg.body.type === "CallExpression" ||
-        lastArg.body.type === "JSXElement") ||
-    lastArg.type === "NewExpression";
+  const groupLastArg = (!lastArg.comments || !lastArg.comments.length) &&
+    (lastArg.type === "ObjectExpression" ||
+      lastArg.type === "ArrayExpression" ||
+      lastArg.type === "FunctionExpression" ||
+      lastArg.type === "ArrowFunctionExpression" &&
+        (lastArg.body.type === "BlockStatement" ||
+          lastArg.body.type === "ArrowFunctionExpression" ||
+          lastArg.body.type === "ObjectExpression" ||
+          lastArg.body.type === "ArrayExpression" ||
+          lastArg.body.type === "CallExpression" ||
+          lastArg.body.type === "JSXElement") ||
+      lastArg.type === "NewExpression");
 
   if (groupLastArg) {
     const shouldBreak = printed.slice(0, -1).some(willBreak);
@@ -2520,7 +2528,37 @@ function makeString(rawContent, enclosingQuote) {
     return match;
   });
 
-  return enclosingQuote + newContent + enclosingQuote;
+  return enclosingQuote + normalizeEscapes(newContent) + enclosingQuote;
+}
+
+function printRegex(regexData) {
+  const pattern = regexData.pattern;
+  const flags = regexData.flags;
+  const skipES2015 = !flags.includes("u");
+
+  return "/" + normalizeEscapes(pattern, skipES2015) + "/" + flags;
+}
+
+function normalizeEscapes(rawContent, skipES2015) {
+  // Matches \x escapes and \u escapes (including the ES2015 variant) and other
+  // escapes.
+  const regex = /(\\x[\da-f]{2}|\\u[\da-f]{4}|\\u\{[\da-f]+\})|(\\[\s\S])/gi;
+
+  return rawContent.replace(regex, (match, escape, otherEscape) => {
+    // Return other escapes as-is.
+    if (otherEscape) {
+      return otherEscape;
+    }
+
+    // Regexes without the /u flag do not support ES2015 unicode escapes, so
+    // return the match as-is.
+    if (skipES2015 && escape[2] === "{") {
+      return escape;
+    }
+
+    // Print all \x and \u escapes lowercase.
+    return escape.toLowerCase();
+  });
 }
 
 function printNumber(rawNumber) {


Test patch:
diff --git a/tests/comments/__snapshots__/jsfmt.spec.js.snap b/tests/comments/__snapshots__/jsfmt.spec.js.snap
index 6888c6572..dbed6c7e6 100644
--- a/tests/comments/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/comments/__snapshots__/jsfmt.spec.js.snap
@@ -26,6 +26,9 @@ exports[`test blank.js 2`] = `
 
 exports[`test dangling.js 1`] = `
 "var x = {/* dangling */};
+var x = {
+  // dangling
+};
 var x = [/* dangling */];
 function x() {
   /* dangling */
@@ -33,6 +36,9 @@ function x() {
 declare class Foo extends Qux<string> {/* dangling */}
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 var x = {/* dangling */};
+var x = {
+  // dangling
+};
 var x = [/* dangling */];
 function x() {
   /* dangling */
@@ -43,6 +49,9 @@ declare class Foo extends Qux<string> {/* dangling */}
 
 exports[`test dangling.js 2`] = `
 "var x = {/* dangling */};
+var x = {
+  // dangling
+};
 var x = [/* dangling */];
 function x() {
   /* dangling */
@@ -50,6 +59,9 @@ function x() {
 declare class Foo extends Qux<string> {/* dangling */}
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 var x = {/* dangling */};
+var x = {
+  // dangling
+};
 var x = [/* dangling */];
 function x() {
   /* dangling */
@@ -161,6 +173,11 @@ else {
 
 // The comment makes the line break in a weird way
 const result = asyncExecute(\'non_existing_command\', /* args */ []);
+
+// The closing paren is printed on the same line as the comment
+foo({}
+  // Hi
+);
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // Does not need to break as it fits in 80 columns
 this.call(a, /* comment */ b);
@@ -270,6 +287,12 @@ if (1) {
 
 // The comment makes the line break in a weird way
 const result = asyncExecute(\"non_existing_command\", /* args */ []);
+
+// The closing paren is printed on the same line as the comment
+foo(
+  {}
+  // Hi
+);
 "
 `;
 
@@ -376,6 +399,11 @@ else {
 
 // The comment makes the line break in a weird way
 const result = asyncExecute(\'non_existing_command\', /* args */ []);
+
+// The closing paren is printed on the same line as the comment
+foo({}
+  // Hi
+);
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // Does not need to break as it fits in 80 columns
 this.call(a, /* comment */ b);
@@ -485,5 +513,11 @@ if (1) {
 
 // The comment makes the line break in a weird way
 const result = asyncExecute(\"non_existing_command\", /* args */ []);
+
+// The closing paren is printed on the same line as the comment
+foo(
+  {}
+  // Hi
+);
 "
 `;
diff --git a/tests/comments/dangling.js b/tests/comments/dangling.js
index 7049ef07f..22208dee5 100644
--- a/tests/comments/dangling.js
+++ b/tests/comments/dangling.js
@@ -1,4 +1,7 @@
 var x = {/* dangling */};
+var x = {
+  // dangling
+};
 var x = [/* dangling */];
 function x() {
   /* dangling */
diff --git a/tests/comments/issues.js b/tests/comments/issues.js
index e48543cd2..c95f79c4a 100644
--- a/tests/comments/issues.js
+++ b/tests/comments/issues.js
@@ -100,3 +100,8 @@ else {
 
 // The comment makes the line break in a weird way
 const result = asyncExecute('non_existing_command', /* args */ []);
+
+// The closing paren is printed on the same line as the comment
+foo({}
+  // Hi
+);
diff --git a/tests/flow/this_type/lib/__snapshots__/jsfmt.spec.js.snap b/tests/flow/this_type/lib/__snapshots__/jsfmt.spec.js.snap
index 9ba16e9c0..8dc8bb152 100644
--- a/tests/flow/this_type/lib/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/this_type/lib/__snapshots__/jsfmt.spec.js.snap
@@ -35,7 +35,8 @@ declare module \"mini-immutable\" {
     set(key: K, value: V): this /* more precise than Map<K,V> (see below)*/
   }
   declare class OrderedMap<K, V> extends Map<K, V> {
-    /* inherits set method returning OrderedMap<K,V> instead of Map<K,V>*/}
+    /* inherits set method returning OrderedMap<K,V> instead of Map<K,V>*/
+  }
 }
 "
 `;
diff --git a/tests/flow/traits/__snapshots__/jsfmt.spec.js.snap b/tests/flow/traits/__snapshots__/jsfmt.spec.js.snap
index 409328331..8e66f054d 100644
--- a/tests/flow/traits/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/traits/__snapshots__/jsfmt.spec.js.snap
@@ -22,7 +22,8 @@ declare class Baz<T> {
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 declare class Foo extends Qux<string> {
   /* KeyedCollection <: Collection*/
-  /* ...KeyedIterable*/}
+  /* ...KeyedIterable*/
+}
 declare class Bar<T> extends Baz<T> {
   // KeyedIterable <: Iterable
   y: T
diff --git a/tests/literal/__snapshots__/jsfmt.spec.js.snap b/tests/literal/__snapshots__/jsfmt.spec.js.snap
index 9e26c22ac..458f81705 100644
--- a/tests/literal/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/literal/__snapshots__/jsfmt.spec.js.snap
@@ -116,3 +116,48 @@ function test5(): string {
 0.1e-10;
 "
 `;
+
+exports[`test regex.js 1`] = `
+"// Normalization of \\x and \\u escapes:
+
+// Basic case.
+/a\\xAaAb\\uB1cDE/gim;
+
+// ES2015 unicode escapes.
+/\\u{1Fa3}/u;
+/\\u{00000000A0}/u;
+
+// Leaves what looks like a ES2015 unicode escape alone if not using the /u flag.
+/\\u{1Fa3}/;
+
+// Leaves what looks like escapes but aren\'t alone.
+/\\xA\\u00BG/;
+
+// Leaves other escapes alone.
+/\\B\\S/;
+
+// Handles escaped backslashes.
+/\\\\xAB\\\\\\xAB\\\\\\\\xAB\\B\\\\\\B\\uAbCd/;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// Normalization of \\x and \\u escapes:
+
+// Basic case.
+/a\\xaaAb\\ub1cdE/gim;
+
+// ES2015 unicode escapes.
+/\\u{1fa3}/u;
+/\\u{00000000a0}/u;
+
+// Leaves what looks like a ES2015 unicode escape alone if not using the /u flag.
+/\\u{1Fa3}/;
+
+// Leaves what looks like escapes but aren\'t alone.
+/\\xA\\u00BG/;
+
+// Leaves other escapes alone.
+/\\B\\S/;
+
+// Handles escaped backslashes.
+/\\\\xAB\\\\\\xab\\\\\\\\xAB\\B\\\\\\B\\uabcd/;
+"
+`;
diff --git a/tests/literal/regex.js b/tests/literal/regex.js
new file mode 100644
index 000000000..30a7b0478
--- /dev/null
+++ b/tests/literal/regex.js
@@ -0,0 +1,20 @@
+// Normalization of \x and \u escapes:
+
+// Basic case.
+/a\xAaAb\uB1cDE/gim;
+
+// ES2015 unicode escapes.
+/\u{1Fa3}/u;
+/\u{00000000A0}/u;
+
+// Leaves what looks like a ES2015 unicode escape alone if not using the /u flag.
+/\u{1Fa3}/;
+
+// Leaves what looks like escapes but aren't alone.
+/\xA\u00BG/;
+
+// Leaves other escapes alone.
+/\B\S/;
+
+// Handles escaped backslashes.
+/\\xAB\\\xAB\\\\xAB\B\\\B\uAbCd/;
diff --git a/tests/strings/__snapshots__/jsfmt.spec.js.snap b/tests/strings/__snapshots__/jsfmt.spec.js.snap
index b75363d4e..7707b472b 100644
--- a/tests/strings/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/strings/__snapshots__/jsfmt.spec.js.snap
@@ -24,6 +24,29 @@ exports[`test strings.js 1`] = `
 
   \'\\uD801\\uDC28\',
 ];
+
+// Normalization of \\x and \\u escapes:
+
+// Basic case.
+\"a\\xAaAb\\uB1cDE\";
+\`a\\xAaAb\\uB1cDE\`;
+
+// ES2015 unicode escapes.
+\"\\u{1Fa3}\";
+\`\\u{1Fa3}\`;
+\"\\u{00000000A0}\";
+\`\\u{00000000A0}\`;
+
+// Leaves other escapes alone.
+\"\\B\\S\";
+\`\\B\\S\`;
+
+// Handles escaped backslashes.
+\"\\\\xAB\\\\\\xAB\\\\\\\\xAB\\B\\\\\\B\\u1234\";
+\`\\\\xAB\\\\\\xAB\\\\\\\\xAB\\B\\\\\\B\\u1234\`;
+
+// Mix of everything.
+\"\\xF0\"\`a\\uaBcD\${\'\\xFdE\'}\\\\\\u{00AbCdE}\`;
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 [
   \"abc\",
@@ -42,6 +65,17 @@ exports[`test strings.js 1`] = `
   \"\\0\",
   \"🐶\",
   \'\\uD801\\uDC28\'
-];
+]; // Normalization of \\x and \\u escapes: // Basic case.
+\"a\\xaaAb\\ub1cdE\";
+\`a\\xaaAb\\ub1cdE\`; // ES2015 unicode escapes.
+\"\\u{1fa3}\";
+\`\\u{1fa3}\`;
+\"\\u{00000000a0}\";
+\`\\u{00000000a0}\`; // Leaves other escapes alone.
+\"\\B\\S\";
+\`\\B\\S\`; // Handles escaped backslashes.
+\"\\\\xAB\\\\\\xab\\\\\\\\xAB\\B\\\\\\B\\u1234\";
+\`\\\\xAB\\\\\\xab\\\\\\\\xAB\\B\\\\\\B\\u1234\`; // Mix of everything.
+\"\\xf0\"\`a\\uabcd\${\"\\xfdE\"}\\\\\\u{00abcde}\`;
 "
 `;
diff --git a/tests/strings/strings.js b/tests/strings/strings.js
index d31dceb2b..c2722e2bb 100644
--- a/tests/strings/strings.js
+++ b/tests/strings/strings.js
@@ -23,3 +23,26 @@
 
   '\uD801\uDC28',
 ];
+
+// Normalization of \x and \u escapes:
+
+// Basic case.
+"a\xAaAb\uB1cDE";
+`a\xAaAb\uB1cDE`;
+
+// ES2015 unicode escapes.
+"\u{1Fa3}";
+`\u{1Fa3}`;
+"\u{00000000A0}";
+`\u{00000000A0}`;
+
+// Leaves other escapes alone.
+"\B\S";
+`\B\S`;
+
+// Handles escaped backslashes.
+"\\xAB\\\xAB\\\\xAB\B\\\B\u1234";
+`\\xAB\\\xAB\\\\xAB\B\\\B\u1234`;
+
+// Mix of everything.
+"\xF0"`a\uaBcD${'\xFdE'}\\\u{00AbCdE}`;