Skip to content

Commit cb5fcb5

Browse files
committed
feat(migration docs): update migration docs based on mc-fe migration
1 parent 259ca74 commit cb5fcb5

File tree

2 files changed

+248
-4
lines changed
  • packages-backend/eslint-config-node/migrations
  • packages/eslint-config-mc-app/migrations

2 files changed

+248
-4
lines changed

packages-backend/eslint-config-node/migrations/v27.md

Lines changed: 124 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,78 @@ The base `mcAppConfig` registers plugins for:
142142

143143
If your override mixes rules from different plugins, split into separate config objects matching each plugin's file types (e.g., `react/` rules in `**/*.{js,jsx,tsx}`, `@typescript-eslint/` rules in `**/*.{ts,tsx}`, core rules in `**/*.{js,jsx,ts,tsx}`).
144144

145+
#### Common mistake: catch-all file patterns with mixed plugin rules
146+
147+
The natural instinct when converting a subdirectory `.eslintrc.cjs` is to put all the rules into a single config object targeting `**/*.{js,jsx,ts,tsx}`. This will fail because not every plugin is registered for every file type.
148+
149+
```js
150+
// WRONG — will error on .ts files because `react` plugin is not registered for them
151+
{
152+
files: ['packages/my-app/src/**/*.{js,jsx,ts,tsx}'],
153+
rules: {
154+
'no-console': 'warn', // core — works on all
155+
'react/jsx-sort-props': 'error', // react — NOT on .ts
156+
'@typescript-eslint/consistent-type-imports': 'error', // ts — NOT on .js/.jsx
157+
},
158+
}
159+
```
160+
161+
Split into separate config objects, each matching the plugin's registered file types:
162+
163+
```js
164+
// Core ESLint rules — all source files
165+
{
166+
files: ['packages/my-app/src/**/*.{js,jsx,ts,tsx}'],
167+
rules: { 'no-console': 'warn' },
168+
},
169+
// React rules — only file types where the react plugin is registered
170+
{
171+
files: ['packages/my-app/src/**/*.{js,jsx,tsx}'],
172+
rules: { 'react/jsx-sort-props': 'error' },
173+
},
174+
// TypeScript rules — only .ts and .tsx
175+
{
176+
files: ['packages/my-app/src/**/*.{ts,tsx}'],
177+
rules: { '@typescript-eslint/consistent-type-imports': 'error' },
178+
},
179+
```
180+
181+
> **Tip**: When converting an existing `.eslintrc.cjs`, group its rules by which plugin they belong to, then create one config object per plugin group with the correct `files` pattern. Core ESLint rules (no plugin prefix) can target all file types.
182+
183+
#### Common mistake: `no-unused-vars` duplication on TypeScript files
184+
185+
The base `mcAppConfig` disables core `no-unused-vars` on `.ts`/`.tsx` files and replaces it with `@typescript-eslint/no-unused-vars`. If your subdirectory config re-enables the core `no-unused-vars` for all file types, both rules will fire on TypeScript files, producing duplicate errors.
186+
187+
```js
188+
// WRONG — re-enables core no-unused-vars on .ts/.tsx where @typescript-eslint
189+
// version is already active from the base config
190+
{
191+
files: ['packages/my-app/src/**/*.{js,jsx,ts,tsx}'],
192+
rules: { 'no-unused-vars': 'error' },
193+
}
194+
```
195+
196+
Scope core `no-unused-vars` overrides to JavaScript files only:
197+
198+
```js
199+
// CORRECT — only overrides the core rule on files where it applies
200+
{
201+
files: ['packages/my-app/src/**/*.{js,jsx}'],
202+
rules: { 'no-unused-vars': 'error' },
203+
}
204+
```
205+
206+
### Self-linting the config file
207+
208+
The root `eslint.config.js` is itself a `.js` file in your project, so ESLint will lint it. Rules like `import/extensions` may error on `.cjs` requires. Add a self-override to avoid this:
209+
210+
```js
211+
{
212+
files: ['eslint.config.js'],
213+
rules: { 'import/extensions': 'off' },
214+
},
215+
```
216+
145217
## Step 4: Update `package.json`
146218

147219
```diff
@@ -168,6 +240,27 @@ If you maintain custom ESLint rule plugins, update deprecated APIs:
168240

169241
Also update the plugin's `peerDependencies` to `"eslint": "9.x"`.
170242

243+
### `jest-runner-eslint` compatibility
244+
245+
If using `jest-runner-eslint`, note that the `rules` key in `cliOptions` is not supported in flat config mode — move those rule overrides into `eslint.config.js` instead. You may also need a `pnpm.overrides` (or npm `overrides`) entry if the package's peer dependency still specifies ESLint 8:
246+
247+
```json
248+
{
249+
"pnpm": {
250+
"overrides": {
251+
"jest-runner-eslint>eslint": "^9.0.0"
252+
}
253+
}
254+
}
255+
```
256+
257+
If the formatter path uses a bare directory import (e.g., `node_modules/eslint-formatter-pretty`), ESM module resolution will reject it. Change to an explicit file path:
258+
259+
```diff
260+
- format: 'node_modules/eslint-formatter-pretty',
261+
+ format: 'node_modules/eslint-formatter-pretty/index.js',
262+
```
263+
171264
## Step 5: Clean up
172265

173266
- Delete **all** `.eslintrc.*` files (root and subdirectories)
@@ -187,11 +280,40 @@ Run eslint on at least one file from **each directory that had its own config**.
187280

188281
## Troubleshooting
189282

190-
**"Definition for rule 'plugin/rule-name' was not found"**: Plugin not registered for that file type. Common case: stale `eslint-disable-next-line` comments for plugins not valid on that file type (e.g., `@typescript-eslint/...` in a `.js` file). **Fix**: remove the stale comment, or register the plugin for that file type.
283+
**"Definition for rule 'plugin/rule-name' was not found"**: This means a rule is referenced on a file type where its plugin isn't registered. Two common causes:
284+
285+
1. **Config rules targeting wrong file types** — your subdirectory config has a catch-all `files` pattern like `**/*.{js,jsx,ts,tsx}` but includes rules from a plugin that isn't registered for all of those types. See "Plugin scoping" above for how to split by plugin.
286+
2. **Stale inline `eslint-disable` comments** — ESLint 8 silently ignored `eslint-disable` comments referencing rules from unregistered plugins. ESLint 9 treats them as errors. This surfaces pre-existing stale comments that were previously harmless (e.g., `// eslint-disable-next-line testing-library/no-render-in-setup` in a file that isn't matched by the `testing-library` plugin's file pattern, or `@typescript-eslint/...` in a `.js` file). **Fix**: remove the stale disable comment, or if the rule should apply, register the plugin for that file type.
191287

192288
**"ReferenceError: module is not defined in ES module scope"**: Config file uses `module.exports` but nearest `package.json` has `"type": "module"`. **Fix**: rename to `.cjs` extension and update imports.
193289

194-
**Jest globals (`describe`, `it`, `expect`) not defined**: The base config only injects Jest globals for `**/*.{spec,test}.*`. For other naming conventions (e.g. `*.visualspec.js`), add `languageOptions: { globals: { ...require('globals').jest } }` for those file patterns.
290+
**Jest globals (`describe`, `it`, `expect`) not defined**: The base config only injects Jest globals for `**/*.{spec,test}.*`. Other files that use Jest APIs need explicit globals. Common patterns that are easy to miss:
291+
292+
- `__mocks__/` directories (at any depth)
293+
- `test-utils/` directories (helper modules used by tests)
294+
295+
Add a config block for these:
296+
297+
```js
298+
{
299+
files: [
300+
'**/__mocks__/**/*.{js,jsx,ts,tsx}',
301+
'**/test-utils/**/*.{js,jsx,ts,tsx}',
302+
],
303+
languageOptions: {
304+
globals: {
305+
jest: 'readonly',
306+
expect: 'readonly',
307+
},
308+
},
309+
},
310+
```
311+
312+
> **Note**: Use `**/__mocks__/**` (with leading `**/`), not `__mocks__/**`. The latter only matches a `__mocks__/` directory at the project root. Most projects have `__mocks__/` directories nested inside packages or `src/` directories.
313+
314+
**Duplicate `no-unused-vars` errors on `.ts`/`.tsx` files**: The base config sets `@typescript-eslint/no-unused-vars` for TypeScript files and disables the core `no-unused-vars` there. If your subdirectory config re-enables `no-unused-vars` for `**/*.{js,jsx,ts,tsx}`, both rules fire on TypeScript files. **Fix**: scope `no-unused-vars` overrides to `**/*.{js,jsx}` only. See "Common mistake" above.
315+
316+
**Lint errors in `eslint.config.js` itself**: The config file is a `.js` file in the project root, so ESLint lints it. Rules like `import/extensions` may error on `.cjs` requires. **Fix**: add a self-override: `{ files: ['eslint.config.js'], rules: { 'import/extensions': 'off' } }`.
195317

196318
**"Cannot find module 'some-eslint-plugin'"**: Plugins must be installed as direct dependencies in flat config.
197319

packages/eslint-config-mc-app/migrations/v27.md

Lines changed: 124 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,78 @@ The base `mcAppConfig` registers plugins for:
142142

143143
If your override mixes rules from different plugins, split into separate config objects matching each plugin's file types (e.g., `react/` rules in `**/*.{js,jsx,tsx}`, `@typescript-eslint/` rules in `**/*.{ts,tsx}`, core rules in `**/*.{js,jsx,ts,tsx}`).
144144

145+
#### Common mistake: catch-all file patterns with mixed plugin rules
146+
147+
The natural instinct when converting a subdirectory `.eslintrc.cjs` is to put all the rules into a single config object targeting `**/*.{js,jsx,ts,tsx}`. This will fail because not every plugin is registered for every file type.
148+
149+
```js
150+
// WRONG — will error on .ts files because `react` plugin is not registered for them
151+
{
152+
files: ['packages/my-app/src/**/*.{js,jsx,ts,tsx}'],
153+
rules: {
154+
'no-console': 'warn', // core — works on all
155+
'react/jsx-sort-props': 'error', // react — NOT on .ts
156+
'@typescript-eslint/consistent-type-imports': 'error', // ts — NOT on .js/.jsx
157+
},
158+
}
159+
```
160+
161+
Split into separate config objects, each matching the plugin's registered file types:
162+
163+
```js
164+
// Core ESLint rules — all source files
165+
{
166+
files: ['packages/my-app/src/**/*.{js,jsx,ts,tsx}'],
167+
rules: { 'no-console': 'warn' },
168+
},
169+
// React rules — only file types where the react plugin is registered
170+
{
171+
files: ['packages/my-app/src/**/*.{js,jsx,tsx}'],
172+
rules: { 'react/jsx-sort-props': 'error' },
173+
},
174+
// TypeScript rules — only .ts and .tsx
175+
{
176+
files: ['packages/my-app/src/**/*.{ts,tsx}'],
177+
rules: { '@typescript-eslint/consistent-type-imports': 'error' },
178+
},
179+
```
180+
181+
> **Tip**: When converting an existing `.eslintrc.cjs`, group its rules by which plugin they belong to, then create one config object per plugin group with the correct `files` pattern. Core ESLint rules (no plugin prefix) can target all file types.
182+
183+
#### Common mistake: `no-unused-vars` duplication on TypeScript files
184+
185+
The base `mcAppConfig` disables core `no-unused-vars` on `.ts`/`.tsx` files and replaces it with `@typescript-eslint/no-unused-vars`. If your subdirectory config re-enables the core `no-unused-vars` for all file types, both rules will fire on TypeScript files, producing duplicate errors.
186+
187+
```js
188+
// WRONG — re-enables core no-unused-vars on .ts/.tsx where @typescript-eslint
189+
// version is already active from the base config
190+
{
191+
files: ['packages/my-app/src/**/*.{js,jsx,ts,tsx}'],
192+
rules: { 'no-unused-vars': 'error' },
193+
}
194+
```
195+
196+
Scope core `no-unused-vars` overrides to JavaScript files only:
197+
198+
```js
199+
// CORRECT — only overrides the core rule on files where it applies
200+
{
201+
files: ['packages/my-app/src/**/*.{js,jsx}'],
202+
rules: { 'no-unused-vars': 'error' },
203+
}
204+
```
205+
206+
### Self-linting the config file
207+
208+
The root `eslint.config.js` is itself a `.js` file in your project, so ESLint will lint it. Rules like `import/extensions` may error on `.cjs` requires. Add a self-override to avoid this:
209+
210+
```js
211+
{
212+
files: ['eslint.config.js'],
213+
rules: { 'import/extensions': 'off' },
214+
},
215+
```
216+
145217
## Step 4: Update `package.json`
146218

147219
```diff
@@ -168,6 +240,27 @@ If you maintain custom ESLint rule plugins, update deprecated APIs:
168240

169241
Also update the plugin's `peerDependencies` to `"eslint": "9.x"`.
170242

243+
### `jest-runner-eslint` compatibility
244+
245+
If using `jest-runner-eslint`, note that the `rules` key in `cliOptions` is not supported in flat config mode — move those rule overrides into `eslint.config.js` instead. You may also need a `pnpm.overrides` (or npm `overrides`) entry if the package's peer dependency still specifies ESLint 8:
246+
247+
```json
248+
{
249+
"pnpm": {
250+
"overrides": {
251+
"jest-runner-eslint>eslint": "^9.0.0"
252+
}
253+
}
254+
}
255+
```
256+
257+
If the formatter path uses a bare directory import (e.g., `node_modules/eslint-formatter-pretty`), ESM module resolution will reject it. Change to an explicit file path:
258+
259+
```diff
260+
- format: 'node_modules/eslint-formatter-pretty',
261+
+ format: 'node_modules/eslint-formatter-pretty/index.js',
262+
```
263+
171264
## Step 5: Clean up
172265

173266
- Delete **all** `.eslintrc.*` files (root and subdirectories)
@@ -187,11 +280,40 @@ Run eslint on at least one file from **each directory that had its own config**.
187280

188281
## Troubleshooting
189282

190-
**"Definition for rule 'plugin/rule-name' was not found"**: Plugin not registered for that file type. Common case: stale `eslint-disable-next-line` comments for plugins not valid on that file type (e.g., `@typescript-eslint/...` in a `.js` file). **Fix**: remove the stale comment, or register the plugin for that file type.
283+
**"Definition for rule 'plugin/rule-name' was not found"**: This means a rule is referenced on a file type where its plugin isn't registered. Two common causes:
284+
285+
1. **Config rules targeting wrong file types** — your subdirectory config has a catch-all `files` pattern like `**/*.{js,jsx,ts,tsx}` but includes rules from a plugin that isn't registered for all of those types. See "Plugin scoping" above for how to split by plugin.
286+
2. **Stale inline `eslint-disable` comments** — ESLint 8 silently ignored `eslint-disable` comments referencing rules from unregistered plugins. ESLint 9 treats them as errors. This surfaces pre-existing stale comments that were previously harmless (e.g., `// eslint-disable-next-line testing-library/no-render-in-setup` in a file that isn't matched by the `testing-library` plugin's file pattern, or `@typescript-eslint/...` in a `.js` file). **Fix**: remove the stale disable comment, or if the rule should apply, register the plugin for that file type.
191287

192288
**"ReferenceError: module is not defined in ES module scope"**: Config file uses `module.exports` but nearest `package.json` has `"type": "module"`. **Fix**: rename to `.cjs` extension and update imports.
193289

194-
**Jest globals (`describe`, `it`, `expect`) not defined**: The base config only injects Jest globals for `**/*.{spec,test}.*`. For other naming conventions (e.g. `*.visualspec.js`), add `languageOptions: { globals: { ...require('globals').jest } }` for those file patterns.
290+
**Jest globals (`describe`, `it`, `expect`) not defined**: The base config only injects Jest globals for `**/*.{spec,test}.*`. Other files that use Jest APIs need explicit globals. Common patterns that are easy to miss:
291+
292+
- `__mocks__/` directories (at any depth)
293+
- `test-utils/` directories (helper modules used by tests)
294+
295+
Add a config block for these:
296+
297+
```js
298+
{
299+
files: [
300+
'**/__mocks__/**/*.{js,jsx,ts,tsx}',
301+
'**/test-utils/**/*.{js,jsx,ts,tsx}',
302+
],
303+
languageOptions: {
304+
globals: {
305+
jest: 'readonly',
306+
expect: 'readonly',
307+
},
308+
},
309+
},
310+
```
311+
312+
> **Note**: Use `**/__mocks__/**` (with leading `**/`), not `__mocks__/**`. The latter only matches a `__mocks__/` directory at the project root. Most projects have `__mocks__/` directories nested inside packages or `src/` directories.
313+
314+
**Duplicate `no-unused-vars` errors on `.ts`/`.tsx` files**: The base config sets `@typescript-eslint/no-unused-vars` for TypeScript files and disables the core `no-unused-vars` there. If your subdirectory config re-enables `no-unused-vars` for `**/*.{js,jsx,ts,tsx}`, both rules fire on TypeScript files. **Fix**: scope `no-unused-vars` overrides to `**/*.{js,jsx}` only. See "Common mistake" above.
315+
316+
**Lint errors in `eslint.config.js` itself**: The config file is a `.js` file in the project root, so ESLint lints it. Rules like `import/extensions` may error on `.cjs` requires. **Fix**: add a self-override: `{ files: ['eslint.config.js'], rules: { 'import/extensions': 'off' } }`.
195317

196318
**"Cannot find module 'some-eslint-plugin'"**: Plugins must be installed as direct dependencies in flat config.
197319

0 commit comments

Comments
 (0)