summaryrefslogtreecommitdiffstats
path: root/tools/esmify/use-import-export-declarations.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/esmify/use-import-export-declarations.js')
-rw-r--r--tools/esmify/use-import-export-declarations.js208
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);
+ }
+ });
+}