Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/tools/lint/eslint/eslint-plugin-mozilla/lib/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 20 kB image not shown  

Quelle  globals.js   Sprache: JAVA

 
/**
 * @fileoverview functions for scanning an AST for globals including
 *               traversing referenced scripts.
 * 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 path = require("path");
const fs = require("fs");
const helpers = require("./helpers");
const htmlparser = require("htmlparser2");
const testharnessEnvironment = require("./environments/testharness.js");

const callExpressionDefinitions = [
  /^loader\.lazyGetter\((?:globalThis|this), "(\w+)"/,
  /^loader\.lazyServiceGetter\((?:globalThis|this), "(\w+)"/,
  /^loader\.lazyRequireGetter\((?:globalThis|this), "(\w+)"/,
  /^XPCOMUtils\.defineLazyGetter\((?:globalThis|this), "(\w+)"/,
  /^ChromeUtils\.defineLazyGetter\((?:globalThis|this), "(\w+)"/,
  /^XPCOMUtils\.defineLazyPreferenceGetter\((?:globalThis|this), "(\w+)"/,
  /^XPCOMUtils\.defineLazyScriptGetter\((?:globalThis|this), "(\w+)"/,
  /^XPCOMUtils\.defineLazyServiceGetter\((?:globalThis|this), "(\w+)"/,
  /^XPCOMUtils\.defineConstant\((?: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.defineLazyServiceGetters(this,",
  "XPCOMUtils.defineLazyServiceGetters(globalThis,",
  "ChromeUtils.defineESModuleGetters(this,",
  "ChromeUtils.defineESModuleGetters(globalThis,",
  "loader.lazyRequireGetter(this,",
  "loader.lazyRequireGetter(globalThis,",
];

const subScriptMatches = [
  /Services\.scriptloader\.loadSubScript\("(.*?)"this\)/,
];

const workerImportFilenameMatch = /(.*\/)*((.*?)\.js)/;

/**
 * Parses a list of "name:boolean_value" or/and "name" options divided by comma
 * or whitespace.
 *
 * This function was copied from eslint.js
 *
 * @param {string} string The string to parse.
 * @param {Comment} comment The comment node which has the string.
 * @returns {Object} Result map object of names and boolean values
 */

function parseBooleanConfig(string, comment) {
  let items = {};

  // Collapse whitespace around : to make parsing easier
  string = string.replace(/\s*:\s*/g, ":");
  // Collapse whitespace around ,
  string = string.replace(/\s*,\s*/g, ",");

  string.split(/\s|,+/).forEach(function (name) {
    if (!name) {
      return;
    }

    let pos = name.indexOf(":");
    let value;
    if (pos !== -1) {
      value = name.substring(pos + 1, name.length);
      name = name.substring(0, pos);
    }

    items[name] = {
      value: value === "true",
      comment,
    };
  });

  return items;
}

/**
 * Global discovery can require parsing many files. This map of
 * {String} => {Object} caches what globals were discovered for a file path.
 */

const globalCache = new Map();

/**
 * Global discovery can occasionally meet circular dependencies due to the way
 * js files are included via html/xhtml files etc. This set is used to avoid
 * getting into loops whilst the discovery is in progress.
 */

var globalDiscoveryInProgressForFiles = new Set();

/**
 * When looking for globals in HTML files, it can be common to have more than
 * one script tag with inline javascript. These will normally be called together,
 * so we store the globals for just the last HTML file processed.
 */

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

  // 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 [];
  }

  let source;
  try {
    source = helpers.getASTSource(node);
  } catch (e) {
    return [];
  }

  for (let reg of subScriptMatches) {
    let match = source.match(reg);
    if (match) {
      return getGlobalsForScript(match[1], "script").map(g => {
        // We don't want any loadSubScript globals to be explicit, as this
        // could trigger no-unused-vars when importing multiple variables
        // from a script and not using all of them.
        g.explicit = false;
        return g;
      });
    }
  }

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

/**
 * Attempts to load the globals for a given script.
 *
 * @param {string} src
 *   The source path or url of the script to look for.
 * @param {string} type
 *   The type of the current file (script/module).
 * @param {string} [dir]
 *   The directory of the current file.
 * @returns {object[]}
 *   An array of objects with details of the globals in them.
 */

function getGlobalsForScript(src, type, dir) {
  let scriptName;
  if (src.includes("http:")) {
    // We don't handle this currently as the paths are complex to match.
  } else if (src.startsWith("chrome://mochikit/content/")) {
    // Various ways referencing test files.
    src = src.replace("chrome://mochikit/content/", "/");
    scriptName = path.join(helpers.rootDir, "testing""mochitest", src);
  } else if (src.startsWith("chrome://mochitests/content/browser")) {
    src = src.replace("chrome://mochitests/content/browser", "");
    scriptName = path.join(helpers.rootDir, src);
  } else if (src.includes("SimpleTest")) {
    // This is another way of referencing test files...
    scriptName = path.join(helpers.rootDir, "testing""mochitest", src);
  } else if (src.startsWith("/tests/")) {
    scriptName = path.join(helpers.rootDir, src.substring(7));
  } else if (src.startsWith("/resources/testharness.js")) {
    return Object.keys(testharnessEnvironment.globals).map(name => ({
      name,
      writable: true,
    }));
  } else if (dir) {
    // Fallback to hoping this is a relative path.
    scriptName = path.join(dir, src);
  }
  if (scriptName && fs.existsSync(scriptName)) {
    return module.exports.getGlobalsForFile(scriptName, {
      ecmaVersion: helpers.getECMAVersion(),
      sourceType: type,
    });
  }
  return [];
}

/**
 * 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
 * parents parameter which is a list of the parent nodes of the current node.
 * Each returns an array of globals found.
 *
 * @param  {String} filePath
 *         The absolute path of the file being parsed.
 */

function GlobalsForNode(filePath, context) {
  this.path = filePath;
  this.context = context;

  if (this.path) {
    this.dirname = path.dirname(this.path);
  } else {
    this.dirname = null;
  }
}

GlobalsForNode.prototype = {
  Program(node) {
    let globals = [];
    for (let comment of node.comments) {
      if (comment.type !== "Block") {
        continue;
      }
      let value = comment.value.trim();
      value = value.replace(/\n/g, "");

      // We have to discover any globals that ESLint would have defined through
      // comment directives.
      let match = /^globals?\s+(.+)/.exec(value);
      if (match) {
        let values = parseBooleanConfig(match[1].trim(), node);
        for (let name of Object.keys(values)) {
          globals.push({
            name,
            writable: values[name].value,
          });
        }
        // We matched globals, so we won't match import-globals-from.
        continue;
      }

      match = /^import-globals-from\s+(.+)$/.exec(value);
      if (!match) {
        continue;
      }

      if (!this.dirname) {
        // If this is testing context without path, ignore import.
        return globals;
      }

      let filePath = match[1].trim();

      if (filePath.endsWith(".mjs")) {
        if (this.context) {
          this.context.report(
            comment,
            "import-globals-from does not support module files - use a direct import instead"
          );
        } else {
          // Fall back to throwing an error, as we do not have a context in all situations,
          // e.g. when loading the environment.
          throw new Error(
            "import-globals-from does not support module files - use a direct import instead"
          );
        }
        continue;
      }

      if (!path.isAbsolute(filePath)) {
        filePath = path.resolve(this.dirname, filePath);
      } else {
        filePath = path.join(helpers.rootDir, filePath);
      }
      globals = globals.concat(module.exports.getGlobalsForFile(filePath));
    }

    return globals;
  },

  ExpressionStatement(node, parents, globalScope) {
    let isGlobal = helpers.getIsGlobalThis(parents);
    let globals = [];

    // Note: We check the expression types here and only call the necessary
    // functions to aid performance.
    if (node.expression.type === "AssignmentExpression") {
      globals = convertThisAssignmentExpressionToGlobals(node, isGlobal);
    } else if (node.expression.type === "CallExpression") {
      globals = convertCallExpressionToGlobals(node, isGlobal);
    }

    // Here we assume that if importScripts is set in the global scope, then
    // this is a worker. It would be nice if eslint gave us a way of getting
    // the environment directly.
    //
    // If this is testing context without path, ignore import.
    if (globalScope && globalScope.set.get("importScripts") && this.dirname) {
      let workerDetails = convertWorkerExpressionToGlobals(
        node,
        isGlobal,
        this.dirname
      );
      globals = globals.concat(workerDetails);
    }

    return globals;
  },
};

module.exports = {
  /**
   * Returns all globals for a given file. Recursively searches through
   * import-globals-from directives and also includes globals defined by
   * standard eslint directives.
   *
   * @param  {String} filePath
   *         The absolute path of the file to be parsed.
   * @param  {Object} astOptions
   *         Extra options to pass to the parser.
   * @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.
   */

  getGlobalsForFile(filePath, astOptions = {}) {
    if (globalCache.has(filePath)) {
      return globalCache.get(filePath);
    }

    if (globalDiscoveryInProgressForFiles.has(filePath)) {
      // We're already processing this file, so return an empty set for now -
      // the initial processing will pick up on the globals for this file.
      return [];
    }
    globalDiscoveryInProgressForFiles.add(filePath);

    let content = fs.readFileSync(filePath, "utf8");

    // Parse the content into an AST
    let { ast, scopeManager, visitorKeys } = helpers.parseCode(
      content,
      astOptions
    );

    // Discover global declarations
    let globalScope = scopeManager.acquire(ast);

    let globals = Object.keys(globalScope.variables).map(v => ({
      name: globalScope.variables[v].name,
      writable: true,
    }));

    // Walk over the AST to find any of our custom globals
    let handler = new GlobalsForNode(filePath);

    helpers.walkAST(ast, visitorKeys, (type, node, parents) => {
      if (type in handler) {
        let newGlobals = handler[type](node, parents, globalScope);
        globals.push.apply(globals, newGlobals);
      }
    });

    globalCache.set(filePath, globals);

    globalDiscoveryInProgressForFiles.delete(filePath);
    return globals;
  },

  /**
   * Returns all globals for a code.
   * This is only for testing.
   *
   * @param  {String} code
   *         The JS code
   * @param  {Object} astOptions
   *         Extra options to pass to the parser.
   * @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.
   */

  getGlobalsForCode(code, astOptions = {}) {
    // Parse the content into an AST
    let { ast, scopeManager, visitorKeys } = helpers.parseCode(
      code,
      astOptions,
      { useBabel: false }
    );

    // Discover global declarations
    let globalScope = scopeManager.acquire(ast);

    let globals = Object.keys(globalScope.variables).map(v => ({
      name: globalScope.variables[v].name,
      writable: true,
    }));

    // Walk over the AST to find any of our custom globals
    let handler = new GlobalsForNode(null);

    helpers.walkAST(ast, visitorKeys, (type, node, parents) => {
      if (type in handler) {
        let newGlobals = handler[type](node, parents, globalScope);
        globals.push.apply(globals, newGlobals);
      }
    });

    return globals;
  },

  /**
   * Returns all the globals for an html file that are defined by imported
   * scripts (i.e. <script src="foo.js">).
   *
   * This function will cache results for one html file only - we expect
   * this to be called sequentially for each chunk of a HTML file, rather
   * than chucks of different files in random order.
   *
   * @param  {String} filePath
   *         The absolute path of the file to be parsed.
   * @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.
   */

  getImportedGlobalsForHTMLFile(filePath) {
    if (lastHTMLGlobals.filename === filePath) {
      return lastHTMLGlobals.globals;
    }

    let dir = path.dirname(filePath);
    let globals = [];

    let content = fs.readFileSync(filePath, "utf8");
    let scriptSrcs = [];

    // We use htmlparser as this ensures we find the script tags correctly.
    let parser = new htmlparser.Parser(
      {
        onopentag(name, attribs) {
          if (name === "script" && "src" in attribs) {
            scriptSrcs.push({
              src: attribs.src,
              type:
                "type" in attribs && attribs.type == "module"
                  ? "module"
                  : "script",
            });
          }
        },
      },
      {
        xmlMode: filePath.endsWith("xhtml"),
      }
    );

    parser.parseComplete(content);

    for (let script of scriptSrcs) {
      // Ensure that the script src isn't just "".
      if (!script.src) {
        continue;
      }
      globals.push(...getGlobalsForScript(script.src, script.type, dir));
    }

    lastHTMLGlobals.filePath = filePath;
    return (lastHTMLGlobals.globals = globals);
  },

  /**
   * Intended to be used as-is for an ESLint rule that parses for globals in
   * the current file and recurses through import-globals-from directives.
   *
   * @param  {Object} context
   *         The ESLint parsing context.
   */

  getESLintGlobalParser(context) {
    let globalScope;

    let parser = {
      Program(node) {
        globalScope = helpers.getScope(context, node);
      },
    };
    let filename = context.getFilename();

    let extraHTMLGlobals = [];
    if (filename.endsWith(".html") || filename.endsWith(".xhtml")) {
      extraHTMLGlobals = module.exports.getImportedGlobalsForHTMLFile(filename);
    }

    // Install thin wrappers around GlobalsForNode
    let handler = new GlobalsForNode(helpers.getAbsoluteFilePath(context));

    for (let type of Object.keys(GlobalsForNode.prototype)) {
      parser[type] = function (node) {
        if (type === "Program") {
          globalScope = helpers.getScope(context, node);
          helpers.addGlobals(extraHTMLGlobals, globalScope);
        }
        let globals = handler[type](
          node,
          helpers.getAncestors(context, node),
          globalScope
        );
        helpers.addGlobals(
          globals,
          globalScope,
          node.type !== "Program" && node
        );
      };
    }

    return parser;
  },
};

Messung V0.5
C=87 H=98 G=92

¤ Dauer der Verarbeitung: 0.7 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.