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(eslint-config): update configs and improve type definitions #578

Merged
merged 11 commits into from
Nov 4, 2024
Merged
6 changes: 6 additions & 0 deletions .changeset/green-fans-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@bfra.me/eslint-config": minor
---

Overhaul and refactor types and type generation.

5 changes: 5 additions & 0 deletions .changeset/sharp-dolls-confess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bfra.me/eslint-config": patch
---

Add epilogue override for sources of CLIs.
6 changes: 6 additions & 0 deletions .changeset/strange-windows-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@bfra.me/eslint-config": patch
---

Add missing config names for TypeScript typed linting.

2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
!packages/api-core/test/lib/
packages/api-core/test-utils/sdks/
packages/eslint-config/.eslint-config-inspector
packages/eslint-config/src/types.ts
packages/eslint-config/src/*.d.ts
2 changes: 2 additions & 0 deletions packages/eslint-config/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
src/*.d.ts linguist-generated=true
src/rules.d.ts -diff linguist-generated=true
1 change: 0 additions & 1 deletion packages/eslint-config/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
.eslint-config-inspector
src/types.ts
8 changes: 4 additions & 4 deletions packages/eslint-config/eslint.config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {composeConfig} from './src'
import {composeConfig} from './src/compose-config'
import config from '../../eslint.config'

export default composeConfig(
config,
{
name: '@bfra.me/works/eslint-config/ignores',
ignores: ['.eslint-config-inspector/', 'src/types.ts'],
name: '@bfra.me/eslint-config/ignores',
ignores: ['.eslint-config-inspector/', 'src/rules.d.ts'],
},
{
name: '@bfra.me/works/eslint-config',
name: '@bfra.me/eslint-config',
files: ['src/**/*.ts'],
rules: {
'perfectionist/sort-objects': [
Expand Down
85 changes: 32 additions & 53 deletions packages/eslint-config/scripts/generate-types.ts
Original file line number Diff line number Diff line change
@@ -1,83 +1,62 @@
import fs from 'node:fs/promises'
import {builtinRules} from 'eslint/use-at-your-own-risk'
import {composer} from 'eslint-flat-config-utils'
import {flatConfigsToRulesDTS} from 'eslint-typegen/core'
import type vitest from '@vitest/eslint-plugin'
import {defineConfig} from '../src/define-config'

const configs = await composer(
defineConfig({
plugins: {
'': {
rules: Object.fromEntries(builtinRules),
},
const configs = await defineConfig({
plugins: {
'': {
rules: Object.fromEntries(builtinRules),
},
vitest: true,
}),
// TODO: The `vitest/valid-title` rule breaks the generated types if saved as a .ts instead of a .d.ts file.
).override('@bfra.me/vitest/plugin', config => {
const {
plugins: {vitest: vitestPlugin},
} = config as {plugins: {vitest: typeof vitest}}
if (vitestPlugin.rules && 'valid-title' in vitestPlugin.rules) {
// HACK: Remove the rule before passing the config to the type generator.
delete (vitestPlugin.rules as {[key: string]: unknown})['valid-title']
}
return config
},
typescript: {
tsconfigPath: 'tsconfig.json',
},
vitest: true,
})

const rulesTypeName = 'Rules'
const configNames = configs.map(config => config.name).filter(Boolean) as string[]
const configNamesDts =
configNames.length > 0 ? `${configNames.map(name => `'${name}'`).join(' | ')}` : 'never'
const configType = `Linter.Config<Linter.RulesRecord & ${rulesTypeName}>`

let dts = await flatConfigsToRulesDTS(configs, {
const rulesDts = await flatConfigsToRulesDTS(configs, {
exportTypeName: rulesTypeName,
includeAugmentation: false,
includeIgnoreComments: false,
})

dts =
`// This file is generated by scripts/generate-types.ts
// Do not edit this file directly.

/* eslint-disable */
` +
dts +
`
const configNames = configs.map(config => config.name).filter(Boolean) as string[]
const configDts = `import type {Linter} from 'eslint'
import type {FlatConfigComposer, ResolvableFlatConfig} from 'eslint-flat-config-utils'
import type {Rules} from './rules'

import type * as FCUTypes from 'eslint-flat-config-utils'
/**
* Represents a value that resolves to one or more ESLint flat configurations.
* @see https://jsr.io/@antfu/eslint-flat-config-utils/doc/~/ResolvableFlatConfig
*/
export type AwaitableFlatConfig = ResolvableFlatConfig<Config>

/**
* Each configuration object contains all of the information ESLint needs to execute on a set of files.
* Represents the configuration for the linter.
* This interface extends the {@link Linter.Config} interface, expanding {@link Rules} to include the rules defined in all configurations.
*
* @see https://eslint.org/docs/latest/use/configure/configuration-files#configuration-objects
*/
export type Config = ${configType}
*/
export interface Config extends Linter.Config<Linter.RulesRecord & ${rulesTypeName}> {}

/**
* Defines a 'composer' for ESLint flat configurations.
* @template Config - The ESLint flat configuration type, extending Linter.Config.
* @template ConfigNames - A literal union of all flat configurations provided by this package.
* @see {@link ConfigNames}
* @see https://jsr.io/@antfu/eslint-flat-config-utils/doc/~/FlatConfigComposer
*/
export type FlatConfigComposer<
Config extends Linter.Config = ${configType},
ConfigNames extends string = ${configNamesDts} | (string & Record<never, never>)
> = FCUTypes.FlatConfigComposer<Config, ConfigNames>
export type ConfigComposer = FlatConfigComposer<Config, ConfigNames>

/**
* Represents a value that resolves to one or more ESLint flat configurations.
* @template Config - The ESLint flat configuration type, extending Linter.Config.
* @see https://jsr.io/@antfu/eslint-flat-config-utils/doc/~/ResolvableFlatConfig
* Defines the names of the available ESLint configurations.
*/
export type ResolvableFlatConfig<
Config extends Linter.Config = ${configType}
> = FCUTypes.ResolvableFlatConfig<Config>
export type ConfigNames =${configNames.length > 0 ? `\n | ${configNames.map(name => `'${name}'`).join('\n | ')}` : ' never'}
`

export type * from './define-config'
const preamble = `// This file is generated by scripts/generate-types.ts
// Do not edit this file directly.

`

await fs.writeFile('src/types.ts', dts)
await fs.writeFile('src/config.d.ts', preamble + configDts)
await fs.writeFile('src/rules.d.ts', preamble + rulesDts)
12 changes: 4 additions & 8 deletions packages/eslint-config/src/compose-config.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import {composer} from 'eslint-flat-config-utils'
import type {FlatConfigComposer} from './types'

type InferConfig<T> = T extends FlatConfigComposer<infer U> ? U : never
type InferConfigNames<T> = T extends FlatConfigComposer<any, infer U> ? U : never
import type {AwaitableFlatConfig, Config, ConfigComposer, ConfigNames} from './config'

/**
* Composes an ESLint configuration object from the provided flat configurations.
*
* @param configs - The configuration names to compose.
* @returns The composed ESLint configuration object.
*/
export const composeConfig = composer<
InferConfig<FlatConfigComposer>,
InferConfigNames<FlatConfigComposer>
>
// @ts-expect-error - TypeScript insists that the return type should be `Promise<T>`, but it's aa type which acts like a `Promise<T>`.
export const composeConfig = async (...configs: AwaitableFlatConfig[]): ConfigComposer =>
composer<Config, ConfigNames>(...configs)
53 changes: 53 additions & 0 deletions packages/eslint-config/src/config.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/eslint-config/src/configs/command.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import config from 'eslint-plugin-command/config'
import type {Config} from '../types'
import type {Config} from '../config'

export async function command(): Promise<Config[]> {
return [
Expand Down
32 changes: 21 additions & 11 deletions packages/eslint-config/src/configs/epilogue.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {GLOB_SRC, GLOB_SRC_EXT} from '../globs'
import type {Config} from '../types'
import type {Config} from '../config'

export async function epilogue(): Promise<Config[]> {
// @keep-sorted {"keys":["name"]}
return [
{
name: '@bfra.me/epilogue/commonjs',
files: ['**/*.js', '**/*.cjs'],
name: '@bfra.me/epilogue/cli',
files: [`**/cli/${GLOB_SRC}`, `**/cli.${GLOB_SRC_EXT}`],
rules: {
'@typescript-eslint/no-require-imports': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'no-console': 'off',
},
},
{
Expand All @@ -19,6 +19,21 @@ export async function epilogue(): Promise<Config[]> {
'no-console': 'off',
},
},
{
name: '@bfra.me/epilogue/scripts',
files: [`**/scripts/${GLOB_SRC}`],
rules: {
'@typescript-eslint/explicit-function-return-type': 'off',
'no-console': 'off',
},
},
{
name: '@bfra.me/epilogue/commonjs',
files: ['**/*.js', '**/*.cjs'],
rules: {
'@typescript-eslint/no-require-imports': 'off',
},
},
{
name: '@bfra.me/epilogue/dts',
files: ['**/*.d.?([cm])ts'],
Expand All @@ -30,12 +45,7 @@ export async function epilogue(): Promise<Config[]> {
},
},
{
name: '@bfra.me/epilogue/scripts',
files: [`**/scripts/${GLOB_SRC}`],
rules: {
'@typescript-eslint/explicit-function-return-type': 'off',
'no-console': 'off',
},
name: '@bfra.me/epilogue',
},
]
}
2 changes: 1 addition & 1 deletion packages/eslint-config/src/configs/eslint-comments.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {Config} from '../types'
import type {Config} from '../config'
import {eslintComments as _eslintComments} from '../plugins'

export async function eslintComments(): Promise<Config[]> {
Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-config/src/configs/ignores.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {Config} from '../types'
import type {Config} from '../config'
import {GLOB_EXCLUDE} from '../globs'

export async function ignores(ignores: string[] = []): Promise<Config[]> {
Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-config/src/configs/imports.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {Config} from '../types'
import type {Config} from '../config'
import {importX as pluginImportX} from '../plugins'

export async function imports(): Promise<Config[]> {
Expand Down
21 changes: 17 additions & 4 deletions packages/eslint-config/src/configs/javascript.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
import {unusedImports as pluginUnusedImports} from '../plugins'
import type {Config, OptionsIsInEditor, OptionsOverrides} from '../types'
import type {Flatten, OptionsIsInEditor, OptionsOverrides} from '../options'
import type {Config} from '../config'
import globals from 'globals'

export async function javascript(
options: OptionsIsInEditor & OptionsOverrides = {},
): Promise<Config[]> {
/**
* Represents the options for configuring the JavaScript ESLint configuration.
* This type is a combination of the {@link OptionsIsInEditor} and {@link OptionsOverrides} types.
*/
export type JavaScriptOptions = Flatten<OptionsIsInEditor & OptionsOverrides>

/**
* Configures the JavaScript ESLint configuration with the specified options.
*
* @param options - The options for configuring the JavaScript ESLint configuration.
* @param options.isInEditor - Indicates whether the code is being edited in an editor.
* @param options.overrides - Additional overrides for the ESLint rules.
* @returns An array of ESLint configurations.
*/
export async function javascript(options: JavaScriptOptions = {}): Promise<Config[]> {
const {isInEditor = false, overrides = {}} = options
return [
{
Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-config/src/configs/jsdoc.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {jsdoc as _jsdoc} from '../plugins'
import type {Config} from '../types'
import type {Config} from '../config'

export async function jsdoc(): Promise<Config[]> {
return [
Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-config/src/configs/perfectionist.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {Config} from '../types'
import type {Config} from '../config'
import {perfectionist as pluginPerfectionist} from '../plugins'

/**
Expand Down
Loading