From 581fcd4a422d7e9cdb3a3c908952ecb7f544aff9 Mon Sep 17 00:00:00 2001 From: Tooru Fujisawa Date: Thu, 20 Oct 2022 03:29:40 +0000 Subject: [PATCH] Bug 1796077 - Part 1: Refactor the function matching and rewriting to support more complex cases. r=yulia Differential Revision: https://phabricator.services.mozilla.com/D159696 --- tools/esmify/import-to-import_esmodule.js | 125 +++++++++++++--------- tools/esmify/static-import.js | 8 +- tools/esmify/utils.js | 84 +++++++++++++-- 3 files changed, 158 insertions(+), 59 deletions(-) diff --git a/tools/esmify/import-to-import_esmodule.js b/tools/esmify/import-to-import_esmodule.js index 57a3c9b6c44a..a9e6162e67da 100644 --- a/tools/esmify/import-to-import_esmodule.js +++ b/tools/esmify/import-to-import_esmodule.js @@ -10,7 +10,6 @@ const _path = require("path"); const { isESMified } = require(_path.resolve(__dirname, "./is-esmified.js")); const { - calleeToString, jsmExtPattern, esmifyExtension, isIdentifier, @@ -18,6 +17,9 @@ const { warnForPath, getPrevStatement, getNextStatement, + isMemberExpressionWithIdentifiers, + rewriteMemberExpressionWithIdentifiers, + createMemberExpressionWithIdentifiers, } = require(_path.resolve(__dirname, "./utils.js")); const { isImportESModuleCall, @@ -54,30 +56,46 @@ function isESMifiedAndTarget(resourceURI) { return true; } -function isImportCall(node) { - const s = calleeToString(node.callee); - return ["Cu.import", "ChromeUtils.import"].includes(s); +const importCalls = [ + { + from: ["Cu", "import"], + to: ["ChromeUtils", "importESModule"], + }, + { + from: ["ChromeUtils", "import"], + to: ["ChromeUtils", "importESModule"], + }, +]; + +const singleLazyGetterCalls = [ + { + from: ["XPCOMUtils", "defineLazyModuleGetter"], + to: ["ChromeUtils", "defineESModuleGetters"], + }, + { + from: ["ChromeUtils", "defineModuleGetter"], + to: ["ChromeUtils", "defineESModuleGetters"], + }, +]; + +const multiLazyGettersCalls = [ + { + from: ["XPCOMUtils", "defineLazyModuleGetters"], + to: ["ChromeUtils", "defineESModuleGetters"], + }, +]; + +function isMemberExpressionMatchingPatterns(node, patterns) { + for (const item of patterns) { + if (isMemberExpressionWithIdentifiers(node, item.from)) { + return item; + } + } + + return null; } -function isLazyGetterCall(node) { - const s = calleeToString(node.callee); - return [ - "XPCOMUtils.defineLazyModuleGetter", - "ChromeUtils.defineModuleGetter", - ].includes(s); -} - -function isLazyGettersCall(node) { - const s = calleeToString(node.callee); - return ["XPCOMUtils.defineLazyModuleGetters"].includes(s); -} - -function isESModuleGettersCall(node) { - const s = calleeToString(node.callee); - return ["ChromeUtils.defineESModuleGetters"].includes(s); -} - -function replaceImportCall(inputFile, jscodeshift, path) { +function replaceImportCall(inputFile, jscodeshift, path, rewriteItem) { if (path.node.arguments.length !== 1) { warnForPath(inputFile, path, `import call should have only one argument`); return; @@ -108,15 +126,14 @@ function replaceImportCall(inputFile, jscodeshift, path) { false ) ) { - path.node.callee.object.name = "ChromeUtils"; - path.node.callee.property.name = "importESModule"; + rewriteMemberExpressionWithIdentifiers(path.node.callee, rewriteItem.to); resourceURINode.value = esmifyExtension(resourceURI); } } -// Find `ChromeUtils.defineESModuleGetters` statement adjacent to `path` which -// uses the same target object. -function findDefineESModuleGettersStmt(path) { +// Find `ChromeUtils.defineESModuleGetters` or variant statement specified by +// expectedIDs, adjacent to `path` which uses the same target object. +function findDefineESModuleGettersStmt(path, expectedIDs) { // `path` must be top-level. if (path.parent.node.type !== "ExpressionStatement") { return null; @@ -133,7 +150,7 @@ function findDefineESModuleGettersStmt(path) { prev && prev.type === "ExpressionStatement" && prev.expression.type === "CallExpression" && - isESModuleGettersCall(prev.expression) + isMemberExpressionWithIdentifiers(prev.expression.callee, expectedIDs) ) { callStmt = prev; } else { @@ -142,7 +159,7 @@ function findDefineESModuleGettersStmt(path) { next && next.type === "ExpressionStatement" && next.expression.type === "CallExpression" && - isESModuleGettersCall(next.expression) + isMemberExpressionWithIdentifiers(next.expression.callee, expectedIDs) ) { callStmt = next; } else { @@ -211,7 +228,7 @@ function moveComments(nodeTo, nodeFrom) { nodeFrom.comments = []; } -function replaceLazyGetterCall(inputFile, jscodeshift, path) { +function replaceLazyGetterCall(inputFile, jscodeshift, path, rewriteItem) { if (path.node.arguments.length !== 3) { warnForPath(inputFile, path, `lazy getter call should have 3 arguments`); return; @@ -246,7 +263,7 @@ function replaceLazyGetterCall(inputFile, jscodeshift, path) { resourceURINode ); - const callStmt = findDefineESModuleGettersStmt(path); + const callStmt = findDefineESModuleGettersStmt(path, rewriteItem.to); if (callStmt) { // Move a property to existing ChromeUtils.defineESModuleGetters call. @@ -258,8 +275,7 @@ function replaceLazyGetterCall(inputFile, jscodeshift, path) { } else { // Convert this call into ChromeUtils.defineESModuleGetters. - path.node.callee.object.name = "ChromeUtils"; - path.node.callee.property.name = "defineESModuleGetters"; + rewriteMemberExpressionWithIdentifiers(path.node.callee, rewriteItem.to); path.node.arguments = [ path.node.arguments[0], jscodeshift.objectExpression([prop]), @@ -267,7 +283,7 @@ function replaceLazyGetterCall(inputFile, jscodeshift, path) { } } -function replaceLazyGettersCall(inputFile, jscodeshift, path) { +function replaceLazyGettersCall(inputFile, jscodeshift, path, rewriteItem) { if (path.node.arguments.length !== 2) { warnForPath(inputFile, path, `lazy getters call should have 2 arguments`); return; @@ -309,7 +325,7 @@ function replaceLazyGettersCall(inputFile, jscodeshift, path) { return; } - let callStmt = findDefineESModuleGettersStmt(path); + let callStmt = findDefineESModuleGettersStmt(path, rewriteItem.to); if (jsmProps.length === 0) { if (callStmt) { // Move all properties to existing ChromeUtils.defineESModuleGetters call. @@ -326,8 +342,7 @@ function replaceLazyGettersCall(inputFile, jscodeshift, path) { } else { // Convert this call into ChromeUtils.defineESModuleGetters. - path.node.callee.object.name = "ChromeUtils"; - path.node.callee.property.name = "defineESModuleGetters"; + rewriteMemberExpressionWithIdentifiers(path.node.callee, rewriteItem.to); for (const prop of esmProps) { const resourceURINode = prop.value; resourceURINode.value = esmifyExtension(resourceURINode.value); @@ -344,10 +359,7 @@ function replaceLazyGettersCall(inputFile, jscodeshift, path) { if (!callStmt) { callStmt = jscodeshift.expressionStatement( jscodeshift.callExpression( - jscodeshift.memberExpression( - jscodeshift.identifier("ChromeUtils"), - jscodeshift.identifier("defineESModuleGetters") - ), + createMemberExpressionWithIdentifiers(jscodeshift, rewriteItem.to), [path.node.arguments[0], jscodeshift.objectExpression([])] ) ); @@ -423,14 +435,29 @@ function tryReplaceActorDefinition(inputFile, path, name) { function doTranslate(inputFile, jscodeshift, root) { root.find(jscodeshift.CallExpression).forEach(path => { - if (isImportCall(path.node)) { - replaceImportCall(inputFile, jscodeshift, path); - } else if (isImportESModuleCall(path.node)) { + if (isImportESModuleCall(path.node)) { replaceImportESModuleCall(inputFile, jscodeshift, path, false); - } else if (isLazyGetterCall(path.node)) { - replaceLazyGetterCall(inputFile, jscodeshift, path); - } else if (isLazyGettersCall(path.node)) { - replaceLazyGettersCall(inputFile, jscodeshift, path); + return; + } + + const callee = path.node.callee; + + let item; + item = isMemberExpressionMatchingPatterns(callee, importCalls); + if (item) { + replaceImportCall(inputFile, jscodeshift, path, item); + return; + } + + item = isMemberExpressionMatchingPatterns(callee, singleLazyGetterCalls); + if (item) { + replaceLazyGetterCall(inputFile, jscodeshift, path, item); + return; + } + + item = isMemberExpressionMatchingPatterns(callee, multiLazyGettersCalls); + if (item) { + replaceLazyGettersCall(inputFile, jscodeshift, path, item); } }); diff --git a/tools/esmify/static-import.js b/tools/esmify/static-import.js index b7d69d50c9c4..e99bfb33800d 100644 --- a/tools/esmify/static-import.js +++ b/tools/esmify/static-import.js @@ -7,10 +7,10 @@ const _path = require("path"); const { getESMFiles } = require(_path.resolve(__dirname, "./is-esmified.js")); const { - calleeToString, esmifyExtension, isString, warnForPath, + isMemberExpressionWithIdentifiers, } = require(_path.resolve(__dirname, "./utils.js")); function isTargetESM(resourceURI) { @@ -30,8 +30,10 @@ function isTargetESM(resourceURI) { } function isImportESModuleCall(node) { - const s = calleeToString(node.callee); - return ["ChromeUtils.importESModule"].includes(s); + return isMemberExpressionWithIdentifiers(node.callee, [ + "ChromeUtils", + "importESModule", + ]); } // Replace `ChromeUtils.import`, `Cu.import`, and `ChromeUtils.importESModule` diff --git a/tools/esmify/utils.js b/tools/esmify/utils.js index c6ad05652634..8ea25d782ba9 100644 --- a/tools/esmify/utils.js +++ b/tools/esmify/utils.js @@ -71,16 +71,84 @@ function esmifyExtension(path) { return path.replace(jsmExtPattern, ".sys.mjs"); } -function calleeToString(node) { - if (node.type === "Identifier") { - return node.name; +// Given possible member expression, return the list of Identifier nodes in +// the source order. +// +// Returns an empty array if: +// * not a simple MemberExpression tree with Identifiers +// * there's computed property +function memberExpressionsToIdentifiers(memberExpr) { + let ids = []; + + function f(node) { + if (node.type !== "MemberExpression" || node.computed) { + return false; + } + + if (node.object.type === "Identifier") { + ids.push(node.object); + ids.push(node.property); + return true; + } + + if (!f(node.object)) { + return false; + } + ids.push(node.property); + return true; } - if (node.type === "MemberExpression" && !node.computed) { - return calleeToString(node.object) + "." + node.property.name; + if (!f(memberExpr)) { + return []; } - return "???"; + return ids; +} + +// Returns true if the node is a simple MemberExpression tree with Identifiers +// matches expectedIDs. +function isMemberExpressionWithIdentifiers(node, expectedIDs) { + const actualIDs = memberExpressionsToIdentifiers(node); + if (actualIDs.length !== expectedIDs.length) { + return false; + } + + for (let i = 0; i < expectedIDs.length; i++) { + if (actualIDs[i].name !== expectedIDs[i]) { + return false; + } + } + + return true; +} + +// Rewrite the Identifiers of MemberExpression tree to toIDs. +// `node` must be a simple MemberExpression tree with Identifiers, and +// the length of Identifiers should match. +function rewriteMemberExpressionWithIdentifiers(node, toIDs) { + const actualIDs = memberExpressionsToIdentifiers(node); + for (let i = 0; i < toIDs.length; i++) { + actualIDs[i].name = toIDs[i]; + } +} + +// Create a simple MemberExpression tree with given Identifiers. +function createMemberExpressionWithIdentifiers(jscodeshift, ids) { + if (ids.length < 2) { + throw new Error("Unexpected length of ids for member expression"); + } + + if (ids.length > 2) { + return jscodeshift.memberExpression( + createMemberExpressionWithIdentifiers(jscodeshift, ids.slice(0, -1)), + jscodeshift.identifier(ids[ids.length - 1]) + ); + } + + return jscodeshift.memberExpression( + jscodeshift.identifier(ids[0]), + jscodeshift.identifier(ids[1]) + ); } exports.warnForPath = warnForPath; @@ -90,4 +158,6 @@ exports.isIdentifier = isIdentifier; exports.isString = isString; exports.jsmExtPattern = jsmExtPattern; exports.esmifyExtension = esmifyExtension; -exports.calleeToString = calleeToString; +exports.isMemberExpressionWithIdentifiers = isMemberExpressionWithIdentifiers; +exports.rewriteMemberExpressionWithIdentifiers = rewriteMemberExpressionWithIdentifiers; +exports.createMemberExpressionWithIdentifiers = createMemberExpressionWithIdentifiers;