From c6f3a55c03581d42c91a05e3864b68df40768534 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 14 Jan 2015 14:00:58 +0300 Subject: [PATCH 1/7] Refactor buffer for clarity and avoid regex for performance --- lib/6to5/generation/buffer.js | 42 ++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/lib/6to5/generation/buffer.js b/lib/6to5/generation/buffer.js index c9b4e662fe..e702efd00c 100644 --- a/lib/6to5/generation/buffer.js +++ b/lib/6to5/generation/buffer.js @@ -67,32 +67,58 @@ Buffer.prototype.removeLast = function (cha) { Buffer.prototype.newline = function (i, removeLast) { if (this.format.compact) return; - - if (_.isBoolean(i)) { - removeLast = i; - i = null; - } + removeLast = removeLast || false; if (_.isNumber(i)) { if (this.endsWith("{\n")) i--; if (this.endsWith(util.repeat(i, "\n"))) return; - for (var j = 0; j < i; j++) { - this.newline(null, removeLast); + while (i--) { + this._newline(removeLast); } return; } + if (_.isBoolean(i)) { + removeLast = i; + } + + this._newline(removeLast); +}; + +Buffer.prototype._newline = function (removeLast) { if (removeLast && this.isLast("\n")) this.removeLast("\n"); this.removeLast(" "); // remove whitespace if last character was a newline - this.buf = this.buf.replace(/\n +$/, "\n"); + var trimStart = this._getPostNewlineTrailingSpaceStart(); + if (trimStart) { + this.buf = this.buf.substring(0, trimStart); + } this._push("\n"); }; +Buffer.prototype._getPostNewlineTrailingSpaceStart = function () { + var lastNewlineIndex = this.buf.lastIndexOf('\n'); + if (lastNewlineIndex === -1) + return; + + var index = this.buf.length - 1; + while (index > lastNewlineIndex) { + if (this.buf[index] !== ' ') { + break; + } + + index--; + } + + if (index === lastNewlineIndex) { + return index + 1; + } +}; + Buffer.prototype.push = function (str, noIndent) { if (this._indent && !noIndent && str !== "\n") { // we have an indent level and we aren't pushing a newline From c7c90acf3fb5863010c608bda3f9dc2106a3d3dd Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 14 Jan 2015 19:08:33 +0300 Subject: [PATCH 2/7] Store ranges instead of line indexes for performance --- lib/6to5/generation/whitespace.js | 16 +++------ lib/6to5/util.js | 60 +++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/lib/6to5/generation/whitespace.js b/lib/6to5/generation/whitespace.js index c7dda860da..6d16536f26 100644 --- a/lib/6to5/generation/whitespace.js +++ b/lib/6to5/generation/whitespace.js @@ -1,6 +1,7 @@ module.exports = Whitespace; -var _ = require("lodash"); +var _ = require("lodash"); +var util = require("../util"); function Whitespace(tokens, comments) { this.tokens = _.sortBy(tokens.concat(comments), "start"); @@ -61,14 +62,5 @@ Whitespace.prototype.getNewlinesBetween = function (startToken, endToken) { var start = startToken ? startToken.loc.end.line : 1; var end = endToken.loc.start.line; - var lines = 0; - - for (var line = start; line < end; line++) { - if (!_.contains(this.used, line)) { - this.used.push(line); - lines++; - } - } - - return lines; -}; + return util.mergeIntegerRange(this.used, start, end); +}; \ No newline at end of file diff --git a/lib/6to5/util.js b/lib/6to5/util.js index 66af587e4f..23d9ec9227 100644 --- a/lib/6to5/util.js +++ b/lib/6to5/util.js @@ -204,6 +204,66 @@ exports.repeat = function (width, cha) { return result; }; +exports.mergeIntegerRange = function (ranges, start, end) { + var pointsAdded = 0; + var insertIndex; + var rangeIndex; + var point; + var range; + var matchingRange; + var rangeImmediatelyToLeft; + var rangeImmediatelyToRight; + + for (point = start; point < end; point++) { + matchingRange = null; + rangeImmediatelyToLeft = null; + rangeImmediatelyToRight = null; + insertIndex = ranges.length; + + for (rangeIndex = 0; rangeIndex < ranges.length; rangeIndex++) { + range = ranges[rangeIndex]; + + if (point >= range.start && point <= range.end) { + matchingRange = range; + break; + } + + if (point < range.start) { + insertIndex = rangeIndex; + } + + if (point === range.end + 1) { + rangeImmediatelyToLeft = range; + } + + if (point === range.start - 1) { + rangeImmediatelyToRight = range; + } + } + + if (matchingRange) + continue; + + pointsAdded++; + + if (rangeImmediatelyToLeft && rangeImmediatelyToRight) { + ranges.splice(ranges.indexOf(rangeImmediatelyToRight), 1); + rangeImmediatelyToLeft.end = rangeImmediatelyToRight.end; + } else if (rangeImmediatelyToLeft) { + rangeImmediatelyToLeft.end = point; + } else if (rangeImmediatelyToRight) { + rangeImmediatelyToRight.start = point; + } else { + ranges.splice(insertIndex, 0, { + start: point, + end: point + }); + } + } + + return pointsAdded; +}; + exports.normaliseAst = function (ast, comments, tokens) { if (ast && ast.type === "Program") { return t.file(ast, comments || [], tokens || []); From 19eaa181a524f9a1ed9f4073f284a5dde271a6d0 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 14 Jan 2015 20:02:40 +0300 Subject: [PATCH 3/7] Speed up common case where consumer moves only forward --- lib/6to5/generation/whitespace.js | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/6to5/generation/whitespace.js b/lib/6to5/generation/whitespace.js index 6d16536f26..5e356c2f27 100644 --- a/lib/6to5/generation/whitespace.js +++ b/lib/6to5/generation/whitespace.js @@ -3,9 +3,22 @@ module.exports = Whitespace; var _ = require("lodash"); var util = require("../util"); +// a helper to iterate array from arbitrary index +function getLookupIndex(i, base, max) { + i += base; + + if (i >= max) + i -= max; + + return i; +} + function Whitespace(tokens, comments) { this.tokens = _.sortBy(tokens.concat(comments), "start"); this.used = []; + + // speed up common case where methods are called for next tokens + this._lastFoundIndex = 0; } Whitespace.prototype.getNewlinesBefore = function (node) { @@ -14,13 +27,15 @@ Whitespace.prototype.getNewlinesBefore = function (node) { var tokens = this.tokens; var token; - for (var i = 0; i < tokens.length; i++) { + for (var j = 0; j < tokens.length; j++) { + var i = getLookupIndex(j, this._lastFoundIndex, this.tokens.length); token = tokens[i]; // this is the token this node starts with if (node.start === token.start) { startToken = tokens[i - 1]; endToken = token; + this._lastFoundIndex = i; break; } } @@ -34,13 +49,15 @@ Whitespace.prototype.getNewlinesAfter = function (node) { var tokens = this.tokens; var token; - for (var i = 0; i < tokens.length; i++) { + for (var j = 0; j < tokens.length; j++) { + var i = getLookupIndex(j, this._lastFoundIndex, this.tokens.length); token = tokens[i]; // this is the token this node ends with if (node.end === token.end) { startToken = token; endToken = tokens[i + 1]; + this._lastFoundIndex = i; break; } } From 58a91ee9e9173acb2d0b8ec5d3c3acb2db1ef68e Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 14 Jan 2015 20:59:32 +0300 Subject: [PATCH 4/7] Optimize node type lookup --- lib/6to5/generation/node/index.js | 2 +- lib/6to5/types/index.js | 91 ++++++++++++++++++------------- 2 files changed, 54 insertions(+), 39 deletions(-) diff --git a/lib/6to5/generation/node/index.js b/lib/6to5/generation/node/index.js index 42daaafaec..bedd7ba60a 100644 --- a/lib/6to5/generation/node/index.js +++ b/lib/6to5/generation/node/index.js @@ -13,7 +13,7 @@ var find = function (obj, node, parent) { for (var i = 0; i < types.length; i++) { var type = types[i]; - if (t["is" + type](node)) { + if (t.is(type, node)) { var fn = obj[type]; result = fn(node, parent); if (result != null) break; diff --git a/lib/6to5/types/index.js b/lib/6to5/types/index.js index c00e9506af..1e2f6487d9 100644 --- a/lib/6to5/types/index.js +++ b/lib/6to5/types/index.js @@ -7,14 +7,18 @@ t.NATIVE_TYPE_NAMES = ["Array", "Object", "Number", "Boolean", "Date", "Array", // -var addAssert = function (type, is) { +function registerType(type, skipAliases) { + var is = t["is" + type] = function (node, opts) { + return t.is(type, node, opts, skipAliases); + }; + t["assert" + type] = function (node, opts) { opts = opts || {}; if (!is(node, opts)) { throw new Error("Expected type " + JSON.stringify(type) + " with option " + JSON.stringify(opts)); } }; -}; +} t.STATEMENT_OR_BLOCK_KEYS = ["consequent", "body"]; @@ -22,14 +26,48 @@ t.STATEMENT_OR_BLOCK_KEYS = ["consequent", "body"]; t.VISITOR_KEYS = require("./visitor-keys"); -_.each(t.VISITOR_KEYS, function (keys, type) { - var is = t["is" + type] = function (node, opts) { - return node && node.type === type && t.shallowEqual(node, opts); - }; +t.ALIAS_KEYS = require("./alias-keys"); - addAssert(type, is); +t.FLIPPED_ALIAS_KEYS = {}; + +_.each(t.VISITOR_KEYS, function (keys, type) { + registerType(type, true); }); +_.each(t.ALIAS_KEYS, function (aliases, type) { + _.each(aliases, function (alias) { + var types = t.FLIPPED_ALIAS_KEYS[alias] = t.FLIPPED_ALIAS_KEYS[alias] || []; + types.push(type); + }); +}); + +_.each(t.FLIPPED_ALIAS_KEYS, function (types, type) { + t[type.toUpperCase() + "_TYPES"] = types; + registerType(type, false); +}); + +t.is = function (type, node, opts, skipAliases) { + if (!node) return; + + var typeMatches = (type === node.type); + + if (!typeMatches && !skipAliases) { + var aliases = t.FLIPPED_ALIAS_KEYS[type]; + + if (typeof aliases !== 'undefined') + typeMatches = aliases.indexOf(node.type) > -1; + } + + if (!typeMatches) { + return false; + } + + if (typeof opts !== 'undefined') + return t.shallowEqual(node, opts); + + return true; +}; + // t.BUILDER_KEYS = _.defaults(require("./builder-keys"), t.VISITOR_KEYS); @@ -45,29 +83,6 @@ _.each(t.BUILDER_KEYS, function (keys, type) { }; }); -// - -t.ALIAS_KEYS = require("./alias-keys"); - -t.FLIPPED_ALIAS_KEYS = {}; - -_.each(t.ALIAS_KEYS, function (aliases, type) { - _.each(aliases, function (alias) { - var types = t.FLIPPED_ALIAS_KEYS[alias] = t.FLIPPED_ALIAS_KEYS[alias] || []; - types.push(type); - }); -}); - -_.each(t.FLIPPED_ALIAS_KEYS, function (types, type) { - t[type.toUpperCase() + "_TYPES"] = types; - - var is = t["is" + type] = function (node, opts) { - return node && types.indexOf(node.type) >= 0 && t.shallowEqual(node, opts); - }; - - addAssert(type, is); -}); - /** * Description * @@ -139,17 +154,17 @@ t.toSequenceExpression = function (nodes, scope) { // t.shallowEqual = function (actual, expected) { - var same = true; + var keys = Object.keys(expected); + var key; - if (expected) { - _.each(expected, function (val, key) { - if (actual[key] !== val) { - return same = false; - } - }); + for (var i = 0; i < keys.length; i++) { + key = keys[i]; + + if (actual[key] !== expected[key]) + return false; } - return same; + return true; }; /** From a452f781b80ac1a239b6d0d4ed527fc6733d1b9f Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 15 Jan 2015 03:38:46 +0300 Subject: [PATCH 5/7] Slightly refactor and add explanations for optimized functions --- lib/6to5/generation/buffer.js | 13 ++++++------- lib/6to5/generation/whitespace.js | 23 +++++++++++++++++++++-- lib/6to5/types/index.js | 26 ++++++++++++++++++++++---- lib/6to5/util.js | 18 ++++++++++++++++++ 4 files changed, 67 insertions(+), 13 deletions(-) diff --git a/lib/6to5/generation/buffer.js b/lib/6to5/generation/buffer.js index e702efd00c..2fadf5db33 100644 --- a/lib/6to5/generation/buffer.js +++ b/lib/6to5/generation/buffer.js @@ -92,15 +92,14 @@ Buffer.prototype._newline = function (removeLast) { this.removeLast(" "); // remove whitespace if last character was a newline - var trimStart = this._getPostNewlineTrailingSpaceStart(); - if (trimStart) { - this.buf = this.buf.substring(0, trimStart); - } - + this._removeSpacesAfterLastNewline(); this._push("\n"); }; -Buffer.prototype._getPostNewlineTrailingSpaceStart = function () { +/** + * If buffer ends with a newline and some spaces after it, trim those spaces. + */ +Buffer.prototype._removeSpacesAfterLastNewline = function () { var lastNewlineIndex = this.buf.lastIndexOf('\n'); if (lastNewlineIndex === -1) return; @@ -115,7 +114,7 @@ Buffer.prototype._getPostNewlineTrailingSpaceStart = function () { } if (index === lastNewlineIndex) { - return index + 1; + this.buf = this.buf.substring(0, index + 1); } }; diff --git a/lib/6to5/generation/whitespace.js b/lib/6to5/generation/whitespace.js index 5e356c2f27..637b155c3a 100644 --- a/lib/6to5/generation/whitespace.js +++ b/lib/6to5/generation/whitespace.js @@ -3,7 +3,15 @@ module.exports = Whitespace; var _ = require("lodash"); var util = require("../util"); -// a helper to iterate array from arbitrary index +/** + * Returns `i`th number from `base`, continuing from 0 when `max` is reached. + * Useful for shifting `for` loop by a fixed number but going over all items. + * + * @param {Number} i Current index in the loop + * @param {Number} base Start index for which to return 0 + * @param {Number} max Array length + * @returns {Number} shiftedIndex + */ function getLookupIndex(i, base, max) { i += base; @@ -17,7 +25,14 @@ function Whitespace(tokens, comments) { this.tokens = _.sortBy(tokens.concat(comments), "start"); this.used = []; - // speed up common case where methods are called for next tokens + // Profiling this code shows that while generator passes over it, indexes + // returned by `getNewlinesBefore` and `getNewlinesAfter` are always increasing. + + // We use this implementation detail for an optimization: instead of always + // starting to look from `this.tokens[0]`, we will start `for` loops from the + // previous successful match. We will enumerate all tokens—but the common + // case will be much faster. + this._lastFoundIndex = 0; } @@ -28,6 +43,7 @@ Whitespace.prototype.getNewlinesBefore = function (node) { var token; for (var j = 0; j < tokens.length; j++) { + // optimize for forward traversal by shifting for loop index var i = getLookupIndex(j, this._lastFoundIndex, this.tokens.length); token = tokens[i]; @@ -35,6 +51,7 @@ Whitespace.prototype.getNewlinesBefore = function (node) { if (node.start === token.start) { startToken = tokens[i - 1]; endToken = token; + this._lastFoundIndex = i; break; } @@ -50,6 +67,7 @@ Whitespace.prototype.getNewlinesAfter = function (node) { var token; for (var j = 0; j < tokens.length; j++) { + // optimize for forward traversal by shifting for loop index var i = getLookupIndex(j, this._lastFoundIndex, this.tokens.length); token = tokens[i]; @@ -57,6 +75,7 @@ Whitespace.prototype.getNewlinesAfter = function (node) { if (node.end === token.end) { startToken = token; endToken = tokens[i + 1]; + this._lastFoundIndex = i; break; } diff --git a/lib/6to5/types/index.js b/lib/6to5/types/index.js index 1e2f6487d9..75270807bf 100644 --- a/lib/6to5/types/index.js +++ b/lib/6to5/types/index.js @@ -7,9 +7,16 @@ t.NATIVE_TYPE_NAMES = ["Array", "Object", "Number", "Boolean", "Date", "Array", // -function registerType(type, skipAliases) { +/** + * Registers `is[Type]` and `assert[Type]` generated functions for a given `type`. + * Pass `skipAliasCheck` to force it to directly compare `node.type` with `type`. + * + * @param {String} type + * @param {Boolean?} skipAliasCheck + */ +function registerType(type, skipAliasCheck) { var is = t["is" + type] = function (node, opts) { - return t.is(type, node, opts, skipAliases); + return t.is(type, node, opts, skipAliasCheck); }; t["assert" + type] = function (node, opts) { @@ -46,12 +53,23 @@ _.each(t.FLIPPED_ALIAS_KEYS, function (types, type) { registerType(type, false); }); -t.is = function (type, node, opts, skipAliases) { +/** + * Returns whether `node` is of given `type`. + * For better performance, use this instead of `is[Type]` when `type` is unknown. + * Optionally, pass `skipAliasCheck` to directly compare `node.type` with `type`. + * + * @param {String} type + * @param {Node} node + * @param {Object?} opts + * @param {Boolean?} skipAliasCheck + * @returns {Boolean} isOfType + */ +t.is = function (type, node, opts, skipAliasCheck) { if (!node) return; var typeMatches = (type === node.type); - if (!typeMatches && !skipAliases) { + if (!typeMatches && !skipAliasCheck) { var aliases = t.FLIPPED_ALIAS_KEYS[type]; if (typeof aliases !== 'undefined') diff --git a/lib/6to5/util.js b/lib/6to5/util.js index 23d9ec9227..c594f3e653 100644 --- a/lib/6to5/util.js +++ b/lib/6to5/util.js @@ -204,6 +204,24 @@ exports.repeat = function (width, cha) { return result; }; +/** + * Merges a range of integers defined by `start` and `end` into an existing + * array of `ranges`, optimizing for common cases and attempting to keep them + * sorted. Returns the count of integers that have been added to `ranges` during + * the call. + * + * @example + * > var ranges = [{ start: 1, end: 3}, { start: 8, end : 10 }]; + * > mergeIntegerRange(ranges, 6, 9); + * 2 + * > ranges + * [{ start: 1, end: 3}, { start: 6, end : 10 }]; + * + * @param {Array} ranges An array of ranges that function will mutate + * @param {Number} start Beginning of range being added + * @param {Number} end End of range being added + * @returns {Number} pointsAdded How many new integers have been added + */ exports.mergeIntegerRange = function (ranges, start, end) { var pointsAdded = 0; var insertIndex; From a1b326a0abb2f6cc67c66644cf45aa3af1e79ad8 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 15 Jan 2015 03:50:52 +0300 Subject: [PATCH 6/7] Use object lookup instead of array --- lib/6to5/generation/whitespace.js | 13 ++++-- lib/6to5/util.js | 78 ------------------------------- 2 files changed, 10 insertions(+), 81 deletions(-) diff --git a/lib/6to5/generation/whitespace.js b/lib/6to5/generation/whitespace.js index 637b155c3a..aa0993bc2a 100644 --- a/lib/6to5/generation/whitespace.js +++ b/lib/6to5/generation/whitespace.js @@ -1,7 +1,6 @@ module.exports = Whitespace; var _ = require("lodash"); -var util = require("../util"); /** * Returns `i`th number from `base`, continuing from 0 when `max` is reached. @@ -23,7 +22,7 @@ function getLookupIndex(i, base, max) { function Whitespace(tokens, comments) { this.tokens = _.sortBy(tokens.concat(comments), "start"); - this.used = []; + this.used = {}; // Profiling this code shows that while generator passes over it, indexes // returned by `getNewlinesBefore` and `getNewlinesAfter` are always increasing. @@ -97,6 +96,14 @@ Whitespace.prototype.getNewlinesAfter = function (node) { Whitespace.prototype.getNewlinesBetween = function (startToken, endToken) { var start = startToken ? startToken.loc.end.line : 1; var end = endToken.loc.start.line; + var lines = 0; - return util.mergeIntegerRange(this.used, start, end); + for (var line = start; line < end; line++) { + if (typeof this.used[line] === 'undefined') { + this.used[line] = true; + lines++; + } + } + + return lines; }; \ No newline at end of file diff --git a/lib/6to5/util.js b/lib/6to5/util.js index c594f3e653..66af587e4f 100644 --- a/lib/6to5/util.js +++ b/lib/6to5/util.js @@ -204,84 +204,6 @@ exports.repeat = function (width, cha) { return result; }; -/** - * Merges a range of integers defined by `start` and `end` into an existing - * array of `ranges`, optimizing for common cases and attempting to keep them - * sorted. Returns the count of integers that have been added to `ranges` during - * the call. - * - * @example - * > var ranges = [{ start: 1, end: 3}, { start: 8, end : 10 }]; - * > mergeIntegerRange(ranges, 6, 9); - * 2 - * > ranges - * [{ start: 1, end: 3}, { start: 6, end : 10 }]; - * - * @param {Array} ranges An array of ranges that function will mutate - * @param {Number} start Beginning of range being added - * @param {Number} end End of range being added - * @returns {Number} pointsAdded How many new integers have been added - */ -exports.mergeIntegerRange = function (ranges, start, end) { - var pointsAdded = 0; - var insertIndex; - var rangeIndex; - var point; - var range; - var matchingRange; - var rangeImmediatelyToLeft; - var rangeImmediatelyToRight; - - for (point = start; point < end; point++) { - matchingRange = null; - rangeImmediatelyToLeft = null; - rangeImmediatelyToRight = null; - insertIndex = ranges.length; - - for (rangeIndex = 0; rangeIndex < ranges.length; rangeIndex++) { - range = ranges[rangeIndex]; - - if (point >= range.start && point <= range.end) { - matchingRange = range; - break; - } - - if (point < range.start) { - insertIndex = rangeIndex; - } - - if (point === range.end + 1) { - rangeImmediatelyToLeft = range; - } - - if (point === range.start - 1) { - rangeImmediatelyToRight = range; - } - } - - if (matchingRange) - continue; - - pointsAdded++; - - if (rangeImmediatelyToLeft && rangeImmediatelyToRight) { - ranges.splice(ranges.indexOf(rangeImmediatelyToRight), 1); - rangeImmediatelyToLeft.end = rangeImmediatelyToRight.end; - } else if (rangeImmediatelyToLeft) { - rangeImmediatelyToLeft.end = point; - } else if (rangeImmediatelyToRight) { - rangeImmediatelyToRight.start = point; - } else { - ranges.splice(insertIndex, 0, { - start: point, - end: point - }); - } - } - - return pointsAdded; -}; - exports.normaliseAst = function (ast, comments, tokens) { if (ast && ast.type === "Program") { return t.file(ast, comments || [], tokens || []); From 1002cf7796b066e58b214bb2c7d572b62bd5ddf5 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 15 Jan 2015 04:18:18 +0300 Subject: [PATCH 7/7] Avoid Node allocations by making prototype call statics --- lib/6to5/generation/node/index.js | 43 +++++++++++++------------------ 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/lib/6to5/generation/node/index.js b/lib/6to5/generation/node/index.js index bedd7ba60a..179b3015d7 100644 --- a/lib/6to5/generation/node/index.js +++ b/lib/6to5/generation/node/index.js @@ -28,13 +28,11 @@ function Node(node, parent) { this.node = node; } -Node.prototype.isUserWhitespacable = function () { - return t.isUserWhitespacable(this.node); +Node.isUserWhitespacable = function (node) { + return t.isUserWhitespacable(node); }; -Node.prototype.needsWhitespace = function (type) { - var parent = this.parent; - var node = this.node; +Node.needsWhitespace = function (node, parent, type) { if (!node) return 0; if (t.isExpressionStatement(node)) { @@ -51,18 +49,15 @@ Node.prototype.needsWhitespace = function (type) { return lines || 0; }; -Node.prototype.needsWhitespaceBefore = function () { - return this.needsWhitespace("before"); +Node.needsWhitespaceBefore = function (node, parent) { + return Node.needsWhitespace(node, parent, "before"); }; -Node.prototype.needsWhitespaceAfter = function () { - return this.needsWhitespace("after"); +Node.needsWhitespaceAfter = function (node, parent) { + return Node.needsWhitespace(node, parent, "after"); }; -Node.prototype.needsParens = function () { - var parent = this.parent; - var node = this.node; - +Node.needsParens = function (node, parent) { if (!parent) return false; if (t.isNewExpression(parent) && parent.callee === node) { @@ -77,10 +72,7 @@ Node.prototype.needsParens = function () { return find(parens, node, parent); }; -Node.prototype.needsParensNoLineTerminator = function () { - var parent = this.parent; - var node = this.node; - +Node.needsParensNoLineTerminator = function (node, parent) { if (!parent) return false; // no comments @@ -100,17 +92,18 @@ Node.prototype.needsParensNoLineTerminator = function () { return false; }; -_.each(Node.prototype, function (fn, key) { - Node[key] = function (node, parent) { - var n = new Node(node, parent); - +_.each(Node, function (fn, key) { + Node.prototype[key] = function () { // Avoid leaking arguments to prevent deoptimization - var skipCount = 2; - var args = new Array(arguments.length - skipCount); + var args = new Array(arguments.length + 2); + + args[0] = this.node; + args[1] = this.parent; + for (var i = 0; i < args.length; i++) { - args[i] = arguments[i + 2]; + args[i + 2] = arguments[i]; } - return n[key].apply(n, args); + return Node[key].apply(null, args); }; });