diff options
Diffstat (limited to 'tools/esmify/static-import.js')
-rw-r--r-- | tools/esmify/static-import.js | 147 |
1 files changed, 147 insertions, 0 deletions
diff --git a/tools/esmify/static-import.js b/tools/esmify/static-import.js new file mode 100644 index 0000000000..e99bfb3380 --- /dev/null +++ b/tools/esmify/static-import.js @@ -0,0 +1,147 @@ +/* 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/. */ + +/* eslint-env node */ + +const _path = require("path"); +const { getESMFiles } = require(_path.resolve(__dirname, "./is-esmified.js")); +const { + esmifyExtension, + isString, + warnForPath, + isMemberExpressionWithIdentifiers, +} = require(_path.resolve(__dirname, "./utils.js")); + +function isTargetESM(resourceURI) { + if ("ESMIFY_TARGET_PREFIX" in process.env) { + const files = getESMFiles(resourceURI); + const targetPrefix = process.env.ESMIFY_TARGET_PREFIX; + for (const esm of files) { + if (esm.startsWith(targetPrefix)) { + return true; + } + } + + return false; + } + + return true; +} + +function isImportESModuleCall(node) { + return isMemberExpressionWithIdentifiers(node.callee, [ + "ChromeUtils", + "importESModule", + ]); +} + +// Replace `ChromeUtils.import`, `Cu.import`, and `ChromeUtils.importESModule` +// with static import if it's at the top-level of system ESM file. +function tryReplacingWithStaticImport( + jscodeshift, + inputFile, + path, + resourceURINode, + alwaysReplace +) { + if (!alwaysReplace && !inputFile.endsWith(".sys.mjs")) { + // Static import is available only in system ESM. + return false; + } + + // Check if it's at the top-level. + if (path.parent.node.type !== "VariableDeclarator") { + return false; + } + + if (path.parent.parent.node.type !== "VariableDeclaration") { + return false; + } + + const decls = path.parent.parent.node; + if (decls.declarations.length !== 1) { + return false; + } + + if (path.parent.parent.parent.node.type !== "Program") { + return false; + } + + if (path.node.arguments.length !== 1) { + return false; + } + + const resourceURI = resourceURINode.value; + + // Collect imported symbols. + const specs = []; + if (path.parent.node.id.type === "Identifier") { + specs.push(jscodeshift.importNamespaceSpecifier(path.parent.node.id)); + } else if (path.parent.node.id.type === "ObjectPattern") { + for (const prop of path.parent.node.id.properties) { + if (prop.shorthand) { + specs.push(jscodeshift.importSpecifier(prop.key)); + } else if (prop.value.type === "Identifier") { + specs.push(jscodeshift.importSpecifier(prop.key, prop.value)); + } else { + return false; + } + } + } else { + return false; + } + + // If this is `ChromeUtils.import` or `Cu.import`, replace the extension. + // no-op for `ChromeUtils.importESModule`. + resourceURINode.value = esmifyExtension(resourceURI); + + const e = jscodeshift.importDeclaration(specs, resourceURINode); + e.comments = path.parent.parent.node.comments; + path.parent.parent.node.comments = []; + path.parent.parent.replace(e); + + return true; +} + +function replaceImportESModuleCall( + inputFile, + jscodeshift, + path, + alwaysReplace +) { + if (path.node.arguments.length !== 1) { + warnForPath( + inputFile, + path, + `importESModule call should have only one argument` + ); + return; + } + + const resourceURINode = path.node.arguments[0]; + if (!isString(resourceURINode)) { + warnForPath(inputFile, path, `resource URI should be a string`); + return; + } + + if (!alwaysReplace) { + const resourceURI = resourceURINode.value; + if (!isTargetESM(resourceURI)) { + return; + } + } + + // If this cannot be replaced with static import, do nothing. + tryReplacingWithStaticImport( + jscodeshift, + inputFile, + path, + resourceURINode, + alwaysReplace + ); +} + +exports.isImportESModuleCall = isImportESModuleCall; +exports.tryReplacingWithStaticImport = tryReplacingWithStaticImport; +exports.replaceImportESModuleCall = replaceImportESModuleCall; |