Repository: babel/babili (js)

Issue:
Syntax errors after minifying unicode
Trying to use [mathiasbynens/he](https://github.com/mathiasbynens/he) to encode html entities in the browser, but after adding the library, the app has started getting syntax errors in Safari and Firefox.

The offending character seems to be a unicode character, that appears in the source after minification.

Here's the result of running the corresponding snippet of code in [Babel's REPL with Babili enabled](https://babeljs.io/repl/#?babili=true&evaluate=true&lineWrap=false&presets=es2015&targets=&browsers=&builtIns=false&code=let%20a%20%3D%20%7B%20'%5Cu2118'%3A'wp'%20%7D&experimental=false&loose=false&spec=false&playground=false):

```js
let a = { '\u2118':'wp' };
// becomes
'use strict';var a={℘:'wp'};
```

Visiting that link in Firefox 50.1.0 results in an "Illegal character error" in the REPL. Seeing as it works fine without Babili, I'm guessing this is a bug, rather than a deliberate constraint.

Pretty sure it happens in [babel-plugin-transform-property-literals](https://github.com/babel/babili/tree/master/packages/babel-plugin-transform-property-literals) as it turns the string into a literal key.


Pull request description:
Fix removal of quotes from property names
+ Fix #416 
+ Fix #445 
+ Fix #415 

Patch:
diff --git a/packages/babel-plugin-transform-property-literals/package.json b/packages/babel-plugin-transform-property-literals/package.json
index c880a34..a27c10c 100644
--- a/packages/babel-plugin-transform-property-literals/package.json
+++ b/packages/babel-plugin-transform-property-literals/package.json
@@ -11,6 +11,8 @@
   "keywords": [
     "babel-plugin"
   ],
-  "dependencies": {},
+  "dependencies": {
+    "esutils": "^2.0.2"
+  },
   "devDependencies": {}
 }
diff --git a/packages/babel-plugin-transform-property-literals/src/escape-string-literal.js b/packages/babel-plugin-transform-property-literals/src/escape-string-literal.js
new file mode 100644
index 0000000..7c06a5f
--- /dev/null
+++ b/packages/babel-plugin-transform-property-literals/src/escape-string-literal.js
@@ -0,0 +1,68 @@
+"use strict";
+
+/**
+ * Original Source:
+ * https://github.com/shapesecurity/shift-codegen-js/blob/0d09bd8a/src/coderep.js#L122
+ *
+ * The following function is an exact copy of the original implementation
+ *
+ * LICENSE
+
+Copyright 2014 Shape Security, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+ */
+module.exports = function escapeStringLiteral(stringValue) {
+  let result = "";
+  let nSingle = 0,
+    nDouble = 0;
+  for (let i = 0, l = stringValue.length; i < l; ++i) {
+    let ch = stringValue[i];
+    if (ch === '"') {
+      ++nDouble;
+    } else if (ch === "'") {
+      ++nSingle;
+    }
+  }
+  let delim = nDouble > nSingle ? "'" : '"';
+  result += delim;
+  for (let i = 0; i < stringValue.length; i++) {
+    let ch = stringValue.charAt(i);
+    switch (ch) {
+      case delim:
+        result += "\\" + delim;
+        break;
+      case "\n":
+        result += "\\n";
+        break;
+      case "\r":
+        result += "\\r";
+        break;
+      case "\\":
+        result += "\\\\";
+        break;
+      case "\u2028":
+        result += "\\u2028";
+        break;
+      case "\u2029":
+        result += "\\u2029";
+        break;
+      default:
+        result += ch;
+        break;
+    }
+  }
+  result += delim;
+  return result;
+};
diff --git a/packages/babel-plugin-transform-property-literals/src/index.js b/packages/babel-plugin-transform-property-literals/src/index.js
index e890bbc..d9b63d5 100644
--- a/packages/babel-plugin-transform-property-literals/src/index.js
+++ b/packages/babel-plugin-transform-property-literals/src/index.js
@@ -1,27 +1,24 @@
 "use strict";
 
+const { reduceStaticPropertyNameES5 } = require("./property-name");
+
 module.exports = function({ types: t }) {
   return {
     name: "transform-property-literals",
     visitor: {
       // { 'foo': 'bar' } -> { foo: 'bar' }
       ObjectProperty: {
-        exit({ node }) {
-          const key = node.key;
-          if (!t.isStringLiteral(key)) {
+        exit(path) {
+          const key = path.get("key");
+          if (!key.isStringLiteral()) {
             return;
           }
 
-          if (key.value.match(/^\d+$/)) {
-            const newProp = parseInt(node.key.value, 10);
-            if (newProp.toString() === node.key.value) {
-              node.key = t.numericLiteral(newProp);
-              node.computed = false;
-            }
-          } else if (t.isValidIdentifier(key.value)) {
-            node.key = t.identifier(key.value);
-            node.computed = false;
-          }
+          const newNode = t.clone(path.node);
+          newNode.key = reduceStaticPropertyNameES5(t, key.node);
+          newNode.computed = false;
+
+          path.replaceWith(newNode);
         }
       }
     }
diff --git a/packages/babel-plugin-transform-property-literals/src/property-name.js b/packages/babel-plugin-transform-property-literals/src/property-name.js
new file mode 100644
index 0000000..45ad695
--- /dev/null
+++ b/packages/babel-plugin-transform-property-literals/src/property-name.js
@@ -0,0 +1,45 @@
+"use strict";
+
+const { keyword } = require("esutils");
+const escapeStringLiteral = require("./escape-string-literal");
+
+module.exports = {
+  reduceStaticPropertyNameES5
+};
+
+/**
+ *
+ * Original Source:
+ * https://github.com/shapesecurity/shift-codegen-js/blob/0d09bd8a/src/minimal-codegen.js#L635
+ *
+ * This implementation modifies the original source in the following ways
+ * + Check for ES5 Identifier name instead of ES6 Identifier name
+ * + Use Babel-Types & Babel's AST instead of ShiftAST
+ *
+ * LICENSE
+
+Copyright 2014 Shape Security, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+ */
+function reduceStaticPropertyNameES5(t, node) {
+  if (keyword.isIdentifierNameES5(node.value)) {
+    return t.Identifier(node.value);
+  }
+  let n = parseFloat(node.value);
+  if (n >= 0 && n.toString() === node.value) {
+    return t.NumericLiteral(n);
+  }
+  return t.Identifier(escapeStringLiteral(node.value));
+}


Test patch:
diff --git a/packages/babel-plugin-transform-property-literals/__tests__/transform-property-literals-test.js b/packages/babel-plugin-transform-property-literals/__tests__/transform-property-literals-test.js
index 7dc2ab0..4bb30ed 100644
--- a/packages/babel-plugin-transform-property-literals/__tests__/transform-property-literals-test.js
+++ b/packages/babel-plugin-transform-property-literals/__tests__/transform-property-literals-test.js
@@ -40,7 +40,13 @@ describe("transform-property-literals-plugin", () => {
         "import": null
       });
     `);
-    expect(transform(source)).toBe(source);
+    const expected = unpad(`
+      ({
+        default: null,
+        import: null
+      });
+    `);
+    expect(transform(source)).toBe(expected);
   });
 
   it("should not transform non-string properties", () => {
@@ -60,4 +66,48 @@ describe("transform-property-literals-plugin", () => {
     `);
     expect(transform(source)).toBe(source);
   });
+
+  it("should not transform invalid es5 property names", () => {
+    const source = unpad(`
+      ({
+        "\u2118": "wp",
+        "𐊧": "foo"
+      });
+    `);
+    expect(transform(source)).toBe(source);
+  });
+
+  it("should transform valid ES5 unicodes as property names", () => {
+    const source = unpad(`
+      ({
+        "ಠ_ಠ": "bar",
+        "12e34": "wut",
+        "\u01FC": "AE"
+      })
+    `);
+    const expected = unpad(`
+      ({
+        ಠ_ಠ: "bar",
+        "12e34": "wut",
+        \u01FC: "AE"
+      });
+    `);
+    expect(transform(source)).toBe(expected);
+  });
+
+  it("should transform computed properties which are strings", () => {
+    const source = unpad(`
+      ({
+        [ಠ_ಠ]: "foo",
+        ["ಠ_ಠ"]: "bar"
+      });
+    `);
+    const expected = unpad(`
+      ({
+        [ಠ_ಠ]: "foo",
+        ಠ_ಠ: "bar"
+      });
+    `);
+    expect(transform(source)).toBe(expected);
+  });
 });