diff options
Diffstat (limited to 'tools/esmify/use-import-export-declarations.js')
-rw-r--r-- | tools/esmify/use-import-export-declarations.js | 208 |
1 files changed, 208 insertions, 0 deletions
diff --git a/tools/esmify/use-import-export-declarations.js b/tools/esmify/use-import-export-declarations.js new file mode 100644 index 0000000000..fe8c4dd286 --- /dev/null +++ b/tools/esmify/use-import-export-declarations.js @@ -0,0 +1,208 @@ +/* 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); + } + }); +} |