From b31f3666c8c5b7d5be18a655a5fca420548430dd Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Wed, 29 Oct 2014 20:51:52 +1100 Subject: [PATCH] yank out acorn-recast and replace it with our own code generator --- lib/6to5/file.js | 17 +- lib/6to5/generator.js | 630 +++++++++++++++++++++++++++++ lib/6to5/transform.js | 29 +- lib/6to5/transformers/_comments.js | 6 + lib/6to5/util.js | 27 +- 5 files changed, 687 insertions(+), 22 deletions(-) create mode 100644 lib/6to5/generator.js create mode 100644 lib/6to5/transformers/_comments.js diff --git a/lib/6to5/file.js b/lib/6to5/file.js index de444a6817..e0d1cdc41d 100644 --- a/lib/6to5/file.js +++ b/lib/6to5/file.js @@ -3,7 +3,7 @@ module.exports = File; var SHEBANG_REGEX = /^\#\!.*/; var transform = require("./transform"); -var recast = require("acorn-recast"); +var generate = require("./generator"); var util = require("./util"); var b = require("acorn-ast-types").builders; var _ = require("lodash"); @@ -103,29 +103,24 @@ File.prototype.generate = function () { var opts = this.opts; var ast = this.ast; - var printOpts = { tabWidth: 2 }; + var printOpts = {}; if (opts.sourceMap) { printOpts.sourceMapName = opts.sourceMapName; } - var result = recast.print(ast, printOpts); - var code = result.code; + var result = generate(ast, printOpts); if (this.shebang) { // add back shebang - code = this.shebang + code; + result.code = this.shebang + result.code; } if (opts.sourceMap === "inline") { - code += "\n" + util.sourceMapToComment(result.map); + result.code += "\n" + util.sourceMapToComment(result.map); } - return { - code: code, - map: result.map || null, - ast: ast - }; + return result; }; File.prototype.generateUid = function (name) { diff --git a/lib/6to5/generator.js b/lib/6to5/generator.js new file mode 100644 index 0000000000..3b303bc47b --- /dev/null +++ b/lib/6to5/generator.js @@ -0,0 +1,630 @@ +module.exports = function (ast, opts) { + var gen = new CodeGenerator; + return gen.generate(ast, opts); +}; + +var _ = require("lodash"); + +function CodeGenerator() { + this.indent = this.indent.bind(this); + this.print = this.print.bind(this); +} + +CodeGenerator.prototype.generate = function (ast, opts) { + opts = opts || {}; + return { + map: null, + ast: ast, + code: this.print(ast) + }; +}; + +CodeGenerator.prototype.buildPrint = function (parent) { + var self = this; + var print = function (node) { + return self.print(node, parent); + }; + + print.sequence = function (nodes) { + return self.printSequence(nodes, print); + }; + + return print; +}; + +CodeGenerator.prototype.print = function (node, parent) { + if (!node) return ""; + + if (this[node.type]) { + return this[node.type](node, this.buildPrint(node), parent); + } else { + throw new ReferenceError("unknown node " + node.type + " " + JSON.stringify(node)); + } +}; + +CodeGenerator.prototype.printSequence = function (nodes, print) { + return nodes.map(print).join("\n"); +}; + +CodeGenerator.prototype.removeEmptyExpressions = function (nodes) { + return nodes.filter(function (node) { + if (node.type === "EmptyStatement") { + return false; + } else { + return true; + } + }); +}; + +CodeGenerator.prototype.indent = function (str) { + return str.split("\n").map(function (line) { + return " " + line; + }).join("\n"); +}; + +CodeGenerator.prototype.File = function (node, print) { + return print(node.program); +}; + +CodeGenerator.prototype.Program = function (node, print) { + return print.sequence(node.body); +}; + +CodeGenerator.prototype.EmptyStatement = function (node, print) { + return ""; +}; + +CodeGenerator.prototype.ExpressionStatement = function (node, print) { + return print(node.expression) + ";"; +}; + +CodeGenerator.prototype.BinaryExpression = +CodeGenerator.prototype.LogicalExpression = +CodeGenerator.prototype.AssignmentExpression = function (node, print) { + return print(node.left) + " " + node.operator + " " + print(node.right); +}; + +CodeGenerator.prototype.MemberExpression = function (node, print) { + var code = this._maybeParans(node.object, print); + + if (node.computed) { + code += "[" + print(node.property) + "]"; + } else { + code += "." + print(node.property); + } + + return code; +}; + +CodeGenerator.prototype.Path = function (node, print) { + return "." + print(node.body); +}; + +CodeGenerator.prototype.Identifier = function (node, print) { + return node.name; +}; + +CodeGenerator.prototype.SpreadElement = +CodeGenerator.prototype.SpreadElementPattern = +CodeGenerator.prototype.SpreadProperty = +CodeGenerator.prototype.SpreadPropertyPattern = function (node, print) { + return "..." + print(node.argument); +}; + +CodeGenerator.prototype.FunctionDeclaration = +CodeGenerator.prototype.FunctionExpression = function (node, print) { + var code = ""; + if (node.async) code += "async "; + code += "function"; + if (node.generator) code += "*"; + if (node.id) code += " " + print(node.id); + code += "(" + node.params.map(print).join(", ") + ")"; + code += " " + print(node.body); + return code; +}; + +CodeGenerator.prototype.ArrowFunctionExpression = function (node, print) { + var code = ""; + if (node.async) code += "async "; + if (node.params.length === 1) { + code += print(node.params[0]); + } else { + code += "(" + node.params.map(this.buildPrint(node)).join(", ") + ")"; + } + code += " => "; + code += print(node.body); + return code; +}; + +CodeGenerator.prototype.MethodDefinition = function (node, print) { + throw new Error("MethodDefinition"); + return; +}; + +CodeGenerator.prototype.YieldExpression = function (node, print) { + var code = "yield"; + if (node.delegate) code += "*"; + if (node.argument) code += " " + print(node.argument); + return code; +}; + +CodeGenerator.prototype.AwaitExpression = function (node, print) { + var code = "await"; + if (node.all) code += "*"; + if (node.argument) code += print(node.argument); + return code; +}; + +CodeGenerator.prototype.ModuleDeclaration = function (node, print) { + var code = "module " + print(node.id); + if (node.source) { + code += " from " + print(node.source); + } else { + code += print(node.body); + } + return code; +}; + +CodeGenerator.prototype.ImportSpecifier = +CodeGenerator.prototype.ExportSpecifier = function (node, print) { + var code = print(node.id); + if (node.name) code += " as " + print(node.name); + return code; +}; + +CodeGenerator.prototype.ExportBatchSpecifier = function (node, print) { + return "*"; +}; + +CodeGenerator.prototype.ExportDeclaration = function (node, print) { + throw new Error("ExportDeclaration"); +}; + +CodeGenerator.prototype.ImportDeclaration = function (node, print) { + throw new Error("ImportDeclaration"); +}; + +CodeGenerator.prototype.BlockStatement = function (node, print) { + var body = this.removeEmptyExpressions(node.body); + if (body.length === 0) { + return "{}"; + } else { + return "{\n" + this.indent(print.sequence(body)) + "\n}" + } +}; + +CodeGenerator.prototype.ReturnStatement = function (node, print) { + var code = "return"; + if (node.argument) { + code += " " + print(node.argument); + } + code += ";"; + return code; +}; + +CodeGenerator.prototype._maybeParans = function (node, print) { + var code = print(node); + if (node.type === "AssignmentExpression" || + node.type === "FunctionExpression" || + node.type === "BinaryExpression") { + code = "(" + code + ")"; + } + return code; +}; + +CodeGenerator.prototype.CallExpression = function (node, print) { + var code = ""; + code += this._maybeParans(node.callee, print); + code += "(" + node.arguments.map(this.buildPrint(node)).join(", ") + ")"; + return code; +}; + +CodeGenerator.prototype.ObjectExpression = +CodeGenerator.prototype.ObjectPattern = function (node, print) { + var allowBreak = false; + var indent = this.indent; + var print = this.buildPrint(node); + var parts = [len > 0 ? "{\n" : "{"]; + var len = node.properties.length; + + _.each(node.properties, function (prop, i) { + var lines = indent(print(prop)); + + var multiLine = lines.length > 1; + if (multiLine && allowBreak) { + // Similar to the logic for BlockStatement. + parts.push("\n"); + } + + parts.push(lines); + + if (i < len - 1) { + // Add an extra line break if the previous object property + // had a multi-line value. + parts.push(multiLine ? ",\n\n" : ",\n"); + allowBreak = !multiLine; + } + }); + + parts.push(len > 0 ? "\n}" : "}"); + + return parts.join("\n"); +}; + +CodeGenerator.prototype.PropertyPattern = function (node, print) { + return print(node.key) + ": " + print(node.pattern); +}; + +CodeGenerator.prototype.Property = function (node, print) { + if (node.method || node.kind === "get" || node.kind === "set") { + throw new Error("Property"); + } else { + return print(node.key) + ": " + print(node.value); + } +}; + +CodeGenerator.prototype.ArrayExpression = +CodeGenerator.prototype.ArrayPattern = function (node, print) { + var elems = node.elements; + var parts = ["["]; + var print = this.buildPrint(node); + var len = elems.length; + + _.each(elems, function(elem, i) { + if (!elem) { + // If the array expression ends with a hole, that hole + // will be ignored by the interpreter, but if it ends with + // two (or more) holes, we need to write out two (or more) + // commas so that the resulting code is interpreted with + // both (all) of the holes. + parts.push(","); + } else { + if (i > 0) parts.push(" "); + parts.push(print(elem)); + if (i < len - 1) parts.push(","); + } + }); + + parts.push("]"); + + return parts.join(""); +}; + +CodeGenerator.prototype.SequenceExpression = function (node, print) { + return node.expressions.map(print).join(", "); +}; + +CodeGenerator.prototype.ThisExpression = function (node, print) { + return "this"; +}; + +CodeGenerator.prototype.Literal = function (node, print) { + var val = node.value; + var type = typeof val; + + if (type === "string" || type === "number" || type === "boolean") { + return JSON.stringify(val); + } + + if (node.regex) { + return "/" + node.regex.pattern + "/" + node.regex.flags; + } + + if (val === null) { + return "null"; + } + + if (node.raw) { + return node.raw; + } +}; + +CodeGenerator.prototype.ModuleSpecifier = function (node, print) { + return "\"" + node.value + "\""; +}; + +CodeGenerator.prototype.UnaryExpression = function (node, print) { + var code = node.operator; + if (/[a-z]$/.test(node.operator)) code += " "; + code += this._maybeParans(node.argument, print); + return code; +}; + +CodeGenerator.prototype.UpdateExpression = function (node, print) { + var parts = [print(node.argument)]; + parts.push(node.operator); + if (node.prefix) parts.reverse(); + return parts.join(""); +}; + +CodeGenerator.prototype.ConditionalExpression = function (node, print) { + var code = "("; + code += print(node.test); + code += " ? "; + code += print(node.consequent); + code += " : "; + code += print(node.alternate); + code += ")"; + return code; +}; + +CodeGenerator.prototype.NewExpression = function (node, print) { + var code = "new "; + code += print(node.callee); + if (node.arguments) { + code += "(" + node.arguments.map(print).join(", ") + ")"; + } + return code; +}; + +CodeGenerator.prototype.VariableDeclaration = function (node, print, parent) { + var print = print; + var code = node.kind + " "; + var maxLen = 0; + + var printed = node.declarations.map(function (declar) { + var lines = print(declar); + maxLen = Math.max(maxLen, lines.length); + return lines; + }); + + switch (maxLen) { + case 0: + code += printed[0]; + + default: + code += printed.join(",\n "); + break; + } + + if ( + parent.type !== "ForStatement" && + parent.type !== "ForInStatement" && + parent.type !== "ForOfStatement" && + parent.type !== "ForOfStatement" + ) { + code += ";"; + } + return code; +}; + +CodeGenerator.prototype.VariableDeclarator = function (node, print) { + if (node.init) { + return print(node.id) + " = " + print(node.init); + } else { + return print(node.id); + } +}; + +CodeGenerator.prototype.WithStatement = function (node, print) { + return "with (" + print(node.object) + ") " + print(node.body); +}; + +CodeGenerator.prototype.IfStatement = function (node, print) { + var code = "if (" + print(node.test) + ") "; + code += print(node.consequent); + return code; +}; + +CodeGenerator.prototype.ForStatement = function (node, print) { + var code = "for ("; + code += print(node.init) + "; "; + code += print(node.test) + "; "; + code += print(node.update) + code += ") "; + code += print(node.body); + return code; +}; + +CodeGenerator.prototype.WhileStatement = function (node, print) { + return "while (" + print(node.test) + ") " + print(node.body); +}; + +CodeGenerator.prototype.ForInStatement = function (node, print) { + var code = node.each ? "for each (" : "for ("; + code += print(node.left); + code += " in "; + code += print(node.right); + code += ") "; + code += print(node.body); + return code; +}; + +CodeGenerator.prototype.DoWhileStatement = function (node, print) { + var code = "do " + print(node.body); + if (/\}$/.test(code)) { + code += " while"; + } else { + code += "\nwhile"; + } + code += " (" + print(node.test) + ");"; + return code; +}; + +CodeGenerator.prototype.BreakStatement = function (node, print) { + var code = "break"; + if (node.label) code += " " + print(node.label); + code += ";"; + return code; +}; + +CodeGenerator.prototype.ContinueStatement = function (node, print) { + var code = "continue"; + if (node.label) code += " " + print(node.label); + code += ";"; + return code; +}; + +CodeGenerator.prototype.LabeledStatement = function (node, print) { + return print(node.label) + ":\n" + print(node.body); +}; + +CodeGenerator.prototype.TryStatement = function (node, print) { + var code = "try " + print(node.block); + code += " " + print(node.handler); + if (node.finalizer) { + code += " finally " + print(node.finalizer); + } + return code; +}; + +CodeGenerator.prototype.CatchClause = function (node, print) { + var code = "catch (" + print(node.param); + if (node.guard) { + code += " if " + print(node.guard); + } + code += ") " + print(node.body); + return code; +}; + +CodeGenerator.prototype.ThrowStatement = function (node, print) { + return "throw " + print(node.argument) + ";"; +}; + +CodeGenerator.prototype.SwitchStatement = function (node, print) { + var code = "switch ("; + code += print(node.discriminant); + code += ") {"; + if (node.cases.length > 0) { + code += "\n" + node.cases.map(print).join("\n") + "\n"; + } + code += "}"; + return code; +}; + +CodeGenerator.prototype.SwitchCase = function (node, print) { + var code = ""; + if (node.test) { + code += "case " + print(node.test) + ":"; + } else { + code += "default:"; + } + if (node.consequent.length === 1) { + code += " " + print(node.consequent[0]); + } else if (node.consequent.length > 1) { + code += "\n" + this.indent(print.sequence(node.consequent)); + } + return this.indent(code); +}; + +CodeGenerator.prototype.DebuggerStatement = function (node, print) { + return "debugger;"; +}; + +CodeGenerator.prototype.ClassExpression = +CodeGenerator.prototype.ClassDeclaration = function (node, print) { + var parts = ["class"]; + + if (node.id) parts.push(" ", print(node.id)); + + if (node.superClass) parts.push(" extends ", print(node.superClass)); + + parts.push(" ", print(node.body)); + + return parts.join(""); +}; + +CodeGenerator.prototype.ClassBody = function (node, print) { + if (node.body.length === 0) { + return "{}"; + } + + return [ + "{\n", + this.indent(node.body.map(print).join("")), + "\n}" + ].join(""); +}; + +CodeGenerator.prototype._method = function (kind, key, value, print) { + var parts = []; + + if (value.async) { + parts.push("async "); + } + + if (!kind || kind === "init") { + if (value.generator) { + parts.push("*"); + } + } else { + assert.ok(kind === "get" || kind === "set"); + parts.push(kind, " "); + } + + parts.push( + print(key), + "(" + value.params.map(print).join(", ") + ")", + print(value.body) + ); + + return parts.join(""); +} + +CodeGenerator.prototype.MethodDefinition = function (node, print) { + var parts = []; + + if (node.static) { + parts.push("static "); + } + + parts.push(this._method( + node.kind, + node.key, + node.value, + print + )); + + return parts.join(""); +}; + +CodeGenerator.prototype.XJSAttribute = function (node, print) { + var code = print(node.name); + if (node.value) code += "=" + print(node.value); + return code; +}; + +CodeGenerator.prototype.XJSIdentifier = function (node, print) { + return node.name; +}; + +CodeGenerator.prototype.XJSNamespacedName = function (node, print) { + return print(node.namespace) + ":" + print(node.name); +}; + +CodeGenerator.prototype.XJSMemberExpression = function (node, print) { + return print(node.object) + "." + print(node.property); +}; + +CodeGenerator.prototype.XJSSpreadAttribute = function (node, print) { + return "{..." + print(node.argument) + "}"; +}; + +CodeGenerator.prototype.XJSExpressionContainer = function (node, print) { + return "{" + print(node.expression) + "}"; +}; + +CodeGenerator.prototype.XJSElement = function (node, print) { + throw new Error("XJSElement"); +}; + +CodeGenerator.prototype.XJSOpeningElement = function (node, print) { + var code = "<" + print(node.name); + if (node.attributes.length < 0) { + code += " " + node.attributes.map(print).join(" "); + } + code += node.selfClosing ? " />" : ">"; + return code; +}; + +CodeGenerator.prototype.XJSClosingElement = function (node, print) { + return ""; +}; + +CodeGenerator.prototype.XJSText = function (node, print) { + return node.value; +}; + +CodeGenerator.prototype.XJSEmptyExpression = function (node, print) { + return ""; +}; diff --git a/lib/6to5/transform.js b/lib/6to5/transform.js index 366ed2ad3b..77aac395c8 100644 --- a/lib/6to5/transform.js +++ b/lib/6to5/transform.js @@ -2,7 +2,7 @@ module.exports = transform; var Transformer = require("./transformer"); var sourceMap = require("source-map"); -var recast = require("acorn-recast"); +var generate = require("./generator"); var File = require("./file"); var util = require("./util"); var _ = require("lodash"); @@ -33,22 +33,31 @@ transform.test = function (task, assert) { execCode = result.code; require("./polyfill"); - var fn = new Function("assert", execCode); - fn(assert); + + try { + var fn = new Function("assert", execCode); + fn(assert); + } catch (err) { + err.message += util.codeFrame(execCode); + throw err; + } } else { var actualCode = actual.code.trim(); var expectCode = expect.code.trim(); - var printOpts = { tabWidth: 2 }; - result = transform(actualCode, getOpts(actual.filename)); - actualCode = recast.prettyPrint(result.ast, printOpts).code; + actualCode = result.code; var expectAst = util.parse(expect, expectCode); - var expectResult = recast.prettyPrint(expectAst, printOpts); + var expectResult = generate(expectAst); expectCode = expectResult.code; - assert.equal(actualCode, expectCode); + try { + assert.equal(actualCode, expectCode); + } catch (err) { + err.showDiff = true; + throw err; + } } if (task.sourceMap) { @@ -78,6 +87,8 @@ transform._ensureTransformerNames = function (type, keys) { }; transform.transformers = { + _comments: require("./transformers/_comments"), + modules: require("./transformers/modules"), computedPropertyNames: require("./transformers/computed-property-names"), propertyNameShorthand: require("./transformers/property-name-shorthand"), @@ -89,9 +100,9 @@ transform.transformers = { templateLiterals: require("./transformers/template-literals"), propertyMethodAssignment: require("./transformers/property-method-assignment"), defaultParameters: require("./transformers/default-parameters"), - letScoping: require("./transformers/let-scoping"), restParameters: require("./transformers/rest-parameters"), destructuring: require("./transformers/destructuring"), + letScoping: require("./transformers/let-scoping"), forOf: require("./transformers/for-of"), unicodeRegex: require("./transformers/unicode-regex"), generators: require("./transformers/generators"), diff --git a/lib/6to5/transformers/_comments.js b/lib/6to5/transformers/_comments.js new file mode 100644 index 0000000000..14ea290d23 --- /dev/null +++ b/lib/6to5/transformers/_comments.js @@ -0,0 +1,6 @@ +var traverse = require("../traverse"); +var _ = require("lodash"); + +module.exports = function (ast) { + +}; diff --git a/lib/6to5/util.js b/lib/6to5/util.js index 94689c8bfe..a0726f0188 100644 --- a/lib/6to5/util.js +++ b/lib/6to5/util.js @@ -1,6 +1,6 @@ var traverse = require("./traverse"); var astTypes = require("acorn-ast-types"); -var recast = require("acorn-recast"); +var acorn = require("acorn-jsx"); var path = require("path"); var util = require("util"); var fs = require("fs"); @@ -220,10 +220,16 @@ exports.codeFrame = function (lines, lineNumber, colNumber) { colNumber = Math.max(colNumber, 0); lines = lines.split("\n"); + var start = Math.max(lineNumber - 3, 0); var end = Math.min(lines.length, lineNumber + 3); var width = (end + "").length; + if (!lineNumber && !colNumber) { + start = 0; + end = lines.length; + } + return "\n" + lines.slice(start, end).map(function (line, i) { var curr = i + start + 1; @@ -258,7 +264,24 @@ exports.parse = function (opts, code, callback) { recastOpts.sourceRoot = opts.sourceRoot; } - var ast = recast.parse(code, recastOpts); + var comments = []; + var tokens = []; + + var ast = acorn.parse(code, { + ecmaVersion: Infinity, + onComment: comments, + locations: true, + onToken: tokens, + ranges: true + }); + + ast = { + type: "File", + program: ast + }; + + ast.tokens = tokens; + ast.comments = comments; if (callback) { return callback(ast);