/** * 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/. */ import path from "path"; import url from "url"; // Get the ESLint module URL and convert it to a simple path, and make sure we're // using the directory name. let eslintBasePath = path.dirname( url.fileURLToPath(import.meta.resolve("eslint")) ); /** * Prepends the base path onto the module path, and converts to a file URL. * * @param {string} modulePath * @returns {string} */ function getFileURLString(modulePath) { let fullPath = path.join(eslintBasePath, modulePath); return url.pathToFileURL(fullPath).href; } // eslint-disable-next-line no-unsanitized/method const baseRule = (await import(getFileURLString("rules/no-redeclare.js"))) .default; const astUtils = // eslint-disable-next-line no-unsanitized/method (await import(getFileURLString("rules/utils/ast-utils.js"))).default; // Hack alert: our eslint env is pretty confused about `require` and // `loader` for devtools modules - so ignore it for now. // See bug 1812547 const gIgnoredImports = new Set(["loader", "require"]); /** * Create a trap for a call to `report` that the original rule is * trying to make on `context`. * * Returns a function that forwards to `report` but provides a fixer * for redeclared imports that just removes those imports. * * @param {object} context * The ESLint context for the file being processed. * @returns {Function} */ function trapReport(context) { return function (obj) { let declarator = obj.node.parent; while ( declarator && declarator.parent && declarator.type != "VariableDeclarator" ) { declarator = declarator.parent; } if ( declarator && declarator.type == "VariableDeclarator" && declarator.id.type == "ObjectPattern" && declarator.init.type == "CallExpression" ) { let initialization = declarator.init; if ( astUtils.isSpecificMemberAccess( initialization.callee, "ChromeUtils", /^importESModule$/ ) ) { // Hack alert: our eslint env is pretty confused about `require` and // `loader` for devtools modules - so ignore it for now. // See bug 1812547 if (gIgnoredImports.has(obj.node.name)) { return; } // OK, we've got something we can fix. But we should be careful in case // there are multiple imports being destructured. // Do the easy (and common) case first - just one property: if (declarator.id.properties.length == 1) { context.report({ node: declarator.parent, messageId: "duplicateImport", data: { name: declarator.id.properties[0].key.name, }, fix(fixer) { return fixer.remove(declarator.parent); }, }); return; } // OK, figure out which import is duplicated here: let node = obj.node.parent; // Then remove a comma after it, or a comma before // if there's no comma after it. let sourceCode = context.sourceCode; let rangeToRemove = node.range; let tokenAfter = sourceCode.getTokenAfter(node); let tokenBefore = sourceCode.getTokenBefore(node); if (astUtils.isCommaToken(tokenAfter)) { rangeToRemove[1] = tokenAfter.range[1]; } else if (astUtils.isCommaToken(tokenBefore)) { rangeToRemove[0] = tokenBefore.range[0]; } context.report({ node, messageId: "duplicateImport", data: { name: node.key.name, }, fix(fixer) { return fixer.removeRange(rangeToRemove); }, }); return; } } if (context.options[0]?.errorForNonImports) { // Report the result from no-redeclare - we can't autofix it. // This can happen for other redeclaration issues, e.g. naming // variables in a way that conflicts with builtins like "URL" or // "escape". context.report(obj); } }; } export default { meta: { docs: { url: "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/rules/no-redeclare-with-import-autofix.html", }, messages: { ...baseRule.meta.messages, duplicateImport: "The import of '{{ name }}' is redundant with one set up earlier (e.g. head.js or the browser window environment). It should be removed.", }, schema: [ { type: "object", properties: { errorForNonImports: { type: "boolean", default: true, }, }, additionalProperties: false, }, ], type: "suggestion", fixable: "code", }, create(context) { let newOptions = [{ builtinGlobals: true }]; const contextForBaseRule = Object.create(context, { report: { value: trapReport(context), writable: false, }, options: { value: newOptions, }, }); return baseRule.create(contextForBaseRule); }, };