[Pkg-javascript-commits] [node-recast] 01/03: New upstream version 0.12.2

Julien Puydt julien.puydt at laposte.net
Thu Mar 23 20:47:18 UTC 2017


This is an automated email from the git hooks/post-receive script.

jpuydt-guest pushed a commit to branch master
in repository node-recast.

commit 7fedcb88c39f0ca45452cc2bcd55579fe6ac67ec
Author: Julien Puydt <julien.puydt at laposte.net>
Date:   Thu Mar 23 21:24:24 2017 +0100

    New upstream version 0.12.2
---
 .travis.yml      |   14 +-
 README.md        |    2 +-
 lib/fast-path.js |  705 ++++++-------
 lib/parser.js    |  267 ++---
 lib/patcher.js   |  827 +++++++--------
 lib/printer.js   |    6 +-
 lib/util.js      |   37 +-
 package.json     |   12 +-
 test/babylon.js  |   40 +-
 test/printer.js  | 2988 +++++++++++++++++++++++++++---------------------------
 10 files changed, 2502 insertions(+), 2396 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 7b3fa17..83ec23f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,18 +1,12 @@
 language: node_js
 node_js:
-  - "7.0"
-  - "6.0"
-  - "5.0"
-  - "4.0"
-  - "iojs"
+  - "7"
+  - "6"
+  - "5"
+  - "4"
   - "0.12"
   - "0.11"
   - "0.10"
-  - "0.8"
-
-matrix:
-  allow_failures:
-    - node_js: "0.8"
 
 # Allow Travis tests to run in containers.
 sudo: false
diff --git a/README.md b/README.md
index 9ef9299..6d2d91b 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# recast, _v_. [![Build Status](https://travis-ci.org/benjamn/recast.svg?branch=master)](https://travis-ci.org/benjamn/recast) [![Join the chat at https://gitter.im/benjamn/recast](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/benjamn/recast?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+# recast, _v_. [![Build Status](https://travis-ci.org/benjamn/recast.svg?branch=master)](https://travis-ci.org/benjamn/recast) [![Join the chat at https://gitter.im/benjamn/recast](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/benjamn/recast?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Greenkeeper badge](https://badges.greenkeeper.io/benjamn/recast.svg)](https://greenkeeper.io/)
 
 1. to give (a metal object) a different form by melting it down and reshaping it.
 1. to form, fashion, or arrange again.
diff --git a/lib/fast-path.js b/lib/fast-path.js
index 017c57a..b8c8ffb 100644
--- a/lib/fast-path.js
+++ b/lib/fast-path.js
@@ -6,8 +6,8 @@ var isArray = types.builtInTypes.array;
 var isNumber = types.builtInTypes.number;
 
 function FastPath(value) {
-    assert.ok(this instanceof FastPath);
-    this.stack = [value];
+  assert.ok(this instanceof FastPath);
+  this.stack = [value];
 }
 
 var FPp = FastPath.prototype;
@@ -15,71 +15,77 @@ module.exports = FastPath;
 
 // Static convenience function for coercing a value to a FastPath.
 FastPath.from = function(obj) {
-    if (obj instanceof FastPath) {
-        // Return a defensive copy of any existing FastPath instances.
-        return obj.copy();
-    }
-
-    if (obj instanceof types.NodePath) {
-        // For backwards compatibility, unroll NodePath instances into
-        // lightweight FastPath [..., name, value] stacks.
-        var copy = Object.create(FastPath.prototype);
-        var stack = [obj.value];
-        for (var pp; (pp = obj.parentPath); obj = pp)
-            stack.push(obj.name, pp.value);
-        copy.stack = stack.reverse();
-        return copy;
-    }
+  if (obj instanceof FastPath) {
+    // Return a defensive copy of any existing FastPath instances.
+    return obj.copy();
+  }
+
+  if (obj instanceof types.NodePath) {
+    // For backwards compatibility, unroll NodePath instances into
+    // lightweight FastPath [..., name, value] stacks.
+    var copy = Object.create(FastPath.prototype);
+    var stack = [obj.value];
+    for (var pp; (pp = obj.parentPath); obj = pp)
+      stack.push(obj.name, pp.value);
+    copy.stack = stack.reverse();
+    return copy;
+  }
 
-    // Otherwise use obj as the value of the new FastPath instance.
-    return new FastPath(obj);
+  // Otherwise use obj as the value of the new FastPath instance.
+  return new FastPath(obj);
 };
 
 FPp.copy = function copy() {
-    var copy = Object.create(FastPath.prototype);
-    copy.stack = this.stack.slice(0);
-    return copy;
+  var copy = Object.create(FastPath.prototype);
+  copy.stack = this.stack.slice(0);
+  return copy;
 };
 
 // The name of the current property is always the penultimate element of
 // this.stack, and always a String.
 FPp.getName = function getName() {
-    var s = this.stack;
-    var len = s.length;
-    if (len > 1) {
-        return s[len - 2];
-    }
-    // Since the name is always a string, null is a safe sentinel value to
-    // return if we do not know the name of the (root) value.
-    return null;
+  var s = this.stack;
+  var len = s.length;
+  if (len > 1) {
+    return s[len - 2];
+  }
+  // Since the name is always a string, null is a safe sentinel value to
+  // return if we do not know the name of the (root) value.
+  return null;
 };
 
 // The value of the current property is always the final element of
 // this.stack.
 FPp.getValue = function getValue() {
-    var s = this.stack;
-    return s[s.length - 1];
+  var s = this.stack;
+  return s[s.length - 1];
+};
+
+FPp.valueIsDuplicate = function () {
+  var s = this.stack;
+  var valueIndex = s.length - 1;
+  return s.lastIndexOf(s[valueIndex], valueIndex - 1) >= 0;
 };
 
 function getNodeHelper(path, count) {
-    var s = path.stack;
+  var s = path.stack;
 
-    for (var i = s.length - 1; i >= 0; i -= 2) {
-        var value = s[i];
-        if (n.Node.check(value) && --count < 0) {
-            return value;
-        }
+  for (var i = s.length - 1; i >= 0; i -= 2) {
+    var value = s[i];
+    if (n.Node.check(value) && --count < 0) {
+      return value;
     }
+  }
 
-    return null;
+  return null;
 }
 
 FPp.getNode = function getNode(count) {
-    return getNodeHelper(this, ~~count);
+  return getNodeHelper(this, ~~count);
 };
 
 FPp.getParentNode = function getParentNode(count) {
-    return getNodeHelper(this, ~~count + 1);
+  return getNodeHelper(this, ~~count + 1);
 };
 
 // The length of the stack can be either even or odd, depending on whether
@@ -88,11 +94,11 @@ FPp.getParentNode = function getParentNode(count) {
 // even, though, which allows us to return the root value in constant time
 // (i.e. without iterating backwards through the stack).
 FPp.getRootValue = function getRootValue() {
-    var s = this.stack;
-    if (s.length % 2 === 0) {
-        return s[1];
-    }
-    return s[0];
+  var s = this.stack;
+  if (s.length % 2 === 0) {
+    return s[1];
+  }
+  return s[0];
 };
 
 // Temporarily push properties named by string arguments given after the
@@ -101,18 +107,18 @@ FPp.getRootValue = function getRootValue() {
 // be restored to its original state after the callback is finished, so it
 // is probably a mistake to retain a reference to the path.
 FPp.call = function call(callback/*, name1, name2, ... */) {
-    var s = this.stack;
-    var origLen = s.length;
-    var value = s[origLen - 1];
-    var argc = arguments.length;
-    for (var i = 1; i < argc; ++i) {
-        var name = arguments[i];
-        value = value[name];
-        s.push(name, value);
-    }
-    var result = callback(this);
-    s.length = origLen;
-    return result;
+  var s = this.stack;
+  var origLen = s.length;
+  var value = s[origLen - 1];
+  var argc = arguments.length;
+  for (var i = 1; i < argc; ++i) {
+    var name = arguments[i];
+    value = value[name];
+    s.push(name, value);
+  }
+  var result = callback(this);
+  s.length = origLen;
+  return result;
 };
 
 // Similar to FastPath.prototype.call, except that the value obtained by
@@ -120,254 +126,253 @@ FPp.call = function call(callback/*, name1, name2, ... */) {
 // callback will be called with a reference to this path object for each
 // element of the array.
 FPp.each = function each(callback/*, name1, name2, ... */) {
-    var s = this.stack;
-    var origLen = s.length;
-    var value = s[origLen - 1];
-    var argc = arguments.length;
-
-    for (var i = 1; i < argc; ++i) {
-        var name = arguments[i];
-        value = value[name];
-        s.push(name, value);
+  var s = this.stack;
+  var origLen = s.length;
+  var value = s[origLen - 1];
+  var argc = arguments.length;
+
+  for (var i = 1; i < argc; ++i) {
+    var name = arguments[i];
+    value = value[name];
+    s.push(name, value);
+  }
+
+  for (var i = 0; i < value.length; ++i) {
+    if (i in value) {
+      s.push(i, value[i]);
+      // If the callback needs to know the value of i, call
+      // path.getName(), assuming path is the parameter name.
+      callback(this);
+      s.length -= 2;
     }
+  }
 
-    for (var i = 0; i < value.length; ++i) {
-        if (i in value) {
-            s.push(i, value[i]);
-            // If the callback needs to know the value of i, call
-            // path.getName(), assuming path is the parameter name.
-            callback(this);
-            s.length -= 2;
-        }
-    }
-
-    s.length = origLen;
+  s.length = origLen;
 };
 
 // Similar to FastPath.prototype.each, except that the results of the
 // callback function invocations are stored in an array and returned at
 // the end of the iteration.
 FPp.map = function map(callback/*, name1, name2, ... */) {
-    var s = this.stack;
-    var origLen = s.length;
-    var value = s[origLen - 1];
-    var argc = arguments.length;
-
-    for (var i = 1; i < argc; ++i) {
-        var name = arguments[i];
-        value = value[name];
-        s.push(name, value);
-    }
-
-    var result = new Array(value.length);
-
-    for (var i = 0; i < value.length; ++i) {
-        if (i in value) {
-            s.push(i, value[i]);
-            result[i] = callback(this, i);
-            s.length -= 2;
-        }
+  var s = this.stack;
+  var origLen = s.length;
+  var value = s[origLen - 1];
+  var argc = arguments.length;
+
+  for (var i = 1; i < argc; ++i) {
+    var name = arguments[i];
+    value = value[name];
+    s.push(name, value);
+  }
+
+  var result = new Array(value.length);
+
+  for (var i = 0; i < value.length; ++i) {
+    if (i in value) {
+      s.push(i, value[i]);
+      result[i] = callback(this, i);
+      s.length -= 2;
     }
+  }
 
-    s.length = origLen;
+  s.length = origLen;
 
-    return result;
+  return result;
 };
 
 // Inspired by require("ast-types").NodePath.prototype.needsParens, but
 // more efficient because we're iterating backwards through a stack.
 FPp.needsParens = function(assumeExpressionContext) {
-    var parent = this.getParentNode();
-    if (!parent) {
-        return false;
-    }
+  var parent = this.getParentNode();
+  if (!parent) {
+    return false;
+  }
 
-    var name = this.getName();
-    var node = this.getNode();
+  var name = this.getName();
+  var node = this.getNode();
 
-    // If the value of this path is some child of a Node and not a Node
-    // itself, then it doesn't need parentheses. Only Node objects (in
-    // fact, only Expression nodes) need parentheses.
-    if (this.getValue() !== node) {
-        return false;
-    }
+  // If the value of this path is some child of a Node and not a Node
+  // itself, then it doesn't need parentheses. Only Node objects (in fact,
+  // only Expression nodes) need parentheses.
+  if (this.getValue() !== node) {
+    return false;
+  }
 
-    // Only statements don't need parentheses.
-    if (n.Statement.check(node)) {
-        return false;
-    }
+  // Only statements don't need parentheses.
+  if (n.Statement.check(node)) {
+    return false;
+  }
+
+  // Identifiers never need parentheses.
+  if (node.type === "Identifier") {
+    return false;
+  }
 
-    // Identifiers never need parentheses.
-    if (node.type === "Identifier") {
-        return false;
+  if (parent.type === "ParenthesizedExpression") {
+    return false;
+  }
+
+  switch (node.type) {
+  case "UnaryExpression":
+  case "SpreadElement":
+  case "SpreadProperty":
+    return parent.type === "MemberExpression"
+      && name === "object"
+      && parent.object === node;
+
+  case "BinaryExpression":
+  case "LogicalExpression":
+    switch (parent.type) {
+    case "CallExpression":
+      return name === "callee"
+        && parent.callee === node;
+
+    case "UnaryExpression":
+    case "SpreadElement":
+    case "SpreadProperty":
+      return true;
+
+    case "MemberExpression":
+      return name === "object"
+        && parent.object === node;
+
+    case "BinaryExpression":
+    case "LogicalExpression":
+      var po = parent.operator;
+      var pp = PRECEDENCE[po];
+      var no = node.operator;
+      var np = PRECEDENCE[no];
+
+      if (pp > np) {
+        return true;
+      }
+
+      if (pp === np && name === "right") {
+        assert.strictEqual(parent.right, node);
+        return true;
+      }
+
+    default:
+      return false;
     }
 
-    if (parent.type === "ParenthesizedExpression") {
-        return false;
+  case "SequenceExpression":
+    switch (parent.type) {
+    case "ReturnStatement":
+      return false;
+
+    case "ForStatement":
+      // Although parentheses wouldn't hurt around sequence expressions in
+      // the head of for loops, traditional style dictates that e.g. i++,
+      // j++ should not be wrapped with parentheses.
+      return false;
+
+    case "ExpressionStatement":
+      return name !== "expression";
+
+    default:
+      // Otherwise err on the side of overparenthesization, adding
+      // explicit exceptions above if this proves overzealous.
+      return true;
     }
 
-    switch (node.type) {
+  case "YieldExpression":
+    switch (parent.type) {
+    case "BinaryExpression":
+    case "LogicalExpression":
     case "UnaryExpression":
     case "SpreadElement":
     case "SpreadProperty":
-        return parent.type === "MemberExpression"
-            && name === "object"
-            && parent.object === node;
+    case "CallExpression":
+    case "MemberExpression":
+    case "NewExpression":
+    case "ConditionalExpression":
+    case "YieldExpression":
+      return true;
 
+    default:
+      return false;
+    }
+
+  case "IntersectionTypeAnnotation":
+  case "UnionTypeAnnotation":
+    return parent.type === "NullableTypeAnnotation";
+
+  case "Literal":
+    return parent.type === "MemberExpression"
+      && isNumber.check(node.value)
+      && name === "object"
+      && parent.object === node;
+
+  case "AssignmentExpression":
+  case "ConditionalExpression":
+    switch (parent.type) {
+    case "UnaryExpression":
+    case "SpreadElement":
+    case "SpreadProperty":
     case "BinaryExpression":
     case "LogicalExpression":
-        switch (parent.type) {
-        case "CallExpression":
-            return name === "callee"
-                && parent.callee === node;
-
-        case "UnaryExpression":
-        case "SpreadElement":
-        case "SpreadProperty":
-            return true;
-
-        case "MemberExpression":
-            return name === "object"
-                && parent.object === node;
-
-        case "BinaryExpression":
-        case "LogicalExpression":
-            var po = parent.operator;
-            var pp = PRECEDENCE[po];
-            var no = node.operator;
-            var np = PRECEDENCE[no];
-
-            if (pp > np) {
-                return true;
-            }
-
-            if (pp === np && name === "right") {
-                assert.strictEqual(parent.right, node);
-                return true;
-            }
-
-        default:
-            return false;
-        }
-
-    case "SequenceExpression":
-        switch (parent.type) {
-        case "ReturnStatement":
-            return false;
-
-        case "ForStatement":
-            // Although parentheses wouldn't hurt around sequence
-            // expressions in the head of for loops, traditional style
-            // dictates that e.g. i++, j++ should not be wrapped with
-            // parentheses.
-            return false;
-
-        case "ExpressionStatement":
-            return name !== "expression";
-
-        default:
-            // Otherwise err on the side of overparenthesization, adding
-            // explicit exceptions above if this proves overzealous.
-            return true;
-        }
+      return true;
+
+    case "CallExpression":
+      return name === "callee"
+        && parent.callee === node;
 
-    case "YieldExpression":
-        switch (parent.type) {
-        case "BinaryExpression":
-        case "LogicalExpression":
-        case "UnaryExpression":
-        case "SpreadElement":
-        case "SpreadProperty":
-        case "CallExpression":
-        case "MemberExpression":
-        case "NewExpression":
-        case "ConditionalExpression":
-        case "YieldExpression":
-            return true;
-
-        default:
-            return false;
-        }
-
-    case "IntersectionTypeAnnotation":
-    case "UnionTypeAnnotation":
-        return parent.type === "NullableTypeAnnotation";
-
-    case "Literal":
-        return parent.type === "MemberExpression"
-            && isNumber.check(node.value)
-            && name === "object"
-            && parent.object === node;
-
-    case "AssignmentExpression":
     case "ConditionalExpression":
-        switch (parent.type) {
-        case "UnaryExpression":
-        case "SpreadElement":
-        case "SpreadProperty":
-        case "BinaryExpression":
-        case "LogicalExpression":
-            return true;
-
-        case "CallExpression":
-            return name === "callee"
-                && parent.callee === node;
-
-        case "ConditionalExpression":
-            return name === "test"
-                && parent.test === node;
-
-        case "MemberExpression":
-            return name === "object"
-                && parent.object === node;
-
-        default:
-            return false;
-        }
-
-    case "ArrowFunctionExpression":
-        if(n.CallExpression.check(parent) && name === 'callee') {
-            return true;
-        }
-        if(n.MemberExpression.check(parent) && name === 'object') {
-            return true;
-        }
-
-        return isBinary(parent);
-
-    case "ObjectExpression":
-        if (parent.type === "ArrowFunctionExpression" &&
-            name === "body") {
-            return true;
-        }
+      return name === "test"
+        && parent.test === node;
+
+    case "MemberExpression":
+      return name === "object"
+        && parent.object === node;
 
     default:
-        if (parent.type === "NewExpression" &&
-            name === "callee" &&
-            parent.callee === node) {
-            return containsCallExpression(node);
-        }
+      return false;
     }
 
-    if (assumeExpressionContext !== true &&
-        !this.canBeFirstInStatement() &&
-        this.firstInStatement())
-        return true;
+  case "ArrowFunctionExpression":
+    if(n.CallExpression.check(parent) && name === 'callee') {
+      return true;
+    }
+    if(n.MemberExpression.check(parent) && name === 'object') {
+      return true;
+    }
 
-    return false;
+    return isBinary(parent);
+
+  case "ObjectExpression":
+    if (parent.type === "ArrowFunctionExpression" &&
+        name === "body") {
+      return true;
+    }
+
+  default:
+    if (parent.type === "NewExpression" &&
+        name === "callee" &&
+        parent.callee === node) {
+      return containsCallExpression(node);
+    }
+  }
+
+  if (assumeExpressionContext !== true &&
+      !this.canBeFirstInStatement() &&
+      this.firstInStatement())
+    return true;
+
+  return false;
 };
 
 function isBinary(node) {
-    return n.BinaryExpression.check(node)
-        || n.LogicalExpression.check(node);
+  return n.BinaryExpression.check(node)
+    || n.LogicalExpression.check(node);
 }
 
 function isUnaryLike(node) {
-    return n.UnaryExpression.check(node)
-        // I considered making SpreadElement and SpreadProperty subtypes
-        // of UnaryExpression, but they're not really Expression nodes.
-        || (n.SpreadElement && n.SpreadElement.check(node))
-        || (n.SpreadProperty && n.SpreadProperty.check(node));
+  return n.UnaryExpression.check(node)
+  // I considered making SpreadElement and SpreadProperty subtypes of
+  // UnaryExpression, but they're not really Expression nodes.
+    || (n.SpreadElement && n.SpreadElement.check(node))
+    || (n.SpreadProperty && n.SpreadProperty.check(node));
 }
 
 var PRECEDENCE = {};
@@ -382,105 +387,105 @@ var PRECEDENCE = {};
  ["+", "-"],
  ["*", "/", "%", "**"]
 ].forEach(function(tier, i) {
-    tier.forEach(function(op) {
-        PRECEDENCE[op] = i;
-    });
+  tier.forEach(function(op) {
+    PRECEDENCE[op] = i;
+  });
 });
 
 function containsCallExpression(node) {
-    if (n.CallExpression.check(node)) {
-        return true;
-    }
+  if (n.CallExpression.check(node)) {
+    return true;
+  }
 
-    if (isArray.check(node)) {
-        return node.some(containsCallExpression);
-    }
+  if (isArray.check(node)) {
+    return node.some(containsCallExpression);
+  }
 
-    if (n.Node.check(node)) {
-        return types.someField(node, function(name, child) {
-            return containsCallExpression(child);
-        });
-    }
+  if (n.Node.check(node)) {
+    return types.someField(node, function(name, child) {
+      return containsCallExpression(child);
+    });
+  }
 
-    return false;
+  return false;
 }
 
 FPp.canBeFirstInStatement = function() {
-    var node = this.getNode();
-    return !n.FunctionExpression.check(node)
-        && !n.ObjectExpression.check(node);
+  var node = this.getNode();
+  return !n.FunctionExpression.check(node)
+    && !n.ObjectExpression.check(node);
 };
 
 FPp.firstInStatement = function() {
-    var s = this.stack;
-    var parentName, parent;
-    var childName, child;
-
-    for (var i = s.length - 1; i >= 0; i -= 2) {
-        if (n.Node.check(s[i])) {
-            childName = parentName;
-            child = parent;
-            parentName = s[i - 1];
-            parent = s[i];
-        }
-
-        if (!parent || !child) {
-            continue;
-        }
-
-        if (n.BlockStatement.check(parent) &&
-            parentName === "body" &&
-            childName === 0) {
-            assert.strictEqual(parent.body[0], child);
-            return true;
-        }
-
-        if (n.ExpressionStatement.check(parent) &&
-            childName === "expression") {
-            assert.strictEqual(parent.expression, child);
-            return true;
-        }
-
-        if (n.SequenceExpression.check(parent) &&
-            parentName === "expressions" &&
-            childName === 0) {
-            assert.strictEqual(parent.expressions[0], child);
-            continue;
-        }
-
-        if (n.CallExpression.check(parent) &&
-            childName === "callee") {
-            assert.strictEqual(parent.callee, child);
-            continue;
-        }
-
-        if (n.MemberExpression.check(parent) &&
-            childName === "object") {
-            assert.strictEqual(parent.object, child);
-            continue;
-        }
-
-        if (n.ConditionalExpression.check(parent) &&
-            childName === "test") {
-            assert.strictEqual(parent.test, child);
-            continue;
-        }
-
-        if (isBinary(parent) &&
-            childName === "left") {
-            assert.strictEqual(parent.left, child);
-            continue;
-        }
-
-        if (n.UnaryExpression.check(parent) &&
-            !parent.prefix &&
-            childName === "argument") {
-            assert.strictEqual(parent.argument, child);
-            continue;
-        }
-
-        return false;
+  var s = this.stack;
+  var parentName, parent;
+  var childName, child;
+
+  for (var i = s.length - 1; i >= 0; i -= 2) {
+    if (n.Node.check(s[i])) {
+      childName = parentName;
+      child = parent;
+      parentName = s[i - 1];
+      parent = s[i];
     }
 
-    return true;
+    if (!parent || !child) {
+      continue;
+    }
+
+    if (n.BlockStatement.check(parent) &&
+        parentName === "body" &&
+        childName === 0) {
+      assert.strictEqual(parent.body[0], child);
+      return true;
+    }
+
+    if (n.ExpressionStatement.check(parent) &&
+        childName === "expression") {
+      assert.strictEqual(parent.expression, child);
+      return true;
+    }
+
+    if (n.SequenceExpression.check(parent) &&
+        parentName === "expressions" &&
+        childName === 0) {
+      assert.strictEqual(parent.expressions[0], child);
+      continue;
+    }
+
+    if (n.CallExpression.check(parent) &&
+        childName === "callee") {
+      assert.strictEqual(parent.callee, child);
+      continue;
+    }
+
+    if (n.MemberExpression.check(parent) &&
+        childName === "object") {
+      assert.strictEqual(parent.object, child);
+      continue;
+    }
+
+    if (n.ConditionalExpression.check(parent) &&
+        childName === "test") {
+      assert.strictEqual(parent.test, child);
+      continue;
+    }
+
+    if (isBinary(parent) &&
+        childName === "left") {
+      assert.strictEqual(parent.left, child);
+      continue;
+    }
+
+    if (n.UnaryExpression.check(parent) &&
+        !parent.prefix &&
+        childName === "argument") {
+      assert.strictEqual(parent.argument, child);
+      continue;
+    }
+
+    return false;
+  }
+
+  return true;
 };
diff --git a/lib/parser.js b/lib/parser.js
index 60a5164..17f3b8a 100644
--- a/lib/parser.js
+++ b/lib/parser.js
@@ -10,149 +10,162 @@ var normalizeOptions = require("./options").normalize;
 var fromString = require("./lines").fromString;
 var attachComments = require("./comments").attach;
 var util = require("./util");
+var Map = global.Map || require("core-js/es6/map");
 
 exports.parse = function parse(source, options) {
-    options = normalizeOptions(options);
-
-    var lines = fromString(source, options);
-
-    var sourceWithoutTabs = lines.toString({
-        tabWidth: options.tabWidth,
-        reuseWhitespace: false,
-        useTabs: false
-    });
-
-    var comments = [];
-    var program = options.parser.parse(sourceWithoutTabs, {
-        jsx: true,
-        loc: true,
-        locations: true,
-        range: options.range,
-        comment: true,
-        onComment: comments,
-        tolerant: options.tolerant,
-        ecmaVersion: 6,
-        sourceType: 'module'
-    });
-
-    // If the source was empty, some parsers give loc.{start,end}.line
-    // values of 0, instead of the minimum of 1.
-    util.fixFaultyLocations(program, lines);
-
-    program.loc = program.loc || {
-        start: lines.firstPos(),
-        end: lines.lastPos()
+  options = normalizeOptions(options);
+
+  var lines = fromString(source, options);
+
+  var sourceWithoutTabs = lines.toString({
+    tabWidth: options.tabWidth,
+    reuseWhitespace: false,
+    useTabs: false
+  });
+
+  var comments = [];
+  var program = options.parser.parse(sourceWithoutTabs, {
+    jsx: true,
+    loc: true,
+    locations: true,
+    range: options.range,
+    comment: true,
+    onComment: comments,
+    tolerant: options.tolerant,
+    ecmaVersion: 6,
+    sourceType: 'module'
+  });
+
+  // If the source was empty, some parsers give loc.{start,end}.line
+  // values of 0, instead of the minimum of 1.
+  util.fixFaultyLocations(program, lines);
+
+  program.loc = program.loc || {
+    start: lines.firstPos(),
+    end: lines.lastPos()
+  };
+
+  program.loc.lines = lines;
+  program.loc.indent = 0;
+
+  // Expand the Program node's .loc to include all comments, since
+  // typically its .loc.start and .loc.end will coincide with those of the
+  // first and last statements, respectively, excluding any comments that
+  // fall outside that region.
+  var trueProgramLoc = util.getTrueLoc(program, lines);
+  program.loc.start = trueProgramLoc.start;
+  program.loc.end = trueProgramLoc.end;
+
+  if (program.comments) {
+    comments = program.comments;
+    delete program.comments;
+  }
+
+  // In order to ensure we reprint leading and trailing program comments,
+  // wrap the original Program node with a File node.
+  var file = program;
+  if (file.type === "Program") {
+    var file = b.file(program, options.sourceFileName || null);
+    file.loc = {
+      lines: lines,
+      indent: 0,
+      start: lines.firstPos(),
+      end: lines.lastPos()
     };
-
-    program.loc.lines = lines;
-    program.loc.indent = 0;
-
-    // Expand the Program node's .loc to include all comments, since
-    // typically its .loc.start and .loc.end will coincide with those of
-    // the first and last statements, respectively, excluding any comments
-    // that fall outside that region.
-    var trueProgramLoc = util.getTrueLoc(program, lines);
-    program.loc.start = trueProgramLoc.start;
-    program.loc.end = trueProgramLoc.end;
-
-    if (program.comments) {
-        comments = program.comments;
-        delete program.comments;
-    }
-
-    // In order to ensure we reprint leading and trailing program
-    // comments, wrap the original Program node with a File node.
-    var file = program;
-    if (file.type === "Program") {
-        var file = b.file(program, options.sourceFileName || null);
-        file.loc = {
-            lines: lines,
-            indent: 0,
-            start: lines.firstPos(),
-            end: lines.lastPos()
-        };
-    } else if (file.type === "File") {
-      program = file.program;
-    }
-
-    // Passing file.program here instead of just file means that initial
-    // comments will be attached to program.body[0] instead of program.
-    attachComments(
-        comments,
-        program.body.length ? file.program : file,
-        lines
-    );
-
-    // Return a copy of the original AST so that any changes made may be
-    // compared to the original.
-    return new TreeCopier(lines).copy(file);
+  } else if (file.type === "File") {
+    program = file.program;
+  }
+
+  // Passing file.program here instead of just file means that initial
+  // comments will be attached to program.body[0] instead of program.
+  attachComments(
+    comments,
+    program.body.length ? file.program : file,
+    lines
+  );
+
+  // Return a copy of the original AST so that any changes made may be
+  // compared to the original.
+  return new TreeCopier(lines).copy(file);
 };
 
 function TreeCopier(lines) {
-    assert.ok(this instanceof TreeCopier);
-    this.lines = lines;
-    this.indent = 0;
+  assert.ok(this instanceof TreeCopier);
+  this.lines = lines;
+  this.indent = 0;
+  this.seen = new Map;
 }
 
 var TCp = TreeCopier.prototype;
 
 TCp.copy = function(node) {
-    if (isArray.check(node)) {
-        return node.map(this.copy, this);
-    }
+  if (this.seen.has(node)) {
+    return this.seen.get(node);
+  }
+
+  if (isArray.check(node)) {
+    var copy = new Array(node.length);
+    this.seen.set(node, copy);
+    node.forEach(function (item, i) {
+      copy[i] = this.copy(item);
+    }, this);
+    return copy;
+  }
 
-    if (!isObject.check(node)) {
-        return node;
-    }
+  if (!isObject.check(node)) {
+    return node;
+  }
 
-    util.fixFaultyLocations(node, this.lines);
-
-    var copy = Object.create(Object.getPrototypeOf(node), {
-        original: { // Provide a link from the copy to the original.
-            value: node,
-            configurable: false,
-            enumerable: false,
-            writable: true
-        }
-    });
-
-    var loc = node.loc;
-    var oldIndent = this.indent;
-    var newIndent = oldIndent;
-
-    if (loc) {
-        // When node is a comment, we set node.loc.indent to
-        // node.loc.start.column so that, when/if we print the comment by
-        // itself, we can strip that much whitespace from the left margin
-        // of the comment. This only really matters for multiline Block
-        // comments, but it doesn't hurt for Line comments.
-        if (node.type === "Block" || node.type === "Line" ||
-            node.type === "CommentBlock" || node.type === "CommentLine" ||
-            this.lines.isPrecededOnlyByWhitespace(loc.start)) {
-            newIndent = this.indent = loc.start.column;
-        }
-
-        loc.lines = this.lines;
-        loc.indent = newIndent;
+  util.fixFaultyLocations(node, this.lines);
+
+  var copy = Object.create(Object.getPrototypeOf(node), {
+    original: { // Provide a link from the copy to the original.
+      value: node,
+      configurable: false,
+      enumerable: false,
+      writable: true
+    }
+  });
+
+  this.seen.set(node, copy);
+
+  var loc = node.loc;
+  var oldIndent = this.indent;
+  var newIndent = oldIndent;
+
+  if (loc) {
+    // When node is a comment, we set node.loc.indent to
+    // node.loc.start.column so that, when/if we print the comment by
+    // itself, we can strip that much whitespace from the left margin of
+    // the comment. This only really matters for multiline Block comments,
+    // but it doesn't hurt for Line comments.
+    if (node.type === "Block" || node.type === "Line" ||
+        node.type === "CommentBlock" || node.type === "CommentLine" ||
+        this.lines.isPrecededOnlyByWhitespace(loc.start)) {
+      newIndent = this.indent = loc.start.column;
     }
 
-    var keys = Object.keys(node);
-    var keyCount = keys.length;
-    for (var i = 0; i < keyCount; ++i) {
-        var key = keys[i];
-        if (key === "loc") {
-            copy[key] = node[key];
-        } else if (key === "tokens" &&
-                   node.type === "File") {
-            // Preserve file.tokens (uncopied) in case client code cares
-            // about it, even though Recast ignores it when reprinting.
-            copy[key] = node[key];
-        } else {
-            copy[key] = this.copy(node[key]);
-        }
+    loc.lines = this.lines;
+    loc.indent = newIndent;
+  }
+
+  var keys = Object.keys(node);
+  var keyCount = keys.length;
+  for (var i = 0; i < keyCount; ++i) {
+    var key = keys[i];
+    if (key === "loc") {
+      copy[key] = node[key];
+    } else if (key === "tokens" &&
+               node.type === "File") {
+      // Preserve file.tokens (uncopied) in case client code cares about
+      // it, even though Recast ignores it when reprinting.
+      copy[key] = node[key];
+    } else {
+      copy[key] = this.copy(node[key]);
     }
+  }
 
-    this.indent = oldIndent;
+  this.indent = oldIndent;
 
-    return copy;
+  return copy;
 };
diff --git a/lib/patcher.js b/lib/patcher.js
index bddfe68..ce66b12 100644
--- a/lib/patcher.js
+++ b/lib/patcher.js
@@ -15,378 +15,391 @@ var isString = types.builtInTypes.string;
 var riskyAdjoiningCharExp = /[0-9a-z_$]/i;
 
 function Patcher(lines) {
-    assert.ok(this instanceof Patcher);
-    assert.ok(lines instanceof linesModule.Lines);
+  assert.ok(this instanceof Patcher);
+  assert.ok(lines instanceof linesModule.Lines);
 
-    var self = this,
-        replacements = [];
+  var self = this,
+  replacements = [];
 
-    self.replace = function(loc, lines) {
-        if (isString.check(lines))
-            lines = linesModule.fromString(lines);
+  self.replace = function(loc, lines) {
+    if (isString.check(lines))
+      lines = linesModule.fromString(lines);
 
-        replacements.push({
-            lines: lines,
-            start: loc.start,
-            end: loc.end
-        });
+    replacements.push({
+      lines: lines,
+      start: loc.start,
+      end: loc.end
+    });
+  };
+
+  self.get = function(loc) {
+    // If no location is provided, return the complete Lines object.
+    loc = loc || {
+      start: { line: 1, column: 0 },
+      end: { line: lines.length,
+             column: lines.getLineLength(lines.length) }
     };
 
-    self.get = function(loc) {
-        // If no location is provided, return the complete Lines object.
-        loc = loc || {
-            start: { line: 1, column: 0 },
-            end: { line: lines.length,
-                   column: lines.getLineLength(lines.length) }
-        };
-
-        var sliceFrom = loc.start,
-            toConcat = [];
-
-        function pushSlice(from, to) {
-            assert.ok(comparePos(from, to) <= 0);
-            toConcat.push(lines.slice(from, to));
-        }
-
-        replacements.sort(function(a, b) {
-            return comparePos(a.start, b.start);
-        }).forEach(function(rep) {
-            if (comparePos(sliceFrom, rep.start) > 0) {
-                // Ignore nested replacement ranges.
-            } else {
-                pushSlice(sliceFrom, rep.start);
-                toConcat.push(rep.lines);
-                sliceFrom = rep.end;
-            }
-        });
+    var sliceFrom = loc.start,
+    toConcat = [];
 
-        pushSlice(sliceFrom, loc.end);
+    function pushSlice(from, to) {
+      assert.ok(comparePos(from, to) <= 0);
+      toConcat.push(lines.slice(from, to));
+    }
 
-        return linesModule.concat(toConcat);
-    };
+    replacements.sort(function(a, b) {
+      return comparePos(a.start, b.start);
+    }).forEach(function(rep) {
+      if (comparePos(sliceFrom, rep.start) > 0) {
+        // Ignore nested replacement ranges.
+      } else {
+        pushSlice(sliceFrom, rep.start);
+        toConcat.push(rep.lines);
+        sliceFrom = rep.end;
+      }
+    });
+
+    pushSlice(sliceFrom, loc.end);
+
+    return linesModule.concat(toConcat);
+  };
 }
 exports.Patcher = Patcher;
 
 var Pp = Patcher.prototype;
 
 Pp.tryToReprintComments = function(newNode, oldNode, print) {
-    var patcher = this;
-
-    if (!newNode.comments &&
-        !oldNode.comments) {
-        // We were (vacuously) able to reprint all the comments!
-        return true;
-    }
+  var patcher = this;
 
-    var newPath = FastPath.from(newNode);
-    var oldPath = FastPath.from(oldNode);
-
-    newPath.stack.push("comments", getSurroundingComments(newNode));
-    oldPath.stack.push("comments", getSurroundingComments(oldNode));
-
-    var reprints = [];
-    var ableToReprintComments =
-        findArrayReprints(newPath, oldPath, reprints);
-
-    // No need to pop anything from newPath.stack or oldPath.stack, since
-    // newPath and oldPath are fresh local variables.
-
-    if (ableToReprintComments && reprints.length > 0) {
-        reprints.forEach(function(reprint) {
-            var oldComment = reprint.oldPath.getValue();
-            assert.ok(oldComment.leading || oldComment.trailing);
-            patcher.replace(
-                oldComment.loc,
-                // Comments can't have .comments, so it doesn't matter
-                // whether we print with comments or without.
-                print(reprint.newPath).indentTail(oldComment.loc.indent)
-            );
-        });
-    }
+  if (!newNode.comments &&
+      !oldNode.comments) {
+    // We were (vacuously) able to reprint all the comments!
+    return true;
+  }
+
+  var newPath = FastPath.from(newNode);
+  var oldPath = FastPath.from(oldNode);
+
+  newPath.stack.push("comments", getSurroundingComments(newNode));
+  oldPath.stack.push("comments", getSurroundingComments(oldNode));
+
+  var reprints = [];
+  var ableToReprintComments =
+    findArrayReprints(newPath, oldPath, reprints);
+
+  // No need to pop anything from newPath.stack or oldPath.stack, since
+  // newPath and oldPath are fresh local variables.
+
+  if (ableToReprintComments && reprints.length > 0) {
+    reprints.forEach(function(reprint) {
+      var oldComment = reprint.oldPath.getValue();
+      assert.ok(oldComment.leading || oldComment.trailing);
+      patcher.replace(
+        oldComment.loc,
+        // Comments can't have .comments, so it doesn't matter whether we
+        // print with comments or without.
+        print(reprint.newPath).indentTail(oldComment.loc.indent)
+      );
+    });
+  }
 
-    return ableToReprintComments;
+  return ableToReprintComments;
 };
 
 // Get all comments that are either leading or trailing, ignoring any
 // comments that occur inside node.loc. Returns an empty array for nodes
 // with no leading or trailing comments.
 function getSurroundingComments(node) {
-    var result = [];
-    if (node.comments &&
-        node.comments.length > 0) {
-        node.comments.forEach(function(comment) {
-            if (comment.leading || comment.trailing) {
-                result.push(comment);
-            }
-        });
-    }
-    return result;
+  var result = [];
+  if (node.comments &&
+      node.comments.length > 0) {
+    node.comments.forEach(function(comment) {
+      if (comment.leading || comment.trailing) {
+        result.push(comment);
+      }
+    });
+  }
+  return result;
 }
 
 Pp.deleteComments = function(node) {
-    if (!node.comments) {
-        return;
+  if (!node.comments) {
+    return;
+  }
+
+  var patcher = this;
+
+  node.comments.forEach(function(comment) {
+    if (comment.leading) {
+      // Delete leading comments along with any trailing whitespace they
+      // might have.
+      patcher.replace({
+        start: comment.loc.start,
+        end: node.loc.lines.skipSpaces(
+          comment.loc.end, false, false)
+      }, "");
+
+    } else if (comment.trailing) {
+      // Delete trailing comments along with any leading whitespace they
+      // might have.
+      patcher.replace({
+        start: node.loc.lines.skipSpaces(
+          comment.loc.start, true, false),
+        end: comment.loc.end
+      }, "");
     }
-
-    var patcher = this;
-
-    node.comments.forEach(function(comment) {
-        if (comment.leading) {
-            // Delete leading comments along with any trailing whitespace
-            // they might have.
-            patcher.replace({
-                start: comment.loc.start,
-                end: node.loc.lines.skipSpaces(
-                    comment.loc.end, false, false)
-            }, "");
-
-        } else if (comment.trailing) {
-            // Delete trailing comments along with any leading whitespace
-            // they might have.
-            patcher.replace({
-                start: node.loc.lines.skipSpaces(
-                    comment.loc.start, true, false),
-                end: comment.loc.end
-            }, "");
-        }
-    });
+  });
 };
 
 exports.getReprinter = function(path) {
-    assert.ok(path instanceof FastPath);
-
-    // Make sure that this path refers specifically to a Node, rather than
-    // some non-Node subproperty of a Node.
-    var node = path.getValue();
-    if (!Printable.check(node))
-        return;
-
-    var orig = node.original;
-    var origLoc = orig && orig.loc;
-    var lines = origLoc && origLoc.lines;
-    var reprints = [];
-
-    if (!lines || !findReprints(path, reprints))
-        return;
-
-    return function(print) {
-        var patcher = new Patcher(lines);
-
-        reprints.forEach(function(reprint) {
-            var newNode = reprint.newPath.getValue();
-            var oldNode = reprint.oldPath.getValue();
-
-            SourceLocation.assert(oldNode.loc, true);
-
-            var needToPrintNewPathWithComments =
-                !patcher.tryToReprintComments(newNode, oldNode, print)
-
-            if (needToPrintNewPathWithComments) {
-                // Since we were not able to preserve all leading/trailing
-                // comments, we delete oldNode's comments, print newPath
-                // with comments, and then patch the resulting lines where
-                // oldNode used to be.
-                patcher.deleteComments(oldNode);
-            }
-
-            var newLines = print(
-                reprint.newPath,
-                needToPrintNewPathWithComments
-            ).indentTail(oldNode.loc.indent);
-
-            var nls = needsLeadingSpace(lines, oldNode.loc, newLines);
-            var nts = needsTrailingSpace(lines, oldNode.loc, newLines);
-
-            // If we try to replace the argument of a ReturnStatement like
-            // return"asdf" with e.g. a literal null expression, we run
-            // the risk of ending up with returnnull, so we need to add an
-            // extra leading space in situations where that might
-            // happen. Likewise for "asdf"in obj. See #170.
-            if (nls || nts) {
-                var newParts = [];
-                nls && newParts.push(" ");
-                newParts.push(newLines);
-                nts && newParts.push(" ");
-                newLines = linesModule.concat(newParts);
-            }
-
-            patcher.replace(oldNode.loc, newLines);
-        });
+  assert.ok(path instanceof FastPath);
+
+  // Make sure that this path refers specifically to a Node, rather than
+  // some non-Node subproperty of a Node.
+  var node = path.getValue();
+  if (!Printable.check(node))
+    return;
+
+  var orig = node.original;
+  var origLoc = orig && orig.loc;
+  var lines = origLoc && origLoc.lines;
+  var reprints = [];
+
+  if (!lines || !findReprints(path, reprints))
+    return;
+
+  return function(print) {
+    var patcher = new Patcher(lines);
+
+    reprints.forEach(function(reprint) {
+      var newNode = reprint.newPath.getValue();
+      var oldNode = reprint.oldPath.getValue();
+
+      SourceLocation.assert(oldNode.loc, true);
+
+      var needToPrintNewPathWithComments =
+        !patcher.tryToReprintComments(newNode, oldNode, print)
+
+      if (needToPrintNewPathWithComments) {
+        // Since we were not able to preserve all leading/trailing
+        // comments, we delete oldNode's comments, print newPath with
+        // comments, and then patch the resulting lines where oldNode used
+        // to be.
+        patcher.deleteComments(oldNode);
+      }
+
+      var newLines = print(
+        reprint.newPath,
+        needToPrintNewPathWithComments
+      ).indentTail(oldNode.loc.indent);
+
+      var nls = needsLeadingSpace(lines, oldNode.loc, newLines);
+      var nts = needsTrailingSpace(lines, oldNode.loc, newLines);
+
+      // If we try to replace the argument of a ReturnStatement like
+      // return"asdf" with e.g. a literal null expression, we run the risk
+      // of ending up with returnnull, so we need to add an extra leading
+      // space in situations where that might happen. Likewise for
+      // "asdf"in obj. See #170.
+      if (nls || nts) {
+        var newParts = [];
+        nls && newParts.push(" ");
+        newParts.push(newLines);
+        nts && newParts.push(" ");
+        newLines = linesModule.concat(newParts);
+      }
+
+      patcher.replace(oldNode.loc, newLines);
+    });
 
-        // Recall that origLoc is the .loc of an ancestor node that is
-        // guaranteed to contain all the reprinted nodes and comments.
-        return patcher.get(origLoc).indentTail(-orig.loc.indent);
-    };
+    // Recall that origLoc is the .loc of an ancestor node that is
+    // guaranteed to contain all the reprinted nodes and comments.
+    return patcher.get(origLoc).indentTail(-orig.loc.indent);
+  };
 };
 
 // If the last character before oldLoc and the first character of newLines
 // are both identifier characters, they must be separated by a space,
 // otherwise they will most likely get fused together into a single token.
 function needsLeadingSpace(oldLines, oldLoc, newLines) {
-    var posBeforeOldLoc = util.copyPos(oldLoc.start);
+  var posBeforeOldLoc = util.copyPos(oldLoc.start);
 
-    // The character just before the location occupied by oldNode.
-    var charBeforeOldLoc =
-        oldLines.prevPos(posBeforeOldLoc) &&
-        oldLines.charAt(posBeforeOldLoc);
+  // The character just before the location occupied by oldNode.
+  var charBeforeOldLoc =
+    oldLines.prevPos(posBeforeOldLoc) &&
+    oldLines.charAt(posBeforeOldLoc);
 
-    // First character of the reprinted node.
-    var newFirstChar = newLines.charAt(newLines.firstPos());
+  // First character of the reprinted node.
+  var newFirstChar = newLines.charAt(newLines.firstPos());
 
-    return charBeforeOldLoc &&
-        riskyAdjoiningCharExp.test(charBeforeOldLoc) &&
-        newFirstChar &&
-        riskyAdjoiningCharExp.test(newFirstChar);
+  return charBeforeOldLoc &&
+    riskyAdjoiningCharExp.test(charBeforeOldLoc) &&
+    newFirstChar &&
+    riskyAdjoiningCharExp.test(newFirstChar);
 }
 
 // If the last character of newLines and the first character after oldLoc
 // are both identifier characters, they must be separated by a space,
 // otherwise they will most likely get fused together into a single token.
 function needsTrailingSpace(oldLines, oldLoc, newLines) {
-    // The character just after the location occupied by oldNode.
-    var charAfterOldLoc = oldLines.charAt(oldLoc.end);
+  // The character just after the location occupied by oldNode.
+  var charAfterOldLoc = oldLines.charAt(oldLoc.end);
 
-    var newLastPos = newLines.lastPos();
+  var newLastPos = newLines.lastPos();
 
-    // Last character of the reprinted node.
-    var newLastChar = newLines.prevPos(newLastPos) &&
-        newLines.charAt(newLastPos);
+  // Last character of the reprinted node.
+  var newLastChar = newLines.prevPos(newLastPos) &&
+    newLines.charAt(newLastPos);
 
-    return newLastChar &&
-        riskyAdjoiningCharExp.test(newLastChar) &&
-        charAfterOldLoc &&
-        riskyAdjoiningCharExp.test(charAfterOldLoc);
+  return newLastChar &&
+    riskyAdjoiningCharExp.test(newLastChar) &&
+    charAfterOldLoc &&
+    riskyAdjoiningCharExp.test(charAfterOldLoc);
 }
 
 function findReprints(newPath, reprints) {
-    var newNode = newPath.getValue();
-    Printable.assert(newNode);
+  var newNode = newPath.getValue();
+  Printable.assert(newNode);
 
-    var oldNode = newNode.original;
-    Printable.assert(oldNode);
+  var oldNode = newNode.original;
+  Printable.assert(oldNode);
 
-    assert.deepEqual(reprints, []);
+  assert.deepEqual(reprints, []);
 
-    if (newNode.type !== oldNode.type) {
-        return false;
-    }
+  if (newNode.type !== oldNode.type) {
+    return false;
+  }
 
-    var oldPath = new FastPath(oldNode);
-    var canReprint = findChildReprints(newPath, oldPath, reprints);
+  var oldPath = new FastPath(oldNode);
+  var canReprint = findChildReprints(newPath, oldPath, reprints);
 
-    if (!canReprint) {
-        // Make absolutely sure the calling code does not attempt to reprint
-        // any nodes.
-        reprints.length = 0;
-    }
+  if (!canReprint) {
+    // Make absolutely sure the calling code does not attempt to reprint
+    // any nodes.
+    reprints.length = 0;
+  }
 
-    return canReprint;
+  return canReprint;
 }
 
 function findAnyReprints(newPath, oldPath, reprints) {
-    var newNode = newPath.getValue();
-    var oldNode = oldPath.getValue();
+  var newNode = newPath.getValue();
+  var oldNode = oldPath.getValue();
 
-    if (newNode === oldNode)
-        return true;
+  if (newNode === oldNode)
+    return true;
 
-    if (isArray.check(newNode))
-        return findArrayReprints(newPath, oldPath, reprints);
+  if (isArray.check(newNode))
+    return findArrayReprints(newPath, oldPath, reprints);
 
-    if (isObject.check(newNode))
-        return findObjectReprints(newPath, oldPath, reprints);
+  if (isObject.check(newNode))
+    return findObjectReprints(newPath, oldPath, reprints);
 
-    return false;
+  return false;
 }
 
 function findArrayReprints(newPath, oldPath, reprints) {
-    var newNode = newPath.getValue();
-    var oldNode = oldPath.getValue();
-    isArray.assert(newNode);
-    var len = newNode.length;
+  var newNode = newPath.getValue();
+  var oldNode = oldPath.getValue();
 
-    if (!(isArray.check(oldNode) &&
-          oldNode.length === len))
-        return false;
+  if (newNode === oldNode ||
+      newPath.valueIsDuplicate() ||
+      oldPath.valueIsDuplicate()) {
+    return true;
+  }
 
-    for (var i = 0; i < len; ++i) {
-        newPath.stack.push(i, newNode[i]);
-        oldPath.stack.push(i, oldNode[i]);
-        var canReprint = findAnyReprints(newPath, oldPath, reprints);
-        newPath.stack.length -= 2;
-        oldPath.stack.length -= 2;
-        if (!canReprint) {
-            return false;
-        }
+  isArray.assert(newNode);
+  var len = newNode.length;
+
+  if (!(isArray.check(oldNode) &&
+        oldNode.length === len))
+    return false;
+
+  for (var i = 0; i < len; ++i) {
+    newPath.stack.push(i, newNode[i]);
+    oldPath.stack.push(i, oldNode[i]);
+    var canReprint = findAnyReprints(newPath, oldPath, reprints);
+    newPath.stack.length -= 2;
+    oldPath.stack.length -= 2;
+    if (!canReprint) {
+      return false;
     }
+  }
 
-    return true;
+  return true;
 }
 
 function findObjectReprints(newPath, oldPath, reprints) {
-    var newNode = newPath.getValue();
-    isObject.assert(newNode);
+  var newNode = newPath.getValue();
+  isObject.assert(newNode);
 
-    if (newNode.original === null) {
-        // If newNode.original node was set to null, reprint the node.
-        return false;
+  if (newNode.original === null) {
+    // If newNode.original node was set to null, reprint the node.
+    return false;
+  }
+
+  var oldNode = oldPath.getValue();
+  if (!isObject.check(oldNode))
+    return false;
+
+  if (newNode === oldNode ||
+      newPath.valueIsDuplicate() ||
+      oldPath.valueIsDuplicate()) {
+    return true;
+  }
+
+  if (Printable.check(newNode)) {
+    if (!Printable.check(oldNode)) {
+      return false;
     }
 
-    var oldNode = oldPath.getValue();
-    if (!isObject.check(oldNode))
-        return false;
+    // Here we need to decide whether the reprinted code for newNode is
+    // appropriate for patching into the location of oldNode.
+
+    if (newNode.type === oldNode.type) {
+      var childReprints = [];
 
-    if (Printable.check(newNode)) {
-        if (!Printable.check(oldNode)) {
-            return false;
-        }
-
-        // Here we need to decide whether the reprinted code for newNode
-        // is appropriate for patching into the location of oldNode.
-
-        if (newNode.type === oldNode.type) {
-            var childReprints = [];
-
-            if (findChildReprints(newPath, oldPath, childReprints)) {
-                reprints.push.apply(reprints, childReprints);
-            } else if (oldNode.loc) {
-                // If we have no .loc information for oldNode, then we
-                // won't be able to reprint it.
-                reprints.push({
-                    oldPath: oldPath.copy(),
-                    newPath: newPath.copy()
-                });
-            } else {
-                return false;
-            }
-
-            return true;
-        }
-
-        if (Expression.check(newNode) &&
-            Expression.check(oldNode) &&
-            // If we have no .loc information for oldNode, then we won't
-            // be able to reprint it.
-            oldNode.loc) {
-
-            // If both nodes are subtypes of Expression, then we should be
-            // able to fill the location occupied by the old node with
-            // code printed for the new node with no ill consequences.
-            reprints.push({
-                oldPath: oldPath.copy(),
-                newPath: newPath.copy()
-            });
-
-            return true;
-        }
-
-        // The nodes have different types, and at least one of the types
-        // is not a subtype of the Expression type, so we cannot safely
-        // assume the nodes are syntactically interchangeable.
+      if (findChildReprints(newPath, oldPath, childReprints)) {
+        reprints.push.apply(reprints, childReprints);
+      } else if (oldNode.loc) {
+        // If we have no .loc information for oldNode, then we won't be
+        // able to reprint it.
+        reprints.push({
+          oldPath: oldPath.copy(),
+          newPath: newPath.copy()
+        });
+      } else {
         return false;
+      }
+
+      return true;
     }
 
-    return findChildReprints(newPath, oldPath, reprints);
+    if (Expression.check(newNode) &&
+        Expression.check(oldNode) &&
+        // If we have no .loc information for oldNode, then we won't be
+        // able to reprint it.
+        oldNode.loc) {
+
+      // If both nodes are subtypes of Expression, then we should be able
+      // to fill the location occupied by the old node with code printed
+      // for the new node with no ill consequences.
+      reprints.push({
+        oldPath: oldPath.copy(),
+        newPath: newPath.copy()
+      });
+
+      return true;
+    }
+
+    // The nodes have different types, and at least one of the types is
+    // not a subtype of the Expression type, so we cannot safely assume
+    // the nodes are syntactically interchangeable.
+    return false;
+  }
+
+  return findChildReprints(newPath, oldPath, reprints);
 }
 
 // This object is reused in hasOpeningParen and hasClosingParen to avoid
@@ -395,141 +408,147 @@ var reusablePos = { line: 1, column: 0 };
 var nonSpaceExp = /\S/;
 
 function hasOpeningParen(oldPath) {
-    var oldNode = oldPath.getValue();
-    var loc = oldNode.loc;
-    var lines = loc && loc.lines;
-
-    if (lines) {
-        var pos = reusablePos;
-        pos.line = loc.start.line;
-        pos.column = loc.start.column;
-
-        while (lines.prevPos(pos)) {
-            var ch = lines.charAt(pos);
-
-            if (ch === "(") {
-                // If we found an opening parenthesis but it occurred before
-                // the start of the original subtree for this reprinting, then
-                // we must not return true for hasOpeningParen(oldPath).
-                return comparePos(oldPath.getRootValue().loc.start, pos) <= 0;
-            }
-
-            if (nonSpaceExp.test(ch)) {
-                return false;
-            }
-        }
+  var oldNode = oldPath.getValue();
+  var loc = oldNode.loc;
+  var lines = loc && loc.lines;
+
+  if (lines) {
+    var pos = reusablePos;
+    pos.line = loc.start.line;
+    pos.column = loc.start.column;
+
+    while (lines.prevPos(pos)) {
+      var ch = lines.charAt(pos);
+
+      if (ch === "(") {
+        // If we found an opening parenthesis but it occurred before the
+        // start of the original subtree for this reprinting, then we must
+        // not return true for hasOpeningParen(oldPath).
+        return comparePos(oldPath.getRootValue().loc.start, pos) <= 0;
+      }
+
+      if (nonSpaceExp.test(ch)) {
+        return false;
+      }
     }
+  }
 
-    return false;
+  return false;
 }
 
 function hasClosingParen(oldPath) {
-    var oldNode = oldPath.getValue();
-    var loc = oldNode.loc;
-    var lines = loc && loc.lines;
-
-    if (lines) {
-        var pos = reusablePos;
-        pos.line = loc.end.line;
-        pos.column = loc.end.column;
-
-        do {
-            var ch = lines.charAt(pos);
-
-            if (ch === ")") {
-                // If we found a closing parenthesis but it occurred after the
-                // end of the original subtree for this reprinting, then we
-                // must not return true for hasClosingParen(oldPath).
-                return comparePos(pos, oldPath.getRootValue().loc.end) <= 0;
-            }
-
-            if (nonSpaceExp.test(ch)) {
-                return false;
-            }
-
-        } while (lines.nextPos(pos));
-    }
+  var oldNode = oldPath.getValue();
+  var loc = oldNode.loc;
+  var lines = loc && loc.lines;
+
+  if (lines) {
+    var pos = reusablePos;
+    pos.line = loc.end.line;
+    pos.column = loc.end.column;
+
+    do {
+      var ch = lines.charAt(pos);
+
+      if (ch === ")") {
+        // If we found a closing parenthesis but it occurred after the end
+        // of the original subtree for this reprinting, then we must not
+        // return true for hasClosingParen(oldPath).
+        return comparePos(pos, oldPath.getRootValue().loc.end) <= 0;
+      }
+
+      if (nonSpaceExp.test(ch)) {
+        return false;
+      }
 
-    return false;
+    } while (lines.nextPos(pos));
+  }
+
+  return false;
 }
 
 function hasParens(oldPath) {
-    // This logic can technically be fooled if the node has parentheses
-    // but there are comments intervening between the parentheses and the
-    // node. In such cases the node will be harmlessly wrapped in an
-    // additional layer of parentheses.
-    return hasOpeningParen(oldPath) && hasClosingParen(oldPath);
+  // This logic can technically be fooled if the node has parentheses but
+  // there are comments intervening between the parentheses and the
+  // node. In such cases the node will be harmlessly wrapped in an
+  // additional layer of parentheses.
+  return hasOpeningParen(oldPath) && hasClosingParen(oldPath);
 }
 
 function findChildReprints(newPath, oldPath, reprints) {
-    var newNode = newPath.getValue();
-    var oldNode = oldPath.getValue();
+  var newNode = newPath.getValue();
+  var oldNode = oldPath.getValue();
 
-    isObject.assert(newNode);
-    isObject.assert(oldNode);
+  isObject.assert(newNode);
+  isObject.assert(oldNode);
 
-    if (newNode.original === null) {
-        // If newNode.original node was set to null, reprint the node.
-        return false;
-    }
+  if (newNode.original === null) {
+    // If newNode.original node was set to null, reprint the node.
+    return false;
+  }
+
+  // If this type of node cannot come lexically first in its enclosing
+  // statement (e.g. a function expression or object literal), and it
+  // seems to be doing so, then the only way we can ignore this problem
+  // and save ourselves from falling back to the pretty printer is if an
+  // opening parenthesis happens to precede the node.  For example,
+  // (function(){ ... }()); does not need to be reprinted, even though the
+  // FunctionExpression comes lexically first in the enclosing
+  // ExpressionStatement and fails the hasParens test, because the parent
+  // CallExpression passes the hasParens test. If we relied on the
+  // path.needsParens() && !hasParens(oldNode) check below, the absence of
+  // a closing parenthesis after the FunctionExpression would trigger
+  // pretty-printing unnecessarily.
+  if (!newPath.canBeFirstInStatement() &&
+      newPath.firstInStatement() &&
+      !hasOpeningParen(oldPath))
+    return false;
 
-    // If this type of node cannot come lexically first in its enclosing
-    // statement (e.g. a function expression or object literal), and it
-    // seems to be doing so, then the only way we can ignore this problem
-    // and save ourselves from falling back to the pretty printer is if an
-    // opening parenthesis happens to precede the node.  For example,
-    // (function(){ ... }()); does not need to be reprinted, even though
-    // the FunctionExpression comes lexically first in the enclosing
-    // ExpressionStatement and fails the hasParens test, because the
-    // parent CallExpression passes the hasParens test. If we relied on
-    // the path.needsParens() && !hasParens(oldNode) check below, the
-    // absence of a closing parenthesis after the FunctionExpression would
-    // trigger pretty-printing unnecessarily.
-    if (!newPath.canBeFirstInStatement() &&
-        newPath.firstInStatement() &&
-        !hasOpeningParen(oldPath))
-        return false;
+  // If this node needs parentheses and will not be wrapped with
+  // parentheses when reprinted, then return false to skip reprinting and
+  // let it be printed generically.
+  if (newPath.needsParens(true) && !hasParens(oldPath)) {
+    return false;
+  }
 
-    // If this node needs parentheses and will not be wrapped with
-    // parentheses when reprinted, then return false to skip reprinting
-    // and let it be printed generically.
-    if (newPath.needsParens(true) && !hasParens(oldPath)) {
-        return false;
-    }
+  var keys = util.getUnionOfKeys(oldNode, newNode);
 
-    var keys = util.getUnionOfKeys(oldNode, newNode);
+  if (oldNode.type === "File" ||
+      newNode.type === "File") {
+    // Don't bother traversing file.tokens, an often very large array
+    // returned by Babylon, and useless for our purposes.
+    delete keys.tokens;
+  }
 
-    if (oldNode.type === "File" ||
-        newNode.type === "File") {
-        // Don't bother traversing file.tokens, an often very large array
-        // returned by Babylon, and useless for our purposes.
-        delete keys.tokens;
-    }
+  // Don't bother traversing .loc objects looking for reprintable nodes.
+  delete keys.loc;
 
-    // Don't bother traversing .loc objects looking for reprintable nodes.
-    delete keys.loc;
+  var originalReprintCount = reprints.length;
 
-    var originalReprintCount = reprints.length;
+  for (var k in keys) {
+    if (k.charAt(0) === "_") {
+      // Ignore "private" AST properties added by e.g. Babel plugins and
+      // parsers like Babylon.
+      continue;
+    }
 
-    for (var k in keys) {
-        newPath.stack.push(k, types.getFieldValue(newNode, k));
-        oldPath.stack.push(k, types.getFieldValue(oldNode, k));
-        var canReprint = findAnyReprints(newPath, oldPath, reprints);
-        newPath.stack.length -= 2;
-        oldPath.stack.length -= 2;
+    newPath.stack.push(k, types.getFieldValue(newNode, k));
+    oldPath.stack.push(k, types.getFieldValue(oldNode, k));
+    var canReprint = findAnyReprints(newPath, oldPath, reprints);
+    newPath.stack.length -= 2;
+    oldPath.stack.length -= 2;
 
-        if (!canReprint) {
-            return false;
-        }
+    if (!canReprint) {
+      return false;
     }
+  }
 
-    // Return statements might end up running into ASI issues due to comments
-    // inserted deep within the tree, so reprint them if anything changed
-    // within them.
-    if (ReturnStatement.check(newPath.getNode()) &&
-        reprints.length > originalReprintCount) {
-        return false;
-    }
+  // Return statements might end up running into ASI issues due to
+  // comments inserted deep within the tree, so reprint them if anything
+  // changed within them.
+  if (ReturnStatement.check(newPath.getNode()) &&
+      reprints.length > originalReprintCount) {
+    return false;
+  }
 
-    return true;
+  return true;
 }
diff --git a/lib/printer.js b/lib/printer.js
index a89ad90..2040b1b 100644
--- a/lib/printer.js
+++ b/lib/printer.js
@@ -1083,6 +1083,9 @@ function genericPrintNoParens(path, options, print) {
     case "JSXSpreadAttribute":
         return concat(["{...", path.call(print, "argument"), "}"]);
 
+    case "JSXSpreadChild":
+        return concat(["{...", path.call(print, "expression"), "}"]);
+
     case "JSXExpressionContainer":
         return concat(["{", path.call(print, "expression"), "}"]);
 
@@ -1510,8 +1513,9 @@ function genericPrintNoParens(path, options, print) {
         return fromString(nodeStr(n.value, options), options);
 
     case "NumberLiteralTypeAnnotation":
+    case "NumericLiteralTypeAnnotation":
         assert.strictEqual(typeof n.value, "number");
-        return fromString("" + n.value, options);
+        return fromString(JSON.stringify(n.value), options);
 
     case "StringTypeAnnotation":
         return fromString("string", options);
diff --git a/lib/util.js b/lib/util.js
index 251cc84..81895de 100644
--- a/lib/util.js
+++ b/lib/util.js
@@ -163,10 +163,10 @@ util.fixFaultyLocations = function(node, lines) {
     loc.end = lines.lastPos();
   }
 
-  if (node.type === "TemplateLiteral") {
-    fixTemplateLiteral(node, lines);
+  fixForLoopHead(node, lines);
+  fixTemplateLiteral(node, lines);
 
-  } else if (loc && node.decorators) {
+  if (loc && node.decorators) {
     // Expand the .loc of the node responsible for printing the decorators
     // (here, the decorated node) so that it includes node.decorators.
     node.decorators.forEach(function (decorator) {
@@ -220,8 +220,37 @@ util.fixFaultyLocations = function(node, lines) {
   }
 };
 
+function fixForLoopHead(node, lines) {
+  if (node.type !== "ForStatement") {
+    return;
+  }
+
+  function fix(child) {
+    var loc = child && child.loc;
+    var start = loc && loc.start;
+    var end = loc && copyPos(loc.end);
+
+    while (start && end && comparePos(start, end) < 0) {
+      lines.prevPos(end);
+      if (lines.charAt(end) === ";") {
+        // Update child.loc.end to *exclude* the ';' character.
+        loc.end.line = end.line;
+        loc.end.column = end.column;
+      } else {
+        break;
+      }
+    }
+  }
+
+  fix(node.init);
+  fix(node.test);
+  fix(node.update);
+}
+
 function fixTemplateLiteral(node, lines) {
-  assert.strictEqual(node.type, "TemplateLiteral");
+  if (node.type !== "TemplateLiteral") {
+    return;
+  }
 
   if (node.quasis.length === 0) {
     // If there are no quasi elements, then there is nothing to fix.
diff --git a/package.json b/package.json
index 225a3ca..2645d5f 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
     "parsing",
     "pretty-printing"
   ],
-  "version": "0.11.22",
+  "version": "0.12.2",
   "homepage": "http://github.com/benjamn/recast",
   "repository": {
     "type": "git",
@@ -28,15 +28,19 @@
     "fs": false
   },
   "dependencies": {
-    "ast-types": "0.9.5",
+    "ast-types": "0.9.10",
+    "core-js": "^2.4.1",
     "esprima": "~3.1.0",
     "private": "~0.1.5",
     "source-map": "~0.5.0"
   },
   "devDependencies": {
-    "babylon": "~6.15.0",
+    "babel-core": "^6.23.1",
+    "babel-preset-es2015": "^6.22.0",
+    "babylon": "~6.16.1",
     "esprima-fb": "^15001.1001.0-dev-harmony-fb",
-    "mocha": "~3.1.2"
+    "mocha": "~3.2.0",
+    "reify": "^0.5.3"
   },
   "engines": {
     "node": ">= 0.8"
diff --git a/test/babylon.js b/test/babylon.js
index 2140773..74c75e2 100644
--- a/test/babylon.js
+++ b/test/babylon.js
@@ -5,20 +5,10 @@ var b = recast.types.builders;
 var eol = require("os").EOL;
 
 describe("decorators", function () {
-  var babylon = require("babylon");
-  var babylonOptions = {
-    sourceType: 'module',
-    allowImportExportEverywhere: false,
-    allowReturnOutsideFunction: false,
-    plugins: ["*", "jsx", "flow"]
-  };
-
+  var babelTransform = require("babel-core").transform;
+  var babelPresetES2015 = require("babel-preset-es2015");
   var parseOptions = {
-    parser: {
-      parse: function (source) {
-        return babylon.parse(source, babylonOptions);
-      }
-    }
+    parser: require("reify/lib/parsers/babylon.js")
   };
 
   it("babel 6 printing", function () {
@@ -273,14 +263,6 @@ describe("decorators", function () {
       '};',
     ].join('\n');
 
-    var parseOptions = {
-      parser: {
-        parse: function (source) {
-          return babylon.parse(source, {plugins: ['flow']});
-        }
-      },
-    };
-
     var ast = recast.parse(code, parseOptions)
     var root = new recast.types.NodePath(ast);
 
@@ -363,4 +345,20 @@ describe("decorators", function () {
       code
     );
   });
+
+  it("tolerates circular references", function () {
+    var code = "function foo(bar = true) {}";
+    var ast = recast.parse(code, {
+      parser: {
+        parse: function (source) {
+          return babelTransform(source, {
+            code: false,
+            ast: true,
+            sourceMap: false,
+            presets: [babelPresetES2015]
+          }).ast;
+        }
+      }
+    });
+  });
 });
diff --git a/test/printer.js b/test/printer.js
index 2efbbc7..5071d67 100644
--- a/test/printer.js
+++ b/test/printer.js
@@ -8,1626 +8,1666 @@ var fromString = require("../lib/lines").fromString;
 var eol = require("os").EOL;
 
 describe("printer", function() {
-    it("Printer", function testPrinter(done) {
-        var code = testPrinter + "";
-        var ast = parse(code);
-        var printer = new Printer;
+  it("Printer", function testPrinter(done) {
+    var code = testPrinter + "";
+    var ast = parse(code);
+    var printer = new Printer;
 
-        assert.strictEqual(typeof printer.print, "function");
-        assert.strictEqual(printer.print(null).code, "");
+    assert.strictEqual(typeof printer.print, "function");
+    assert.strictEqual(printer.print(null).code, "");
 
-        var string = printer.printGenerically(ast).code;
-        assert.ok(string.indexOf("done();") > 0);
+    var string = printer.printGenerically(ast).code;
+    assert.ok(string.indexOf("done();") > 0);
 
-        string = printer.print(ast).code;
+    string = printer.print(ast).code;
 
-        // TODO
+    // TODO
 
-        assert.ok(string.indexOf("// TODO") > 0);
+    assert.ok(string.indexOf("// TODO") > 0);
 
-        done();
-    });
+    done();
+  });
 
-    var uselessSemicolons = [
-        'function a() {',
-        '  return "a";',
-        '};',
-        '',
-        'function b() {',
-        '  return "b";',
-        '};'
-    ].join(eol);
+  var uselessSemicolons = [
+    'function a() {',
+    '  return "a";',
+    '};',
+    '',
+    'function b() {',
+    '  return "b";',
+    '};'
+  ].join(eol);
 
-    it("EmptyStatements", function() {
-        var ast = parse(uselessSemicolons);
-        var printer = new Printer({ tabWidth: 2 });
+  it("EmptyStatements", function() {
+    var ast = parse(uselessSemicolons);
+    var printer = new Printer({ tabWidth: 2 });
 
-        var reprinted = printer.print(ast).code;
-        assert.strictEqual(typeof reprinted, "string");
-        assert.strictEqual(reprinted, uselessSemicolons);
+    var reprinted = printer.print(ast).code;
+    assert.strictEqual(typeof reprinted, "string");
+    assert.strictEqual(reprinted, uselessSemicolons);
 
-        var generic = printer.printGenerically(ast).code;
-        var withoutTrailingSemicolons = uselessSemicolons.replace(/\};/g, "}");
-        assert.strictEqual(typeof generic, "string");
-        assert.strictEqual(generic, withoutTrailingSemicolons);
+    var generic = printer.printGenerically(ast).code;
+    var withoutTrailingSemicolons = uselessSemicolons.replace(/\};/g, "}");
+    assert.strictEqual(typeof generic, "string");
+    assert.strictEqual(generic, withoutTrailingSemicolons);
+  });
+
+  var importantSemicolons = [
+    "var a = {};", // <--- this trailing semi-colon is very important
+    "(function() {})();"
+  ].join(eol);
+
+  it("IffeAfterVariableDeclarationEndingInObjectLiteral", function() {
+    var ast = parse(importantSemicolons);
+    var printer = new Printer({ tabWidth: 2 });
+
+    var reprinted = printer.printGenerically(ast).code;
+    assert.strictEqual(typeof reprinted, "string");
+    assert.strictEqual(reprinted, importantSemicolons);
+  });
+
+  var arrayExprWithTrailingComma = '[1, 2,];';
+  var arrayExprWithoutTrailingComma = '[1, 2];';
+
+  it("ArrayExpressionWithTrailingComma", function() {
+    var ast = parse(arrayExprWithTrailingComma);
+    var printer = new Printer({ tabWidth: 2 });
+
+    var body = ast.program.body;
+    var arrayExpr = body[0].expression;
+    n.ArrayExpression.assert(arrayExpr);
+
+    // This causes the array expression to be reprinted.
+    var arrayExprOrig = arrayExpr.original;
+    arrayExpr.original = null;
+
+    assert.strictEqual(
+      printer.print(ast).code,
+      arrayExprWithoutTrailingComma
+    );
+
+    arrayExpr.original = arrayExprOrig;
+
+    assert.strictEqual(
+      printer.print(ast).code,
+      arrayExprWithTrailingComma
+    );
+  });
+
+  var arrayExprWithHoles = '[,,];';
+
+  it("ArrayExpressionWithHoles", function() {
+    var ast = parse(arrayExprWithHoles);
+    var printer = new Printer({ tabWidth: 2 });
+
+    var body = ast.program.body;
+    var arrayExpr = body[0].expression;
+    n.ArrayExpression.assert(arrayExpr);
+
+    // This causes the array expression to be reprinted.
+    var arrayExprOrig = arrayExpr.original;
+    arrayExpr.original = null;
+
+    assert.strictEqual(
+      printer.print(ast).code,
+      arrayExprWithHoles
+    );
+
+    arrayExpr.original = arrayExprOrig;
+
+    assert.strictEqual(
+      printer.print(ast).code,
+      arrayExprWithHoles
+    );
+  });
+
+  var objectExprWithTrailingComma = '({x: 1, y: 2,});';
+  var objectExprWithoutTrailingComma = '({' + eol + '  x: 1,' + eol + '  y: 2' + eol + '});';
+
+  it("ArrayExpressionWithTrailingComma", function() {
+    var ast = parse(objectExprWithTrailingComma);
+    var printer = new Printer({ tabWidth: 2 });
+
+    var body = ast.program.body;
+    var objectExpr = body[0].expression;
+    n.ObjectExpression.assert(objectExpr);
+
+    // This causes the array expression to be reprinted.
+    var objectExprOrig = objectExpr.original;
+    objectExpr.original = null;
+
+    assert.strictEqual(
+      printer.print(ast).code,
+      objectExprWithoutTrailingComma
+    );
+
+    objectExpr.original = objectExprOrig;
+
+    assert.strictEqual(
+      printer.print(ast).code,
+      objectExprWithTrailingComma
+    );
+  });
+
+  var switchCase = [
+    "switch (test) {",
+    "  default:",
+    "  case a: break",
+    "",
+    "  case b:",
+    "    break;",
+    "}",
+  ].join(eol);
+
+  var switchCaseReprinted = [
+    "if (test) {",
+    "  switch (test) {",
+    "  default:",
+    "  case a: break",
+    "  case b:",
+    "    break;",
+    "  }",
+    "}"
+  ].join(eol);
+
+  var switchCaseGeneric = [
+    "if (test) {",
+    "  switch (test) {",
+    "  default:",
+    "  case a:",
+    "    break;",
+    "  case b:",
+    "    break;",
+    "  }",
+    "}"
+  ].join(eol);
+
+  it("SwitchCase", function() {
+    var ast = parse(switchCase);
+    var printer = new Printer({ tabWidth: 2 });
+
+    var body = ast.program.body;
+    var switchStmt = body[0];
+    n.SwitchStatement.assert(switchStmt);
+
+    // This causes the switch statement to be reprinted.
+    switchStmt.original = null;
+
+    body[0] = b.ifStatement(
+      b.identifier("test"),
+      b.blockStatement([
+        switchStmt
+      ])
+    );
+
+    assert.strictEqual(
+      printer.print(ast).code,
+      switchCaseReprinted
+    );
+
+    assert.strictEqual(
+      printer.printGenerically(ast).code,
+      switchCaseGeneric
+    );
+  });
+
+  var tryCatch = [
+    "try {",
+    "  a();",
+    "} catch (e) {",
+    "  b(e);",
+    "}"
+  ].join(eol);
+
+  it("IndentTryCatch", function() {
+    var ast = parse(tryCatch);
+    var printer = new Printer({ tabWidth: 2 });
+    var body = ast.program.body;
+    var tryStmt = body[0];
+    n.TryStatement.assert(tryStmt);
+
+    // Force reprinting.
+    assert.strictEqual(printer.printGenerically(ast).code, tryCatch);
+  });
+
+  var classBody = [
+    "class A {",
+    "  foo(x) { return x }",
+    "  bar(y) { this.foo(y); }",
+    "  baz(x, y) {",
+    "    this.foo(x);",
+    "    this.bar(y);",
+    "  }",
+    "}"
+  ];
+
+  var classBodyExpected = [
+    "class A {",
+    "  foo(x) { return x }",
+    "  bar(y) { this.foo(y); }",
+    "  baz(x, y) {",
+    "    this.foo(x);",
+    "    this.bar(y);",
+    "  }",
+    "  foo(x) { return x }",
+    "}"
+  ];
+
+  it("MethodPrinting", function() {
+    var code = classBody.join(eol);
+    try {
+      var ast = parse(code);
+    } catch (e) {
+      // ES6 not supported, silently finish
+      return;
+    }
+    var printer = new Printer({ tabWidth: 2 });
+    var cb = ast.program.body[0].body;
+    n.ClassBody.assert(cb);
+
+    // Trigger reprinting of the class body.
+    cb.body.push(cb.body[0]);
+
+    assert.strictEqual(
+      printer.print(ast).code,
+      classBodyExpected.join(eol)
+    );
+  });
+
+  var multiLineParams = [
+    "function f(/* first",
+    "              xxx",
+    "              param */ a,",
+    "  // other params",
+    "  b, c, // see?",
+    "  d",
+    ") {",
+    "  return a + b + c + d;",
+    "}"
+  ];
+
+  var multiLineParamsExpected = [
+    "function f(",
+    "  /* first",
+    "     xxx",
+    "     param */ a,",
+    "  // other params",
+    "  b,",
+    "  // see?",
+    "  c,",
+    "  d",
+    ") {",
+    "  return a + b + c + d;",
+    "}"
+  ];
+
+  it("MultiLineParams", function() {
+    var code = multiLineParams.join(eol);
+    var ast = parse(code);
+    var printer = new Printer({ tabWidth: 2 });
+
+    recast.visit(ast, {
+      visitNode: function(path) {
+        path.value.original = null;
+        this.traverse(path);
+      }
     });
 
-    var importantSemicolons = [
-        "var a = {};", // <--- this trailing semi-colon is very important
-        "(function() {})();"
+    assert.strictEqual(
+      printer.print(ast).code,
+      multiLineParamsExpected.join(eol)
+    );
+  });
+
+  it("SimpleVarPrinting", function() {
+    var printer = new Printer({ tabWidth: 2 });
+    var varDecl = b.variableDeclaration("var", [
+      b.variableDeclarator(b.identifier("x"), null),
+      b.variableDeclarator(b.identifier("y"), null),
+      b.variableDeclarator(b.identifier("z"), null)
+    ]);
+
+    assert.strictEqual(
+      printer.print(b.program([varDecl])).code,
+      "var x, y, z;"
+    );
+
+    var z = varDecl.declarations.pop();
+    varDecl.declarations.pop();
+    varDecl.declarations.push(z);
+
+    assert.strictEqual(
+      printer.print(b.program([varDecl])).code,
+      "var x, z;"
+    );
+  });
+
+  it("MultiLineVarPrinting", function() {
+    var printer = new Printer({ tabWidth: 2 });
+    var varDecl = b.variableDeclaration("var", [
+      b.variableDeclarator(b.identifier("x"), null),
+      b.variableDeclarator(
+        b.identifier("y"),
+        b.objectExpression([
+          b.property("init", b.identifier("why"), b.literal("not"))
+        ])
+      ),
+      b.variableDeclarator(b.identifier("z"), null)
+    ]);
+
+    assert.strictEqual(printer.print(b.program([varDecl])).code, [
+      "var x,",
+      "    y = {",
+      "      why: \"not\"",
+      "    },",
+      "    z;"
+    ].join(eol));
+  });
+
+  it("ForLoopPrinting", function() {
+    var printer = new Printer({ tabWidth: 2 });
+    var loop = b.forStatement(
+      b.variableDeclaration("var", [
+        b.variableDeclarator(b.identifier("i"), b.literal(0))
+      ]),
+      b.binaryExpression("<", b.identifier("i"), b.literal(3)),
+      b.updateExpression("++", b.identifier("i"), /* prefix: */ false),
+      b.expressionStatement(
+        b.callExpression(b.identifier("log"), [b.identifier("i")])
+      )
+    );
+
+    assert.strictEqual(
+      printer.print(loop).code,
+      "for (var i = 0; i < 3; i++)" + eol +
+        "  log(i);"
+    );
+  });
+
+  it("EmptyForLoopPrinting", function() {
+    var printer = new Printer({ tabWidth: 2 });
+    var loop = b.forStatement(
+      b.variableDeclaration("var", [
+        b.variableDeclarator(b.identifier("i"), b.literal(0))
+      ]),
+      b.binaryExpression("<", b.identifier("i"), b.literal(3)),
+      b.updateExpression("++", b.identifier("i"), /* prefix: */ false),
+      b.emptyStatement()
+    );
+
+    assert.strictEqual(
+      printer.print(loop).code,
+      "for (var i = 0; i < 3; i++)" + eol +
+        "  ;"
+    );
+  });
+
+  it("ForInLoopPrinting", function() {
+    var printer = new Printer({ tabWidth: 2 });
+    var loop = b.forInStatement(
+      b.variableDeclaration("var", [
+        b.variableDeclarator(b.identifier("key"), null)
+      ]),
+      b.identifier("obj"),
+      b.expressionStatement(
+        b.callExpression(b.identifier("log"), [b.identifier("key")])
+      ),
+      /* each: */ false
+    );
+
+    assert.strictEqual(
+      printer.print(loop).code,
+      "for (var key in obj)" + eol +
+        "  log(key);"
+    );
+  });
+
+  it("GuessTabWidth", function() {
+    var code = [
+      "function identity(x) {",
+      "  return x;",
+      "}"
     ].join(eol);
 
-    it("IffeAfterVariableDeclarationEndingInObjectLiteral", function() {
-        var ast = parse(importantSemicolons);
-        var printer = new Printer({ tabWidth: 2 });
-
-        var reprinted = printer.printGenerically(ast).code;
-        assert.strictEqual(typeof reprinted, "string");
-        assert.strictEqual(reprinted, importantSemicolons);
-    });
-
-    var arrayExprWithTrailingComma = '[1, 2,];';
-    var arrayExprWithoutTrailingComma = '[1, 2];';
-
-    it("ArrayExpressionWithTrailingComma", function() {
-        var ast = parse(arrayExprWithTrailingComma);
-        var printer = new Printer({ tabWidth: 2 });
-
-        var body = ast.program.body;
-        var arrayExpr = body[0].expression;
-        n.ArrayExpression.assert(arrayExpr);
-
-        // This causes the array expression to be reprinted.
-        var arrayExprOrig = arrayExpr.original;
-        arrayExpr.original = null;
-
-        assert.strictEqual(
-            printer.print(ast).code,
-            arrayExprWithoutTrailingComma
-        );
-
-        arrayExpr.original = arrayExprOrig;
-
-        assert.strictEqual(
-            printer.print(ast).code,
-            arrayExprWithTrailingComma
-        );
-    });
-
-    var arrayExprWithHoles = '[,,];';
-
-    it("ArrayExpressionWithHoles", function() {
-        var ast = parse(arrayExprWithHoles);
-        var printer = new Printer({ tabWidth: 2 });
-
-        var body = ast.program.body;
-        var arrayExpr = body[0].expression;
-        n.ArrayExpression.assert(arrayExpr);
-
-        // This causes the array expression to be reprinted.
-        var arrayExprOrig = arrayExpr.original;
-        arrayExpr.original = null;
-
-        assert.strictEqual(
-            printer.print(ast).code,
-            arrayExprWithHoles
-        );
-
-        arrayExpr.original = arrayExprOrig;
-
-        assert.strictEqual(
-            printer.print(ast).code,
-            arrayExprWithHoles
-        );
-    });
-
-    var objectExprWithTrailingComma = '({x: 1, y: 2,});';
-    var objectExprWithoutTrailingComma = '({' + eol + '  x: 1,' + eol + '  y: 2' + eol + '});';
-
-    it("ArrayExpressionWithTrailingComma", function() {
-        var ast = parse(objectExprWithTrailingComma);
-        var printer = new Printer({ tabWidth: 2 });
-
-        var body = ast.program.body;
-        var objectExpr = body[0].expression;
-        n.ObjectExpression.assert(objectExpr);
-
-        // This causes the array expression to be reprinted.
-        var objectExprOrig = objectExpr.original;
-        objectExpr.original = null;
-
-        assert.strictEqual(
-            printer.print(ast).code,
-            objectExprWithoutTrailingComma
-        );
-
-        objectExpr.original = objectExprOrig;
-
-        assert.strictEqual(
-            printer.print(ast).code,
-            objectExprWithTrailingComma
-        );
-    });
-
-    var switchCase = [
-        "switch (test) {",
-        "  default:",
-        "  case a: break",
-        "",
-        "  case b:",
-        "    break;",
-        "}",
+    var guessedTwo = [
+      "function identity(x) {",
+      "  log(x);",
+      "  return x;",
+      "}"
     ].join(eol);
 
-    var switchCaseReprinted = [
-        "if (test) {",
-        "  switch (test) {",
-        "  default:",
-        "  case a: break",
-        "  case b:",
-        "    break;",
-        "  }",
-        "}"
+    var explicitFour = [
+      "function identity(x) {",
+      "    log(x);",
+      "    return x;",
+      "}"
     ].join(eol);
 
-    var switchCaseGeneric = [
-        "if (test) {",
-        "  switch (test) {",
-        "  default:",
-        "  case a:",
-        "    break;",
-        "  case b:",
-        "    break;",
-        "  }",
-        "}"
+    var ast = parse(code);
+
+    var funDecl = ast.program.body[0];
+    n.FunctionDeclaration.assert(funDecl);
+
+    var funBody = funDecl.body.body;
+
+    funBody.unshift(
+      b.expressionStatement(
+        b.callExpression(
+          b.identifier("log"),
+          funDecl.params
+        )
+      )
+    );
+
+    assert.strictEqual(
+      new Printer().print(ast).code,
+      guessedTwo
+    );
+
+    assert.strictEqual(
+      new Printer({
+        tabWidth: 4
+      }).print(ast).code,
+      explicitFour
+    );
+  });
+
+  it("FunctionDefaultsAndRest", function() {
+    var printer = new Printer();
+    var funExpr = b.functionExpression(
+      b.identifier('a'),
+      [b.identifier('b'), b.identifier('c')],
+      b.blockStatement([]),
+      false,
+      false,
+      false,
+      undefined
+    );
+
+    funExpr.defaults = [undefined, b.literal(1)];
+    funExpr.rest = b.identifier('d');
+
+    assert.strictEqual(
+      printer.print(funExpr).code,
+      "function a(b, c = 1, ...d) {}"
+    );
+
+    var arrowFunExpr = b.arrowFunctionExpression(
+      [b.identifier('b'), b.identifier('c')],
+      b.blockStatement([]),
+      false,
+      false,
+      false,
+      undefined);
+
+    arrowFunExpr.defaults = [undefined, b.literal(1)];
+    arrowFunExpr.rest = b.identifier('d');
+
+    assert.strictEqual(
+      printer.print(arrowFunExpr).code,
+      "(b, c = 1, ...d) => {}"
+    );
+  });
+
+  it("generically prints parsed code and generated code the same way", function() {
+    var printer = new Printer();
+    var ast = b.program([
+      b.expressionStatement(b.literal(1)),
+      b.expressionStatement(b.literal(2))
+    ]);
+
+    assert.strictEqual(
+      printer.printGenerically(parse("1; 2;")).code,
+      printer.printGenerically(ast).code
+    );
+  });
+
+  it("ExportDeclaration semicolons", function() {
+    var printer = new Printer();
+    var code = "export var foo = 42;";
+    var ast = parse(code);
+
+    assert.strictEqual(printer.print(ast).code, code);
+    assert.strictEqual(printer.printGenerically(ast).code, code);
+
+    code = "export var foo = 42";
+    ast = parse(code);
+
+    assert.strictEqual(printer.print(ast).code, code);
+    assert.strictEqual(printer.printGenerically(ast).code, code + ";");
+
+    code = "export function foo() {}";
+    ast = parse(code);
+
+    assert.strictEqual(printer.print(ast).code, code);
+    assert.strictEqual(printer.printGenerically(ast).code, code);
+  });
+
+  var stmtListSpaces = [
+    "",
+    "var x = 1;",
+    "",
+    "",
+    "// y summation",
+    "var y = x + 1;",
+    "var z = x + y;",
+    "// after z",
+    "",
+    "console.log(x, y, z);",
+    "",
+    ""
+  ].join(eol);
+
+  var stmtListSpacesExpected = [
+    "",
+    "debugger;",
+    "var x = 1;",
+    "",
+    "",
+    "// y summation",
+    "var y = x + 1;",
+    "debugger;",
+    "var z = x + y;",
+    "// after z",
+    "",
+    "console.log(x, y, z);",
+    "",
+    "debugger;",
+    "",
+    ""
+  ].join(eol);
+
+  it("Statement list whitespace reuse", function() {
+    var ast = parse(stmtListSpaces);
+    var printer = new Printer({ tabWidth: 2 });
+    var debugStmt = b.expressionStatement(b.identifier("debugger"));
+
+    ast.program.body.splice(2, 0, debugStmt);
+    ast.program.body.unshift(debugStmt);
+    ast.program.body.push(debugStmt);
+
+    assert.strictEqual(
+      printer.print(ast).code,
+      stmtListSpacesExpected
+    );
+
+    var funDecl = b.functionDeclaration(
+      b.identifier("foo"),
+      [],
+      b.blockStatement(ast.program.body)
+    );
+
+    var linesModule = require("../lib/lines");
+
+    assert.strictEqual(
+      printer.print(funDecl).code,
+      linesModule.concat([
+        "function foo() {" + eol,
+        linesModule.fromString(
+          stmtListSpacesExpected.replace(/^\s+|\s+$/g, "")
+        ).indent(2),
+        eol + "}"
+      ]).toString()
+    );
+  });
+
+  it("should print static methods with the static keyword", function() {
+    var printer = new Printer({ tabWidth: 4 });
+    var ast = parse([
+      "class A {",
+      "  static foo() {}",
+      "}"
+    ].join(eol));
+
+    var classBody = ast.program.body[0].body;
+    n.ClassBody.assert(classBody);
+
+    var foo = classBody.body[0];
+    n.MethodDefinition.assert(foo);
+
+    classBody.body.push(foo);
+
+    foo.key.name = "formerlyFoo";
+
+    assert.strictEqual(printer.print(ast).code, [
+      "class A {",
+      "    static formerlyFoo() {}",
+      "    static formerlyFoo() {}",
+      "}"
+    ].join(eol));
+  });
+
+  it("should print string literals with the specified delimiter", function() {
+    var ast = parse([
+      "var obj = {",
+      "    \"foo's\": 'bar',",
+      "    '\"bar\\'s\"': /regex/m",
+      "};"
+    ].join(eol));
+
+    var variableDeclaration = ast.program.body[0];
+    n.VariableDeclaration.assert(variableDeclaration);
+
+    var printer = new Printer({ quote: "single" });
+    assert.strictEqual(printer.printGenerically(ast).code, [
+      "var obj = {",
+      "    'foo\\'s': 'bar',",
+      "    '\"bar\\'s\"': /regex/m",
+      "};"
+    ].join(eol));
+
+    var printer2 = new Printer({ quote: "double" });
+    assert.strictEqual(printer2.printGenerically(ast).code, [
+      "var obj = {",
+      "    \"foo's\": \"bar\",",
+      '    "\\"bar\'s\\"": /regex/m',
+      "};"
+    ].join(eol));
+
+    var printer3 = new Printer({ quote: "auto" });
+    assert.strictEqual(printer3.printGenerically(ast).code, [
+      "var obj = {",
+      '    "foo\'s": "bar",',
+      '    \'"bar\\\'s"\': /regex/m',
+      "};"
+    ].join(eol));
+  });
+
+  it("should print block comments at head of class once", function() {
+    // Given.
+    var ast = parse([
+      "/**",
+      " * This class was in an IIFE and returned an instance of itself.",
+      " */",
+      "function SimpleClass() {",
+      "};"
+    ].join(eol));
+
+    var classIdentifier = b.identifier('SimpleClass');
+    var exportsExpression = b.memberExpression(b.identifier('module'), b.identifier('exports'), false);
+    var assignmentExpression = b.assignmentExpression('=', exportsExpression, classIdentifier);
+    var exportStatement = b.expressionStatement(assignmentExpression);
+
+    ast.program.body.push(exportStatement);
+
+    // When.
+    var printedClass = new Printer().print(ast).code;
+
+    // Then.
+    assert.strictEqual(printedClass, [
+      "/**",
+      " * This class was in an IIFE and returned an instance of itself.",
+      " */",
+      "function SimpleClass() {",
+      "}",
+      "module.exports = SimpleClass;"
+    ].join(eol));
+  });
+
+  it("should support computed properties", function() {
+    var code = [
+      'class A {',
+      '  ["a"]() {}',
+      '  [ID("b")]() {}',
+      '  [0]() {}',
+      '  [ID(1)]() {}',
+      '  get ["a"]() {}',
+      '  get [ID("b")]() {}',
+      '  get [0]() {}',
+      '  get [ID(1)]() {}',
+      '  set ["a"](x) {}',
+      '  set [ID("b")](x) {}',
+      '  set [0](x) {}',
+      '  set [ID(1)](x) {}',
+      '  static ["a"]() {}',
+      '  static [ID("b")]() {}',
+      '  static [0]() {}',
+      '  static [ID(1)]() {}',
+      '  static get ["a"]() {}',
+      '  static get [ID("b")]() {}',
+      '  static get [0]() {}',
+      '  static get [ID(1)]() {}',
+      '  static set ["a"](x) {}',
+      '  static set [ID("b")](x) {}',
+      '  static set [0](x) {}',
+      '  static set [ID(1)](x) {}',
+      '}'
     ].join(eol);
 
-    it("SwitchCase", function() {
-        var ast = parse(switchCase);
-        var printer = new Printer({ tabWidth: 2 });
-
-        var body = ast.program.body;
-        var switchStmt = body[0];
-        n.SwitchStatement.assert(switchStmt);
-
-        // This causes the switch statement to be reprinted.
-        switchStmt.original = null;
-
-        body[0] = b.ifStatement(
-            b.identifier("test"),
-            b.blockStatement([
-                switchStmt
-            ])
-        );
-
-        assert.strictEqual(
-            printer.print(ast).code,
-            switchCaseReprinted
-        );
-
-        assert.strictEqual(
-            printer.printGenerically(ast).code,
-            switchCaseGeneric
-        );
+    var ast = parse(code);
+
+    var printer = new Printer({
+      tabWidth: 2
     });
 
-    var tryCatch = [
-        "try {",
-        "  a();",
-        "} catch (e) {",
-        "  b(e);",
-        "}"
+    assert.strictEqual(
+      printer.printGenerically(ast).code,
+      code
+    );
+
+    var code = [
+      'var obj = {',
+      '  ["a"]: 1,',
+      '  [ID("b")]: 2,',
+      '  [0]: 3,',
+      '  [ID(1)]: 4,',
+      '  ["a"]() {},',
+      '  [ID("b")]() {},',
+      '  [0]() {},',
+      '  [ID(1)]() {},',
+      '  get ["a"]() {},',
+      '  get [ID("b")]() {},',
+      '  get [0]() {},',
+      '  get [ID(1)]() {},',
+      '  set ["a"](x) {},',
+      '  set [ID("b")](x) {},',
+      '  set [0](x) {},',
+      '  set [ID(1)](x) {}',
+      '};'
     ].join(eol);
 
-    it("IndentTryCatch", function() {
-        var ast = parse(tryCatch);
-        var printer = new Printer({ tabWidth: 2 });
-        var body = ast.program.body;
-        var tryStmt = body[0];
-        n.TryStatement.assert(tryStmt);
+    ast = parse(code);
+
+    assert.strictEqual(
+      printer.printGenerically(ast).code,
+      code
+    );
+
+    ast = parse([
+      "var o = {",
+      "  // This foo will become a computed method name.",
+      "  foo() { return bar }",
+      "};"
+    ].join(eol));
+
+    var objExpr = ast.program.body[0].declarations[0].init;
+    n.ObjectExpression.assert(objExpr);
+
+    assert.strictEqual(objExpr.properties[0].computed, false);
+    objExpr.properties[0].computed = true;
+    objExpr.properties[0].kind = "get";
+
+    assert.strictEqual(recast.print(ast).code, [
+      "var o = {",
+      "  // This foo will become a computed method name.",
+      "  get [foo]() { return bar }",
+      "};"
+    ].join(eol));
+  });
+
+  it("prints trailing commas in object literals", function() {
+    var code = [
+      "({",
+      "  foo: bar,",
+      "  bar: foo,",
+      "});"
+    ].join(eol);
 
-        // Force reprinting.
-        assert.strictEqual(printer.printGenerically(ast).code, tryCatch);
+    var ast = parse(code, {
+      // Supports trailing commas whereas plain esprima does not.
+      parser: require("esprima-fb")
     });
 
-    var classBody = [
-        "class A {",
-        "  foo(x) { return x }",
-        "  bar(y) { this.foo(y); }",
-        "  baz(x, y) {",
-        "    this.foo(x);",
-        "    this.bar(y);",
-        "  }",
-        "}"
-    ];
-
-    var classBodyExpected = [
-        "class A {",
-        "  foo(x) { return x }",
-        "  bar(y) { this.foo(y); }",
-        "  baz(x, y) {",
-        "    this.foo(x);",
-        "    this.bar(y);",
-        "  }",
-        "  foo(x) { return x }",
-        "}"
-    ];
-
-    it("MethodPrinting", function() {
-        var code = classBody.join(eol);
-        try {
-            var ast = parse(code);
-        } catch (e) {
-            // ES6 not supported, silently finish
-            return;
-        }
-        var printer = new Printer({ tabWidth: 2 });
-        var cb = ast.program.body[0].body;
-        n.ClassBody.assert(cb);
-
-        // Trigger reprinting of the class body.
-        cb.body.push(cb.body[0]);
-
-        assert.strictEqual(
-            printer.print(ast).code,
-            classBodyExpected.join(eol)
-        );
+    var printer = new Printer({
+      tabWidth: 2,
+      trailingComma: true,
     });
 
-    var multiLineParams = [
-        "function f(/* first",
-        "              xxx",
-        "              param */ a,",
-        "  // other params",
-        "  b, c, // see?",
-        "  d",
-        ") {",
-        "  return a + b + c + d;",
-        "}"
-    ];
-
-    var multiLineParamsExpected = [
-        "function f(",
-        "  /* first",
-        "     xxx",
-        "     param */ a,",
-        "  // other params",
-        "  b,",
-        "  // see?",
-        "  c,",
-        "  d",
-        ") {",
-        "  return a + b + c + d;",
-        "}"
-    ];
+    var pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
 
-    it("MultiLineParams", function() {
-        var code = multiLineParams.join(eol);
-        var ast = parse(code);
-        var printer = new Printer({ tabWidth: 2 });
+    // It should also work when using the `trailingComma` option as an object.
+    printer = new Printer({
+      tabWidth: 2,
+      trailingComma: { objects: true },
+    });
 
-        recast.visit(ast, {
-            visitNode: function(path) {
-                path.value.original = null;
-                this.traverse(path);
-            }
-        });
+    pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+  });
 
-        assert.strictEqual(
-            printer.print(ast).code,
-            multiLineParamsExpected.join(eol)
-        );
-    });
+  it("prints trailing commas in function calls", function() {
+    var code = [
+      "call(",
+      "  1,",
+      "  2,",
+      ");"
+    ].join(eol);
 
-    it("SimpleVarPrinting", function() {
-        var printer = new Printer({ tabWidth: 2 });
-        var varDecl = b.variableDeclaration("var", [
-            b.variableDeclarator(b.identifier("x"), null),
-            b.variableDeclarator(b.identifier("y"), null),
-            b.variableDeclarator(b.identifier("z"), null)
-        ]);
-
-        assert.strictEqual(
-            printer.print(b.program([varDecl])).code,
-            "var x, y, z;"
-        );
-
-        var z = varDecl.declarations.pop();
-        varDecl.declarations.pop();
-        varDecl.declarations.push(z);
-
-        assert.strictEqual(
-            printer.print(b.program([varDecl])).code,
-            "var x, z;"
-        );
+    var ast = parse(code, {
+      // Supports trailing commas whereas plain esprima does not.
+      parser: require("esprima-fb")
     });
 
-    it("MultiLineVarPrinting", function() {
-        var printer = new Printer({ tabWidth: 2 });
-        var varDecl = b.variableDeclaration("var", [
-            b.variableDeclarator(b.identifier("x"), null),
-            b.variableDeclarator(
-                b.identifier("y"),
-                b.objectExpression([
-                    b.property("init", b.identifier("why"), b.literal("not"))
-                ])
-            ),
-            b.variableDeclarator(b.identifier("z"), null)
-        ]);
-
-        assert.strictEqual(printer.print(b.program([varDecl])).code, [
-            "var x,",
-            "    y = {",
-            "      why: \"not\"",
-            "    },",
-            "    z;"
-        ].join(eol));
+    var printer = new Printer({
+      tabWidth: 2,
+      wrapColumn: 1,
+      trailingComma: true,
     });
 
-    it("ForLoopPrinting", function() {
-        var printer = new Printer({ tabWidth: 2 });
-        var loop = b.forStatement(
-            b.variableDeclaration("var", [
-                b.variableDeclarator(b.identifier("i"), b.literal(0))
-            ]),
-            b.binaryExpression("<", b.identifier("i"), b.literal(3)),
-            b.updateExpression("++", b.identifier("i"), /* prefix: */ false),
-            b.expressionStatement(
-                b.callExpression(b.identifier("log"), [b.identifier("i")])
-            )
-        );
+    var pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
 
-        assert.strictEqual(
-            printer.print(loop).code,
-            "for (var i = 0; i < 3; i++)" + eol +
-            "  log(i);"
-        );
+    // It should also work when using the `trailingComma` option as an object.
+    printer = new Printer({
+      tabWidth: 2,
+      wrapColumn: 1,
+      trailingComma: { parameters: true },
     });
 
-    it("EmptyForLoopPrinting", function() {
-        var printer = new Printer({ tabWidth: 2 });
-        var loop = b.forStatement(
-            b.variableDeclaration("var", [
-                b.variableDeclarator(b.identifier("i"), b.literal(0))
-            ]),
-            b.binaryExpression("<", b.identifier("i"), b.literal(3)),
-            b.updateExpression("++", b.identifier("i"), /* prefix: */ false),
-            b.emptyStatement()
-        );
-
-        assert.strictEqual(
-            printer.print(loop).code,
-            "for (var i = 0; i < 3; i++)" + eol +
-            "  ;"
-        );
-    });
+    pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+  });
 
-    it("ForInLoopPrinting", function() {
-        var printer = new Printer({ tabWidth: 2 });
-        var loop = b.forInStatement(
-            b.variableDeclaration("var", [
-                b.variableDeclarator(b.identifier("key"), null)
-            ]),
-            b.identifier("obj"),
-            b.expressionStatement(
-                b.callExpression(b.identifier("log"), [b.identifier("key")])
-            ),
-            /* each: */ false
-        );
-
-        assert.strictEqual(
-            printer.print(loop).code,
-            "for (var key in obj)" + eol +
-            "  log(key);"
-        );
-    });
-
-    it("GuessTabWidth", function() {
-        var code = [
-            "function identity(x) {",
-            "  return x;",
-            "}"
-        ].join(eol);
-
-        var guessedTwo = [
-            "function identity(x) {",
-            "  log(x);",
-            "  return x;",
-            "}"
-        ].join(eol);
-
-        var explicitFour = [
-            "function identity(x) {",
-            "    log(x);",
-            "    return x;",
-            "}"
-        ].join(eol);
-
-        var ast = parse(code);
-
-        var funDecl = ast.program.body[0];
-        n.FunctionDeclaration.assert(funDecl);
-
-        var funBody = funDecl.body.body;
-
-        funBody.unshift(
-            b.expressionStatement(
-                b.callExpression(
-                    b.identifier("log"),
-                    funDecl.params
-                )
-            )
-        );
-
-        assert.strictEqual(
-            new Printer().print(ast).code,
-            guessedTwo
-        );
-
-        assert.strictEqual(
-            new Printer({
-                tabWidth: 4
-            }).print(ast).code,
-            explicitFour
-        );
-    });
+  it("prints trailing commas in array expressions", function() {
+    var code = [
+      "[",
+      "  1,",
+      "  2,",
+      "];"
+    ].join(eol);
 
-    it("FunctionDefaultsAndRest", function() {
-        var printer = new Printer();
-        var funExpr = b.functionExpression(
-            b.identifier('a'),
-            [b.identifier('b'), b.identifier('c')],
-            b.blockStatement([]),
-            false,
-            false,
-            false,
-            undefined
-        );
-
-        funExpr.defaults = [undefined, b.literal(1)];
-        funExpr.rest = b.identifier('d');
-
-        assert.strictEqual(
-            printer.print(funExpr).code,
-            "function a(b, c = 1, ...d) {}"
-        );
-
-        var arrowFunExpr = b.arrowFunctionExpression(
-            [b.identifier('b'), b.identifier('c')],
-            b.blockStatement([]),
-            false,
-            false,
-            false,
-            undefined);
-
-        arrowFunExpr.defaults = [undefined, b.literal(1)];
-        arrowFunExpr.rest = b.identifier('d');
-
-        assert.strictEqual(
-            printer.print(arrowFunExpr).code,
-            "(b, c = 1, ...d) => {}"
-        );
+    var ast = parse(code, {
+      // Supports trailing commas whereas plain esprima does not.
+      parser: require("esprima-fb")
     });
 
-    it("generically prints parsed code and generated code the same way", function() {
-        var printer = new Printer();
-        var ast = b.program([
-            b.expressionStatement(b.literal(1)),
-            b.expressionStatement(b.literal(2))
-        ]);
-
-        assert.strictEqual(
-            printer.printGenerically(parse("1; 2;")).code,
-            printer.printGenerically(ast).code
-        );
+    var printer = new Printer({
+      tabWidth: 2,
+      wrapColumn: 1,
+      trailingComma: true,
     });
 
-    it("ExportDeclaration semicolons", function() {
-        var printer = new Printer();
-        var code = "export var foo = 42;";
-        var ast = parse(code);
-
-        assert.strictEqual(printer.print(ast).code, code);
-        assert.strictEqual(printer.printGenerically(ast).code, code);
-
-        code = "export var foo = 42";
-        ast = parse(code);
-
-        assert.strictEqual(printer.print(ast).code, code);
-        assert.strictEqual(printer.printGenerically(ast).code, code + ";");
+    var pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
 
-        code = "export function foo() {}";
-        ast = parse(code);
-
-        assert.strictEqual(printer.print(ast).code, code);
-        assert.strictEqual(printer.printGenerically(ast).code, code);
+    // It should also work when using the `trailingComma` option as an object.
+    printer = new Printer({
+      tabWidth: 2,
+      wrapColumn: 1,
+      trailingComma: { arrays: true },
     });
 
-    var stmtListSpaces = [
-        "",
-        "var x = 1;",
-        "",
-        "",
-        "// y summation",
-        "var y = x + 1;",
-        "var z = x + y;",
-        "// after z",
-        "",
-        "console.log(x, y, z);",
-        "",
-        ""
-    ].join(eol);
+    pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+  });
 
-    var stmtListSpacesExpected = [
-        "",
-        "debugger;",
-        "var x = 1;",
-        "",
-        "",
-        "// y summation",
-        "var y = x + 1;",
-        "debugger;",
-        "var z = x + y;",
-        "// after z",
-        "",
-        "console.log(x, y, z);",
-        "",
-        "debugger;",
-        "",
-        ""
+  it("prints trailing commas in function definitions", function() {
+    var code = [
+      "function foo(",
+      "  a,",
+      "  b,",
+      ") {}"
     ].join(eol);
 
-    it("Statement list whitespace reuse", function() {
-        var ast = parse(stmtListSpaces);
-        var printer = new Printer({ tabWidth: 2 });
-        var debugStmt = b.expressionStatement(b.identifier("debugger"));
-
-        ast.program.body.splice(2, 0, debugStmt);
-        ast.program.body.unshift(debugStmt);
-        ast.program.body.push(debugStmt);
-
-        assert.strictEqual(
-            printer.print(ast).code,
-            stmtListSpacesExpected
-        );
-
-        var funDecl = b.functionDeclaration(
-            b.identifier("foo"),
-            [],
-            b.blockStatement(ast.program.body)
-        );
-
-        var linesModule = require("../lib/lines");
-
-        assert.strictEqual(
-            printer.print(funDecl).code,
-            linesModule.concat([
-                "function foo() {" + eol,
-                linesModule.fromString(
-                    stmtListSpacesExpected.replace(/^\s+|\s+$/g, "")
-                ).indent(2),
-                eol + "}"
-            ]).toString()
-        );
+    var ast = parse(code, {
+      // Supports trailing commas whereas plain esprima does not.
+      parser: require("esprima-fb")
     });
 
-    it("should print static methods with the static keyword", function() {
-        var printer = new Printer({ tabWidth: 4 });
-        var ast = parse([
-            "class A {",
-            "  static foo() {}",
-            "}"
-        ].join(eol));
-
-        var classBody = ast.program.body[0].body;
-        n.ClassBody.assert(classBody);
-
-        var foo = classBody.body[0];
-        n.MethodDefinition.assert(foo);
-
-        classBody.body.push(foo);
-
-        foo.key.name = "formerlyFoo";
-
-        assert.strictEqual(printer.print(ast).code, [
-            "class A {",
-            "    static formerlyFoo() {}",
-            "    static formerlyFoo() {}",
-            "}"
-        ].join(eol));
+    var printer = new Printer({
+      tabWidth: 2,
+      wrapColumn: 1,
+      trailingComma: true,
     });
 
-    it("should print string literals with the specified delimiter", function() {
-        var ast = parse([
-            "var obj = {",
-            "    \"foo's\": 'bar',",
-            "    '\"bar\\'s\"': /regex/m",
-            "};"
-        ].join(eol));
-
-        var variableDeclaration = ast.program.body[0];
-        n.VariableDeclaration.assert(variableDeclaration);
-
-        var printer = new Printer({ quote: "single" });
-        assert.strictEqual(printer.printGenerically(ast).code, [
-            "var obj = {",
-            "    'foo\\'s': 'bar',",
-            "    '\"bar\\'s\"': /regex/m",
-            "};"
-        ].join(eol));
-
-        var printer2 = new Printer({ quote: "double" });
-        assert.strictEqual(printer2.printGenerically(ast).code, [
-            "var obj = {",
-            "    \"foo's\": \"bar\",",
-            '    "\\"bar\'s\\"": /regex/m',
-            "};"
-        ].join(eol));
-
-        var printer3 = new Printer({ quote: "auto" });
-        assert.strictEqual(printer3.printGenerically(ast).code, [
-            "var obj = {",
-            '    "foo\'s": "bar",',
-            '    \'"bar\\\'s"\': /regex/m',
-            "};"
-        ].join(eol));
-    });
+    var pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
 
-    it("should print block comments at head of class once", function() {
-        // Given.
-        var ast = parse([
-            "/**",
-            " * This class was in an IIFE and returned an instance of itself.",
-            " */",
-            "function SimpleClass() {",
-            "};"
-        ].join(eol));
-
-        var classIdentifier = b.identifier('SimpleClass');
-        var exportsExpression = b.memberExpression(b.identifier('module'), b.identifier('exports'), false);
-        var assignmentExpression = b.assignmentExpression('=', exportsExpression, classIdentifier);
-        var exportStatement = b.expressionStatement(assignmentExpression);
-
-        ast.program.body.push(exportStatement);
-
-        // When.
-        var printedClass = new Printer().print(ast).code;
-
-        // Then.
-        assert.strictEqual(printedClass, [
-            "/**",
-            " * This class was in an IIFE and returned an instance of itself.",
-            " */",
-            "function SimpleClass() {",
-            "}",
-            "module.exports = SimpleClass;"
-        ].join(eol));
+    // It should also work when using the `trailingComma` option as an object.
+    printer = new Printer({
+      tabWidth: 2,
+      wrapColumn: 1,
+      trailingComma: { parameters: true },
     });
 
-    it("should support computed properties", function() {
-        var code = [
-            'class A {',
-            '  ["a"]() {}',
-            '  [ID("b")]() {}',
-            '  [0]() {}',
-            '  [ID(1)]() {}',
-            '  get ["a"]() {}',
-            '  get [ID("b")]() {}',
-            '  get [0]() {}',
-            '  get [ID(1)]() {}',
-            '  set ["a"](x) {}',
-            '  set [ID("b")](x) {}',
-            '  set [0](x) {}',
-            '  set [ID(1)](x) {}',
-            '  static ["a"]() {}',
-            '  static [ID("b")]() {}',
-            '  static [0]() {}',
-            '  static [ID(1)]() {}',
-            '  static get ["a"]() {}',
-            '  static get [ID("b")]() {}',
-            '  static get [0]() {}',
-            '  static get [ID(1)]() {}',
-            '  static set ["a"](x) {}',
-            '  static set [ID("b")](x) {}',
-            '  static set [0](x) {}',
-            '  static set [ID(1)](x) {}',
-            '}'
-        ].join(eol);
-
-        var ast = parse(code);
-
-        var printer = new Printer({
-            tabWidth: 2
-        });
+    pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+  });
+
+  it("shouldn't print a trailing comma for a RestElement", function() {
+    var code = [
+      "function foo(",
+      "  a,",
+      "  b,",
+      "  ...rest",
+      ") {}"
+    ].join(eol);
 
-        assert.strictEqual(
-            printer.printGenerically(ast).code,
-            code
-        );
-
-        var code = [
-            'var obj = {',
-            '  ["a"]: 1,',
-            '  [ID("b")]: 2,',
-            '  [0]: 3,',
-            '  [ID(1)]: 4,',
-            '  ["a"]() {},',
-            '  [ID("b")]() {},',
-            '  [0]() {},',
-            '  [ID(1)]() {},',
-            '  get ["a"]() {},',
-            '  get [ID("b")]() {},',
-            '  get [0]() {},',
-            '  get [ID(1)]() {},',
-            '  set ["a"](x) {},',
-            '  set [ID("b")](x) {},',
-            '  set [0](x) {},',
-            '  set [ID(1)](x) {}',
-            '};'
-        ].join(eol);
-
-        ast = parse(code);
-
-        assert.strictEqual(
-            printer.printGenerically(ast).code,
-            code
-        );
-
-        ast = parse([
-            "var o = {",
-            "  // This foo will become a computed method name.",
-            "  foo() { return bar }",
-            "};"
-        ].join(eol));
-
-        var objExpr = ast.program.body[0].declarations[0].init;
-        n.ObjectExpression.assert(objExpr);
-
-        assert.strictEqual(objExpr.properties[0].computed, false);
-        objExpr.properties[0].computed = true;
-        objExpr.properties[0].kind = "get";
-
-        assert.strictEqual(recast.print(ast).code, [
-            "var o = {",
-            "  // This foo will become a computed method name.",
-            "  get [foo]() { return bar }",
-            "};"
-        ].join(eol));
+    var ast = parse(code, {
+      // The flow parser and Babylon recognize `...rest` as a `RestElement`
+      parser: require("babylon")
     });
 
-    it("prints trailing commas in object literals", function() {
-        var code = [
-            "({",
-            "  foo: bar,",
-            "  bar: foo,",
-            "});"
-        ].join(eol);
-
-        var ast = parse(code, {
-            // Supports trailing commas whereas plain esprima does not.
-            parser: require("esprima-fb")
-        });
-
-        var printer = new Printer({
-            tabWidth: 2,
-            trailingComma: true,
-        });
-
-        var pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
-
-        // It should also work when using the `trailingComma` option as an object.
-        printer = new Printer({
-          tabWidth: 2,
-          trailingComma: { objects: true },
-        });
-
-        pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
+    var printer = new Printer({
+      tabWidth: 2,
+      wrapColumn: 1,
+      trailingComma: true,
     });
 
-    it("prints trailing commas in function calls", function() {
-        var code = [
-            "call(",
-            "  1,",
-            "  2,",
-            ");"
-        ].join(eol);
-
-        var ast = parse(code, {
-            // Supports trailing commas whereas plain esprima does not.
-            parser: require("esprima-fb")
-        });
-
-        var printer = new Printer({
-            tabWidth: 2,
-            wrapColumn: 1,
-            trailingComma: true,
-        });
-
-        var pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
+    var pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+  });
 
-        // It should also work when using the `trailingComma` option as an object.
-        printer = new Printer({
-          tabWidth: 2,
-          wrapColumn: 1,
-          trailingComma: { parameters: true },
-        });
+  it("should support AssignmentPattern and RestElement", function() {
+    var code = [
+      "function foo(a, [b, c] = d(a), ...[e, f, ...rest]) {",
+      "  return [a, b, c, e, f, rest];",
+      "}"
+    ].join(eol);
 
-        pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
+    var ast = parse(code, {
+      // Supports rest parameter destructuring whereas plain esprima
+      // does not.
+      parser: require("esprima-fb")
     });
 
-    it("prints trailing commas in array expressions", function() {
-        var code = [
-            "[",
-            "  1,",
-            "  2,",
-            "];"
-        ].join(eol);
-
-        var ast = parse(code, {
-            // Supports trailing commas whereas plain esprima does not.
-            parser: require("esprima-fb")
-        });
-
-        var printer = new Printer({
-            tabWidth: 2,
-            wrapColumn: 1,
-            trailingComma: true,
-        });
-
-        var pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
-
-        // It should also work when using the `trailingComma` option as an object.
-        printer = new Printer({
-          tabWidth: 2,
-          wrapColumn: 1,
-          trailingComma: { arrays: true },
-        });
-
-        pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
+    var printer = new Printer({
+      tabWidth: 2
     });
 
-    it("prints trailing commas in function definitions", function() {
-        var code = [
-            "function foo(",
-            "  a,",
-            "  b,",
-            ") {}"
-        ].join(eol);
-
-        var ast = parse(code, {
-            // Supports trailing commas whereas plain esprima does not.
-            parser: require("esprima-fb")
-        });
+    var pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+  });
 
-        var printer = new Printer({
-            tabWidth: 2,
-            wrapColumn: 1,
-            trailingComma: true,
-        });
-
-        var pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
+  it("adds parenthesis around spread patterns", function() {
+    var code = "(...rest) => rest;";
 
-        // It should also work when using the `trailingComma` option as an object.
-        printer = new Printer({
-          tabWidth: 2,
-          wrapColumn: 1,
-          trailingComma: { parameters: true },
-        });
+    var ast = b.program([
+      b.expressionStatement(b.arrowFunctionExpression(
+        [b.spreadElementPattern(b.identifier('rest'))],
+        b.identifier('rest'),
+        false
+      ))
+    ]);
 
-        pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
+    var printer = new Printer({
+      tabWidth: 2
     });
 
-    it("shouldn't print a trailing comma for a RestElement", function() {
-        var code = [
-            "function foo(",
-            "  a,",
-            "  b,",
-            "  ...rest",
-            ") {}"
-        ].join(eol);
-
-        var ast = parse(code, {
-            // The flow parser and Babylon recognize `...rest` as a `RestElement`
-            parser: require("babylon")
-        });
-
-        var printer = new Printer({
-            tabWidth: 2,
-            wrapColumn: 1,
-            trailingComma: true,
-        });
-
-        var pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
+    var pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+
+    // Print RestElement the same way
+    ast = b.program([
+      b.expressionStatement(b.arrowFunctionExpression(
+        [b.restElement(b.identifier('rest'))],
+        b.identifier('rest'),
+        false
+      ))
+    ]);
+
+    pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+
+    // Do the same for the `rest` field.
+    var arrowFunction = b.arrowFunctionExpression(
+      [],
+      b.identifier('rest'),
+      false
+    );
+    arrowFunction.rest = b.identifier('rest');
+    ast = b.program([
+      b.expressionStatement(arrowFunction)
+    ]);
+
+    pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+  });
+
+  it("adds parenthesis around single arrow function arg when options.arrowParensAlways is true", function() {
+    var code = "(a) => {};";
+
+    var fn = b.arrowFunctionExpression(
+      [b.identifier('a')],
+      b.blockStatement([]),
+      false
+    );
+
+    var ast = b.program([
+      b.expressionStatement(fn)
+    ]);
+
+    var printer = new Printer({
+      arrowParensAlways: true
     });
-
-    it("should support AssignmentPattern and RestElement", function() {
-        var code = [
-            "function foo(a, [b, c] = d(a), ...[e, f, ...rest]) {",
-            "  return [a, b, c, e, f, rest];",
-            "}"
-        ].join(eol);
-
-        var ast = parse(code, {
-            // Supports rest parameter destructuring whereas plain esprima
-            // does not.
-            parser: require("esprima-fb")
-        });
-
-        var printer = new Printer({
-            tabWidth: 2
-        });
-
-        var pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
+    var pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+  });
+
+  it("adds parenthesis around arrow function when binding", function() {
+    var code = "var a = (x => y).bind(z);";
+
+    var fn = b.arrowFunctionExpression(
+      [b.identifier("x")],
+      b.identifier("y")
+    );
+
+    var declaration = b.variableDeclaration("var", [
+      b.variableDeclarator(
+        b.identifier("a"),
+        b.callExpression(
+          b.memberExpression(fn, b.identifier("bind"), false),
+          [b.identifier("z")]
+        )
+      )
+    ]);
+
+    var ast = b.program([declaration]);
+
+    var printer = new Printer();
+    var pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+  });
+
+  it("adds parenthesis around async arrow functions with args", function() {
+    var code = "async () => {};";
+
+    var fn = b.arrowFunctionExpression(
+      [],
+      b.blockStatement([]),
+      false
+    );
+    fn.async = true;
+
+    var ast = b.program([
+      b.expressionStatement(fn)
+    ]);
+
+    var printer = new Printer({
+      tabWidth: 2
     });
 
-    it("adds parenthesis around spread patterns", function() {
-        var code = "(...rest) => rest;";
+    var pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
 
-        var ast = b.program([
-            b.expressionStatement(b.arrowFunctionExpression(
-                [b.spreadElementPattern(b.identifier('rest'))],
-                b.identifier('rest'),
-                false
-            ))
-        ]);
+    // No parenthesis for single params if they are identifiers
+    code = "async foo => {};";
+    fn.params = [b.identifier('foo')];
 
-        var printer = new Printer({
-            tabWidth: 2
-        });
+    pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
 
-        var pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
+    // Add parenthesis for destructuring
+    code = "async ([a, b]) => {};";
+    fn.params = [b.arrayPattern([b.identifier('a'), b.identifier('b')])];
 
-        // Print RestElement the same way
-        ast = b.program([
-            b.expressionStatement(b.arrowFunctionExpression(
-                [b.restElement(b.identifier('rest'))],
-                b.identifier('rest'),
-                false
-            ))
-        ]);
+    pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+  });
 
-        pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
+  it("adds parenthesis around arrow functions with single arg and a type", function() {
+    var code = "(a: b) => {};";
 
-        // Do the same for the `rest` field.
-        var arrowFunction = b.arrowFunctionExpression(
-            [],
-            b.identifier('rest'),
-            false
-        );
-        arrowFunction.rest = b.identifier('rest');
-        ast = b.program([
-            b.expressionStatement(arrowFunction)
-        ]);
-
-        pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
-    });
+    var arg = b.identifier('a');
+    arg.typeAnnotation = b.typeAnnotation(
+      b.genericTypeAnnotation(b.identifier('b'), null)
+    );
 
-    it("adds parenthesis around single arrow function arg when options.arrowParensAlways is true", function() {
-      var code = "(a) => {};";
+    var fn = b.arrowFunctionExpression(
+      [arg],
+      b.blockStatement([]),
+      false
+    );
 
-      var fn = b.arrowFunctionExpression(
-          [b.identifier('a')],
-          b.blockStatement([]),
-          false
-      );
+    var ast = b.program([
+      b.expressionStatement(fn)
+    ]);
 
-      var ast = b.program([
-          b.expressionStatement(fn)
-      ]);
+    var printer = new Printer();
+    var pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+  });
 
-      var printer = new Printer({
-        arrowParensAlways: true
-      });
-      var pretty = printer.printGenerically(ast).code;
-      assert.strictEqual(pretty, code);
-    });
+  it("adds parenthesis around arrow functions with single arg and a return type", function() {
+    var code = "(a): void => {};";
 
-    it("adds parenthesis around arrow function when binding", function() {
-      var code = "var a = (x => y).bind(z);";
+    var arg = b.identifier('a');
 
-      var fn = b.arrowFunctionExpression(
-          [b.identifier("x")],
-          b.identifier("y")
-      );
+    var fn = b.arrowFunctionExpression(
+      [arg],
+      b.blockStatement([]),
+      false
+    );
 
-      var declaration = b.variableDeclaration("var", [
-          b.variableDeclarator(
-              b.identifier("a"),
-              b.callExpression(
-                  b.memberExpression(fn, b.identifier("bind"), false),
-                  [b.identifier("z")]
-              )
-          )
-      ]);
+    fn.returnType = b.typeAnnotation(
+      b.voidTypeAnnotation()
+    );
 
-      var ast = b.program([declaration]);
+    var ast = b.program([
+      b.expressionStatement(fn)
+    ]);
 
-      var printer = new Printer();
-      var pretty = printer.printGenerically(ast).code;
-      assert.strictEqual(pretty, code);
-    });
+    var printer = new Printer();
+    var pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+  });
 
-    it("adds parenthesis around async arrow functions with args", function() {
-        var code = "async () => {};";
+  it("prints class property initializers with type annotations correctly", function() {
+    var code = [
+      "class A {",
+      "  foo = (a: b): void => {};",
+      "}",
+    ].join(eol);
 
-        var fn = b.arrowFunctionExpression(
-            [],
-            b.blockStatement([]),
+    var arg = b.identifier('a');
+    arg.typeAnnotation = b.typeAnnotation(
+      b.genericTypeAnnotation(b.identifier('b'), null)
+    );
+
+    var fn = b.arrowFunctionExpression(
+      [arg],
+      b.blockStatement([]),
+      false
+    );
+    fn.returnType = b.typeAnnotation(
+      b.voidTypeAnnotation()
+    );
+
+    var ast = b.program([
+      b.classDeclaration(
+        b.identifier('A'),
+        b.classBody([
+          b.classProperty(
+            b.identifier('foo'),
+            fn,
+            null,
             false
-        );
-        fn.async = true;
-
-        var ast = b.program([
-            b.expressionStatement(fn)
-        ]);
-
-        var printer = new Printer({
-            tabWidth: 2
-        });
-
-        var pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
-
-        // No parenthesis for single params if they are identifiers
-        code = "async foo => {};";
-        fn.params = [b.identifier('foo')];
-
-        pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
-
-        // Add parenthesis for destructuring
-        code = "async ([a, b]) => {};";
-        fn.params = [b.arrayPattern([b.identifier('a'), b.identifier('b')])];
+          )
+        ])
+      )
+    ]);
 
-        pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
+    var printer = new Printer({
+      tabWidth: 2
     });
 
-    it("adds parenthesis around arrow functions with single arg and a type", function() {
-        var code = "(a: b) => {};";
-
-        var arg = b.identifier('a');
-        arg.typeAnnotation = b.typeAnnotation(
-            b.genericTypeAnnotation(b.identifier('b'), null)
-        );
+    var pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+  });
 
-        var fn = b.arrowFunctionExpression(
-            [arg],
-            b.blockStatement([]),
-            false
-        );
+  it("prints ClassProperty correctly", function() {
+    var code = [
+      "class A {",
+      "  foo: Type = Bar;",
+      "}",
+    ].join(eol);
 
-        var ast = b.program([
-            b.expressionStatement(fn)
-        ]);
+    var ast = b.program([
+      b.classDeclaration(
+        b.identifier('A'),
+        b.classBody([
+          b.classProperty(
+            b.identifier('foo'),
+            b.identifier('Bar'),
+            b.typeAnnotation(
+              b.genericTypeAnnotation(b.identifier('Type'), null)
+            )
+          )
+        ])
+      )
+    ]);
 
-        var printer = new Printer();
-        var pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
+    var printer = new Printer({
+      tabWidth: 2
     });
 
-    it("adds parenthesis around arrow functions with single arg and a return type", function() {
-        var code = "(a): void => {};";
-
-        var arg = b.identifier('a');
-
-        var fn = b.arrowFunctionExpression(
-            [arg],
-            b.blockStatement([]),
-            false
-        );
+    var pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+  });
 
-        fn.returnType = b.typeAnnotation(
-            b.voidTypeAnnotation()
-        );
+  it("prints static ClassProperty correctly", function() {
+    var code = [
+      "class A {",
+      "  static foo = Bar;",
+      "}",
+    ].join(eol);
 
-        var ast = b.program([
-            b.expressionStatement(fn)
-        ]);
+    var ast = b.program([
+      b.classDeclaration(
+        b.identifier('A'),
+        b.classBody([
+          b.classProperty(
+            b.identifier('foo'),
+            b.identifier('Bar'),
+            null,
+            true
+          )
+        ])
+      )
+    ]);
 
-        var printer = new Printer();
-        var pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
+    var printer = new Printer({
+      tabWidth: 2
     });
 
-    it("prints class property initializers with type annotations correctly", function() {
-        var code = [
-            "class A {",
-            "  foo = (a: b): void => {};",
-            "}",
-        ].join(eol);
-
-        var arg = b.identifier('a');
-        arg.typeAnnotation = b.typeAnnotation(
-            b.genericTypeAnnotation(b.identifier('b'), null)
-        );
-
-        var fn = b.arrowFunctionExpression(
-            [arg],
-            b.blockStatement([]),
-            false
-        );
-        fn.returnType = b.typeAnnotation(
-            b.voidTypeAnnotation()
-        );
-
-        var ast = b.program([
-            b.classDeclaration(
-                b.identifier('A'),
-                b.classBody([
-                    b.classProperty(
-                        b.identifier('foo'),
-                        fn,
-                        null,
-                        false
-                    )
-                ])
-            )
-        ]);
+    var pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+  });
 
-        var printer = new Printer({
-            tabWidth: 2
-        });
+  it("prints template expressions correctly", function() {
+    var code = [
+      "graphql`query`;",
+    ].join(eol);
 
-        var pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
+    var ast = b.program([
+      b.taggedTemplateStatement(
+        b.identifier('graphql'),
+        b.templateLiteral(
+          [b.templateElement({cooked: 'query', raw: 'query'}, false)],
+          []
+        )
+      )
+    ]);
+
+    var printer = new Printer({
+      tabWidth: 2
     });
 
-    it("prints ClassProperty correctly", function() {
-        var code = [
-            "class A {",
-            "  foo: Type = Bar;",
-            "}",
-        ].join(eol);
-
-        var ast = b.program([
-            b.classDeclaration(
-                b.identifier('A'),
-                b.classBody([
-                    b.classProperty(
-                        b.identifier('foo'),
-                        b.identifier('Bar'),
-                        b.typeAnnotation(
-                            b.genericTypeAnnotation(b.identifier('Type'), null)
-                        )
-                    )
-                ])
-            )
-        ]);
+    var pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
 
-        var printer = new Printer({
-            tabWidth: 2
-        });
+    code = [
+      "graphql`query${foo.getQuery()}field${bar}`;",
+    ].join(eol);
 
-        var pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
-    });
+    ast = b.program([
+      b.taggedTemplateStatement(
+        b.identifier('graphql'),
+        b.templateLiteral(
+          [
+            b.templateElement(
+              {cooked: 'query', raw: 'query'},
+              false
+            ),
+            b.templateElement(
+              {cooked: 'field', raw: 'field'},
+              false
+            ),
+            b.templateElement(
+              {cooked: '', raw: ''},
+              true
+            ),
+          ],
+          [
+            b.callExpression(
+              b.memberExpression(
+                b.identifier('foo'),
+                b.identifier('getQuery'),
+                false
+              ),
+              []
+            ),
+            b.identifier('bar')
+          ]
+        )
+      )
+    ]);
+
+    pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+
+    code = [
+      "graphql`",
+      "  query {",
+      "    ${foo.getQuery()},",
+      "    field,",
+      "    ${bar},",
+      "  }",
+      "`;",
+    ].join(eol);
 
-    it("prints static ClassProperty correctly", function() {
-        var code = [
-            "class A {",
-            "  static foo = Bar;",
-            "}",
-        ].join(eol);
-
-        var ast = b.program([
-            b.classDeclaration(
-                b.identifier('A'),
-                b.classBody([
-                    b.classProperty(
-                        b.identifier('foo'),
-                        b.identifier('Bar'),
-                        null,
-                        true
-                    )
-                ])
-            )
-        ]);
+    ast = parse(code);
+    pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+  });
 
-        var printer = new Printer({
-            tabWidth: 2
-        });
+  it("preserves newlines at the beginning/end of files", function() {
+    var code = [
+      "",
+      "f();",
+      ""
+    ].join(eol);
 
-        var pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
+    var lines = fromString(code);
+    var ast = parse(code, {
+      esprima: {
+        parse: function(source, options) {
+          var program = require("esprima").parse(source, options);
+          n.Program.assert(program);
+          // Expand ast.program.loc to include any
+          // leading/trailing whitespace, to simulate the
+          // behavior of some parsers, e.g. babel-core.
+          lines.skipSpaces(program.loc.start, true, true);
+          lines.skipSpaces(program.loc.end, false, true);
+          return program;
+        }
+      }
     });
 
-    it("prints template expressions correctly", function() {
-        var code = [
-            "graphql`query`;",
-        ].join(eol);
-
-        var ast = b.program([
-            b.taggedTemplateStatement(
-                b.identifier('graphql'),
-                b.templateLiteral(
-                    [b.templateElement({cooked: 'query', raw: 'query'}, false)],
-                    []
-                )
-            )
-        ]);
+    ast.program.body.unshift(b.debuggerStatement());
 
-        var printer = new Printer({
-            tabWidth: 2
-        });
-
-        var pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
-
-        code = [
-            "graphql`query${foo.getQuery()}field${bar}`;",
-        ].join(eol);
-
-        ast = b.program([
-            b.taggedTemplateStatement(
-                b.identifier('graphql'),
-                b.templateLiteral(
-                    [
-                        b.templateElement(
-                            {cooked: 'query', raw: 'query'},
-                            false
-                        ),
-                        b.templateElement(
-                            {cooked: 'field', raw: 'field'},
-                            false
-                        ),
-                        b.templateElement(
-                            {cooked: '', raw: ''},
-                            true
-                        ),
-                    ],
-                    [
-                        b.callExpression(
-                            b.memberExpression(
-                                b.identifier('foo'),
-                                b.identifier('getQuery'),
-                                false
-                            ),
-                            []
-                        ),
-                        b.identifier('bar')
-                    ]
-                )
-            )
-        ]);
-
-        pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
-
-        code = [
-            "graphql`",
-            "  query {",
-            "    ${foo.getQuery()},",
-            "    field,",
-            "    ${bar},",
-            "  }",
-            "`;",
-        ].join(eol);
-
-        ast = parse(code);
-        pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
+    var printer = new Printer({
+      tabWidth: 2
     });
 
-    it("preserves newlines at the beginning/end of files", function() {
-        var code = [
-            "",
-            "f();",
-            ""
-        ].join(eol);
-
-        var lines = fromString(code);
-        var ast = parse(code, {
-            esprima: {
-                parse: function(source, options) {
-                    var program = require("esprima").parse(source, options);
-                    n.Program.assert(program);
-                    // Expand ast.program.loc to include any
-                    // leading/trailing whitespace, to simulate the
-                    // behavior of some parsers, e.g. babel-core.
-                    lines.skipSpaces(program.loc.start, true, true);
-                    lines.skipSpaces(program.loc.end, false, true);
-                    return program;
-                }
-            }
-        });
+    assert.strictEqual(printer.print(ast).code, [
+      "",
+      "debugger;",
+      "f();",
+      ""
+    ].join(eol));
+  });
+
+  it("respects options.lineTerminator", function() {
+    var lines = [
+      "var first = 1;",
+      "var second = 2;"
+    ];
+    var code = lines.join("\n");
+    var ast = parse(code);
+
+    assert.strictEqual(
+      new Printer({
+        lineTerminator: "\r\n"
+      }).print(ast).code,
+      lines.join("\r\n")
+    );
+  });
+
+  it("preserves indentation in unmodified template expressions", function() {
+    var printer = new Printer({
+      tabWidth: 2
+    });
 
-        ast.program.body.unshift(b.debuggerStatement());
+    var code = [
+      "var x = {",
+      "  y: () => Relay.QL`",
+      "    query {",
+      "      ${foo},",
+      "      field,",
+      "    }",
+      "  `",
+      "};",
+    ].join(eol);
 
-        var printer = new Printer({
-            tabWidth: 2
-        });
+    var ast = parse(code);
+    var pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+  });
+
+  it("preserves indentation in modified template expressions", function() {
+    var code = [
+      "const fragments = {",
+      "  viewer: Relay.QL`",
+      "    fragment on Viewer {   // 2 extraneous spaces.",
+      "      actor {              // 2 extraneous spaces.",
+      "        id,            // 2 extraneous spaces.",
+      "        ${ foo},           // 3 extraneous spaces.",
+      "        ${bar },              // Correct!",
+      "        name,                // Correct!",
+      "        ${baz},              // Correct!",
+      "        address {          // 2 extraneous spaces.",
+      "          id,          // 2 extraneous spaces.",
+      "        },                 // 2 extraneous spaces.",
+      "      }                    // 2 extraneous spaces.",
+      "    }                      // 2 extraneous spaces.",
+      "<~ This line should not be indented.",
+      "  `,                       // 2 extraneous spaces.",
+      "};"
+    ].join(eol);
 
-        assert.strictEqual(printer.print(ast).code, [
-            "",
-            "debugger;",
-            "f();",
-            ""
-        ].join(eol));
+    var ast = parse(code);
+    var printer = new Printer({
+      tabWidth: 2
     });
 
-    it("respects options.lineTerminator", function() {
-        var lines = [
-            "var first = 1;",
-            "var second = 2;"
-        ];
-        var code = lines.join("\n");
-        var ast = parse(code);
-
-        assert.strictEqual(
-            new Printer({
-                lineTerminator: "\r\n"
-            }).print(ast).code,
-            lines.join("\r\n")
-        );
-    });
+    recast.visit(ast, {
+      visitTaggedTemplateExpression: function (path) {
+        function replaceIdWithNodeId(path) {
+          path.replace(path.value.replace(/\bid\b/g, "nodeID"));
+        }
 
-    it("preserves indentation in unmodified template expressions", function() {
-        var printer = new Printer({
-            tabWidth: 2
+        path.get("quasi", "quasis").each(function (quasiPath) {
+          replaceIdWithNodeId(quasiPath.get("value", "cooked"));
+          replaceIdWithNodeId(quasiPath.get("value", "raw"));
         });
 
-        var code = [
-            "var x = {",
-            "  y: () => Relay.QL`",
-            "    query {",
-            "      ${foo},",
-            "      field,",
-            "    }",
-            "  `",
-            "};",
-        ].join(eol);
-
-        var ast = parse(code);
-        var pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
+        this.traverse(path);
+      }
     });
 
-    it("preserves indentation in modified template expressions", function() {
-        var code = [
-            "const fragments = {",
-            "  viewer: Relay.QL`",
-            "    fragment on Viewer {   // 2 extraneous spaces.",
-            "      actor {              // 2 extraneous spaces.",
-            "        id,            // 2 extraneous spaces.",
-            "        ${ foo},           // 3 extraneous spaces.",
-            "        ${bar },              // Correct!",
-            "        name,                // Correct!",
-            "        ${baz},              // Correct!",
-            "        address {          // 2 extraneous spaces.",
-            "          id,          // 2 extraneous spaces.",
-            "        },                 // 2 extraneous spaces.",
-            "      }                    // 2 extraneous spaces.",
-            "    }                      // 2 extraneous spaces.",
-            "<~ This line should not be indented.",
-            "  `,                       // 2 extraneous spaces.",
-            "};"
-        ].join(eol);
-
-        var ast = parse(code);
-        var printer = new Printer({
-            tabWidth: 2
-        });
-
-        recast.visit(ast, {
-            visitTaggedTemplateExpression: function (path) {
-                function replaceIdWithNodeId(path) {
-                    path.replace(path.value.replace(/\bid\b/g, "nodeID"));
-                }
+    var actual = printer.print(ast).code;
+    var expected = code.replace(/\bid\b/g, "nodeID");
 
-                path.get("quasi", "quasis").each(function (quasiPath) {
-                    replaceIdWithNodeId(quasiPath.get("value", "cooked"));
-                    replaceIdWithNodeId(quasiPath.get("value", "raw"));
-                });
+    assert.strictEqual(actual, expected);
+  });
 
-                this.traverse(path);
-            }
-        });
+  it("prints commas for flow object types by default", function() {
+    var code = [
+      "type MyType = {",
+      "    message: string,",
+      "    isAwesome: boolean,",
+      "};"
+    ].join(eol);
 
-        var actual = printer.print(ast).code;
-        var expected = code.replace(/\bid\b/g, "nodeID");
+    var ast = b.typeAlias(
+      b.identifier("MyType"),
+      null,
+      b.objectTypeAnnotation([
+        b.objectTypeProperty(
+          b.identifier("message"),
+          b.stringTypeAnnotation(),
+          false
+        ),
+        b.objectTypeProperty(
+          b.identifier("isAwesome"),
+          b.booleanTypeAnnotation(),
+          false
+        )
+      ])
+    );
+
+    var printer = new Printer();
+    var pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+  });
+
+  it("shouldn't print a trailing comma for single-line flow object types", function() {
+    var code1 = "type MyType = { message: string };";
+    var code2 = "type MyType = { [key: string]: string };";
+
+    var ast1 = b.typeAlias(
+      b.identifier("MyType"),
+      null,
+      b.objectTypeAnnotation([
+        b.objectTypeProperty(
+          b.identifier("message"),
+          b.stringTypeAnnotation(),
+          false
+        )
+      ])
+    );
+
+    var ast2 = b.typeAlias(
+      b.identifier("MyType"),
+      null,
+      b.objectTypeAnnotation([], [
+        b.objectTypeIndexer(
+          b.identifier('key'),
+          b.stringTypeAnnotation(),
+          b.stringTypeAnnotation(),
+          false
+        )
+      ])
+    );
+
+    var printer = new Printer({trailingComma: true});
+    var pretty1 = printer.printGenerically(ast1).code;
+    var pretty2 = printer.printGenerically(ast2).code;
+    assert.strictEqual(pretty1, code1);
+    assert.strictEqual(pretty2, code2);
+  });
+
+  it("prints semicolons for flow object types when options.flowObjectCommas is falsy", function() {
+    var code = [
+      "type MyType = {",
+      "    message: string;",
+      "    isAwesome: boolean;",
+      "};"
+    ].join(eol);
 
-        assert.strictEqual(actual, expected);
-    });
+    var ast = b.typeAlias(
+      b.identifier("MyType"),
+      null,
+      b.objectTypeAnnotation([
+        b.objectTypeProperty(
+          b.identifier("message"),
+          b.stringTypeAnnotation(),
+          false
+        ),
+        b.objectTypeProperty(
+          b.identifier("isAwesome"),
+          b.booleanTypeAnnotation(),
+          false
+        )
+      ])
+    );
+
+    var printer = new Printer({ flowObjectCommas: false });
+    var pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+  });
+
+  it("prints parens for nullable union/intersection types", function() {
+    var code = "type MyType = ?(string | number);";
+
+    var ast = b.typeAlias(
+      b.identifier("MyType"),
+      null,
+      b.nullableTypeAnnotation(
+        b.unionTypeAnnotation(
+          [b.stringTypeAnnotation(), b.numberTypeAnnotation()]
+        )
+      )
+    );
+
+    var printer = new Printer({});
+    var pretty = printer.printGenerically(ast).code;
+    assert.strictEqual(pretty, code);
+  });
+
+  it("uses the `arrayBracketSpacing` and the `objectCurlySpacing` option", function() {
+    var babylon = require("babylon");
+    var parseOptions = {
+      parser: {
+        parse: function (source) {
+          return babylon.parse(source, {
+            sourceType: 'module',
+            plugins: ['flow'],
+          });
+        }
+      }
+    };
 
-    it("prints commas for flow object types by default", function() {
-        var code = [
-            "type MyType = {",
-            "    message: string,",
-            "    isAwesome: boolean,",
-            "};"
-        ].join(eol);
+    var testCaseList = [{
+      printerConfig: {arrayBracketSpacing: false, objectCurlySpacing: false},
+      code: [
+        'import {java, script} from "javascript";',
+        '',
+        'function foo(a) {',
+        '    type MyType = {message: string};',
+        '    return [1, 2, 3];',
+        '}',
+        '',
+        'export {foo};'
+      ].join(eol)
+    }, {
+      printerConfig: {arrayBracketSpacing: true, objectCurlySpacing: false},
+      code: [
+        'import {java, script} from "javascript";',
+        '',
+        'function foo(a) {',
+        '    type MyType = {message: string};',
+        '    return [ 1, 2, 3 ];',
+        '}',
+        '',
+        'export {foo};'
+      ].join(eol)
+    }, {
+      printerConfig: {arrayBracketSpacing: false, objectCurlySpacing: true},
+      code: [
+        'import { java, script } from "javascript";',
+        '',
+        'function foo(a) {',
+        '    type MyType = { message: string };',
+        '    return [1, 2, 3];',
+        '}',
+        '',
+        'export { foo };'
+      ].join(eol)
+    }, {
+      printerConfig: {arrayBracketSpacing: true, objectCurlySpacing: true},
+      code: [
+        'import { java, script } from "javascript";',
+        '',
+        'function foo(a) {',
+        '    type MyType = { message: string };',
+        '    return [ 1, 2, 3 ];',
+        '}',
+        '',
+        'export { foo };'
+      ].join(eol)
+    }];
 
-        var ast = b.typeAlias(
-            b.identifier("MyType"),
-            null,
-            b.objectTypeAnnotation([
-                b.objectTypeProperty(
-                    b.identifier("message"),
-                    b.stringTypeAnnotation(),
-                    false
-                ),
-                b.objectTypeProperty(
-                    b.identifier("isAwesome"),
-                    b.booleanTypeAnnotation(),
-                    false
-                )
-            ])
-        );
-
-        var printer = new Printer();
-        var pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
-    });
+    testCaseList.forEach(function(testCase) {
+      var code = testCase.code;
+      var printer = new Printer(testCase.printerConfig);
 
-    it("shouldn't print a trailing comma for single-line flow object types", function() {
-        var code1 = "type MyType = { message: string };";
-        var code2 = "type MyType = { [key: string]: string };";
+      var ast = parse(code, parseOptions);
+      var pretty = printer.printGenerically(ast).code;
 
-        var ast1 = b.typeAlias(
-            b.identifier("MyType"),
-            null,
-            b.objectTypeAnnotation([
-                b.objectTypeProperty(
-                    b.identifier("message"),
-                    b.stringTypeAnnotation(),
-                    false
-                )
-            ])
-        );
-
-        var ast2 = b.typeAlias(
-            b.identifier("MyType"),
-            null,
-            b.objectTypeAnnotation([], [
-                b.objectTypeIndexer(
-                    b.identifier('key'),
-                    b.stringTypeAnnotation(),
-                    b.stringTypeAnnotation(),
-                    false
-                )
-            ])
-        );
-
-        var printer = new Printer({trailingComma: true});
-        var pretty1 = printer.printGenerically(ast1).code;
-        var pretty2 = printer.printGenerically(ast2).code;
-        assert.strictEqual(pretty1, code1);
-        assert.strictEqual(pretty2, code2);
+      assert.strictEqual(pretty, code);
     });
+  });
 
-    it("prints semicolons for flow object types when options.flowObjectCommas is falsy", function() {
-        var code = [
-            "type MyType = {",
-            "    message: string;",
-            "    isAwesome: boolean;",
-            "};"
-        ].join(eol);
+  it("prints no extra semicolons in for-loop heads (#377)", function () {
+    function check(head, parser) {
+      var source = "for (" + head + ") console.log(i);";
+      var ast = recast.parse(source, { parser: parser });
+      var loop = ast.program.body[0];
+      assert.strictEqual(loop.type, "ForStatement");
+      loop.body = b.blockStatement([]);
 
-        var ast = b.typeAlias(
-            b.identifier("MyType"),
-            null,
-            b.objectTypeAnnotation([
-                b.objectTypeProperty(
-                    b.identifier("message"),
-                    b.stringTypeAnnotation(),
-                    false
-                ),
-                b.objectTypeProperty(
-                    b.identifier("isAwesome"),
-                    b.booleanTypeAnnotation(),
-                    false
-                )
-            ])
-        );
-
-        var printer = new Printer({ flowObjectCommas: false });
-        var pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
-    });
+      var reprinted = recast.print(ast).code;
 
-    it("prints parens for nullable union/intersection types", function() {
-        var code = "type MyType = ?(string | number);";
+      var openParenIndex = reprinted.indexOf("(");
+      assert.notStrictEqual(openParenIndex, -1);
 
-        var ast = b.typeAlias(
-            b.identifier("MyType"),
-            null,
-            b.nullableTypeAnnotation(
-                b.unionTypeAnnotation(
-                    [b.stringTypeAnnotation(), b.numberTypeAnnotation()]
-                )
-            )
-        );
+      var closeParenIndex = reprinted.indexOf(")", openParenIndex);
+      assert.notStrictEqual(closeParenIndex, -1);
 
-        var printer = new Printer({});
-        var pretty = printer.printGenerically(ast).code;
-        assert.strictEqual(pretty, code);
-    });
+      var newHead = reprinted.slice(
+        openParenIndex + 1,
+        closeParenIndex
+      );
 
-    it("uses the `arrayBracketSpacing` and the `objectCurlySpacing` option", function() {
-      var babylon = require("babylon");
-      var parseOptions = {
-        parser: {
-          parse: function (source) {
-            return babylon.parse(source, {
-              sourceType: 'module',
-              plugins: ['flow'],
-            });
-          }
-        }
-      };
-
-      var testCaseList = [{
-        printerConfig: {arrayBracketSpacing: false, objectCurlySpacing: false},
-        code: [
-          'import {java, script} from "javascript";',
-          '',
-          'function foo(a) {',
-          '    type MyType = {message: string};',
-          '    return [1, 2, 3];',
-          '}',
-          '',
-          'export {foo};'
-        ].join(eol)
-      }, {
-        printerConfig: {arrayBracketSpacing: true, objectCurlySpacing: false},
-        code: [
-          'import {java, script} from "javascript";',
-          '',
-          'function foo(a) {',
-          '    type MyType = {message: string};',
-          '    return [ 1, 2, 3 ];',
-          '}',
-          '',
-          'export {foo};'
-        ].join(eol)
-      }, {
-        printerConfig: {arrayBracketSpacing: false, objectCurlySpacing: true},
-        code: [
-          'import { java, script } from "javascript";',
-          '',
-          'function foo(a) {',
-          '    type MyType = { message: string };',
-          '    return [1, 2, 3];',
-          '}',
-          '',
-          'export { foo };'
-        ].join(eol)
-      }, {
-        printerConfig: {arrayBracketSpacing: true, objectCurlySpacing: true},
-        code: [
-          'import { java, script } from "javascript";',
-          '',
-          'function foo(a) {',
-          '    type MyType = { message: string };',
-          '    return [ 1, 2, 3 ];',
-          '}',
-          '',
-          'export { foo };'
-        ].join(eol)
-      }];
-
-      testCaseList.forEach(function(testCase) {
-        var code = testCase.code;
-        var printer = new Printer(testCase.printerConfig);
-
-        var ast = parse(code, parseOptions);
-        var pretty = printer.printGenerically(ast).code;
-
-        assert.strictEqual(pretty, code);
-      });
-    });
+      assert.strictEqual(newHead.split(";").length, 3);
+    }
+
+    function checkWith(parser) {
+      check("let i = 0; i < 1; i++", parser);
+      check("let i = 0 ; i < 1; i++", parser);
+      check("let i = 0; ; i++", parser);
+      check("let i = 0 ; ; i++", parser);
+      check("let i = 0; i < 1; ", parser);
+      check("let i = 0 ; i < 1; ", parser);
+      check("let i = 0; ; ", parser);
+      check("let i = 0 ; ; ", parser);
+    }
+
+    checkWith(require("esprima"));
+    checkWith(require("reify/lib/parsers/acorn.js"));
+    checkWith(require("reify/lib/parsers/babylon.js"));
+  });
 });

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-recast.git



More information about the Pkg-javascript-commits mailing list