Source code

Revision control

Copy as Markdown

Other Tools

/* 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;