diff --git a/.changeset/great-dodos-dream.md b/.changeset/great-dodos-dream.md new file mode 100644 index 000000000..fcdaab357 --- /dev/null +++ b/.changeset/great-dodos-dream.md @@ -0,0 +1,14 @@ +--- +"eslint-plugin-import-x": minor +--- + +Add ESLint flat configuration presets. You can access them with: + +```ts +import eslintPluginImportX from 'eslint-plugin-import-x'; + +eslintPluginImportX.flatConfigs.recommended; +eslintPluginImportX.flatConfigs.react; +eslintPluginImportX.flatConfigs.typescript; +eslintPluginImportX.flatConfigs.electron; +``` diff --git a/package.json b/package.json index e0a0fef72..a23ed1175 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "codesandbox:install": "yarn --ignore-engines", "lint": "run-p lint:*", "lint:docs": "yarn update:eslint-docs --check", - "lint:es": "ESLINT_USE_FLAT_CONFIG=false eslint . --cache", + "lint:es": "cross-env ESLINT_USE_FLAT_CONFIG=false eslint . --cache", "lint:tsc": "tsc -p tsconfig.base.json --noEmit", "prepare": "patch-package", "release": "changeset publish", diff --git a/src/config/flat/electron.ts b/src/config/flat/electron.ts new file mode 100644 index 000000000..add2e58d9 --- /dev/null +++ b/src/config/flat/electron.ts @@ -0,0 +1,10 @@ +import type { PluginFlatConfig } from '../../types' + +/** + * Default settings for Electron applications. + */ +export default { + settings: { + 'import-x/core-modules': ['electron'], + }, +} satisfies PluginFlatConfig diff --git a/src/config/flat/errors.ts b/src/config/flat/errors.ts new file mode 100644 index 000000000..a27a60153 --- /dev/null +++ b/src/config/flat/errors.ts @@ -0,0 +1,15 @@ +import type { PluginFlatConfig } from '../../types' + +/** + * unopinionated config. just the things that are necessarily runtime errors + * waiting to happen. + */ +export default { + rules: { + 'import-x/no-unresolved': 2, + 'import-x/named': 2, + 'import-x/namespace': 2, + 'import-x/default': 2, + 'import-x/export': 2, + }, +} satisfies PluginFlatConfig diff --git a/src/config/flat/react-native.ts b/src/config/flat/react-native.ts new file mode 100644 index 000000000..5f924dec6 --- /dev/null +++ b/src/config/flat/react-native.ts @@ -0,0 +1,15 @@ +import type { PluginFlatBaseConfig } from '../../types' + +/** + * adds platform extensions to Node resolver + */ +export default { + settings: { + 'import-x/resolver': { + node: { + // Note: will not complain if only _one_ of these files exists. + extensions: ['.js', '.web.js', '.ios.js', '.android.js'], + }, + }, + }, +} satisfies PluginFlatBaseConfig diff --git a/src/config/flat/react.ts b/src/config/flat/react.ts new file mode 100644 index 000000000..dd688e3cc --- /dev/null +++ b/src/config/flat/react.ts @@ -0,0 +1,21 @@ +import type { PluginFlatBaseConfig } from '../../types' + +/** + * 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. + */ +export default { + settings: { + 'import-x/extensions': ['.js', '.jsx', '.mjs', '.cjs'], + }, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, +} satisfies PluginFlatBaseConfig diff --git a/src/config/flat/recommended.ts b/src/config/flat/recommended.ts new file mode 100644 index 000000000..69056e74a --- /dev/null +++ b/src/config/flat/recommended.ts @@ -0,0 +1,27 @@ +import type { PluginFlatBaseConfig } from '../../types' + +/** + * The basics. + */ +export default { + rules: { + // analysis/correctness + 'import-x/no-unresolved': 'error', + 'import-x/named': 'error', + 'import-x/namespace': 'error', + 'import-x/default': 'error', + 'import-x/export': 'error', + + // red flags (thus, warnings) + 'import-x/no-named-as-default': 'warn', + 'import-x/no-named-as-default-member': 'warn', + 'import-x/no-duplicates': 'warn', + }, + + // need all these for parsing dependencies (even if _your_ code doesn't need + // all of them) + languageOptions: { + ecmaVersion: 2018, + sourceType: 'module', + }, +} satisfies PluginFlatBaseConfig diff --git a/src/config/flat/stage-0.ts b/src/config/flat/stage-0.ts new file mode 100644 index 000000000..ca7b0be16 --- /dev/null +++ b/src/config/flat/stage-0.ts @@ -0,0 +1,12 @@ +import type { PluginFlatBaseConfig } from '../../types' + +/** + * Rules in progress. + * + * Do not expect these to adhere to semver across releases. + */ +export default { + rules: { + 'import-x/no-deprecated': 1, + }, +} satisfies PluginFlatBaseConfig diff --git a/src/config/flat/typescript.ts b/src/config/flat/typescript.ts new file mode 100644 index 000000000..a7484bfd9 --- /dev/null +++ b/src/config/flat/typescript.ts @@ -0,0 +1,41 @@ +import type { PluginFlatBaseConfig } from '../../types' + +/** + * This config: + * 1) adds `.jsx`, `.ts`, `.cts`, `.mts`, and `.tsx` as an extension + * 2) enables JSX/TSX parsing + */ + +// Omit `.d.ts` because 1) TypeScript compilation already confirms that +// types are resolved, and 2) it would mask an unresolved +// `.ts`/`.tsx`/`.js`/`.jsx` implementation. +const typeScriptExtensions = ['.ts', '.tsx', '.cts', '.mts'] as const + +const allExtensions = [ + ...typeScriptExtensions, + '.js', + '.jsx', + '.cjs', + '.mjs', +] as const + +export default { + settings: { + 'import-x/extensions': allExtensions, + 'import-x/external-module-folders': ['node_modules', 'node_modules/@types'], + 'import-x/parsers': { + '@typescript-eslint/parser': [...typeScriptExtensions], + }, + 'import-x/resolver': { + node: { + extensions: allExtensions, + }, + }, + }, + rules: { + // analysis/correctness + + // TypeScript compilation already ensures that named imports exist in the referenced module + 'import-x/named': 'off', + }, +} satisfies PluginFlatBaseConfig diff --git a/src/config/flat/warnings.ts b/src/config/flat/warnings.ts new file mode 100644 index 000000000..ca9dde2db --- /dev/null +++ b/src/config/flat/warnings.ts @@ -0,0 +1,12 @@ +import type { PluginFlatBaseConfig } from '../../types' + +/** + * more opinionated config. + */ +export default { + rules: { + 'import-x/no-named-as-default': 1, + 'import-x/no-named-as-default-member': 1, + 'import-x/no-duplicates': 1, + }, +} satisfies PluginFlatBaseConfig diff --git a/src/config/typescript.ts b/src/config/typescript.ts index 21269b2b4..444fee65c 100644 --- a/src/config/typescript.ts +++ b/src/config/typescript.ts @@ -9,16 +9,22 @@ import type { PluginConfig } from '../types' // Omit `.d.ts` because 1) TypeScript compilation already confirms that // types are resolved, and 2) it would mask an unresolved // `.ts`/`.tsx`/`.js`/`.jsx` implementation. -const typeScriptExtensions = ['.ts', '.tsx'] as const +const typeScriptExtensions = ['.ts', '.tsx', '.cts', '.mts'] as const -const allExtensions = [...typeScriptExtensions, '.js', '.jsx'] as const +const allExtensions = [ + ...typeScriptExtensions, + '.js', + '.jsx', + '.cjs', + '.mjs', +] as const export = { settings: { 'import-x/extensions': allExtensions, 'import-x/external-module-folders': ['node_modules', 'node_modules/@types'], 'import-x/parsers': { - '@typescript-eslint/parser': [...typeScriptExtensions, '.cts', '.mts'], + '@typescript-eslint/parser': [...typeScriptExtensions], }, 'import-x/resolver': { node: { diff --git a/src/index.ts b/src/index.ts index 69cd2bfd4..2d28d25cf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,14 +1,25 @@ import type { TSESLint } from '@typescript-eslint/utils' -// rules +import { name, version } from '../package.json' + import electron from './config/electron' import errors from './config/errors' +import electronFlat from './config/flat/electron' +import errorsFlat from './config/flat/errors' +import reactFlat from './config/flat/react' +import reactNativeFlat from './config/flat/react-native' +import recommendedFlat from './config/flat/recommended' +import stage0Flat from './config/flat/stage-0' +import typescriptFlat from './config/flat/typescript' +import warningsFlat from './config/flat/warnings' import react from './config/react' import reactNative from './config/react-native' import recommended from './config/recommended' import stage0 from './config/stage-0' import typescript from './config/typescript' import warnings from './config/warnings' + +// rules import consistentTypeSpecifierStyle from './rules/consistent-type-specifier-style' import default_ from './rules/default' import dynamicImportChunkname from './rules/dynamic-import-chunkname' @@ -55,23 +66,11 @@ import order from './rules/order' import preferDefaultExport from './rules/prefer-default-export' import unambiguous from './rules/unambiguous' // configs -import type { PluginConfig } from './types' - -const configs = { - recommended, - - errors, - warnings, - - // shhhh... work in progress "secret" rules - 'stage-0': stage0, - - // useful stuff for folks using various environments - react, - 'react-native': reactNative, - electron, - typescript, -} satisfies Record +import type { + PluginConfig, + PluginFlatBaseConfig, + PluginFlatConfig, +} from './types' const rules = { 'no-unresolved': noUnresolved, @@ -129,7 +128,56 @@ const rules = { 'imports-first': importsFirst, } satisfies Record> +const configs = { + recommended, + + errors, + warnings, + + // shhhh... work in progress "secret" rules + 'stage-0': stage0, + + // useful stuff for folks using various environments + react, + 'react-native': reactNative, + electron, + typescript, +} satisfies Record + +// Base Plugin Object +const plugin = { + 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: PluginFlatBaseConfig, + configName: string, +): PluginFlatConfig => ({ + ...baseConfig, + name: `import-x/${configName}`, + plugins: { 'import-x': plugin }, +}) + +const flatConfigs = { + recommended: createFlatConfig(recommendedFlat, 'recommended'), + + errors: createFlatConfig(errorsFlat, 'errors'), + warnings: createFlatConfig(warningsFlat, 'warnings'), + + // shhhh... work in progress "secret" rules + 'stage-0': createFlatConfig(stage0Flat, 'stage-0'), + + // useful stuff for folks using various environments + react: reactFlat, + 'react-native': reactNativeFlat, + electron: electronFlat, + typescript: typescriptFlat, +} satisfies Record + export = { configs, + flatConfigs, rules, } diff --git a/src/types.ts b/src/types.ts index 34dd04fb3..5a0c88d4b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -70,6 +70,15 @@ export type PluginConfig = { rules?: Record<`${PluginName}/${string}`, TSESLint.Linter.RuleEntry> } & TSESLint.Linter.ConfigType +export type PluginFlatBaseConfig = { + settings?: PluginSettings + rules?: Record<`${PluginName}/${string}`, TSESLint.FlatConfig.RuleEntry> +} & TSESLint.FlatConfig.Config + +export type PluginFlatConfig = PluginFlatBaseConfig & { + name?: `${PluginName}/${string}` +} + export type RuleContext< TMessageIds extends string = string, TOptions extends readonly unknown[] = readonly unknown[], diff --git a/src/utils/ignore.ts b/src/utils/ignore.ts index e96575ee8..105357a1a 100644 --- a/src/utils/ignore.ts +++ b/src/utils/ignore.ts @@ -28,7 +28,7 @@ function validExtensions(context: ChildContext | RuleContext) { export function getFileExtensions(settings: PluginSettings) { // start with explicit JS-parsed extensions const exts = new Set( - settings['import-x/extensions'] || ['.js'], + settings['import-x/extensions'] || ['.js', '.mjs', '.cjs'], ) // all alternate parser extensions are also valid