diff --git a/packages/babel-core/src/transformation/file/index.js b/packages/babel-core/src/transformation/file/index.js index e4f5dbcbcb..21913e8db7 100644 --- a/packages/babel-core/src/transformation/file/index.js +++ b/packages/babel-core/src/transformation/file/index.js @@ -442,7 +442,11 @@ export default class File extends Store { const pluginPasses = this.pluginPasses[i]; this.call("pre", pluginPasses); this.log.debug("Start transform traverse"); - traverse(this.ast, traverse.visitors.merge(this.pluginVisitors[i], pluginPasses), this.scope); + + // merge all plugin visitors into a single visitor + let visitor = traverse.visitors.merge(this.pluginVisitors[i], pluginPasses, this.opts.wrapPluginVisitorMethod); + traverse(this.ast, visitor, this.scope); + this.log.debug("End transform traverse"); this.call("post", pluginPasses); } diff --git a/packages/babel-core/src/transformation/file/options/config.js b/packages/babel-core/src/transformation/file/options/config.js index 56c98c9589..18638da688 100644 --- a/packages/babel-core/src/transformation/file/options/config.js +++ b/packages/babel-core/src/transformation/file/options/config.js @@ -102,6 +102,11 @@ module.exports = { description: "optional callback to control whether a comment should be inserted, when this is used the comments option is ignored" }, + wrapPluginVisitorMethod: { + hidden: true, + description: "optional callback to wrap all visitor methods" + }, + compact: { type: "booleanString", default: "auto", diff --git a/packages/babel-core/src/transformation/internal-plugins/block-hoist.js b/packages/babel-core/src/transformation/internal-plugins/block-hoist.js index afd312b1de..de8bedf5bb 100644 --- a/packages/babel-core/src/transformation/internal-plugins/block-hoist.js +++ b/packages/babel-core/src/transformation/internal-plugins/block-hoist.js @@ -13,6 +13,8 @@ export default new Plugin({ * - 3 We want this to be at the **very** top */ + name: "internal.blockHoist", + visitor: { Block: { exit({ node }) { diff --git a/packages/babel-core/src/transformation/internal-plugins/shadow-functions.js b/packages/babel-core/src/transformation/internal-plugins/shadow-functions.js index a6e2ae6ccd..818ee5cf0f 100644 --- a/packages/babel-core/src/transformation/internal-plugins/shadow-functions.js +++ b/packages/babel-core/src/transformation/internal-plugins/shadow-functions.js @@ -16,6 +16,8 @@ const superVisitor = { }; export default new Plugin({ + name: "internal.shadowFunctions", + visitor: { ThisExpression(path) { remap(path, "this"); diff --git a/packages/babel-core/src/transformation/plugin-pass.js b/packages/babel-core/src/transformation/plugin-pass.js index 10b7d9381b..1d1d60b01b 100644 --- a/packages/babel-core/src/transformation/plugin-pass.js +++ b/packages/babel-core/src/transformation/plugin-pass.js @@ -1,27 +1,21 @@ import type Plugin from "./plugin"; import Store from "../store"; -import traverse from "babel-traverse"; import File from "./file"; export default class PluginPass extends Store { constructor(file: File, plugin: Plugin, options: Object = {}) { super(); this.plugin = plugin; + this.key = plugin.key; this.file = file; this.opts = options; } + key: string; plugin: Plugin; file: File; opts: Object; - transform() { - let file = this.file; - file.log.debug(`Start transformer ${this.key}`); - traverse(file.ast, this.plugin.visitor, file.scope, file); - file.log.debug(`Finish transformer ${this.key}`); - } - addHelper(...args) { return this.file.addHelper(...args); } diff --git a/packages/babel-core/src/transformation/plugin.js b/packages/babel-core/src/transformation/plugin.js index e378fb68d9..afca4c882b 100644 --- a/packages/babel-core/src/transformation/plugin.js +++ b/packages/babel-core/src/transformation/plugin.js @@ -15,7 +15,7 @@ export default class Plugin extends Store { this.initialized = false; this.raw = assign({}, plugin); - this.key = key; + this.key = this.take("name") || key; this.manipulateOptions = this.take("manipulateOptions"); this.post = this.take("post"); diff --git a/packages/babel-core/test/api.js b/packages/babel-core/test/api.js index 18ddd0d1b9..3e79693e2b 100644 --- a/packages/babel-core/test/api.js +++ b/packages/babel-core/test/api.js @@ -76,6 +76,38 @@ suite("api", function () { }); }); + test("option wrapPluginVisitorMethod", function () { + var calledRaw = 0; + var calledIntercept = 0; + + babel.transform("function foo() { bar(foobar); }", { + wrapPluginVisitorMethod: function (pluginAlias, visitorType, callback) { + if (pluginAlias !== "foobar") { + return callback; + } + + assert.equal(visitorType, "enter"); + + return function () { + calledIntercept++; + return callback.apply(this, arguments); + }; + }, + + plugins: [new Plugin({ + name: "foobar", + visitor: { + "Program|Identifier": function () { + calledRaw++; + } + } + })] + }); + + assert.equal(calledRaw, 4); + assert.equal(calledIntercept, 4); + }); + test("pass per preset", function () { var aliasBaseType = null; diff --git a/packages/babel-traverse/src/visitors.js b/packages/babel-traverse/src/visitors.js index e67824995b..023cdb186c 100644 --- a/packages/babel-traverse/src/visitors.js +++ b/packages/babel-traverse/src/visitors.js @@ -162,7 +162,7 @@ function validateVisitorMethods(path, val) { } } -export function merge(visitors: Array, states: Array = []) { +export function merge(visitors: Array, states: Array = [], wrapper?: ?Function) { let rootVisitor = {}; for (let i = 0; i < visitors.length; i++) { @@ -174,8 +174,10 @@ export function merge(visitors: Array, states: Array = []) { for (let type in visitor) { let visitorType = visitor[type]; - // if we have state then overload the callbacks to take it - if (state) visitorType = wrapWithState(visitorType, state); + // if we have state or wrapper then overload the callbacks to take it + if (state || wrapper) { + visitorType = wrapWithStateOrWrapper(visitorType, state, wrapper); + } let nodeVisitor = rootVisitor[type] = rootVisitor[type] || {}; mergePair(nodeVisitor, visitorType); @@ -185,7 +187,7 @@ export function merge(visitors: Array, states: Array = []) { return rootVisitor; } -function wrapWithState(oldVisitor, state) { +function wrapWithStateOrWrapper(oldVisitor, state, wrapper: ?Function) { let newVisitor = {}; for (let key in oldVisitor) { @@ -195,10 +197,18 @@ function wrapWithState(oldVisitor, state) { if (!Array.isArray(fns)) continue; fns = fns.map(function (fn) { - let newFn = function (path) { - return fn.call(state, path, state); - }; - newFn.toString = () => fn.toString(); + let newFn = fn; + + if (state) { + newFn = function (path) { + return fn.call(state, path, state); + }; + } + + if (wrapper) { + newFn = wrapper(state.key, key, newFn); + } + return newFn; });