improve whitespace handling of code generator, reduce the use of lookaheads, add max newlines of 2 and better newline insertion for generated nodes

This commit is contained in:
Sebastian McKenzie 2015-02-20 11:35:27 +11:00
parent fbb19fc656
commit 710ff548cb
6 changed files with 144 additions and 84 deletions

View File

@ -13,13 +13,7 @@ function Buffer(position, format, opts, code) {
this.position = position; this.position = position;
this._indent = format.indent.base; this._indent = format.indent.base;
this.format = format; this.format = format;
this.deopt = false;
this.buf = ""; this.buf = "";
if (code.length > 100000) { // 100KB
this.deopt = true;
console.error(messages.get("codeGeneratorDeopt", opts.filename, "100KB"));
}
} }
Buffer.prototype.get = function () { Buffer.prototype.get = function () {
@ -27,7 +21,7 @@ Buffer.prototype.get = function () {
}; };
Buffer.prototype.getIndent = function () { Buffer.prototype.getIndent = function () {
if (this.deopt || this.format.concise) { if (this.format.compact || this.format.concise) {
return ""; return "";
} else { } else {
return repeating(this.format.indent.style, this._indent); return repeating(this.format.indent.style, this._indent);
@ -65,14 +59,14 @@ Buffer.prototype.keyword = function (name) {
}; };
Buffer.prototype.space = function () { Buffer.prototype.space = function () {
if (this.deopt) return; if (this.format.compact) return;
if (this.buf && !this.isLast(" ") && !this.isLast("\n")) { if (this.buf && !this.isLast(" ") && !this.isLast("\n")) {
this.push(" "); this.push(" ");
} }
}; };
Buffer.prototype.removeLast = function (cha) { Buffer.prototype.removeLast = function (cha) {
if (this.deopt) return; if (this.format.compact) return;
if (!this.isLast(cha)) return; if (!this.isLast(cha)) return;
this.buf = this.buf.substr(0, this.buf.length - 1); 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) { Buffer.prototype.newline = function (i, removeLast) {
if (this.deopt) return; if (this.format.compact) return;
if (this.format.concise) { if (this.format.concise) {
this.space(); this.space();
@ -90,7 +84,10 @@ Buffer.prototype.newline = function (i, removeLast) {
removeLast = removeLast || false; removeLast = removeLast || false;
if (isNumber(i)) { 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) { while (i > 0) {
this._newline(removeLast); this._newline(removeLast);
@ -107,6 +104,10 @@ Buffer.prototype.newline = function (i, removeLast) {
}; };
Buffer.prototype._newline = function (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"); if (removeLast && this.isLast("\n")) this.removeLast("\n");
this.removeLast(" "); this.removeLast(" ");
@ -114,7 +115,7 @@ Buffer.prototype._newline = function (removeLast) {
}; };
Buffer.prototype.push = function (str, noIndent) { 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 // we have an indent level and we aren't pushing a newline
var indent = this.getIndent(); var indent = this.getIndent();
@ -133,8 +134,12 @@ Buffer.prototype._push = function (str) {
this.buf += str; this.buf += str;
}; };
Buffer.prototype.endsWith = function (str) {
return this.buf.slice(-str.length) === str;
};
Buffer.prototype.isLast = function (cha) { Buffer.prototype.isLast = function (cha) {
if (this.deopt) return false; if (this.format.compact) return false;
var buf = this.buf; var buf = this.buf;
var last = buf[buf.length - 1]; var last = buf[buf.length - 1];

View File

@ -62,6 +62,10 @@ exports.ImportDeclaration = function (node, print) {
this.push("import "); this.push("import ");
if (node.isType) {
this.push("type ");
}
var specfiers = node.specifiers; var specfiers = node.specifiers;
if (specfiers && specfiers.length) { if (specfiers && specfiers.length) {
var foundImportSpecifier = false; var foundImportSpecifier = false;

View File

@ -184,7 +184,7 @@ exports.VariableDeclaration = function (node, print, parent) {
} }
var sep = ","; var sep = ",";
if (!this.buffer.deopt && hasInits) { if (!this.format.compact && hasInits) {
sep += "\n" + repeating(" ", node.kind.length + 1); sep += "\n" + repeating(" ", node.kind.length + 1);
} else { } else {
sep += " "; sep += " ";

View File

@ -47,16 +47,28 @@ CodeGenerator.normalizeOptions = function (code, opts) {
if (indent && indent !== " ") style = indent; if (indent && indent !== " ") style = indent;
} }
return merge({ var format = merge({
parentheses: true, parentheses: true,
comments: opts.comments == null || opts.comments, comments: opts.comments == null || opts.comments,
concise: false, concise: false,
compact: "auto",
indent: { indent: {
adjustMultilineComment: true, adjustMultilineComment: true,
style: style, style: style,
base: 0 base: 0
} }
}, opts.format || {}); }, 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 = { CodeGenerator.generators = {
@ -165,10 +177,10 @@ CodeGenerator.prototype.print = function (node, parent, opts) {
var needs = n.needsWhitespaceAfter; var needs = n.needsWhitespaceAfter;
if (leading) needs = n.needsWhitespaceBefore; if (leading) needs = n.needsWhitespaceBefore;
lines += needs(node, parent); if (needs(node, parent)) lines++;
// generated nodes can't add starting file whitespace // generated nodes can't add starting file whitespace
if (!self.buffer.get()) lines = 0; if (!self.buffer.buf) lines = 0;
} }
self.newline(lines); self.newline(lines);
@ -296,7 +308,7 @@ CodeGenerator.prototype._getComments = function (key, node) {
}; };
CodeGenerator.prototype._printComments = function (comments) { CodeGenerator.prototype._printComments = function (comments) {
if (this.buffer.deopt) return; if (this.format.compact) return;
if (!this.format.comments) return; if (!this.format.comments) return;
if (!comments || !comments.length) return; if (!comments || !comments.length) return;

View File

@ -4,9 +4,9 @@ module.exports = Node;
var whitespace = require("./whitespace"); var whitespace = require("./whitespace");
var parens = require("./parentheses"); var parens = require("./parentheses");
var t = require("../../types");
var each = require("lodash/collection/each"); var each = require("lodash/collection/each");
var some = require("lodash/collection/some"); var some = require("lodash/collection/some");
var t = require("../../types");
var find = function (obj, node, parent) { var find = function (obj, node, parent) {
if (!obj) return; if (!obj) return;
@ -42,14 +42,19 @@ Node.needsWhitespace = function (node, parent, type) {
node = node.expression; node = node.expression;
} }
var lines = find(whitespace[type].nodes, node, parent); var linesInfo = find(whitespace.nodes, node, parent);
if (lines) return lines;
each(find(whitespace[type].list, node, parent), function (expr) { if (!linesInfo) {
lines = Node.needsWhitespace(expr, node, type); var items = find(whitespace.list, node, parent);
if (lines) return false; if (items) {
}); for (var i = 0; i < items.length; i++) {
return lines || 0; linesInfo = Node.needsWhitespace(items[i], node, type);
if (linesInfo) break;
}
}
}
return (linesInfo && linesInfo[type]) || 0;
}; };
Node.needsWhitespaceBefore = function (node, parent) { Node.needsWhitespaceBefore = function (node, parent) {

View File

@ -1,84 +1,118 @@
"use strict"; "use strict";
var t = require("../../types"); var isBoolean = require("lodash/lang/isBoolean");
var each = require("lodash/collection/each"); var each = require("lodash/collection/each");
var map = require("lodash/collection/map"); var map = require("lodash/collection/map");
var isNumber = require("lodash/lang/isNumber"); var t = require("../../types");
exports.before = { var shouldWhitespace = function (node) {
nodes: { if (t.isFunction(node)) {
Property: function (node, parent) { return true;
if (parent.properties[0] === node) { } else if (t.isAssignmentExpression(node)) {
return 1; return shouldWhitespace(node.right);
} } else if (t.isBinary(node)) {
}, return shouldWhitespace(node.left) || shouldWhitespace(node.right);
}
SpreadProperty: function (node, parent) { return false;
return exports.before.nodes.Property(node, parent); };
},
SwitchCase: function (node, parent) { exports.nodes = {
if (parent.cases[0] === node) { AssignmentExpression: function (node) {
return 1; if (shouldWhitespace(node.right)) {
} return {
}, before: true,
after: true
};
}
},
CallExpression: function (node) { SwitchCase: function (node, parent) {
if (t.isFunction(node.callee)) { if (parent.cases[0] === node) {
return 1; 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 = { exports.nodes.Property =
nodes: { exports.nodes.SpreadProperty = function (node, parent) {
LogicalExpression: function (node) { if (parent.properties[0] === node) {
return t.isFunction(node.left) || t.isFunction(node.right); return {
}, before: true
};
}
};
AssignmentExpression: function (node) { exports.list = {
if (t.isFunction(node.right)) { VariableDeclaration: function (node) {
return 1; return map(node.declarations, "init");
}
}
}, },
list: { ArrayExpression: function (node) {
VariableDeclaration: function (node) { return node.elements;
return map(node.declarations, "init"); },
},
ArrayExpression: function (node) { ObjectExpression: function (node) {
return node.elements; return node.properties;
},
ObjectExpression: function (node) {
return node.properties;
}
} }
}; };
each({ each({
Function: 1, Function: true,
Class: 1, Class: true,
For: 1, Loop: true,
ArrayExpression: { after: 1 }, LabeledStatement: true,
ObjectExpression: { after: 1 }, SwitchStatement: true,
SwitchStatement: 1, TryStatement: true,
IfStatement: { before: 1 }, IfStatement: true
CallExpression: { after: 1 },
Literal: { after: 1 }
}, function (amounts, type) { }, function (amounts, type) {
if (isNumber(amounts)) { if (isBoolean(amounts)) {
amounts = { after: amounts, before: amounts }; amounts = { after: amounts, before: amounts };
} }
each([type].concat(t.FLIPPED_ALIAS_KEYS[type] || []), function (type) { each([type].concat(t.FLIPPED_ALIAS_KEYS[type] || []), function (type) {
each(amounts, function (amount, key) { exports.nodes[type] = function () {
exports[key].nodes[type] = function () { return amounts;
return amount; };
};
});
}); });
}); });