| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 | /** * @fileoverview Reject use of instanceof against DOM interfaces * * 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/. */"use strict";const fs = require("fs");const { maybeGetMemberPropertyName } = require("../helpers");const privilegedGlobals = Object.keys(  require("../environments/privileged.js").globals);// -----------------------------------------------------------------------------// Rule Definition// -----------------------------------------------------------------------------/** * Whether an identifier is defined by eslint configuration. * `env: { browser: true }` or `globals: []` for example. * @param {import("eslint-scope").Scope} currentScope * @param {import("estree").Identifier} id */function refersToEnvironmentGlobals(currentScope, id) {  const reference = currentScope.references.find(ref => ref.identifier === id);  const { resolved } = reference || {};  if (!resolved) {    return false;  }  // No definition in script files; defined via .eslintrc  return resolved.scope.type === "global" && resolved.defs.length === 0;}/** * Whether a node points to a DOM interface. * Includes direct references to interfaces objects and also indirect references * via property access. * OS.File and lazy.(Foo) are explicitly excluded. * * @example HTMLElement * @example win.HTMLElement * @example iframe.contentWindow.HTMLElement * @example foo.HTMLElement * * @param {import("eslint-scope").Scope} currentScope * @param {import("estree").Node} node */function pointsToDOMInterface(currentScope, node) {  if (node.type === "MemberExpression") {    const objectName = maybeGetMemberPropertyName(node.object);    if (objectName === "lazy") {      // lazy.Foo is probably a non-IDL import.      return false;    }    if (objectName === "OS" && node.property.name === "File") {      // OS.File is an exception that is not a Web IDL interface      return false;    }    // For `win.Foo`, `iframe.contentWindow.Foo`, or such.    return privilegedGlobals.includes(node.property.name);  }  if (    node.type === "Identifier" &&    refersToEnvironmentGlobals(currentScope, node)  ) {    return privilegedGlobals.includes(node.name);  }  return false;}/** * @param {import("eslint").Rule.RuleContext} context */function isChromeContext(context) {  const filename = context.getFilename();  const isChromeFileName =    filename.endsWith(".sys.mjs") ||    filename.endsWith(".jsm") ||    filename.endsWith(".jsm.js");  if (isChromeFileName) {    return true;  }  if (filename.endsWith(".xhtml")) {    // Treat scripts in XUL files as chrome scripts    // Note: readFile is needed as getSourceCode() only gives JS blocks    return fs.readFileSync(filename).includes("there.is.only.xul");  }  // Treat scripts as chrome privileged when using:  // 1. ChromeUtils, but not SpecialPowers.ChromeUtils  // 2. BrowserTestUtils, PlacesUtils  // 3. document.createXULElement  // 4. loader.lazyRequireGetter  // 5. Services.foo, but not SpecialPowers.Services.foo  // 6. evalInSandbox  const source = context.getSourceCode().text;  return !!source.match(    /(^|\s)ChromeUtils|BrowserTestUtils|PlacesUtils|createXULElement|lazyRequireGetter|(^|\s)Services\.|evalInSandbox/  );}module.exports = {  meta: {    docs: {      url:        "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/use-isInstance.html",    },    fixable: "code",    schema: [],    type: "problem",  },  /**   * @param {import("eslint").Rule.RuleContext} context   */  create(context) {    if (!isChromeContext(context)) {      return {};    }    return {      BinaryExpression(node) {        const { operator, right } = node;        if (          operator === "instanceof" &&          pointsToDOMInterface(context.getScope(), right)        ) {          context.report({            node,            message:              "Please prefer .isInstance() in chrome scripts over the standard instanceof operator for DOM interfaces, " +              "since the latter will return false when the object is created from a different context.",            fix(fixer) {              const sourceCode = context.getSourceCode();              return fixer.replaceText(                node,                `${sourceCode.getText(right)}.isInstance(${sourceCode.getText(                  node.left                )})`              );            },          });        }      },    };  },};
 |