[Pkg-javascript-commits] [node-recast] 01/03: Import node-recast_0.10.39.orig.tar.gz

Julien Puydt julien.puydt at laposte.net
Sat Jan 2 08:02:59 UTC 2016


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 f56058009a2dde6135027a8808da55c916f579fc
Author: Julien Puydt <julien.puydt at laposte.net>
Date:   Fri Jan 1 20:01:40 2016 +0100

    Import node-recast_0.10.39.orig.tar.gz
---
 .travis.yml         |   1 +
 README.md           |   5 +-
 lib/comments.js     |  14 +--
 lib/lines.js        |  28 +++++-
 lib/options.js      |   8 +-
 lib/parser.js       |  25 +++--
 lib/patcher.js      |  53 ++++++++--
 lib/printer.js      |  43 ++++++--
 lib/util.js         | 282 ++++++++++++++++++++++++++++++++--------------------
 package.json        |   6 +-
 test/comments.js    |  63 ++++++------
 test/es6tests.js    |   5 +-
 test/lines.js       |  85 ++++++++--------
 test/mapping.js     |   9 +-
 test/parens.js      |  13 +--
 test/parser.js      | 279 ++++++++++++++++++++++++++++-----------------------
 test/patcher.js     |  19 ++--
 test/printer.js     | 197 +++++++++++++++++++++++++++---------
 test/type-syntax.js |  15 +--
 test/visit.js       |   9 +-
 20 files changed, 730 insertions(+), 429 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 80d1fad..695586b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,6 @@
 language: node_js
 node_js:
+  - "4.0"
   - "iojs"
   - "0.12"
   - "0.11"
diff --git a/README.md b/README.md
index a98ce06..d4d7c19 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
-# recast, _v_. [![Build Status](https://travis-ci.org/benjamn/recast.png?branch=master)](https://travis-ci.org/benjamn/recast)
+# recast, _v_. [![Build Status](https://travis-ci.org/benjamn/recast.png?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)
+
 1. to give (a metal object) a different form by melting it down and reshaping it.
 1. to form, fashion, or arrange again.
 1. to remodel or reconstruct (a literary work, document, sentence, etc.).
@@ -83,7 +84,7 @@ The magic of Recast is that it reprints only those parts of the syntax tree that
 ```js
 recast.print(recast.parse(source)).code === source
 ```
-Whenever Recast cannot reprint a modified node using the orginal source code, it falls back to using a generic pretty printer. So the worst that can happen is that your changes trigger some harmless reformatting of your code.
+Whenever Recast cannot reprint a modified node using the original source code, it falls back to using a generic pretty printer. So the worst that can happen is that your changes trigger some harmless reformatting of your code.
 
 If you really don't care about preserving the original formatting, you can access the pretty printer directly:
 ```js
diff --git a/lib/comments.js b/lib/comments.js
index df1d383..4247b6a 100644
--- a/lib/comments.js
+++ b/lib/comments.js
@@ -13,7 +13,7 @@ var childNodesCacheKey = require("private").makeUniqueKey();
 
 // TODO Move a non-caching implementation of this function into ast-types,
 // and implement a caching wrapper function here.
-function getSortedChildNodes(node, resultArray) {
+function getSortedChildNodes(node, lines, resultArray) {
     if (!node) {
         return;
     }
@@ -22,7 +22,7 @@ function getSortedChildNodes(node, resultArray) {
     // are fixed by this utility function. Specifically, if it decides to
     // set node.loc to null, indicating that the node's .loc information
     // is unreliable, then we don't want to add node to the resultArray.
-    util.fixFaultyLocations(node);
+    util.fixFaultyLocations(node, lines);
 
     if (resultArray) {
         if (n.Node.check(node) &&
@@ -60,7 +60,7 @@ function getSortedChildNodes(node, resultArray) {
     }
 
     for (var i = 0, nameCount = names.length; i < nameCount; ++i) {
-        getSortedChildNodes(node[names[i]], resultArray);
+        getSortedChildNodes(node[names[i]], lines, resultArray);
     }
 
     return resultArray;
@@ -69,8 +69,8 @@ function getSortedChildNodes(node, resultArray) {
 // As efficiently as possible, decorate the comment object with
 // .precedingNode, .enclosingNode, and/or .followingNode properties, at
 // least one of which is guaranteed to be defined.
-function decorateComment(node, comment) {
-    var childNodes = getSortedChildNodes(node);
+function decorateComment(node, comment, lines) {
+    var childNodes = getSortedChildNodes(node, lines);
 
     // Time to dust off the old binary search robes and wizard hat.
     var left = 0, right = childNodes.length;
@@ -81,7 +81,7 @@ function decorateComment(node, comment) {
         if (comparePos(child.loc.start, comment.loc.start) <= 0 &&
             comparePos(comment.loc.end, child.loc.end) <= 0) {
             // The comment is completely contained by this child node.
-            decorateComment(comment.enclosingNode = child, comment);
+            decorateComment(comment.enclosingNode = child, comment, lines);
             return; // Abandon the binary search at this level.
         }
 
@@ -126,7 +126,7 @@ exports.attach = function(comments, ast, lines) {
 
     comments.forEach(function(comment) {
         comment.loc.lines = lines;
-        decorateComment(ast, comment);
+        decorateComment(ast, comment, lines);
 
         var pn = comment.precedingNode;
         var en = comment.enclosingNode;
diff --git a/lib/lines.js b/lib/lines.js
index e084f8f..b83f8a9 100644
--- a/lib/lines.js
+++ b/lib/lines.js
@@ -71,6 +71,7 @@ function copyLineInfo(info) {
     return {
         line: info.line,
         indent: info.indent,
+        locked: info.locked,
         sliceStart: info.sliceStart,
         sliceEnd: info.sliceEnd
     };
@@ -134,6 +135,7 @@ function fromString(string, options) {
 
     var tabWidth = options && options.tabWidth;
     var tabless = string.indexOf("\t") < 0;
+    var locked = !! (options && options.locked);
     var cacheable = !options && tabless && (string.length <= maxCacheKeyLen);
 
     assert.ok(tabWidth || tabless, "No tab width specified but encountered tabs in string\n" + string);
@@ -146,6 +148,8 @@ function fromString(string, options) {
         return {
             line: line,
             indent: countSpaces(spaces, tabWidth),
+            // Boolean indicating whether this line can be reindented.
+            locked: locked,
             sliceStart: spaces.length,
             sliceEnd: line.length
         };
@@ -336,7 +340,7 @@ Lp.indent = function(by) {
     var secret = getSecret(this);
 
     var lines = new Lines(secret.infos.map(function(info) {
-        if (info.line) {
+        if (info.line && ! info.locked) {
             info = copyLineInfo(info);
             info.indent += by;
         }
@@ -364,7 +368,7 @@ Lp.indentTail = function(by) {
     var secret = getSecret(this);
 
     var lines = new Lines(secret.infos.map(function(info, i) {
-        if (i > 0 && info.line) {
+        if (i > 0 && info.line && ! info.locked) {
             info = copyLineInfo(info);
             info.indent += by;
         }
@@ -383,6 +387,20 @@ Lp.indentTail = function(by) {
     return lines;
 };
 
+Lp.lockIndentTail = function () {
+    if (this.length < 2) {
+        return this;
+    }
+
+    var infos = getSecret(this).infos;
+
+    return new Lines(infos.map(function (info, i) {
+        info = copyLineInfo(info);
+        info.locked = i > 0;
+        return info;
+    }));
+};
+
 Lp.getIndentAt = function(line) {
     assert.ok(line >= 1, "no line " + line + " (line numbers start from 1)");
     var secret = getSecret(this),
@@ -688,6 +706,8 @@ function sliceInfo(info, startCol, endCol) {
     return {
         line: info.line,
         indent: indent,
+        // A destructive slice always unlocks indentation.
+        locked: false,
         sliceStart: sliceStart,
         sliceEnd: sliceEnd
     };
@@ -792,6 +812,10 @@ Lp.join = function(elements) {
                 0, prevInfo.sliceEnd) + indent + info.line.slice(
                     info.sliceStart, info.sliceEnd);
 
+            // If any part of a line is indentation-locked, the whole line
+            // will be indentation-locked.
+            prevInfo.locked = prevInfo.locked || info.locked;
+
             prevInfo.sliceEnd = prevInfo.line.length;
 
             if (secret.mappings.length > 0) {
diff --git a/lib/options.js b/lib/options.js
index c8eb04d..29ccccb 100644
--- a/lib/options.js
+++ b/lib/options.js
@@ -1,8 +1,8 @@
 var defaults = {
     // If you want to use a different branch of esprima, or any other
     // module that supports a .parse function, pass that module object to
-    // recast.parse as options.esprima.
-    esprima: require("esprima-fb"),
+    // recast.parse as options.parser (legacy synonym: options.esprima).
+    parser: require("esprima-fb"),
 
     // Number of spaces the pretty-printer should use per tab for
     // indentation. If you do not pass this option explicitly, it will be
@@ -57,7 +57,7 @@ var defaults = {
     // If you want to override the quotes used in string literals, specify
     // either "single", "double", or "auto" here ("auto" will select the one
     // which results in the shorter literal)
-    // Otherwise, the input marks will be preserved
+    // Otherwise, double quotes are used.
     quote: null,
 
     // If you want to print trailing commas in object literals,
@@ -86,7 +86,7 @@ exports.normalize = function(options) {
         sourceMapName: get("sourceMapName"),
         sourceRoot: get("sourceRoot"),
         inputSourceMap: get("inputSourceMap"),
-        esprima: get("esprima"),
+        parser: get("esprima") || get("parser"),
         range: get("range"),
         tolerant: get("tolerant"),
         quote: get("quote"),
diff --git a/lib/parser.js b/lib/parser.js
index ce40743..de589bd 100644
--- a/lib/parser.js
+++ b/lib/parser.js
@@ -23,7 +23,7 @@ exports.parse = function parse(source, options) {
     });
 
     var comments = [];
-    var program = options.esprima.parse(sourceWithoutTabs, {
+    var program = options.parser.parse(sourceWithoutTabs, {
         loc: true,
         locations: true,
         range: options.range,
@@ -34,6 +34,10 @@ exports.parse = function parse(source, options) {
         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()
@@ -57,13 +61,16 @@ exports.parse = function parse(source, options) {
 
     // In order to ensure we reprint leading and trailing program
     // comments, wrap the original Program node with a File node.
-    var file = b.file(program);
-    file.loc = {
-        lines: lines,
-        indent: 0,
-        start: lines.firstPos(),
-        end: lines.lastPos()
-    };
+    var file = program;
+    if (file.type === "Program") {
+        var file = b.file(program);
+        file.loc = {
+            lines: lines,
+            indent: 0,
+            start: lines.firstPos(),
+            end: lines.lastPos()
+        };
+    }
 
     // Passing file.program here instead of just file means that initial
     // comments will be attached to program.body[0] instead of program.
@@ -95,7 +102,7 @@ TCp.copy = function(node) {
         return node;
     }
 
-    util.fixFaultyLocations(node);
+    util.fixFaultyLocations(node, this.lines);
 
     var copy = Object.create(Object.getPrototypeOf(node), {
         original: { // Provide a link from the copy to the original.
diff --git a/lib/patcher.js b/lib/patcher.js
index b9eb986..ac21eff 100644
--- a/lib/patcher.js
+++ b/lib/patcher.js
@@ -188,28 +188,24 @@ exports.getReprinter = function(path) {
                 patcher.deleteComments(oldNode);
             }
 
-            var pos = util.copyPos(oldNode.loc.start);
-            var needsLeadingSpace = lines.prevPos(pos) &&
-                riskyAdjoiningCharExp.test(lines.charAt(pos));
-
             var newLines = print(
                 reprint.newPath,
                 needToPrintNewPathWithComments
             ).indentTail(oldNode.loc.indent);
 
-            var needsTrailingSpace =
-                riskyAdjoiningCharExp.test(lines.charAt(oldNode.loc.end));
+            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 (needsLeadingSpace || needsTrailingSpace) {
+            if (nls || nts) {
                 var newParts = [];
-                needsLeadingSpace && newParts.push(" ");
+                nls && newParts.push(" ");
                 newParts.push(newLines);
-                needsTrailingSpace && newParts.push(" ");
+                nts && newParts.push(" ");
                 newLines = linesModule.concat(newParts);
             }
 
@@ -222,6 +218,45 @@ exports.getReprinter = function(path) {
     };
 };
 
+// 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);
+
+    // 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());
+
+    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);
+
+    var newLastPos = newLines.lastPos();
+
+    // Last character of the reprinted node.
+    var newLastChar = newLines.prevPos(newLastPos) &&
+        newLines.charAt(newLastPos);
+
+    return newLastChar &&
+        riskyAdjoiningCharExp.test(newLastChar) &&
+        charAfterOldLoc &&
+        riskyAdjoiningCharExp.test(charAfterOldLoc);
+}
+
 function findReprints(newPath, reprints) {
     var newNode = newPath.getValue();
     Printable.assert(newNode);
diff --git a/lib/printer.js b/lib/printer.js
index 53ea319..5adc810 100644
--- a/lib/printer.js
+++ b/lib/printer.js
@@ -83,15 +83,26 @@ function Printer(originalOptions) {
 
     function maybeReprint(path) {
         var reprinter = getReprinter(path);
-        if (reprinter)
+        if (reprinter) {
+            // Since the print function that we pass to the reprinter will
+            // be used to print "new" nodes, it's tempting to think we
+            // should pass printRootGenerically instead of print, to avoid
+            // calling maybeReprint again, but that would be a mistake
+            // because the new nodes might not be entirely new, but merely
+            // moved from elsewhere in the AST. The print function is the
+            // right choice because it gives us the opportunity to reprint
+            // such nodes using their original source.
             return maybeAddParens(path, reprinter(print));
+        }
         return printRootGenerically(path);
     }
 
     // Print the root node generically, but then resume reprinting its
     // children non-generically.
-    function printRootGenerically(path) {
-        return genericPrint(path, options, printWithComments);
+    function printRootGenerically(path, includeComments) {
+        return includeComments
+            ? printComments(path, printRootGenerically)
+            : genericPrint(path, options, printWithComments);
     }
 
     // Print the entire AST generically.
@@ -282,8 +293,7 @@ function genericPrintNoParens(path, options, print) {
         if (
             n.params.length === 1 &&
             !n.rest &&
-            n.params[0].type !== 'SpreadElementPattern' &&
-            n.params[0].type !== 'RestElement'
+            n.params[0].type === 'Identifier'
         ) {
             parts.push(path.call(print, "params", 0));
         } else {
@@ -1136,7 +1146,15 @@ function genericPrintNoParens(path, options, print) {
 
     case "ClassDeclaration":
     case "ClassExpression":
-        var parts = ["class"];
+        var parts = [];
+
+        if (n.decorators) {
+            path.each(function(decoratorPath) {
+                parts.push(print(decoratorPath), "\n");
+            }, "decorators");
+        }
+
+        parts.push("class");
 
         if (n.id) {
             parts.push(
@@ -1166,7 +1184,7 @@ function genericPrintNoParens(path, options, print) {
         return concat(parts);
 
     case "TemplateElement":
-        return fromString(n.value.raw, options);
+        return fromString(n.value.raw, options).lockIndentTail();
 
     case "TemplateLiteral":
         var expressions = path.map(print, "expressions");
@@ -1182,7 +1200,7 @@ function genericPrintNoParens(path, options, print) {
 
         parts.push("`");
 
-        return concat(parts);
+        return concat(parts).lockIndentTail();
 
     case "TaggedTemplateExpression":
         return concat([
@@ -1513,9 +1531,14 @@ function printStatementSequence(path, options, print) {
             // a Statement when the Comment can't be attached to any other
             // non-Comment node in the tree.
             sawComment = true;
-        } else if (!inClassBody) {
-            namedTypes.Statement.assert(stmt);
+        } else if (namedTypes.Statement.check(stmt)) {
             sawStatement = true;
+        } else {
+            // When the pretty printer encounters a string instead of an
+            // AST node, it just prints the string. This behavior can be
+            // useful for fine-grained formatting decisions like inserting
+            // blank lines.
+            isString.assert(stmt);
         }
 
         // We can't hang onto stmtPath outside of this function, because
diff --git a/lib/util.js b/lib/util.js
index 6af3980..5497f86 100644
--- a/lib/util.js
+++ b/lib/util.js
@@ -8,143 +8,213 @@ var SourceMapGenerator = sourceMap.SourceMapGenerator;
 var hasOwn = Object.prototype.hasOwnProperty;
 
 function getUnionOfKeys() {
-    var result = {};
-    var argc = arguments.length;
-    for (var i = 0; i < argc; ++i) {
-        var keys = Object.keys(arguments[i]);
-        var keyCount = keys.length;
-        for (var j = 0; j < keyCount; ++j) {
-            result[keys[j]] = true;
-        }
+  var result = {};
+  var argc = arguments.length;
+  for (var i = 0; i < argc; ++i) {
+    var keys = Object.keys(arguments[i]);
+    var keyCount = keys.length;
+    for (var j = 0; j < keyCount; ++j) {
+      result[keys[j]] = true;
     }
-    return result;
+  }
+  return result;
 }
 exports.getUnionOfKeys = getUnionOfKeys;
 
 function comparePos(pos1, pos2) {
-    return (pos1.line - pos2.line) || (pos1.column - pos2.column);
+  return (pos1.line - pos2.line) || (pos1.column - pos2.column);
 }
 exports.comparePos = comparePos;
 
 function copyPos(pos) {
-    return {
-        line: pos.line,
-        column: pos.column
-    };
+  return {
+    line: pos.line,
+    column: pos.column
+  };
 }
 exports.copyPos = copyPos;
 
 exports.composeSourceMaps = function(formerMap, latterMap) {
-    if (formerMap) {
-        if (!latterMap) {
-            return formerMap;
-        }
-    } else {
-        return latterMap || null;
+  if (formerMap) {
+    if (!latterMap) {
+      return formerMap;
+    }
+  } else {
+    return latterMap || null;
+  }
+
+  var smcFormer = new SourceMapConsumer(formerMap);
+  var smcLatter = new SourceMapConsumer(latterMap);
+  var smg = new SourceMapGenerator({
+    file: latterMap.file,
+    sourceRoot: latterMap.sourceRoot
+  });
+
+  var sourcesToContents = {};
+
+  smcLatter.eachMapping(function(mapping) {
+    var origPos = smcFormer.originalPositionFor({
+      line: mapping.originalLine,
+      column: mapping.originalColumn
+    });
+
+    var sourceName = origPos.source;
+    if (sourceName === null) {
+      return;
     }
 
-    var smcFormer = new SourceMapConsumer(formerMap);
-    var smcLatter = new SourceMapConsumer(latterMap);
-    var smg = new SourceMapGenerator({
-        file: latterMap.file,
-        sourceRoot: latterMap.sourceRoot
+    smg.addMapping({
+      source: sourceName,
+      original: copyPos(origPos),
+      generated: {
+        line: mapping.generatedLine,
+        column: mapping.generatedColumn
+      },
+      name: mapping.name
     });
 
-    var sourcesToContents = {};
+    var sourceContent = smcFormer.sourceContentFor(sourceName);
+    if (sourceContent && !hasOwn.call(sourcesToContents, sourceName)) {
+      sourcesToContents[sourceName] = sourceContent;
+      smg.setSourceContent(sourceName, sourceContent);
+    }
+  });
 
-    smcLatter.eachMapping(function(mapping) {
-        var origPos = smcFormer.originalPositionFor({
-            line: mapping.originalLine,
-            column: mapping.originalColumn
-        });
+  return smg.toJSON();
+};
 
-        var sourceName = origPos.source;
-        if (sourceName === null) {
-            return;
+exports.getTrueLoc = function(node, lines) {
+  // It's possible that node is newly-created (not parsed by Esprima),
+  // in which case it probably won't have a .loc property (or an
+  // .original property for that matter). That's fine; we'll just
+  // pretty-print it as usual.
+  if (!node.loc) {
+    return null;
+  }
+
+  var start = node.loc.start;
+  var end = node.loc.end;
+
+  // If the node has any comments, their locations might contribute to
+  // the true start/end positions of the node.
+  if (node.comments) {
+    node.comments.forEach(function(comment) {
+      if (comment.loc) {
+        if (comparePos(comment.loc.start, start) < 0) {
+          start = comment.loc.start;
         }
 
-        smg.addMapping({
-            source: sourceName,
-            original: copyPos(origPos),
-            generated: {
-                line: mapping.generatedLine,
-                column: mapping.generatedColumn
-            },
-            name: mapping.name
-        });
-
-        var sourceContent = smcFormer.sourceContentFor(sourceName);
-        if (sourceContent && !hasOwn.call(sourcesToContents, sourceName)) {
-            sourcesToContents[sourceName] = sourceContent;
-            smg.setSourceContent(sourceName, sourceContent);
+        if (comparePos(end, comment.loc.end) < 0) {
+          end = comment.loc.end;
         }
+      }
     });
+  }
+
+  if (comparePos(start, end) < 0) {
+    // Trim leading whitespace.
+    start = copyPos(start);
+    lines.skipSpaces(start, false, true);
+
+    if (comparePos(start, end) < 0) {
+      // Trim trailing whitespace, if the end location is not already the
+      // same as the start location.
+      end = copyPos(end);
+      lines.skipSpaces(end, true, true);
+    }
+  }
 
-    return smg.toJSON();
+  return { start: start, end: end };
 };
 
-exports.getTrueLoc = function(node, lines) {
-    // It's possible that node is newly-created (not parsed by Esprima),
-    // in which case it probably won't have a .loc property (or an
-    // .original property for that matter). That's fine; we'll just
-    // pretty-print it as usual.
-    if (!node.loc) {
-        return null;
+exports.fixFaultyLocations = function(node, lines) {
+  var loc = node.loc;
+  if (loc) {
+    if (loc.start.line < 1) {
+      loc.start.line = 1;
     }
 
-    var start = node.loc.start;
-    var end = node.loc.end;
-
-    // If the node has any comments, their locations might contribute to
-    // the true start/end positions of the node.
-    if (node.comments) {
-        node.comments.forEach(function(comment) {
-            if (comment.loc) {
-                if (comparePos(comment.loc.start, start) < 0) {
-                    start = comment.loc.start;
-                }
-
-                if (comparePos(end, comment.loc.end) < 0) {
-                    end = comment.loc.end;
-                }
-            }
-        });
+    if (loc.end.line < 1) {
+      loc.end.line = 1;
     }
-
-    return {
-        // Finally, trim any leading or trailing whitespace from the true
-        // location of the node.
-        start: lines.skipSpaces(start, false, false),
-        end: lines.skipSpaces(end, true, false)
-    };
+  }
+
+  if (node.type === "TemplateLiteral") {
+    fixTemplateLiteral(node, lines);
+  } else if ((n.MethodDefinition && n.MethodDefinition.check(node)) ||
+             (n.Property.check(node) && (node.method || node.shorthand))) {
+    // If the node is a MethodDefinition or a .method or .shorthand
+    // Property, then the location information stored in
+    // node.value.loc is very likely untrustworthy (just the {body}
+    // part of a method, or nothing in the case of shorthand
+    // properties), so we null out that information to prevent
+    // accidental reuse of bogus source code during reprinting.
+    node.value.loc = null;
+
+    if (n.FunctionExpression.check(node.value)) {
+      // FunctionExpression method values should be anonymous,
+      // because their .id fields are ignored anyway.
+      node.value.id = null;
+    }
+  }
 };
 
-exports.fixFaultyLocations = function(node) {
-    if ((n.MethodDefinition && n.MethodDefinition.check(node)) ||
-        (n.Property.check(node) && (node.method || node.shorthand))) {
-        // If the node is a MethodDefinition or a .method or .shorthand
-        // Property, then the location information stored in
-        // node.value.loc is very likely untrustworthy (just the {body}
-        // part of a method, or nothing in the case of shorthand
-        // properties), so we null out that information to prevent
-        // accidental reuse of bogus source code during reprinting.
-        node.value.loc = null;
-
-        if (n.FunctionExpression.check(node.value)) {
-            // FunctionExpression method values should be anonymous,
-            // because their .id fields are ignored anyway.
-            node.value.id = null;
-        }
+function fixTemplateLiteral(node, lines) {
+  assert.strictEqual(node.type, "TemplateLiteral");
+
+  if (node.quasis.length === 0) {
+    // If there are no quasi elements, then there is nothing to fix.
+    return;
+  }
+
+  // First we need to exclude the opening ` from the .loc of the first
+  // quasi element, in case the parser accidentally decided to include it.
+  var afterLeftBackTickPos = copyPos(node.loc.start);
+  assert.strictEqual(lines.charAt(afterLeftBackTickPos), "`");
+  assert.ok(lines.nextPos(afterLeftBackTickPos));
+  var firstQuasi = node.quasis[0];
+  if (comparePos(firstQuasi.loc.start, afterLeftBackTickPos) < 0) {
+    firstQuasi.loc.start = afterLeftBackTickPos;
+  }
+
+  // Next we need to exclude the closing ` from the .loc of the last quasi
+  // element, in case the parser accidentally decided to include it.
+  var rightBackTickPos = copyPos(node.loc.end);
+  assert.ok(lines.prevPos(rightBackTickPos));
+  assert.strictEqual(lines.charAt(rightBackTickPos), "`");
+  var lastQuasi = node.quasis[node.quasis.length - 1];
+  if (comparePos(rightBackTickPos, lastQuasi.loc.end) < 0) {
+    lastQuasi.loc.end = rightBackTickPos;
+  }
+
+  // Now we need to exclude ${ and } characters from the .loc's of all
+  // quasi elements, since some parsers accidentally include them.
+  node.expressions.forEach(function (expr, i) {
+    // Rewind from expr.loc.start over any whitespace and the ${ that
+    // precedes the expression. The position of the $ should be the same
+    // as the .loc.end of the preceding quasi element, but some parsers
+    // accidentally include the ${ in the .loc of the quasi element.
+    var dollarCurlyPos = lines.skipSpaces(expr.loc.start, true, false);
+    if (lines.prevPos(dollarCurlyPos) &&
+        lines.charAt(dollarCurlyPos) === "{" &&
+        lines.prevPos(dollarCurlyPos) &&
+        lines.charAt(dollarCurlyPos) === "$") {
+      var quasiBefore = node.quasis[i];
+      if (comparePos(dollarCurlyPos, quasiBefore.loc.end) < 0) {
+        quasiBefore.loc.end = dollarCurlyPos;
+      }
     }
 
-    var loc = node.loc;
-    if (loc) {
-        if (loc.start.line < 1) {
-            loc.start.line = 1;
-        }
-
-        if (loc.end.line < 1) {
-            loc.end.line = 1;
-        }
+    // Likewise, some parsers accidentally include the } that follows
+    // the expression in the .loc of the following quasi element.
+    var rightCurlyPos = lines.skipSpaces(expr.loc.end, false, false);
+    if (lines.charAt(rightCurlyPos) === "}") {
+      assert.ok(lines.nextPos(rightCurlyPos));
+      // Now rightCurlyPos is technically the position just after the }.
+      var quasiAfter = node.quasis[i + 1];
+      if (comparePos(quasiAfter.loc.start, rightCurlyPos) < 0) {
+        quasiAfter.loc.start = rightCurlyPos;
+      }
     }
-};
+  });
+}
diff --git a/package.json b/package.json
index 7b37e88..15c07ba 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
     "parsing",
     "pretty-printing"
   ],
-  "version": "0.10.32",
+  "version": "0.10.39",
   "homepage": "http://github.com/benjamn/recast",
   "repository": {
     "type": "git",
@@ -26,9 +26,9 @@
   },
   "dependencies": {
     "esprima-fb": "~15001.1001.0-dev-harmony-fb",
-    "source-map": "~0.4.4",
+    "source-map": "~0.5.0",
     "private": "~0.1.5",
-    "ast-types": "0.8.11"
+    "ast-types": "0.8.12"
   },
   "devDependencies": {
     "mocha": "~2.2.5"
diff --git a/test/comments.js b/test/comments.js
index e7e2272..cd4051c 100644
--- a/test/comments.js
+++ b/test/comments.js
@@ -5,6 +5,7 @@ var Printer = require("../lib/printer").Printer;
 var fromString = require("../lib/lines").fromString;
 var assert = require("assert");
 var printer = new Printer;
+var eol = require("os").EOL;
 
 var annotated = [
     "function dup(/* string */ s,",
@@ -18,7 +19,7 @@ var annotated = [
 
 describe("comments", function() {
     it("attachment and reprinting", function() {
-        var code = annotated.join("\n");
+        var code = annotated.join(eol);
         var ast = recast.parse(code);
 
         var dup = ast.program.body[0];
@@ -42,13 +43,13 @@ describe("comments", function() {
 
         assert.strictEqual(
             recast.print(dup.body).code,
-            ["/* string */"].concat(annotated.slice(2)).join("\n")
+            ["/* string */"].concat(annotated.slice(2)).join(eol)
         );
 
         var retStmt = dup.body.body[0];
         n.ReturnStatement.assert(retStmt);
 
-        var indented = annotated.slice(3, 6).join("\n");
+        var indented = annotated.slice(3, 6).join(eol);
         var flush = fromString(indented).indent(-2);
 
         assert.strictEqual(
@@ -65,7 +66,7 @@ describe("comments", function() {
         assert.strictEqual(recast.print(one).code, [
             "/*",
             " * off-by-*/ 1"
-        ].join("\n"));
+        ].join(eol));
     });
 
     var trailing = [
@@ -116,7 +117,7 @@ describe("comments", function() {
     ];
 
     it("TrailingComments", function() {
-        var code = trailing.join("\n");
+        var code = trailing.join(eol);
         var ast = recast.parse(code);
         assert.strictEqual(recast.print(ast).code, code);
 
@@ -149,7 +150,7 @@ describe("comments", function() {
         ));
 
         var actual = recast.print(ast, { tabWidth: 2 }).code;
-        var expected = trailingExpected.join("\n");
+        var expected = trailingExpected.join(eol);
 
         // Check semantic equivalence:
         recast.types.astNodesAreEquivalent.assert(
@@ -175,7 +176,7 @@ describe("comments", function() {
     ];
 
     it("BodyTrailingComments", function() {
-        var code = bodyTrailing.join("\n");
+        var code = bodyTrailing.join(eol);
         var ast = recast.parse(code);
 
         // Drop all original source information to force reprinting.
@@ -187,7 +188,7 @@ describe("comments", function() {
         });
 
         var actual = recast.print(ast, { tabWidth: 2 }).code;
-        var expected = bodyTrailingExpected.join("\n");
+        var expected = bodyTrailingExpected.join(eol);
 
         assert.strictEqual(actual, expected);
     });
@@ -205,7 +206,7 @@ describe("comments", function() {
     ];
 
     it("ParamTrailingComments", function() {
-        var code = paramTrailing.join("\n");
+        var code = paramTrailing.join(eol);
         var ast = recast.parse(code);
 
         var func = ast.program.body[0];
@@ -214,7 +215,7 @@ describe("comments", function() {
         func.params.unshift(b.identifier("zxcv"));
 
         var actual = recast.print(ast, { tabWidth: 2 }).code;
-        var expected = paramTrailingExpected.join("\n");
+        var expected = paramTrailingExpected.join(eol);
 
         assert.strictEqual(actual, expected);
     });
@@ -231,7 +232,7 @@ describe("comments", function() {
     ];
 
     it("ProtoAssignComment", function() {
-        var code = protoAssign.join("\n");
+        var code = protoAssign.join(eol);
         var ast = recast.parse(code);
 
         var foo = ast.program.body[0];
@@ -266,7 +267,7 @@ describe("comments", function() {
     ];
 
     it("should correctly attach to concise methods", function() {
-        var code = conciseMethods.join("\n");
+        var code = conciseMethods.join(eol);
         var ast = recast.parse(code);
 
         var objExpr = ast.program.body[0].declarations[0].init;
@@ -334,7 +335,7 @@ describe("comments", function() {
             "var obj = {",
             "};",
         ];
-        var code = simpleCommentedCode.join("\n");
+        var code = simpleCommentedCode.join(eol);
         var ast = recast.parse(code);
 
         // When
@@ -352,7 +353,7 @@ describe("comments", function() {
             "foo;",
             "// bar",
             "bar;"
-        ].join("\n");
+        ].join(eol);
 
         var ast = recast.parse(code);
 
@@ -367,7 +368,7 @@ describe("comments", function() {
             "foo;",
             "// barbara",
             "bar;"
-        ].join("\n"));
+        ].join(eol));
 
         ast.program.body[0].comments = comments;
         delete ast.program.body[1].comments;
@@ -375,7 +376,7 @@ describe("comments", function() {
             "// barbara",
             "foo;",
             "bar;"
-        ].join("\n"));
+        ].join(eol));
 
         ast.program.body[0] = b.blockStatement([
             ast.program.body[0]
@@ -387,7 +388,7 @@ describe("comments", function() {
             "}",
             "",
             "bar;"
-        ].join("\n"));
+        ].join(eol));
 
         var comment = ast.program.body[0].body[0].comments[0];
         comment.type = "Block";
@@ -398,7 +399,7 @@ describe("comments", function() {
             "}",
             "",
             "bar;"
-        ].join("\n"));
+        ].join(eol));
 
         comment.value += "\n * babar\n ";
         assert.strictEqual(recast.print(ast).code, [
@@ -410,7 +411,7 @@ describe("comments", function() {
             "}",
             "",
             "bar;"
-        ].join("\n"));
+        ].join(eol));
 
         ast.program.body[1].comments = [comment];
         assert.strictEqual(recast.print(ast).code, [
@@ -425,7 +426,7 @@ describe("comments", function() {
             " * babar",
             " */",
             "bar;"
-        ].join("\n"));
+        ].join(eol));
 
         delete ast.program.body[0].body[0].comments;
         ast.program.comments = [b.line(" program comment")];
@@ -439,7 +440,7 @@ describe("comments", function() {
             " * babar",
             " */",
             "bar;"
-        ].join("\n"));
+        ].join(eol));
 
         ast.program.body.push(
             ast.program.body.shift()
@@ -454,7 +455,7 @@ describe("comments", function() {
             "{",
             "  foo;",
             "}"
-        ].join("\n"));
+        ].join(eol));
 
         recast.visit(ast, {
             visitNode: function(path) {
@@ -468,13 +469,13 @@ describe("comments", function() {
             "{",
             "  foo;",
             "}"
-        ].join("\n"));
+        ].join(eol));
 
         ast.program.body[1] = ast.program.body[1].body[0];
         assert.strictEqual(recast.print(ast).code, [
             "bar;",
             "foo;"
-        ].join("\n"));
+        ].join(eol));
     });
 
     it("should preserve stray non-comment syntax", function() {
@@ -486,7 +487,7 @@ describe("comments", function() {
             "  , /* comma */",
             "  bar",
             "]"
-        ].join("\n");
+        ].join(eol);
 
         var ast = recast.parse(code);
         assert.strictEqual(recast.print(ast).code, code);
@@ -502,13 +503,13 @@ describe("comments", function() {
             "  , /* comma */",
             "  bar",
             "]"
-        ].join("\n"));
+        ].join(eol));
     });
 
     it("should be reprinted even if dangling", function() {
         var code = [
             "[/*dangling*/] // array literal"
-        ].join("\n");
+        ].join(eol);
 
         var ast = recast.parse(code);
         var array = ast.program.body[0].expression;
@@ -524,12 +525,12 @@ describe("comments", function() {
         danglingComment.value = " neither leading nor trailing ";
         assert.strictEqual(recast.print(ast).code, [
             "[/* neither leading nor trailing */] // array literal"
-        ].join("\n"));
+        ].join(eol));
 
         trailingComment.value = " trailing";
         assert.strictEqual(recast.print(ast).code, [
             "[/* neither leading nor trailing */] // trailing"
-        ].join("\n"));
+        ].join(eol));
 
         // Unfortuantely altering the elements of the array leads to
         // reprinting which blows away the dangling comment.
@@ -586,7 +587,7 @@ describe("comments", function() {
             "  ) /*after*/ {",
             "  },",
             "};",
-        ].join('\n');
+        ].join(eol);
 
         var ast = recast.parse(code);
         var printer = new Printer({
@@ -604,7 +605,7 @@ describe("comments", function() {
             "var sum = function /*anonymous*/(/*...args*/) /*int*/ {",
             "  // TODO",
             "};"
-        ].join("\n");
+        ].join(eol);
 
         var ast = recast.parse(code);
         var funExp = ast.program.body[0].declarations[0].init;
diff --git a/test/es6tests.js b/test/es6tests.js
index 12e2614..4203154 100644
--- a/test/es6tests.js
+++ b/test/es6tests.js
@@ -4,6 +4,7 @@ var Printer = require("../lib/printer").Printer;
 var types = require("../lib/types");
 var n = types.namedTypes;
 var b = types.builders;
+var eol = require("os").EOL;
 
 describe("ES6 Compatability", function() {
     function convertShorthandMethod() {
@@ -15,7 +16,7 @@ describe("ES6 Compatability", function() {
             "  name,",
             "  func() { return 'value'; }",
             "};"
-        ].join("\n");
+        ].join(eol);
 
         var ast = parse(code);
         n.VariableDeclaration.assert(ast.program.body[1]);
@@ -280,7 +281,7 @@ describe("import/export syntax", function() {
             'var noun = "fool";',
             'var s = `I am a ${noun}`;',
             'var t = tag`You said: ${s}!`;'
-        ].join("\n");
+        ].join(eol);
 
         var ast = parse(code);
 
diff --git a/test/lines.js b/test/lines.js
index 3087b8d..da11afa 100644
--- a/test/lines.js
+++ b/test/lines.js
@@ -4,6 +4,7 @@ var path = require("path");
 var linesModule = require("../lib/lines");
 var fromString = linesModule.fromString;
 var concat = linesModule.concat;
+var eol = require("os").EOL;
 
 function check(a, b) {
     assert.strictEqual(a.toString(), b.toString());
@@ -42,7 +43,7 @@ describe("lines", function() {
 
         checkIsCached("");
         checkIsCached(",");
-        checkIsCached("\n");
+        checkIsCached(eol);
         checkIsCached("this");
         checkIsCached(", ");
         checkIsCached(": ");
@@ -90,6 +91,10 @@ describe("lines", function() {
         // lastPos) should be the only empty string.
         assert.strictEqual(emptyCount, 1);
 
+        // Function.prototype.toString uses \r\n line endings on non-*NIX
+        // systems, so normalize those to \n characters.
+        code = code.replace(/\r\n/g, "\n");
+
         var joined = chars.join("");
         assert.strictEqual(joined.length, code.length);
         assert.strictEqual(joined, code);
@@ -104,9 +109,7 @@ describe("lines", function() {
     }
 
     it("EachPos", function() {
-        // Function.prototype.toString uses \r\n line endings on non-*NIX
-        // systems, so normalize those to \n characters.
-        var code = (arguments.callee + "").replace(/\r\n/g, "\n");
+        var code = (arguments.callee + "");
         var lines = fromString(code);
 
         testEachPosHelper(lines, code);
@@ -139,9 +142,9 @@ describe("lines", function() {
         // out-of-bounds input positions.
         fromString(exports.testBasic).eachPos(compare);
 
-        var original = fromString("  ab\n  c"),
+        var original = fromString("  ab" + eol + "  c"),
             indented = original.indentTail(4),
-            reference = fromString("  ab\n      c");
+            reference = fromString("  ab" + eol + "      c");
 
         function compareIndented(pos) {
             var c = indented.charAt(pos);
@@ -160,12 +163,12 @@ describe("lines", function() {
 
     it("Concat", function() {
         var strings = ["asdf", "zcxv", "qwer"],
-            lines = fromString(strings.join("\n")),
+            lines = fromString(strings.join(eol)),
             indented = lines.indentTail(4);
 
         assert.strictEqual(lines.length, 3);
 
-        check(indented, strings.join("\n    "));
+        check(indented, strings.join(eol + "    "));
 
         assert.strictEqual(5, concat([lines, indented]).length);
         assert.strictEqual(5, concat([indented, lines]).length);
@@ -174,11 +177,11 @@ describe("lines", function() {
               lines.toString() + indented.toString());
 
         check(concat([lines, indented]).indentTail(4),
-              strings.join("\n    ") +
-              strings.join("\n        "));
+              strings.join(eol + "    ") +
+              strings.join(eol + "        "));
 
         check(concat([indented, lines]),
-              strings.join("\n    ") + lines.toString());
+              strings.join(eol + "    ") + lines.toString());
 
         check(concat([lines, indented]),
               lines.concat(indented));
@@ -227,13 +230,13 @@ describe("lines", function() {
         c("");
         c(" ");
         c("    ");
-        c(" \n");
-        c("\n ");
-        c(" \n ");
-        c("\n \n ");
-        c(" \n\n ");
-        c(" \n \n ");
-        c(" \n \n\n");
+        c(" " + eol);
+        c(eol + " ");
+        c(" " + eol + " ");
+        c(eol + " " + eol + " ");
+        c(" " + eol + eol + " ");
+        c(" " + eol + " " + eol + " ");
+        c(" " + eol + " " + eol + eol);
     });
 
     it("SingleLine", function() {
@@ -249,7 +252,7 @@ describe("lines", function() {
 
         // Multi-line Lines objects are altered by indentTail, but only if the
         // amount of the indentation is non-zero.
-        var twice = line.concat("\n", line);
+        var twice = line.concat(eol, line);
         assert.notStrictEqual(twice.indentTail(10), twice);
         assert.strictEqual(twice.indentTail(0), twice);
 
@@ -296,7 +299,7 @@ describe("lines", function() {
             var indented = lines.indentTail(indent),
                 loc = getSourceLocation(indented),
                 string = indented.toString(),
-                strings = string.split("\n"),
+                strings = string.split(eol),
                 lastLine = strings[strings.length - 1];
 
             assert.strictEqual(loc.end.line, strings.length);
@@ -312,7 +315,7 @@ describe("lines", function() {
     });
 
     it("Trim", function() {
-        var string = "  xxx \n ";
+        var string = "  xxx " + eol + " ";
         var options = { tabWidth: 4 };
         var lines = fromString(string);
 
@@ -325,21 +328,21 @@ describe("lines", function() {
 
         test("");
         test(" ");
-        test("  xxx \n ");
+        test("  xxx " + eol + " ");
         test("  xxx");
         test("xxx  ");
-        test("\nx\nx\nx\n");
-        test("\t\nx\nx\nx\n\t\n");
+        test(eol + "x" + eol + "x" + eol + "x" + eol);
+        test("\t" + eol + "x" + eol + "x" + eol + "x" + eol + "\t" + eol);
         test("xxx");
     });
 
     it("NoIndentEmptyLines", function() {
-        var lines = fromString("a\n\nb"),
+        var lines = fromString("a" + eol + eol + "b"),
             indented = lines.indent(4),
             tailIndented = lines.indentTail(5);
 
-        check(indented, fromString("    a\n\n    b"));
-        check(tailIndented, fromString("a\n\n     b"));
+        check(indented, fromString("    a" + eol + eol + "    b"));
+        check(tailIndented, fromString("a" + eol + eol + "     b"));
 
         check(indented.indent(-4), lines);
         check(tailIndented.indent(-5), lines);
@@ -400,7 +403,7 @@ describe("lines", function() {
             "function f() {",
             "\treturn this;",
             "}"
-        ].join("\n");
+        ].join(eol);
 
         function checkUnchanged(lines, code) {
             check(lines.toString(tabOpts), code);
@@ -416,31 +419,31 @@ describe("lines", function() {
             " function f() {",
             "\t return this;",
             " }"
-        ].join("\n"));
+        ].join(eol));
 
         check(lines.indent(tabWidth).toString(tabOpts), [
             "\tfunction f() {",
             "\t\treturn this;",
             "\t}"
-        ].join("\n"));
+        ].join(eol));
 
         check(lines.indent(1).toString(noTabOpts), [
             " function f() {",
             "     return this;",
             " }"
-        ].join("\n"));
+        ].join(eol));
 
         check(lines.indent(tabWidth).toString(noTabOpts), [
             "    function f() {",
             "        return this;",
             "    }"
-        ].join("\n"));
+        ].join(eol));
 
         var funkyCode = [
             " function g() { \t ",
             " \t\t  return this;  ",
             "\t} "
-        ].join("\n");
+        ].join(eol);
 
         var funky = fromString(funkyCode, tabOpts);
         checkUnchanged(funky, funkyCode);
@@ -449,25 +452,25 @@ describe("lines", function() {
             "  function g() { \t ",
             "\t\t   return this;  ",
             "\t } "
-        ].join("\n"));
+        ].join(eol));
 
         check(funky.indent(2).toString(tabOpts), [
             "   function g() { \t ",
             "\t\t\treturn this;  ",
             "\t  } "
-        ].join("\n"));
+        ].join(eol));
 
         check(funky.indent(1).toString(noTabOpts), [
             "  function g() { \t ",
             "           return this;  ",
             "     } "
-        ].join("\n"));
+        ].join(eol));
 
         check(funky.indent(2).toString(noTabOpts), [
             "   function g() { \t ",
             "            return this;  ",
             "      } "
-        ].join("\n"));
+        ].join(eol));
 
         // Test that '\v' characters are ignored for the purposes of indentation,
         // but preserved when printing untouched lines.
@@ -475,7 +478,7 @@ describe("lines", function() {
             "\vfunction f() {\v",
             " \v   return \vthis;\v",
             "\v} \v "
-        ].join("\n");
+        ].join(eol);
 
         lines = fromString(code, tabOpts);
 
@@ -485,13 +488,13 @@ describe("lines", function() {
             "    function f() {\v",
             "        return \vthis;\v",
             "    } \v "
-        ].join("\n"));
+        ].join(eol));
 
         check(lines.indent(5).toString(tabOpts), [
             "\t function f() {\v",
             "\t\t return \vthis;\v",
             "\t } \v "
-        ].join("\n"));
+        ].join(eol));
     });
 
     it("GuessTabWidth", function(done) {
@@ -502,7 +505,7 @@ describe("lines", function() {
             "function identity(x) {",
             "  return x;",
             "}"
-        ].join("\n"));
+        ].join(eol));
         assert.strictEqual(lines.guessTabWidth(), 2);
         assert.strictEqual(lines.indent(5).guessTabWidth(), 2);
         assert.strictEqual(lines.indent(-4).guessTabWidth(), 2);
diff --git a/test/mapping.js b/test/mapping.js
index a3a63c1..1d85de3 100644
--- a/test/mapping.js
+++ b/test/mapping.js
@@ -9,6 +9,7 @@ var fromString = require("../lib/lines").fromString;
 var parse = require("../lib/parser").parse;
 var Printer = require("../lib/printer").Printer;
 var Mapping = require("../lib/mapping");
+var eol = require("os").EOL;
 
 describe("source maps", function() {
     it("should generate correct mappings", function() {
@@ -16,7 +17,7 @@ describe("source maps", function() {
             "function foo(bar) {",
             "  return 1 + bar;",
             "}"
-        ].join("\n");
+        ].join(eol);
 
         var lines = fromString(code);
         var ast = parse(code, {
@@ -118,7 +119,7 @@ describe("source maps", function() {
             "  console.log(a, b);",
             "  return sum;",
             "}"
-        ].join("\n");
+        ].join(eol);
 
         var ast = parse(code, {
             sourceFileName: "original.js"
@@ -174,7 +175,7 @@ describe("source maps", function() {
         var code = [
             "for (var i = 0; false; i++)",
             "  log(i);"
-        ].join("\n");
+        ].join(eol);
         var ast = parse(code);
         var path = new NodePath(ast);
 
@@ -187,7 +188,7 @@ describe("source maps", function() {
         assert.strictEqual(printed.code, [
             "for (var i = 0; false; )",
             "  log(i);"
-        ].join("\n"));
+        ].join(eol));
     });
 
     it("should tolerate programs that become empty", function() {
diff --git a/test/parens.js b/test/parens.js
index 2c22722..d9764d3 100644
--- a/test/parens.js
+++ b/test/parens.js
@@ -7,6 +7,7 @@ var types = require("../lib/types");
 var n = types.namedTypes;
 var b = types.builders;
 var printer = new Printer;
+var eol = require("os").EOL;
 
 function parseExpression(expr) {
     var ast = esprima.parse(expr);
@@ -188,7 +189,7 @@ describe("parens", function() {
             "for (var i = 0; i < 10; ++i) {",
             "  console.log(i);",
             "}"
-        ].join("\n"))
+        ].join(eol))
 
         var loop = ast.program.body[0];
         var test = loop.test;
@@ -205,7 +206,7 @@ describe("parens", function() {
             "for (var i = 0; !(i < 10); ++i) {",
             "  console.log(i);",
             "}"
-        ].join("\n"));
+        ].join(eol));
     });
 
     it("MisleadingExistingParens", function() {
@@ -216,7 +217,7 @@ describe("parens", function() {
             'if (key === "oyez") {',
             "  throw new Error(key);",
             "}"
-        ].join("\n"));
+        ].join(eol));
 
         var ifStmt = ast.program.body[0];
         ifStmt.test = b.unaryExpression("!", ifStmt.test);
@@ -230,7 +231,7 @@ describe("parens", function() {
             'if (!(key === "oyez")) {',
             "  throw new Error(key);",
             "}"
-        ].join("\n"));
+        ].join(eol));
     });
 
     it("DiscretionaryParens", function() {
@@ -238,7 +239,7 @@ describe("parens", function() {
             "if (info.line && (i > 0 || !skipFirstLine)) {",
             "  info = copyLineInfo(info);",
             "}"
-        ].join("\n");
+        ].join(eol);
 
         var ast = parse(code);
 
@@ -258,7 +259,7 @@ describe("parens", function() {
             "    c",
             "  );",
             "}"
-        ].join("\n");
+        ].join(eol);
 
         var ast = parse(code);
         var printer = new Printer({
diff --git a/test/parser.js b/test/parser.js
index d1b2d75..d729bbf 100644
--- a/test/parser.js
+++ b/test/parser.js
@@ -9,139 +9,164 @@ var concat = linesModule.concat;
 var types = require("../lib/types");
 var namedTypes = types.namedTypes;
 var FastPath = require("../lib/fast-path");
+var eol = require("os").EOL;
 
 // Esprima seems unable to handle unnamed top-level functions, so declare
 // test functions with names and then export them later.
 
 describe("parser", function() {
-    it("Parser", function testParser(done) {
-        var code = testParser + "";
-        var ast = parse(code);
-
-        namedTypes.File.assert(ast);
-        assert.ok(getReprinter(FastPath.from(ast)));
-
-        var funDecl = ast.program.body[0],
-            funBody = funDecl.body;
-
-        namedTypes.FunctionDeclaration.assert(funDecl);
-        namedTypes.BlockStatement.assert(funBody);
-        assert.ok(getReprinter(FastPath.from(funBody)));
-
-        var lastStatement = funBody.body.pop(),
-            doneCall = lastStatement.expression;
-
-        assert.ok(!getReprinter(FastPath.from(funBody)));
-        assert.ok(getReprinter(FastPath.from(ast)));
-
-        funBody.body.push(lastStatement);
-        assert.ok(getReprinter(FastPath.from(funBody)));
-
-        assert.strictEqual(doneCall.callee.name, "done");
-
-        assert.strictEqual(lastStatement.comments.length, 2);
-
-        var firstComment = lastStatement.comments[0];
-        assert.strictEqual(firstComment.type, "Line");
-        assert.strictEqual(firstComment.leading, true);
-        assert.strictEqual(firstComment.trailing, false);
-        assert.strictEqual(
-            firstComment.value,
-            " Make sure done() remains the final statement in this function,"
-        );
-
-        var secondComment = lastStatement.comments[1];
-        assert.strictEqual(secondComment.type, "Line");
-        assert.strictEqual(secondComment.leading, true);
-        assert.strictEqual(secondComment.trailing, false);
-        assert.strictEqual(
-            secondComment.value,
-            " or the above assertions will probably fail."
-        );
-
-        // Make sure done() remains the final statement in this function,
-        // or the above assertions will probably fail.
-        done();
+  it("empty source", function () {
+    var printer = new Printer;
+
+    function check(code) {
+      var ast = parse(code);
+      assert.strictEqual(printer.print(ast).code, code);
+    }
+
+    check("");
+    check("/* block comment */");
+    check("// line comment");
+    check("\t\t\t");
+    check("\n");
+    check("\n\n");
+    check("    ");
+  });
+
+  it("Parser", function testParser(done) {
+    var code = testParser + "";
+    var ast = parse(code);
+
+    namedTypes.File.assert(ast);
+    assert.ok(getReprinter(FastPath.from(ast)));
+
+    var funDecl = ast.program.body[0];
+    var funBody = funDecl.body;
+
+    namedTypes.FunctionDeclaration.assert(funDecl);
+    namedTypes.BlockStatement.assert(funBody);
+    assert.ok(getReprinter(FastPath.from(funBody)));
+
+    var lastStatement = funBody.body.pop();
+    var doneCall = lastStatement.expression;
+
+    assert.ok(!getReprinter(FastPath.from(funBody)));
+    assert.ok(getReprinter(FastPath.from(ast)));
+
+    funBody.body.push(lastStatement);
+    assert.ok(getReprinter(FastPath.from(funBody)));
+
+    assert.strictEqual(doneCall.callee.name, "done");
+
+    assert.strictEqual(lastStatement.comments.length, 2);
+
+    var firstComment = lastStatement.comments[0];
+    assert.strictEqual(firstComment.type, "Line");
+    assert.strictEqual(firstComment.leading, true);
+    assert.strictEqual(firstComment.trailing, false);
+    assert.strictEqual(
+      firstComment.value,
+      " Make sure done() remains the final statement in this function,"
+    );
+
+    var secondComment = lastStatement.comments[1];
+    assert.strictEqual(secondComment.type, "Line");
+    assert.strictEqual(secondComment.leading, true);
+    assert.strictEqual(secondComment.trailing, false);
+    assert.strictEqual(
+      secondComment.value,
+      " or the above assertions will probably fail."
+    );
+
+    // Make sure done() remains the final statement in this function,
+    // or the above assertions will probably fail.
+    done();
+  });
+
+  it("LocationFixer", function() {
+    var code = [
+      "function foo() {",
+      "    a()",
+      "    b()",
+      "}"
+    ].join(eol);
+    var ast = parse(code);
+    var printer = new Printer;
+
+    types.visit(ast, {
+      visitFunctionDeclaration: function(path) {
+        path.node.body.body.reverse();
+        this.traverse(path);
+      }
     });
 
-    it("LocationFixer", function() {
-        var code = [
-            "function foo() {",
-            "    a()",
-            "    b()",
-            "}"
-        ].join("\n");
-        var ast = parse(code);
-        var printer = new Printer;
-
-        types.visit(ast, {
-            visitFunctionDeclaration: function(path) {
-                path.node.body.body.reverse();
-                this.traverse(path);
-            }
-        });
-
-        var altered = code
-            .replace("a()", "xxx")
-            .replace("b()", "a()")
-            .replace("xxx", "b()");
-
-        assert.strictEqual(altered, printer.print(ast).code);
-    });
-
-    it("TabHandling", function() {
-        function check(code, tabWidth) {
-            var lines = fromString(code, { tabWidth: tabWidth });
-            assert.strictEqual(lines.length, 1);
-
-            types.visit(parse(code, { tabWidth: tabWidth }), {
-                check: function(s, loc) {
-                    var sliced = lines.slice(loc.start, loc.end);
-                    assert.strictEqual(s + "", sliced.toString());
-                },
-
-                visitIdentifier: function(path) {
-                    var ident = path.node;
-                    this.check(ident.name, ident.loc);
-                    this.traverse(path);
-                },
-
-                visitLiteral: function(path) {
-                    var lit = path.node;
-                    this.check(lit.value, lit.loc);
-                    this.traverse(path);
-                }
-            });
-        }
-
-        for (var tabWidth = 1; tabWidth <= 8; ++tabWidth) {
-            check("\t\ti = 10;", tabWidth);
-            check("\t\ti \t= 10;", tabWidth);
-            check("\t\ti \t=\t 10;", tabWidth);
-            check("\t \ti \t=\t 10;", tabWidth);
-            check("\t \ti \t=\t 10;\t", tabWidth);
-            check("\t \ti \t=\t 10;\t ", tabWidth);
+    var altered = code
+      .replace("a()", "xxx")
+      .replace("b()", "a()")
+      .replace("xxx", "b()");
+
+    assert.strictEqual(altered, printer.print(ast).code);
+  });
+
+  it("TabHandling", function() {
+    function check(code, tabWidth) {
+      var lines = fromString(code, { tabWidth: tabWidth });
+      assert.strictEqual(lines.length, 1);
+
+      types.visit(parse(code, { tabWidth: tabWidth }), {
+        check: function(s, loc) {
+          var sliced = lines.slice(loc.start, loc.end);
+          assert.strictEqual(s + "", sliced.toString());
+        },
+
+        visitIdentifier: function(path) {
+          var ident = path.node;
+          this.check(ident.name, ident.loc);
+          this.traverse(path);
+        },
+
+        visitLiteral: function(path) {
+          var lit = path.node;
+          this.check(lit.value, lit.loc);
+          this.traverse(path);
         }
-    });
-
-    it("AlternateEsprima", function() {
-        var types = require("../lib/types");
-        var b = types.builders;
-        var esprima = {
-            parse: function(code) {
-                var program = b.program([
-                    b.expressionStatement(b.identifier("surprise"))
-                ]);
-                program.comments = [];
-                return program;
-            }
-        };
-        var ast = parse("ignored", { esprima: esprima });
-        var printer = new Printer;
-
-        types.namedTypes.File.assert(ast, true);
-        assert.strictEqual(
-            printer.printGenerically(ast).code,
-            "surprise;");
-    });
+      });
+    }
+
+    for (var tabWidth = 1; tabWidth <= 8; ++tabWidth) {
+      check("\t\ti = 10;", tabWidth);
+      check("\t\ti \t= 10;", tabWidth);
+      check("\t\ti \t=\t 10;", tabWidth);
+      check("\t \ti \t=\t 10;", tabWidth);
+      check("\t \ti \t=\t 10;\t", tabWidth);
+      check("\t \ti \t=\t 10;\t ", tabWidth);
+    }
+  });
+
+  it("AlternateParser", function() {
+    var types = require("../lib/types");
+    var b = types.builders;
+    var parser = {
+      parse: function(code) {
+        var program = b.program([
+          b.expressionStatement(b.identifier("surprise"))
+        ]);
+        program.comments = [];
+        return program;
+      }
+    };
+
+    function check(options) {
+      var ast = parse("ignored", options);
+      var printer = new Printer;
+
+      types.namedTypes.File.assert(ast, true);
+      assert.strictEqual(
+        printer.printGenerically(ast).code,
+        "surprise;"
+      );
+    }
+
+    check({ esprima: parser });
+    check({ parser: parser });
+  });
 });
diff --git a/test/patcher.js b/test/patcher.js
index 27a5e0b..3fc023f 100644
--- a/test/patcher.js
+++ b/test/patcher.js
@@ -9,6 +9,7 @@ var Patcher = patcherModule.Patcher;
 var fromString = require("../lib/lines").fromString;
 var parse = require("../lib/parser").parse;
 var FastPath = require("../lib/fast-path");
+var eol = require("os").EOL;
 
 var code = [
     "// file comment",
@@ -28,7 +29,7 @@ function loc(sl, sc, el, ec) {
 
 describe("patcher", function() {
     it("Patcher", function() {
-        var lines = fromString(code.join("\n")),
+        var lines = fromString(code.join(eol)),
             patcher = new Patcher(lines),
             selfLoc = loc(5, 9, 5, 13);
 
@@ -39,7 +40,7 @@ describe("patcher", function() {
         assert.strictEqual(patcher.get(selfLoc).toString(), "self");
 
         var got = patcher.get().toString();
-        assert.strictEqual(got, code.join("\n").replace("this", "self"));
+        assert.strictEqual(got, code.join(eol).replace("this", "self"));
 
         // Make sure comments are preserved.
         assert.ok(got.indexOf("// some") >= 0);
@@ -54,7 +55,7 @@ describe("patcher", function() {
         assert.strictEqual(patcher.get().toString(), [
             "// file comment",
             "exports.foo(oyez);"
-        ].join("\n"));
+        ].join(eol));
 
         // "Reset" the patcher.
         patcher = new Patcher(lines);
@@ -64,7 +65,7 @@ describe("patcher", function() {
         assert.strictEqual(patcher.get().toString(), [
             "// file comment",
             "exports.foo(oyez);"
-        ].join("\n"));
+        ].join(eol));
     });
 
     var trickyCode = [
@@ -73,7 +74,7 @@ describe("patcher", function() {
         "  baz) {",
         "        qux();",
         "    }"
-    ].join("\n");
+    ].join(eol);
 
     it("GetIndent", function() {
         function check(indent) {
@@ -95,7 +96,7 @@ describe("patcher", function() {
                 "{",
                 "    qux();",
                 "}"
-            ].join("\n"));
+            ].join(eol));
         }
 
         for (var indent = -4; indent <= 4; ++indent) {
@@ -157,7 +158,7 @@ describe("patcher", function() {
         var twoLineCode = [
             "return",      // Because of ASI rules, these two lines will
             '"use strict"' // parse as separate statements.
-        ].join("\n");
+        ].join(eol);
 
         var twoLineAST = parse(twoLineCode);
 
@@ -179,7 +180,7 @@ describe("patcher", function() {
             "return",
             "sloppy" // The key is that no space should be added to the
                      // beginning of this line.
-        ].join("\n"));
+        ].join(eol));
 
         twoLineAST.program.body[1] = b.expressionStatement(
             b.callExpression(b.identifier("foo"), [])
@@ -189,6 +190,6 @@ describe("patcher", function() {
         assert.strictEqual(withFooCall, [
             "return",
             "foo()"
-        ].join("\n"));
+        ].join(eol));
     });
 });
diff --git a/test/printer.js b/test/printer.js
index fa4ad8b..01d0710 100644
--- a/test/printer.js
+++ b/test/printer.js
@@ -5,6 +5,7 @@ var Printer = require("../lib/printer").Printer;
 var n = require("../lib/types").namedTypes;
 var b = require("../lib/types").builders;
 var fromString = require("../lib/lines").fromString;
+var eol = require("os").EOL;
 
 describe("printer", function() {
     it("Printer", function testPrinter(done) {
@@ -35,7 +36,7 @@ describe("printer", function() {
         'function b() {',
         '  return "b";',
         '};'
-    ].join("\n");
+    ].join(eol);
 
     it("EmptyStatements", function() {
         var ast = parse(uselessSemicolons);
@@ -54,7 +55,7 @@ describe("printer", function() {
     var importantSemicolons = [
         "var a = {};", // <--- this trailing semi-colon is very important
         "(function() {})();"
-    ].join("\n");
+    ].join(eol);
 
     it("IffeAfterVariableDeclarationEndingInObjectLiteral", function() {
         var ast = parse(importantSemicolons);
@@ -121,7 +122,7 @@ describe("printer", function() {
     });
 
     var objectExprWithTrailingComma = '({x: 1, y: 2,});';
-    var objectExprWithoutTrailingComma = '({\n  x: 1,\n  y: 2\n});';
+    var objectExprWithoutTrailingComma = '({' + eol + '  x: 1,' + eol + '  y: 2' + eol + '});';
 
     it("ArrayExpressionWithTrailingComma", function() {
         var ast = parse(objectExprWithTrailingComma);
@@ -156,7 +157,7 @@ describe("printer", function() {
         "  case b:",
         "    break;",
         "}",
-    ].join("\n");
+    ].join(eol);
 
     var switchCaseReprinted = [
         "if (test) {",
@@ -167,7 +168,7 @@ describe("printer", function() {
         "    break;",
         "  }",
         "}"
-    ].join("\n");
+    ].join(eol);
 
     var switchCaseGeneric = [
         "if (test) {",
@@ -179,7 +180,7 @@ describe("printer", function() {
         "    break;",
         "  }",
         "}"
-    ].join("\n");
+    ].join(eol);
 
     it("SwitchCase", function() {
         var ast = parse(switchCase);
@@ -216,7 +217,7 @@ describe("printer", function() {
         "} catch (e) {",
         "  b(e);",
         "}"
-    ].join("\n");
+    ].join(eol);
 
     it("IndentTryCatch", function() {
         var ast = parse(tryCatch);
@@ -253,7 +254,7 @@ describe("printer", function() {
     ];
 
     it("MethodPrinting", function() {
-        var code = classBody.join("\n");
+        var code = classBody.join(eol);
         try {
             var ast = parse(code);
         } catch (e) {
@@ -269,7 +270,7 @@ describe("printer", function() {
 
         assert.strictEqual(
             printer.print(ast).code,
-            classBodyExpected.join("\n")
+            classBodyExpected.join(eol)
         );
     });
 
@@ -299,7 +300,7 @@ describe("printer", function() {
     ];
 
     it("MultiLineParams", function() {
-        var code = multiLineParams.join("\n");
+        var code = multiLineParams.join(eol);
         var ast = parse(code);
         var printer = new Printer({ tabWidth: 2 });
 
@@ -312,7 +313,7 @@ describe("printer", function() {
 
         assert.strictEqual(
             printer.print(ast).code,
-            multiLineParamsExpected.join("\n")
+            multiLineParamsExpected.join(eol)
         );
     });
 
@@ -358,7 +359,7 @@ describe("printer", function() {
             "      why: \"not\"",
             "    },",
             "    z;"
-        ].join("\n"));
+        ].join(eol));
     });
 
     it("ForLoopPrinting", function() {
@@ -376,7 +377,7 @@ describe("printer", function() {
 
         assert.strictEqual(
             printer.print(loop).code,
-            "for (var i = 0; i < 3; i++)\n" +
+            "for (var i = 0; i < 3; i++)" + eol +
             "  log(i);"
         );
     });
@@ -394,7 +395,7 @@ describe("printer", function() {
 
         assert.strictEqual(
             printer.print(loop).code,
-            "for (var i = 0; i < 3; i++)\n" +
+            "for (var i = 0; i < 3; i++)" + eol +
             "  ;"
         );
     });
@@ -414,7 +415,7 @@ describe("printer", function() {
 
         assert.strictEqual(
             printer.print(loop).code,
-            "for (var key in obj)\n" +
+            "for (var key in obj)" + eol +
             "  log(key);"
         );
     });
@@ -424,21 +425,21 @@ describe("printer", function() {
             "function identity(x) {",
             "  return x;",
             "}"
-        ].join("\n");
+        ].join(eol);
 
         var guessedTwo = [
             "function identity(x) {",
             "  log(x);",
             "  return x;",
             "}"
-        ].join("\n");
+        ].join(eol);
 
         var explicitFour = [
             "function identity(x) {",
             "    log(x);",
             "    return x;",
             "}"
-        ].join("\n");
+        ].join(eol);
 
         var ast = parse(code);
 
@@ -559,7 +560,7 @@ describe("printer", function() {
         "console.log(x, y, z);",
         "",
         ""
-    ].join("\n");
+    ].join(eol);
 
     var stmtListSpacesExpected = [
         "",
@@ -578,7 +579,7 @@ describe("printer", function() {
         "debugger;",
         "",
         ""
-    ].join("\n");
+    ].join(eol);
 
     it("Statement list whitespace reuse", function() {
         var ast = parse(stmtListSpaces);
@@ -605,11 +606,11 @@ describe("printer", function() {
         assert.strictEqual(
             printer.print(funDecl).code,
             linesModule.concat([
-                "function foo() {\n",
+                "function foo() {" + eol,
                 linesModule.fromString(
                     stmtListSpacesExpected.replace(/^\s+|\s+$/g, "")
                 ).indent(2),
-                "\n}"
+                eol + "}"
             ]).toString()
         );
     });
@@ -620,7 +621,7 @@ describe("printer", function() {
             "class A {",
             "  static foo() {}",
             "}"
-        ].join("\n"));
+        ].join(eol));
 
         var classBody = ast.program.body[0].body;
         n.ClassBody.assert(classBody);
@@ -637,7 +638,7 @@ describe("printer", function() {
             "    static formerlyFoo() {}",
             "    static formerlyFoo() {}",
             "}"
-        ].join("\n"));
+        ].join(eol));
     });
 
     it("should print string literals with the specified delimiter", function() {
@@ -646,7 +647,7 @@ describe("printer", function() {
             "    \"foo's\": 'bar',",
             "    '\"bar\\'s\"': /regex/m",
             "};"
-        ].join("\n"));
+        ].join(eol));
 
         var variableDeclaration = ast.program.body[0];
         n.VariableDeclaration.assert(variableDeclaration);
@@ -657,7 +658,7 @@ describe("printer", function() {
             "    'foo\\'s': 'bar',",
             "    '\"bar\\'s\"': /regex/m",
             "};"
-        ].join("\n"));
+        ].join(eol));
 
         var printer2 = new Printer({ quote: "double" });
         assert.strictEqual(printer2.printGenerically(ast).code, [
@@ -665,7 +666,7 @@ describe("printer", function() {
             "    \"foo's\": \"bar\",",
             '    "\\"bar\'s\\"": /regex/m',
             "};"
-        ].join("\n"));
+        ].join(eol));
 
         var printer3 = new Printer({ quote: "auto" });
         assert.strictEqual(printer3.printGenerically(ast).code, [
@@ -673,7 +674,7 @@ describe("printer", function() {
             '    "foo\'s": "bar",',
             '    \'"bar\\\'s"\': /regex/m',
             "};"
-        ].join("\n"));
+        ].join(eol));
     });
 
     it("should print block comments at head of class once", function() {
@@ -684,7 +685,7 @@ describe("printer", function() {
             " */",
             "function SimpleClass() {",
             "};"
-        ].join("\n"));
+        ].join(eol));
 
         var classIdentifier = b.identifier('SimpleClass');
         var exportsExpression = b.memberExpression(b.identifier('module'), b.identifier('exports'), false);
@@ -704,7 +705,7 @@ describe("printer", function() {
             "function SimpleClass() {",
             "}",
             "module.exports = SimpleClass;"
-        ].join("\n"));
+        ].join(eol));
     });
 
     it("should support computed properties", function() {
@@ -735,7 +736,7 @@ describe("printer", function() {
             '  static set [0](x) {}',
             '  static set [ID(1)](x) {}',
             '}'
-        ].join("\n");
+        ].join(eol);
 
         var ast = parse(code);
 
@@ -767,7 +768,7 @@ describe("printer", function() {
             '  set [0](x) {},',
             '  set [ID(1)](x) {}',
             '};'
-        ].join("\n");
+        ].join(eol);
 
         ast = parse(code);
 
@@ -781,7 +782,7 @@ describe("printer", function() {
             "  // This foo will become a computed method name.",
             "  foo() { return bar }",
             "};"
-        ].join("\n"));
+        ].join(eol));
 
         var objExpr = ast.program.body[0].declarations[0].init;
         n.ObjectExpression.assert(objExpr);
@@ -795,7 +796,7 @@ describe("printer", function() {
             "  // This foo will become a computed method name.",
             "  get [foo]() { return bar }",
             "};"
-        ].join("\n"));
+        ].join(eol));
     });
 
     it("prints trailing commas in object literals", function() {
@@ -804,7 +805,7 @@ describe("printer", function() {
             "  foo: bar,",
             "  bar: foo,",
             "});"
-        ].join("\n");
+        ].join(eol);
 
         var ast = parse(code);
 
@@ -823,7 +824,7 @@ describe("printer", function() {
             "  1,",
             "  2,",
             ");"
-        ].join("\n");
+        ].join(eol);
 
         var ast = parse(code);
 
@@ -843,7 +844,7 @@ describe("printer", function() {
             "  1,",
             "  2,",
             "];"
-        ].join("\n");
+        ].join(eol);
 
         var ast = parse(code);
 
@@ -863,7 +864,7 @@ describe("printer", function() {
             "  a,",
             "  b,",
             ") {}"
-        ].join("\n");
+        ].join(eol);
 
         var ast = parse(code);
 
@@ -882,7 +883,7 @@ describe("printer", function() {
             "function foo(a, [b, c]=d(a), ...[e, f, ...rest]) {",
             "  return [a, b, c, e, f, rest];",
             "}"
-        ].join("\n");
+        ].join(eol);
 
         var ast = parse(code);
         var printer = new Printer({
@@ -938,12 +939,48 @@ describe("printer", function() {
         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
+        });
+
+        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);
+    });
+
     it("prints ClassProperty correctly", function() {
         var code = [
             "class A {",
             "  foo: Type = Bar;",
             "}",
-        ].join("\n");
+        ].join(eol);
 
         var ast = b.program([
             b.classDeclaration(
@@ -973,7 +1010,7 @@ describe("printer", function() {
             "class A {",
             "  static foo = Bar;",
             "}",
-        ].join("\n");
+        ].join(eol);
 
         var ast = b.program([
             b.classDeclaration(
@@ -1000,7 +1037,7 @@ describe("printer", function() {
     it("prints template expressions correctly", function() {
         var code = [
             "graphql`query`;",
-        ].join("\n");
+        ].join(eol);
 
         var ast = b.program([
             b.taggedTemplateStatement(
@@ -1021,7 +1058,7 @@ describe("printer", function() {
 
         code = [
             "graphql`query${foo.getQuery()}field${bar}`;",
-        ].join("\n");
+        ].join(eol);
 
         ast = b.program([
             b.taggedTemplateStatement(
@@ -1067,7 +1104,7 @@ describe("printer", function() {
             "    ${bar},",
             "  }",
             "`;",
-        ].join("\n");
+        ].join(eol);
 
         ast = parse(code);
         pretty = printer.printGenerically(ast).code;
@@ -1079,7 +1116,7 @@ describe("printer", function() {
             "",
             "f();",
             ""
-        ].join("\n");
+        ].join(eol);
 
         var lines = fromString(code);
         var ast = parse(code, {
@@ -1108,7 +1145,7 @@ describe("printer", function() {
             "debugger;",
             "f();",
             ""
-        ].join("\n"));
+        ].join(eol));
     });
 
     it("respects options.lineTerminator", function() {
@@ -1126,4 +1163,72 @@ describe("printer", function() {
             lines.join("\r\n")
         );
     });
+
+    it("preserves indentation in unmodified template expressions", function() {
+        var printer = new Printer({
+            tabWidth: 2
+        });
+
+        var code = [
+            "var x = {",
+            "  y: () => Relay.QL`",
+            "    query {",
+            "      ${foo},",
+            "      field,",
+            "    }",
+            "  `",
+            "};",
+        ].join("\n");
+
+        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("\n");
+
+        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"));
+                }
+
+                path.get("quasi", "quasis").each(function (quasiPath) {
+                    replaceIdWithNodeId(quasiPath.get("value", "cooked"));
+                    replaceIdWithNodeId(quasiPath.get("value", "raw"));
+                });
+
+                this.traverse(path);
+            }
+        });
+
+        var actual = printer.print(ast).code;
+        var expected = code.replace(/\bid\b/g, "nodeID");
+
+        assert.strictEqual(actual, expected);
+    });
 });
diff --git a/test/type-syntax.js b/test/type-syntax.js
index 6c7bde9..f84391a 100644
--- a/test/type-syntax.js
+++ b/test/type-syntax.js
@@ -4,6 +4,7 @@ var Printer = require("../lib/printer").Printer;
 var types = require("../lib/types");
 var n = types.namedTypes;
 var b = types.builders;
+var eol = require("os").EOL;
 
 describe("type syntax", function() {
     var printer = new Printer({ tabWidth: 2, quote: 'single' });
@@ -56,10 +57,10 @@ describe("type syntax", function() {
         check("var a: () => X = fn;");
 
         // Object
-        check("var a: {\n  b: number;\n  x: {y: A};\n};");
+        check("var a: {" + eol + "  b: number;" + eol + "  x: {y: A};" + eol + "};");
         check("var b: {[key: string]: number};")
         check("var c: {(): number};")
-        check("var d: {\n  [key: string]: A;\n  [key: number]: B;\n  (): C;\n  a: D;\n};")
+        check("var d: {" + eol + "  [key: string]: A;" + eol + "  [key: number]: B;" + eol + "  (): C;" + eol + "  a: D;" + eol + "};")
 
         // Casts
         check("(1 + 1: number);");
@@ -71,19 +72,19 @@ describe("type syntax", function() {
         check("declare function foo(c: C, b: B): void;");
         check("declare function foo(c: (e: Event) => void, b: B): void;");
         check("declare class C {x: string}");
-        check("declare module M {\n  declare function foo(c: C): void;\n}");
+        check("declare module M {" + eol + "  declare function foo(c: C): void;" + eol + "}");
 
         // Classes
-        check("class A {\n  a: number;\n}");
-        check("class A {\n  foo(a: number): string {}\n}");
-        check("class A {\n  static foo(a: number): string {}\n}");
+        check("class A {" + eol + "  a: number;" + eol + "}");
+        check("class A {" + eol + "  foo(a: number): string {}" + eol + "}");
+        check("class A {" + eol + "  static foo(a: number): string {}" + eol + "}");
 
         // Type parameters
         check("class A<T> {}");
         check("class A<X, Y> {}");
         check("class A<X> extends B<Y> {}");
         check("function a<T>(y: Y<T>): T {}");
-        check("class A {\n  foo<T>(a: number): string {}\n}");
+        check("class A {" + eol + "  foo<T>(a: number): string {}" + eol + "}");
 
         // Interfaces
         check("interface A<X> extends B<A>, C {a: number}");
diff --git a/test/visit.js b/test/visit.js
index 0f42a06..ac94a87 100644
--- a/test/visit.js
+++ b/test/visit.js
@@ -4,6 +4,7 @@ var namedTypes = types.namedTypes;
 var builders = types.builders;
 var parse = require("../lib/parser").parse;
 var Printer = require("../lib/printer").Printer;
+var eol = require("os").EOL;
 
 var lines = [
     "// file comment",
@@ -16,7 +17,7 @@ var lines = [
 
 describe("types.visit", function() {
     it("replacement", function() {
-        var source = lines.join("\n");
+        var source = lines.join(eol);
         var printer = new Printer;
         var ast = parse(source);
         var withThis = printer.print(ast).code;
@@ -94,7 +95,7 @@ describe("types.visit", function() {
             "})));"
         ];
 
-        var source = lines.join("\n");
+        var source = lines.join(eol);
         var ast = parse(source);
         var printer = new Printer;
 
@@ -127,11 +128,11 @@ describe("types.visit", function() {
 
             visitObjectExpression: function() {
                 return funExpr;
-            }            
+            }
         });
 
         assert.strictEqual(
-            altered.join("\n"),
+            altered.join(eol),
             printer.print(ast).code
         );
     });

-- 
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