Add wrapPluginVisitorMethod option to allow introspection and metrics tracking of plugins (#3659)

This commit is contained in:
Sebastian McKenzie 2016-08-20 15:36:52 +01:00 committed by Henry Zhu
parent ea69362249
commit 07b3dc18a0
8 changed files with 67 additions and 18 deletions

View File

@ -442,7 +442,11 @@ export default class File extends Store {
const pluginPasses = this.pluginPasses[i]; const pluginPasses = this.pluginPasses[i];
this.call("pre", pluginPasses); this.call("pre", pluginPasses);
this.log.debug("Start transform traverse"); 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.log.debug("End transform traverse");
this.call("post", pluginPasses); this.call("post", pluginPasses);
} }

View File

@ -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" 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: { compact: {
type: "booleanString", type: "booleanString",
default: "auto", default: "auto",

View File

@ -13,6 +13,8 @@ export default new Plugin({
* - 3 We want this to be at the **very** top * - 3 We want this to be at the **very** top
*/ */
name: "internal.blockHoist",
visitor: { visitor: {
Block: { Block: {
exit({ node }) { exit({ node }) {

View File

@ -16,6 +16,8 @@ const superVisitor = {
}; };
export default new Plugin({ export default new Plugin({
name: "internal.shadowFunctions",
visitor: { visitor: {
ThisExpression(path) { ThisExpression(path) {
remap(path, "this"); remap(path, "this");

View File

@ -1,27 +1,21 @@
import type Plugin from "./plugin"; import type Plugin from "./plugin";
import Store from "../store"; import Store from "../store";
import traverse from "babel-traverse";
import File from "./file"; import File from "./file";
export default class PluginPass extends Store { export default class PluginPass extends Store {
constructor(file: File, plugin: Plugin, options: Object = {}) { constructor(file: File, plugin: Plugin, options: Object = {}) {
super(); super();
this.plugin = plugin; this.plugin = plugin;
this.key = plugin.key;
this.file = file; this.file = file;
this.opts = options; this.opts = options;
} }
key: string;
plugin: Plugin; plugin: Plugin;
file: File; file: File;
opts: Object; 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) { addHelper(...args) {
return this.file.addHelper(...args); return this.file.addHelper(...args);
} }

View File

@ -15,7 +15,7 @@ export default class Plugin extends Store {
this.initialized = false; this.initialized = false;
this.raw = assign({}, plugin); this.raw = assign({}, plugin);
this.key = key; this.key = this.take("name") || key;
this.manipulateOptions = this.take("manipulateOptions"); this.manipulateOptions = this.take("manipulateOptions");
this.post = this.take("post"); this.post = this.take("post");

View File

@ -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 () { test("pass per preset", function () {
var aliasBaseType = null; var aliasBaseType = null;

View File

@ -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 = {}; let rootVisitor = {};
for (let i = 0; i < visitors.length; i++) { for (let i = 0; i < visitors.length; i++) {
@ -174,8 +174,10 @@ export function merge(visitors: Array, states: Array = []) {
for (let type in visitor) { for (let type in visitor) {
let visitorType = visitor[type]; let visitorType = visitor[type];
// if we have state then overload the callbacks to take it // if we have state or wrapper then overload the callbacks to take it
if (state) visitorType = wrapWithState(visitorType, state); if (state || wrapper) {
visitorType = wrapWithStateOrWrapper(visitorType, state, wrapper);
}
let nodeVisitor = rootVisitor[type] = rootVisitor[type] || {}; let nodeVisitor = rootVisitor[type] = rootVisitor[type] || {};
mergePair(nodeVisitor, visitorType); mergePair(nodeVisitor, visitorType);
@ -185,7 +187,7 @@ export function merge(visitors: Array, states: Array = []) {
return rootVisitor; return rootVisitor;
} }
function wrapWithState(oldVisitor, state) { function wrapWithStateOrWrapper(oldVisitor, state, wrapper: ?Function) {
let newVisitor = {}; let newVisitor = {};
for (let key in oldVisitor) { for (let key in oldVisitor) {
@ -195,10 +197,18 @@ function wrapWithState(oldVisitor, state) {
if (!Array.isArray(fns)) continue; if (!Array.isArray(fns)) continue;
fns = fns.map(function (fn) { fns = fns.map(function (fn) {
let newFn = function (path) { let newFn = fn;
return fn.call(state, path, state);
}; if (state) {
newFn.toString = () => fn.toString(); newFn = function (path) {
return fn.call(state, path, state);
};
}
if (wrapper) {
newFn = wrapper(state.key, key, newFn);
}
return newFn; return newFn;
}); });