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
- )})`
- );
- },
- });
- }
- },
- };
- },
- };
|