Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add flat config support #122

Merged
merged 2 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .changeset/great-dodos-dream.md
Original file line number Diff line number Diff line change
@@ -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;
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 10 additions & 0 deletions src/config/flat/electron.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { PluginFlatConfig } from '../../types'

/**
* Default settings for Electron applications.
*/
export default {
settings: {
'import-x/core-modules': ['electron'],
},
} satisfies PluginFlatConfig
15 changes: 15 additions & 0 deletions src/config/flat/errors.ts
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions src/config/flat/react-native.ts
Original file line number Diff line number Diff line change
@@ -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
21 changes: 21 additions & 0 deletions src/config/flat/react.ts
Original file line number Diff line number Diff line change
@@ -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
27 changes: 27 additions & 0 deletions src/config/flat/recommended.ts
Original file line number Diff line number Diff line change
@@ -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
12 changes: 12 additions & 0 deletions src/config/flat/stage-0.ts
Original file line number Diff line number Diff line change
@@ -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
41 changes: 41 additions & 0 deletions src/config/flat/typescript.ts
Original file line number Diff line number Diff line change
@@ -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: {
michaelfaith marked this conversation as resolved.
Show resolved Hide resolved
extensions: allExtensions,
},
},
},
rules: {
// analysis/correctness

// TypeScript compilation already ensures that named imports exist in the referenced module
'import-x/named': 'off',
},
} satisfies PluginFlatBaseConfig
12 changes: 12 additions & 0 deletions src/config/flat/warnings.ts
Original file line number Diff line number Diff line change
@@ -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
12 changes: 9 additions & 3 deletions src/config/typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
84 changes: 66 additions & 18 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -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'

Check failure on line 20 in src/index.ts

View workflow job for this annotation

GitHub Actions / Lint and Test with Node.js 20 and ESLint 8.56 on macos-latest

There should be no empty line within import group

Check failure on line 20 in src/index.ts

View workflow job for this annotation

GitHub Actions / Lint and Test with Node.js 20 and ESLint 8 on macos-latest

There should be no empty line within import group

Check failure on line 20 in src/index.ts

View workflow job for this annotation

GitHub Actions / Lint and Test with Node.js 20 and ESLint 8.56 on ubuntu-latest

There should be no empty line within import group

Check failure on line 20 in src/index.ts

View workflow job for this annotation

GitHub Actions / Lint and Test with Node.js 20 and ESLint 8 on ubuntu-latest

There should be no empty line within import group

Check failure on line 20 in src/index.ts

View workflow job for this annotation

GitHub Actions / Lint and Test with Node.js 20 and ESLint 9 on ubuntu-latest

There should be no empty line within import group

// rules
import consistentTypeSpecifierStyle from './rules/consistent-type-specifier-style'
import default_ from './rules/default'
import dynamicImportChunkname from './rules/dynamic-import-chunkname'
Expand Down Expand Up @@ -55,23 +66,11 @@
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<string, PluginConfig>
import type {
PluginConfig,
PluginFlatBaseConfig,
PluginFlatConfig,
} from './types'

const rules = {
'no-unresolved': noUnresolved,
Expand Down Expand Up @@ -129,7 +128,56 @@
'imports-first': importsFirst,
} satisfies Record<string, TSESLint.RuleModule<string, readonly unknown[]>>

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<string, PluginConfig>

// 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<string, PluginFlatConfig>

export = {
configs,
flatConfigs,
rules,
}
9 changes: 9 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[],
Expand Down
2 changes: 1 addition & 1 deletion src/utils/ignore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function validExtensions(context: ChildContext | RuleContext) {
export function getFileExtensions(settings: PluginSettings) {
// start with explicit JS-parsed extensions
const exts = new Set<FileExtension>(
settings['import-x/extensions'] || ['.js'],
settings['import-x/extensions'] || ['.js', '.mjs', '.cjs'],
michaelfaith marked this conversation as resolved.
Show resolved Hide resolved
)

// all alternate parser extensions are also valid
Expand Down
Loading