Skip to content

Commit

Permalink
Improve support for flat config in eslint plugin (resolves #806)
Browse files Browse the repository at this point in the history
  • Loading branch information
webpro committed Oct 28, 2024
1 parent 366acdc commit c6d5c10
Show file tree
Hide file tree
Showing 9 changed files with 59 additions and 23 deletions.

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

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

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

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

39 changes: 30 additions & 9 deletions packages/knip/src/plugins/eslint/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,33 @@ import type { PluginOptions } from '../../types/config.js';
import { compact } from '../../util/array.js';
import { type ConfigInput, type Input, toConfig, toDeferResolve } from '../../util/input.js';
import { getPackageNameFromFilePath, getPackageNameFromModuleSpecifier } from '../../util/modules.js';
import { isAbsolute, isInternal } from '../../util/path.js';
import { extname, isAbsolute, isInternal } from '../../util/path.js';
import { getDependenciesFromConfig } from '../babel/index.js';
import type { ESLintConfig, OverrideConfig } from './types.js';
import type { ESLintConfig, ESLintConfigDeprecated, OverrideConfigDeprecated } from './types.js';

export const getDependencies = (
config: ESLintConfig | OverrideConfig,
export const getInputs = (
config: ESLintConfigDeprecated | OverrideConfigDeprecated | ESLintConfig,
options: PluginOptions
): (Input | ConfigInput)[] => {
const { configFileName } = options;

if (extname(configFileName) === '.json' || !/eslint\.config/.test(configFileName)) {
return getInputsDeprecated(config as ESLintConfigDeprecated | OverrideConfigDeprecated, options);
}

const inputs = (config as ESLintConfig).flatMap(config => {
const settings = config.settings
? getDependenciesFromSettings(config.settings).filter(id => id !== '@typescript-eslint/parser')
: [];

return compact([...settings]).map(d => toDeferResolve(d));
});

return inputs;
};

const getInputsDeprecated = (
config: ESLintConfigDeprecated | OverrideConfigDeprecated,
options: PluginOptions
): (Input | ConfigInput)[] => {
const extendsSpecifiers = config.extends ? compact([config.extends].flat().map(resolveExtendSpecifier)) : [];
Expand All @@ -23,9 +44,9 @@ export const getDependencies = (
const settings = config.settings ? getDependenciesFromSettings(config.settings) : [];
// const rules = getDependenciesFromRules(config.rules); // TODO enable in next major? Unexpected/breaking in certain cases w/ eslint v8
const rules = getDependenciesFromRules({});
const overrides = config.overrides ? [config.overrides].flat().flatMap(d => getDependencies(d, options)) : [];
const x = compact([...extendsSpecifiers, ...plugins, parser, ...settings, ...rules]).map(toDeferResolve);
return [...extendConfigs, ...x, ...babelDependencies, ...overrides];
const overrides = config.overrides ? [config.overrides].flat().flatMap(d => getInputsDeprecated(d, options)) : [];
const deferred = compact([...extendsSpecifiers, ...plugins, parser, ...settings, ...rules]).map(toDeferResolve);
return [...extendConfigs, ...deferred, ...babelDependencies, ...overrides];
};

const isQualifiedSpecifier = (specifier: string) =>
Expand Down Expand Up @@ -57,12 +78,12 @@ const resolveExtendSpecifier = (specifier: string) => {
return resolveSpecifier(namespace, specifier);
};

const getDependenciesFromRules = (rules: ESLintConfig['rules'] = {}) =>
const getDependenciesFromRules = (rules: ESLintConfigDeprecated['rules'] = {}) =>
Object.keys(rules).flatMap(ruleKey =>
ruleKey.includes('/') ? [resolveSpecifier('eslint-plugin', ruleKey.split('/').slice(0, -1).join('/'))] : []
);

const getDependenciesFromSettings = (settings: ESLintConfig['settings'] = {}) => {
const getDependenciesFromSettings = (settings: ESLintConfigDeprecated['settings'] = {}) => {
return Object.entries(settings).flatMap(([settingKey, settings]) => {
if (settingKey === 'import/resolver') {
return (typeof settings === 'string' ? [settings] : Object.keys(settings))
Expand Down
17 changes: 10 additions & 7 deletions packages/knip/src/plugins/eslint/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { IsPluginEnabled, Plugin, ResolveConfig } from '../../types/config.js';
import { hasDependency } from '../../util/plugin.js';
import { getDependencies } from './helpers.js';
import type { ESLintConfig } from './types.js';
import { getInputs } from './helpers.js';
import type { ESLintConfigDeprecated } from './types.js';

// New: https://eslint.org/docs/latest/use/configure/configuration-files
// Old: https://eslint.org/docs/latest/use/configure/configuration-files-deprecated
Expand All @@ -20,18 +20,21 @@ const isEnabled: IsPluginEnabled = ({ dependencies, manifest, config }) =>

const packageJsonPath = 'eslintConfig';

const entry = ['eslint.config.{js,cjs,mjs}'];
const config = [
'eslint.config.{js,cjs,mjs,ts,mts,cts}',
'.eslintrc',
'.eslintrc.{js,json,cjs}',
'.eslintrc.{yml,yaml}',
'package.json',
];

const config = ['.eslintrc', '.eslintrc.{js,json,cjs}', '.eslintrc.{yml,yaml}', 'package.json'];

const resolveConfig: ResolveConfig<ESLintConfig> = (localConfig, options) => getDependencies(localConfig, options);
const resolveConfig: ResolveConfig<ESLintConfigDeprecated> = (localConfig, options) => getInputs(localConfig, options);

export default {
title,
enablers,
isEnabled,
packageJsonPath,
entry,
config,
resolveConfig,
} satisfies Plugin;
8 changes: 5 additions & 3 deletions packages/knip/src/plugins/eslint/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ type BaseConfig = {
rules?: Rules;
};

export type OverrideConfig = BaseConfig & { files: string[]; overrides: OverrideConfig };
export type ESLintConfig = BaseConfig[];

export type ESLintConfig = BaseConfig & {
export type OverrideConfigDeprecated = BaseConfig & { files: string[]; overrides: OverrideConfigDeprecated };

export type ESLintConfigDeprecated = BaseConfig & {
env?: Record<string, boolean>;
overrides?: OverrideConfig[];
overrides?: OverrideConfigDeprecated[];
};
4 changes: 2 additions & 2 deletions packages/knip/src/plugins/xo/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { IsPluginEnabled, Plugin, ResolveConfig } from '../../types/config.js';
import { hasDependency } from '../../util/plugin.js';
import { getDependencies } from '../eslint/helpers.js';
import { getInputs } from '../eslint/helpers.js';
import type { XOConfig } from './types.js';

// link to xo docs: https://github.com/xojs/xo#config
Expand All @@ -20,7 +20,7 @@ const config = ['package.json', '.xo-config', '.xo-config.{js,cjs,json}', 'xo.co
const entry: string[] = ['.xo-config.{js,cjs}', 'xo.config.{js,cjs}'];

const resolveConfig: ResolveConfig<XOConfig> = async (config, options) => {
const inputs = getDependencies(config, options);
const inputs = getInputs(config, options);
return [...inputs];
};

Expand Down
4 changes: 2 additions & 2 deletions packages/knip/src/plugins/xo/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ESLintConfig } from '../eslint/types.js';
import type { ESLintConfigDeprecated } from '../eslint/types.js';

export type XOConfig = ESLintConfig & {
export type XOConfig = ESLintConfigDeprecated & {
envs?: string[] | undefined;
globals?: string[] | undefined;
ignores?: string[] | undefined;
Expand Down

0 comments on commit c6d5c10

Please sign in to comment.