Bug 1812977 - Move ESLint functions for globals handling from helpers.js to globals.js. r=Gijs

This is a better location for these functions and helps avoid circular dependencies in the next patches.

Differential Revision: https://phabricator.services.mozilla.com/D168067
This commit is contained in:
Mark Banner 2023-01-30 19:33:36 +00:00
Родитель 385fa57278
Коммит cc23ce6654
2 изменённых файлов: 211 добавлений и 216 удалений

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

@ -13,6 +13,40 @@ const fs = require("fs");
const helpers = require("./helpers"); const helpers = require("./helpers");
const htmlparser = require("htmlparser2"); const htmlparser = require("htmlparser2");
const callExpressionDefinitions = [
/^loader\.lazyGetter\((?:globalThis|this), "(\w+)"/,
/^loader\.lazyServiceGetter\((?:globalThis|this), "(\w+)"/,
/^loader\.lazyRequireGetter\((?:globalThis|this), "(\w+)"/,
/^XPCOMUtils\.defineLazyGetter\((?:globalThis|this), "(\w+)"/,
/^XPCOMUtils\.defineLazyModuleGetter\((?:globalThis|this), "(\w+)"/,
/^ChromeUtils\.defineModuleGetter\((?:globalThis|this), "(\w+)"/,
/^XPCOMUtils\.defineLazyPreferenceGetter\((?:globalThis|this), "(\w+)"/,
/^XPCOMUtils\.defineLazyProxy\((?:globalThis|this), "(\w+)"/,
/^XPCOMUtils\.defineLazyScriptGetter\((?:globalThis|this), "(\w+)"/,
/^XPCOMUtils\.defineLazyServiceGetter\((?:globalThis|this), "(\w+)"/,
/^XPCOMUtils\.defineConstant\((?:globalThis|this), "(\w+)"/,
/^DevToolsUtils\.defineLazyModuleGetter\((?:globalThis|this), "(\w+)"/,
/^DevToolsUtils\.defineLazyGetter\((?:globalThis|this), "(\w+)"/,
/^Object\.defineProperty\((?:globalThis|this), "(\w+)"/,
/^Reflect\.defineProperty\((?:globalThis|this), "(\w+)"/,
/^this\.__defineGetter__\("(\w+)"/,
];
const callExpressionMultiDefinitions = [
"XPCOMUtils.defineLazyGlobalGetters(this,",
"XPCOMUtils.defineLazyGlobalGetters(globalThis,",
"XPCOMUtils.defineLazyModuleGetters(this,",
"XPCOMUtils.defineLazyModuleGetters(globalThis,",
"XPCOMUtils.defineLazyServiceGetters(this,",
"XPCOMUtils.defineLazyServiceGetters(globalThis,",
"ChromeUtils.defineESModuleGetters(this,",
"ChromeUtils.defineESModuleGetters(globalThis,",
"loader.lazyRequireGetter(this,",
"loader.lazyRequireGetter(globalThis,",
];
const workerImportFilenameMatch = /(.*\/)*((.*?)\.jsm?)/;
/** /**
* Parses a list of "name:boolean_value" or/and "name" options divided by comma * Parses a list of "name:boolean_value" or/and "name" options divided by comma
* or whitespace. * or whitespace.
@ -72,6 +106,180 @@ var globalDiscoveryInProgressForFiles = new Set();
*/ */
var lastHTMLGlobals = {}; var lastHTMLGlobals = {};
/**
* Attempts to convert an CallExpressions that look like module imports
* into global variable definitions.
*
* @param {Object} node
* The AST node to convert.
* @param {boolean} isGlobal
* True if the current node is in the global scope.
*
* @return {Array}
* An array of objects that contain details about the globals:
* - {String} name
* The name of the global.
* - {Boolean} writable
* If the global is writeable or not.
*/
function convertCallExpressionToGlobals(node, isGlobal) {
let express = node.expression;
if (
express.type === "CallExpression" &&
express.callee.type === "MemberExpression" &&
express.callee.object &&
express.callee.object.type === "Identifier" &&
express.arguments.length === 1 &&
express.arguments[0].type === "ArrayExpression" &&
express.callee.property.type === "Identifier" &&
express.callee.property.name === "importGlobalProperties"
) {
return express.arguments[0].elements.map(literal => {
return {
explicit: true,
name: literal.value,
writable: false,
};
});
}
let source;
try {
source = helpers.getASTSource(node);
} catch (e) {
return [];
}
// The definition matches below must be in the global scope for us to define
// a global, so bail out early if we're not a global.
if (!isGlobal) {
return [];
}
for (let reg of callExpressionDefinitions) {
let match = source.match(reg);
if (match) {
return [{ name: match[1], writable: true, explicit: true }];
}
}
if (
callExpressionMultiDefinitions.some(expr => source.startsWith(expr)) &&
node.expression.arguments[1]
) {
let arg = node.expression.arguments[1];
if (arg.type === "ObjectExpression") {
return arg.properties
.map(p => ({
name: p.type === "Property" && p.key.name,
writable: true,
explicit: true,
}))
.filter(g => g.name);
}
if (arg.type === "ArrayExpression") {
return arg.elements
.map(p => ({
name: p.type === "Literal" && p.value,
writable: true,
explicit: true,
}))
.filter(g => typeof g.name == "string");
}
}
if (
node.expression.callee.type == "MemberExpression" &&
node.expression.callee.property.type == "Identifier" &&
node.expression.callee.property.name == "defineLazyScriptGetter"
) {
// The case where we have a single symbol as a string has already been
// handled by the regexp, so we have an array of symbols here.
return node.expression.arguments[1].elements.map(n => ({
name: n.value,
writable: true,
explicit: true,
}));
}
return [];
}
/**
* Attempts to convert an AssignmentExpression into a global variable
* definition if it applies to `this` in the global scope.
*
* @param {Object} node
* The AST node to convert.
* @param {boolean} isGlobal
* True if the current node is in the global scope.
*
* @return {Array}
* An array of objects that contain details about the globals:
* - {String} name
* The name of the global.
* - {Boolean} writable
* If the global is writeable or not.
*/
function convertThisAssignmentExpressionToGlobals(node, isGlobal) {
if (
isGlobal &&
node.expression.left &&
node.expression.left.object &&
node.expression.left.object.type === "ThisExpression" &&
node.expression.left.property &&
node.expression.left.property.type === "Identifier"
) {
return [{ name: node.expression.left.property.name, writable: true }];
}
return [];
}
/**
* Attempts to convert an ExpressionStatement to likely global variable
* definitions.
*
* @param {Object} node
* The AST node to convert.
* @param {boolean} isGlobal
* True if the current node is in the global scope.
*
* @return {Array}
* An array of objects that contain details about the globals:
* - {String} name
* The name of the global.
* - {Boolean} writable
* If the global is writeable or not.
*/
function convertWorkerExpressionToGlobals(node, isGlobal, dirname) {
let results = [];
let expr = node.expression;
if (
node.expression.type === "CallExpression" &&
expr.callee &&
expr.callee.type === "Identifier" &&
expr.callee.name === "importScripts"
) {
for (var arg of expr.arguments) {
var match = arg.value && arg.value.match(workerImportFilenameMatch);
if (match) {
if (!match[1]) {
let filePath = path.resolve(dirname, match[2]);
if (fs.existsSync(filePath)) {
let additionalGlobals = module.exports.getGlobalsForFile(filePath);
results = results.concat(additionalGlobals);
}
}
// Import with relative/absolute path should explicitly use
// `import-globals-from` comment.
}
}
}
return results;
}
/** /**
* An object that returns found globals for given AST node types. Each prototype * An object that returns found globals for given AST node types. Each prototype
* property should be named for a node type and accepts a node parameter and a * property should be named for a node type and accepts a node parameter and a
@ -163,12 +371,9 @@ GlobalsForNode.prototype = {
// Note: We check the expression types here and only call the necessary // Note: We check the expression types here and only call the necessary
// functions to aid performance. // functions to aid performance.
if (node.expression.type === "AssignmentExpression") { if (node.expression.type === "AssignmentExpression") {
globals = helpers.convertThisAssignmentExpressionToGlobals( globals = convertThisAssignmentExpressionToGlobals(node, isGlobal);
node,
isGlobal
);
} else if (node.expression.type === "CallExpression") { } else if (node.expression.type === "CallExpression") {
globals = helpers.convertCallExpressionToGlobals(node, isGlobal); globals = convertCallExpressionToGlobals(node, isGlobal);
} }
// Here we assume that if importScripts is set in the global scope, then // Here we assume that if importScripts is set in the global scope, then
@ -177,7 +382,7 @@ GlobalsForNode.prototype = {
// //
// If this is testing context without path, ignore import. // If this is testing context without path, ignore import.
if (globalScope && globalScope.set.get("importScripts") && this.dirname) { if (globalScope && globalScope.set.get("importScripts") && this.dirname) {
let workerDetails = helpers.convertWorkerExpressionToGlobals( let workerDetails = convertWorkerExpressionToGlobals(
node, node,
isGlobal, isGlobal,
this.dirname this.dirname

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

@ -18,40 +18,6 @@ const recommendedConfig = require("./configs/recommended");
var gRootDir = null; var gRootDir = null;
var directoryManifests = new Map(); var directoryManifests = new Map();
const callExpressionDefinitions = [
/^loader\.lazyGetter\((?:globalThis|this), "(\w+)"/,
/^loader\.lazyServiceGetter\((?:globalThis|this), "(\w+)"/,
/^loader\.lazyRequireGetter\((?:globalThis|this), "(\w+)"/,
/^XPCOMUtils\.defineLazyGetter\((?:globalThis|this), "(\w+)"/,
/^XPCOMUtils\.defineLazyModuleGetter\((?:globalThis|this), "(\w+)"/,
/^ChromeUtils\.defineModuleGetter\((?:globalThis|this), "(\w+)"/,
/^XPCOMUtils\.defineLazyPreferenceGetter\((?:globalThis|this), "(\w+)"/,
/^XPCOMUtils\.defineLazyProxy\((?:globalThis|this), "(\w+)"/,
/^XPCOMUtils\.defineLazyScriptGetter\((?:globalThis|this), "(\w+)"/,
/^XPCOMUtils\.defineLazyServiceGetter\((?:globalThis|this), "(\w+)"/,
/^XPCOMUtils\.defineConstant\((?:globalThis|this), "(\w+)"/,
/^DevToolsUtils\.defineLazyModuleGetter\((?:globalThis|this), "(\w+)"/,
/^DevToolsUtils\.defineLazyGetter\((?:globalThis|this), "(\w+)"/,
/^Object\.defineProperty\((?:globalThis|this), "(\w+)"/,
/^Reflect\.defineProperty\((?:globalThis|this), "(\w+)"/,
/^this\.__defineGetter__\("(\w+)"/,
];
const callExpressionMultiDefinitions = [
"XPCOMUtils.defineLazyGlobalGetters(this,",
"XPCOMUtils.defineLazyGlobalGetters(globalThis,",
"XPCOMUtils.defineLazyModuleGetters(this,",
"XPCOMUtils.defineLazyModuleGetters(globalThis,",
"XPCOMUtils.defineLazyServiceGetters(this,",
"XPCOMUtils.defineLazyServiceGetters(globalThis,",
"ChromeUtils.defineESModuleGetters(this,",
"ChromeUtils.defineESModuleGetters(globalThis,",
"loader.lazyRequireGetter(this,",
"loader.lazyRequireGetter(globalThis,",
];
const workerImportFilenameMatch = /(.*\/)*((.*?)\.jsm?)/;
let xpidlData; let xpidlData;
module.exports = { module.exports = {
@ -251,182 +217,6 @@ module.exports = {
} }
}, },
/**
* Attempts to convert an ExpressionStatement to likely global variable
* definitions.
*
* @param {Object} node
* The AST node to convert.
* @param {boolean} isGlobal
* True if the current node is in the global scope.
*
* @return {Array}
* An array of objects that contain details about the globals:
* - {String} name
* The name of the global.
* - {Boolean} writable
* If the global is writeable or not.
*/
convertWorkerExpressionToGlobals(node, isGlobal, dirname) {
var getGlobalsForFile = require("./globals").getGlobalsForFile;
let results = [];
let expr = node.expression;
if (
node.expression.type === "CallExpression" &&
expr.callee &&
expr.callee.type === "Identifier" &&
expr.callee.name === "importScripts"
) {
for (var arg of expr.arguments) {
var match = arg.value && arg.value.match(workerImportFilenameMatch);
if (match) {
if (!match[1]) {
let filePath = path.resolve(dirname, match[2]);
if (fs.existsSync(filePath)) {
let additionalGlobals = getGlobalsForFile(filePath);
results = results.concat(additionalGlobals);
}
}
// Import with relative/absolute path should explicitly use
// `import-globals-from` comment.
}
}
}
return results;
},
/**
* Attempts to convert an AssignmentExpression into a global variable
* definition if it applies to `this` in the global scope.
*
* @param {Object} node
* The AST node to convert.
* @param {boolean} isGlobal
* True if the current node is in the global scope.
*
* @return {Array}
* An array of objects that contain details about the globals:
* - {String} name
* The name of the global.
* - {Boolean} writable
* If the global is writeable or not.
*/
convertThisAssignmentExpressionToGlobals(node, isGlobal) {
if (
isGlobal &&
node.expression.left &&
node.expression.left.object &&
node.expression.left.object.type === "ThisExpression" &&
node.expression.left.property &&
node.expression.left.property.type === "Identifier"
) {
return [{ name: node.expression.left.property.name, writable: true }];
}
return [];
},
/**
* Attempts to convert an CallExpressions that look like module imports
* into global variable definitions.
*
* @param {Object} node
* The AST node to convert.
* @param {boolean} isGlobal
* True if the current node is in the global scope.
*
* @return {Array}
* An array of objects that contain details about the globals:
* - {String} name
* The name of the global.
* - {Boolean} writable
* If the global is writeable or not.
*/
convertCallExpressionToGlobals(node, isGlobal) {
let express = node.expression;
if (
express.type === "CallExpression" &&
express.callee.type === "MemberExpression" &&
express.callee.object &&
express.callee.object.type === "Identifier" &&
express.arguments.length === 1 &&
express.arguments[0].type === "ArrayExpression" &&
express.callee.property.type === "Identifier" &&
express.callee.property.name === "importGlobalProperties"
) {
return express.arguments[0].elements.map(literal => {
return {
explicit: true,
name: literal.value,
writable: false,
};
});
}
let source;
try {
source = this.getASTSource(node);
} catch (e) {
return [];
}
// The definition matches below must be in the global scope for us to define
// a global, so bail out early if we're not a global.
if (!isGlobal) {
return [];
}
for (let reg of callExpressionDefinitions) {
let match = source.match(reg);
if (match) {
return [{ name: match[1], writable: true, explicit: true }];
}
}
if (
callExpressionMultiDefinitions.some(expr => source.startsWith(expr)) &&
node.expression.arguments[1]
) {
let arg = node.expression.arguments[1];
if (arg.type === "ObjectExpression") {
return arg.properties
.map(p => ({
name: p.type === "Property" && p.key.name,
writable: true,
explicit: true,
}))
.filter(g => g.name);
}
if (arg.type === "ArrayExpression") {
return arg.elements
.map(p => ({
name: p.type === "Literal" && p.value,
writable: true,
explicit: true,
}))
.filter(g => typeof g.name == "string");
}
}
if (
node.expression.callee.type == "MemberExpression" &&
node.expression.callee.property.type == "Identifier" &&
node.expression.callee.property.name == "defineLazyScriptGetter"
) {
// The case where we have a single symbol as a string has already been
// handled by the regexp, so we have an array of symbols here.
return node.expression.arguments[1].elements.map(n => ({
name: n.value,
writable: true,
explicit: true,
}));
}
return [];
},
/** /**
* Add a variable to the current scope. * Add a variable to the current scope.
* HACK: This relies on eslint internals so it could break at any time. * HACK: This relies on eslint internals so it could break at any time.