routie dev init since i didn't adhere to any proper guidance up until now

This commit is contained in:
2026-04-29 22:27:29 -06:00
commit e1dabb71e2
15301 changed files with 3562618 additions and 0 deletions
+411
View File
@@ -0,0 +1,411 @@
import { ConfigWithExtends, Plugin } from '@eslint/config-helpers';
export { ConfigWithExtends } from '@eslint/config-helpers';
import { Linter, Rule, ESLint } from 'eslint';
/**
* Rename plugin prefixes in a rule object.
* Accepts a map of prefixes to rename.
*
* @example
* ```ts
* import { renamePluginsInRules } from 'eslint-flat-config-utils'
*
* export default [{
* rules: renamePluginsInRules(
* {
* '@typescript-eslint/indent': 'error'
* },
* { '@typescript-eslint': 'ts' }
* )
* }]
* ```
*/
declare function renamePluginsInRules(rules: Record<string, any>, map: Record<string, string>): Record<string, any>;
/**
* The options for `renamePluginsInConfigs`
*/
interface RenamePluginsInConfigsOptions {
/**
* Resolve conflicts by merging plugins.
*
* Note there is no guarantee that the result works the same as the original configs.
*
* @default false
*/
mergePlugins?: boolean;
}
/**
* Rename plugin names a flat configs array
*
* @example
* ```ts
* import { renamePluginsInConfigs } from 'eslint-flat-config-utils'
* import someConfigs from './some-configs'
*
* export default renamePluginsInConfigs(someConfigs, {
* '@typescript-eslint': 'ts',
* 'import-x': 'import',
* })
* ```
*/
declare function renamePluginsInConfigs<T extends Linter.Config = Linter.Config>(configs: T[], map: Record<string, string>, options?: RenamePluginsInConfigsOptions): T[];
/**
* Alias to `Linter.Config`
*
* @deprecated
*/
interface FlatConfigItem extends Linter.Config {
}
/**
* A type that can be awaited. Promise<T> or T.
*/
type Awaitable<T> = T | Promise<T>;
/**
* A type that can be an array or a single item.
*/
type Arrayable<T> = T | T[];
/**
* Default config names map. Used for type augmentation.
*
* @example
* ```ts
* declare module 'eslint-flat-config-utils' {
* interface DefaultConfigNamesMap {
* 'my-custom-config': true
* }
* }
* ```
*/
interface DefaultConfigNamesMap {
}
interface Nothing {
}
/**
* type StringLiteralUnion<'foo'> = 'foo' | string
* This has auto completion whereas `'foo' | string` doesn't
* Adapted from https://github.com/microsoft/TypeScript/issues/29729
*/
type StringLiteralUnion<T extends U, U = string> = T | (U & Nothing);
type FilterType<T, F> = T extends F ? T : never;
type NullableObject<T> = {
[K in keyof T]?: T[K] | null | undefined;
};
type GetRuleRecordFromConfig<T> = T extends {
rules?: infer R;
} ? R : Linter.RulesRecord;
declare const DEFAULT_PLUGIN_CONFLICTS_ERROR = "Different instances of plugin \"{{pluginName}}\" found in multiple configs: {{configNames}}. It's likely you misconfigured the merge of these configs.";
interface DisableFixesOptions {
builtinRules?: Map<string, Rule.RuleModule> | (() => Awaitable<Map<string, Rule.RuleModule>>);
}
type PluginConflictsError<T extends Linter.Config = Linter.Config> = (pluginName: string, configs: T[]) => string;
/**
* Awaitable array of ESLint flat configs or a composer object.
*/
type ResolvableFlatConfig<T extends object = ConfigWithExtends> = Awaitable<Arrayable<(T | false | undefined | null)>> | Awaitable<(ConfigWithExtends | false | undefined | null)[]> | Awaitable<(Linter.Config | false | undefined | null)[]> | FlatConfigComposer<any>;
/**
* Create a chainable composer object that makes manipulating ESLint flat config easier.
*
* It extends Promise, so that you can directly await or export it to `eslint.config.mjs`
*
* ```ts
* // eslint.config.mjs
* import { composer } from 'eslint-flat-config-utils'
*
* export default composer(
* {
* plugins: {},
* rules: {},
* }
* // ...some configs, accepts same arguments as `concat`
* )
* .append(
* // appends more configs at the end, accepts same arguments as `concat`
* )
* .prepend(
* // prepends more configs at the beginning, accepts same arguments as `concat`
* )
* .insertAfter(
* 'config-name', // specify the name of the target config, or index
* // insert more configs after the target, accepts same arguments as `concat`
* )
* .renamePlugins({
* // rename plugins
* 'old-name': 'new-name',
* // for example, rename `n` from `eslint-plugin-n` to more a explicit prefix `node`
* 'n': 'node'
* // applies to all plugins and rules in the configs
* })
* .override(
* 'config-name', // specify the name of the target config, or index
* {
* // merge with the target config
* rules: {
* 'no-console': 'off'
* },
* }
* )
*
* // And you an directly return the composer object to `eslint.config.mjs`
* ```
*/
declare function composer<T extends ConfigWithExtends = ConfigWithExtends, ConfigNames extends string = keyof DefaultConfigNamesMap>(...configs: ResolvableFlatConfig<ConfigWithExtends extends T ? T : ConfigWithExtends>[]): FlatConfigComposer<ConfigWithExtends extends T ? T : ConfigWithExtends, ConfigNames>;
/**
* The underlying impolementation of `composer()`.
*/
declare class FlatConfigComposer<T extends object = ConfigWithExtends, ConfigNames extends string = keyof DefaultConfigNamesMap> extends Promise<Linter.Config[]> {
private _operations;
private _operationsOverrides;
private _operationsResolved;
private _renames;
private _renamesOptions;
private _pluginsConflictsError;
constructor(...configs: ResolvableFlatConfig<T>[]);
/**
* Set plugin renames, like `n` -> `node`, `import-x` -> `import`, etc.
*
* This will runs after all config items are resolved. Applies to `plugins` and `rules`.
*/
renamePlugins(renames: Record<string, string>, options?: RenamePluginsInConfigsOptions): this;
/**
* Append configs to the end of the current configs array.
*/
append(...items: ResolvableFlatConfig<T>[]): this;
/**
* Prepend configs to the beginning of the current configs array.
*/
prepend(...items: ResolvableFlatConfig<T>[]): this;
/**
* Insert configs before a specific config.
*/
insertBefore(nameOrIndex: StringLiteralUnion<ConfigNames, string | number>, ...items: ResolvableFlatConfig<T>[]): this;
/**
* Insert configs after a specific config.
*/
insertAfter(nameOrIndex: StringLiteralUnion<ConfigNames, string | number>, ...items: ResolvableFlatConfig<T>[]): this;
/**
* Provide overrides to a specific config.
*
* It will be merged with the original config, or provide a custom function to replace the config entirely.
*/
override(nameOrIndex: StringLiteralUnion<ConfigNames, string | number>, config: T | ((config: T) => Awaitable<T>)): this;
/**
* Provide overrides to multiple configs as an object map.
*
* Same as calling `override` multiple times.
*/
overrides(overrides: Partial<Record<StringLiteralUnion<ConfigNames, string | number>, T | ((config: T) => Awaitable<T>)>>): this;
/**
* Override rules and it's options in **all configs**.
*
* Pass `null` as the value to remove the rule.
*
* @example
* ```ts
* composer
* .overrideRules({
* 'no-console': 'off',
* 'no-unused-vars': ['error', { vars: 'all', args: 'after-used' }],
* // remove the rule from all configs
* 'no-undef': null,
* })
* ```
*/
overrideRules(rules: NullableObject<GetRuleRecordFromConfig<T>>): this;
/**
* Remove rules from **all configs**.
*
* @example
* ```ts
* composer
* .removeRules(
* 'no-console',
* 'no-unused-vars'
* )
* ```
*/
removeRules(...rules: StringLiteralUnion<FilterType<keyof GetRuleRecordFromConfig<T>, string>, string>[]): this;
/**
* Remove plugins by name and all the rules referenced by them.
*
* @example
* ```ts
* composer
* .removePlugins(
* 'node'
* )
* ```
*
* The `plugins: { node }` and `rules: { 'node/xxx': 'error' }` will be removed from all configs.
*/
removePlugins(...names: string[]): this;
/**
* Remove a specific config by name or index.
*/
remove(nameOrIndex: StringLiteralUnion<ConfigNames, string | number>): this;
/**
* Replace a plugin with another.
*
* @example
* ```ts
* composer
* .replacePlugin('foo', (fooPlugin) => ({
* ...fooPlugin,
* rules: {
* ...fooPlugin.rules,
* someNewRule,
* },
* }))
* ```
*
* The `plugins: { foo }` will be replaced from all configs with a new plugin that is a merge of it and the `bar` plugin
*/
replacePlugin(name: string, replacement: Awaitable<Plugin> | ((original: Plugin) => Awaitable<Plugin>)): this;
/**
* Replace a specific config by name or index.
*
* The original config will be removed and replaced with the new one.
*/
replace(nameOrIndex: StringLiteralUnion<ConfigNames, string | number>, ...items: ResolvableFlatConfig<T>[]): this;
/**
* Hijack into plugins to disable fixes for specific rules.
*
* Note this mutates the plugin object, use with caution.
*
* @example
* ```ts
* const config = await composer(...)
* .disableRulesFix([
* 'unused-imports/no-unused-imports',
* 'vitest/no-only-tests'
* ])
* ```
*/
disableRulesFix(ruleIds: string[], options?: DisableFixesOptions): this;
/**
* Set a custom warning message for plugins conflicts.
*
* The error message can be a string or a function that returns a string.
*
* Error message accepts template strings:
* - `{{pluginName}}`: the name of the plugin that has conflicts
* - `{{configName1}}`: the name of the first config that uses the plugin
* - `{{configName2}}`: the name of the second config that uses the plugin
* - `{{configNames}}`: a list of config names that uses the plugin
*
* When only one argument is provided, it will be used as the default error message.
*/
setPluginConflictsError(warning?: string | PluginConflictsError): this;
setPluginConflictsError(pluginName: string, warning: string | PluginConflictsError): this;
private _verifyPluginsConflicts;
/**
* Hook when all configs are resolved but before returning the final configs.
*
* You can modify the final configs here.
*/
onResolved(callback: (configs: T[]) => Awaitable<T[] | void>): this;
/**
* Clone the composer object.
*/
clone(): FlatConfigComposer<T>;
/**
* Resolve the pipeline and return the final configs.
*
* This returns a promise. Calling `.then()` has the same effect.
*/
toConfigs(): Promise<Linter.Config[]>;
then<T>(onFulfilled: (value: Linter.Config[]) => T, onRejected?: (reason: any) => any): Promise<Awaited<T>>;
catch(onRejected: (reason: any) => any): Promise<any>;
finally(onFinally: () => void): Promise<Linter.Config[]>;
}
/**
* @deprecated Renamed to `composer`.
*/
declare const pipe: typeof composer;
/**
* @deprecated Renamed to `FlatConfigComposer`.
*/
declare class FlatConfigPipeline<T extends object = Linter.Config, ConfigNames extends string = string> extends FlatConfigComposer<T, ConfigNames> {
}
/**
* Concat multiple flat configs into a single flat config array.
*
* It also resolves promises and flattens the result.
*
* @example
*
* ```ts
* import { concat } from 'eslint-flat-config-utils'
* import eslint from '@eslint/js'
* import stylistic from '@stylistic/eslint-plugin'
*
* export default concat(
* eslint,
* stylistic.configs.customize(),
* { rules: { 'no-console': 'off' } },
* // ...
* )
* ```
*/
declare function concat<T extends Linter.Config = Linter.Config>(...configs: Awaitable<T | T[]>[]): Promise<T[]>;
/**
* A function that returns the config as-is, useful for providing type hints.
*/
declare function defineFlatConfig<T extends Linter.Config = Linter.Config>(config: T): T;
/**
* Extend another flat configs and rename globs paths.
*
* @example
* ```ts
* import { extend } from 'eslint-flat-config-utils'
*
* export default [
* ...await extend(
* // configs to extend
* import('./other-configs/eslint.config.js').then(m => m.default),
* // relative directory path
* 'other-configs/',
* ),
* ]
* ```
*/
declare function extend(configs: Awaitable<Linter.Config[]>, relativePath: string): Promise<Linter.Config[]>;
/**
* Replace a rule in a plugin with given factory.
*/
declare function hijackPluginRule(plugin: ESLint.Plugin, name: string, factory: (rule: Rule.RuleModule) => Rule.RuleModule): ESLint.Plugin;
/**
* Hijack into a rule's `context.report` to disable fixes.
*/
declare function disableRuleFixes(rule: Rule.RuleModule): Rule.RuleModule;
/**
* Merge multiple flat configs into a single flat config.
*
* Note there is no guarantee that the result works the same as the original configs.
*/
declare function mergeConfigs<T extends Linter.Config = Linter.Config>(...configs: T[]): T;
/**
* Merge two or more plugins into one.
*
* Note there is no guarantee that the result works the same as the original plugins.
*
* Limitations:
* - The resulting plugin will not have a `configs` property.
* - In the case of conflicts, the last plugin will be used.
*/
declare function mergePlugins(...plugins: readonly [Plugin, ...Plugin[]]): Plugin;
declare function mergePlugins(...plugins: readonly Plugin[]): Plugin | undefined;
declare function parseRuleId(ruleId: string): {
plugin: string | null;
rule: string;
};
export { DEFAULT_PLUGIN_CONFLICTS_ERROR, FlatConfigComposer, FlatConfigPipeline, composer, concat, defineFlatConfig, disableRuleFixes, extend, hijackPluginRule, mergeConfigs, mergePlugins, parseRuleId, pipe, renamePluginsInConfigs, renamePluginsInRules };
export type { Arrayable, Awaitable, DefaultConfigNamesMap, DisableFixesOptions, FilterType, FlatConfigItem, GetRuleRecordFromConfig, NullableObject, PluginConflictsError, RenamePluginsInConfigsOptions, ResolvableFlatConfig, StringLiteralUnion };
+411
View File
@@ -0,0 +1,411 @@
import { ConfigWithExtends, Plugin } from '@eslint/config-helpers';
export { ConfigWithExtends } from '@eslint/config-helpers';
import { Linter, Rule, ESLint } from 'eslint';
/**
* Rename plugin prefixes in a rule object.
* Accepts a map of prefixes to rename.
*
* @example
* ```ts
* import { renamePluginsInRules } from 'eslint-flat-config-utils'
*
* export default [{
* rules: renamePluginsInRules(
* {
* '@typescript-eslint/indent': 'error'
* },
* { '@typescript-eslint': 'ts' }
* )
* }]
* ```
*/
declare function renamePluginsInRules(rules: Record<string, any>, map: Record<string, string>): Record<string, any>;
/**
* The options for `renamePluginsInConfigs`
*/
interface RenamePluginsInConfigsOptions {
/**
* Resolve conflicts by merging plugins.
*
* Note there is no guarantee that the result works the same as the original configs.
*
* @default false
*/
mergePlugins?: boolean;
}
/**
* Rename plugin names a flat configs array
*
* @example
* ```ts
* import { renamePluginsInConfigs } from 'eslint-flat-config-utils'
* import someConfigs from './some-configs'
*
* export default renamePluginsInConfigs(someConfigs, {
* '@typescript-eslint': 'ts',
* 'import-x': 'import',
* })
* ```
*/
declare function renamePluginsInConfigs<T extends Linter.Config = Linter.Config>(configs: T[], map: Record<string, string>, options?: RenamePluginsInConfigsOptions): T[];
/**
* Alias to `Linter.Config`
*
* @deprecated
*/
interface FlatConfigItem extends Linter.Config {
}
/**
* A type that can be awaited. Promise<T> or T.
*/
type Awaitable<T> = T | Promise<T>;
/**
* A type that can be an array or a single item.
*/
type Arrayable<T> = T | T[];
/**
* Default config names map. Used for type augmentation.
*
* @example
* ```ts
* declare module 'eslint-flat-config-utils' {
* interface DefaultConfigNamesMap {
* 'my-custom-config': true
* }
* }
* ```
*/
interface DefaultConfigNamesMap {
}
interface Nothing {
}
/**
* type StringLiteralUnion<'foo'> = 'foo' | string
* This has auto completion whereas `'foo' | string` doesn't
* Adapted from https://github.com/microsoft/TypeScript/issues/29729
*/
type StringLiteralUnion<T extends U, U = string> = T | (U & Nothing);
type FilterType<T, F> = T extends F ? T : never;
type NullableObject<T> = {
[K in keyof T]?: T[K] | null | undefined;
};
type GetRuleRecordFromConfig<T> = T extends {
rules?: infer R;
} ? R : Linter.RulesRecord;
declare const DEFAULT_PLUGIN_CONFLICTS_ERROR = "Different instances of plugin \"{{pluginName}}\" found in multiple configs: {{configNames}}. It's likely you misconfigured the merge of these configs.";
interface DisableFixesOptions {
builtinRules?: Map<string, Rule.RuleModule> | (() => Awaitable<Map<string, Rule.RuleModule>>);
}
type PluginConflictsError<T extends Linter.Config = Linter.Config> = (pluginName: string, configs: T[]) => string;
/**
* Awaitable array of ESLint flat configs or a composer object.
*/
type ResolvableFlatConfig<T extends object = ConfigWithExtends> = Awaitable<Arrayable<(T | false | undefined | null)>> | Awaitable<(ConfigWithExtends | false | undefined | null)[]> | Awaitable<(Linter.Config | false | undefined | null)[]> | FlatConfigComposer<any>;
/**
* Create a chainable composer object that makes manipulating ESLint flat config easier.
*
* It extends Promise, so that you can directly await or export it to `eslint.config.mjs`
*
* ```ts
* // eslint.config.mjs
* import { composer } from 'eslint-flat-config-utils'
*
* export default composer(
* {
* plugins: {},
* rules: {},
* }
* // ...some configs, accepts same arguments as `concat`
* )
* .append(
* // appends more configs at the end, accepts same arguments as `concat`
* )
* .prepend(
* // prepends more configs at the beginning, accepts same arguments as `concat`
* )
* .insertAfter(
* 'config-name', // specify the name of the target config, or index
* // insert more configs after the target, accepts same arguments as `concat`
* )
* .renamePlugins({
* // rename plugins
* 'old-name': 'new-name',
* // for example, rename `n` from `eslint-plugin-n` to more a explicit prefix `node`
* 'n': 'node'
* // applies to all plugins and rules in the configs
* })
* .override(
* 'config-name', // specify the name of the target config, or index
* {
* // merge with the target config
* rules: {
* 'no-console': 'off'
* },
* }
* )
*
* // And you an directly return the composer object to `eslint.config.mjs`
* ```
*/
declare function composer<T extends ConfigWithExtends = ConfigWithExtends, ConfigNames extends string = keyof DefaultConfigNamesMap>(...configs: ResolvableFlatConfig<ConfigWithExtends extends T ? T : ConfigWithExtends>[]): FlatConfigComposer<ConfigWithExtends extends T ? T : ConfigWithExtends, ConfigNames>;
/**
* The underlying impolementation of `composer()`.
*/
declare class FlatConfigComposer<T extends object = ConfigWithExtends, ConfigNames extends string = keyof DefaultConfigNamesMap> extends Promise<Linter.Config[]> {
private _operations;
private _operationsOverrides;
private _operationsResolved;
private _renames;
private _renamesOptions;
private _pluginsConflictsError;
constructor(...configs: ResolvableFlatConfig<T>[]);
/**
* Set plugin renames, like `n` -> `node`, `import-x` -> `import`, etc.
*
* This will runs after all config items are resolved. Applies to `plugins` and `rules`.
*/
renamePlugins(renames: Record<string, string>, options?: RenamePluginsInConfigsOptions): this;
/**
* Append configs to the end of the current configs array.
*/
append(...items: ResolvableFlatConfig<T>[]): this;
/**
* Prepend configs to the beginning of the current configs array.
*/
prepend(...items: ResolvableFlatConfig<T>[]): this;
/**
* Insert configs before a specific config.
*/
insertBefore(nameOrIndex: StringLiteralUnion<ConfigNames, string | number>, ...items: ResolvableFlatConfig<T>[]): this;
/**
* Insert configs after a specific config.
*/
insertAfter(nameOrIndex: StringLiteralUnion<ConfigNames, string | number>, ...items: ResolvableFlatConfig<T>[]): this;
/**
* Provide overrides to a specific config.
*
* It will be merged with the original config, or provide a custom function to replace the config entirely.
*/
override(nameOrIndex: StringLiteralUnion<ConfigNames, string | number>, config: T | ((config: T) => Awaitable<T>)): this;
/**
* Provide overrides to multiple configs as an object map.
*
* Same as calling `override` multiple times.
*/
overrides(overrides: Partial<Record<StringLiteralUnion<ConfigNames, string | number>, T | ((config: T) => Awaitable<T>)>>): this;
/**
* Override rules and it's options in **all configs**.
*
* Pass `null` as the value to remove the rule.
*
* @example
* ```ts
* composer
* .overrideRules({
* 'no-console': 'off',
* 'no-unused-vars': ['error', { vars: 'all', args: 'after-used' }],
* // remove the rule from all configs
* 'no-undef': null,
* })
* ```
*/
overrideRules(rules: NullableObject<GetRuleRecordFromConfig<T>>): this;
/**
* Remove rules from **all configs**.
*
* @example
* ```ts
* composer
* .removeRules(
* 'no-console',
* 'no-unused-vars'
* )
* ```
*/
removeRules(...rules: StringLiteralUnion<FilterType<keyof GetRuleRecordFromConfig<T>, string>, string>[]): this;
/**
* Remove plugins by name and all the rules referenced by them.
*
* @example
* ```ts
* composer
* .removePlugins(
* 'node'
* )
* ```
*
* The `plugins: { node }` and `rules: { 'node/xxx': 'error' }` will be removed from all configs.
*/
removePlugins(...names: string[]): this;
/**
* Remove a specific config by name or index.
*/
remove(nameOrIndex: StringLiteralUnion<ConfigNames, string | number>): this;
/**
* Replace a plugin with another.
*
* @example
* ```ts
* composer
* .replacePlugin('foo', (fooPlugin) => ({
* ...fooPlugin,
* rules: {
* ...fooPlugin.rules,
* someNewRule,
* },
* }))
* ```
*
* The `plugins: { foo }` will be replaced from all configs with a new plugin that is a merge of it and the `bar` plugin
*/
replacePlugin(name: string, replacement: Awaitable<Plugin> | ((original: Plugin) => Awaitable<Plugin>)): this;
/**
* Replace a specific config by name or index.
*
* The original config will be removed and replaced with the new one.
*/
replace(nameOrIndex: StringLiteralUnion<ConfigNames, string | number>, ...items: ResolvableFlatConfig<T>[]): this;
/**
* Hijack into plugins to disable fixes for specific rules.
*
* Note this mutates the plugin object, use with caution.
*
* @example
* ```ts
* const config = await composer(...)
* .disableRulesFix([
* 'unused-imports/no-unused-imports',
* 'vitest/no-only-tests'
* ])
* ```
*/
disableRulesFix(ruleIds: string[], options?: DisableFixesOptions): this;
/**
* Set a custom warning message for plugins conflicts.
*
* The error message can be a string or a function that returns a string.
*
* Error message accepts template strings:
* - `{{pluginName}}`: the name of the plugin that has conflicts
* - `{{configName1}}`: the name of the first config that uses the plugin
* - `{{configName2}}`: the name of the second config that uses the plugin
* - `{{configNames}}`: a list of config names that uses the plugin
*
* When only one argument is provided, it will be used as the default error message.
*/
setPluginConflictsError(warning?: string | PluginConflictsError): this;
setPluginConflictsError(pluginName: string, warning: string | PluginConflictsError): this;
private _verifyPluginsConflicts;
/**
* Hook when all configs are resolved but before returning the final configs.
*
* You can modify the final configs here.
*/
onResolved(callback: (configs: T[]) => Awaitable<T[] | void>): this;
/**
* Clone the composer object.
*/
clone(): FlatConfigComposer<T>;
/**
* Resolve the pipeline and return the final configs.
*
* This returns a promise. Calling `.then()` has the same effect.
*/
toConfigs(): Promise<Linter.Config[]>;
then<T>(onFulfilled: (value: Linter.Config[]) => T, onRejected?: (reason: any) => any): Promise<Awaited<T>>;
catch(onRejected: (reason: any) => any): Promise<any>;
finally(onFinally: () => void): Promise<Linter.Config[]>;
}
/**
* @deprecated Renamed to `composer`.
*/
declare const pipe: typeof composer;
/**
* @deprecated Renamed to `FlatConfigComposer`.
*/
declare class FlatConfigPipeline<T extends object = Linter.Config, ConfigNames extends string = string> extends FlatConfigComposer<T, ConfigNames> {
}
/**
* Concat multiple flat configs into a single flat config array.
*
* It also resolves promises and flattens the result.
*
* @example
*
* ```ts
* import { concat } from 'eslint-flat-config-utils'
* import eslint from '@eslint/js'
* import stylistic from '@stylistic/eslint-plugin'
*
* export default concat(
* eslint,
* stylistic.configs.customize(),
* { rules: { 'no-console': 'off' } },
* // ...
* )
* ```
*/
declare function concat<T extends Linter.Config = Linter.Config>(...configs: Awaitable<T | T[]>[]): Promise<T[]>;
/**
* A function that returns the config as-is, useful for providing type hints.
*/
declare function defineFlatConfig<T extends Linter.Config = Linter.Config>(config: T): T;
/**
* Extend another flat configs and rename globs paths.
*
* @example
* ```ts
* import { extend } from 'eslint-flat-config-utils'
*
* export default [
* ...await extend(
* // configs to extend
* import('./other-configs/eslint.config.js').then(m => m.default),
* // relative directory path
* 'other-configs/',
* ),
* ]
* ```
*/
declare function extend(configs: Awaitable<Linter.Config[]>, relativePath: string): Promise<Linter.Config[]>;
/**
* Replace a rule in a plugin with given factory.
*/
declare function hijackPluginRule(plugin: ESLint.Plugin, name: string, factory: (rule: Rule.RuleModule) => Rule.RuleModule): ESLint.Plugin;
/**
* Hijack into a rule's `context.report` to disable fixes.
*/
declare function disableRuleFixes(rule: Rule.RuleModule): Rule.RuleModule;
/**
* Merge multiple flat configs into a single flat config.
*
* Note there is no guarantee that the result works the same as the original configs.
*/
declare function mergeConfigs<T extends Linter.Config = Linter.Config>(...configs: T[]): T;
/**
* Merge two or more plugins into one.
*
* Note there is no guarantee that the result works the same as the original plugins.
*
* Limitations:
* - The resulting plugin will not have a `configs` property.
* - In the case of conflicts, the last plugin will be used.
*/
declare function mergePlugins(...plugins: readonly [Plugin, ...Plugin[]]): Plugin;
declare function mergePlugins(...plugins: readonly Plugin[]): Plugin | undefined;
declare function parseRuleId(ruleId: string): {
plugin: string | null;
rule: string;
};
export { DEFAULT_PLUGIN_CONFLICTS_ERROR, FlatConfigComposer, FlatConfigPipeline, composer, concat, defineFlatConfig, disableRuleFixes, extend, hijackPluginRule, mergeConfigs, mergePlugins, parseRuleId, pipe, renamePluginsInConfigs, renamePluginsInRules };
export type { Arrayable, Awaitable, DefaultConfigNamesMap, DisableFixesOptions, FilterType, FlatConfigItem, GetRuleRecordFromConfig, NullableObject, PluginConflictsError, RenamePluginsInConfigsOptions, ResolvableFlatConfig, StringLiteralUnion };
+622
View File
@@ -0,0 +1,622 @@
import { defineConfig } from '@eslint/config-helpers';
function hijackPluginRule(plugin, name, factory) {
const original = plugin.rules?.[name];
if (!original) {
throw new Error(`Rule "${name}" not found in plugin "${plugin.meta?.name || plugin.name}"`);
}
const patched = factory(original);
if (patched !== plugin.rules[name])
plugin.rules[name] = patched;
return plugin;
}
const disabledRuleFixes = /* @__PURE__ */ new WeakSet();
function disableRuleFixes(rule) {
if (disabledRuleFixes.has(rule)) {
return rule;
}
const originalCreate = rule.create.bind(rule);
rule.create = (context) => {
const clonedContext = { ...context };
const proxiedContext = new Proxy(clonedContext, {
get(target, prop, receiver) {
if (prop === "report") {
return function(report) {
if (report.fix) {
delete report.fix;
}
return Reflect.get(context, prop, receiver)({
...report,
fix: void 0
});
};
}
return Reflect.get(context, prop, receiver);
},
set(target, prop, value, receiver) {
return Reflect.set(context, prop, value, receiver);
}
});
const proxy = originalCreate(proxiedContext);
return proxy;
};
disabledRuleFixes.add(rule);
return rule;
}
function mergeConfigs(...configs) {
const keys = new Set(configs.flatMap((i) => Object.keys(i)));
const merged = configs.reduce((acc, cur) => {
return {
...acc,
...cur,
files: [
...acc.files || [],
...cur.files || []
],
ignores: [
...acc.ignores || [],
...cur.ignores || []
],
plugins: {
...acc.plugins,
...cur.plugins
},
rules: {
...acc.rules,
...cur.rules
},
languageOptions: {
...acc.languageOptions,
...cur.languageOptions
},
linterOptions: {
...acc.linterOptions,
...cur.linterOptions
}
};
}, {});
for (const key of Object.keys(merged)) {
if (!keys.has(key))
delete merged[key];
}
return merged;
}
function mergePlugins(...plugins) {
const uniquePlugins = [...new Set(plugins)];
if (uniquePlugins.length <= 1)
return uniquePlugins[0];
const shallowMergeInto = (a, b) => Object.assign(a, b);
const mergedPlugin = {
meta: {
name: `merged plugin of [${uniquePlugins.map((p) => p.meta?.name ?? p.name ?? "unnamed").join(", ")}]`
}
};
const environments = new Set(uniquePlugins.map((p) => p?.environments).filter((a) => a !== void 0));
if (environments.size > 0) {
mergedPlugin.environments = [...environments].reduce(shallowMergeInto, {});
}
const languages = new Set(uniquePlugins.map((p) => p?.languages).filter((a) => a !== void 0));
if (languages.size > 0) {
mergedPlugin.languages = [...languages].reduce(shallowMergeInto, {});
}
const processors = new Set(uniquePlugins.map((p) => p?.processors).filter((a) => a !== void 0));
if (processors.size > 0) {
mergedPlugin.processors = [...processors].reduce(shallowMergeInto, {});
}
const rules = new Set(uniquePlugins.map((p) => p?.rules).filter((a) => a !== void 0));
if (rules.size > 0) {
mergedPlugin.rules = [...rules].reduce(shallowMergeInto, {});
}
return mergedPlugin;
}
function parseRuleId(ruleId) {
let plugin;
let rule = ruleId;
if (ruleId.includes("/")) {
if (ruleId.startsWith("@")) {
plugin = ruleId.slice(0, ruleId.lastIndexOf("/"));
} else {
plugin = ruleId.slice(0, ruleId.indexOf("/"));
}
rule = ruleId.slice(plugin.length + 1);
} else {
plugin = null;
rule = ruleId;
}
return {
plugin,
rule
};
}
function renamePluginsInRules(rules, map) {
const entries = Object.entries(map).sort(([a], [b]) => b.length - a.length);
return Object.fromEntries(
Object.entries(rules).map(([key, value]) => {
for (const [from, to] of entries) {
if (key.startsWith(`${from}/`))
return [to + key.slice(from.length), value];
}
return [key, value];
})
);
}
function renamePluginsInConfigs(configs, map, options) {
return configs.map((i) => {
const clone = { ...i };
if (clone.rules)
clone.rules = renamePluginsInRules(clone.rules, map);
if (clone.plugins) {
const renamed = Object.entries(clone.plugins).map(([key, value]) => {
if (key in map)
return [map[key], value];
return [key, value];
});
const grouped = Object.groupBy(renamed, (entry) => entry[0]);
const shouldMerge = options?.mergePlugins ?? false;
clone.plugins = Object.fromEntries(
Object.entries(grouped).map(([key, values]) => {
if (shouldMerge)
return [key, mergePlugins(...values.map((entry) => entry[1]))];
if (values.length > 1)
console.warn(`ESLintFlatConfigUtils: Trying to rename multiple plugins to the name "${key}", using the last one`);
return values.at(-1);
})
);
}
return clone;
});
}
const RE_PLUGIN_NAME = /\{\{pluginName\}\}/g;
const RE_CONFIG_NAME1 = /\{\{configName1\}\}/g;
const RE_CONFIG_NAME2 = /\{\{configName2\}\}/g;
const RE_CONFIG_NAMES = /\{\{configNames\}\}/g;
const DEFAULT_PLUGIN_CONFLICTS_ERROR = `Different instances of plugin "{{pluginName}}" found in multiple configs: {{configNames}}. It's likely you misconfigured the merge of these configs.`;
function composer(...configs) {
return new FlatConfigComposer(
...configs
);
}
class FlatConfigComposer extends Promise {
_operations = [];
_operationsOverrides = [];
_operationsResolved = [];
_renames = {};
_renamesOptions;
_pluginsConflictsError = /* @__PURE__ */ new Map();
constructor(...configs) {
super(() => {
});
if (configs.length)
this.append(...configs);
}
/**
* Set plugin renames, like `n` -> `node`, `import-x` -> `import`, etc.
*
* This will runs after all config items are resolved. Applies to `plugins` and `rules`.
*/
renamePlugins(renames, options) {
Object.assign(this._renames, renames);
this._renamesOptions = options;
return this;
}
/**
* Append configs to the end of the current configs array.
*/
append(...items) {
const promise = Promise.all(items);
this._operations.push(async (configs) => {
const resolved = (await promise).flat().filter(Boolean);
return [...configs, ...resolved];
});
return this;
}
/**
* Prepend configs to the beginning of the current configs array.
*/
prepend(...items) {
const promise = Promise.all(items);
this._operations.push(async (configs) => {
const resolved = (await promise).flat().filter(Boolean);
return [...resolved, ...configs];
});
return this;
}
/**
* Insert configs before a specific config.
*/
insertBefore(nameOrIndex, ...items) {
const promise = Promise.all(items);
this._operations.push(async (configs) => {
const resolved = (await promise).flat().filter(Boolean);
const index = getConfigIndex(configs, nameOrIndex);
configs.splice(index, 0, ...resolved);
return configs;
});
return this;
}
/**
* Insert configs after a specific config.
*/
insertAfter(nameOrIndex, ...items) {
const promise = Promise.all(items);
this._operations.push(async (configs) => {
const resolved = (await promise).flat().filter(Boolean);
const index = getConfigIndex(configs, nameOrIndex);
configs.splice(index + 1, 0, ...resolved);
return configs;
});
return this;
}
/**
* Provide overrides to a specific config.
*
* It will be merged with the original config, or provide a custom function to replace the config entirely.
*/
override(nameOrIndex, config) {
this._operationsOverrides.push(async (configs) => {
const index = getConfigIndex(configs, nameOrIndex);
const extended = typeof config === "function" ? await config(configs[index]) : mergeConfigs(configs[index], config);
configs.splice(index, 1, extended);
return configs;
});
return this;
}
/**
* Provide overrides to multiple configs as an object map.
*
* Same as calling `override` multiple times.
*/
overrides(overrides) {
for (const [name, config] of Object.entries(overrides)) {
if (config)
this.override(name, config);
}
return this;
}
/**
* Override rules and it's options in **all configs**.
*
* Pass `null` as the value to remove the rule.
*
* @example
* ```ts
* composer
* .overrideRules({
* 'no-console': 'off',
* 'no-unused-vars': ['error', { vars: 'all', args: 'after-used' }],
* // remove the rule from all configs
* 'no-undef': null,
* })
* ```
*/
overrideRules(rules) {
this._operationsOverrides.push(async (configs) => {
for (const config of configs) {
if (!("rules" in config) || !config.rules)
continue;
const configRules = config.rules;
for (const [key, value] of Object.entries(rules)) {
if (!(key in configRules))
continue;
if (value == null)
delete configRules[key];
else
configRules[key] = value;
}
}
return configs;
});
return this;
}
/**
* Remove rules from **all configs**.
*
* @example
* ```ts
* composer
* .removeRules(
* 'no-console',
* 'no-unused-vars'
* )
* ```
*/
removeRules(...rules) {
return this.overrideRules(Object.fromEntries(
rules.map((rule) => [rule, null])
));
}
/**
* Remove plugins by name and all the rules referenced by them.
*
* @example
* ```ts
* composer
* .removePlugins(
* 'node'
* )
* ```
*
* The `plugins: { node }` and `rules: { 'node/xxx': 'error' }` will be removed from all configs.
*/
removePlugins(...names) {
this._operationsOverrides.push(async (configs) => {
for (const config of configs) {
if ("plugins" in config && typeof config.plugins === "object" && config.plugins) {
for (const name of names) {
if (name in config.plugins)
delete config.plugins[name];
}
}
if ("rules" in config && typeof config.rules === "object" && config.rules) {
for (const key of Object.keys(config.rules)) {
if (names.some((n) => key.startsWith(`${n}/`)))
delete config.rules[key];
}
}
}
return configs;
});
return this;
}
/**
* Remove a specific config by name or index.
*/
remove(nameOrIndex) {
this._operations.push(async (configs) => {
const index = getConfigIndex(configs, nameOrIndex);
configs.splice(index, 1);
return configs;
});
return this;
}
/**
* Replace a plugin with another.
*
* @example
* ```ts
* composer
* .replacePlugin('foo', (fooPlugin) => ({
* ...fooPlugin,
* rules: {
* ...fooPlugin.rules,
* someNewRule,
* },
* }))
* ```
*
* The `plugins: { foo }` will be replaced from all configs with a new plugin that is a merge of it and the `bar` plugin
*/
replacePlugin(name, replacement) {
this._operationsOverrides.push(async (configs) => {
for (const config of configs) {
if ("plugins" in config && typeof config.plugins === "object" && config.plugins) {
if (name in config.plugins) {
const value = typeof replacement === "function" ? replacement(config.plugins[name]) : replacement;
config.plugins[name] = await value;
}
}
}
return configs;
});
return this;
}
/**
* Replace a specific config by name or index.
*
* The original config will be removed and replaced with the new one.
*/
replace(nameOrIndex, ...items) {
const promise = Promise.all(items);
this._operations.push(async (configs) => {
const resolved = (await promise).flat().filter(Boolean);
const index = getConfigIndex(configs, nameOrIndex);
configs.splice(index, 1, ...resolved);
return configs;
});
return this;
}
/**
* Hijack into plugins to disable fixes for specific rules.
*
* Note this mutates the plugin object, use with caution.
*
* @example
* ```ts
* const config = await composer(...)
* .disableRulesFix([
* 'unused-imports/no-unused-imports',
* 'vitest/no-only-tests'
* ])
* ```
*/
disableRulesFix(ruleIds, options = {}) {
this._operations.push(async (configs) => {
for (const name of ruleIds) {
const parsed = parseRuleId(name);
if (!parsed.plugin) {
if (!options.builtinRules)
throw new Error(`Patching core rule "${name}" require pass \`{ builtinRules: () => import('eslint/use-at-your-own-risk').then(r => r.builtinRules) }\` in the options`);
const builtinRules = typeof options.builtinRules === "function" ? await options.builtinRules() : options.builtinRules;
const rule = builtinRules.get(name);
if (!rule)
throw new Error(`Rule "${name}" not found in core rules`);
disableRuleFixes(rule);
} else {
const plugins = new Set(configs.map((c) => c.plugins?.[parsed.plugin]).filter((x) => !!x));
for (const plugin of plugins) {
hijackPluginRule(plugin, parsed.rule, (rule) => disableRuleFixes(rule));
}
}
}
return configs;
});
return this;
}
setPluginConflictsError(arg1 = DEFAULT_PLUGIN_CONFLICTS_ERROR, arg2) {
if (arg2 != null)
this._pluginsConflictsError.set(arg1, arg2);
else
this._pluginsConflictsError.set("*", arg1);
return this;
}
_verifyPluginsConflicts(configs) {
if (!this._pluginsConflictsError.size)
return;
const plugins = /* @__PURE__ */ new Map();
const names = /* @__PURE__ */ new Set();
for (const config of configs) {
if (!config.plugins)
continue;
for (const [name, plugin] of Object.entries(config.plugins)) {
names.add(name);
if (!plugins.has(plugin))
plugins.set(plugin, { name, configs: [] });
plugins.get(plugin).configs.push(config);
}
}
function getConfigName(config) {
return config.name || `#${configs.indexOf(config)}`;
}
const errors = [];
for (const name of names) {
const instancesOfName = [...plugins.values()].filter((p) => p.name === name);
if (instancesOfName.length <= 1)
continue;
const configsOfName = instancesOfName.map((p) => p.configs[0]);
const message = this._pluginsConflictsError.get(name) || this._pluginsConflictsError.get("*");
if (typeof message === "function") {
errors.push(message(name, configsOfName));
} else if (message) {
errors.push(
message.replace(RE_PLUGIN_NAME, name).replace(RE_CONFIG_NAME1, getConfigName(configsOfName[0])).replace(RE_CONFIG_NAME2, getConfigName(configsOfName[1])).replace(RE_CONFIG_NAMES, configsOfName.map(getConfigName).join(", "))
);
}
}
if (errors.length) {
if (errors.length === 1)
throw new Error(`ESLintFlatConfigUtils: ${errors[0]}`);
else
throw new Error(`ESLintFlatConfigUtils:
${errors.map((e, i) => ` ${i + 1}: ${e}`).join("\n")}`);
}
}
/**
* Hook when all configs are resolved but before returning the final configs.
*
* You can modify the final configs here.
*/
onResolved(callback) {
this._operationsResolved.push(callback);
return this;
}
/**
* Clone the composer object.
*/
clone() {
const composer2 = new FlatConfigComposer();
composer2._operations = this._operations.slice();
composer2._operationsOverrides = this._operationsOverrides.slice();
composer2._operationsResolved = this._operationsResolved.slice();
composer2._renames = { ...this._renames };
composer2._renamesOptions = this._renamesOptions;
composer2._pluginsConflictsError = new Map(this._pluginsConflictsError);
return composer2;
}
/**
* Resolve the pipeline and return the final configs.
*
* This returns a promise. Calling `.then()` has the same effect.
*/
async toConfigs() {
let configs = [];
for (const promise of this._operations)
configs = await promise(configs);
for (const promise of this._operationsOverrides)
configs = await promise(configs);
configs = renamePluginsInConfigs(configs, this._renames, this._renamesOptions);
for (const promise of this._operationsResolved)
configs = await promise(configs) || configs;
const resolved = defineConfig(configs);
this._verifyPluginsConflicts(resolved);
return resolved;
}
then(onFulfilled, onRejected) {
return this.toConfigs().then(onFulfilled, onRejected);
}
// eslint-disable-next-line ts/explicit-function-return-type
catch(onRejected) {
return this.toConfigs().catch(onRejected);
}
finally(onFinally) {
return this.toConfigs().finally(onFinally);
}
}
function getConfigIndex(configs, nameOrIndex) {
if (typeof nameOrIndex === "number") {
if (nameOrIndex < 0 || nameOrIndex >= configs.length)
throw new Error(`ESLintFlatConfigUtils: Failed to locate config at index ${nameOrIndex}
(${configs.length} configs in total)`);
return nameOrIndex;
} else {
const index = configs.findIndex((config) => config.name === nameOrIndex);
if (index === -1) {
const named = configs.map((config) => config.name).filter(Boolean);
const countUnnamed = configs.length - named.length;
const messages = [
`Failed to locate config with name "${nameOrIndex}"`,
`Available names are: ${named.join(", ")}`,
countUnnamed ? `(${countUnnamed} unnamed configs)` : ""
].filter(Boolean).join("\n");
throw new Error(`ESLintFlatConfigUtils: ${messages}`);
}
return index;
}
}
const pipe = composer;
class FlatConfigPipeline extends FlatConfigComposer {
}
async function concat(...configs) {
const resolved = await Promise.all(configs);
return resolved.flat();
}
function defineFlatConfig(config) {
return config;
}
async function extend(configs, relativePath) {
const { join } = await import('pathe');
const resolved = await configs;
if (relativePath === "")
return resolved;
function renameGlobs(i) {
if (typeof i !== "string")
return i;
if (i.startsWith("!"))
return `!${join(relativePath, i.slice(1))}`;
return join(relativePath, i);
}
return resolved.map((i) => {
if (!i || !i.files && !i.ignores)
return i;
const clone = { ...i };
if (clone.files) {
clone.files = clone.files.map(
(f) => Array.isArray(f) ? f.map((t) => renameGlobs(t)) : renameGlobs(f)
);
}
if (clone.ignores) {
clone.ignores = clone.ignores.map(
(f) => renameGlobs(f)
);
}
return clone;
});
}
export { DEFAULT_PLUGIN_CONFLICTS_ERROR, FlatConfigComposer, FlatConfigPipeline, composer, concat, defineFlatConfig, disableRuleFixes, extend, hijackPluginRule, mergeConfigs, mergePlugins, parseRuleId, pipe, renamePluginsInConfigs, renamePluginsInRules };