// The MIT License // Copyright (c) Kate Hudson // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. "use strict"; const DEFAULT_OPTIONS = { // Only Cu.imports matching the following pattern will be rewritten as import statements. basePath: /^resource:\/\//, // Should the import path be rewritten to exclude the basePath? // e.g. if the basePath is "resource://}, "resource://foo.jsm" becomes "foo.jsm" replace: false, }; module.exports = function plugin(babel) { const t = babel.types; let exportItems = []; // module.exports = {foo, bar, baz}; function createModuleExports(exportedIdentifiers) { const left = t.memberExpression( t.identifier("module"), t.identifier("exports") ); const right = t.objectExpression( exportedIdentifiers.map(item => { return t.objectProperty( t.identifier(item), t.identifier(item), false, true ); // return typeof item === "string" ? t.objectProperty(item, item) : t.objectProperty(item[0], item[1]); }) ); return t.expressionStatement(t.assignmentExpression("=", left, right)); } function replaceExports(nodes) { nodes.forEach(path => { if ( path.isVariableDeclaration() && path.get("declarations")[0] && path.get("declarations")[0].node.id.name === "EXPORTED_SYMBOLS" ) { exportItems = path .get("declarations")[0] .get("init.elements") .map(e => e.node.value); path.remove(); } else if ( path.isExpressionStatement() && path.get("expression").isAssignmentExpression() && path.get("expression.left.object").isThisExpression() && path.get("expression.left.property").isIdentifier() ) { const left = path.node.expression.left.property; const { right } = path.node.expression; if (left.name === "EXPORTED_SYMBOLS") { // const names = right.elements.map(el => el.value); // path.replaceWith(createModuleExports(names)); exportItems = right.elements.map(el => el.value); path.remove(); } else { const decl = t.variableDeclaration("var", [ t.variableDeclarator(left, right), ]); if (left.name === right.name) { path.remove(); } else { path.replaceWith(decl); } } } }); } function checkForDeclarations(nodes, id, initFinalResults = []) { const results = []; let finalResults = [...initFinalResults]; nodes.forEach(parentPath => { if (!parentPath.isVariableDeclaration()) return; parentPath.traverse({ VariableDeclarator(path) { if (!path.get("id").isIdentifier()) return; const init = path.get("init"); if (init.isIdentifier() && init.node.name === id) { results.push(path.node.id.name); } }, }); }); if (results.length) { finalResults.push(...results); results.forEach(name => { finalResults = checkForDeclarations(nodes, name, finalResults); }); return finalResults; } return finalResults; } function checkForUtilsDeclarations(nodes, ids) { const results = []; nodes.forEach(parentPath => { if (!parentPath.isVariableDeclaration()) return; parentPath.traverse({ VariableDeclarator(path) { const id = path.get("id"); const init = path.get("init"); // const {utils} = Components; if ( id.isObjectPattern() && init.isIdentifier() && ids.includes(init.node.name) ) { id.node.properties.forEach(prop => { if (prop.key.name === "utils") { results.push(prop.value.name); } }); } // const foo = Components.utils; else if ( id.isIdentifier() && init.isMemberExpression() && init.get("object").isIdentifier() && ids.includes(init.get("object").node.name) && init.get("property").isIdentifier() && init.get("property").node.name === "utils" ) { results.push(id.node.name); } }, }); }); return results; } function replaceImports( nodes, ComponentNames, CuNames, basePath, replacePath, removeOtherImports ) { nodes.forEach(p => { if (!p.isVariableDeclaration()) return; p.traverse({ CallExpression(path) { if ( t.isStringLiteral(path.node.arguments[0]) && // path.node.arguments[0].value.match(basePath) && t.isObjectPattern(path.parentPath.node.id) && // Check if actually Components.utils.import path.get("callee").isMemberExpression() && (path.get("callee.property").node.name === "import" || path.get("callee.property").node.name === "importESModule") ) { const callee = path.get("callee"); if (callee.get("object").isMemberExpression()) { if ( !ComponentNames.includes( callee.get("object.object").node.name ) || callee.get("object.property").node.name !== "utils" ) { return; } } else { const objectName = callee.get("object").node.name; if ( objectName !== "ChromeUtils" && !CuNames.includes(objectName) ) { return; } } let filePath = path.node.arguments[0].value; if ( !removeOtherImports || (replacePath && filePath.match(basePath)) ) { if (replacePath) filePath = filePath.replace(basePath, ""); const requireStatement = t.callExpression( t.identifier("require"), [t.stringLiteral(filePath)] ); path.replaceWith(requireStatement); } else if (removeOtherImports) { path.parentPath.parentPath.remove(); } } }, }); }); } function replaceModuleGetters(paths, basePath, replacePath) { paths.forEach(path => { if ( path.isExpressionStatement() && path.get("expression").isCallExpression() && ["XPCOMUtils", "ChromeUtils"].includes( path.get("expression.callee.object.name").node ) && ["defineLazyModuleGetter", "defineModuleGetter"].includes( path.get("expression.callee.property.name").node ) ) { const argPaths = path.get("expression.arguments"); const idName = argPaths[1].node.value; let filePath = argPaths[2].node.value; if (!filePath.match(basePath)) return; if (replacePath) filePath = filePath.replace(basePath, ""); const requireStatement = t.callExpression(t.identifier("require"), [ t.stringLiteral(filePath), ]); const varDecl = t.variableDeclaration("var", [ t.variableDeclarator( t.objectPattern([ t.objectProperty( t.identifier(idName), t.identifier(idName), false, true ), ]), requireStatement ), ]); path.replaceWith(varDecl); } }); } return { visitor: { Program(path, state) { const opts = Object.assign({}, DEFAULT_OPTIONS, state.opts); const topLevelNodes = path.get("body"); const ids = checkForDeclarations(topLevelNodes, "Components", [ "Components", ]); const utils = checkForUtilsDeclarations(topLevelNodes, ids); replaceImports( topLevelNodes, ids, utils, opts.basePath, opts.replace, opts.removeOtherImports ); replaceModuleGetters(topLevelNodes, opts.basePath, opts.replace); replaceExports(topLevelNodes); if (exportItems.length) { path.pushContainer("body", createModuleExports(exportItems)); } }, }, }; };