From 710ff548cb19d7fbf8caf2faf0e2553f23fdacab Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Fri, 20 Feb 2015 11:35:27 +1100 Subject: [PATCH] improve whitespace handling of code generator, reduce the use of lookaheads, add max newlines of 2 and better newline insertion for generated nodes --- lib/babel/generation/buffer.js | 31 ++-- lib/babel/generation/generators/modules.js | 4 + lib/babel/generation/generators/statements.js | 2 +- lib/babel/generation/index.js | 20 ++- lib/babel/generation/node/index.js | 21 ++- lib/babel/generation/node/whitespace.js | 150 +++++++++++------- 6 files changed, 144 insertions(+), 84 deletions(-) diff --git a/lib/babel/generation/buffer.js b/lib/babel/generation/buffer.js index 7081d8f2ff..67ea4fdc63 100644 --- a/lib/babel/generation/buffer.js +++ b/lib/babel/generation/buffer.js @@ -13,13 +13,7 @@ function Buffer(position, format, opts, code) { this.position = position; this._indent = format.indent.base; this.format = format; - this.deopt = false; this.buf = ""; - - if (code.length > 100000) { // 100KB - this.deopt = true; - console.error(messages.get("codeGeneratorDeopt", opts.filename, "100KB")); - } } Buffer.prototype.get = function () { @@ -27,7 +21,7 @@ Buffer.prototype.get = function () { }; Buffer.prototype.getIndent = function () { - if (this.deopt || this.format.concise) { + if (this.format.compact || this.format.concise) { return ""; } else { return repeating(this.format.indent.style, this._indent); @@ -65,14 +59,14 @@ Buffer.prototype.keyword = function (name) { }; Buffer.prototype.space = function () { - if (this.deopt) return; + if (this.format.compact) return; if (this.buf && !this.isLast(" ") && !this.isLast("\n")) { this.push(" "); } }; Buffer.prototype.removeLast = function (cha) { - if (this.deopt) return; + if (this.format.compact) return; if (!this.isLast(cha)) return; this.buf = this.buf.substr(0, this.buf.length - 1); @@ -80,7 +74,7 @@ Buffer.prototype.removeLast = function (cha) { }; Buffer.prototype.newline = function (i, removeLast) { - if (this.deopt) return; + if (this.format.compact) return; if (this.format.concise) { this.space(); @@ -90,7 +84,10 @@ Buffer.prototype.newline = function (i, removeLast) { removeLast = removeLast || false; if (isNumber(i)) { - if (this.isLast("\n")) i--; + i = Math.min(2, i); + + if (this.endsWith("{\n")) i--; + if (i <= 0) return; while (i > 0) { this._newline(removeLast); @@ -107,6 +104,10 @@ Buffer.prototype.newline = function (i, removeLast) { }; Buffer.prototype._newline = function (removeLast) { + // never allow more than two lines + if (this.endsWith("\n\n")) return; + + // remove the last newline if (removeLast && this.isLast("\n")) this.removeLast("\n"); this.removeLast(" "); @@ -114,7 +115,7 @@ Buffer.prototype._newline = function (removeLast) { }; Buffer.prototype.push = function (str, noIndent) { - if (!this.deopt && this._indent && !noIndent && str !== "\n") { + if (!this.format.compact && this._indent && !noIndent && str !== "\n") { // we have an indent level and we aren't pushing a newline var indent = this.getIndent(); @@ -133,8 +134,12 @@ Buffer.prototype._push = function (str) { this.buf += str; }; +Buffer.prototype.endsWith = function (str) { + return this.buf.slice(-str.length) === str; +}; + Buffer.prototype.isLast = function (cha) { - if (this.deopt) return false; + if (this.format.compact) return false; var buf = this.buf; var last = buf[buf.length - 1]; diff --git a/lib/babel/generation/generators/modules.js b/lib/babel/generation/generators/modules.js index 9b8aa4ef36..a7c53d4b3c 100644 --- a/lib/babel/generation/generators/modules.js +++ b/lib/babel/generation/generators/modules.js @@ -62,6 +62,10 @@ exports.ImportDeclaration = function (node, print) { this.push("import "); + if (node.isType) { + this.push("type "); + } + var specfiers = node.specifiers; if (specfiers && specfiers.length) { var foundImportSpecifier = false; diff --git a/lib/babel/generation/generators/statements.js b/lib/babel/generation/generators/statements.js index b8132c5e80..f9a75d656e 100644 --- a/lib/babel/generation/generators/statements.js +++ b/lib/babel/generation/generators/statements.js @@ -184,7 +184,7 @@ exports.VariableDeclaration = function (node, print, parent) { } var sep = ","; - if (!this.buffer.deopt && hasInits) { + if (!this.format.compact && hasInits) { sep += "\n" + repeating(" ", node.kind.length + 1); } else { sep += " "; diff --git a/lib/babel/generation/index.js b/lib/babel/generation/index.js index aca6bfbf55..c27fd5ee93 100644 --- a/lib/babel/generation/index.js +++ b/lib/babel/generation/index.js @@ -47,16 +47,28 @@ CodeGenerator.normalizeOptions = function (code, opts) { if (indent && indent !== " ") style = indent; } - return merge({ + var format = merge({ parentheses: true, comments: opts.comments == null || opts.comments, concise: false, + compact: "auto", indent: { adjustMultilineComment: true, style: style, base: 0 } }, opts.format || {}); + + if (format.compact === "auto") { + format.compact = code.length > 100000; // 100KB + + if (format.compact) { + format.compact = true; + console.error(messages.get("codeGeneratorDeopt", opts.filename, "100KB")); + } + } + + return format; }; CodeGenerator.generators = { @@ -165,10 +177,10 @@ CodeGenerator.prototype.print = function (node, parent, opts) { var needs = n.needsWhitespaceAfter; if (leading) needs = n.needsWhitespaceBefore; - lines += needs(node, parent); + if (needs(node, parent)) lines++; // generated nodes can't add starting file whitespace - if (!self.buffer.get()) lines = 0; + if (!self.buffer.buf) lines = 0; } self.newline(lines); @@ -296,7 +308,7 @@ CodeGenerator.prototype._getComments = function (key, node) { }; CodeGenerator.prototype._printComments = function (comments) { - if (this.buffer.deopt) return; + if (this.format.compact) return; if (!this.format.comments) return; if (!comments || !comments.length) return; diff --git a/lib/babel/generation/node/index.js b/lib/babel/generation/node/index.js index 0ded23714b..9ff5d164c6 100644 --- a/lib/babel/generation/node/index.js +++ b/lib/babel/generation/node/index.js @@ -4,9 +4,9 @@ module.exports = Node; var whitespace = require("./whitespace"); var parens = require("./parentheses"); -var t = require("../../types"); var each = require("lodash/collection/each"); var some = require("lodash/collection/some"); +var t = require("../../types"); var find = function (obj, node, parent) { if (!obj) return; @@ -42,14 +42,19 @@ Node.needsWhitespace = function (node, parent, type) { node = node.expression; } - var lines = find(whitespace[type].nodes, node, parent); - if (lines) return lines; + var linesInfo = find(whitespace.nodes, node, parent); - each(find(whitespace[type].list, node, parent), function (expr) { - lines = Node.needsWhitespace(expr, node, type); - if (lines) return false; - }); - return lines || 0; + if (!linesInfo) { + var items = find(whitespace.list, node, parent); + if (items) { + for (var i = 0; i < items.length; i++) { + linesInfo = Node.needsWhitespace(items[i], node, type); + if (linesInfo) break; + } + } + } + + return (linesInfo && linesInfo[type]) || 0; }; Node.needsWhitespaceBefore = function (node, parent) { diff --git a/lib/babel/generation/node/whitespace.js b/lib/babel/generation/node/whitespace.js index f0f0740a83..e0362d5144 100644 --- a/lib/babel/generation/node/whitespace.js +++ b/lib/babel/generation/node/whitespace.js @@ -1,84 +1,118 @@ "use strict"; -var t = require("../../types"); -var each = require("lodash/collection/each"); -var map = require("lodash/collection/map"); -var isNumber = require("lodash/lang/isNumber"); +var isBoolean = require("lodash/lang/isBoolean"); +var each = require("lodash/collection/each"); +var map = require("lodash/collection/map"); +var t = require("../../types"); -exports.before = { - nodes: { - Property: function (node, parent) { - if (parent.properties[0] === node) { - return 1; - } - }, +var shouldWhitespace = function (node) { + if (t.isFunction(node)) { + return true; + } else if (t.isAssignmentExpression(node)) { + return shouldWhitespace(node.right); + } else if (t.isBinary(node)) { + return shouldWhitespace(node.left) || shouldWhitespace(node.right); + } - SpreadProperty: function (node, parent) { - return exports.before.nodes.Property(node, parent); - }, + return false; +}; - SwitchCase: function (node, parent) { - if (parent.cases[0] === node) { - return 1; - } - }, +exports.nodes = { + AssignmentExpression: function (node) { + if (shouldWhitespace(node.right)) { + return { + before: true, + after: true + }; + } + }, - CallExpression: function (node) { - if (t.isFunction(node.callee)) { - return 1; + SwitchCase: function (node, parent) { + if (parent.cases[0] === node) { + return { + before: true + }; + } + }, + + LogicalExpression: function (node) { + if (t.isFunction(node.left) || t.isFunction(node.right)) { + return { + after: true + }; + } + }, + + Literal: function (node) { + if (node.value === "use strict") { + return { + after: true + }; + } + }, + + CallExpression: function (node) { + if (t.isFunction(node.callee)) { + return { + before: true, + after: true + }; + } + }, + + VariableDeclaration: function (node) { + for (var i = 0; i < node.declarations.length; i++) { + var declar = node.declarations[i]; + var init = declar.init; + if (!t.isIdentifier(init) && shouldWhitespace(init)) { + return { + before: true, + after: true + }; } } } }; -exports.after = { - nodes: { - LogicalExpression: function (node) { - return t.isFunction(node.left) || t.isFunction(node.right); - }, +exports.nodes.Property = +exports.nodes.SpreadProperty = function (node, parent) { + if (parent.properties[0] === node) { + return { + before: true + }; + } +}; - AssignmentExpression: function (node) { - if (t.isFunction(node.right)) { - return 1; - } - } +exports.list = { + VariableDeclaration: function (node) { + return map(node.declarations, "init"); }, - list: { - VariableDeclaration: function (node) { - return map(node.declarations, "init"); - }, + ArrayExpression: function (node) { + return node.elements; + }, - ArrayExpression: function (node) { - return node.elements; - }, - - ObjectExpression: function (node) { - return node.properties; - } + ObjectExpression: function (node) { + return node.properties; } }; each({ - Function: 1, - Class: 1, - For: 1, - ArrayExpression: { after: 1 }, - ObjectExpression: { after: 1 }, - SwitchStatement: 1, - IfStatement: { before: 1 }, - CallExpression: { after: 1 }, - Literal: { after: 1 } + Function: true, + Class: true, + Loop: true, + LabeledStatement: true, + SwitchStatement: true, + TryStatement: true, + IfStatement: true }, function (amounts, type) { - if (isNumber(amounts)) { + if (isBoolean(amounts)) { amounts = { after: amounts, before: amounts }; } each([type].concat(t.FLIPPED_ALIAS_KEYS[type] || []), function (type) { - each(amounts, function (amount, key) { - exports[key].nodes[type] = function () { - return amount; - }; - }); + exports.nodes[type] = function () { + return amounts; + }; }); });