Skip to content

Commit e1a5fda

Browse files
committed
feat: add support for Flat Config
This change adds support for ESLint's new Flat config system. It maintains backwards compatibility with eslintrc style configs as well. To achieve this, we're now dynamically creating flat configs on a new `flatConfigs` export. Usage ```js import importPlugin from 'eslint-plugin-import'; import js from '@eslint/js'; import tsParser from '@typescript-eslint/parser'; export default [ js.configs.recommended, importPlugin.flatConfigs.recommended, importPlugin.flatConfigs.react, importPlugin.flatConfigs.typescript, { files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'], languageOptions: { parser: tsParser, ecmaVersion: 'latest', sourceType: 'module', }, ignores: ['eslint.config.js'], rules: { 'no-unused-vars': 'off', 'import/no-dynamic-require': 'warn', 'import/no-nodejs-modules': 'warn', }, }, ]; ```
1 parent 6554bd5 commit e1a5fda

22 files changed

+302
-1
lines changed

.editorconfig

+1
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ insert_final_newline = true
77
indent_style = space
88
indent_size = 2
99
end_of_line = lf
10+
quote_type = single

.eslintignore

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ tests/files/with-syntax-error
77
tests/files/just-json-files/invalid.json
88
tests/files/typescript-d-ts/
99
resolvers/webpack/test/files
10+
examples
1011
# we want to ignore "tests/files" here, but unfortunately doing so would
1112
# interfere with unit test and fail it for some reason.
1213
# tests/files

README.md

+30-1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ The maintainers of `eslint-plugin-import` and thousands of other packages are wo
106106
npm install eslint-plugin-import --save-dev
107107
```
108108

109+
### Config - Legacy (`.eslintrc`)
110+
109111
All rules are off by default. However, you may configure them manually
110112
in your `.eslintrc.(yml|json|js)`, or extend one of the canned configs:
111113

@@ -123,14 +125,41 @@ plugins:
123125
- import
124126

125127
rules:
126-
import/no-unresolved: [2, {commonjs: true, amd: true}]
128+
import/no-unresolved: [2, { commonjs: true, amd: true }]
127129
import/named: 2
128130
import/namespace: 2
129131
import/default: 2
130132
import/export: 2
131133
# etc...
132134
```
133135

136+
### Config - Flat (`eslint.config.js`)
137+
138+
All rules are off by default. However, you may configure them manually
139+
in your `eslint.config.(js|cjs|mjs)`, or extend one of the canned configs:
140+
141+
```js
142+
import importPlugin from 'eslint-plugin-import';
143+
import js from '@eslint/js';
144+
145+
export default [
146+
js.configs.recommended,
147+
importPlugin.flatConfigs.recommended,
148+
{
149+
files: ['**/*.{js,mjs,cjs}'],
150+
languageOptions: {
151+
ecmaVersion: 'latest',
152+
sourceType: 'module',
153+
},
154+
rules: {
155+
'no-unused-vars': 'off',
156+
'import/no-dynamic-require': 'warn',
157+
'import/no-nodejs-modules': 'warn',
158+
},
159+
},
160+
];
161+
```
162+
134163
## TypeScript
135164

136165
You may use the following snippet or assemble your own config using the granular settings described below it.

config/flat/errors.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* unopinionated config. just the things that are necessarily runtime errors
3+
* waiting to happen.
4+
* @type {Object}
5+
*/
6+
module.exports = {
7+
rules: {
8+
'import/no-unresolved': 2,
9+
'import/named': 2,
10+
'import/namespace': 2,
11+
'import/default': 2,
12+
'import/export': 2,
13+
},
14+
};

config/flat/react.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Adds `.jsx` as an extension, and enables JSX parsing.
3+
*
4+
* Even if _you_ aren't using JSX (or .jsx) directly, if your dependencies
5+
* define jsnext:main and have JSX internally, you may run into problems
6+
* if you don't enable these settings at the top level.
7+
*/
8+
module.exports = {
9+
settings: {
10+
'import/extensions': ['.js', '.jsx'],
11+
},
12+
languageOptions: {
13+
parserOptions: {
14+
ecmaFeatures: {
15+
jsx: true,
16+
},
17+
},
18+
},
19+
};

config/flat/recommended.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* The basics.
3+
* @type {Object}
4+
*/
5+
module.exports = {
6+
rules: {
7+
// analysis/correctness
8+
'import/no-unresolved': 'error',
9+
'import/named': 'error',
10+
'import/namespace': 'error',
11+
'import/default': 'error',
12+
'import/export': 'error',
13+
14+
// red flags (thus, warnings)
15+
'import/no-named-as-default': 'warn',
16+
'import/no-named-as-default-member': 'warn',
17+
'import/no-duplicates': 'warn',
18+
},
19+
20+
// need all these for parsing dependencies (even if _your_ code doesn't need
21+
// all of them)
22+
languageOptions: {
23+
ecmaVersion: 2018,
24+
sourceType: 'module',
25+
},
26+
};

config/flat/stage-0.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Rules in progress.
3+
*
4+
* Do not expect these to adhere to semver across releases.
5+
* @type {Object}
6+
*/
7+
module.exports = {
8+
rules: {
9+
'import/no-deprecated': 1,
10+
},
11+
};

config/flat/warnings.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* more opinionated config.
3+
* @type {Object}
4+
*/
5+
module.exports = {
6+
rules: {
7+
'import/no-named-as-default': 1,
8+
'import/no-named-as-default-member': 1,
9+
'import/no-duplicates': 1,
10+
},
11+
};

examples/flat/eslint.config.mjs

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import importPlugin from 'eslint-plugin-import';
2+
import js from '@eslint/js';
3+
import tsParser from '@typescript-eslint/parser';
4+
5+
export default [
6+
js.configs.recommended,
7+
importPlugin.flatConfigs.recommended,
8+
importPlugin.flatConfigs.react,
9+
importPlugin.flatConfigs.typescript,
10+
{
11+
files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
12+
languageOptions: {
13+
parser: tsParser,
14+
ecmaVersion: 'latest',
15+
sourceType: 'module',
16+
},
17+
ignores: ['eslint.config.js'],
18+
rules: {
19+
'no-unused-vars': 'off',
20+
'import/no-dynamic-require': 'warn',
21+
'import/no-nodejs-modules': 'warn',
22+
},
23+
},
24+
];

examples/flat/package.json

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "flat",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"scripts": {
6+
"lint": "cross-env ESLINT_USE_FLAT_CONFIG=true eslint src --report-unused-disable-directives"
7+
},
8+
"devDependencies": {
9+
"@eslint/js": "^9.5.0",
10+
"@types/node": "^20.14.5",
11+
"@typescript-eslint/parser": "^7.13.1",
12+
"cross-env": "^7.0.3",
13+
"eslint": "^8.57.0",
14+
"eslint-plugin-import": "file:../..",
15+
"typescript": "^5.4.5"
16+
}
17+
}

examples/flat/src/exports.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export type ScalarType = string | number;
2+
export type ObjType = {
3+
a: ScalarType;
4+
b: ScalarType;
5+
};
6+
7+
export const a = 13;
8+
export const b = 18;
9+
10+
const defaultExport: ObjType = { a, b };
11+
12+
export default defaultExport;

examples/flat/src/imports.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import c from './exports';
2+
import { a, b } from './exports';
3+
import type { ScalarType, ObjType } from './exports';
4+
5+
import path from 'path';
6+
import fs from 'node:fs';
7+
import console from 'console';

examples/flat/src/jsx.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const Components = () => {
2+
return <></>;
3+
};

examples/flat/tsconfig.json

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"compilerOptions": {
3+
"jsx": "react-jsx",
4+
"lib": ["ESNext"],
5+
"target": "ESNext",
6+
"module": "ESNext",
7+
"rootDir": "./",
8+
"moduleResolution": "Bundler",
9+
"esModuleInterop": true,
10+
"forceConsistentCasingInFileNames": true,
11+
"strict": true,
12+
"skipLibCheck": true
13+
}
14+
}

examples/legacy/.eslintrc.cjs

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
module.exports = {
2+
root: true,
3+
env: { es2022: true },
4+
extends: [
5+
'eslint:recommended',
6+
'plugin:import/recommended',
7+
'plugin:import/react',
8+
'plugin:import/typescript',
9+
],
10+
settings: {},
11+
ignorePatterns: ['.eslintrc.cjs'],
12+
parser: '@typescript-eslint/parser',
13+
parserOptions: {
14+
ecmaVersion: 'latest',
15+
sourceType: 'module',
16+
},
17+
plugins: ['import'],
18+
rules: {
19+
'no-unused-vars': 'off',
20+
'import/no-dynamic-require': 'warn',
21+
'import/no-nodejs-modules': 'warn',
22+
},
23+
};

examples/legacy/package.json

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "legacy",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"scripts": {
6+
"lint": "cross-env ESLINT_USE_FLAT_CONFIG=false eslint src --ext js,jsx,ts,tsx --report-unused-disable-directives"
7+
},
8+
"devDependencies": {
9+
"@types/node": "^20.14.5",
10+
"@typescript-eslint/parser": "^7.13.1",
11+
"cross-env": "^7.0.3",
12+
"eslint": "^8.57.0",
13+
"eslint-plugin-import": "file:../..",
14+
"typescript": "^5.4.5"
15+
}
16+
}

examples/legacy/src/exports.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export type ScalarType = string | number;
2+
export type ObjType = {
3+
a: ScalarType;
4+
b: ScalarType;
5+
};
6+
7+
export const a = 13;
8+
export const b = 18;
9+
10+
const defaultExport: ObjType = { a, b };
11+
12+
export default defaultExport;

examples/legacy/src/imports.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import c from './exports';
2+
import { a, b } from './exports';
3+
import type { ScalarType, ObjType } from './exports';
4+
5+
import path from 'path';
6+
import fs from 'node:fs';
7+
import console from 'console';

examples/legacy/src/jsx.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const Components = () => {
2+
return <></>;
3+
};

examples/legacy/tsconfig.json

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"compilerOptions": {
3+
"jsx": "react-jsx",
4+
"lib": ["ESNext"],
5+
"target": "ESNext",
6+
"module": "ESNext",
7+
"rootDir": "./",
8+
"moduleResolution": "Bundler",
9+
"esModuleInterop": true,
10+
"forceConsistentCasingInFileNames": true,
11+
"strict": true,
12+
"skipLibCheck": true
13+
}
14+
}

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
"test": "npm run tests-only",
3131
"test-compiled": "npm run prepublish && BABEL_ENV=testCompiled mocha --compilers js:babel-register tests/src",
3232
"test-all": "node --require babel-register ./scripts/testAll",
33+
"test-examples": "npm run build && npm run test-example:legacy && npm run test-example:flat",
34+
"test-example:legacy": "cd examples/legacy && npm install && npm run lint",
35+
"test-example:flat": "cd examples/flat && npm install && npm run lint",
3336
"prepublishOnly": "safe-publish-latest && npm run build",
3437
"prepublish": "not-in-publish || npm run prepublishOnly",
3538
"preupdate:eslint-docs": "npm run build",

src/index.js

+34
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { name, version } from '../package.json';
2+
13
export const rules = {
24
'no-unresolved': require('./rules/no-unresolved'),
35
named: require('./rules/named'),
@@ -69,3 +71,35 @@ export const configs = {
6971
electron: require('../config/electron'),
7072
typescript: require('../config/typescript'),
7173
};
74+
75+
// Base Plugin Object
76+
const importPlugin = {
77+
meta: { name, version },
78+
rules,
79+
};
80+
81+
// Create flat configs (Only ones that declare plugins and parser options need to be different from the legacy config)
82+
const createFlatConfig = (baseConfig, configName) => ({
83+
...baseConfig,
84+
name: `import/${configName}`,
85+
plugins: { import: importPlugin },
86+
});
87+
88+
export const flatConfigs = {
89+
recommended: createFlatConfig(
90+
require('../config/flat/recommended'),
91+
'recommended',
92+
),
93+
94+
errors: createFlatConfig(require('../config/flat/errors'), 'errors'),
95+
warnings: createFlatConfig(require('../config/flat/warnings'), 'warnings'),
96+
97+
// shhhh... work in progress "secret" rules
98+
'stage-0': createFlatConfig(require('../config/flat/stage-0'), 'stage-0'),
99+
100+
// useful stuff for folks using various environments
101+
react: require('../config/flat/react'),
102+
'react-native': configs['react-native'],
103+
electron: configs.electron,
104+
typescript: configs.typescript,
105+
};

0 commit comments

Comments
 (0)