Mozilla Add-ons Linter

repository·master·Indexed 18 days ago

https://github.com/mozilla/addons-linter

A tool to validate WebExtensions for compliance with Mozilla's standards, available as a standalone CLI or Node.js library. It validates add-on packages (such as .zip or .xpi files) against rules for manifest.json, JavaScript, HTML, and package layout. The linter is used by web-ext and addons.mozilla.org to ensure security, compatibility, and adherence to policies, including checks for banned third-party library versions.

Tokens
34.1K
Snippets
40
Records
57
Agent score
63%

What's inside addons-linter

  1. Supported Add-on Types

    master

    The addons-linter is designed to support several types of add-ons. While amo-validator remains the linter for legacy add-ons, addons-linter focuses on the following types:

    • Web Extensions: Validation of manifest.json, content script JS validation, and API permission checks.
    • Dictionaries: Support for dictionary-specific linting rules.
    • Language Packs: Validation of chrome.manifest and other language pack specific rules.
    • Search Add-ons: Validation of OpenSearch XML files.
  2. How the addons-linter works

    master

    The linter follows a pipeline architecture to validate add-on packages:

    1. Extraction: It takes an add-on package (like an .xpi or .zip) and extracts the metadata.
    2. Scanners: The linter uses file-type specific scanners (e.g., JavaScriptScanner). Scanners iterate through relevant files and pass them through a parser.
    3. Rules: Parsers hand off data to a set of rules. Each rule is a standalone function that performs specific checks and returns a list of validation objects.
    4. Collector: All validation messages generated by rules are sent to the Collector, which acts as an in-memory store for all collected messages.
    5. Output: Once processing is complete, the Collector's data is formatted and output as either text or JSON.
  3. Understand banned and unadvised third-party libraries

    master
    The linter enforces rules regarding third-party JavaScript libraries used within add-ons. Certain libraries or specific versions of libraries are either banned or unadvised due to security vulnerabilities or lack of support. When developing an add-on, ensure you are not using banned versions of AngularJS, jQuery, or DOMPurify to avoid submission rejection.
  4. Import the most recent Firefox schema

    master

    To update the linter with the latest Firefox schema, you must download a specific Mercurial tag from the mozilla-unified repository and then run the import script. This process is typically performed after a major Firefox beta merge. Note that downloading tags can take significant time as releases are approximately 370MB.

    # 1. List available tags (example using version 63)
    ./scripts/list-firefox-tags 63
    
    # 2. Download the desired tag
    ./scripts/download-import-tag FIREFOX_63_0b14_RELEASE
    
    # 3. Import the schema from the downloaded tarball
    ./scripts/firefox-schema-import tmp/FIREFOX_63_0b14_RELEASE.tar.gz
  5. Verify schema updates and custom formats

    master

    After importing a new Firefox schema, perform the following manual checks to ensure linter accuracy:

    1. Review Schema Changes: Carefully inspect the update for new properties. Determine if any properties are intended only for internal add-ons and should trigger warnings for regular add-ons. If unsure, consult the relevant Firefox teams.
    2. Update Custom Formats: Check src/schema/formats.js for custom format validations (e.g., manifestShortcutKey). Ensure these are updated to match the upstream code provided in the new schema.
  6. Understand validation message types

    master

    The linter categorizes validation results into three distinct types. These types are used to communicate the severity of a finding:

    • error (VALIDATION_ERROR): A critical issue that must be resolved.
    • warning (VALIDATION_WARNING): A potential issue that should be reviewed.
    • notice (VALIDATION_NOTICE): Informational messages that do not indicate a problem.
  7. Use addons-linter as a library

    master

    You can integrate the linter directly into your Node.js applications using the createInstance method.

    To prevent the linter from exiting your Node.js process when it finishes, set runAsBinary: false in the configuration object. The config object mimics the command-line arguments provided via yargs.

    import addonsLinter from 'addons-linter';
    
    const sourceDir = process.cwd();
    
    const linter = addonsLinter.createInstance({
      config: {
        // The directory to the extension (mimics first CLI argument)
        _: [sourceDir],
        logLevel: process.env.VERBOSE ? 'debug' : 'fatal',
        stack: Boolean(process.env.VERBOSE),
        pretty: false,
        warningsAsErrors: false,
        metadata: false,
        output: 'none',
        boring: false,
        selfHosted: false,
        // Function to determine if a file should be scanned
        shouldScanFile: (fileName) => true,
      },
      // Prevents the linter from exiting the nodejs application
      runAsBinary: false,
    });
    
    linter.run()
      .then((linterResults) => {
        // Handle results
      })
      .catch((err) => console.error("addons-linter failure: ", err));
    import addonsLinter from 'addons-linter';
    
    const sourceDir = process.cwd();
    
    const linter = addonsLinter.createInstance({
      config: {
        _: [sourceDir],
        logLevel: process.env.VERBOSE ? 'debug' : 'fatal',
        stack: Boolean(process.env.VERBOSE),
        pretty: false,
        warningsAsErrors: false,
        metadata: false,
        output: 'none',
        boring: false,
        selfHosted: false,
        shouldScanFile: (fileName) => true,
      },
      runAsBinary: false,
    });
    
    linter.run()
      .then((linterResults) => ...) 
      .catch((err) => console.error("addons-linter failure: ", err));
  8. ESLint configuration for addons-linter

    master

    The addons-linter project uses a flat ESLint configuration via eslint.config.mjs. It extends eslint-config-amo/base.js, utilizes the eslint-plugin-amo plugin, and uses @babel/eslint-parser for parsing.

    Key configuration details:

    • Globals: Supports both node and browser environments.
    • Module Resolution: Configured to resolve modules from node_modules, src, and vendor directories.
    • Ignored Patterns: The configuration ignores coverage, distribution, locale, vendor, specific scripts, and various test fixtures/JSON files.
    import { defineConfig, globalIgnores } from 'eslint/config';
    import amoBaseConfig from 'eslint-config-amo/base.js';
    import amoPlugin from 'eslint-plugin-amo';
    import globals from 'globals';
    import babelParser from '@babel/eslint-parser';
    
    export default defineConfig([
      globalIgnores([
        '**/coverage',
        '**/dist',
        // ... other ignores
      ]),
      {
        extends: [amoBaseConfig],
        plugins: { amo: amoPlugin },
        languageOptions: {
          globals: { ...globals.node, ...globals.browser },
          parser: babelParser,
        },
        settings: {
          'import/resolver': {
            node: { moduleDirectory: ['node_modules', 'src', 'vendor'] },
          },
        },
        rules: {
          'no-console': 'off',
          'amo/i18n-no-tagged-templates': 'error',
          'import/no-unresolved': ['error', { ignore: ['^addons-scanner-utils/'] }],
        },
      },
    ]);
  9. Configure Jest integration tests

    master

    The integration test configuration extends the base jest.config.js and specifically targets files that follow the integration naming convention. It uses the testMatch pattern to identify integration test files, which must match the pattern <rootDir>/**/integration(*).js?(x). This includes files ending in integration.js, integration.jsx, integration.ts, or integration.tsx located anywhere within the project directory.

    const config = require('./jest.config');
    
    module.exports = {
      ...config,
      testMatch: ['<rootDir>/**/integration(*).js?(x)'],
    };
  10. Configure default linter settings

    master

    The DEFAULT_CONFIG object defines the standard behavior of the linter. If you are programmatically invoking the linter, you can override these keys:

    • logLevel: Defaults to 'fatal'.
    • warningsAsErrors: Boolean; if true, warnings are treated as errors.
    • output: Defaults to 'text'.
    • metadata: Boolean; whether to include metadata in output.
    • pretty: Boolean; whether to use pretty-printed output.
    • stack: Boolean; whether to include stack traces.
    • boring: Boolean.
    • enterprise: Boolean.
    • privileged: Boolean.
    • selfHosted: Boolean.
    • enableBackgroundServiceWorker: Boolean.
    • minManifestVersion: Defaults to 2.
    • maxManifestVersion: Defaults to 3.
    • disableXpiAutoclose: Boolean.
    • enableDataCollectionPermissions: Boolean; defaults to true.
    export const DEFAULT_CONFIG = {
      logLevel: 'fatal',
      warningsAsErrors: false,
      output: 'text',
      metadata: false,
      pretty: false,
      stack: false,
      boring: false,
      enterprise: false,
      privileged: false,
      selfHosted: false,
      enableBackgroundServiceWorker: false,
      minManifestVersion: 2,
      maxManifestVersion: 3,
      disableXpiAutoclose: false,
      enableDataCollectionPermissions: true,
    };
  11. Reference JavaScript linter rules

    master

    The linter checks JavaScript files for security, compatibility, and best practices. Rules are categorized by severity: error, warning, and notice.

    | Message code                | Severity | Description                                                                            |
    | --------------------------- | -------- | --------------------------------------------------------------------------------------|
    | `KNOWN_LIBRARY`             | notice   | This is version of a JS library is known and generally accepted.                       |
    | `UNEXPECTED_GLOBAL_ARG`     | warning  | Unexpected global passed as an argument.                                               |
    | `NO_IMPLIED_EVAL`           | warning  | disallow the use of `eval()`-like methods.                                              |
    | `NO_DOCUMENT_WRITE`         | warning  | Use of `document.write` strongly discouraged.                                          |
    | `JS_SYNTAX_ERROR`           | warning  | JavaScript compile-time error.                                                         |
    | `UNADVISED_LIBRARY`         | warning  | This version of a JS library is not recommended.                                       |
    | `DEPRECATED_API`            | warning  | API is deprecated.                                                                     |
    | `STORAGE_SYNC`              | warning  | Temporary IDs can cause issues with `storage.sync`.                                     |
    | `STORAGE_MANAGED`           | warning  | Temporary IDs can cause issues with `storage.managed`.                                 |
    | `IDENTITY_GETREDIRECTURL`   | warning  | Temporary IDs can cause issues with `identity.getRedirectURL`.                         |
    | `RUNTIME_ONMESSAGEEXTERNAL` | warning  | Temporary IDs can cause issues with `runtime.onMessageExternal`.                       |
    | `RUNTIME_ONCONNECTEXTERNAL` | warning  | Temporary IDs can cause issues with `runtime.onConnectExternal`.                       |
    | `BANNED_LIBRARY`            | error    | This version of a JS library is banned for security reasons.                           |
    | `INCOMPATIBLE_API`          | warning  | API not compatible with `applications.gecko.strict_min_version`                        |
    | `ANDROID_INCOMPATIBLE_API`  | warning  | API not compatible with Firefox for Android at `applications.gecko.strict_min_version` |