diff --git a/src/babel/generation/buffer.js b/src/babel/generation/buffer.js index 3b2a4522d4..d498d64421 100644 --- a/src/babel/generation/buffer.js +++ b/src/babel/generation/buffer.js @@ -1,173 +1,173 @@ -module.exports = Buffer; - var repeating = require("repeating"); var trimRight = require("trim-right"); var isBoolean = require("lodash/lang/isBoolean"); var includes = require("lodash/collection/includes"); var isNumber = require("lodash/lang/isNumber"); -function Buffer(position, format) { - this.position = position; - this._indent = format.indent.base; - this.format = format; - this.buf = ""; -} - -Buffer.prototype.get = function () { - return trimRight(this.buf); -}; - -Buffer.prototype.getIndent = function () { - if (this.format.compact || this.format.concise) { - return ""; - } else { - return repeating(this.format.indent.style, this._indent); +export default class Buffer { + constructor(position, format) { + this.position = position; + this._indent = format.indent.base; + this.format = format; + this.buf = ""; } -}; -Buffer.prototype.indentSize = function () { - return this.getIndent().length; -}; - -Buffer.prototype.indent = function () { - this._indent++; -}; - -Buffer.prototype.dedent = function () { - this._indent--; -}; - -Buffer.prototype.semicolon = function () { - this.push(";"); -}; - -Buffer.prototype.ensureSemicolon = function () { - if (!this.isLast(";")) this.semicolon(); -}; - -Buffer.prototype.rightBrace = function () { - this.newline(true); - this.push("}"); -}; - -Buffer.prototype.keyword = function (name) { - this.push(name); - this.space(); -}; - -Buffer.prototype.space = function () { - if (this.format.compact) return; - if (this.buf && !this.isLast(" ") && !this.isLast("\n")) { - this.push(" "); + get() { + return trimRight(this.buf); } -}; -Buffer.prototype.removeLast = function (cha) { - if (this.format.compact) return; - if (!this.isLast(cha)) return; + getIndent() { + if (this.format.compact || this.format.concise) { + return ""; + } else { + return repeating(this.format.indent.style, this._indent); + } + } - this.buf = this.buf.substr(0, this.buf.length - 1); - this.position.unshift(cha); -}; + indentSize() { + return this.getIndent().length; + } -Buffer.prototype.newline = function (i, removeLast) { - if (this.format.compact) return; + indent() { + this._indent++; + } - if (this.format.concise) { + dedent() { + this._indent--; + } + + semicolon() { + this.push(";"); + } + + ensureSemicolon() { + if (!this.isLast(";")) this.semicolon(); + } + + rightBrace() { + this.newline(true); + this.push("}"); + } + + keyword(name) { + this.push(name); this.space(); - return; } - removeLast ||= false; - - if (isNumber(i)) { - i = Math.min(2, i); - - if (this.endsWith("{\n") || this.endsWith(":\n")) i--; - if (i <= 0) return; - - while (i > 0) { - this._newline(removeLast); - i--; + space() { + if (this.format.compact) return; + if (this.buf && !this.isLast(" ") && !this.isLast("\n")) { + this.push(" "); } - return; } - if (isBoolean(i)) { - removeLast = i; + removeLast(cha) { + if (this.format.compact) return; + if (!this.isLast(cha)) return; + + this.buf = this.buf.substr(0, this.buf.length - 1); + this.position.unshift(cha); } - this._newline(removeLast); -}; + newline(i, removeLast) { + if (this.format.compact) return; -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(" "); - this._removeSpacesAfterLastNewline(); - this._push("\n"); -}; - -/** - * 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; - - var index = this.buf.length - 1; - while (index > lastNewlineIndex) { - if (this.buf[index] !== " ") { - break; + if (this.format.concise) { + this.space(); + return; } - index--; + removeLast ||= false; + + if (isNumber(i)) { + i = Math.min(2, i); + + if (this.endsWith("{\n") || this.endsWith(":\n")) i--; + if (i <= 0) return; + + while (i > 0) { + this._newline(removeLast); + i--; + } + return; + } + + if (isBoolean(i)) { + removeLast = i; + } + + this._newline(removeLast); } - if (index === lastNewlineIndex) { - this.buf = this.buf.substring(0, index + 1); - } -}; + _newline(removeLast) { + // never allow more than two lines + if (this.endsWith("\n\n")) return; -Buffer.prototype.push = function (str, noIndent) { - 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(); + // remove the last newline + if (removeLast && this.isLast("\n")) this.removeLast("\n"); - // replace all newlines with newlines with the indentation - str = str.replace(/\n/g, "\n" + indent); - - // we've got a newline before us so prepend on the indentation - if (this.isLast("\n")) this._push(indent); + this.removeLast(" "); + this._removeSpacesAfterLastNewline(); + this._push("\n"); } - this._push(str); -}; + /** + * If buffer ends with a newline and some spaces after it, trim those spaces. + */ -Buffer.prototype._push = function (str) { - this.position.push(str); - this.buf += str; -}; + _removeSpacesAfterLastNewline() { + var lastNewlineIndex = this.buf.lastIndexOf("\n"); + if (lastNewlineIndex === -1) + return; -Buffer.prototype.endsWith = function (str) { - return this.buf.slice(-str.length) === str; -}; + var index = this.buf.length - 1; + while (index > lastNewlineIndex) { + if (this.buf[index] !== " ") { + break; + } -Buffer.prototype.isLast = function (cha) { - if (this.format.compact) return false; + index--; + } - var buf = this.buf; - var last = buf[buf.length - 1]; - - if (Array.isArray(cha)) { - return includes(cha, last); - } else { - return cha === last; + if (index === lastNewlineIndex) { + this.buf = this.buf.substring(0, index + 1); + } } -}; + + push(str, noIndent) { + 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(); + + // replace all newlines with newlines with the indentation + str = str.replace(/\n/g, "\n" + indent); + + // we've got a newline before us so prepend on the indentation + if (this.isLast("\n")) this._push(indent); + } + + this._push(str); + } + + _push(str) { + this.position.push(str); + this.buf += str; + } + + endsWith(str) { + return this.buf.slice(-str.length) === str; + } + + isLast(cha) { + if (this.format.compact) return false; + + var buf = this.buf; + var last = buf[buf.length - 1]; + + if (Array.isArray(cha)) { + return includes(cha, last); + } else { + return cha === last; + } + } +} diff --git a/src/babel/generation/index.js b/src/babel/generation/index.js index 93babf3dbb..9703388576 100644 --- a/src/babel/generation/index.js +++ b/src/babel/generation/index.js @@ -1,10 +1,3 @@ -module.exports = function (ast, opts, code) { - var gen = new CodeGenerator(ast, opts, code); - return gen.generate(); -}; - -module.exports.CodeGenerator = CodeGenerator; - var detectIndent = require("detect-indent"); var Whitespace = require("./whitespace"); var repeating = require("repeating"); @@ -17,19 +10,336 @@ var each = require("lodash/collection/each"); var n = require("./node"); var t = require("../types"); -function CodeGenerator(ast, opts, code) { - opts ||= {}; +class CodeGenerator { + constructor(ast, opts, code) { + opts ||= {}; - this.comments = ast.comments || []; - this.tokens = ast.tokens || []; - this.format = CodeGenerator.normalizeOptions(code, opts); - this.opts = opts; - this.ast = ast; + this.comments = ast.comments || []; + this.tokens = ast.tokens || []; + this.format = CodeGenerator.normalizeOptions(code, opts); + this.opts = opts; + this.ast = ast; - this.whitespace = new Whitespace(this.tokens, this.comments, this.format); - this.position = new Position; - this.map = new SourceMap(this.position, opts, code); - this.buffer = new Buffer(this.position, this.format); + this.whitespace = new Whitespace(this.tokens, this.comments, this.format); + this.position = new Position; + this.map = new SourceMap(this.position, opts, code); + this.buffer = new Buffer(this.position, this.format); + } + + static normalizeOptions(code, opts) { + var style = " "; + if (code) { + var indent = detectIndent(code).indent; + if (indent && indent !== " ") style = indent; + } + + var format = { + comments: opts.comments == null || opts.comments, + compact: opts.compact, + indent: { + adjustMultilineComment: true, + style: style, + base: 0 + } + }; + + if (format.compact === "auto") { + format.compact = code.length > 100000; // 100KB + + if (format.compact) { + console.error(messages.get("codeGeneratorDeopt", opts.filename, "100KB")); + } + } + + return format; + } + + static generators = { + templateLiterals: require("./generators/template-literals"), + comprehensions: require("./generators/comprehensions"), + expressions: require("./generators/expressions"), + statements: require("./generators/statements"), + playground: require("./generators/playground"), + classes: require("./generators/classes"), + methods: require("./generators/methods"), + modules: require("./generators/modules"), + types: require("./generators/types"), + flow: require("./generators/flow"), + base: require("./generators/base"), + jsx: require("./generators/jsx") + }; + + generate() { + var ast = this.ast; + + this.print(ast); + + var comments = []; + each(ast.comments, function (comment) { + if (!comment._displayed) comments.push(comment); + }); + this._printComments(comments); + + return { + map: this.map.get(), + code: this.buffer.get() + }; + } + + buildPrint(parent) { + var print = (node, opts) => { + return this.print(node, parent, opts); + }; + + print.sequence = (nodes, opts) => { + opts ||= {}; + opts.statement = true; + return this.printJoin(print, nodes, opts); + }; + + print.join = (nodes, opts) => { + return this.printJoin(print, nodes, opts); + }; + + print.list = function (items, opts) { + opts ||= {}; + opts.separator ||= ", "; + print.join(items, opts); + }; + + print.block = (node) => { + return this.printBlock(print, node); + }; + + print.indentOnComments = (node) => { + return this.printAndIndentOnComments(print, node); + }; + + return print; + } + + print(node, parent, opts) { + if (!node) return ""; + + if (parent && parent._compact) { + node._compact = true; + } + + var oldConcise = this.format.concise; + if (node._compact) { + this.format.concise = true; + } + + opts ||= {}; + + var newline = (leading) => { + if (!opts.statement && !n.isUserWhitespacable(node, parent)) { + return; + } + + var lines = 0; + + if (node.start != null && !node._ignoreUserWhitespace) { + // user node + if (leading) { + lines = this.whitespace.getNewlinesBefore(node); + } else { + lines = this.whitespace.getNewlinesAfter(node); + } + } else { + // generated node + if (!leading) lines++; // always include at least a single line after + if (opts.addNewlines) lines += opts.addNewlines(leading, node) || 0; + + var needs = n.needsWhitespaceAfter; + if (leading) needs = n.needsWhitespaceBefore; + if (needs(node, parent)) lines++; + + // generated nodes can't add starting file whitespace + if (!this.buffer.buf) lines = 0; + } + + this.newline(lines); + }; + + if (this[node.type]) { + var needsNoLineTermParens = n.needsParensNoLineTerminator(node, parent); + var needsParens = needsNoLineTermParens || n.needsParens(node, parent); + + if (needsParens) this.push("("); + if (needsNoLineTermParens) this.indent(); + + this.printLeadingComments(node, parent); + + newline(true); + + if (opts.before) opts.before(); + this.map.mark(node, "start"); + + this[node.type](node, this.buildPrint(node), parent); + + if (needsNoLineTermParens) { + this.newline(); + this.dedent(); + } + if (needsParens) this.push(")"); + + this.map.mark(node, "end"); + if (opts.after) opts.after(); + + newline(false); + + this.printTrailingComments(node, parent); + } else { + throw new ReferenceError("unknown node of type " + JSON.stringify(node.type) + " with constructor " + JSON.stringify(node && node.constructor.name)); + } + + this.format.concise = oldConcise; + } + + printJoin(print, nodes, opts) { + if (!nodes || !nodes.length) return; + + opts ||= {}; + + var len = nodes.length; + + if (opts.indent) this.indent(); + + each(nodes, (node, i) => { + print(node, { + statement: opts.statement, + addNewlines: opts.addNewlines, + after: () => { + if (opts.iterator) { + opts.iterator(node, i); + } + + if (opts.separator && i < len - 1) { + this.push(opts.separator); + } + } + }); + }); + + if (opts.indent) this.dedent(); + } + + printAndIndentOnComments(print, node) { + var indent = !!node.leadingComments; + if (indent) this.indent(); + print(node); + if (indent) this.dedent(); + } + + printBlock(print, node) { + if (t.isEmptyStatement(node)) { + this.semicolon(); + } else { + this.push(" "); + print(node); + } + } + + generateComment(comment) { + var val = comment.value; + if (comment.type === "Line") { + val = "//" + val; + } else { + val = "/*" + val + "*/"; + } + return val; + } + + printTrailingComments(node, parent) { + this._printComments(this.getComments("trailingComments", node, parent)); + } + + printLeadingComments(node, parent) { + this._printComments(this.getComments("leadingComments", node, parent)); + } + + getComments(key, node, parent) { + if (t.isExpressionStatement(parent)) { + return []; + } + + var comments = []; + var nodes = [node]; + + if (t.isExpressionStatement(node)) { + nodes.push(node.argument); + } + + each(nodes, (node) => { + comments = comments.concat(this._getComments(key, node)); + }); + + return comments; + } + + _getComments(key, node) { + return (node && node[key]) || []; + } + + _printComments(comments) { + if (this.format.compact) return; + + if (!this.format.comments) return; + if (!comments || !comments.length) return; + + each(comments, (comment) => { + var skip = false; + + // find the original comment in the ast and set it as displayed + each(this.ast.comments, function (origComment) { + if (origComment.start === comment.start) { + // comment has already been output + if (origComment._displayed) skip = true; + + origComment._displayed = true; + return false; + } + }); + + if (skip) return; + + // whitespace before + this.newline(this.whitespace.getNewlinesBefore(comment)); + + var column = this.position.column; + var val = this.generateComment(comment); + + if (column && !this.isLast(["\n", " ", "[", "{"])) { + this._push(" "); + column++; + } + + // + + if (comment.type === "Block" && this.format.indent.adjustMultilineComment) { + var offset = comment.loc.start.column; + if (offset) { + var newlineRegex = new RegExp("\\n\\s{1," + offset + "}", "g"); + val = val.replace(newlineRegex, "\n"); + } + + var indent = Math.max(this.indentSize(), column); + val = val.replace(/\n/g, "\n" + repeating(" ", indent)); + } + + if (column === 0) { + val = this.getIndent() + val; + } + + // + + this._push(val); + + // whitespace after + this.newline(this.whitespace.getNewlinesAfter(comment)); + }); + } } each(Buffer.prototype, function (fn, key) { @@ -38,321 +348,13 @@ each(Buffer.prototype, function (fn, key) { }; }); -CodeGenerator.normalizeOptions = function (code, opts) { - var style = " "; - if (code) { - var indent = detectIndent(code).indent; - if (indent && indent !== " ") style = indent; - } - - var format = { - comments: opts.comments == null || opts.comments, - compact: opts.compact, - indent: { - adjustMultilineComment: true, - style: style, - base: 0 - } - }; - - if (format.compact === "auto") { - format.compact = code.length > 100000; // 100KB - - if (format.compact) { - console.error(messages.get("codeGeneratorDeopt", opts.filename, "100KB")); - } - } - - return format; -}; - -CodeGenerator.generators = { - templateLiterals: require("./generators/template-literals"), - comprehensions: require("./generators/comprehensions"), - expressions: require("./generators/expressions"), - statements: require("./generators/statements"), - playground: require("./generators/playground"), - classes: require("./generators/classes"), - methods: require("./generators/methods"), - modules: require("./generators/modules"), - types: require("./generators/types"), - flow: require("./generators/flow"), - base: require("./generators/base"), - jsx: require("./generators/jsx") -}; - each(CodeGenerator.generators, function (generator) { extend(CodeGenerator.prototype, generator); }); -CodeGenerator.prototype.generate = function () { - var ast = this.ast; - - this.print(ast); - - var comments = []; - each(ast.comments, function (comment) { - if (!comment._displayed) comments.push(comment); - }); - this._printComments(comments); - - return { - map: this.map.get(), - code: this.buffer.get() - }; +module.exports = function (ast, opts, code) { + var gen = new CodeGenerator(ast, opts, code); + return gen.generate(); }; -CodeGenerator.prototype.buildPrint = function (parent) { - var print = (node, opts) => { - return this.print(node, parent, opts); - }; - - print.sequence = (nodes, opts) => { - opts ||= {}; - opts.statement = true; - return this.printJoin(print, nodes, opts); - }; - - print.join = (nodes, opts) => { - return this.printJoin(print, nodes, opts); - }; - - print.list = function (items, opts) { - opts ||= {}; - opts.separator ||= ", "; - print.join(items, opts); - }; - - print.block = (node) => { - return this.printBlock(print, node); - }; - - print.indentOnComments = (node) => { - return this.printAndIndentOnComments(print, node); - }; - - return print; -}; - -CodeGenerator.prototype.print = function (node, parent, opts) { - if (!node) return ""; - - if (parent && parent._compact) { - node._compact = true; - } - - var oldConcise = this.format.concise; - if (node._compact) { - this.format.concise = true; - } - - opts ||= {}; - - var newline = (leading) => { - if (!opts.statement && !n.isUserWhitespacable(node, parent)) { - return; - } - - var lines = 0; - - if (node.start != null && !node._ignoreUserWhitespace) { - // user node - if (leading) { - lines = this.whitespace.getNewlinesBefore(node); - } else { - lines = this.whitespace.getNewlinesAfter(node); - } - } else { - // generated node - if (!leading) lines++; // always include at least a single line after - if (opts.addNewlines) lines += opts.addNewlines(leading, node) || 0; - - var needs = n.needsWhitespaceAfter; - if (leading) needs = n.needsWhitespaceBefore; - if (needs(node, parent)) lines++; - - // generated nodes can't add starting file whitespace - if (!this.buffer.buf) lines = 0; - } - - this.newline(lines); - }; - - if (this[node.type]) { - var needsNoLineTermParens = n.needsParensNoLineTerminator(node, parent); - var needsParens = needsNoLineTermParens || n.needsParens(node, parent); - - if (needsParens) this.push("("); - if (needsNoLineTermParens) this.indent(); - - this.printLeadingComments(node, parent); - - newline(true); - - if (opts.before) opts.before(); - this.map.mark(node, "start"); - - this[node.type](node, this.buildPrint(node), parent); - - if (needsNoLineTermParens) { - this.newline(); - this.dedent(); - } - if (needsParens) this.push(")"); - - this.map.mark(node, "end"); - if (opts.after) opts.after(); - - newline(false); - - this.printTrailingComments(node, parent); - } else { - throw new ReferenceError("unknown node of type " + JSON.stringify(node.type) + " with constructor " + JSON.stringify(node && node.constructor.name)); - } - - this.format.concise = oldConcise; -}; - -CodeGenerator.prototype.printJoin = function (print, nodes, opts) { - if (!nodes || !nodes.length) return; - - opts ||= {}; - - var len = nodes.length; - - if (opts.indent) this.indent(); - - each(nodes, (node, i) => { - print(node, { - statement: opts.statement, - addNewlines: opts.addNewlines, - after: () => { - if (opts.iterator) { - opts.iterator(node, i); - } - - if (opts.separator && i < len - 1) { - this.push(opts.separator); - } - } - }); - }); - - if (opts.indent) this.dedent(); -}; - -CodeGenerator.prototype.printAndIndentOnComments = function (print, node) { - var indent = !!node.leadingComments; - if (indent) this.indent(); - print(node); - if (indent) this.dedent(); -}; - -CodeGenerator.prototype.printBlock = function (print, node) { - if (t.isEmptyStatement(node)) { - this.semicolon(); - } else { - this.push(" "); - print(node); - } -}; - -CodeGenerator.prototype.generateComment = function (comment) { - var val = comment.value; - if (comment.type === "Line") { - val = "//" + val; - } else { - val = "/*" + val + "*/"; - } - return val; -}; - -CodeGenerator.prototype.printTrailingComments = function (node, parent) { - this._printComments(this.getComments("trailingComments", node, parent)); -}; - -CodeGenerator.prototype.printLeadingComments = function (node, parent) { - this._printComments(this.getComments("leadingComments", node, parent)); -}; - -CodeGenerator.prototype.getComments = function (key, node, parent) { - if (t.isExpressionStatement(parent)) { - return []; - } - - var comments = []; - var nodes = [node]; - - if (t.isExpressionStatement(node)) { - nodes.push(node.argument); - } - - each(nodes, (node) => { - comments = comments.concat(this._getComments(key, node)); - }); - - return comments; -}; - -CodeGenerator.prototype._getComments = function (key, node) { - return (node && node[key]) || []; -}; - -CodeGenerator.prototype._printComments = function (comments) { - if (this.format.compact) return; - - if (!this.format.comments) return; - if (!comments || !comments.length) return; - - each(comments, (comment) => { - var skip = false; - - // find the original comment in the ast and set it as displayed - each(this.ast.comments, function (origComment) { - if (origComment.start === comment.start) { - // comment has already been output - if (origComment._displayed) skip = true; - - origComment._displayed = true; - return false; - } - }); - - if (skip) return; - - // whitespace before - this.newline(this.whitespace.getNewlinesBefore(comment)); - - var column = this.position.column; - var val = this.generateComment(comment); - - if (column && !this.isLast(["\n", " ", "[", "{"])) { - this._push(" "); - column++; - } - - // - - if (comment.type === "Block" && this.format.indent.adjustMultilineComment) { - var offset = comment.loc.start.column; - if (offset) { - var newlineRegex = new RegExp("\\n\\s{1," + offset + "}", "g"); - val = val.replace(newlineRegex, "\n"); - } - - var indent = Math.max(this.indentSize(), column); - val = val.replace(/\n/g, "\n" + repeating(" ", indent)); - } - - if (column === 0) { - val = this.getIndent() + val; - } - - // - - this._push(val); - - // whitespace after - this.newline(this.whitespace.getNewlinesAfter(comment)); - }); -}; +module.exports.CodeGenerator = CodeGenerator; diff --git a/src/babel/generation/node/index.js b/src/babel/generation/node/index.js index 3f207e9fdf..32447d1b8e 100644 --- a/src/babel/generation/node/index.js +++ b/src/babel/generation/node/index.js @@ -1,5 +1,3 @@ -module.exports = Node; - var whitespace = require("./whitespace"); var parens = require("./parentheses"); var each = require("lodash/collection/each"); @@ -24,79 +22,81 @@ var find = function (obj, node, parent) { return result; }; -function Node(node, parent) { - this.parent = parent; - this.node = node; -} - -Node.isUserWhitespacable = function (node) { - return t.isUserWhitespacable(node); -}; - -Node.needsWhitespace = function (node, parent, type) { - if (!node) return 0; - - if (t.isExpressionStatement(node)) { - node = node.expression; +export default class Node { + constructor(node, parent) { + this.parent = parent; + this.node = node; } - var linesInfo = find(whitespace.nodes, node, parent); + static isUserWhitespacable(node) { + return t.isUserWhitespacable(node); + } - 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; + static needsWhitespace(node, parent, type) { + if (!node) return 0; + + if (t.isExpressionStatement(node)) { + node = node.expression; + } + + var linesInfo = find(whitespace.nodes, node, parent); + + 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; } - return (linesInfo && linesInfo[type]) || 0; -}; - -Node.needsWhitespaceBefore = function (node, parent) { - return Node.needsWhitespace(node, parent, "before"); -}; - -Node.needsWhitespaceAfter = function (node, parent) { - return Node.needsWhitespace(node, parent, "after"); -}; - -Node.needsParens = function (node, parent) { - if (!parent) return false; - - if (t.isNewExpression(parent) && parent.callee === node) { - if (t.isCallExpression(node)) return true; - - var hasCall = some(node, function (val) { - return t.isCallExpression(val); - }); - if (hasCall) return true; + static needsWhitespaceBefore(node, parent) { + return Node.needsWhitespace(node, parent, "before"); } - return find(parens, node, parent); -}; + static needsWhitespaceAfter(node, parent) { + return Node.needsWhitespace(node, parent, "after"); + } -Node.needsParensNoLineTerminator = function (node, parent) { - if (!parent) return false; + static needsParens(node, parent) { + if (!parent) return false; + + if (t.isNewExpression(parent) && parent.callee === node) { + if (t.isCallExpression(node)) return true; + + var hasCall = some(node, function (val) { + return t.isCallExpression(val); + }); + if (hasCall) return true; + } + + return find(parens, node, parent); + } + + static needsParensNoLineTerminator(node, parent) { + if (!parent) return false; + + // no comments + if (!node.leadingComments || !node.leadingComments.length) { + return false; + } + + if (t.isYieldExpression(parent) || t.isAwaitExpression(parent)) { + return true; + } + + if (t.isContinueStatement(parent) || t.isBreakStatement(parent) || + t.isReturnStatement(parent) || t.isThrowStatement(parent)) { + return true; + } - // no comments - if (!node.leadingComments || !node.leadingComments.length) { return false; } - - if (t.isYieldExpression(parent) || t.isAwaitExpression(parent)) { - return true; - } - - if (t.isContinueStatement(parent) || t.isBreakStatement(parent) || - t.isReturnStatement(parent) || t.isThrowStatement(parent)) { - return true; - } - - return false; -}; +} each(Node, function (fn, key) { Node.prototype[key] = function () { diff --git a/src/babel/generation/position.js b/src/babel/generation/position.js index cae67ca356..c7ae9d1bed 100644 --- a/src/babel/generation/position.js +++ b/src/babel/generation/position.js @@ -1,27 +1,27 @@ -module.exports = Position; +export default class Position { + constructor() { + this.line = 1; + this.column = 0; + } -function Position() { - this.line = 1; - this.column = 0; + push(str) { + for (var i = 0; i < str.length; i++) { + if (str[i] === "\n") { + this.line++; + this.column = 0; + } else { + this.column++; + } + } + } + + unshift(str) { + for (var i = 0; i < str.length; i++) { + if (str[i] === "\n") { + this.line--; + } else { + this.column--; + } + } + } } - -Position.prototype.push = function (str) { - for (var i = 0; i < str.length; i++) { - if (str[i] === "\n") { - this.line++; - this.column = 0; - } else { - this.column++; - } - } -}; - -Position.prototype.unshift = function (str) { - for (var i = 0; i < str.length; i++) { - if (str[i] === "\n") { - this.line--; - } else { - this.column--; - } - } -}; diff --git a/src/babel/generation/source-map.js b/src/babel/generation/source-map.js index a9727a6742..6831b4e2b1 100644 --- a/src/babel/generation/source-map.js +++ b/src/babel/generation/source-map.js @@ -1,54 +1,54 @@ -module.exports = SourceMap; - var sourceMap = require("source-map"); var t = require("../types"); -function SourceMap(position, opts, code) { - this.position = position; - this.opts = opts; +export default class SourceMap { + constructor(position, opts, code) { + this.position = position; + this.opts = opts; - if (opts.sourceMap) { - this.map = new sourceMap.SourceMapGenerator({ - file: opts.sourceMapName, - sourceRoot: opts.sourceRoot + if (opts.sourceMap) { + this.map = new sourceMap.SourceMapGenerator({ + file: opts.sourceMapName, + sourceRoot: opts.sourceRoot + }); + + this.map.setSourceContent(opts.sourceFileName, code); + } else { + this.map = null; + } + } + + get() { + var map = this.map; + if (map) { + return map.toJSON(); + } else { + return map; + } + } + + mark(node, type) { + var loc = node.loc; + if (!loc) return; // no location info + + var map = this.map; + if (!map) return; // no source map + + if (t.isProgram(node) || t.isFile(node)) return; // illegal mapping nodes + + var position = this.position; + + var generated = { + line: position.line, + column: position.column + }; + + var original = loc[type]; + + map.addMapping({ + source: this.opts.sourceFileName, + generated: generated, + original: original }); - - this.map.setSourceContent(opts.sourceFileName, code); - } else { - this.map = null; } } - -SourceMap.prototype.get = function () { - var map = this.map; - if (map) { - return map.toJSON(); - } else { - return map; - } -}; - -SourceMap.prototype.mark = function (node, type) { - var loc = node.loc; - if (!loc) return; // no location info - - var map = this.map; - if (!map) return; // no source map - - if (t.isProgram(node) || t.isFile(node)) return; // illegal mapping nodes - - var position = this.position; - - var generated = { - line: position.line, - column: position.column - }; - - var original = loc[type]; - - map.addMapping({ - source: this.opts.sourceFileName, - generated: generated, - original: original - }); -}; diff --git a/src/babel/generation/whitespace.js b/src/babel/generation/whitespace.js index 8da21b9349..786303f0fd 100644 --- a/src/babel/generation/whitespace.js +++ b/src/babel/generation/whitespace.js @@ -1,5 +1,3 @@ -module.exports = Whitespace; - var sortBy = require("lodash/collection/sortBy"); /** @@ -22,92 +20,94 @@ function getLookupIndex(i, base, max) { return i; } -function Whitespace(tokens, comments) { - this.tokens = sortBy(tokens.concat(comments), "start"); - this.used = {}; +export default class Whitespace { + constructor(tokens, comments) { + this.tokens = sortBy(tokens.concat(comments), "start"); + this.used = {}; - // Profiling this code shows that while generator passes over it, indexes - // returned by `getNewlinesBefore` and `getNewlinesAfter` are always increasing. + // 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. + // 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; -} - -Whitespace.prototype.getNewlinesBefore = function (node) { - var startToken; - var endToken; - var tokens = this.tokens; - 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]; - - // this is the token this node starts with - if (node.start === token.start) { - startToken = tokens[i - 1]; - endToken = token; - - this._lastFoundIndex = i; - break; - } + this._lastFoundIndex = 0; } - return this.getNewlinesBetween(startToken, endToken); -}; + getNewlinesBefore(node) { + var startToken; + var endToken; + var tokens = this.tokens; + var token; -Whitespace.prototype.getNewlinesAfter = function (node) { - var startToken; - var endToken; - var tokens = this.tokens; - 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]; - 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]; + // this is the token this node starts with + if (node.start === token.start) { + startToken = tokens[i - 1]; + endToken = token; - // this is the token this node ends with - if (node.end === token.end) { - startToken = token; - endToken = tokens[i + 1]; - - this._lastFoundIndex = i; - break; + this._lastFoundIndex = i; + break; + } } + + return this.getNewlinesBetween(startToken, endToken); } - if (endToken && endToken.type.type === "eof") { - return 1; - } else { - var lines = this.getNewlinesBetween(startToken, endToken); - if (node.type === "Line" && !lines) { - // line comment + getNewlinesAfter(node) { + var startToken; + var endToken; + var tokens = this.tokens; + 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]; + + // this is the token this node ends with + if (node.end === token.end) { + startToken = token; + endToken = tokens[i + 1]; + + this._lastFoundIndex = i; + break; + } + } + + if (endToken && endToken.type.type === "eof") { return 1; } else { - return lines; - } - } -}; - -Whitespace.prototype.getNewlinesBetween = function (startToken, endToken) { - if (!endToken || !endToken.loc) return 0; - - 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 (typeof this.used[line] === "undefined") { - this.used[line] = true; - lines++; + var lines = this.getNewlinesBetween(startToken, endToken); + if (node.type === "Line" && !lines) { + // line comment + return 1; + } else { + return lines; + } } } - return lines; -}; + getNewlinesBetween(startToken, endToken) { + if (!endToken || !endToken.loc) return 0; + + 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 (typeof this.used[line] === "undefined") { + this.used[line] = true; + lines++; + } + } + + return lines; + } +} diff --git a/src/babel/transformation/file.js b/src/babel/transformation/file.js index ce0678825d..25be52f9be 100644 --- a/src/babel/transformation/file.js +++ b/src/babel/transformation/file.js @@ -1,5 +1,3 @@ -module.exports = File; - var sourceMapToComment = require("source-map-to-comment"); var shebangRegex = require("shebang-regex"); var isFunction = require("lodash/lang/isFunction"); @@ -16,409 +14,6 @@ var path = require("path"); var each = require("lodash/collection/each"); var t = require("../types"); -function File(opts) { - this.dynamicImportedNoDefault = []; - this.dynamicImportIds = {}; - this.dynamicImported = []; - this.dynamicImports = []; - - this.usedHelpers = {}; - this.dynamicData = {}; - this.data = {}; - - this.lastStatements = []; - this.opts = this.normalizeOptions(opts); - this.ast = {}; - - this.buildTransformers(); -} - -File.helpers = [ - "inherits", - "defaults", - "prototype-properties", - "apply-constructor", - "tagged-template-literal", - "tagged-template-literal-loose", - "interop-require", - "to-array", - "to-consumable-array", - "sliced-to-array", - "object-without-properties", - "has-own", - "slice", - "bind", - "define-property", - "async-to-generator", - "interop-require-wildcard", - "typeof", - "extends", - "get", - "set", - "class-call-check", - "object-destructuring-empty", - "temporal-undefined", - "temporal-assert-defined", - "self-global" -]; - -File.validOptions = [ - "filename", - "filenameRelative", - "blacklist", - "whitelist", - "loose", - "optional", - "modules", - "sourceMap", - "sourceMapName", - "sourceFileName", - "sourceRoot", - "moduleRoot", - "moduleIds", - "comments", - "reactCompat", - "keepModuleIdExtensions", - "code", - "ast", - "playground", - "experimental", - "externalHelpers", - "auxiliaryComment", - "compact", - "returnUsedHelpers", - - "resolveModuleSource", - "moduleId", - - // legacy - "format", - - // these are used by plugins - "ignore", - "only", - "extensions", - "accept" -]; - -File.prototype.normalizeOptions = function (opts) { - opts = assign({}, opts); - - for (var key in opts) { - if (key[0] !== "_" && File.validOptions.indexOf(key) < 0) { - throw new ReferenceError("Unknown option: " + key); - } - } - - defaults(opts, { - keepModuleIdExtensions: false, - resolveModuleSource: null, - returnUsedHelpers: false, - externalHelpers: false, - auxilaryComment: "", - experimental: false, - reactCompat: false, - playground: false, - moduleIds: false, - blacklist: [], - whitelist: [], - sourceMap: false, - optional: [], - comments: true, - filename: "unknown", - modules: "common", - compact: "auto", - loose: [], - code: true, - ast: true - }); - - // normalize windows path separators to unix - opts.filename = slash(opts.filename); - if (opts.sourceRoot) { - opts.sourceRoot = slash(opts.sourceRoot); - } - - if (opts.moduleId) { - opts.moduleIds = true; - } - - opts.basename = path.basename(opts.filename, path.extname(opts.filename)); - - opts.blacklist = util.arrayify(opts.blacklist); - opts.whitelist = util.arrayify(opts.whitelist); - opts.optional = util.arrayify(opts.optional); - opts.compact = util.booleanify(opts.compact); - opts.loose = util.arrayify(opts.loose); - - if (includes(opts.loose, "all") || includes(opts.loose, true)) { - opts.loose = Object.keys(transform.transformers); - } - - defaults(opts, { - moduleRoot: opts.sourceRoot - }); - - defaults(opts, { - sourceRoot: opts.moduleRoot - }); - - defaults(opts, { - filenameRelative: opts.filename - }); - - defaults(opts, { - sourceFileName: opts.filenameRelative, - sourceMapName: opts.filenameRelative - }); - - if (opts.playground) { - opts.experimental = true; - } - - if (opts.externalHelpers) { - this.set("helpersNamespace", t.identifier("babelHelpers")); - } - - opts.blacklist = transform._ensureTransformerNames("blacklist", opts.blacklist); - opts.whitelist = transform._ensureTransformerNames("whitelist", opts.whitelist); - opts.optional = transform._ensureTransformerNames("optional", opts.optional); - opts.loose = transform._ensureTransformerNames("loose", opts.loose); - - if (opts.reactCompat) { - opts.optional.push("reactCompat"); - console.error("The reactCompat option has been moved into the optional transformer `reactCompat`"); - } - - var ensureEnabled = function (key) { - var namespace = transform.transformerNamespaces[key]; - if (namespace === "es7") opts.experimental = true; - if (namespace === "playground") opts.playground = true; - }; - - each(opts.whitelist, ensureEnabled); - each(opts.optional, ensureEnabled); - - return opts; -}; - -File.prototype.isLoose = function (key) { - return includes(this.opts.loose, key); -}; - -File.prototype.buildTransformers = function () { - var file = this; - - var transformers = {}; - - var secondaryStack = []; - var stack = []; - - each(transform.transformers, function (transformer, key) { - var pass = transformers[key] = transformer.buildPass(file); - - if (pass.canRun(file)) { - stack.push(pass); - - if (transformer.secondPass) { - secondaryStack.push(pass); - } - - if (transformer.manipulateOptions) { - transformer.manipulateOptions(file.opts, file); - } - } - }); - - this.transformerStack = stack.concat(secondaryStack); - this.transformers = transformers; -}; - -File.prototype.debug = function (msg) { - var parts = this.opts.filename; - if (msg) parts += ": " + msg; - util.debug(parts); -}; - -File.prototype.getModuleFormatter = function (type) { - var ModuleFormatter = isFunction(type) ? type : transform.moduleFormatters[type]; - - if (!ModuleFormatter) { - var loc = util.resolve(type); - if (loc) ModuleFormatter = require(loc); - } - - if (!ModuleFormatter) { - throw new ReferenceError("Unknown module formatter type " + JSON.stringify(type)); - } - - return new ModuleFormatter(this); -}; - -File.prototype.parseShebang = function (code) { - var shebangMatch = shebangRegex.exec(code); - - if (shebangMatch) { - this.shebang = shebangMatch[0]; - - // remove shebang - code = code.replace(shebangRegex, ""); - } - - return code; -}; - -File.prototype.set = function (key, val) { - return this.data[key] = val; -}; - -File.prototype.setDynamic = function (key, fn) { - this.dynamicData[key] = fn; -}; - -File.prototype.get = function (key) { - var data = this.data[key]; - if (data) { - return data; - } else { - var dynamic = this.dynamicData[key]; - if (dynamic) { - return this.set(key, dynamic()); - } - } -}; - -File.prototype.addImport = function (source, name, noDefault) { - name ||= source; - var id = this.dynamicImportIds[name]; - - if (!id) { - id = this.dynamicImportIds[name] = this.scope.generateUidIdentifier(name); - - var specifiers = [t.importSpecifier(t.identifier("default"), id)]; - var declar = t.importDeclaration(specifiers, t.literal(source)); - declar._blockHoist = 3; - - this.dynamicImported.push(declar); - if (noDefault) this.dynamicImportedNoDefault.push(declar); - - this.moduleFormatter.importSpecifier(specifiers[0], declar, this.dynamicImports); - } - - return id; -}; - -File.prototype.isConsequenceExpressionStatement = function (node) { - return t.isExpressionStatement(node) && this.lastStatements.indexOf(node) >= 0; -}; - -File.prototype.attachAuxiliaryComment = function (node) { - var comment = this.opts.auxiliaryComment; - if (comment) { - node.leadingComments ||= []; - node.leadingComments.push({ - type: "Line", - value: " " + comment - }); - } - return node; -}; - -File.prototype.addHelper = function (name) { - if (!includes(File.helpers, name)) { - throw new ReferenceError("Unknown helper " + name); - } - - var program = this.ast.program; - - var declar = program._declarations && program._declarations[name]; - if (declar) return declar.id; - - this.usedHelpers[name] = true; - - var runtime = this.get("helpersNamespace"); - if (runtime) { - name = t.identifier(t.toIdentifier(name)); - return t.memberExpression(runtime, name); - } else { - var ref = util.template(name); - ref._compact = true; - var uid = this.scope.generateUidIdentifier(name); - this.scope.push({ - key: name, - id: uid, - init: ref - }); - return uid; - } -}; - -File.prototype.logDeopt = function () { - // todo, (node, msg) -}; - -File.prototype.errorWithNode = function (node, msg, Error) { - Error ||= SyntaxError; - - var loc = node.loc.start; - var err = new Error("Line " + loc.line + ": " + msg); - err.loc = loc; - return err; -}; - -File.prototype.addCode = function (code) { - code = (code || "") + ""; - this.code = code; - return this.parseShebang(code); -}; - -File.prototype.parse = function (code) { - code = this.addCode(code); - - var opts = this.opts; - - opts.allowImportExportEverywhere = this.isLoose("es6.modules"); - opts.strictMode = this.transformers.useStrict.canRun(); - - return parse(opts, code, (tree) => { - this.transform(tree); - return this.generate(); - }); -}; - -File.prototype.transform = function (ast) { - this.debug(); - - this.ast = ast; - this.lastStatements = t.getLastStatements(ast.program); - this.scope = new Scope(ast.program, ast, null, this); - - var modFormatter = this.moduleFormatter = this.getModuleFormatter(this.opts.modules); - if (modFormatter.init && this.transformers["es6.modules"].canRun()) { - modFormatter.init(); - } - - this.checkNode(ast); - - this.call("pre"); - - each(this.transformerStack, function (pass) { - pass.transform(); - }); - - this.call("post"); -}; - -File.prototype.call = function (key) { - var stack = this.transformerStack; - for (var i = 0; i < stack.length; i++) { - var transformer = stack[i].transformer; - if (transformer[key]) { - transformer[key](this); - } - } -}; - var checkTransformerVisitor = { enter(node, parent, scope, state) { checkNode(state.stack, node, scope); @@ -432,47 +27,452 @@ var checkNode = function (stack, node, scope) { }); }; -File.prototype.checkNode = function (node, scope) { - var stack = this.transformerStack; - scope ||= this.scope; +export default class File { + constructor(opts) { + this.dynamicImportedNoDefault = []; + this.dynamicImportIds = {}; + this.dynamicImported = []; + this.dynamicImports = []; - checkNode(stack, node, scope); + this.usedHelpers = {}; + this.dynamicData = {}; + this.data = {}; - scope.traverse(node, checkTransformerVisitor, { - stack: stack - }); -}; + this.lastStatements = []; + this.opts = this.normalizeOptions(opts); + this.ast = {}; -File.prototype.generate = function () { - var opts = this.opts; - var ast = this.ast; + this.buildTransformers(); + } - var result = { - code: "", - map: null, - ast: null + static helpers = [ + "inherits", + "defaults", + "prototype-properties", + "apply-constructor", + "tagged-template-literal", + "tagged-template-literal-loose", + "interop-require", + "to-array", + "to-consumable-array", + "sliced-to-array", + "object-without-properties", + "has-own", + "slice", + "bind", + "define-property", + "async-to-generator", + "interop-require-wildcard", + "typeof", + "extends", + "get", + "set", + "class-call-check", + "object-destructuring-empty", + "temporal-undefined", + "temporal-assert-defined", + "self-global" + ]; + + static validOptions = [ + "filename", + "filenameRelative", + "blacklist", + "whitelist", + "loose", + "optional", + "modules", + "sourceMap", + "sourceMapName", + "sourceFileName", + "sourceRoot", + "moduleRoot", + "moduleIds", + "comments", + "reactCompat", + "keepModuleIdExtensions", + "code", + "ast", + "playground", + "experimental", + "externalHelpers", + "auxiliaryComment", + "compact", + "returnUsedHelpers", + + "resolveModuleSource", + "moduleId", + + // legacy + "format", + + // these are used by plugins + "ignore", + "only", + "extensions", + "accept" + ]; + + normalizeOptions(opts) { + opts = assign({}, opts); + + for (var key in opts) { + if (key[0] !== "_" && File.validOptions.indexOf(key) < 0) { + throw new ReferenceError("Unknown option: " + key); + } + } + + defaults(opts, { + keepModuleIdExtensions: false, + resolveModuleSource: null, + returnUsedHelpers: false, + externalHelpers: false, + auxilaryComment: "", + experimental: false, + reactCompat: false, + playground: false, + moduleIds: false, + blacklist: [], + whitelist: [], + sourceMap: false, + optional: [], + comments: true, + filename: "unknown", + modules: "common", + compact: "auto", + loose: [], + code: true, + ast: true + }); + + // normalize windows path separators to unix + opts.filename = slash(opts.filename); + if (opts.sourceRoot) { + opts.sourceRoot = slash(opts.sourceRoot); + } + + if (opts.moduleId) { + opts.moduleIds = true; + } + + opts.basename = path.basename(opts.filename, path.extname(opts.filename)); + + opts.blacklist = util.arrayify(opts.blacklist); + opts.whitelist = util.arrayify(opts.whitelist); + opts.optional = util.arrayify(opts.optional); + opts.compact = util.booleanify(opts.compact); + opts.loose = util.arrayify(opts.loose); + + if (includes(opts.loose, "all") || includes(opts.loose, true)) { + opts.loose = Object.keys(transform.transformers); + } + + defaults(opts, { + moduleRoot: opts.sourceRoot + }); + + defaults(opts, { + sourceRoot: opts.moduleRoot + }); + + defaults(opts, { + filenameRelative: opts.filename + }); + + defaults(opts, { + sourceFileName: opts.filenameRelative, + sourceMapName: opts.filenameRelative + }); + + if (opts.playground) { + opts.experimental = true; + } + + if (opts.externalHelpers) { + this.set("helpersNamespace", t.identifier("babelHelpers")); + } + + opts.blacklist = transform._ensureTransformerNames("blacklist", opts.blacklist); + opts.whitelist = transform._ensureTransformerNames("whitelist", opts.whitelist); + opts.optional = transform._ensureTransformerNames("optional", opts.optional); + opts.loose = transform._ensureTransformerNames("loose", opts.loose); + + if (opts.reactCompat) { + opts.optional.push("reactCompat"); + console.error("The reactCompat option has been moved into the optional transformer `reactCompat`"); + } + + var ensureEnabled = function (key) { + var namespace = transform.transformerNamespaces[key]; + if (namespace === "es7") opts.experimental = true; + if (namespace === "playground") opts.playground = true; + }; + + each(opts.whitelist, ensureEnabled); + each(opts.optional, ensureEnabled); + + return opts; }; - if (this.opts.returnUsedHelpers) { - result.usedHelpers = Object.keys(this.usedHelpers); + isLoose(key) { + return includes(this.opts.loose, key); } - if (opts.ast) result.ast = ast; - if (!opts.code) return result; + buildTransformers() { + var file = this; - var _result = generate(ast, opts, this.code); - result.code = _result.code; - result.map = _result.map; + var transformers = {}; - if (this.shebang) { - // add back shebang - result.code = this.shebang + "\n" + result.code; + var secondaryStack = []; + var stack = []; + + each(transform.transformers, function (transformer, key) { + var pass = transformers[key] = transformer.buildPass(file); + + if (pass.canRun(file)) { + stack.push(pass); + + if (transformer.secondPass) { + secondaryStack.push(pass); + } + + if (transformer.manipulateOptions) { + transformer.manipulateOptions(file.opts, file); + } + } + }); + + this.transformerStack = stack.concat(secondaryStack); + this.transformers = transformers; } - if (opts.sourceMap === "inline") { - result.code += "\n" + sourceMapToComment(result.map); - result.map = null; + debug(msg) { + var parts = this.opts.filename; + if (msg) parts += ": " + msg; + util.debug(parts); } - return result; -}; + getModuleFormatter(type) { + var ModuleFormatter = isFunction(type) ? type : transform.moduleFormatters[type]; + + if (!ModuleFormatter) { + var loc = util.resolve(type); + if (loc) ModuleFormatter = require(loc); + } + + if (!ModuleFormatter) { + throw new ReferenceError("Unknown module formatter type " + JSON.stringify(type)); + } + + return new ModuleFormatter(this); + } + + parseShebang(code) { + var shebangMatch = shebangRegex.exec(code); + + if (shebangMatch) { + this.shebang = shebangMatch[0]; + + // remove shebang + code = code.replace(shebangRegex, ""); + } + + return code; + } + + set(key, val) { + return this.data[key] = val; + }; + + setDynamic(key, fn) { + this.dynamicData[key] = fn; + } + + get(key) { + var data = this.data[key]; + if (data) { + return data; + } else { + var dynamic = this.dynamicData[key]; + if (dynamic) { + return this.set(key, dynamic()); + } + } + } + + addImport(source, name, noDefault) { + name ||= source; + var id = this.dynamicImportIds[name]; + + if (!id) { + id = this.dynamicImportIds[name] = this.scope.generateUidIdentifier(name); + + var specifiers = [t.importSpecifier(t.identifier("default"), id)]; + var declar = t.importDeclaration(specifiers, t.literal(source)); + declar._blockHoist = 3; + + this.dynamicImported.push(declar); + if (noDefault) this.dynamicImportedNoDefault.push(declar); + + this.moduleFormatter.importSpecifier(specifiers[0], declar, this.dynamicImports); + } + + return id; + } + + isConsequenceExpressionStatement(node) { + return t.isExpressionStatement(node) && this.lastStatements.indexOf(node) >= 0; + } + + attachAuxiliaryComment(node) { + var comment = this.opts.auxiliaryComment; + if (comment) { + node.leadingComments ||= []; + node.leadingComments.push({ + type: "Line", + value: " " + comment + }); + } + return node; + } + + addHelper(name) { + if (!includes(File.helpers, name)) { + throw new ReferenceError("Unknown helper " + name); + } + + var program = this.ast.program; + + var declar = program._declarations && program._declarations[name]; + if (declar) return declar.id; + + this.usedHelpers[name] = true; + + var runtime = this.get("helpersNamespace"); + if (runtime) { + name = t.identifier(t.toIdentifier(name)); + return t.memberExpression(runtime, name); + } else { + var ref = util.template(name); + ref._compact = true; + var uid = this.scope.generateUidIdentifier(name); + this.scope.push({ + key: name, + id: uid, + init: ref + }); + return uid; + } + } + + logDeopt() { + // todo, (node, msg) + } + + errorWithNode(node, msg, Error) { + Error ||= SyntaxError; + + var loc = node.loc.start; + var err = new Error("Line " + loc.line + ": " + msg); + err.loc = loc; + return err; + } + + addCode(code) { + code = (code || "") + ""; + this.code = code; + return this.parseShebang(code); + } + + parse(code) { + code = this.addCode(code); + + var opts = this.opts; + + opts.allowImportExportEverywhere = this.isLoose("es6.modules"); + opts.strictMode = this.transformers.useStrict.canRun(); + + return parse(opts, code, (tree) => { + this.transform(tree); + return this.generate(); + }); + } + + transform(ast) { + this.debug(); + + this.ast = ast; + this.lastStatements = t.getLastStatements(ast.program); + this.scope = new Scope(ast.program, ast, null, this); + + var modFormatter = this.moduleFormatter = this.getModuleFormatter(this.opts.modules); + if (modFormatter.init && this.transformers["es6.modules"].canRun()) { + modFormatter.init(); + } + + this.checkNode(ast); + + this.call("pre"); + + each(this.transformerStack, function (pass) { + pass.transform(); + }); + + this.call("post"); + } + + call(key) { + var stack = this.transformerStack; + for (var i = 0; i < stack.length; i++) { + var transformer = stack[i].transformer; + if (transformer[key]) { + transformer[key](this); + } + } + } + + checkNode(node, scope) { + var stack = this.transformerStack; + scope ||= this.scope; + + checkNode(stack, node, scope); + + scope.traverse(node, checkTransformerVisitor, { + stack: stack + }); + } + + generate() { + var opts = this.opts; + var ast = this.ast; + + var result = { + code: "", + map: null, + ast: null + }; + + if (this.opts.returnUsedHelpers) { + result.usedHelpers = Object.keys(this.usedHelpers); + } + + if (opts.ast) result.ast = ast; + if (!opts.code) return result; + + var _result = generate(ast, opts, this.code); + result.code = _result.code; + result.map = _result.map; + + if (this.shebang) { + // add back shebang + result.code = this.shebang + "\n" + result.code; + } + + if (opts.sourceMap === "inline") { + result.code += "\n" + sourceMapToComment(result.map); + result.map = null; + } + + return result; + } +} diff --git a/src/babel/transformation/helpers/replace-supers.js b/src/babel/transformation/helpers/replace-supers.js index 6597a47128..d9f12e7e3b 100644 --- a/src/babel/transformation/helpers/replace-supers.js +++ b/src/babel/transformation/helpers/replace-supers.js @@ -3,94 +3,16 @@ module.exports = ReplaceSupers; var messages = require("../../messages"); var t = require("../../types"); -/** - * Description - * - * @param {Object} opts - * @param {Boolean} [inClass] - */ -function ReplaceSupers(opts, inClass) { - this.topLevelThisReference = opts.topLevelThisReference; - this.methodNode = opts.methodNode; - this.className = opts.className; - this.superName = opts.superName; - this.isStatic = opts.isStatic; - this.hasSuper = false; - this.inClass = inClass; - this.isLoose = opts.isLoose; - this.scope = opts.scope; - this.file = opts.file; -} - -/** - * Sets a super class value of the named property. - * - * @example - * - * _set(Object.getPrototypeOf(CLASS.prototype), "METHOD", "VALUE", this) - * - * @param {Node} property - * @param {Node} value - * @param {Boolean} isComputed - * @param {Node} thisExpression - * - * @returns {Node} - */ - -ReplaceSupers.prototype.setSuperProperty = function (property, value, isComputed, thisExpression) { - return t.callExpression( - this.file.addHelper("set"), - [ - t.callExpression( - t.memberExpression(t.identifier("Object"), t.identifier("getPrototypeOf")), - [ - this.isStatic ? this.className : t.memberExpression(this.className, t.identifier("prototype")) - ] - ), - isComputed ? property : t.literal(property.name), - value, - thisExpression - ] - ); +var isIllegalBareSuper = function (node, parent) { + if (!isSuper(node, parent)) return false; + if (t.isMemberExpression(parent, { computed: false })) return false; + if (t.isCallExpression(parent, { callee: node })) return false; + return true; }; -/** - * Gets a node representing the super class value of the named property. - * - * @example - * - * _get(Object.getPrototypeOf(CLASS.prototype), "METHOD", this) - * - * @param {Node} property - * @param {Boolean} isComputed - * @param {Node} thisExpression - * - * @returns {Node} - */ - -ReplaceSupers.prototype.getSuperProperty = function (property, isComputed, thisExpression) { - return t.callExpression( - this.file.addHelper("get"), - [ - t.callExpression( - t.memberExpression(t.identifier("Object"), t.identifier("getPrototypeOf")), - [ - this.isStatic ? this.className : t.memberExpression(this.className, t.identifier("prototype")) - ] - ), - isComputed ? property : t.literal(property.name), - thisExpression - ] - ); -}; - -/** - * Description - */ - -ReplaceSupers.prototype.replace = function () { - this.traverseLevel(this.methodNode.value, true); +var isSuper = function (node, parent) { + return t.isIdentifier(node, { name: "super" }) && t.isReferenced(node, parent); }; var visitor = { @@ -121,181 +43,263 @@ var visitor = { } }; -/** - * Description - * - * @param {Object} node - * @param {Boolean} topLevel - */ +export default class ReplaceSupers { -ReplaceSupers.prototype.traverseLevel = function (node, topLevel) { - var state = { self: this, topLevel: topLevel }; - this.scope.traverse(node, visitor, state); -}; + /** + * Description + * + * @param {Object} opts + * @param {Boolean} [inClass] + */ -/** - * Description - */ - -ReplaceSupers.prototype.getThisReference = function () { - if (this.topLevelThisReference) { - return this.topLevelThisReference; - } else { - var ref = this.topLevelThisReference = this.scope.generateUidIdentifier("this"); - this.methodNode.value.body.body.unshift(t.variableDeclaration("var", [ - t.variableDeclarator(this.topLevelThisReference, t.thisExpression()) - ])); - return ref; + constructor(opts, inClass) { + this.topLevelThisReference = opts.topLevelThisReference; + this.methodNode = opts.methodNode; + this.className = opts.className; + this.superName = opts.superName; + this.isStatic = opts.isStatic; + this.hasSuper = false; + this.inClass = inClass; + this.isLoose = opts.isLoose; + this.scope = opts.scope; + this.file = opts.file; } -}; -/** - * Description - * - * @param {Object} node - * @param {Object} id - * @param {Object} parent - * @returns {Object} - */ + /** + * Sets a super class value of the named property. + * + * @example + * + * _set(Object.getPrototypeOf(CLASS.prototype), "METHOD", "VALUE", this) + * + * @param {Node} property + * @param {Node} value + * @param {Boolean} isComputed + * @param {Node} thisExpression + * + * @returns {Node} + */ -ReplaceSupers.prototype.getLooseSuperProperty = function (id, parent) { - var methodNode = this.methodNode; - var methodName = methodNode.key; - var superName = this.superName || t.identifier("Function"); + setSuperProperty(property, value, isComputed, thisExpression) { + return t.callExpression( + this.file.addHelper("set"), + [ + t.callExpression( + t.memberExpression(t.identifier("Object"), t.identifier("getPrototypeOf")), + [ + this.isStatic ? this.className : t.memberExpression(this.className, t.identifier("prototype")) + ] + ), + isComputed ? property : t.literal(property.name), + value, + thisExpression + ] + ); + } - if (parent.property === id) { - return; - } else if (t.isCallExpression(parent, { callee: id })) { - // super(); -> ClassName.prototype.MethodName.call(this); - parent.arguments.unshift(t.thisExpression()); + /** + * Gets a node representing the super class value of the named property. + * + * @example + * + * _get(Object.getPrototypeOf(CLASS.prototype), "METHOD", this) + * + * @param {Node} property + * @param {Boolean} isComputed + * @param {Node} thisExpression + * + * @returns {Node} + */ - if (methodName.name === "constructor") { - // constructor() { super(); } - return t.memberExpression(superName, t.identifier("call")); + getSuperProperty(property, isComputed, thisExpression) { + return t.callExpression( + this.file.addHelper("get"), + [ + t.callExpression( + t.memberExpression(t.identifier("Object"), t.identifier("getPrototypeOf")), + [ + this.isStatic ? this.className : t.memberExpression(this.className, t.identifier("prototype")) + ] + ), + isComputed ? property : t.literal(property.name), + thisExpression + ] + ); + } + + /** + * Description + */ + + replace() { + this.traverseLevel(this.methodNode.value, true); + } + + /** + * Description + * + * @param {Object} node + * @param {Boolean} topLevel + */ + + traverseLevel(node, topLevel) { + var state = { self: this, topLevel: topLevel }; + this.scope.traverse(node, visitor, state); + } + + /** + * Description + */ + + getThisReference() { + if (this.topLevelThisReference) { + return this.topLevelThisReference; } else { - id = superName; - - // foo() { super(); } - if (!methodNode.static) { - id = t.memberExpression(id, t.identifier("prototype")); - } - - id = t.memberExpression(id, methodName, methodNode.computed); - return t.memberExpression(id, t.identifier("call")); + var ref = this.topLevelThisReference = this.scope.generateUidIdentifier("this"); + this.methodNode.value.body.body.unshift(t.variableDeclaration("var", [ + t.variableDeclarator(this.topLevelThisReference, t.thisExpression()) + ])); + return ref; } - } else if (t.isMemberExpression(parent) && !methodNode.static) { - // super.test -> ClassName.prototype.test - return t.memberExpression(superName, t.identifier("prototype")); - } else { - return superName; - } -}; - -/** - * Description - * - * @param {Function} getThisReference - * @param {Object} node - * @param {Object} parent - */ - -ReplaceSupers.prototype.looseHandle = function (getThisReference, node, parent) { - if (t.isIdentifier(node, { name: "super" })) { - this.hasSuper = true; - return this.getLooseSuperProperty(node, parent); - } else if (t.isCallExpression(node)) { - var callee = node.callee; - if (!t.isMemberExpression(callee)) return; - if (callee.object.name !== "super") return; - - // super.test(); -> ClassName.prototype.MethodName.call(this); - this.hasSuper = true; - t.appendToMemberExpression(callee, t.identifier("call")); - node.arguments.unshift(getThisReference()); - } -}; - -/** - * Description - * - * @param {Function} getThisReference - * @param {Object} node - * @param {Object} parent - */ - -ReplaceSupers.prototype.specHandle = function (getThisReference, node, parent) { - var methodNode = this.methodNode; - var property; - var computed; - var args; - var thisReference; - - if (isIllegalBareSuper(node, parent)) { - throw this.file.errorWithNode(node, messages.get("classesIllegalBareSuper")); } - if (t.isCallExpression(node)) { - var callee = node.callee; - if (isSuper(callee, node)) { - // super(); -> _get(Object.getPrototypeOf(ClassName), "MethodName", this).call(this); - property = methodNode.key; - computed = methodNode.computed; - args = node.arguments; + /** + * Description + * + * @param {Object} node + * @param {Object} id + * @param {Object} parent + * @returns {Object} + */ - // bare `super` call is illegal inside non-constructors - // - https://esdiscuss.org/topic/super-call-in-methods - // - https://twitter.com/wycats/status/544553184396836864 - if (methodNode.key.name !== "constructor" || !this.inClass) { - var methodName = methodNode.key.name || "METHOD_NAME"; - throw this.file.errorWithNode(node, messages.get("classesIllegalSuperCall", methodName)); + getLooseSuperProperty(id, parent) { + var methodNode = this.methodNode; + var methodName = methodNode.key; + var superName = this.superName || t.identifier("Function"); + + if (parent.property === id) { + return; + } else if (t.isCallExpression(parent, { callee: id })) { + // super(); -> ClassName.prototype.MethodName.call(this); + parent.arguments.unshift(t.thisExpression()); + + if (methodName.name === "constructor") { + // constructor() { super(); } + return t.memberExpression(superName, t.identifier("call")); + } else { + id = superName; + + // foo() { super(); } + if (!methodNode.static) { + id = t.memberExpression(id, t.identifier("prototype")); + } + + id = t.memberExpression(id, methodName, methodNode.computed); + return t.memberExpression(id, t.identifier("call")); } - } else if (t.isMemberExpression(callee) && isSuper(callee.object, callee)) { - // super.test(); -> _get(Object.getPrototypeOf(ClassName.prototype), "test", this).call(this); - property = callee.property; - computed = callee.computed; - args = node.arguments; - } - } else if (t.isMemberExpression(node) && isSuper(node.object, node)) { - // super.name; -> _get(Object.getPrototypeOf(ClassName.prototype), "name", this); - property = node.property; - computed = node.computed; - } else if (t.isAssignmentExpression(node) && isSuper(node.left.object, node.left) && methodNode.kind === "set") { - // super.name = "val"; -> _set(Object.getPrototypeOf(ClassName.prototype), "name", this); - this.hasSuper = true; - return this.setSuperProperty(node.left.property, node.right, node.left.computed, getThisReference()); - } - - if (!property) return; - - this.hasSuper = true; - - thisReference = getThisReference(); - var superProperty = this.getSuperProperty(property, computed, thisReference); - if (args) { - if (args.length === 1 && t.isSpreadElement(args[0])) { - // super(...arguments); - return t.callExpression( - t.memberExpression(superProperty, t.identifier("apply")), - [thisReference, args[0].argument] - ); + } else if (t.isMemberExpression(parent) && !methodNode.static) { + // super.test -> ClassName.prototype.test + return t.memberExpression(superName, t.identifier("prototype")); } else { - return t.callExpression( - t.memberExpression(superProperty, t.identifier("call")), - [thisReference].concat(args) - ); + return superName; } - } else { - return superProperty; } -}; -var isIllegalBareSuper = function (node, parent) { - if (!isSuper(node, parent)) return false; - if (t.isMemberExpression(parent, { computed: false })) return false; - if (t.isCallExpression(parent, { callee: node })) return false; - return true; -}; + /** + * Description + * + * @param {Function} getThisReference + * @param {Object} node + * @param {Object} parent + */ -var isSuper = function (node, parent) { - return t.isIdentifier(node, { name: "super" }) && t.isReferenced(node, parent); -}; + looseHandle(getThisReference, node, parent) { + if (t.isIdentifier(node, { name: "super" })) { + this.hasSuper = true; + return this.getLooseSuperProperty(node, parent); + } else if (t.isCallExpression(node)) { + var callee = node.callee; + if (!t.isMemberExpression(callee)) return; + if (callee.object.name !== "super") return; + + // super.test(); -> ClassName.prototype.MethodName.call(this); + this.hasSuper = true; + t.appendToMemberExpression(callee, t.identifier("call")); + node.arguments.unshift(getThisReference()); + } + } + + /** + * Description + * + * @param {Function} getThisReference + * @param {Object} node + * @param {Object} parent + */ + + specHandle(getThisReference, node, parent) { + var methodNode = this.methodNode; + var property; + var computed; + var args; + var thisReference; + + if (isIllegalBareSuper(node, parent)) { + throw this.file.errorWithNode(node, messages.get("classesIllegalBareSuper")); + } + + if (t.isCallExpression(node)) { + var callee = node.callee; + if (isSuper(callee, node)) { + // super(); -> _get(Object.getPrototypeOf(ClassName), "MethodName", this).call(this); + property = methodNode.key; + computed = methodNode.computed; + args = node.arguments; + + // bare `super` call is illegal inside non-constructors + // - https://esdiscuss.org/topic/super-call-in-methods + // - https://twitter.com/wycats/status/544553184396836864 + if (methodNode.key.name !== "constructor" || !this.inClass) { + var methodName = methodNode.key.name || "METHOD_NAME"; + throw this.file.errorWithNode(node, messages.get("classesIllegalSuperCall", methodName)); + } + } else if (t.isMemberExpression(callee) && isSuper(callee.object, callee)) { + // super.test(); -> _get(Object.getPrototypeOf(ClassName.prototype), "test", this).call(this); + property = callee.property; + computed = callee.computed; + args = node.arguments; + } + } else if (t.isMemberExpression(node) && isSuper(node.object, node)) { + // super.name; -> _get(Object.getPrototypeOf(ClassName.prototype), "name", this); + property = node.property; + computed = node.computed; + } else if (t.isAssignmentExpression(node) && isSuper(node.left.object, node.left) && methodNode.kind === "set") { + // super.name = "val"; -> _set(Object.getPrototypeOf(ClassName.prototype), "name", this); + this.hasSuper = true; + return this.setSuperProperty(node.left.property, node.right, node.left.computed, getThisReference()); + } + + if (!property) return; + + this.hasSuper = true; + + thisReference = getThisReference(); + var superProperty = this.getSuperProperty(property, computed, thisReference); + if (args) { + if (args.length === 1 && t.isSpreadElement(args[0])) { + // super(...arguments); + return t.callExpression( + t.memberExpression(superProperty, t.identifier("apply")), + [thisReference, args[0].argument] + ); + } else { + return t.callExpression( + t.memberExpression(superProperty, t.identifier("call")), + [thisReference].concat(args) + ); + } + } else { + return superProperty; + } + } +} diff --git a/src/babel/transformation/transformer-pass.js b/src/babel/transformation/transformer-pass.js index bcfb8c6a20..3729fd4711 100644 --- a/src/babel/transformation/transformer-pass.js +++ b/src/babel/transformation/transformer-pass.js @@ -1,5 +1,3 @@ -module.exports = TransformerPass; - var includes = require("lodash/collection/includes"); /** @@ -7,57 +5,59 @@ var includes = require("lodash/collection/includes"); * AST and running it's parent transformers handlers over it. */ -function TransformerPass(file, transformer) { - this.transformer = transformer; - this.shouldRun = !transformer.check; - this.handlers = transformer.handlers; - this.file = file; -} +export default class TransformerPass { + constructor(file, transformer) { + this.transformer = transformer; + this.shouldRun = !transformer.check; + this.handlers = transformer.handlers; + this.file = file; + } -TransformerPass.prototype.canRun = function () { - var transformer = this.transformer; + canRun() { + var transformer = this.transformer; - var opts = this.file.opts; - var key = transformer.key; + var opts = this.file.opts; + var key = transformer.key; - // internal - if (key[0] === "_") return true; + // internal + if (key[0] === "_") return true; - // blacklist - var blacklist = opts.blacklist; - if (blacklist.length && includes(blacklist, key)) return false; + // blacklist + var blacklist = opts.blacklist; + if (blacklist.length && includes(blacklist, key)) return false; - // whitelist - var whitelist = opts.whitelist; - if (whitelist.length) return includes(whitelist, key); + // whitelist + var whitelist = opts.whitelist; + if (whitelist.length) return includes(whitelist, key); - // optional - if (transformer.optional && !includes(opts.optional, key)) return false; + // optional + if (transformer.optional && !includes(opts.optional, key)) return false; - // experimental - if (transformer.experimental && !opts.experimental) return false; + // experimental + if (transformer.experimental && !opts.experimental) return false; - // playground - if (transformer.playground && !opts.playground) return false; + // playground + if (transformer.playground && !opts.playground) return false; - return true; -}; - -TransformerPass.prototype.checkNode = function (node) { - var check = this.transformer.check; - if (check) { - return this.shouldRun = check(node); - } else { return true; } -}; -TransformerPass.prototype.transform = function () { - if (!this.shouldRun) return; + checkNode(node) { + var check = this.transformer.check; + if (check) { + return this.shouldRun = check(node); + } else { + return true; + } + } - var file = this.file; + transform() { + if (!this.shouldRun) return; - file.debug("Running transformer " + this.transformer.key); + var file = this.file; - file.scope.traverse(file.ast, this.handlers, file); -}; + file.debug("Running transformer " + this.transformer.key); + + file.scope.traverse(file.ast, this.handlers, file); + } +} diff --git a/src/babel/transformation/transformer.js b/src/babel/transformation/transformer.js index d06c0f74eb..6bf00be2bd 100644 --- a/src/babel/transformation/transformer.js +++ b/src/babel/transformation/transformer.js @@ -1,5 +1,3 @@ -module.exports = Transformer; - var TransformerPass = require("./transformer-pass"); var isFunction = require("lodash/lang/isFunction"); var traverse = require("../traversal"); @@ -13,59 +11,61 @@ var each = require("lodash/collection/each"); * actually running the transformer over the provided `File`. */ -function Transformer(key, transformer, opts) { - transformer = assign({}, transformer); +export default class Transformer { + constructor(key, transformer, opts) { + transformer = assign({}, transformer); - var take = function (key) { - var val = transformer[key]; - delete transformer[key]; - return val; - }; + var take = function (key) { + var val = transformer[key]; + delete transformer[key]; + return val; + }; - this.manipulateOptions = take("manipulateOptions"); - this.check = take("check"); - this.post = take("post"); - this.pre = take("pre"); + this.manipulateOptions = take("manipulateOptions"); + this.check = take("check"); + this.post = take("post"); + this.pre = take("pre"); - this.experimental = !!take("experimental"); - this.playground = !!take("playground"); - this.secondPass = !!take("secondPass"); - this.optional = !!take("optional"); + this.experimental = !!take("experimental"); + this.playground = !!take("playground"); + this.secondPass = !!take("secondPass"); + this.optional = !!take("optional"); - this.handlers = this.normalize(transformer); - this.opts ||= {}; - this.key = key; -} - -Transformer.prototype.normalize = function (transformer) { - if (isFunction(transformer)) { - transformer = { ast: transformer }; + this.handlers = this.normalize(transformer); + this.opts ||= {}; + this.key = key; } - traverse.explode(transformer); - - each(transformer, (fns, type) => { - // hidden property - if (type[0] === "_") { - this[type] = fns; - return; + normalize(transformer) { + if (isFunction(transformer)) { + transformer = { ast: transformer }; } - if (type === "enter" || type === "exit") return; + traverse.explode(transformer); - if (isFunction(fns)) fns = { enter: fns }; + each(transformer, (fns, type) => { + // hidden property + if (type[0] === "_") { + this[type] = fns; + return; + } - if (!isObject(fns)) return; + if (type === "enter" || type === "exit") return; - if (!fns.enter) fns.enter = function () { }; - if (!fns.exit) fns.exit = function () { }; + if (isFunction(fns)) fns = { enter: fns }; - transformer[type] = fns; - }); + if (!isObject(fns)) return; - return transformer; -}; + if (!fns.enter) fns.enter = function () { }; + if (!fns.exit) fns.exit = function () { }; -Transformer.prototype.buildPass = function (file) { - return new TransformerPass(file, this); -}; + transformer[type] = fns; + }); + + return transformer; + } + + buildPass(file) { + return new TransformerPass(file, this); + } +} diff --git a/src/babel/traversal/context.js b/src/babel/traversal/context.js index 2f8e6bc7db..dd6723a428 100644 --- a/src/babel/traversal/context.js +++ b/src/babel/traversal/context.js @@ -1,52 +1,52 @@ -module.exports = TraversalContext; - var TraversalPath = require("./path"); var flatten = require("lodash/array/flatten"); var compact = require("lodash/array/compact"); -function TraversalContext(scope, opts, state, parentPath) { - this.shouldFlatten = false; - this.parentPath = parentPath; +export default class TraversalConext { + constructor(scope, opts, state, parentPath) { + this.shouldFlatten = false; + this.parentPath = parentPath; - this.scope = scope; - this.state = state; - this.opts = opts; + this.scope = scope; + this.state = state; + this.opts = opts; + } + + flatten() { + this.shouldFlatten = true; + } + + visitNode(node, obj, key) { + var iteration = new TraversalPath(this, node, obj, key); + return iteration.visit(); + } + + visit(node, key) { + var nodes = node[key]; + if (!nodes) return; + + if (!Array.isArray(nodes)) { + return this.visitNode(node, node, key); + } + + // nothing to traverse! + if (nodes.length === 0) { + return; + } + + for (var i = 0; i < nodes.length; i++) { + if (nodes[i] && this.visitNode(node, nodes, i)) { + return true; + } + } + + if (this.shouldFlatten) { + node[key] = flatten(node[key]); + + if (key === "body") { + // we can safely compact this + node[key] = compact(node[key]); + } + } + } } - -TraversalContext.prototype.flatten = function () { - this.shouldFlatten = true; -}; - -TraversalContext.prototype.visitNode = function (node, obj, key) { - var iteration = new TraversalPath(this, node, obj, key); - return iteration.visit(); -}; - -TraversalContext.prototype.visit = function (node, key) { - var nodes = node[key]; - if (!nodes) return; - - if (!Array.isArray(nodes)) { - return this.visitNode(node, node, key); - } - - // nothing to traverse! - if (nodes.length === 0) { - return; - } - - for (var i = 0; i < nodes.length; i++) { - if (nodes[i] && this.visitNode(node, nodes, i)) { - return true; - } - } - - if (this.shouldFlatten) { - node[key] = flatten(node[key]); - - if (key === "body") { - // we can safely compact this - node[key] = compact(node[key]); - } - } -}; diff --git a/src/babel/traversal/path.js b/src/babel/traversal/path.js index f98242fc8a..592cddf2ce 100644 --- a/src/babel/traversal/path.js +++ b/src/babel/traversal/path.js @@ -1,68 +1,66 @@ -module.exports = TraversalPath; - var traverse = require("./index"); var includes = require("lodash/collection/includes"); var Scope = require("./scope"); var t = require("../types"); -function TraversalPath(context, parent, container, key) { - this.shouldRemove = false; - this.shouldSkip = false; - this.shouldStop = false; +export default class TraversalPath { + constructor(context, parent, container, key) { + this.shouldRemove = false; + this.shouldSkip = false; + this.shouldStop = false; - this.parentPath = context.parentPath; - this.context = context; - this.state = this.context.state; - this.opts = this.context.opts; + this.parentPath = context.parentPath; + this.context = context; + this.state = this.context.state; + this.opts = this.context.opts; - this.container = container; - this.key = key; + this.container = container; + this.key = key; - this.parent = parent; - this.state = context.state; + this.parent = parent; + this.state = context.state; - this.setScope(); -} - -TraversalPath.getScope = function (node, parent, scope) { - var ourScope = scope; - - // we're entering a new scope so let's construct it! - if (t.isScope(node, parent)) { - ourScope = new Scope(node, parent, scope); + this.setScope(); } - return ourScope; -}; + static getScope(node, parent, scope) { + var ourScope = scope; -TraversalPath.prototype.setScope = function () { - this.scope = TraversalPath.getScope(this.node, this.parent, this.context.scope); -}; + // we're entering a new scope so let's construct it! + if (t.isScope(node, parent)) { + ourScope = new Scope(node, parent, scope); + } -TraversalPath.prototype.remove = function () { - this.shouldRemove = true; - this.shouldSkip = true; -}; + return ourScope; + } -TraversalPath.prototype.skip = function () { - this.shouldSkip = true; -}; + setScope() { + this.scope = TraversalPath.getScope(this.node, this.parent, this.context.scope); + } -TraversalPath.prototype.stop = function () { - this.shouldStop = true; - this.shouldSkip = true; -}; + remove() { + this.shouldRemove = true; + this.shouldSkip = true; + } -TraversalPath.prototype.flatten = function () { - this.context.flatten(); -}; + skip() { + this.shouldSkip = true; + } -Object.defineProperty(TraversalPath.prototype, "node", { - get() { + stop() { + this.shouldStop = true; + this.shouldSkip = true; + } + + flatten() { + this.context.flatten(); + } + + get node() { return this.container[this.key]; - }, + } - set(replacement) { + set node(replacement) { var isArray = Array.isArray(replacement); // inherit comments from original node to the first replacement node @@ -95,59 +93,59 @@ Object.defineProperty(TraversalPath.prototype, "node", { this.flatten(); } } -}); -TraversalPath.prototype.call = function (key) { - var node = this.node; - if (!node) return; + call(key) { + var node = this.node; + if (!node) return; - var opts = this.opts; - var fn = opts[key] || opts; - if (opts[node.type]) fn = opts[node.type][key] || fn; + var opts = this.opts; + var fn = opts[key] || opts; + if (opts[node.type]) fn = opts[node.type][key] || fn; - var replacement = fn.call(this, node, this.parent, this.scope, this.state); + var replacement = fn.call(this, node, this.parent, this.scope, this.state); - if (replacement) { - this.node = replacement; + if (replacement) { + this.node = replacement; + } + + if (this.shouldRemove) { + this.container[this.key] = null; + this.flatten(); + } } - if (this.shouldRemove) { - this.container[this.key] = null; - this.flatten(); - } -}; + visit() { + var opts = this.opts; + var node = this.node; -TraversalPath.prototype.visit = function () { - var opts = this.opts; - var node = this.node; + // type is blacklisted + if (opts.blacklist && opts.blacklist.indexOf(node.type) > -1) { + return; + } - // type is blacklisted - if (opts.blacklist && opts.blacklist.indexOf(node.type) > -1) { - return; - } + this.call("enter"); - this.call("enter"); + if (this.shouldSkip) { + return this.shouldStop; + } + + node = this.node; + + if (Array.isArray(node)) { + // traverse over these replacement nodes we purposely don't call exitNode + // as the original node has been destroyed + for (var i = 0; i < node.length; i++) { + traverse.node(node[i], opts, this.scope, this.state, this); + } + } else { + traverse.node(node, opts, this.scope, this.state, this); + this.call("exit"); + } - if (this.shouldSkip) { return this.shouldStop; } - node = this.node; - - if (Array.isArray(node)) { - // traverse over these replacement nodes we purposely don't call exitNode - // as the original node has been destroyed - for (var i = 0; i < node.length; i++) { - traverse.node(node[i], opts, this.scope, this.state, this); - } - } else { - traverse.node(node, opts, this.scope, this.state, this); - this.call("exit"); + isReferencedIdentifier() { + return t.isReferencedIdentifier(this.node); } - - return this.shouldStop; -}; - -TraversalPath.prototype.isReferencedIdentifier = function () { - return t.isReferencedIdentifier(this.node); -}; +} diff --git a/src/babel/traversal/scope.js b/src/babel/traversal/scope.js index 4d5f3d2dbc..9d67184fbf 100644 --- a/src/babel/traversal/scope.js +++ b/src/babel/traversal/scope.js @@ -1,4 +1,4 @@ -module.exports = Scope; + var includes = require("lodash/collection/includes"); var traverse = require("./index"); @@ -11,363 +11,6 @@ var object = require("../helpers/object"); var each = require("lodash/collection/each"); var t = require("../types"); -/** - * This searches the current "scope" and collects all references/bindings - * within. - * - * @param {Node} block - * @param {Node} parentBlock - * @param {Scope} [parent] - * @param {File} [file] - */ - -function Scope(block, parentBlock, parent, file) { - this.parent = parent; - this.file = parent ? parent.file : file; - - this.parentBlock = parentBlock; - this.block = block; - - this.crawl(); -} - -Scope.globals = flatten([globals.builtin, globals.browser, globals.node].map(Object.keys)); - -/** - * Description - * - * @param {Object} node - * @param {Object} opts - * @param [state] - */ - -Scope.prototype.traverse = function (node, opts, state) { - traverse(node, opts, this, state); -}; - -/** - * Description - * - * @param {String} [name="temp"] - */ - -Scope.prototype.generateTemp = function (name) { - var id = this.generateUidIdentifier(name || "temp"); - this.push({ - key: id.name, - id: id - }); - return id; -}; - -/** - * Description - * - * @param {String} name - */ - -Scope.prototype.generateUidIdentifier = function (name) { - var id = t.identifier(this.generateUid(name)); - this.getFunctionParent().registerBinding("uid", id); - return id; -}; - -/** - * Description - * - * @param {String} name - */ - -Scope.prototype.generateUid = function (name) { - name = t.toIdentifier(name).replace(/^_+/, ""); - - var uid; - var i = 0; - do { - uid = this._generateUid(name, i); - i++; - } while (this.hasBinding(uid) || this.hasGlobal(uid)); - return uid; -}; - -Scope.prototype._generateUid = function (name, i) { - var id = name; - if (i > 1) id += i; - return "_" + id; -}; - -/* - * Description - * - * @param {Object} parent - * @returns {Object} - */ - -Scope.prototype.generateUidBasedOnNode = function (parent) { - var node = parent; - - if (t.isAssignmentExpression(parent)) { - node = parent.left; - } else if (t.isVariableDeclarator(parent)) { - node = parent.id; - } else if (t.isProperty(node)) { - node = node.key; - } - - var parts = []; - - var add = function (node) { - if (t.isMemberExpression(node)) { - add(node.object); - add(node.property); - } else if (t.isIdentifier(node)) { - parts.push(node.name); - } else if (t.isLiteral(node)) { - parts.push(node.value); - } else if (t.isCallExpression(node)) { - add(node.callee); - } - }; - - add(node); - - var id = parts.join("$"); - id = id.replace(/^_/, "") || "ref"; - - return this.generateUidIdentifier(id); -}; - -/** - * Description - * - * @param {Object} node - * @returns {Object} - */ - -Scope.prototype.generateTempBasedOnNode = function (node) { - if (t.isIdentifier(node) && this.hasBinding(node.name)) { - return null; - } - - var id = this.generateUidBasedOnNode(node); - this.push({ - key: id.name, - id: id - }); - return id; -}; - -Scope.prototype.checkBlockScopedCollisions = function (kind, name, id) { - var local = this.getOwnBindingInfo(name); - if (!local) return; - - if (kind === "param") return; - if (kind === "hoisted" && local.kind === "let") return; - - if (local.kind === "let" || local.kind === "const" || local.kind === "module") { - throw this.file.errorWithNode(id, messages.get("scopeDuplicateDeclaration", name), TypeError); - } -}; - -Scope.prototype.rename = function (oldName, newName) { - newName ||= this.generateUidIdentifier(oldName).name; - - var info = this.getBindingInfo(oldName); - if (!info) return; - - var binding = info.identifier; - var scope = info.scope; - - scope.traverse(scope.block, { - enter(node, parent, scope) { - if (t.isReferencedIdentifier(node, parent) && node.name === oldName) { - node.name = newName; - } else if (t.isDeclaration(node)) { - var ids = t.getBindingIdentifiers(node); - for (var name in ids) { - if (name === oldName) ids[name].name = newName; - } - } else if (t.isScope(node, parent)) { - if (!scope.bindingIdentifierEquals(oldName, binding)) { - this.skip(); - } - } - } - }); - - this.clearOwnBinding(oldName); - scope.bindings[newName] = info; - - binding.name = newName; -}; - -Scope.prototype.inferType = function (node) { - var target; - - if (t.isVariableDeclarator(node)) { - target = node.init; - } - - if (t.isArrayExpression(target)) { - return t.genericTypeAnnotation(t.identifier("Array")); - } - - if (t.isObjectExpression(target)) { - return; - } - - if (t.isLiteral(target)) { - return; - } - - if (t.isCallExpression(target) && t.isIdentifier(target.callee)) { - var funcInfo = this.getBindingInfo(target.callee.name); - if (funcInfo) { - var funcNode = funcInfo.node; - return !funcInfo.reassigned && t.isFunction(funcNode) && node.returnType; - } - } - - if (t.isIdentifier(target)) { - return; - } -}; - -Scope.prototype.isTypeGeneric = function (name, genericName) { - var info = this.getBindingInfo(name); - if (!info) return false; - - var type = info.typeAnnotation; - return t.isGenericTypeAnnotation(type) && t.isIdentifier(type.id, { name: genericName }); -}; - -Scope.prototype.assignTypeGeneric = function (name, type) { - this.assignType(name, t.genericTypeAnnotation(t.identifier(type))); -}; - -Scope.prototype.assignType = function (name, type) { - var info = this.getBindingInfo(name); - if (!info) return; - - info.identifier.typeAnnotation = info.typeAnnotation = type; -}; - -Scope.prototype.getTypeAnnotation = function (name, id, node) { - var info = { - annotation: null, - inferred: false - }; - - var type; - - if (id.typeAnnotation) { - type = id.typeAnnotation; - } - - if (!type) { - info.inferred = true; - type = this.inferType(node); - } - - if (type) { - if (t.isTypeAnnotation(type)) type = type.typeAnnotation; - info.annotation = type; - } - - return info; -}; - -Scope.prototype.toArray = function (node, i) { - var file = this.file; - - if (t.isIdentifier(node) && this.isTypeGeneric(node.name, "Array")) { - return node; - } - - if (t.isArrayExpression(node)) { - return node; - } - - if (t.isIdentifier(node, { name: "arguments" })) { - return t.callExpression(t.memberExpression(file.addHelper("slice"), t.identifier("call")), [node]); - } - - var helperName = "to-array"; - var args = [node]; - if (i === true) { - helperName = "to-consumable-array"; - } else if (i) { - args.push(t.literal(i)); - helperName = "sliced-to-array"; - } - return t.callExpression(file.addHelper(helperName), args); -}; - -Scope.prototype.clearOwnBinding = function (name) { - delete this.bindings[name]; -}; - -Scope.prototype.registerDeclaration = function (node) { - if (t.isFunctionDeclaration(node)) { - this.registerBinding("hoisted", node); - } else if (t.isVariableDeclaration(node)) { - for (var i = 0; i < node.declarations.length; i++) { - this.registerBinding(node.kind, node.declarations[i]); - } - } else if (t.isClassDeclaration(node)) { - this.registerBinding("let", node); - } else if (t.isImportDeclaration(node) || t.isExportDeclaration(node)) { - this.registerBinding("module", node); - } else { - this.registerBinding("unknown", node); - } -}; - -Scope.prototype.registerBindingReassignment = function (node) { - var ids = t.getBindingIdentifiers(node); - for (var name in ids) { - var info = this.getBindingInfo(name); - if (info) { - info.reassigned = true; - - if (info.typeAnnotationInferred) { - // destroy the inferred typeAnnotation - info.typeAnnotation = null; - } - } - } -}; - -Scope.prototype.registerBinding = function (kind, node) { - if (!kind) throw new ReferenceError("no `kind`"); - - var ids = t.getBindingIdentifiers(node); - - for (var name in ids) { - var id = ids[name]; - - this.checkBlockScopedCollisions(kind, name, id); - - var typeInfo = this.getTypeAnnotation(name, id, node); - - this.bindings[name] = { - typeAnnotationInferred: typeInfo.inferred, - typeAnnotation: typeInfo.annotation, - reassigned: false, - identifier: id, - scope: this, - node: node, - kind: kind - }; - } -}; - -Scope.prototype.registerVariableDeclaration = function (declar) { - var declars = declar.declarations; - for (var i = 0; i < declars.length; i++) { - this.registerBinding(declars[i], declar.kind); - } -}; var functionVariableVisitor = { enter(node, parent, scope, state) { @@ -396,20 +39,6 @@ var functionVariableVisitor = { } }; -Scope.prototype.addGlobal = function (node) { - this.globals[node.name] = node; -}; - -Scope.prototype.hasGlobal = function (name) { - var scope = this; - - do { - if (scope.globals[name]) return true; - } while (scope = scope.parent); - - return false; -}; - var programReferenceVisitor = { enter(node, parent, scope, state) { if (t.isReferencedIdentifier(node, parent) && !scope.hasBinding(node.name)) { @@ -432,213 +61,588 @@ var blockVariableVisitor = { } }; -Scope.prototype.crawl = function () { - var block = this.block; - var i; +export default class Scope { - // + /** + * This searches the current "scope" and collects all references/bindings + * within. + * + * @param {Node} block + * @param {Node} parentBlock + * @param {Scope} [parent] + * @param {File} [file] + */ - var info = block._scopeInfo; - if (info) { - extend(this, info); - return; + constructor(block, parentBlock, parent, file) { + this.parent = parent; + this.file = parent ? parent.file : file; + + this.parentBlock = parentBlock; + this.block = block; + + this.crawl(); } - info = block._scopeInfo = { - bindings: object(), - globals: object() + static globals = flatten([globals.builtin, globals.browser, globals.node].map(Object.keys)); + + /** + * Description + * + * @param {Object} node + * @param {Object} opts + * @param [state] + */ + + traverse(node, opts, state) { + traverse(node, opts, this, state); + } + + /** + * Description + * + * @param {String} [name="temp"] + */ + + generateTemp(name) { + var id = this.generateUidIdentifier(name || "temp"); + this.push({ + key: id.name, + id: id + }); + return id; + } + + /** + * Description + * + * @param {String} name + */ + + generateUidIdentifier(name) { + var id = t.identifier(this.generateUid(name)); + this.getFunctionParent().registerBinding("uid", id); + return id; + } + + /** + * Description + * + * @param {String} name + */ + + generateUid(name) { + name = t.toIdentifier(name).replace(/^_+/, ""); + + var uid; + var i = 0; + do { + uid = this._generateUid(name, i); + i++; + } while (this.hasBinding(uid) || this.hasGlobal(uid)); + return uid; + } + + _generateUid(name, i) { + var id = name; + if (i > 1) id += i; + return "_" + id; + } + + /* + * Description + * + * @param {Object} parent + * @returns {Object} + */ + + generateUidBasedOnNode(parent) { + var node = parent; + + if (t.isAssignmentExpression(parent)) { + node = parent.left; + } else if (t.isVariableDeclarator(parent)) { + node = parent.id; + } else if (t.isProperty(node)) { + node = node.key; + } + + var parts = []; + + var add = function (node) { + if (t.isMemberExpression(node)) { + add(node.object); + add(node.property); + } else if (t.isIdentifier(node)) { + parts.push(node.name); + } else if (t.isLiteral(node)) { + parts.push(node.value); + } else if (t.isCallExpression(node)) { + add(node.callee); + } + }; + + add(node); + + var id = parts.join("$"); + id = id.replace(/^_/, "") || "ref"; + + return this.generateUidIdentifier(id); + } + + /** + * Description + * + * @param {Object} node + * @returns {Object} + */ + + generateTempBasedOnNode(node) { + if (t.isIdentifier(node) && this.hasBinding(node.name)) { + return null; + } + + var id = this.generateUidBasedOnNode(node); + this.push({ + key: id.name, + id: id + }); + return id; + } + + checkBlockScopedCollisions(kind, name, id) { + var local = this.getOwnBindingInfo(name); + if (!local) return; + + if (kind === "param") return; + if (kind === "hoisted" && local.kind === "let") return; + + if (local.kind === "let" || local.kind === "const" || local.kind === "module") { + throw this.file.errorWithNode(id, messages.get("scopeDuplicateDeclaration", name), TypeError); + } + } + + rename(oldName, newName) { + newName ||= this.generateUidIdentifier(oldName).name; + + var info = this.getBindingInfo(oldName); + if (!info) return; + + var binding = info.identifier; + var scope = info.scope; + + scope.traverse(scope.block, { + enter(node, parent, scope) { + if (t.isReferencedIdentifier(node, parent) && node.name === oldName) { + node.name = newName; + } else if (t.isDeclaration(node)) { + var ids = t.getBindingIdentifiers(node); + for (var name in ids) { + if (name === oldName) ids[name].name = newName; + } + } else if (t.isScope(node, parent)) { + if (!scope.bindingIdentifierEquals(oldName, binding)) { + this.skip(); + } + } + } + }); + + this.clearOwnBinding(oldName); + scope.bindings[newName] = info; + + binding.name = newName; + } + + inferType(node) { + var target; + + if (t.isVariableDeclarator(node)) { + target = node.init; + } + + if (t.isArrayExpression(target)) { + return t.genericTypeAnnotation(t.identifier("Array")); + } + + if (t.isObjectExpression(target)) { + return; + } + + if (t.isLiteral(target)) { + return; + } + + if (t.isCallExpression(target) && t.isIdentifier(target.callee)) { + var funcInfo = this.getBindingInfo(target.callee.name); + if (funcInfo) { + var funcNode = funcInfo.node; + return !funcInfo.reassigned && t.isFunction(funcNode) && node.returnType; + } + } + + if (t.isIdentifier(target)) { + return; + } + } + + isTypeGeneric(name, genericName) { + var info = this.getBindingInfo(name); + if (!info) return false; + + var type = info.typeAnnotation; + return t.isGenericTypeAnnotation(type) && t.isIdentifier(type.id, { name: genericName }); + } + + assignTypeGeneric(name, type) { + this.assignType(name, t.genericTypeAnnotation(t.identifier(type))); + } + + assignType(name, type) { + var info = this.getBindingInfo(name); + if (!info) return; + + info.identifier.typeAnnotation = info.typeAnnotation = type; + } + + getTypeAnnotation(name, id, node) { + var info = { + annotation: null, + inferred: false + }; + + var type; + + if (id.typeAnnotation) { + type = id.typeAnnotation; + } + + if (!type) { + info.inferred = true; + type = this.inferType(node); + } + + if (type) { + if (t.isTypeAnnotation(type)) type = type.typeAnnotation; + info.annotation = type; + } + + return info; + } + + toArray(node, i) { + var file = this.file; + + if (t.isIdentifier(node) && this.isTypeGeneric(node.name, "Array")) { + return node; + } + + if (t.isArrayExpression(node)) { + return node; + } + + if (t.isIdentifier(node, { name: "arguments" })) { + return t.callExpression(t.memberExpression(file.addHelper("slice"), t.identifier("call")), [node]); + } + + var helperName = "to-array"; + var args = [node]; + if (i === true) { + helperName = "to-consumable-array"; + } else if (i) { + args.push(t.literal(i)); + helperName = "sliced-to-array"; + } + return t.callExpression(file.addHelper(helperName), args); + } + + clearOwnBinding(name) { + delete this.bindings[name]; + } + + registerDeclaration(node) { + if (t.isFunctionDeclaration(node)) { + this.registerBinding("hoisted", node); + } else if (t.isVariableDeclaration(node)) { + for (var i = 0; i < node.declarations.length; i++) { + this.registerBinding(node.kind, node.declarations[i]); + } + } else if (t.isClassDeclaration(node)) { + this.registerBinding("let", node); + } else if (t.isImportDeclaration(node) || t.isExportDeclaration(node)) { + this.registerBinding("module", node); + } else { + this.registerBinding("unknown", node); + } + } + + registerBindingReassignment(node) { + var ids = t.getBindingIdentifiers(node); + for (var name in ids) { + var info = this.getBindingInfo(name); + if (info) { + info.reassigned = true; + + if (info.typeAnnotationInferred) { + // destroy the inferred typeAnnotation + info.typeAnnotation = null; + } + } + } + } + + registerBinding(kind, node) { + if (!kind) throw new ReferenceError("no `kind`"); + + var ids = t.getBindingIdentifiers(node); + + for (var name in ids) { + var id = ids[name]; + + this.checkBlockScopedCollisions(kind, name, id); + + var typeInfo = this.getTypeAnnotation(name, id, node); + + this.bindings[name] = { + typeAnnotationInferred: typeInfo.inferred, + typeAnnotation: typeInfo.annotation, + reassigned: false, + identifier: id, + scope: this, + node: node, + kind: kind + }; + } + } + + registerVariableDeclaration(declar) { + var declars = declar.declarations; + for (var i = 0; i < declars.length; i++) { + this.registerBinding(declars[i], declar.kind); + } + } + + addGlobal(node) { + this.globals[node.name] = node; }; - extend(this, info); + hasGlobal(name) { + var scope = this; - // ForStatement - left, init + do { + if (scope.globals[name]) return true; + } while (scope = scope.parent); - if (t.isLoop(block)) { - for (i = 0; i < t.FOR_INIT_KEYS.length; i++) { - var node = block[t.FOR_INIT_KEYS[i]]; - if (t.isBlockScoped(node)) this.registerBinding("let", node); + return false; + } + + crawl() { + var block = this.block; + var i; + + // + + var info = block._scopeInfo; + if (info) { + extend(this, info); + return; } - if (t.isBlockStatement(block.body)) { + info = block._scopeInfo = { + bindings: object(), + globals: object() + }; + + extend(this, info); + + // ForStatement - left, init + + if (t.isLoop(block)) { + for (i = 0; i < t.FOR_INIT_KEYS.length; i++) { + var node = block[t.FOR_INIT_KEYS[i]]; + if (t.isBlockScoped(node)) this.registerBinding("let", node); + } + + if (t.isBlockStatement(block.body)) { + block = block.body; + } + } + + // FunctionExpression - id + + if (t.isFunctionExpression(block) && block.id) { + if (!t.isProperty(this.parentBlock, { method: true })) { + this.registerBinding("var", block.id); + } + } + + // Function - params, rest + + if (t.isFunction(block)) { + for (i = 0; i < block.params.length; i++) { + this.registerBinding("param", block.params[i]); + } + this.traverse(block.body, blockVariableVisitor, this); + } + + // Program, BlockStatement, Function - let variables + + if (t.isBlockStatement(block) || t.isProgram(block)) { + this.traverse(block, blockVariableVisitor, this); + } + + // CatchClause - param + + if (t.isCatchClause(block)) { + this.registerBinding("let", block.param); + } + + // ComprehensionExpression - blocks + + if (t.isComprehensionExpression(block)) { + this.registerBinding("let", block); + } + + // Program, Function - var variables + + if (t.isProgram(block) || t.isFunction(block)) { + this.traverse(block, functionVariableVisitor, { + blockId: block.id, + scope: this + }); + } + + // Program + + if (t.isProgram(block)) { + this.traverse(block, programReferenceVisitor, this); + } + } + + /** + * Description + * + * @param {Object} opts + */ + + push(opts) { + var block = this.block; + + if (t.isLoop(block) || t.isCatchClause(block) || t.isFunction(block)) { + t.ensureBlock(block); block = block.body; } - } - // FunctionExpression - id - - if (t.isFunctionExpression(block) && block.id) { - if (!t.isProperty(this.parentBlock, { method: true })) { - this.registerBinding("var", block.id); + if (t.isBlockStatement(block) || t.isProgram(block)) { + block._declarations ||= {}; + block._declarations[opts.key] = { + kind: opts.kind, + id: opts.id, + init: opts.init + }; + } else { + throw new TypeError("cannot add a declaration here in node type " + block.type); } } - // Function - params, rest + /** + * Walk up the scope tree until we hit either a Function or reach the + * very top and hit Program. + */ - if (t.isFunction(block)) { - for (i = 0; i < block.params.length; i++) { - this.registerBinding("param", block.params[i]); + getFunctionParent() { + var scope = this; + while (scope.parent && !t.isFunction(scope.block)) { + scope = scope.parent; } - this.traverse(block.body, blockVariableVisitor, this); + return scope; } - // Program, BlockStatement, Function - let variables + /** + * Walks the scope tree and gathers **all** bindings. + * + * @returns {Object} + */ - if (t.isBlockStatement(block) || t.isProgram(block)) { - this.traverse(block, blockVariableVisitor, this); + getAllBindings() { + var ids = object(); + + var scope = this; + do { + defaults(ids, scope.bindings); + scope = scope.parent; + } while (scope); + + return ids; } - // CatchClause - param + /** + * Walks the scope tree and gathers all declarations of `kind`. + * + * @param {String} kind + * @returns {Object} + */ - if (t.isCatchClause(block)) { - this.registerBinding("let", block.param); + getAllBindingsOfKind(kind) { + var ids = object(); + + var scope = this; + do { + for (var name in scope.bindings) { + var binding = scope.bindings[name]; + if (binding.kind === kind) ids[name] = binding; + } + scope = scope.parent; + } while (scope); + + return ids; } - // ComprehensionExpression - blocks + // misc - if (t.isComprehensionExpression(block)) { - this.registerBinding("let", block); + bindingIdentifierEquals(name, node) { + return this.getBindingIdentifier(name) === node; } - // Program, Function - var variables + // get - if (t.isProgram(block) || t.isFunction(block)) { - this.traverse(block, functionVariableVisitor, { - blockId: block.id, - scope: this - }); + getBindingInfo(name) { + var scope = this; + + do { + var binding = scope.getOwnBindingInfo(name); + if (binding) return binding; + } while (scope = scope.parent); } - // Program - - if (t.isProgram(block)) { - this.traverse(block, programReferenceVisitor, this); - } -}; - -/** - * Description - * - * @param {Object} opts - */ - -Scope.prototype.push = function (opts) { - var block = this.block; - - if (t.isLoop(block) || t.isCatchClause(block) || t.isFunction(block)) { - t.ensureBlock(block); - block = block.body; + getOwnBindingInfo(name) { + return this.bindings[name]; } - if (t.isBlockStatement(block) || t.isProgram(block)) { - block._declarations ||= {}; - block._declarations[opts.key] = { - kind: opts.kind, - id: opts.id, - init: opts.init - }; - } else { - throw new TypeError("cannot add a declaration here in node type " + block.type); + getBindingIdentifier(name) { + var info = this.getBindingInfo(name); + return info && info.identifier; } -}; -/** - * Walk up the scope tree until we hit either a Function or reach the - * very top and hit Program. - */ - -Scope.prototype.getFunctionParent = function () { - var scope = this; - while (scope.parent && !t.isFunction(scope.block)) { - scope = scope.parent; + getOwnBindingIdentifier(name) { + var binding = this.bindings[name]; + return binding && binding.identifier; } - return scope; -}; -/** - * Walks the scope tree and gathers **all** bindings. - * - * @returns {Object} - */ + // has -Scope.prototype.getAllBindings = function () { - var ids = object(); + hasOwnBinding(name) { + return !!this.getOwnBindingInfo(name); + } - var scope = this; - do { - defaults(ids, scope.bindings); - scope = scope.parent; - } while (scope); + hasBinding(name) { + if (!name) return false; + if (this.hasOwnBinding(name)) return true; + if (this.parentHasBinding(name)) return true; + if (includes(Scope.globals, name)) return true; + return false; + } - return ids; -}; - -/** - * Walks the scope tree and gathers all declarations of `kind`. - * - * @param {String} kind - * @returns {Object} - */ - -Scope.prototype.getAllBindingsOfKind = function (kind) { - var ids = object(); - - var scope = this; - do { - for (var name in scope.bindings) { - var binding = scope.bindings[name]; - if (binding.kind === kind) ids[name] = binding; - } - scope = scope.parent; - } while (scope); - - return ids; -}; - -// misc - -Scope.prototype.bindingIdentifierEquals = function (name, node) { - return this.getBindingIdentifier(name) === node; -}; - -// get - -Scope.prototype.getBindingInfo = function (name) { - var scope = this; - - do { - var binding = scope.getOwnBindingInfo(name); - if (binding) return binding; - } while (scope = scope.parent); -}; - -Scope.prototype.getOwnBindingInfo = function (name) { - return this.bindings[name]; -}; - -Scope.prototype.getBindingIdentifier = function (name) { - var info = this.getBindingInfo(name); - return info && info.identifier; -}; - -Scope.prototype.getOwnBindingIdentifier = function (name) { - var binding = this.bindings[name]; - return binding && binding.identifier; -}; - -// has - -Scope.prototype.hasOwnBinding = function (name) { - return !!this.getOwnBindingInfo(name); -}; - -Scope.prototype.hasBinding = function (name) { - if (!name) return false; - if (this.hasOwnBinding(name)) return true; - if (this.parentHasBinding(name)) return true; - if (includes(Scope.globals, name)) return true; - return false; -}; - -Scope.prototype.parentHasBinding = function (name) { - return this.parent && this.parent.hasBinding(name); -}; + parentHasBinding(name) { + return this.parent && this.parent.hasBinding(name); + } +}