Migrate ESLint config to flat config for ESLint 9

Dependabot bumped eslint to ^9 across the lint-scoped TS packages but did
not migrate the legacy .eslintrc.* configs, breaking CI lint on develop.

Behavior preserved: yarn lint passes locally with the same effective rule
coverage as the pre-bump setup. Pre-existing warnings in nym-wallet and
mui-theme are unchanged. Orphan .eslintrc files in sdk/typescript outside
the lerna lint scope are left untouched.
This commit is contained in:
Tommy Verrall
2026-04-23 15:39:11 +01:00
parent ef6fc82c39
commit 7669d0933f
6 changed files with 393 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
/**
* Flat ESLint config for @nymproject/nym-wallet-app.
*
* Imports the shared React/TS preset and overrides parserOptions.project to
* point at the wallet's dedicated lint tsconfig.
*/
const sharedConfig = require('@nymproject/eslint-config-react-typescript');
module.exports = [
...sharedConfig,
{
languageOptions: {
parserOptions: {
project: './tsconfig.eslint.json',
tsconfigRootDir: __dirname,
},
},
},
{
ignores: ['dist/**', 'node_modules/**', 'src-tauri/**', '*.config.js'],
},
];
@@ -0,0 +1,123 @@
/**
* Flat ESLint config for @nymproject/mui-theme.
*
* Mirrors the previous inherited config at sdk/typescript/.eslintrc.js
* (airbnb-base, NOT airbnb-React). mui-theme has no dedicated tsconfig.eslint
* - it uses tsconfig.json, matching the previous parent default.
*
* Behavior preserved 1:1 with the pre-ESLint-9 setup.
*/
const { FlatCompat } = require('@eslint/eslintrc');
const js = require('@eslint/js');
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all,
});
module.exports = [
...compat.config({
env: {
browser: true,
es6: true,
node: true,
jest: true,
},
parserOptions: {
ecmaVersion: 2019,
sourceType: 'module',
},
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly',
},
plugins: ['prettier'],
// airbnb-typescript/base is intentionally NOT at the top level - see the
// matching note in react-components/eslint.config.js for the rationale.
extends: ['airbnb-base', 'prettier'],
rules: {
'prettier/prettier': 'error',
'import/prefer-default-export': 'off',
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: ['**/*.test.[jt]s', '**/*.spec.[jt]s'],
},
],
'import/extensions': [
'error',
'ignorePackages',
{
ts: 'never',
js: 'never',
},
],
},
overrides: [
{
files: ['**/*.ts', '**/*.tsx'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'airbnb-typescript/base',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
rules: {
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'no-use-before-define': [0],
'@typescript-eslint/no-use-before-define': [1],
'import/no-unresolved': 0,
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: ['**/*.test.ts', '**/*.spec.ts'],
},
],
quotes: [
2,
'single',
{
avoidEscape: true,
},
],
'@typescript-eslint/no-unused-vars': [
2,
{ argsIgnorePattern: '^_', caughtErrors: 'none' },
],
// Rules removed in @typescript-eslint v6/v7/v8 but still referenced
// by airbnb-typescript@16; disable to avoid unknown-rule errors.
'@typescript-eslint/lines-between-class-members': 'off',
'@typescript-eslint/no-throw-literal': 'off',
'@typescript-eslint/space-before-function-paren': 'off',
'@typescript-eslint/no-loss-of-precision': 'off',
'@typescript-eslint/quotes': 'off',
// New in @typescript-eslint v8 - was previously @typescript-eslint/no-empty-interface
// and @typescript-eslint/ban-types. Disabled to preserve pre-v8 behavior on
// existing code (the project relies on `{}` as the default type parameter in some
// .d.ts type aliases). Address in a follow-up code-cleanup PR.
'@typescript-eslint/no-empty-object-type': 'off',
// Pre-existing relative-directory imports rely on Node module resolution
// (e.g. `import './theme'` resolving to `./theme/index.ts`). The legacy
// setup did not enforce file extensions for this case either.
'import/extensions': 'off',
},
},
],
}),
{
ignores: ['tsconfig.json', '**/*.d.ts', 'dist/**/*', 'dist', 'node_modules', '*.config.js'],
},
];
@@ -0,0 +1,147 @@
/**
* Flat ESLint config for @nymproject/react.
*
* Mirrors the previous inherited config at sdk/typescript/.eslintrc.js
* (airbnb-base, NOT airbnb-React) plus the per-package parserOptions override
* that previously lived in this directory's .eslintrc.json.
*
* Behavior preserved 1:1 with the pre-ESLint-9 setup. The fact that React
* rules are not active here is pre-existing and intentionally not changed in
* this migration PR.
*/
const { FlatCompat } = require('@eslint/eslintrc');
const js = require('@eslint/js');
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all,
});
module.exports = [
...compat.config({
env: {
browser: true,
es6: true,
node: true,
jest: true,
},
parserOptions: {
ecmaVersion: 2019,
sourceType: 'module',
},
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly',
},
plugins: ['prettier'],
// airbnb-typescript/base is intentionally NOT at the top level because it
// bundles type-aware @typescript-eslint rules (e.g. dot-notation, return-await)
// that throw hard errors on .js files lacking parserOptions.project under
// typescript-eslint v8. The legacy v5 setup let these rules fail silently;
// this restructure preserves the effective behavior (no type-aware checks on
// .js files) without the new hard errors.
extends: ['airbnb-base', 'prettier'],
rules: {
'prettier/prettier': 'error',
'import/prefer-default-export': 'off',
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: ['**/*.test.[jt]s', '**/*.spec.[jt]s'],
},
],
'import/extensions': [
'error',
'ignorePackages',
{
ts: 'never',
js: 'never',
},
],
},
overrides: [
{
// Intentionally NOT including '**/*.tsx'. The legacy eslintrc had
// `files: '**/*.ts'` (no tsx), and ESLint 8's default extension list
// was .js-only, so .tsx files were never actually linted. Adding them
// here would surface a large body of pre-existing issues unrelated to
// the v9 migration; address in a follow-up dedicated to lint cleanup.
files: ['**/*.ts'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.eslint.json',
tsconfigRootDir: __dirname,
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'airbnb-typescript/base',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
rules: {
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'no-use-before-define': [0],
'@typescript-eslint/no-use-before-define': [1],
'import/no-unresolved': 0,
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: ['**/*.test.ts', '**/*.spec.ts'],
},
],
quotes: [
2,
'single',
{
avoidEscape: true,
},
],
'@typescript-eslint/no-unused-vars': [
2,
{ argsIgnorePattern: '^_', caughtErrors: 'none' },
],
// Rules removed in @typescript-eslint v6/v7/v8 but still referenced
// by airbnb-typescript@16; disable to avoid unknown-rule errors.
'@typescript-eslint/lines-between-class-members': 'off',
'@typescript-eslint/no-throw-literal': 'off',
'@typescript-eslint/space-before-function-paren': 'off',
'@typescript-eslint/no-loss-of-precision': 'off',
'@typescript-eslint/quotes': 'off',
// New in @typescript-eslint v8 - was previously @typescript-eslint/no-empty-interface
// and @typescript-eslint/ban-types. Disabled to preserve pre-v8 behavior on
// existing code. Address in a follow-up code-cleanup PR.
'@typescript-eslint/no-empty-object-type': 'off',
// Pre-existing relative-directory imports rely on Node module resolution.
// The legacy setup did not enforce file extensions for this case either.
'import/extensions': 'off',
},
},
],
}),
{
// Skip .tsx files and the .storybook dir to match what the legacy eslintrc
// setup actually linted in practice (default .js extensions plus the
// **/*.ts override). The .storybook/*.js files use modern syntax (optional
// chaining, JSX in .js) that would require a separate parser config; the
// .tsx files have a pre-existing backlog of lint issues. Both are out of
// scope for the v9 migration PR.
ignores: [
'tsconfig.json',
'**/*.d.ts',
'**/*.tsx',
'.storybook/**',
'dist/**/*',
'dist',
'node_modules',
'*.config.js',
],
},
];
@@ -0,0 +1,38 @@
/**
* Self-lint config for the shared eslint-config package itself.
*
* Intentionally lightweight - this file lints `index.js` (and only that), so
* we avoid pulling in the full airbnb / typescript-eslint chain that the
* exported config carries for consumers.
*/
const js = require('@eslint/js');
const prettierPlugin = require('eslint-plugin-prettier');
const prettierConfig = require('eslint-config-prettier');
module.exports = [
js.configs.recommended,
{
files: ['*.js'],
languageOptions: {
ecmaVersion: 2022,
sourceType: 'commonjs',
globals: {
module: 'readonly',
require: 'readonly',
__dirname: 'readonly',
process: 'readonly',
},
},
plugins: {
prettier: prettierPlugin,
},
rules: {
...prettierConfig.rules,
'prettier/prettier': 'error',
},
},
{
ignores: ['node_modules/**', 'dist/**'],
},
];
+25
View File
@@ -0,0 +1,25 @@
/**
* Flat ESLint config for @nymproject/types.
*
* Imports the shared React/TS preset and overrides parserOptions.project to
* point at this package's tsconfig (the shared preset assumes ./tsconfig.json
* which is what we already use here).
*/
const sharedConfig = require('@nymproject/eslint-config-react-typescript');
const path = require('path');
module.exports = [
...sharedConfig,
{
languageOptions: {
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
},
},
{
ignores: ['dist/**', 'node_modules/**', '*.config.js'],
},
];
+37
View File
@@ -0,0 +1,37 @@
/**
* Lints the webpack helper scripts in this package (index.js, webpack.*.js).
*
* Intentionally minimal - prettier + eslint:recommended only; matches the
* pre-flat-config behavior from the previous .eslintrc.json.
*/
const js = require('@eslint/js');
const prettierPlugin = require('eslint-plugin-prettier');
const prettierConfig = require('eslint-config-prettier');
module.exports = [
js.configs.recommended,
{
files: ['*.js'],
languageOptions: {
ecmaVersion: 2022,
sourceType: 'commonjs',
globals: {
module: 'readonly',
require: 'readonly',
__dirname: 'readonly',
process: 'readonly',
},
},
plugins: {
prettier: prettierPlugin,
},
rules: {
...prettierConfig.rules,
'prettier/prettier': 'error',
},
},
{
ignores: ['node_modules/**', 'dist/**'],
},
];