tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

eslint.config.mjs (15259B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 import sdl from "@microsoft/eslint-plugin-sdl";
      6 import eslintConfigPrettier from "eslint-config-prettier/flat";
      7 import html from "eslint-plugin-html";
      8 import importPlugin from "eslint-plugin-import";
      9 import json from "@eslint/json";
     10 import lit from "eslint-plugin-lit";
     11 import mozilla from "eslint-plugin-mozilla";
     12 import reactHooks from "eslint-plugin-react-hooks";
     13 
     14 import fs from "fs";
     15 import globals from "globals";
     16 import path from "path";
     17 
     18 import globalIgnores from "./eslint-ignores.config.mjs";
     19 import testPathsConfig from "./eslint-test-paths.config.mjs";
     20 import repositoryGlobals from "./eslint-file-globals.config.mjs";
     21 import rollouts from "./eslint-rollouts.config.mjs";
     22 import subdirConfigs from "./eslint-subdirs.config.mjs";
     23 
     24 const testPaths = testPathsConfig.testPaths;
     25 
     26 function readFile(filePath) {
     27  return fs
     28    .readFileSync(filePath, { encoding: "utf-8" })
     29    .split("\n")
     30    .filter(p => p && !p.startsWith("#"));
     31 }
     32 
     33 const httpTestingPaths = [
     34  `**/*mixedcontent*.{${mozilla.allFileExtensions.join(",")}}`,
     35  `**/*CrossOrigin*.{${mozilla.allFileExtensions.join(",")}}`,
     36  `**/*crossorigin*.{${mozilla.allFileExtensions.join(",")}}`,
     37  `**/*cors*.{${mozilla.allFileExtensions.join(",")}}`,
     38  `**/*downgrade*.{${mozilla.allFileExtensions.join(",")}}`,
     39  `**/*Downgrade*.{${mozilla.allFileExtensions.join(",")}}`,
     40 ];
     41 
     42 /**
     43 * Takes each path in the paths array, and expands it with the list of extensions
     44 * that ESLint is watching.
     45 *
     46 * @param {object} options
     47 * @param {string[]} options.paths
     48 *   The list of paths to wrap.
     49 * @param {string[]} [options.excludedExtensions]
     50 *   The list of extensions to be excluded from the wrapping.
     51 */
     52 function wrapPaths({ paths, excludedExtensions }) {
     53  let extensions = excludedExtensions
     54    ? mozilla.allFileExtensions.filter(f => !excludedExtensions.includes(f))
     55    : mozilla.allFileExtensions;
     56  return paths.map(p => {
     57    if (p.endsWith("**")) {
     58      return p + `/*.{${extensions.join(",")}}`;
     59    }
     60    if (p.endsWith("/")) {
     61      return p + `**/*.{${extensions.join(",")}}`;
     62    }
     63    if (p.endsWith("*")) {
     64      return p + `.{${extensions.join(",")}}`;
     65    }
     66    return p;
     67  });
     68 }
     69 
     70 /**
     71 * Wraps the paths listed in the files section of a configuration with the
     72 * file extensions that ESLint is watching.
     73 *
     74 * @param {object} configs
     75 */
     76 function wrapPathsInConfig(configs) {
     77  for (let config of configs) {
     78    config.files = wrapPaths({ paths: config.files });
     79  }
     80  return configs;
     81 }
     82 
     83 let config = [
     84  {
     85    name: "import-plugin-settings",
     86    settings: {
     87      "import/extensions": [".mjs"],
     88      "import/resolver": {
     89        [path.resolve(import.meta.dirname, "srcdir-resolver.js")]: {},
     90        node: {},
     91      },
     92    },
     93  },
     94  {
     95    name: "ignores",
     96    ignores: [
     97      ...globalIgnores,
     98      ...readFile(
     99        path.join(
    100          import.meta.dirname,
    101          "tools",
    102          "rewriting",
    103          "ThirdPartyPaths.txt"
    104        )
    105      ),
    106      ...readFile(
    107        path.join(import.meta.dirname, "tools", "rewriting", "Generated.txt")
    108      ),
    109      ...readFile(
    110        path.join(
    111          import.meta.dirname,
    112          "devtools",
    113          "client",
    114          "debugger",
    115          "src",
    116          ".eslintignore"
    117        )
    118      ).map(p => `devtools/client/debugger/src/${p}`),
    119    ],
    120  },
    121  {
    122    name: "all-files",
    123    files: wrapPaths({ paths: ["**"] }),
    124    linterOptions: {
    125      // With this option on, if an inline comment disables a rule, and the
    126      // rule is able to be automatically fixed, then ESLint will remove the
    127      // inline comment and apply the fix. We don't want this because we have
    128      // some rules that intentionally need to be turned off in specific cases,
    129      // e.g. @microsoft/sdl/no-insecure-url.
    130      reportUnusedDisableDirectives: "off",
    131    },
    132    plugins: { lit },
    133    rules: {
    134      "lit/quoted-expressions": ["error", "never"],
    135      "lit/no-invalid-html": "error",
    136      "lit/no-value-attribute": "error",
    137    },
    138  },
    139  {
    140    name: "source-type-script",
    141    files: ["**/*.{js,json,html,sjs,xhtml}"],
    142    languageOptions: {
    143      sourceType: "script",
    144    },
    145  },
    146  ...mozilla.configs["flat/recommended"],
    147  {
    148    name: "json-recommended-with-comments",
    149    files: ["**/*.json"],
    150    language: "json/jsonc",
    151    ...json.configs.recommended,
    152  },
    153  {
    154    name: "json-recommended-no-comments",
    155    files: ["**/package.json"],
    156    language: "json/json",
    157    ...json.configs.recommended,
    158  },
    159  {
    160    name: "json-empty-keys-off-for-image_builder",
    161    files: ["taskcluster/docker/image_builder/policy.json"],
    162    rules: {
    163      "json/no-empty-keys": "off",
    164    },
    165  },
    166  {
    167    name: "eslint-plugin-html",
    168    files: ["**/*.html", "**/*.xhtml"],
    169    plugins: { html },
    170  },
    171 
    172  {
    173    name: "define-globals-for-browser-env",
    174    // Not available for sjs files.
    175    files: wrapPaths({ paths: ["**"], excludedExtensions: ["sjs"] }),
    176    ignores: [
    177      // Also not available for various other scopes and tools.
    178      "**/*.sys.mjs",
    179      "**/?(*.)worker.?(m)js",
    180      "**/?(*.)serviceworker.?(m)js",
    181      ...wrapPaths({
    182        paths: testPaths.xpcshell,
    183        excludedExtensions: ["mjs", "sjs"],
    184      }),
    185      "tools/lint/eslint/**",
    186    ],
    187    languageOptions: {
    188      globals: globals.browser,
    189    },
    190  },
    191  {
    192    // Generally we assume that all files, except mjs ones are in our
    193    // privileged and specific environment. mjs are handled separately by
    194    // the recommended configuration in eslint-plugin-mozilla.
    195    name: "define-privileged-and-specific-globals-for-most-files",
    196    files: wrapPaths({ paths: ["**"], excludedExtensions: ["json"] }),
    197    ignores: ["browser/components/storybook/**", "tools"],
    198    languageOptions: {
    199      globals: {
    200        ...mozilla.environments.privileged.globals,
    201        ...mozilla.environments.specific.globals,
    202      },
    203    },
    204  },
    205  {
    206    name: "define-globals-for-node-files",
    207    files: [
    208      // All .eslintrc.mjs files are in the node environment, so turn that
    209      // on here.
    210      "**/.eslintrc*.mjs",
    211      // .js files in the top-level are generally assumed to be node.
    212      "\.*.js",
    213      // *.config.js files are generally assumed to be configuration files
    214      // based for node.
    215      "**/*.config.js",
    216      // The resolver for moz-src for eslint, vscode etc.
    217      "srcdir-resolver.js",
    218    ],
    219    languageOptions: {
    220      globals: { ...globals.node, ...mozilla.turnOff(globals.browser) },
    221    },
    222  },
    223 
    224  {
    225    name: "browser-no-more-globals",
    226    files: ["browser/base/content/browser.js"],
    227    rules: {
    228      "mozilla/no-more-globals": "error",
    229    },
    230  },
    231  {
    232    name: "jsx-files",
    233    files: ["**/*.jsx", "browser/components/storybook/.storybook/**/*.mjs"],
    234    languageOptions: {
    235      parserOptions: {
    236        ecmaFeatures: {
    237          jsx: true,
    238        },
    239      },
    240    },
    241  },
    242  {
    243    name: "eslint-plugin-import-rules",
    244    files: ["**/*.mjs"],
    245    plugins: { import: importPlugin },
    246    rules: {
    247      "import/default": "error",
    248      "import/export": "error",
    249      "import/named": "error",
    250      "import/namespace": "error",
    251      "import/newline-after-import": "error",
    252      "import/no-duplicates": "error",
    253      "import/no-absolute-path": "error",
    254      "import/no-named-default": "error",
    255      "import/no-named-as-default": "error",
    256      "import/no-named-as-default-member": "error",
    257      "import/no-self-import": "error",
    258      "import/no-unassigned-import": "error",
    259      "import/no-unresolved": [
    260        "error",
    261        // Bug 1773473 - Ignore resolver URLs for chrome and resource as we
    262        // do not yet have a resolver for them.
    263        { ignore: ["chrome://", "resource://"] },
    264      ],
    265      "import/no-useless-path-segments": "error",
    266    },
    267  },
    268  {
    269    name: "turn-off-unassigned-import-for-stories",
    270    // Turn off no-unassigned-import for files that typically test our
    271    // custom elements, which are imported for the side effects (ie
    272    // the custom element being registered) rather than any particular
    273    // export:
    274    files: ["**/*.stories.mjs"],
    275    plugins: { import: importPlugin },
    276    rules: {
    277      "import/no-unassigned-import": "off",
    278    },
    279  },
    280  {
    281    ...mozilla.configs["flat/general-test"],
    282    files: wrapPaths({ paths: ["**/test/**", "**/tests/**"] }),
    283  },
    284  {
    285    ...mozilla.configs["flat/xpcshell-test"],
    286    files: wrapPaths({
    287      paths: testPaths.xpcshell,
    288      excludedExtensions: ["mjs", "sjs"],
    289    }),
    290  },
    291  {
    292    name: "no-unused-vars-disable-on-headjs",
    293    // If it is an xpcshell head file, we turn off global unused variable checks, as it
    294    // would require searching the other test files to know if they are used or not.
    295    // This would be expensive and slow, and it isn't worth it for head files.
    296    // We could get developers to declare as exported, but that doesn't seem worth it.
    297    files: testPaths.xpcshell.map(filePath => `${filePath}head*.js`),
    298    rules: {
    299      "no-unused-vars": [
    300        "error",
    301        {
    302          argsIgnorePattern: "^_",
    303          caughtErrors: "none",
    304          vars: "local",
    305        },
    306      ],
    307    },
    308  },
    309  {
    310    name: "no-unused-vars-for-xpcshell",
    311    // This section enables errors of no-unused-vars globally for all test*.js
    312    // files in xpcshell test paths.
    313    // This is not done in the xpcshell-test configuration as we cannot pull
    314    // in overrides from there. We should at some stage, aim to enable this
    315    // for all files in xpcshell-tests.
    316    files: testPaths.xpcshell.map(filePath => `${filePath}test*.js`),
    317    rules: {
    318      // No declaring variables that are never used
    319      "no-unused-vars": [
    320        "error",
    321        {
    322          argsIgnorePattern: "^_",
    323          caughtErrors: "none",
    324          vars: "all",
    325        },
    326      ],
    327    },
    328  },
    329  {
    330    ...mozilla.configs["flat/browser-test"],
    331    files: wrapPaths({
    332      paths: testPaths.browser,
    333      excludedExtensions: ["mjs", "sjs"],
    334    }),
    335  },
    336  {
    337    ...mozilla.configs["flat/mochitest-test"],
    338    files: wrapPaths({
    339      paths: testPaths.mochitest,
    340      excludedExtensions: ["mjs"],
    341    }),
    342    ignores: ["security/manager/ssl/tests/mochitest/browser/**"],
    343  },
    344  {
    345    ...mozilla.configs["flat/chrome-test"],
    346    files: wrapPaths({
    347      paths: testPaths.chrome,
    348      excludedExtensions: ["mjs", "sjs"],
    349    }),
    350  },
    351  {
    352    name: "simpletest",
    353    languageOptions: {
    354      globals: {
    355        ...mozilla.environments.simpletest.globals,
    356      },
    357    },
    358    files: [
    359      ...testPaths.mochitest.map(filePath => `${filePath}/**/*.js`),
    360      ...testPaths.chrome.map(filePath => `${filePath}/**/*.js`),
    361    ],
    362  },
    363  {
    364    name: "multiple-test-kinds",
    365    // Some directories have multiple kinds of tests, and some rules
    366    // don't work well for HTML-based mochitests, so disable those.
    367    files: testPaths.xpcshell
    368      .concat(testPaths.browser)
    369      .map(filePath => [`${filePath}/**/*.html`, `${filePath}/**/*.xhtml`])
    370      .flat(),
    371    rules: {
    372      // plain/chrome mochitests don't automatically include Assert, so
    373      // autofixing `ok()` to Assert.something is bad.
    374      "mozilla/no-comparison-or-assignment-inside-ok": "off",
    375    },
    376  },
    377  {
    378    name: "test-file-reuse",
    379    // Some directories reuse `test_foo.js` files between mochitest-plain and
    380    // unit tests, or use custom postMessage-based assertion propagation into
    381    // browser tests. Ignore those too:
    382    files: wrapPaths({
    383      paths: [
    384        // Reuses xpcshell unit test scripts in mochitest-plain HTML files.
    385        "dom/indexedDB/test/**",
    386        // Dispatches functions to the webpage in ways that are hard to detect.
    387        "toolkit/components/antitracking/test/**",
    388      ],
    389    }),
    390    rules: {
    391      "mozilla/no-comparison-or-assignment-inside-ok": "off",
    392    },
    393  },
    394  {
    395    // Rules of Hooks broadly checks for camelCase "use" identifiers, so
    396    // enable only for paths actually using React to avoid false positives.
    397    name: "react-hooks",
    398    files: [
    399      "browser/components/aboutwelcome/**",
    400      "browser/components/asrouter/**",
    401      "browser/extensions/newtab/**",
    402      "devtools/**",
    403    ],
    404    ...reactHooks.configs["recommended-latest"],
    405    plugins: { "react-hooks": reactHooks },
    406    rules: {
    407      // react-hooks/recommended has exhaustive-deps as a warning, we prefer
    408      // errors, so that raised issues get addressed one way or the other.
    409      "react-hooks/exhaustive-deps": "error",
    410    },
    411  },
    412  {
    413    name: "disable-no-insecure-url-for-http-testing",
    414    // Exempt files with these paths since they have to use http for full coverage
    415    files: httpTestingPaths,
    416    plugins: { "@microsoft/sdl": sdl },
    417    rules: {
    418      "@microsoft/sdl/no-insecure-url": "off",
    419    },
    420  },
    421  {
    422    name: "mozilla/valid-jsdoc",
    423    files: wrapPaths({ paths: ["**"] }),
    424    ...mozilla.configs["flat/valid-jsdoc"],
    425  },
    426  {
    427    name: "mozilla/require-jsdoc",
    428    files: wrapPaths({ paths: ["**"] }),
    429    ...mozilla.configs["flat/require-jsdoc"],
    430  },
    431  {
    432    name: "rollout-no-browser-refs-in-toolkit",
    433    files: ["toolkit/**"],
    434    ignores: ["toolkit/**/test/**", "toolkit/**/tests/**"],
    435    plugins: { mozilla },
    436    rules: {
    437      "mozilla/no-browser-refs-in-toolkit": "warn",
    438    },
    439  },
    440  {
    441    name: "no-newtab-refs-outside-newtab",
    442    files: ["**/*.mjs", "**/*.js", "**/*.sys.mjs"],
    443    ignores: [
    444      "tools/@types/generated/**",
    445      "browser/base/content/test/static/browser_all_files_referenced.js",
    446      "tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-newtab-refs-outside-newtab.mjs",
    447      "tools/lint/eslint/eslint-plugin-mozilla/tests/no-newtab-refs-outside-newtab.mjs",
    448    ],
    449    plugins: { mozilla },
    450    rules: {
    451      "mozilla/no-newtab-refs-outside-newtab": "error",
    452    },
    453  },
    454 
    455  ...wrapPathsInConfig(subdirConfigs),
    456  ...wrapPathsInConfig(repositoryGlobals),
    457 
    458  /**
    459   * The items below should always be the last items in this order:
    460   *
    461   * - Enable eslint-config-prettier.
    462   * - Enable curly.
    463   * - Rollouts
    464   */
    465 
    466  // Turn off rules that conflict with Prettier.
    467  { name: "eslint-config-prettier", ...eslintConfigPrettier },
    468  {
    469    name: "enable-curly",
    470    files: wrapPaths({ paths: ["**/"] }),
    471    rules: {
    472      // Require braces around blocks that start a new line. This must be
    473      // configured after eslint-config-prettier is included, as otherwise
    474      // eslint-config-prettier disables the curly rule. Hence, we do
    475      // not include it in
    476      // `tools/lint/eslint/eslint-plugin-mozilla/lib/configs/recommended.js`.
    477      curly: ["error", "all"],
    478    },
    479  },
    480  ...wrapPathsInConfig(rollouts),
    481 ];
    482 
    483 // The various places we get our globals from use true/false rather than
    484 // the strings required by ESLint, so translate those here.
    485 config.map(entry => {
    486  if (entry.languageOptions?.globals) {
    487    let newGlobals = {};
    488    for (let [key, value] of Object.entries(entry.languageOptions.globals)) {
    489      if (typeof entry.languageOptions.globals[key] == "boolean") {
    490        newGlobals[key] = value ? "writable" : "readonly";
    491      } else {
    492        newGlobals[key] = value;
    493      }
    494    }
    495  }
    496  return entry;
    497 });
    498 
    499 export default config;