taler-typescript-core

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

eslint.config.mjs (9205B)


      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       "**/vendor/**",
     79       "**/tsconfig.tsbuildinfo",
     80       "**/.eslintrc.js",
     81       "**/.eslintrc.cjs",
     82       // Generated bundle, not source.
     83       "packages/web-util/src/tailwind.js",
     84       // Third-party code kept in-tree.  It is maintained by syncing with
     85       // upstream, so it has to stay close to it; upstream's style is not ours
     86       // to fix, and reformatting it would make the next sync harder.
     87       "packages/taler-util/src/globbing/**", // minimatch
     88       "packages/taler-util/src/punycode.ts", // punycode.js
     89       "packages/taler-util/src/whatwg-url.ts", // jsdom/whatwg-url
     90       "packages/taler-util/src/bech32.ts", // BIP-173 reference code
     91       "packages/taler-util/src/segwit_addr.ts", // BIP-173 reference code
     92       "packages/idb-bridge/src/idbtypes.ts", // TypeScript's DOM IndexedDB types
     93       // Web Platform Tests ported verbatim, deliberately kept close to upstream.
     94       "packages/idb-bridge/src/idb-wpt-ported/**",
     95       // Input fixtures for the pogen extractor, not code that runs.
     96       "packages/pogen/example/**",
     97     ],
     98   },
     99   eslint.configs.recommended,
    100   ...tseslint.configs.recommended,
    101   {
    102     files: ["**/*.{ts,tsx,js,jsx,mjs,cjs}"],
    103     plugins: {
    104       react: reactPlugin,
    105       "react-hooks": reactHooksPlugin,
    106       "jsx-a11y": jsxA11yPlugin,
    107       import: importPlugin,
    108     },
    109     languageOptions: {
    110       globals: {
    111         ...globals.browser,
    112         ...globals.node,
    113         ...globals.es2021,
    114       },
    115       parserOptions: {
    116         ecmaVersion: "latest",
    117         sourceType: "module",
    118         ecmaFeatures: {
    119           jsx: true,
    120         },
    121       },
    122     },
    123     settings: {
    124       react: {
    125         version: "18.0",
    126         pragma: "h",
    127       },
    128     },
    129     rules: {
    130       // ---------------------------------------------------------------
    131       // Correctness.  These flag code that is, or is about to be, wrong.
    132       // ---------------------------------------------------------------
    133       ...reactHooksPlugin.configs.recommended.rules,
    134       // Calling a hook conditionally desynchronizes the hook order between
    135       // renders, which mixes up one hook's state with another's. These are
    136       // real defects; there are too many to unpick here, so they are visible
    137       // rather than blocking.
    138       "react-hooks/rules-of-hooks": "warn",
    139       // A list rendered without keys re-uses component state across items.
    140       "react/jsx-key": "error",
    141       "react/jsx-no-undef": "error",
    142       // Not diagnostics: these two exist so that no-unused-vars can see the
    143       // identifiers a JSX expression references.  Without them every
    144       // component imported for use in JSX is reported as unused.
    145       "react/jsx-uses-vars": "error",
    146       "react/jsx-uses-react": "error",
    147       // Node16 module resolution: an import without the extension fails at
    148       // run time, and tsc does not catch it in every configuration.
    149       "import/extensions": ["error", "ignorePackages"],
    150       // An infinite loop is written as `while (true)` on purpose.
    151       "no-constant-condition": ["error", { checkLoops: false }],
    152       // An empty catch is a deliberate "ignore this", an empty block is not.
    153       "no-empty": ["error", { allowEmptyCatch: true }],
    154       // `cond && fn()` is a call, not a stray expression; the codebase uses it
    155       // throughout. What is left is the genuine case: a statement with no
    156       // effect at all.
    157       "@typescript-eslint/no-unused-expressions": [
    158         "error",
    159         { allowShortCircuit: true, allowTernary: true },
    160       ],
    161 
    162       // ---------------------------------------------------------------
    163       // Known debt.  Real problems, too many to fix in one go, so they
    164       // stay visible without failing the run.
    165       // ---------------------------------------------------------------
    166       "@typescript-eslint/no-unused-vars": ["warn", { args: "none" }],
    167       // Accessibility findings are real, but there are too many to clear here.
    168       // Downgraded rather than dropped, and only the ones the recommended set
    169       // actually turns on -- mapping the whole table would switch on rules
    170       // that upstream deliberately leaves off.
    171       ...Object.fromEntries(
    172         Object.entries(jsxA11yPlugin.configs.recommended.rules)
    173           .filter(([, setting]) => {
    174             const severity = Array.isArray(setting) ? setting[0] : setting;
    175             return severity !== "off" && severity !== 0;
    176           })
    177           .map(([rule]) => [rule, "warn"]),
    178       ),
    179 
    180       // ---------------------------------------------------------------
    181       // Style and preference.  Prettier owns layout; the rest below is a
    182       // matter of taste and should not be reported as a defect.
    183       // ---------------------------------------------------------------
    184       "no-var": "off",
    185       "prefer-const": "off",
    186       "no-extra-boolean-cast": "off",
    187       "no-prototype-builtins": "off",
    188       "@typescript-eslint/explicit-function-return-type": "off",
    189       "@typescript-eslint/no-use-before-define": "off",
    190       "@typescript-eslint/no-this-alias": "off",
    191       "@typescript-eslint/no-empty-object-type": "off",
    192       "@typescript-eslint/no-explicit-any": "off",
    193       "@typescript-eslint/no-namespace": "off",
    194       "@typescript-eslint/ban-ts-comment": "off",
    195       // preact/compat provides the JSX pragma; React is not in scope.
    196       "react/react-in-jsx-scope": "off",
    197     },
    198   },
    199   // A module that imports its own package barrel pulls in every other module
    200   // of the package, which is how the import cycles here are formed: the cycle
    201   // then breaks whichever module happens to be initialized first.
    202   ...barrelImportBan(5),
    203   {
    204     // A barrel re-exporting the package is exactly what these files are for.
    205     files: barrelFiles,
    206     rules: {
    207       "no-restricted-imports": "off",
    208     },
    209   },
    210   {
    211     // chai states an assertion as a property access -- `expect(x).undefined`
    212     // is the assertion, not a statement someone forgot to finish.
    213     files: ["**/*.test.{ts,tsx}"],
    214     rules: {
    215       "@typescript-eslint/no-unused-expressions": "off",
    216     },
    217   },
    218   {
    219     // Shims that hand CommonJS built-ins to esbuild-bundled code. Reaching for
    220     // require() is the whole point of the file.
    221     files: ["**/import-meta-url.js"],
    222     rules: {
    223       "@typescript-eslint/no-require-imports": "off",
    224     },
    225   },
    226   {
    227     // No React here: taler-harness is a CLI, and its `use*` helpers are test
    228     // fixtures, not hooks.
    229     files: ["packages/taler-harness/**"],
    230     rules: {
    231       "react-hooks/rules-of-hooks": "off",
    232       "react-hooks/exhaustive-deps": "off",
    233     },
    234   },
    235   {
    236     files: ["**/*.js", "**/*.mjs", "**/*.cjs"],
    237     ...tseslint.configs.disableTypeChecked,
    238   },
    239   prettierConfig,
    240 );