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
This commit is contained in:
Tooru Fujisawa 2022-10-20 03:29:40 +00:00
Родитель d42db71402
Коммит 581fcd4a42
3 изменённых файлов: 158 добавлений и 59 удалений

Просмотреть файл

@ -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);
}
});

Просмотреть файл

@ -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`

Просмотреть файл

@ -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;