Skip to content

Commit 3e2ae16

Browse files
committed
feat: add flat config support
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. I was a bit on the fence about using this convention, or the other convention that's become prevalent in the community: adding the flat configs directly to the `configs` object, but with a 'flat/' prefix. I like this better, since it's slightly more ergonomic when using it in practice. e.g. `...importX.flatConfigs.recommended` vs `...importX.configs['flat/recommended']`, but i'm open to changing that. Example 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', }, }, ]; ``` Closes #29
1 parent 4ac2b98 commit 3e2ae16

14 files changed

+247
-23
lines changed

.changeset/great-dodos-dream.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-import-x": minor
3+
---
4+
5+
add support for flat configs

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"codesandbox:install": "yarn --ignore-engines",
3636
"lint": "run-p lint:*",
3737
"lint:docs": "yarn update:eslint-docs --check",
38-
"lint:es": "ESLINT_USE_FLAT_CONFIG=false eslint . --cache",
38+
"lint:es": "cross-env ESLINT_USE_FLAT_CONFIG=false eslint . --cache",
3939
"lint:tsc": "tsc -p tsconfig.base.json --noEmit",
4040
"prepare": "patch-package",
4141
"release": "changeset publish",

src/config/flat/electron.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type { PluginFlatConfig } from '../../types'
2+
3+
/**
4+
* Default settings for Electron applications.
5+
*/
6+
export default {
7+
settings: {
8+
'import-x/core-modules': ['electron'],
9+
},
10+
} satisfies PluginFlatConfig

src/config/flat/errors.ts

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

src/config/flat/react-native.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { PluginFlatBaseConfig } from '../../types'
2+
3+
/**
4+
* adds platform extensions to Node resolver
5+
*/
6+
export default {
7+
settings: {
8+
'import-x/resolver': {
9+
node: {
10+
// Note: will not complain if only _one_ of these files exists.
11+
extensions: ['.js', '.web.js', '.ios.js', '.android.js'],
12+
},
13+
},
14+
},
15+
} satisfies PluginFlatBaseConfig

src/config/flat/react.ts

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

src/config/flat/recommended.ts

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

src/config/flat/stage-0.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type { PluginFlatBaseConfig } from '../../types'
2+
3+
/**
4+
* Rules in progress.
5+
*
6+
* Do not expect these to adhere to semver across releases.
7+
*/
8+
export default {
9+
rules: {
10+
'import-x/no-deprecated': 1,
11+
},
12+
} satisfies PluginFlatBaseConfig

src/config/flat/typescript.ts

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import type { PluginFlatBaseConfig } from '../../types'
2+
3+
/**
4+
* This config:
5+
* 1) adds `.jsx`, `.ts`, `.cts`, `.mts`, and `.tsx` as an extension
6+
* 2) enables JSX/TSX parsing
7+
*/
8+
9+
// Omit `.d.ts` because 1) TypeScript compilation already confirms that
10+
// types are resolved, and 2) it would mask an unresolved
11+
// `.ts`/`.tsx`/`.js`/`.jsx` implementation.
12+
const typeScriptExtensions = ['.ts', '.tsx', '.cts', '.mts'] as const
13+
14+
const allExtensions = [
15+
...typeScriptExtensions,
16+
'.js',
17+
'.jsx',
18+
'.cjs',
19+
'.mjs',
20+
] as const
21+
22+
export default {
23+
settings: {
24+
'import-x/extensions': allExtensions,
25+
'import-x/external-module-folders': ['node_modules', 'node_modules/@types'],
26+
'import-x/parsers': {
27+
'@typescript-eslint/parser': [...typeScriptExtensions],
28+
},
29+
'import-x/resolver': {
30+
node: {
31+
extensions: allExtensions,
32+
},
33+
},
34+
},
35+
rules: {
36+
// analysis/correctness
37+
38+
// TypeScript compilation already ensures that named imports exist in the referenced module
39+
'import-x/named': 'off',
40+
},
41+
} satisfies PluginFlatBaseConfig

src/config/flat/warnings.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type { PluginFlatBaseConfig } from '../../types'
2+
3+
/**
4+
* more opinionated config.
5+
*/
6+
export default {
7+
rules: {
8+
'import-x/no-named-as-default': 1,
9+
'import-x/no-named-as-default-member': 1,
10+
'import-x/no-duplicates': 1,
11+
},
12+
} satisfies PluginFlatBaseConfig

src/config/typescript.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,22 @@ import type { PluginConfig } from '../types'
99
// Omit `.d.ts` because 1) TypeScript compilation already confirms that
1010
// types are resolved, and 2) it would mask an unresolved
1111
// `.ts`/`.tsx`/`.js`/`.jsx` implementation.
12-
const typeScriptExtensions = ['.ts', '.tsx'] as const
12+
const typeScriptExtensions = ['.ts', '.tsx', '.cts', '.mts'] as const
1313

14-
const allExtensions = [...typeScriptExtensions, '.js', '.jsx'] as const
14+
const allExtensions = [
15+
...typeScriptExtensions,
16+
'.js',
17+
'.jsx',
18+
'.cjs',
19+
'.mjs',
20+
] as const
1521

1622
export = {
1723
settings: {
1824
'import-x/extensions': allExtensions,
1925
'import-x/external-module-folders': ['node_modules', 'node_modules/@types'],
2026
'import-x/parsers': {
21-
'@typescript-eslint/parser': [...typeScriptExtensions, '.cts', '.mts'],
27+
'@typescript-eslint/parser': [...typeScriptExtensions],
2228
},
2329
'import-x/resolver': {
2430
node: {

src/index.ts

+69-18
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,28 @@
11
import type { TSESLint } from '@typescript-eslint/utils'
22

3-
// rules
3+
import { name, version } from '../package.json'
4+
5+
// legacy configs
46
import electron from './config/electron'
57
import errors from './config/errors'
8+
9+
// flat configs
10+
import electronFlat from './config/flat/electron'
11+
import errorsFlat from './config/flat/errors'
12+
import reactFlat from './config/flat/react'
13+
import reactNativeFlat from './config/flat/react-native'
14+
import recommendedFlat from './config/flat/recommended'
15+
import stage0Flat from './config/flat/stage-0'
16+
import typescriptFlat from './config/flat/typescript'
17+
import warningsFlat from './config/flat/warnings'
618
import react from './config/react'
719
import reactNative from './config/react-native'
820
import recommended from './config/recommended'
921
import stage0 from './config/stage-0'
1022
import typescript from './config/typescript'
1123
import warnings from './config/warnings'
24+
25+
// rules
1226
import consistentTypeSpecifierStyle from './rules/consistent-type-specifier-style'
1327
import default_ from './rules/default'
1428
import dynamicImportChunkname from './rules/dynamic-import-chunkname'
@@ -55,23 +69,11 @@ import order from './rules/order'
5569
import preferDefaultExport from './rules/prefer-default-export'
5670
import unambiguous from './rules/unambiguous'
5771
// configs
58-
import type { PluginConfig } from './types'
59-
60-
const configs = {
61-
recommended,
62-
63-
errors,
64-
warnings,
65-
66-
// shhhh... work in progress "secret" rules
67-
'stage-0': stage0,
68-
69-
// useful stuff for folks using various environments
70-
react,
71-
'react-native': reactNative,
72-
electron,
73-
typescript,
74-
} satisfies Record<string, PluginConfig>
72+
import type {
73+
PluginConfig,
74+
PluginFlatBaseConfig,
75+
PluginFlatConfig,
76+
} from './types'
7577

7678
const rules = {
7779
'no-unresolved': noUnresolved,
@@ -129,7 +131,56 @@ const rules = {
129131
'imports-first': importsFirst,
130132
} satisfies Record<string, TSESLint.RuleModule<string, readonly unknown[]>>
131133

134+
const configs = {
135+
recommended,
136+
137+
errors,
138+
warnings,
139+
140+
// shhhh... work in progress "secret" rules
141+
'stage-0': stage0,
142+
143+
// useful stuff for folks using various environments
144+
react,
145+
'react-native': reactNative,
146+
electron,
147+
typescript,
148+
} satisfies Record<string, PluginConfig>
149+
150+
// Base Plugin Object
151+
const plugin = {
152+
meta: { name, version },
153+
rules,
154+
}
155+
156+
// Create flat configs (Only ones that declare plugins and parser options need to be different from the legacy config)
157+
const createFlatConfig = (
158+
baseConfig: PluginFlatBaseConfig,
159+
configName: string,
160+
): PluginFlatConfig => ({
161+
...baseConfig,
162+
name: `import-x/${configName}`,
163+
plugins: { 'import-x': plugin },
164+
})
165+
166+
const flatConfigs = {
167+
recommended: createFlatConfig(recommendedFlat, 'recommended'),
168+
169+
errors: createFlatConfig(errorsFlat, 'errors'),
170+
warnings: createFlatConfig(warningsFlat, 'warnings'),
171+
172+
// shhhh... work in progress "secret" rules
173+
'stage-0': createFlatConfig(stage0Flat, 'stage-0'),
174+
175+
// useful stuff for folks using various environments
176+
react: reactFlat,
177+
'react-native': reactNativeFlat,
178+
electron: electronFlat,
179+
typescript: typescriptFlat,
180+
} satisfies Record<string, PluginFlatConfig>
181+
132182
export = {
133183
configs,
184+
flatConfigs,
134185
rules,
135186
}

src/types.ts

+9
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ export type PluginConfig = {
7070
rules?: Record<`${PluginName}/${string}`, TSESLint.Linter.RuleEntry>
7171
} & TSESLint.Linter.ConfigType
7272

73+
export type PluginFlatBaseConfig = {
74+
settings?: PluginSettings
75+
rules?: Record<`${PluginName}/${string}`, TSESLint.FlatConfig.RuleEntry>
76+
} & TSESLint.FlatConfig.Config
77+
78+
export type PluginFlatConfig = PluginFlatBaseConfig & {
79+
name?: `${PluginName}/${string}`
80+
}
81+
7382
export type RuleContext<
7483
TMessageIds extends string = string,
7584
TOptions extends readonly unknown[] = readonly unknown[],

src/utils/ignore.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ function validExtensions(context: ChildContext | RuleContext) {
2828
export function getFileExtensions(settings: PluginSettings) {
2929
// start with explicit JS-parsed extensions
3030
const exts = new Set<FileExtension>(
31-
settings['import-x/extensions'] || ['.js'],
31+
settings['import-x/extensions'] || ['.js', '.mjs', '.cjs'],
3232
)
3333

3434
// all alternate parser extensions are also valid

0 commit comments

Comments
 (0)