/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // jscodeshift rule to replace EXPORTED_SYMBOLS with export declarations, // and also convert existing ChromeUtils.importESModule to static import. /* eslint-env node */ const _path = require("path"); const { warnForPath, getPrevStatement, getNextStatement, } = require(_path.resolve(__dirname, "./utils.js")); const { isImportESModuleCall, replaceImportESModuleCall, } = require(_path.resolve(__dirname, "./static-import.js")); module.exports = function (fileInfo, api) { const { jscodeshift } = api; const root = jscodeshift(fileInfo.source); doTranslate(fileInfo.path, jscodeshift, root); return root.toSource({ lineTerminator: "\n" }); }; module.exports.doTranslate = doTranslate; // Move the comment for `path.node` to adjacent statement, keeping the position // as much as possible. function moveComments(inputFile, path) { const next = getNextStatement(path); if (next) { if (next.comments) { next.comments = [...path.node.comments, ...next.comments]; } else { next.comments = path.node.comments; } path.node.comments = []; return; } const prev = getPrevStatement(path); if (prev) { path.node.comments.forEach(c => { c.leading = false; c.trailing = true; }); if (prev.comments) { prev.comments = [...prev.comments, ...path.node.comments]; } else { prev.comments = path.node.comments; } path.node.comments = []; return; } warnForPath( inputFile, path, `EXPORTED_SYMBOLS has comments and it cannot be preserved` ); } function collectAndRemoveExportedSymbols(inputFile, root) { const nodes = root.findVariableDeclarators("EXPORTED_SYMBOLS"); if (!nodes.length) { throw Error(`EXPORTED_SYMBOLS not found`); } let path = nodes.get(0); const obj = nodes.get(0).node.init; if (!obj) { throw Error(`EXPORTED_SYMBOLS is not statically known`); } if (path.parent.node.declarations.length !== 1) { throw Error(`EXPORTED_SYMBOLS shouldn't be declared with other variables`); } if (path.parent.node.comments && path.parent.node.comments.length) { moveComments(inputFile, path.parent); } path.parent.prune(); const EXPORTED_SYMBOLS = new Set(); if (obj.type !== "ArrayExpression") { throw Error(`EXPORTED_SYMBOLS is not statically known`); } for (const elem of obj.elements) { if (elem.type !== "Literal") { throw Error(`EXPORTED_SYMBOLS is not statically known`); } var name = elem.value; if (typeof name !== "string") { throw Error(`EXPORTED_SYMBOLS item must be a string`); } EXPORTED_SYMBOLS.add(name); } return EXPORTED_SYMBOLS; } function isTopLevel(path) { return path.parent.node.type === "Program"; } function convertToExport(jscodeshift, path, name) { const e = jscodeshift.exportNamedDeclaration(path.node); e.comments = []; e.comments = path.node.comments; path.node.comments = []; path.replace(e); } function doTranslate(inputFile, jscodeshift, root) { const EXPORTED_SYMBOLS = collectAndRemoveExportedSymbols(inputFile, root); root.find(jscodeshift.FunctionDeclaration).forEach(path => { if (!isTopLevel(path)) { return; } const name = path.node.id.name; if (!EXPORTED_SYMBOLS.has(name)) { return; } EXPORTED_SYMBOLS.delete(name); convertToExport(jscodeshift, path, name); }); root.find(jscodeshift.ClassDeclaration).forEach(path => { if (!isTopLevel(path)) { return; } const name = path.node.id.name; if (!EXPORTED_SYMBOLS.has(name)) { return; } EXPORTED_SYMBOLS.delete(name); convertToExport(jscodeshift, path, name); }); root.find(jscodeshift.VariableDeclaration).forEach(path => { if (!isTopLevel(path)) { return; } let exists = false; let name; for (const decl of path.node.declarations) { if (decl.id.type === "Identifier") { name = decl.id.name; if (EXPORTED_SYMBOLS.has(name)) { exists = true; break; } } if (decl.id.type === "ObjectPattern") { if (decl.id.properties.length === 1) { const prop = decl.id.properties[0]; if (prop.shorthand) { if (prop.key.type === "Identifier") { name = prop.key.name; if (EXPORTED_SYMBOLS.has(name)) { exists = true; break; } } } } } } if (!exists) { return; } if (path.node.declarations.length !== 1) { throw Error( `exported variable shouldn't be declared with other variables` ); } EXPORTED_SYMBOLS.delete(name); convertToExport(jscodeshift, path, name); }); if (EXPORTED_SYMBOLS.size !== 0) { throw Error( `exported symbols ${[...EXPORTED_SYMBOLS].join(", ")} not found` ); } root.find(jscodeshift.CallExpression).forEach(path => { if (isImportESModuleCall(path.node)) { // This file is not yet renamed. Skip the extension check. // Also skip the isTargetESM. replaceImportESModuleCall(inputFile, jscodeshift, path, true); } }); }