Skip to content

Commit

Permalink
feat(eslint-config): update configs and improve type definitions (#578)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcusrbrown authored Nov 4, 2024
1 parent 9b395b9 commit bbfa9b1
Show file tree
Hide file tree
Showing 24 changed files with 7,411 additions and 199 deletions.
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

0 comments on commit bbfa9b1

Please sign in to comment.