From a452f781b80ac1a239b6d0d4ed527fc6733d1b9f Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 15 Jan 2015 03:38:46 +0300 Subject: [PATCH] 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;