docs/src/use/migrate-to-9.0.0.md
ESLint v9.0.0 is a major release of ESLint, and as such, has several breaking changes that you need to be aware of. This guide is intended to walk you through the breaking changes.
To help with this migration, ESLint provides codemods to automate many of the changes described in this guide. All official ESLint codemods are available in the eslint/codemods repository and through the Codemod Registry.
The @eslint/v8-to-v9-config codemod migrates ESLint v8 configuration files to the ESLint v9 flat config format. It updates config structure, rule schemas, plugins, ignores, and deprecated JSDoc rules while preserving existing behavior as much as possible. It also modifies package.json entries containing eslintConfig, .eslintignore/.gitignore files, /* exported */ comments, and certain /* eslint */ comments.
npx codemod @eslint/v8-to-v9-config
Learn more in the Codemod Registry.
If you maintain custom ESLint rules, the @eslint/v8-to-v9-custom-rules codemod converts function-style rules to the object format and updates deprecated rule APIs to their ESLint v9-compatible equivalents.
::: warning
The workflow runs on **/*.js, **/*.mjs, **/*.cjs, **/*.jsx, **/*.ts, **/*.mts, **/*.cts, and **/*.tsx. Run it only against rule files or directories, since it may incorrectly transform non-rule JavaScript files.
:::
npx codemod @eslint/v8-to-v9-custom-rules
Learn more in the Codemod Registry.
Codemods are a starting point. Review the changes and consult the breaking changes below for anything the codemods do not cover.
The lists below are ordered roughly by the number of users each change is expected to affect, where the first items are expected to affect the most users.
eslint.config.js)require-jsdoc and valid-jsdoc ruleseslint:recommended has been updated--quiet no longer runs rules set to "warn"--output-file now writes a file to disk even with an empty output/* eslint */ comments with only severity now retain options from the config file/* eslint */ comments for the same rule are now disallowed/* exported */ parsingno-constructor-return and no-sequences rule schemas are stricterno-implicit-coercion by defaultno-invalid-regexpvarsIgnorePattern option of no-unused-vars no longer applies to catch argumentsno-restricted-imports now accepts multiple config entries with the same name"eslint:recommended" and "eslint:all" strings no longer accepted in flat configno-inner-declarations has a new default behavior with a new optionno-unused-vars now defaults caughtErrors to "all"no-useless-computed-key flags unnecessary computed member names in classes by defaultcamelcase allow option only accepts an array of stringscontext methodssourceCode.getComments()CodePath#currentSegmentsmeta.schema is required for rules with optionsFlatRuleTester is now RuleTesterRuleTester checksFlatESLint is now ESLintLinter now expects flat config formatESLint is officially dropping support for these versions of Node.js starting with ESLint v9.0.0. ESLint now supports the following versions of Node.js:
To address: Make sure you upgrade to at least Node.js v18.18.0 when using ESLint v9.0.0. One important thing to double check is the Node.js version supported by your editor when using ESLint via editor integrations. If you are unable to upgrade, we recommend continuing to use ESLint v8.56.0 until you are able to upgrade Node.js.
Related issue(s): #17595
eslint.config.js)As announced in our blog post, in ESLint v9.0.0, eslint.config.js is the new default configuration format. The previous format, eslintrc, is now deprecated and will not automatically be searched for.
Codemod: Use the @eslint/v8-to-v9-config codemod to automate much of this migration.
To address: Update your configuration manually to the new format following the Configuration Migration Guide. In case you still need to use the deprecated eslintrc config format, set environment variable ESLINT_USE_FLAT_CONFIG to false.
Related Issues(s): #13481
ESLint v9.0.0 has removed the following formatters from the core:
| Removed Formatter | Replacement npm Package |
|---|---|
checkstyle | eslint-formatter-checkstyle |
compact | eslint-formatter-compact |
jslint-xml | eslint-formatter-jslint-xml |
junit | eslint-formatter-junit |
tap | eslint-formatter-tap |
unix | eslint-formatter-unix |
visualstudio | eslint-formatter-visualstudio |
Codemod: The @eslint/v8-to-v9-config codemod fully supports this change.
To address: If you are using any of these formatters via the -f command line flag, you'll need to install the respective package for the formatter.
Related issue(s): #17524
require-jsdoc and valid-jsdoc rulesThe require-jsdoc and valid-jsdoc rules have been removed in ESLint v9.0.0. These rules were initially deprecated in 2018.
Codemod: The @eslint/v8-to-v9-config codemod migrates deprecated JSDoc rules. When those rules are active in the configuration, it removes require-jsdoc/valid-jsdoc, adds eslint-plugin-jsdoc and jsdoc({ config: 'flat/recommended' }), and also removes file-level JSDoc ESLint comments.
To address: Use the replacement rules in eslint-plugin-jsdoc for equivalent linting.
Related issue(s): #15820
eslint:recommended has been updatedFour new rules have been enabled in eslint:recommended:
no-constant-binary-expressionno-empty-static-blockno-new-native-nonconstructorno-unused-private-class-membersAdditionally, the following rules have been removed from eslint:recommended:
Codemod: The @eslint/v8-to-v9-config codemod partially covers this change. When your configuration extends eslint:recommended, it disables the four newly enabled recommended rules listed above (unless you already configure them explicitly) to preserve ESLint v8 lint behavior. It does not re-enable rules removed from the preset, fix new violations in source files, or change configs that use @eslint/js presets without a legacy eslint:recommended extend entry.
To address: Fix errors or disable these rules.
Related issue(s): #15576, #17446, #17596
--quiet no longer runs rules set to "warn"Prior to ESLint v9.0.0, the --quiet CLI flag would run all rules set to either "error" or "warn" and then hide the results from rules set to "warn". In ESLint v9.0.0, --quiet will prevent rules from being executed when set to "warn". This can result in a performance improvement for configurations containing many rules set to "warn".
If --max-warnings is used then --quiet will not suppress the execution of rules set to "warn" but the output of those rules will be suppressed.
To address: In most cases, this change is transparent. If, however, you are running a rule set to "warn" that makes changes to the data available to other rules (for example, if the rule uses sourceCode.markVariableAsUsed()), then this can result in a behavior change. In such a case, you'll need to either set the rule to "error" or stop using --quiet.
Related issue(s): #16450
--output-file now writes a file to disk even with an empty outputPrior to ESLint v9.0.0, the --output-file flag would skip writing a file to disk if the output was empty. However, in ESLint v9.0.0, --output-file now consistently writes a file to disk, even when the output is empty. This update ensures a more consistent and reliable behavior for --output-file.
To address: Review your usage of the --output-file flag, especially if your processes depend on the file's presence or absence based on output content. If necessary, update your scripts or configurations to accommodate this change.
Related Issues(s): #17660
Prior to ESLint v9.0.0, running the ESLint CLI without any file or directory patterns would result in no files being linted and would exit with code 0. This was confusing because it wasn't clear that nothing had actually happened. In ESLint v9.0.0, this behavior has been updated:
npx eslint or eslint (if globally installed) and ESLint will assume you want to lint the current directory. Effectively, passing no patterns is equivalent to passing ..To address: In most cases, no change is necessary, and you may find some locations where you thought ESLint was running but it wasn't. If you'd like to keep the v8.x behavior, where passing no patterns results in ESLint exiting with code 0, add the --pass-on-no-patterns flag to the CLI call.
Related issue(s): #14308
/* eslint */ comments with only severity now retain options from the config filePrior to ESLint v9.0.0, configuration comments such as /* eslint curly: "warn" */ or /* eslint curly: ["warn"] */ would completely override any configuration specified for the rule in the config file, and thus enforce the default options of the rule.
In ESLint v9.0.0, the behavior of configuration comments is aligned with how rule configurations in config files are merged, meaning that a configuration comment with only severity now retains options specified in the config file and just overrides the severity.
For example, if you have the following config file:
// eslint.config.js
export default [
{
rules: {
curly: ["error", "multi"],
},
},
];
and the following configuration comment:
// my-file.js
/* eslint curly: "warn" */
the resulting configuration for the curly rule when linting my-file.js will be curly: ["warn", "multi"].
Note that this change only affects cases where the same rule is configured in the config file with options and using a configuration comment without options. In all other cases (e.g. the rule is only configured using a configuration comment), the behavior remains the same as prior to ESLint v9.0.0.
To address: We expect that in most cases no change is necessary, as rules configured using configuration comments are typically not already configured in the config file. However, if you need a configuration comment to completely override configuration from the config file and enforce the default options, you'll need to specify at least one option:
// my-file.js
/* eslint curly: ["warn", "all"] */
Related issue(s): #17381
/* eslint */ comments for the same rule are now disallowedPrior to ESLint v9.0.0, if the file being linted contained multiple /* eslint */ configuration comments for the same rule, the last one would be applied, while the others would be silently ignored. For example:
/* eslint semi: ["error", "always"] */
/* eslint semi: ["error", "never"] */
foo(); // valid, because the configuration is "never"
In ESLint v9.0.0, the first one is applied, while the others are reported as lint errors:
/* eslint semi: ["error", "always"] */
/* eslint semi: ["error", "never"] */ // error: Rule "semi" is already configured by another configuration comment in the preceding code. This configuration is ignored.
foo(); // error: Missing semicolon
Codemod: The @eslint/v8-to-v9-config codemod includes a remove-unnecessary-eslint-comments step that modifies these comments. It removes all ESLint config comments matching its regex except the first one, not just duplicates of the same rule.
To address: Remove duplicate /* eslint */ comments.
Related issue(s): #18132
/* exported */ parsingPrior to ESLint v9.0.0, the /* exported */ directive incorrectly allowed the following syntax:
/* exported foo: true, bar: false */
// and
/* exported foo bar */
The true and false in this example had no effect on ESLint's behavior, and in fact, was a parsing bug.
In ESLint v9.0.0, any /* exported */ variables followed by a colon and value will be ignored as invalid.
Codemod: The @eslint/v8-to-v9-config codemod rewrites both /* exported foo: true, bar: false */ and the whitespace-separated form into a comma-separated format.
To address: Update any /* exported */ directives to eliminate the colons and subsequent values, and ensure there are commas between variable names such as:
/* exported foo, bar */
Related issue(s): #17622
no-constructor-return and no-sequences rule schemas are stricterIn previous versions of ESLint, no-constructor-return and no-sequences rules were mistakenly accepting invalid options.
This has been fixed in ESLint v9.0.0:
no-constructor-return rule does not accept any options.no-sequences rule can take one option, an object with a property "allowInParentheses" (boolean).{
"rules": {
"no-constructor-return": ["error"],
"no-sequences": ["error", { "allowInParentheses": false }]
}
}
Codemod: The @eslint/v8-to-v9-config codemod may update your configuration for this.
To address: If ESLint still reports invalid configuration for any of these rules, update your configuration manually.
Related issue(s): #16879
no-implicit-coercion by defaultIn ESLint v9.0.0, the no-implicit-coercion rule additionally reports the following cases by default:
-(-foo);
foo - 0;
To address: If you want to retain the previous behavior of this rule, set "allow": ["-", "- -"].
{
"rules": {
"no-implicit-coercion": [2, { "allow": ["-", "- -"] }]
}
}
Related issue(s): #17832
no-invalid-regexpIn ESLint v9.0.0, the option allowConstructorFlags is now case-sensitive.
Codemod: The @eslint/v8-to-v9-config codemod expands allowConstructorFlags with opposite-case variants to preserve ESLint v8 behavior under v9's case-sensitive matching.
To address: Update your configuration manually if needed.
Related issue(s): #16574
varsIgnorePattern option of no-unused-vars no longer applies to catch argumentsIn previous versions of ESLint, the varsIgnorePattern option of no-unused-vars incorrectly ignored errors specified in a catch clause. In ESLint v9.0.0, varsIgnorePattern no longer applies to errors in catch clauses. For example:
/*eslint no-unused-vars: ["error", { "caughtErrors": "all", "varsIgnorePattern": "^err" }]*/
try {
//...
} catch (err) {
// 'err' will be reported.
console.error("errors");
}
Codemod: The @eslint/v8-to-v9-config codemod copies varsIgnorePattern to caughtErrorsIgnorePattern when the rule is explicitly configured, catch errors are checked (caughtErrors is not "none"), and no separate caughtErrorsIgnorePattern is set. This preserves v8 behavior where varsIgnorePattern incorrectly applied to catch bindings. It does not apply when the rule comes only from a preset, when caughtErrors is "none", or for @typescript-eslint/no-unused-vars.
To address: If you want to specify ignore patterns for catch clause variable names, use the caughtErrorsIgnorePattern option in addition to varsIgnorePattern.
Related issue(s): #17540
no-restricted-imports now accepts multiple config entries with the same nameIn previous versions of ESLint, if multiple entries in the paths array of your configuration for the no-restricted-imports rule had the same name property, only the last one would apply, while the previous ones would be ignored.
As of ESLint v9.0.0, all entries apply, allowing for specifying different messages for different imported names. For example, you can now configure the rule like this:
{
rules: {
"no-restricted-imports": ["error", {
paths: [
{
name: "react-native",
importNames: ["Text"],
message: "import 'Text' from 'ui/_components' instead"
},
{
name: "react-native",
importNames: ["View"],
message: "import 'View' from 'ui/_components' instead"
}
]
}]
}
}
and both import { Text } from "react-native" and import { View } from "react-native" will be reported, with different messages.
In previous versions of ESLint, with this configuration only import { View } from "react-native" would be reported.
Codemod: The @eslint/v8-to-v9-config codemod updates no-restricted-imports configurations during migration. When the rule uses a paths array, it restructures the options for flat config and deduplicates entries that share the same name, keeping the last entry so lint behavior matches ESLint v8 (where only the last matching entry applied).
To address: If your configuration for this rule has multiple entries with the same name, you may need to remove unintentional ones.
Related issue(s): #15261
"eslint:recommended" and "eslint:all" no longer accepted in flat configIn ESLint v8.x, eslint.config.js could refer to "eslint:recommended" and "eslint:all" configurations by inserting a string into the config array, as in this example:
// eslint.config.js
export default ["eslint:recommended", "eslint:all"];
In ESLint v9.0.0, this format is no longer supported and will result in an error.
Codemod: Use the @eslint/v8-to-v9-config codemod. It converts legacy string presets in existing eslint.config.* files and migrates eslintrc configs via FlatCompat + @eslint/js.
To address: Install and use @eslint/js manually:
// eslint.config.js
import js from "@eslint/js";
export default [js.configs.recommended, js.configs.all];
Related issue(s): #17488
no-inner-declarations has a new default behavior with a new optionESLint v9.0.0 introduces a new option in no-inner-declarations rule called blockScopeFunctions which by default allows block-level functions in strict mode when languageOptions.ecmaVersion is set to 2015 or above.
/*eslint no-inner-declarations: "error"*/
"use strict";
if (test) {
function foo() {} // no error
}
Codemod: The @eslint/v8-to-v9-config codemod adds blockScopedFunctions: "disallow" when no-inner-declarations is explicitly configured, preserving v8 reporting behavior. It does not apply when the rule comes only from a preset.
To address: If you want to report the block-level functions in every condition regardless of strict or non-strict mode, set the blockScopeFunctions option to "disallow".
Related issue(s): #15576
no-unused-vars now defaults caughtErrors to "all"ESLint v9.0.0 changes the default value for the no-unused-vars rule's caughtErrors option.
Previously it defaulted to "none" to never check whether caught errors were used.
It now defaults to "all" to check caught errors for being used.
/*eslint no-unused-vars: "error"*/
try {
} catch (error) {
// 'error' is defined but never used
}
Codemod: The @eslint/v8-to-v9-config codemod updates no-unused-vars when the rule is explicitly configured. It adds caughtErrors: "none" if that option is omitted, preserving ESLint v8 behavior. Existing caughtErrors values are kept unchanged. If catch errors are checked and varsIgnorePattern is set, it also copies that pattern to caughtErrorsIgnorePattern. It does not add the rule when it is enabled only through eslint:recommended, and it does not migrate @typescript-eslint/no-unused-vars.
To address: If you want to allow unused caught errors, such as when writing code that will be directly run in an environment that does not support ES2019 optional catch bindings, set the caughtErrors option to "none".
Otherwise, delete the unused caught errors.
/*eslint no-unused-vars: "error"*/
try {
} catch {
// no error
}
Related issue(s): #17974
no-useless-computed-key flags unnecessary computed member names in classes by defaultIn ESLint v9.0.0, the default value of the enforceForClassMembers option of the no-useless-computed-key rule was changed from false to true.
The effect of this change is that unnecessary computed member names in classes will be flagged by default.
/*eslint no-useless-computed-key: "error"*/
class SomeClass {
["someMethod"]() {} // ok in ESLint v8, error in ESLint v9.
}
Codemod: The @eslint/v8-to-v9-config codemod sets enforceForClassMembers to false when the no-useless-computed-key rule is configured with an empty options object, preserving ESLint v8 behavior.
To address: Fix the problems reported by the rule or revert to the previous behavior by setting the enforceForClassMembers option to false.
Related issue(s): #18042
camelcase allow option only accepts an array of stringsPreviously the camelcase rule didn't enforce the allow option to be an array of strings. In ESLint v9.0.0, the allow option now only accepts an array of strings.
Codemod: The @eslint/v8-to-v9-config codemod may update your configuration for this.
To address: If ESLint still reports invalid configuration for this rule, update your configuration manually.
Related issue(s): #18232
context methodsESLint v9.0.0 removes multiple deprecated methods from the context object and moves them onto the SourceCode object:
Removed on context | Replacement(s) on SourceCode |
|---|---|
context.getSource() | sourceCode.getText() |
context.getSourceLines() | sourceCode.getLines() |
context.getAllComments() | sourceCode.getAllComments() |
context.getNodeByRangeIndex() | sourceCode.getNodeByRangeIndex() |
context.getComments() | sourceCode.getCommentsBefore(), sourceCode.getCommentsAfter(), sourceCode.getCommentsInside() |
context.getCommentsBefore() | sourceCode.getCommentsBefore() |
context.getCommentsAfter() | sourceCode.getCommentsAfter() |
context.getCommentsInside() | sourceCode.getCommentsInside() |
context.getJSDocComment() | sourceCode.getJSDocComment() |
context.getFirstToken() | sourceCode.getFirstToken() |
context.getFirstTokens() | sourceCode.getFirstTokens() |
context.getLastToken() | sourceCode.getLastToken() |
context.getLastTokens() | sourceCode.getLastTokens() |
context.getTokenAfter() | sourceCode.getTokenAfter() |
context.getTokenBefore() | sourceCode.getTokenBefore() |
context.getTokenByRangeStart() | sourceCode.getTokenByRangeStart() |
context.getTokens() | sourceCode.getTokens() |
context.getTokensAfter() | sourceCode.getTokensAfter() |
context.getTokensBefore() | sourceCode.getTokensBefore() |
context.getTokensBetween() | sourceCode.getTokensBetween() |
context.parserServices | sourceCode.parserServices |
context.getDeclaredVariables() | sourceCode.getDeclaredVariables() |
In addition to the methods in the above table, there are several other methods that are also moved but required different method signatures:
Removed on context | Replacement(s) on SourceCode |
|---|---|
context.getAncestors() | sourceCode.getAncestors(node) |
context.getScope() | sourceCode.getScope(node) |
context.markVariableAsUsed(name) | sourceCode.markVariableAsUsed(name, node) |
Codemod: Use the @eslint/v8-to-v9-custom-rules codemod to automate much of this migration.
To address: For more information, see the blog post.
Related Issues(s): #16999, #13481
sourceCode.getComments()ESLint v9.0.0 removes the deprecated sourceCode.getComments() method.
Codemod: Use the @eslint/v8-to-v9-custom-rules codemod. It converts both context.getComments() and sourceCode.getComments() to combinations of getCommentsBefore(), getCommentsInside(), and getCommentsAfter().
To address: Replace with sourceCode.getCommentsBefore(), sourceCode.getCommentsAfter(), or sourceCode.getCommentsInside().
Related Issues(s): #14744
CodePath#currentSegmentsESLint v9.0.0 removes the deprecated CodePath#currentSegments property.
Codemod: The @eslint/v8-to-v9-custom-rules codemod may automate part of this migration.
To address: Update your code following the recommendations in the blog post.
Related Issues(s): #17457
Prior to ESLint v9.0.0, code paths were calculated during the same AST traversal used by rules, meaning that the information passed to methods like onCodePathStart and onCodePathSegmentStart was incomplete. Specifically, array properties like CodePath#childCodePaths and CodePathSegment#nextSegments began empty and then were filled with the appropriate information as the traversal completed, meaning that those arrays could have different elements depending on when you checked their values.
ESLint v9.0.0 now precalculates code path information before the traversal used by rules. As a result, the code path information is now complete regardless of where it is accessed inside of a rule.
Codemod: The migration codemods do not cover code path precalculation. Rules that rely on when CodePath or CodePathSegment array properties are populated must be reviewed manually. The @eslint/v8-to-v9-custom-rules codemod only migrates removed CodePath#currentSegments usage.
To address: If you are accessing any array properties on CodePath or CodePathSegment, you'll need to update your code. Specifically:
length of any array properties, ensure you are using relative comparison operators like <, >, <=, and >= instead of equals.nextSegments, prevSegments, allNextSegments, or allPrevSegments properties on a CodePathSegment, or CodePath#childCodePaths, verify that your code will still work as expected. To be backwards compatible, consider moving the logic that checks these properties into onCodePathEnd.Related Issues(s): #16999
ESLint v9.0.0 drops support for function-style rules. Function-style rules are rules created by exporting a function rather than an object with a create() method. This rule format was deprecated in 2016.
Codemod: Use the @eslint/v8-to-v9-custom-rules codemod to convert function-style rules to the object format.
To address: Update your rules to the most recent rule format. For rules written in CommonJS, you can also use eslint-transforms.
The eslint-plugin/prefer-object-rule rule can help enforce the usage of object-style rules and autofix any remaining function-style rules.
Related Issues(s): #14709
meta.schema is required for rules with optionsAs of ESLint v9.0.0, an error will be thrown if any options are passed to a rule that doesn't specify meta.schema property.
Codemod: The @eslint/v8-to-v9-custom-rules codemod generates meta.schema for function-style and object-style rules, migrates legacy module.exports.schema, and inserts a TODO when it detects context.options without a schema. The limitation is that it does not fully infer the actual schema.
To address:
meta.schema property to a JSON Schema format description of the rule’s options. This schema will be used by ESLint to validate configured options and prevent invalid or unexpected inputs to your rule.meta.schema to false to disable this validation, but it is highly recommended to provide a schema if the rule expects options and omit the schema (or set []) if the rule doesn't expect options so that ESLint can ensure that your users' configurations are valid.The eslint-plugin/require-meta-schema rule can help enforce that rules have schemas when required.
Related Issues(s): #14709
FlatRuleTester is now RuleTesterAs announced in our blog post, the temporary FlatRuleTester class has been renamed to RuleTester, while the RuleTester class from v8.x has been removed. Additionally, the FlatRuleTester export from eslint/use-at-your-own-risk has been removed.
Codemod: The @eslint/v8-to-v9-custom-rules codemod renames FlatRuleTester to RuleTester and moves parserOptions to languageOptions in test cases. Updating imports from eslint/use-at-your-own-risk to eslint, adjusting for new RuleTester defaults, and translating other eslintrc-style test config must be done manually.
To address: Update your rule tests to use the new RuleTester. To do so, here are some of the common changes you'll need to make:
Be aware of new defaults for ecmaVersion and sourceType. By default, RuleTester uses the flat config default of ecmaVersion: "latest" and sourceType: "module". This may cause some tests to break if they were expecting the old default of ecmaVersion: 5 and sourceType: "script". If you'd like to use the old default, you'll need to manually specify that in your RuleTester like this:
// use eslintrc defaults
const ruleTester = new RuleTester({
languageOptions: {
ecmaVersion: 5,
sourceType: "script",
},
});
Change parserOptions to languageOptions. If you're setting ecmaVersion or sourceType on your tests, move those from parserOptions to languageOptions, like this:
ruleTester.run("my-rule", myRule, {
valid: [
{
code: "foo",
parserOptions: {
ecmaVersion: 6,
},
},
],
});
// becomes
ruleTester.run("my-rule", myRule, {
valid: [
{
code: "foo",
languageOptions: {
ecmaVersion: 6,
},
},
],
});
Translate other config keys. Keys such as env and parser that used to run on the eslintrc config system must be translated into the flat config system. Please refer to the Configuration Migration Guide for details on translating other keys you may be using.
Related Issues(s): #13481
RuleTester checksIn order to aid in the development of high-quality custom rules that are free from common bugs, ESLint v9.0.0 implements several changes to RuleTester:
output must be different from code. In ESLint v8.x, if output is the same as code, it asserts that there was no autofix. When looking at a test case, it's not always immediately clear whether output differs from code, especially if the strings are longer or multiline, making it difficult for developers to determine whether or not the test case expects an autofix. In ESLint v9.0.0, to avoid this ambiguity, RuleTester now throws an error if the test output has the same value as the test code. Therefore, specifying output now necessarily means that the test case expects an autofix and asserts its result. If the test case doesn't expect an autofix, omit the output property or set it to null. This asserts that there was no autofix.message or messageId. To improve the quality of test coverage, RuleTester now throws an error if neither message nor messageId is specified on test error objects.suggestions if the actual error provides suggestions. In ESLint v8.x, if the suggestions property was omitted from test error objects, RuleTester wasn't performing any checks related to suggestions, so it was easy to forget to assert if a test case produces suggestions. In ESLint v9.0.0, omitting the suggestions property asserts that the actual error does not provide suggestions, while you need to specify the suggestions property if the actual error does provide suggestions. We highly recommend that you test suggestions in detail by specifying an array of test suggestion objects, but you can also specify suggestions: <number> to assert just the number of suggestions.output. To improve the quality of test coverage, RuleTester now throws an error if output property is not specified on test suggestion objects.desc or messageId. To improve the quality of test coverage, RuleTester now throws an error if neither desc nor messageId property is specified on test suggestion objects. It's also not allowed to specify both. If you want to assert the suggestion description text in addition to the messageId, then also add the data property.RuleTester now throws an error if the suggestion test output is the same as the test code.RuleTester now parses the output of suggestions using the same language options as the code value and throws an error if parsing fails.filename and only must be of the expected type. RuleTester now checks the type of filename and only properties of test objects. If specified, filename must be a string value. If specified, only must be a boolean value.RuleTester now also checks if there are {% raw %}{{ placeholder }} {% endraw %} still in the message as their values were not passed via data in the respective context.report() call.Codemod: The @eslint/v8-to-v9-custom-rules codemod renames FlatRuleTester to RuleTester, moves parserOptions to languageOptions in test cases, and removes output when it equals code. All other stricter RuleTester checks must be fixed manually by running your rule tests and updating failing cases.
To address: Run your rule tests using RuleTester and fix any errors that occur. The changes you'll need to make to satisfy RuleTester are compatible with ESLint v8.x.
Related Issues(s): #15104, #15735, #16908, #18016
FlatESLint is now ESLintAs announced in our blog post, the temporary FlatESLint class has been renamed to ESLint, while the ESLint class from v8.x has been renamed to LegacyESLint.
Codemod: The @eslint/v8-to-v9-custom-rules codemod renames FlatESLint to ESLint.
To address: If you are currently using the ESLint class, verify that your tests pass using the new ESLint class. Not all of the old options are supported, so you may need to update the arguments passed to the constructor. See the Node.js API Reference for details.
If you still need the v8.x ESLint functionality, use the LegacyESLint class like this:
const { LegacyESLint } = require("eslint/use-at-your-own-risk");
Related Issues(s): #13481
Linter now expects flat config formatIn ESLint v9.0.0, the config argument passed to Linter#verify() and Linter#verifyAndFix() methods should be in the flat config format.
Additionally, methods Linter#defineRule(), Linter#defineRules(), Linter#defineParser(), and Linter#getRules() are no longer available.
Codemod: The @eslint/v8-to-v9-custom-rules codemod renames FlatESLint to ESLint and moves parserOptions to languageOptions in Linter#verify() and Linter#verifyAndFix() calls. Calls to removed methods (defineRule, defineRules, defineParser, getRules) are marked with TODO comments; migrating rules and parsers into flat plugins / languageOptions config must be done manually.
To address: If you are using the Linter class, verify that your tests pass.
If you're passing configuration objects that are incompatible with the flat config format, you'll need to update the code.
// eslintrc config format
linter.verify(code, {
parserOptions: {
ecmaVersion: 6,
},
});
// flat config format
linter.verify(code, {
languageOptions: {
ecmaVersion: 6,
},
});
Please refer to the Configuration Migration Guide for details on translating other keys you may be using.
Rules and parsers can be defined directly in the configuration.
// eslintrc mode
linter.defineRule("my-rule1", myRule1);
linter.defineRules({
"my-rule2": myRule2,
"my-rule3": myRule3,
});
linter.defineParser("my-parser", myParser);
linter.verify(code, {
rules: {
"my-rule1": "error",
"my-rule2": "error",
"my-rule3": "error",
},
parser: "my-parser",
});
// flat config mode
linter.verify(code, {
plugins: {
"my-plugin-foo": {
rules: {
"my-rule1": myRule1,
},
},
"my-plugin-bar": {
rules: {
"my-rule2": myRule2,
"my-rule3": myRule3,
},
},
},
rules: {
"my-plugin-foo/my-rule1": "error",
"my-plugin-bar/my-rule2": "error",
"my-plugin-bar/my-rule3": "error",
},
languageOptions: {
parser: myParser,
},
});
If you still need the v8.x Linter functionality, pass configType: "eslintrc" to the constructor like this:
const linter = new Linter({ configType: "eslintrc" });
linter.verify(code, {
parserOptions: {
ecmaVersion: 6,
},
});
linter.getRules();
Related Issues(s): #13481