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

fix: implement fixes to work with eslint 8.57.0 #228

Merged
merged 4 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
fail-fast: false
matrix:
node-version: [12.x, 14.x, 16.x, 18.x, 19.x, 20.x]
eslint-version: [7, "8.40", 8]
eslint-version: [7, "8.40", "8.55", 8]
jest-version: [27, 28, 29]
jest-watch-typeahead-version: [1, 2]
exclude:
Expand Down
92 changes: 30 additions & 62 deletions src/runner/runESLint.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,25 @@
const { ESLint } = require('eslint');
const eslint = require('eslint');
const getESLintOptions = require('../utils/getESLintOptions');

let FlatESLint;
cprussin marked this conversation as resolved.
Show resolved Hide resolved
let shouldUseFlatConfig;
const { ESLint } = eslint;
let { loadESLint } = eslint;

try {
// Use a dynamic require here rather than a global require because this
// import path does not exist in eslint v7 which this library still
// supports
//
// ESlint exposes the new FlatESLint API under `eslint/use-at-your-own-risk` by
// using it's [export configuration](https://tinyurl.com/2s45zh9b). However,
// the `import/no-unresolved` rule is [not aware of
// `exports`](https://tinyurl.com/469djpx3) and causes a false error here. So,
// let's ignore that rule for this import.
//
// eslint-disable-next-line global-require, import/no-unresolved
const eslintExperimental = require('eslint/use-at-your-own-risk');
FlatESLint = eslintExperimental.FlatESLint;
shouldUseFlatConfig = eslintExperimental.shouldUseFlatConfig;
} catch {
/* no-op */
}

if (shouldUseFlatConfig === undefined) {
shouldUseFlatConfig = () => Promise.resolve(false);
// loadESLint and ESLint.configType were added in eslint v8.57.0. The block
// below is some compat code to make this library work with flat config and
// versions of eslint prior to 8.57.0.
if (!loadESLint) {
try {
const {
FlatESLint,
shouldUseFlatConfig,
// eslint-disable-next-line global-require, import/no-unresolved
} = require('eslint/use-at-your-own-risk');
FlatESLint.configType = 'flat';
loadESLint = async () =>
(await shouldUseFlatConfig?.()) ? FlatESLint : ESLint;
} catch {
/* no-op */
}
}

/*
Expand Down Expand Up @@ -131,53 +126,26 @@ function removeUndefinedFromObject(object) {
);
}

const getESLintConstructor = async () => {
if (await shouldUseFlatConfig()) {
return FlatESLint;
}

return ESLint;
};

// Remove options that are not constructor args.
const getESLintConstructorArgs = async cliOptions => {
// these are not constructor args for either the legacy or the flat ESLint
// api
const { fixDryRun, format, maxWarnings, quiet, ...legacyConstructorArgs } =
cliOptions;

if (await shouldUseFlatConfig()) {
// these options are supported by the legacy ESLint api but aren't
// supported by the ESLintFlat api
const {
extensions,
ignorePath,
rulePaths,
resolvePluginsRelativeTo,
useEslintrc,
overrideConfig,
...flatConstructorArgs
} = legacyConstructorArgs;
return flatConstructorArgs;
}

return legacyConstructorArgs;
};

let cachedValues;
const getCachedValues = async (config, extraOptions) => {
if (!cachedValues) {
const { cliOptions: baseCliOptions } = getESLintOptions(config);
const ESLintConstructor = (await loadESLint?.()) ?? ESLint;

const { cliOptions: baseCliOptions } = getESLintOptions(
ESLintConstructor.configType,
config,
);
const cliOptions = {
...baseCliOptions,
fix: getComputedFixValue(baseCliOptions),
...removeUndefinedFromObject(extraOptions),
};

const ESLintConstructor = await getESLintConstructor();
const cli = new ESLintConstructor(
await getESLintConstructorArgs(cliOptions),
);
// Remove options that are not constructor args.
const { fixDryRun, format, maxWarnings, quiet, ...constructorArgs } =
cliOptions;

const cli = new ESLintConstructor(constructorArgs);

cachedValues = {
isPathIgnored: cli.isPathIgnored.bind(cli),
Expand Down
4 changes: 2 additions & 2 deletions src/utils/__tests__/normalizeConfig.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const normalizeConfig = require('../normalizeConfig');

const normalizeCLIOptions = cliOptions =>
normalizeConfig({ cliOptions }).cliOptions;
const normalizeCLIOptions = (cliOptions, configType) =>
normalizeConfig(configType, { cliOptions }).cliOptions;

it('ignores unknown options', () => {
expect(normalizeCLIOptions({ other: true })).not.toMatchObject({
Expand Down
6 changes: 3 additions & 3 deletions src/utils/getESLintOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ const normalizeConfig = require('./normalizeConfig');

const explorer = cosmiconfigSync('jest-runner-eslint');

const getESLintOptions = config => {
const getESLintOptions = (configType, config) => {
const result = explorer.search(config.rootDir);

if (result) {
return normalizeConfig(result.config);
return normalizeConfig(configType, result.config);
}

return normalizeConfig({});
return normalizeConfig(configType, {});
};

module.exports = getESLintOptions;
63 changes: 38 additions & 25 deletions src/utils/normalizeConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,6 @@ const BASE_CONFIG = {
cacheLocation: {
default: '.eslintcache',
},
ext: {
name: 'extensions',
default: ['.js'],
transform: asArray,
},
fix: {
default: false,
},
Expand All @@ -35,18 +30,10 @@ const BASE_CONFIG = {
format: {
default: undefined,
},
ignorePath: {
default: null,
},
maxWarnings: {
default: -1,
transform: asInt,
},
noEslintrc: {
name: 'useEslintrc',
default: false,
transform: negate,
},
noIgnore: {
name: 'ignore',
default: false,
Expand All @@ -60,16 +47,36 @@ const BASE_CONFIG = {
quiet: {
default: false,
},
resolvePluginsRelativeTo: {
default: undefined,
config: {
name: 'overrideConfigFile',
default: null,
},
};

const LEGACY_CONFIG = {
...BASE_CONFIG,
ext: {
name: 'extensions',
default: ['.js'],
transform: asArray,
},
ignorePath: {
default: null,
},
rulesdir: {
name: 'rulePaths',
default: [],
transform: asArray,
},
config: {
name: 'overrideConfigFile',
resolvePluginsRelativeTo: {
default: undefined,
},
noEslintrc: {
name: 'useEslintrc',
default: false,
transform: negate,
},
reportUnusedDisableDirectives: {
default: null,
},
env: {
Expand Down Expand Up @@ -98,22 +105,28 @@ const BASE_CONFIG = {
default: [],
transform: asArray,
},
reportUnusedDisableDirectives: {
default: null,
},
rules: {
name: 'overrideConfig.rules',
default: {},
},
};

const normalizeCliOptions = rawConfig => {
return Object.keys(BASE_CONFIG).reduce((config, key) => {
const FLAT_CONFIG = {
...BASE_CONFIG,
reportUnusedDisableDirectives: {
name: 'overrideConfig.linterOptions.reportUnusedDisableDirectives',
default: false,
},
};

const normalizeCliOptions = (configType, rawConfig) => {
const configConfig = configType === 'flat' ? FLAT_CONFIG : LEGACY_CONFIG;
return Object.keys(configConfig).reduce((config, key) => {
const {
name = key,
transform = identity,
default: defaultValue,
} = BASE_CONFIG[key];
} = configConfig[key];

const value = rawConfig[key] !== undefined ? rawConfig[key] : defaultValue;

Expand All @@ -123,10 +136,10 @@ const normalizeCliOptions = rawConfig => {
}, {});
};

const normalizeConfig = config => {
const normalizeConfig = (configType, config) => {
return {
...config,
cliOptions: normalizeCliOptions(config.cliOptions || {}),
cliOptions: normalizeCliOptions(configType, config.cliOptions || {}),
};
};

Expand Down