diff --git a/README.md b/README.md index e82b03baed..9ac6cd5be0 100644 --- a/README.md +++ b/README.md @@ -223,6 +223,7 @@ We don't recommend using this style in general usages, as there are shared optio ```js // eslint.config.js import { + combine, comments, ignores, imports, @@ -240,21 +241,21 @@ import { yaml, } from '@antfu/eslint-config' -export default [ - ...ignores(), - ...javascript(/* Options */), - ...comments(), - ...node(), - ...jsdoc(), - ...imports(), - ...unicorn(), - ...typescript(/* Options */), - ...stylistic(), - ...vue(), - ...jsonc(), - ...yaml(), - ...markdown(), -] +export default combine( + ignores(), + javascript(/* Options */), + comments(), + node(), + jsdoc(), + imports(), + unicorn(), + typescript(/* Options */), + stylistic(), + vue(), + jsonc(), + yaml(), + markdown(), +) ``` diff --git a/src/configs/comments.ts b/src/configs/comments.ts index bc88abbeb0..a56283698e 100644 --- a/src/configs/comments.ts +++ b/src/configs/comments.ts @@ -1,7 +1,7 @@ -import type { ConfigItem } from '../types' +import type { FlatConfigItem } from '../types' import { pluginComments } from '../plugins' -export function comments(): ConfigItem[] { +export async function comments(): Promise { return [ { name: 'antfu:eslint-comments', diff --git a/src/configs/ignores.ts b/src/configs/ignores.ts index 7f3f77a8ea..ec83734a47 100644 --- a/src/configs/ignores.ts +++ b/src/configs/ignores.ts @@ -1,7 +1,7 @@ -import type { ConfigItem } from '../types' +import type { FlatConfigItem } from '../types' import { GLOB_EXCLUDE } from '../globs' -export function ignores(): ConfigItem[] { +export async function ignores(): Promise { return [ { ignores: GLOB_EXCLUDE, diff --git a/src/configs/imports.ts b/src/configs/imports.ts index c86dd6c7d7..80ffdaec64 100644 --- a/src/configs/imports.ts +++ b/src/configs/imports.ts @@ -1,7 +1,7 @@ -import type { ConfigItem, OptionsStylistic } from '../types' +import type { FlatConfigItem, OptionsStylistic } from '../types' import { pluginAntfu, pluginImport } from '../plugins' -export function imports(options: OptionsStylistic = {}): ConfigItem[] { +export async function imports(options: OptionsStylistic = {}): Promise { const { stylistic = true, } = options diff --git a/src/configs/javascript.ts b/src/configs/javascript.ts index c718b98e8c..ada312cecd 100644 --- a/src/configs/javascript.ts +++ b/src/configs/javascript.ts @@ -1,14 +1,15 @@ import globals from 'globals' -import type { ConfigItem, OptionsIsInEditor, OptionsOverrides } from '../types' +import type { FlatConfigItem, OptionsIsInEditor, OptionsOverrides } from '../types' import { pluginAntfu, pluginUnusedImports } from '../plugins' import { GLOB_SRC, GLOB_SRC_EXT } from '../globs' -export function javascript(options: OptionsIsInEditor & OptionsOverrides = {}): ConfigItem[] { +export async function javascript( + options: OptionsIsInEditor & OptionsOverrides = {}, +): Promise { const { isInEditor = false, overrides = {}, } = options - return [ { languageOptions: { diff --git a/src/configs/jsdoc.ts b/src/configs/jsdoc.ts index 06706be44b..bdd4bf7a07 100644 --- a/src/configs/jsdoc.ts +++ b/src/configs/jsdoc.ts @@ -1,7 +1,7 @@ -import type { ConfigItem, OptionsStylistic } from '../types' -import { pluginJsdoc } from '../plugins' +import { interopDefault } from 'src' +import type { FlatConfigItem, OptionsStylistic } from '../types' -export function jsdoc(options: OptionsStylistic = {}): ConfigItem[] { +export async function jsdoc(options: OptionsStylistic = {}): Promise { const { stylistic = true, } = options @@ -10,7 +10,8 @@ export function jsdoc(options: OptionsStylistic = {}): ConfigItem[] { { name: 'antfu:jsdoc', plugins: { - jsdoc: pluginJsdoc, + // @ts-expect-error missing types + jsdoc: await interopDefault(import('eslint-plugin-jsdoc')), }, rules: { 'jsdoc/check-access': 'warn', diff --git a/src/configs/jsonc.ts b/src/configs/jsonc.ts index dcf5dd50f9..30eb29cc9c 100644 --- a/src/configs/jsonc.ts +++ b/src/configs/jsonc.ts @@ -1,8 +1,8 @@ -import type { ConfigItem, OptionsOverrides, OptionsStylistic } from '../types' +import type { FlatConfigItem, OptionsOverrides, OptionsStylistic } from '../types' import { GLOB_JSON, GLOB_JSON5, GLOB_JSONC } from '../globs' -import { parserJsonc, pluginJsonc } from '../plugins' +import { interopDefault } from '../utils' -export function jsonc(options: OptionsStylistic & OptionsOverrides = {}): ConfigItem[] { +export async function jsonc(options: OptionsStylistic & OptionsOverrides = {}): Promise { const { overrides = {}, stylistic = true, @@ -12,6 +12,14 @@ export function jsonc(options: OptionsStylistic & OptionsOverrides = {}): Config indent = 2, } = typeof stylistic === 'boolean' ? {} : stylistic + const [ + pluginJsonc, + parserJsonc, + ] = await Promise.all([ + interopDefault(import('eslint-plugin-jsonc')), + interopDefault(import('jsonc-eslint-parser')), + ] as const) + return [ { name: 'antfu:jsonc:setup', diff --git a/src/configs/markdown.ts b/src/configs/markdown.ts index 54dcaf06e0..47d810cdeb 100644 --- a/src/configs/markdown.ts +++ b/src/configs/markdown.ts @@ -1,8 +1,8 @@ -import type { ConfigItem, OptionsComponentExts, OptionsOverrides } from '../types' +import type { FlatConfigItem, OptionsComponentExts, OptionsOverrides } from '../types' import { GLOB_MARKDOWN, GLOB_MARKDOWN_CODE } from '../globs' -import { pluginMarkdown } from '../plugins' +import { interopDefault } from '../utils' -export function markdown(options: OptionsComponentExts & OptionsOverrides = {}): ConfigItem[] { +export async function markdown(options: OptionsComponentExts & OptionsOverrides = {}): Promise { const { componentExts = [], overrides = {}, @@ -12,7 +12,8 @@ export function markdown(options: OptionsComponentExts & OptionsOverrides = {}): { name: 'antfu:markdown:setup', plugins: { - markdown: pluginMarkdown, + // @ts-expect-error missing types + markdown: await interopDefault(import('eslint-plugin-markdown')), }, }, { diff --git a/src/configs/node.ts b/src/configs/node.ts index 505ed62b01..17438242c3 100644 --- a/src/configs/node.ts +++ b/src/configs/node.ts @@ -1,7 +1,7 @@ -import type { ConfigItem } from '../types' +import type { FlatConfigItem } from '../types' import { pluginNode } from '../plugins' -export function node(): ConfigItem[] { +export async function node(): Promise { return [ { name: 'antfu:node', diff --git a/src/configs/perfectionist.ts b/src/configs/perfectionist.ts index c4c7483e1f..9722d45fad 100644 --- a/src/configs/perfectionist.ts +++ b/src/configs/perfectionist.ts @@ -1,4 +1,4 @@ -import type { ConfigItem } from '../types' +import type { FlatConfigItem } from '../types' import { pluginPerfectionist } from '../plugins' /** @@ -6,7 +6,7 @@ import { pluginPerfectionist } from '../plugins' * * @see https://github.com/azat-io/eslint-plugin-perfectionist */ -export function perfectionist(): ConfigItem[] { +export async function perfectionist(): Promise { return [ { name: 'antfu:perfectionist', diff --git a/src/configs/sort.ts b/src/configs/sort.ts index adbed8ead8..d0dd3c854b 100644 --- a/src/configs/sort.ts +++ b/src/configs/sort.ts @@ -1,11 +1,11 @@ -import type { ConfigItem } from '../types' +import type { FlatConfigItem } from '../types' /** * Sort package.json * * Requires `jsonc` config */ -export function sortPackageJson(): ConfigItem[] { +export async function sortPackageJson(): Promise { return [ { files: ['**/package.json'], @@ -100,7 +100,7 @@ export function sortPackageJson(): ConfigItem[] { * Requires `jsonc` config */ -export function sortTsconfig(): ConfigItem[] { +export function sortTsconfig(): FlatConfigItem[] { return [ { files: ['**/tsconfig.json', '**/tsconfig.*.json'], diff --git a/src/configs/stylistic.ts b/src/configs/stylistic.ts index e3535e3b6a..48eba9fde9 100644 --- a/src/configs/stylistic.ts +++ b/src/configs/stylistic.ts @@ -1,7 +1,8 @@ -import type { ConfigItem, StylisticConfig } from '../types' -import { pluginAntfu, pluginStylistic } from '../plugins' +import { interopDefault } from 'src' +import type { FlatConfigItem, StylisticConfig } from '../types' +import { pluginAntfu } from '../plugins' -export function stylistic(options: StylisticConfig = {}): ConfigItem[] { +export async function stylistic(options: StylisticConfig = {}): Promise { const { indent = 2, jsx = true, @@ -9,6 +10,8 @@ export function stylistic(options: StylisticConfig = {}): ConfigItem[] { semi = false, } = options + const pluginStylistic = await interopDefault(import('@stylistic/eslint-plugin')) + const config = pluginStylistic.configs.customize({ flat: true, indent, diff --git a/src/configs/test.ts b/src/configs/test.ts index b89b5f92fc..f8573ad872 100644 --- a/src/configs/test.ts +++ b/src/configs/test.ts @@ -1,13 +1,22 @@ -import type { ConfigItem, OptionsIsInEditor, OptionsOverrides } from '../types' -import { pluginNoOnlyTests, pluginVitest } from '../plugins' +import { interopDefault } from 'src' +import type { FlatConfigItem, OptionsIsInEditor, OptionsOverrides } from '../types' import { GLOB_TESTS } from '../globs' -export function test(options: OptionsIsInEditor & OptionsOverrides = {}): ConfigItem[] { +export async function test(options: OptionsIsInEditor & OptionsOverrides = {}): Promise { const { isInEditor = false, overrides = {}, } = options + const [ + pluginVitest, + pluginNoOnlyTests, + ] = await Promise.all([ + interopDefault(import('eslint-plugin-vitest')), + // @ts-expect-error missing types + interopDefault(import('eslint-plugin-no-only-tests')), + ] as const) + return [ { name: 'antfu:test:setup', diff --git a/src/configs/typescript.ts b/src/configs/typescript.ts index 65f37dc845..ad9b0c3958 100644 --- a/src/configs/typescript.ts +++ b/src/configs/typescript.ts @@ -1,19 +1,19 @@ import process from 'node:process' -import type { ConfigItem, OptionsComponentExts, OptionsOverrides, OptionsTypeScriptParserOptions, OptionsTypeScriptWithTypes } from '../types' +import type { FlatConfigItem, OptionsComponentExts, OptionsOverrides, OptionsTypeScriptParserOptions, OptionsTypeScriptWithTypes } from '../types' import { GLOB_SRC } from '../globs' -import { parserTs, pluginAntfu, pluginImport, pluginTs } from '../plugins' -import { renameRules, toArray } from '../utils' +import { pluginAntfu } from '../plugins' +import { interopDefault, renameRules, toArray } from '../utils' -export function typescript( +export async function typescript( options?: OptionsComponentExts & OptionsOverrides & OptionsTypeScriptWithTypes & OptionsTypeScriptParserOptions, -): ConfigItem[] { +): Promise { const { componentExts = [], overrides = {}, parserOptions = {}, } = options ?? {} - const typeAwareRules: ConfigItem['rules'] = { + const typeAwareRules: FlatConfigItem['rules'] = { 'dot-notation': 'off', 'no-implied-eval': 'off', 'no-throw-literal': 'off', @@ -39,13 +39,20 @@ export function typescript( ? toArray(options.tsconfigPath) : undefined + const [ + pluginTs, + parserTs, + ] = await Promise.all([ + interopDefault(import('@typescript-eslint/eslint-plugin')), + interopDefault(import('@typescript-eslint/parser')), + ] as const) + return [ { // Install the plugins without globs, so they can be configured separately. name: 'antfu:typescript:setup', plugins: { antfu: pluginAntfu, - import: pluginImport, ts: pluginTs as any, }, }, diff --git a/src/configs/unicorn.ts b/src/configs/unicorn.ts index b03dacab9c..3481ad2458 100644 --- a/src/configs/unicorn.ts +++ b/src/configs/unicorn.ts @@ -1,7 +1,7 @@ -import type { ConfigItem } from '../types' +import type { FlatConfigItem } from '../types' import { pluginUnicorn } from '../plugins' -export function unicorn(): ConfigItem[] { +export async function unicorn(): Promise { return [ { name: 'antfu:unicorn', diff --git a/src/configs/vue.ts b/src/configs/vue.ts index 7a8f587b00..1568a8807e 100644 --- a/src/configs/vue.ts +++ b/src/configs/vue.ts @@ -1,10 +1,10 @@ -import type { ConfigItem, OptionsHasTypeScript, OptionsOverrides, OptionsStylistic } from '../types' +import { interopDefault } from 'src' +import type { FlatConfigItem, OptionsHasTypeScript, OptionsOverrides, OptionsStylistic } from '../types' import { GLOB_VUE } from '../globs' -import { parserTs, parserVue, pluginVue } from '../plugins' -export function vue( +export async function vue( options: OptionsHasTypeScript & OptionsOverrides & OptionsStylistic = {}, -): ConfigItem[] { +): Promise { const { overrides = {}, stylistic = true, @@ -14,6 +14,15 @@ export function vue( indent = 2, } = typeof stylistic === 'boolean' ? {} : stylistic + const [ + pluginVue, + parserVue, + ] = await Promise.all([ + // @ts-expect-error missing types + interopDefault(import('eslint-plugin-vue')), + interopDefault(import('vue-eslint-parser')), + ] as const) + return [ { name: 'antfu:vue:setup', @@ -30,7 +39,9 @@ export function vue( jsx: true, }, extraFileExtensions: ['.vue'], - parser: options.typescript ? parserTs as any : null, + parser: options.typescript + ? await interopDefault(import('@typescript-eslint/parser')) as any + : null, sourceType: 'module', }, }, diff --git a/src/configs/yaml.ts b/src/configs/yaml.ts index dfb431c4cd..e15c0c7b11 100644 --- a/src/configs/yaml.ts +++ b/src/configs/yaml.ts @@ -1,10 +1,10 @@ -import type { ConfigItem, OptionsOverrides, OptionsStylistic } from '../types' +import type { FlatConfigItem, OptionsOverrides, OptionsStylistic } from '../types' import { GLOB_YAML } from '../globs' -import { parserYaml, pluginYaml } from '../plugins' +import { interopDefault } from '../utils' -export function yaml( +export async function yaml( options: OptionsOverrides & OptionsStylistic = {}, -): ConfigItem[] { +): Promise { const { overrides = {}, stylistic = true, @@ -15,11 +15,19 @@ export function yaml( quotes = 'single', } = typeof stylistic === 'boolean' ? {} : stylistic + const [ + pluginYaml, + parserYaml, + ] = await Promise.all([ + interopDefault(import('eslint-plugin-yml')), + interopDefault(import('yaml-eslint-parser')), + ] as const) + return [ { name: 'antfu:yaml:setup', plugins: { - yaml: pluginYaml as any, + yaml: pluginYaml, }, }, { diff --git a/src/factory.ts b/src/factory.ts index e27680b14c..8951e51116 100644 --- a/src/factory.ts +++ b/src/factory.ts @@ -1,8 +1,7 @@ import process from 'node:process' import fs from 'node:fs' import { isPackageExists } from 'local-pkg' -import gitignore from 'eslint-config-flat-gitignore' -import type { ConfigItem, OptionsConfig } from './types' +import type { Awaitable, FlatConfigItem, OptionsConfig, UserConfigItem } from './types' import { comments, ignores, @@ -22,9 +21,9 @@ import { vue, yaml, } from './configs' -import { combine } from './utils' +import { combine, interopDefault } from './utils' -const flatConfigProps: (keyof ConfigItem)[] = [ +const flatConfigProps: (keyof FlatConfigItem)[] = [ 'files', 'ignores', 'languageOptions', @@ -45,7 +44,10 @@ const VuePackages = [ /** * Construct an array of ESLint flat config items. */ -export function antfu(options: OptionsConfig & ConfigItem = {}, ...userConfigs: (ConfigItem | ConfigItem[])[]) { +export async function antfu( + options: OptionsConfig & FlatConfigItem = {}, + ...userConfigs: Awaitable[] +): Promise { const { componentExts = [], gitignore: enableGitignore = true, @@ -63,15 +65,15 @@ export function antfu(options: OptionsConfig & ConfigItem = {}, ...userConfigs: if (stylisticOptions && !('jsx' in stylisticOptions)) stylisticOptions.jsx = options.jsx ?? true - const configs: ConfigItem[][] = [] + const configs: Awaitable[] = [] if (enableGitignore) { if (typeof enableGitignore !== 'boolean') { - configs.push([gitignore(enableGitignore)]) + configs.push(interopDefault(import('eslint-config-flat-gitignore')).then(r => [r(enableGitignore)])) } else { if (fs.existsSync('.gitignore')) - configs.push([gitignore()]) + configs.push(interopDefault(import('eslint-config-flat-gitignore')).then(r => [r()])) } } @@ -158,7 +160,7 @@ export function antfu(options: OptionsConfig & ConfigItem = {}, ...userConfigs: if (key in options) acc[key] = options[key] as any return acc - }, {} as ConfigItem) + }, {} as FlatConfigItem) if (Object.keys(fusedConfig).length) configs.push([fusedConfig]) diff --git a/src/index.ts b/src/index.ts index 122f171b1c..518a734a44 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,6 @@ import { antfu } from './factory' export * from './configs' export * from './factory' export * from './globs' -export * from './plugins' export * from './types' export * from './utils' diff --git a/src/plugins.ts b/src/plugins.ts index 2a7de22bd0..23299cbb18 100644 --- a/src/plugins.ts +++ b/src/plugins.ts @@ -4,21 +4,7 @@ export { default as pluginAntfu } from 'eslint-plugin-antfu' export { default as pluginComments } from 'eslint-plugin-eslint-comments' export * as pluginImport from 'eslint-plugin-i' -export { default as pluginJsdoc } from 'eslint-plugin-jsdoc' -export * as pluginJsonc from 'eslint-plugin-jsonc' -export { default as pluginMarkdown } from 'eslint-plugin-markdown' export { default as pluginNode } from 'eslint-plugin-n' -export { default as pluginStylistic } from '@stylistic/eslint-plugin' -export { default as pluginTs } from '@typescript-eslint/eslint-plugin' export { default as pluginUnicorn } from 'eslint-plugin-unicorn' export { default as pluginUnusedImports } from 'eslint-plugin-unused-imports' -export { default as pluginVue } from 'eslint-plugin-vue' -export * as pluginYaml from 'eslint-plugin-yml' -export { default as pluginNoOnlyTests } from 'eslint-plugin-no-only-tests' -export { default as pluginVitest } from 'eslint-plugin-vitest' export { default as pluginPerfectionist } from 'eslint-plugin-perfectionist' - -export * as parserTs from '@typescript-eslint/parser' -export { default as parserVue } from 'vue-eslint-parser' -export { default as parserYaml } from 'yaml-eslint-parser' -export { default as parserJsonc } from 'jsonc-eslint-parser' diff --git a/src/types.ts b/src/types.ts index b7b452196d..c169afa1cd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,6 @@ import type { FlatGitignoreOptions } from 'eslint-config-flat-gitignore' import type { ParserOptions } from '@typescript-eslint/parser' +import type { Linter } from 'eslint' import type { EslintCommentsRules, EslintRules, @@ -25,6 +26,8 @@ export type WrapRuleConfig = { [K in keyof T]: T[K] extends RuleConfig ? T[K] : RuleConfig } +export type Awaitable = T | Promise + export type Rules = WrapRuleConfig< MergeIntersection< RenamePrefix & @@ -46,7 +49,7 @@ export type Rules = WrapRuleConfig< > > -export type ConfigItem = Omit, 'plugins'> & { +export type FlatConfigItem = Omit, 'plugins'> & { /** * Custom name of each config item */ @@ -61,6 +64,8 @@ export type ConfigItem = Omit, 'plugins'> & { plugins?: Record } +export type UserConfigItem = FlatConfigItem | Linter.FlatConfig + export interface OptionsComponentExts { /** * Additional extensions for components. @@ -98,7 +103,7 @@ export interface StylisticConfig extends Pick[]): Promise { + const resolved = await Promise.all(configs) + return resolved.flat() } export function renameRules(rules: Record, from: string, to: string) { @@ -21,3 +22,8 @@ export function renameRules(rules: Record, from: string, to: string export function toArray(value: T | T[]): T[] { return Array.isArray(value) ? value : [value] } + +export async function interopDefault(m: Awaitable): Promise { + const resolved = await m + return (resolved as any).default || resolved +} diff --git a/test/fixtures.test.ts b/test/fixtures.test.ts index 4c80490792..e9e5988f3c 100644 --- a/test/fixtures.test.ts +++ b/test/fixtures.test.ts @@ -3,7 +3,7 @@ import { afterAll, beforeAll, it } from 'vitest' import fs from 'fs-extra' import { execa } from 'execa' import fg from 'fast-glob' -import type { ConfigItem, OptionsConfig } from '../src/types' +import type { FlatConfigItem, OptionsConfig } from '../src/types' beforeAll(async () => { await fs.rm('_fixtures', { recursive: true, force: true }) @@ -55,7 +55,7 @@ runWithConfig( }, ) -function runWithConfig(name: string, configs: OptionsConfig, ...items: ConfigItem[]) { +function runWithConfig(name: string, configs: OptionsConfig, ...items: FlatConfigItem[]) { it.concurrent(name, async ({ expect }) => { const from = resolve('fixtures/input') const output = resolve('fixtures/output', name)