async scan()

in src/scanners/javascript.js [42:208]


  async scan({
    _ESLint = ESLint,
    _messages = messages,
    _ruleMapping = ESLINT_RULE_MAPPING,
    // This property is used to inject additional custom eslint rules
    // as part of tests.
    _rules = undefined,
  } = {}) {
    const detectedSourceType = this.detectSourceType(this.filename);
    this.sourceType = detectedSourceType.sourceType;

    const rules = {};
    Object.keys(_ruleMapping).forEach((ruleName) => {
      if (!this.disabledRules.includes(ruleName)) {
        rules[ruleName] = _ruleMapping[ruleName];
        this._rulesProcessed++;
      }
    });

    const linter = new _ESLint.Linter();

    // Load additional rules injected by unit tests.
    if (_rules) {
      for (const ruleName of Object.keys(_rules)) {
        linter.defineRule(ruleName, _rules[ruleName]);
      }
    }

    // Load custom eslint rules embedded into addons-linter bundle.
    for (const key of Object.keys(customEslintRules)) {
      linter.defineRule(key, customEslintRules[key]);
    }

    // Load plugins rules.
    const pluginRules = noUnsanitized.rules;
    for (const key of Object.keys(pluginRules)) {
      linter.defineRule(`no-unsanitized/${key}`, pluginRules[key]);
    }

    linter.defineParser('addons-linter-espree', espree);

    const eslintConfig = {
      env: {
        browser: true,
        es6: true,
        webextensions: true,
      },

      // Ensure we use the same parser and parserOptions used to detect
      // the sourceType.
      parser: 'addons-linter-espree',
      parserOptions: {
        ecmaVersion: ECMA_VERSION,
        sourceType: this.sourceType,
      },

      rules,
      plugins: ['no-unsanitized'],

      // Scan files in `node_modules/` as well as dotfiles. As of ESLInt 7.0,
      // bower files are scanned.
      // See: https://github.com/mozilla/addons-linter/issues/1288
      // See: https://eslint.org/docs/user-guide/migrating-to-7.0.0#default-ignore-patterns-have-changed
      ignorePatterns: ['!node_modules/*', '!.*'],
      settings: {
        addonMetadata: this.options.addonMetadata,
        existingFiles: this.options.existingFiles,
        privileged: this.options.privileged,
      },
    };

    const results = linter.verify(this.code, eslintConfig, {
      allowInlineConfig: false,
      filename: this.filename,
    });

    // eslint prepends the filename with the current working directory,
    // strip that out.
    this.scannedFiles.push(this.filename);

    results.forEach((message) => {
      let extraShortDescription = '';

      // Fatal error messages (like SyntaxErrors) are a bit different, we
      // need to handle them specially. Messages related to parsing errors do
      // not have a `ruleId`, which is why we check that, too.
      if (message.fatal === true && message.ruleId === null) {
        // If there was a parsing error during the sourceType detection, we
        // want to add it to the short description. We start by adding it to
        // a temporary variable in case there are other messages we want to
        // append to the final short description (which will be the `message`
        // in the final output).
        if (detectedSourceType.parsingError !== null) {
          const { type, error } = detectedSourceType.parsingError;
          extraShortDescription = `(Parsing as ${type} error: ${error})`;
        }

        // If there was another error, we want to append it to the short
        // description as well. `message.message` will contain the full
        // exception message, which likely includes a prefix that we don't
        // want to keep.
        const formattedError = message.message.replace('Parsing error: ', '');
        extraShortDescription = [
          extraShortDescription,
          oneLine`(Parsing as ${this.sourceType} error: ${formattedError} at
            line: ${message.line} and column: ${message.column})`,
        ].join(' ');
        // eslint-disable-next-line no-param-reassign
        message.message = _messages.JS_SYNTAX_ERROR.code;
      }

      if (typeof message.message === 'undefined') {
        throw new Error(
          oneLine`JS rules must pass a valid message as
          the second argument to context.report()`
        );
      }

      // Fallback to looking up the message object by the message
      let code = message.message;
      let shortDescription;
      let description;

      // Support 3rd party eslint rules that don't have our internal
      // message structure and allow us to optionally overwrite
      // their `message` and `description`.
      if (Object.prototype.hasOwnProperty.call(_messages, code)) {
        ({ message: shortDescription, description } = _messages[code]);
      } else if (
        Object.prototype.hasOwnProperty.call(
          messages.ESLINT_OVERWRITE_MESSAGE,
          message.ruleId
        )
      ) {
        const overwrites = messages.ESLINT_OVERWRITE_MESSAGE[message.ruleId];
        shortDescription = overwrites.message || message.message;
        description = overwrites.description || message.description;

        if (overwrites.code) {
          code = overwrites.code;
        }
      } else {
        shortDescription = code;
        description = null;
      }

      if (extraShortDescription.length) {
        shortDescription += ` ${extraShortDescription}`;
      }

      this.linterMessages.push({
        code,
        column: message.column,
        description,
        file: this.filename,
        line: message.line,
        message: shortDescription,
        sourceCode: message.source,
        type: ESLINT_TYPES[message.severity],
      });
    });

    return {
      linterMessages: this.linterMessages,
      scannedFiles: this.scannedFiles,
    };
  }