Skip to content

Commit

Permalink
feat: add support for Flat Config
Browse files Browse the repository at this point in the history
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',
    },
  },
];
```
  • Loading branch information
michaelfaith committed Jun 19, 2024
1 parent 6554bd5 commit 7aadb19
Show file tree
Hide file tree
Showing 21 changed files with 301 additions and 1 deletion.
1 change: 1 addition & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ insert_final_newline = true
indent_style = space
indent_size = 2
end_of_line = lf
quote_type = single
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ The maintainers of `eslint-plugin-import` and thousands of other packages are wo
npm install eslint-plugin-import --save-dev
```

### Config - Legacy (`.eslintrc`)

All rules are off by default. However, you may configure them manually
in your `.eslintrc.(yml|json|js)`, or extend one of the canned configs:

Expand All @@ -123,14 +125,41 @@ plugins:
- import

rules:
import/no-unresolved: [2, {commonjs: true, amd: true}]
import/no-unresolved: [2, { commonjs: true, amd: true }]
import/named: 2
import/namespace: 2
import/default: 2
import/export: 2
# etc...
```

### Config - Flat (`eslint.config.js`)

All rules are off by default. However, you may configure them manually
in your `eslint.config.(js|cjs|mjs)`, or extend one of the canned configs:

```js
import importPlugin from 'eslint-plugin-import';
import js from '@eslint/js';

export default [
js.configs.recommended,
importPlugin.flatConfigs.recommended,
{
files: ['**/*.{js,mjs,cjs}'],
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
rules: {
'no-unused-vars': 'off',
'import/no-dynamic-require': 'warn',
'import/no-nodejs-modules': 'warn',
},
},
];
```

## TypeScript

You may use the following snippet or assemble your own config using the granular settings described below it.
Expand Down
14 changes: 14 additions & 0 deletions config/flat/errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* unopinionated config. just the things that are necessarily runtime errors
* waiting to happen.
* @type {Object}
*/
module.exports = {
rules: {
'import/no-unresolved': 2,
'import/named': 2,
'import/namespace': 2,
'import/default': 2,
'import/export': 2,
},
};
19 changes: 19 additions & 0 deletions config/flat/react.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Adds `.jsx` as an extension, and enables JSX parsing.
*
* Even if _you_ aren't using JSX (or .jsx) directly, if your dependencies
* define jsnext:main and have JSX internally, you may run into problems
* if you don't enable these settings at the top level.
*/
module.exports = {
settings: {
'import/extensions': ['.js', '.jsx'],
},
languageOptions: {
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
};
26 changes: 26 additions & 0 deletions config/flat/recommended.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* The basics.
* @type {Object}
*/
module.exports = {
rules: {
// analysis/correctness
'import/no-unresolved': 'error',
'import/named': 'error',
'import/namespace': 'error',
'import/default': 'error',
'import/export': 'error',

// red flags (thus, warnings)
'import/no-named-as-default': 'warn',
'import/no-named-as-default-member': 'warn',
'import/no-duplicates': 'warn',
},

// need all these for parsing dependencies (even if _your_ code doesn't need
// all of them)
languageOptions: {
ecmaVersion: 2018,
sourceType: 'module',
},
};
11 changes: 11 additions & 0 deletions config/flat/stage-0.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Rules in progress.
*
* Do not expect these to adhere to semver across releases.
* @type {Object}
*/
module.exports = {
rules: {
'import/no-deprecated': 1,
},
};
11 changes: 11 additions & 0 deletions config/flat/warnings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* more opinionated config.
* @type {Object}
*/
module.exports = {
rules: {
'import/no-named-as-default': 1,
'import/no-named-as-default-member': 1,
'import/no-duplicates': 1,
},
};
24 changes: 24 additions & 0 deletions examples/flat/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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',
},
},
];
17 changes: 17 additions & 0 deletions examples/flat/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "flat",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"lint": "cross-env ESLINT_USE_FLAT_CONFIG=true eslint src --report-unused-disable-directives"
},
"devDependencies": {
"@eslint/js": "^9.5.0",
"@types/node": "^20.14.5",
"@typescript-eslint/parser": "^7.13.1",
"cross-env": "^7.0.3",
"eslint": "^8.57.0",
"eslint-plugin-import": "file:../..",
"typescript": "^5.4.5"
}
}
12 changes: 12 additions & 0 deletions examples/flat/src/exports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export type ScalarType = string | number;
export type ObjType = {
a: ScalarType;
b: ScalarType;
};

export const a = 13;
export const b = 18;

const defaultExport: ObjType = { a, b };

export default defaultExport;
7 changes: 7 additions & 0 deletions examples/flat/src/imports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import c from './exports';
import { a, b } from './exports';
import type { ScalarType, ObjType } from './exports';

import path from 'path';
import fs from 'node:fs';
import console from 'console';
3 changes: 3 additions & 0 deletions examples/flat/src/jsx.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const Components = () => {
return <></>;
};
14 changes: 14 additions & 0 deletions examples/flat/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"rootDir": "./",
"moduleResolution": "Bundler",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
23 changes: 23 additions & 0 deletions examples/legacy/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module.exports = {
root: true,
env: { es2022: true },
extends: [
'eslint:recommended',
'plugin:import/recommended',
'plugin:import/react',
'plugin:import/typescript',
],
settings: {},
ignorePatterns: ['.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: ['import'],
rules: {
'no-unused-vars': 'off',
'import/no-dynamic-require': 'warn',
'import/no-nodejs-modules': 'warn',
},
};
16 changes: 16 additions & 0 deletions examples/legacy/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "legacy",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"lint": "cross-env ESLINT_USE_FLAT_CONFIG=false eslint src --ext js,jsx,ts,tsx --report-unused-disable-directives"
},
"devDependencies": {
"@types/node": "^20.14.5",
"@typescript-eslint/parser": "^7.13.1",
"cross-env": "^7.0.3",
"eslint": "^8.57.0",
"eslint-plugin-import": "file:../..",
"typescript": "^5.4.5"
}
}
12 changes: 12 additions & 0 deletions examples/legacy/src/exports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export type ScalarType = string | number;
export type ObjType = {
a: ScalarType;
b: ScalarType;
};

export const a = 13;
export const b = 18;

const defaultExport: ObjType = { a, b };

export default defaultExport;
7 changes: 7 additions & 0 deletions examples/legacy/src/imports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import c from './exports';
import { a, b } from './exports';
import type { ScalarType, ObjType } from './exports';

import path from 'path';
import fs from 'node:fs';
import console from 'console';
3 changes: 3 additions & 0 deletions examples/legacy/src/jsx.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const Components = () => {
return <></>;
};
14 changes: 14 additions & 0 deletions examples/legacy/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"rootDir": "./",
"moduleResolution": "Bundler",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
"test": "npm run tests-only",
"test-compiled": "npm run prepublish && BABEL_ENV=testCompiled mocha --compilers js:babel-register tests/src",
"test-all": "node --require babel-register ./scripts/testAll",
"test-examples": "npm run build && npm run test-example:legacy && npm run test-example:flat",
"test-example:legacy": "cd examples/legacy && npm install && npm run lint",
"test-example:flat": "cd examples/flat && npm install && npm run lint",
"prepublishOnly": "safe-publish-latest && npm run build",
"prepublish": "not-in-publish || npm run prepublishOnly",
"preupdate:eslint-docs": "npm run build",
Expand Down
34 changes: 34 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { name, version } from '../package.json';

export const rules = {
'no-unresolved': require('./rules/no-unresolved'),
named: require('./rules/named'),
Expand Down Expand Up @@ -69,3 +71,35 @@ export const configs = {
electron: require('../config/electron'),
typescript: require('../config/typescript'),
};

// Base Plugin Object
const importPlugin = {
meta: { name, version },
rules,
};

// Create flat configs (Only ones that declare plugins and parser options need to be different from the legacy config)
const createFlatConfig = (baseConfig, configName) => ({
...baseConfig,
name: `import/${configName}`,
plugins: { import: importPlugin },
});

export const flatConfigs = {
recommended: createFlatConfig(
require('../config/flat/recommended'),
'recommended',
),

errors: createFlatConfig(require('../config/flat/errors'), 'errors'),
warnings: createFlatConfig(require('../config/flat/warnings'), 'warnings'),

// shhhh... work in progress "secret" rules
'stage-0': createFlatConfig(require('../config/flat/stage-0'), 'stage-0'),

// useful stuff for folks using various environments
react: require('../config/flat/react'),
'react-native': configs['react-native'],
electron: configs.electron,
typescript: configs.typescript,
};

0 comments on commit 7aadb19

Please sign in to comment.