taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

eslint.config.mjs (9183B)


      1 // This file has been placed into the public domain.
      2 
      3 import { createRequire } from "node:module";
      4 const require = createRequire(import.meta.url);
      5 
      6 import { dirname, join } from "node:path";
      7 import { fileURLToPath } from "node:url";
      8 
      9 const __dirname = dirname(fileURLToPath(import.meta.url));
     10 
     11 const resolve = (pkg) => {
     12   const qaToolingDir = join(__dirname, "packages/qa-tooling");
     13   try {
     14     return require.resolve(pkg, { paths: [qaToolingDir] });
     15   } catch {
     16     return pkg;
     17   }
     18 };
     19 
     20 const eslint = require(resolve("@eslint/js"));
     21 const tseslint = require(resolve("typescript-eslint"));
     22 const reactPlugin = require(resolve("eslint-plugin-react"));
     23 const reactHooksPlugin = require(resolve("eslint-plugin-react-hooks"));
     24 const importPlugin = require(resolve("eslint-plugin-import"));
     25 const jsxA11yPlugin = require(resolve("eslint-plugin-jsx-a11y"));
     26 const prettierConfig = require(resolve("eslint-config-prettier"));
     27 const globals = require(resolve("globals"));
     28 
     29 /**
     30  * Barrel modules of a package, which legitimately re-export everything.
     31  */
     32 const barrelFiles = ["**/index.ts", "**/index.*.ts"];
     33 
     34 const barrelImportMessage =
     35   "Importing the package barrel from inside the package creates an import " +
     36   "cycle. Import from the module that defines the symbol.";
     37 
     38 /**
     39  * Ban importing a package's own barrel (src/index.ts and its platform
     40  * variants) from the modules of that package.
     41  *
     42  * The relative path to the barrel depends on how deep the importing file
     43  * sits, and only that exact path may be banned: a folder's own index.ts is
     44  * an ordinary module, and several packages deliberately split a component
     45  * across index/state/views.  So one config block per depth, each pairing the
     46  * files at that depth with the exact way up to the package root.
     47  */
     48 const barrelImportBan = (maxDepth) =>
     49   Array.from({ length: maxDepth }, (_, i) => {
     50     const depth = i + 1;
     51     const subdirs = "*/".repeat(depth - 1);
     52     const toRoot = depth === 1 ? "./" : "../".repeat(depth - 1);
     53     return {
     54       files: [`packages/*/src/${subdirs}*.{ts,tsx}`],
     55       rules: {
     56         "no-restricted-imports": [
     57           "error",
     58           {
     59             patterns: [
     60               {
     61                 group: [`${toRoot}index.js`, `${toRoot}index.*.js`],
     62                 message: barrelImportMessage,
     63               },
     64             ],
     65           },
     66         ],
     67       },
     68     };
     69   });
     70 
     71 export default tseslint.config(
     72   {
     73     ignores: [
     74       "prebuilt/**",
     75       "**/dist/**",
     76       "**/lib/**",
     77       "**/node_modules/**",
     78       "**/tsconfig.tsbuildinfo",
     79       "**/.eslintrc.js",
     80       "**/.eslintrc.cjs",
     81       // Generated bundle, not source.
     82       "packages/web-util/src/tailwind.js",
     83       // Third-party code kept in-tree.  It is maintained by syncing with
     84       // upstream, so it has to stay close to it; upstream's style is not ours
     85       // to fix, and reformatting it would make the next sync harder.
     86       "packages/taler-util/src/globbing/**", // minimatch
     87       "packages/taler-util/src/punycode.ts", // punycode.js
     88       "packages/taler-util/src/whatwg-url.ts", // jsdom/whatwg-url
     89       "packages/taler-util/src/bech32.ts", // BIP-173 reference code
     90       "packages/taler-util/src/segwit_addr.ts", // BIP-173 reference code
     91       "packages/idb-bridge/src/idbtypes.ts", // TypeScript's DOM IndexedDB types
     92       // Web Platform Tests ported verbatim, deliberately kept close to upstream.
     93       "packages/idb-bridge/src/idb-wpt-ported/**",
     94       // Input fixtures for the pogen extractor, not code that runs.
     95       "packages/pogen/example/**",
     96     ],
     97   },
     98   eslint.configs.recommended,
     99   ...tseslint.configs.recommended,
    100   {
    101     files: ["**/*.{ts,tsx,js,jsx,mjs,cjs}"],
    102     plugins: {
    103       react: reactPlugin,
    104       "react-hooks": reactHooksPlugin,
    105       "jsx-a11y": jsxA11yPlugin,
    106       import: importPlugin,
    107     },
    108     languageOptions: {
    109       globals: {
    110         ...globals.browser,
    111         ...globals.node,
    112         ...globals.es2021,
    113       },
    114       parserOptions: {
    115         ecmaVersion: "latest",
    116         sourceType: "module",
    117         ecmaFeatures: {
    118           jsx: true,
    119         },
    120       },
    121     },
    122     settings: {
    123       react: {
    124         version: "18.0",
    125         pragma: "h",
    126       },
    127     },
    128     rules: {
    129       // ---------------------------------------------------------------
    130       // Correctness.  These flag code that is, or is about to be, wrong.
    131       // ---------------------------------------------------------------
    132       ...reactHooksPlugin.configs.recommended.rules,
    133       // Calling a hook conditionally desynchronizes the hook order between
    134       // renders, which mixes up one hook's state with another's. These are
    135       // real defects; there are too many to unpick here, so they are visible
    136       // rather than blocking.
    137       "react-hooks/rules-of-hooks": "warn",
    138       // A list rendered without keys re-uses component state across items.
    139       "react/jsx-key": "error",
    140       "react/jsx-no-undef": "error",
    141       // Not diagnostics: these two exist so that no-unused-vars can see the
    142       // identifiers a JSX expression references.  Without them every
    143       // component imported for use in JSX is reported as unused.
    144       "react/jsx-uses-vars": "error",
    145       "react/jsx-uses-react": "error",
    146       // Node16 module resolution: an import without the extension fails at
    147       // run time, and tsc does not catch it in every configuration.
    148       "import/extensions": ["error", "ignorePackages"],
    149       // An infinite loop is written as `while (true)` on purpose.
    150       "no-constant-condition": ["error", { checkLoops: false }],
    151       // An empty catch is a deliberate "ignore this", an empty block is not.
    152       "no-empty": ["error", { allowEmptyCatch: true }],
    153       // `cond && fn()` is a call, not a stray expression; the codebase uses it
    154       // throughout. What is left is the genuine case: a statement with no
    155       // effect at all.
    156       "@typescript-eslint/no-unused-expressions": [
    157         "error",
    158         { allowShortCircuit: true, allowTernary: true },
    159       ],
    160 
    161       // ---------------------------------------------------------------
    162       // Known debt.  Real problems, too many to fix in one go, so they
    163       // stay visible without failing the run.
    164       // ---------------------------------------------------------------
    165       "@typescript-eslint/no-unused-vars": ["warn", { args: "none" }],
    166       // Accessibility findings are real, but there are too many to clear here.
    167       // Downgraded rather than dropped, and only the ones the recommended set
    168       // actually turns on -- mapping the whole table would switch on rules
    169       // that upstream deliberately leaves off.
    170       ...Object.fromEntries(
    171         Object.entries(jsxA11yPlugin.configs.recommended.rules)
    172           .filter(([, setting]) => {
    173             const severity = Array.isArray(setting) ? setting[0] : setting;
    174             return severity !== "off" && severity !== 0;
    175           })
    176           .map(([rule]) => [rule, "warn"]),
    177       ),
    178 
    179       // ---------------------------------------------------------------
    180       // Style and preference.  Prettier owns layout; the rest below is a
    181       // matter of taste and should not be reported as a defect.
    182       // ---------------------------------------------------------------
    183       "no-var": "off",
    184       "prefer-const": "off",
    185       "no-extra-boolean-cast": "off",
    186       "no-prototype-builtins": "off",
    187       "@typescript-eslint/explicit-function-return-type": "off",
    188       "@typescript-eslint/no-use-before-define": "off",
    189       "@typescript-eslint/no-this-alias": "off",
    190       "@typescript-eslint/no-empty-object-type": "off",
    191       "@typescript-eslint/no-explicit-any": "off",
    192       "@typescript-eslint/no-namespace": "off",
    193       "@typescript-eslint/ban-ts-comment": "off",
    194       // preact/compat provides the JSX pragma; React is not in scope.
    195       "react/react-in-jsx-scope": "off",
    196     },
    197   },
    198   // A module that imports its own package barrel pulls in every other module
    199   // of the package, which is how the import cycles here are formed: the cycle
    200   // then breaks whichever module happens to be initialized first.
    201   ...barrelImportBan(5),
    202   {
    203     // A barrel re-exporting the package is exactly what these files are for.
    204     files: barrelFiles,
    205     rules: {
    206       "no-restricted-imports": "off",
    207     },
    208   },
    209   {
    210     // chai states an assertion as a property access -- `expect(x).undefined`
    211     // is the assertion, not a statement someone forgot to finish.
    212     files: ["**/*.test.{ts,tsx}"],
    213     rules: {
    214       "@typescript-eslint/no-unused-expressions": "off",
    215     },
    216   },
    217   {
    218     // Shims that hand CommonJS built-ins to esbuild-bundled code. Reaching for
    219     // require() is the whole point of the file.
    220     files: ["**/import-meta-url.js"],
    221     rules: {
    222       "@typescript-eslint/no-require-imports": "off",
    223     },
    224   },
    225   {
    226     // No React here: taler-harness is a CLI, and its `use*` helpers are test
    227     // fixtures, not hooks.
    228     files: ["packages/taler-harness/**"],
    229     rules: {
    230       "react-hooks/rules-of-hooks": "off",
    231       "react-hooks/exhaustive-deps": "off",
    232     },
    233   },
    234   {
    235     files: ["**/*.js", "**/*.mjs", "**/*.cjs"],
    236     ...tseslint.configs.disableTypeChecked,
    237   },
    238   prettierConfig,
    239 );