# Eslint > ESLint is an open source JavaScript linting utility originally created by Nicholas C. Zakas in June 2013. Code linting is a type of static analysis that is frequently used to find problematic patt --- --- title: About --- ESLint is an open source JavaScript linting utility originally created by Nicholas C. Zakas in June 2013. Code [linting][] is a type of static analysis that is frequently used to find problematic patterns or code that doesn't adhere to certain style guidelines. There are code linters for most programming languages, and compilers sometimes incorporate linting into the compilation process. JavaScript, being a dynamic and loosely-typed language, is especially prone to developer error. Without the benefit of a compilation process, JavaScript code is typically executed in order to find syntax or other errors. Linting tools like ESLint allow developers to discover problems with their JavaScript code without executing it. The primary reason ESLint was created was to allow developers to create their own linting rules. ESLint is designed to have all rules completely pluggable. The default rules are written just like any plugin rules would be. They can all follow the same pattern, both for the rules themselves as well as tests. While ESLint will ship with some built-in rules to make it useful from the start, you'll be able to dynamically load rules at any point in time. ESLint is written using Node.js to provide a fast runtime environment and easy installation via [npm][]. [linting]: https://en.wikipedia.org/wiki/Lint_(software) [npm]: https://npmjs.org/ ## Philosophy Everything is pluggable: - Rule API is used both by bundled and custom rules. - Formatter API is used both by bundled and custom formatters. - Additional rules and formatters can be specified at runtime. - Rules and formatters don't have to be bundled to be used. Every rule: - Is standalone. - Can be turned off or on (nothing can be deemed "too important to turn off"). - Can be set to a warning or error individually. Additionally: - Rules are "agenda free" - ESLint does not promote any particular coding style. - Any bundled rules are generalizable. The project: - Values documentation and clear communication. - Is as transparent as possible. - Believes in the importance of testing. --- --- title: Architecture eleventyNavigation: key: architecture parent: contribute to eslint title: Architecture order: 5 --- :::img-container dependency graph ::: At a high level, there are a few key parts to ESLint: - `bin/eslint.js` - this is the file that actually gets executed with the command line utility. It's a dumb wrapper that does nothing more than bootstrap ESLint, passing the command line arguments to `cli`. This is intentionally small so as not to require heavy testing. - `lib/api.js` - this is the entry point of `require("eslint")`. This file exposes an object that contains public classes `Linter`, `ESLint`, `RuleTester`, and `SourceCode`. - `lib/cli.js` - this is the heart of the ESLint CLI. It takes an array of arguments and then uses `eslint` to execute the commands. By keeping this as a separate utility, it allows others to effectively call ESLint from within another Node.js program as if it were done on the command line. The main call is `cli.execute()`. This is also the part that does all the file reading, directory traversing, input, and output. - `lib/cli-engine/` - this module is `CLIEngine` class that finds source code files and configuration files then does code verifying with the `Linter` class. This includes the loading logic of configuration files, parsers, plugins, and formatters. - `lib/linter/` - this module is the core `Linter` class that does code verifying based on configuration options. This file does no file I/O and does not interact with the `console` at all. For other Node.js programs that have JavaScript text to verify, they would be able to use this interface directly. - `lib/rule-tester/` - this module is `RuleTester` class that is a wrapper around Mocha so that rules can be unit tested. This class lets us write consistently formatted tests for each rule that is implemented and be confident that each of the rules work. The RuleTester interface was modeled after Mocha and works with Mocha's global testing methods. RuleTester can also be modified to work with other testing frameworks. - `lib/source-code/` - this module is `SourceCode` class that is used to represent the parsed source code. It takes in source code and the Program node of the AST representing the code. - `lib/rules/` - this contains built-in rules that verify source code. ## The `cli` object The `cli` object is the API for the command line interface. Literally, the `bin/eslint.js` file simply passes arguments to the `cli` object and then sets `process.exitCode` to the returned exit code. The main method is `cli.execute()`, which accepts an array of strings that represent the command line options (as if `process.argv` were passed without the first two arguments). If you want to run ESLint from inside of another program and have it act like the CLI, then `cli` is the object to use. This object's responsibilities include: - Interpreting command line arguments. - Reading from the file system. - Outputting to the console. - Outputting to the filesystem. - Use a formatter. - Returning the correct exit code. This object may not: - Call `process.exit()` directly. - Perform any asynchronous operations. ## The `CLIEngine` object The `CLIEngine` type represents the core functionality of the CLI except that it reads nothing from the command line and doesn't output anything by default. Instead, it accepts many (but not all) of the arguments that are passed into the CLI. It reads both configuration and source files as well as managing the environment that is passed into the `Linter` object. The main method of the `CLIEngine` is `executeOnFiles()`, which accepts an array of file and directory names to run the linter on. This object's responsibilities include: - Managing the execution environment for `Linter`. - Reading from the file system. - Reading configuration information from config files (including `.eslintrc` and `package.json`). This object may not: - Call `process.exit()` directly. - Perform any asynchronous operations. - Output to the console. - Use formatters. ## The `Linter` object The main method of the `Linter` object is `verify()` and accepts two arguments: the source text to verify and a configuration object (the baked configuration of the given configuration file plus command line options). The method first parses the given text with `espree` (or whatever the configured parser is) and retrieves the AST. The AST is produced with both line/column and range locations which are useful for reporting location of issues and retrieving the source text related to an AST node, respectively. Once the AST is available, `estraverse` is used to traverse the AST from top to bottom. At each node, the `Linter` object emits an event that has the same name as the node type (i.e., "Identifier", "WithStatement", etc.). On the way back up the subtree, an event is emitted with the AST type name and suffixed with ":exit", such as "Identifier:exit" - this allows rules to take action both on the way down and on the way up in the traversal. Each event is emitted with the appropriate AST node available. This object's responsibilities include: - Inspecting JavaScript code strings. - Creating an AST for the code. - Executing rules on the AST. - Reporting back the results of the execution. This object may not: - Call `process.exit()` directly. - Perform any asynchronous operations. - Use Node.js-specific features. - Access the file system. - Call `console.log()` or any other similar method. ## Rules Individual rules are the most specialized part of the ESLint architecture. Rules can do very little, they are simply a set of instructions executed against an AST that is provided. They do get some context information passed in, but the primary responsibility of a rule is to inspect the AST and report warnings. These objects' responsibilities are: - Inspect the AST for specific patterns. - Reporting warnings when certain patterns are found. These objects may not: - Call `process.exit()` directly. - Perform any asynchronous operations. - Use Node.js-specific features. - Access the file system. - Call `console.log()` or any other similar method. --- --- title: Code Conventions --- Code conventions for ESLint are determined by [eslint-config-eslint](https://www.npmjs.com/package/eslint-config-eslint). The rationales for the specific rules in use can be found by looking to the project documentation for any given rule. If the rule is one of our own, see our own [rule documentation](../rules/) and otherwise, see the documentation of the plugin in which the rule can be found. If you need to make changes to a `package.json` file, please see the [package.json conventions](./package-json-conventions). --- --- title: Code of Conduct eleventyNavigation: key: code of conduct parent: contribute to eslint title: Code of Conduct order: 0 --- ESLint welcomes contributions from everyone and adheres to the [OpenJS Foundation Code of Conduct](https://eslint.org/conduct). We kindly request that you read over this code of conduct before contributing. --- --- title: Contribute to Core Rules eleventyNavigation: key: contribute core rule parent: contribute to eslint title: Contribute to Core Rules order: 11 --- The ESLint core rules are the rules included in the ESLint package. ## Rule Writing Documentation For full reference information on writing rules, refer to [Custom Rules](../extend/custom-rules). Both custom rules and core rules have the same API. The primary difference between core and custom rules are: 1. Core rules are included in the `eslint` package. 1. Core rules must adhere to the conventions documented on this page. ## File Structure Each core rule in ESLint has three files named with its identifier (for example, `no-extra-semi`). - in the `lib/rules` directory: a source file (for example, `no-extra-semi.js`). - in the `tests/lib/rules` directory: a test file (for example, `no-extra-semi.js`). - in the `docs/src/rules` directory: a Markdown documentation file (for example, `no-extra-semi.md`). **Important:** If you submit a core rule to the ESLint repository, you **must** follow the conventions explained below. Here is the basic format of the source file for a rule: ```js /** * @fileoverview Rule to disallow unnecessary semicolons * @author Nicholas C. Zakas */ "use strict"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ /** @type {import('../types').Rule.RuleModule} */ module.exports = { meta: { type: "suggestion", docs: { description: "disallow unnecessary semicolons", recommended: true, url: "https://eslint.org/docs/rules/no-extra-semi", }, fixable: "code", schema: [], // no options }, create: function (context) { return { // callback functions }; }, }; ``` ## Rule Unit Tests Each bundled rule for ESLint core must have a set of unit tests submitted with it to be accepted. The test file is named the same as the source file but lives in `tests/lib/`. For example, if the rule source file is `lib/rules/foo.js` then the test file should be `tests/lib/rules/foo.js`. ESLint provides the [`RuleTester`](../integrate/nodejs-api#ruletester) utility to make it easy to write tests for rules. ## Performance Testing To keep the linting process efficient and unobtrusive, it is useful to verify the performance impact of new rules or modifications to existing rules. To learn how to profile the performance of individual rules, refer to [Profile Rule Performance](../extend/custom-rules#profile-rule-performance) in the custom rules documentation. When developing in the ESLint core repository, the `npm run perf` command gives a high-level overview of ESLint running time with all core rules enabled. ```bash $ git checkout main Switched to branch 'main' $ npm run perf CPU Speed is 2200 with multiplier 7500000 Performance Run #1: 1394.689313ms Performance Run #2: 1423.295351ms Performance Run #3: 1385.09515ms Performance Run #4: 1382.406982ms Performance Run #5: 1409.68566ms Performance budget ok: 1394.689313ms (limit: 3409.090909090909ms) $ git checkout my-rule-branch Switched to branch 'my-rule-branch' $ npm run perf CPU Speed is 2200 with multiplier 7500000 Performance Run #1: 1443.736547ms Performance Run #2: 1419.193291ms Performance Run #3: 1436.018228ms Performance Run #4: 1473.605485ms Performance Run #5: 1457.455283ms Performance budget ok: 1443.736547ms (limit: 3409.090909090909ms) ``` ## Rule Naming Conventions The rule naming conventions for ESLint are as follows: - Use dashes between words. - If your rule only disallows something, prefix it with `no-` such as `no-eval` for disallowing `eval()` and `no-debugger` for disallowing `debugger`. - If your rule is enforcing the inclusion of something, use a short name without a special prefix. ## Frozen Rules When rules are feature complete, they are marked as frozen (indicated with ❄️ in the documentation). Rules are considered feature complete when the intended purpose of the rule has been fully implemented such that it catches 80% or more of expected violations and covers the majority of common exceptions. After that point, we expect users to use [disable comments](../use/configure/rules#using-configuration-comments-1) when they find an edge case that isn't covered. When a rule is frozen, it means: - **Bug fixes**: We will still fix confirmed bugs. - **New ECMAScript features**: We will ensure compatibility with new ECMAScript features, meaning the rule will not break on new syntax. - **TypeScript support**: We will ensure compatibility with TypeScript syntax, meaning the rule will not break on TypeScript syntax and violations are appropriate for TypeScript. - **New options**: We will **not** add any new options unless an option is the only way to fix a bug or support a newly-added ECMAScript feature. If you find that a frozen rule would work better for you with a change, we recommend copying the rule source code and modifying it to fit your needs. --- --- title: Set up a Development Environment eleventyNavigation: key: development environment parent: contribute to eslint title: Set up a Development Environment order: 6 --- {%- from 'components/npm_tabs.macro.html' import npm_tabs with context %} ESLint has a very lightweight development environment that makes updating code fast and easy. This is a step-by-step guide to setting up a local development environment that will let you contribute back to the project. ## Step 1: Install Node.js Go to to download and install the latest stable version for your operating system. Most of the installers already come with [npm](https://www.npmjs.com/) but if for some reason npm doesn't work on your system, you can install it manually using the instructions on the site. ## Step 2: Fork and Checkout Your Own ESLint Repository Go to and click the "Fork" button. Follow the [GitHub documentation](https://help.github.com/articles/fork-a-repo) for forking and cloning. Clone your fork: ```shell git clone https://github.com//eslint ``` Once you've cloned the repository, run `npm install` to get all the necessary dependencies: ```shell cd eslint ``` {{ npm_tabs({ command: "install", packages: [], args: [] }) }} You must be connected to the Internet for this step to work. You'll see a lot of utilities being downloaded. **Note:** It's a good idea to re-run `npm install` whenever you pull from the main repository to ensure you have the latest development dependencies. ## Step 3: Add the Upstream Source The _upstream source_ is the main ESLint repository where active development happens. While you won't have push access to upstream, you will have pull access, allowing you to pull in the latest code whenever you want. To add the upstream source for ESLint, run the following in your repository: ```shell git remote add upstream git@github.com:eslint/eslint.git ``` Now, the remote `upstream` points to the upstream source. ## Step 4: Install the Yeoman Generator [Yeoman](https://yeoman.io) is a scaffold generator that ESLint uses to help streamline development of new rules. If you don't already have Yeoman installed, you can install it via npm: {{ npm_tabs({ command: "install", packages: ["yo"], args: ["--global"] }) }} Then, you can install the ESLint Yeoman generator: {{ npm_tabs({ command: "install", packages: ["generator-eslint"], args: ["--global"] }) }} Please see the [generator documentation](https://github.com/eslint/generator-eslint) for instructions on how to use it. ## Step 5: Run the Tests Running the tests is the best way to ensure you have correctly set up your development environment. Make sure you're in the `eslint` directory and run: ```shell npm test ``` The testing takes a few minutes to complete. If any tests fail, that likely means one or more parts of the environment setup didn't complete correctly. The upstream tests always pass. ## Reference Information ### Directory Structure The ESLint directory and file structure is as follows: - `bin` - executable files that are available when ESLint is installed. - `conf` - default configuration information. - `docs` - documentation for the project. - `lib` - contains the source code. - `formatters` - all source files defining formatters. - `rules` - all source files defining rules. - `tests` - the main unit test folder. - `lib` - tests for the source code. - `formatters` - tests for the formatters. - `rules` - tests for the rules. ### Workflow Once you have your development environment installed, you can make and submit changes to the ESLint source files. Doing this successfully requires careful adherence to our [pull-request submission workflow](./pull-requests). ### Build Scripts ESLint has several build scripts that help with various parts of development. #### npm test The primary script to use is `npm test`, which does several things: 1. Lints all JavaScript (including tests) and JSON. 1. Runs all tests on Node.js. 1. Checks code coverage targets. 1. Generates `build/eslint.js` for use in a browser. 1. Runs a subset of tests in PhantomJS. Be sure to run this after making changes and before sending a pull request with your changes. **Note:** The full code coverage report is output into `/coverage`. #### npm run lint Runs just the JavaScript and JSON linting on the repository. #### npm run webpack Generates `build/eslint.js`, a version of ESLint for use in the browser. --- --- title: Governance eleventyNavigation: key: governance parent: contribute to eslint title: Governance order: 12 --- ESLint is an open source project that depends on contributions from the community. Anyone may contribute to the project at any time by submitting code, participating in discussions, making suggestions, or any other contribution they see fit. This document describes how various types of contributors work within the ESLint project. ## Roles and Responsibilities ### Users Users are community members who have a need for the project. Anyone can be a User; there are no special requirements. Common User contributions include evangelizing the project (e.g., display a link on a website and raise awareness through word-of-mouth), informing developers of strengths and weaknesses from a new user perspective, or providing moral support (a "thank you" goes a long way). Users who continue to engage with the project and its community will often become more and more involved. Such Users may find themselves becoming Contributors, as described in the next section. ### Contributors Contributors are community members who contribute in concrete ways to the project, most often in the form of code and/or documentation. Anyone can become a Contributor, and contributions can take many forms. There is no expectation of commitment to the project, no specific skill requirements, and no selection process. Contributors have read-only access to source code and so submit changes via pull requests. Contributor pull requests have their contribution reviewed and merged by a TSC member. TSC members and Committers work with Contributors to review their code and prepare it for merging. As Contributors gain experience and familiarity with the project, their profile within, and commitment to, the community will increase. At some stage, they may find themselves being nominated as either a Website Team Member or Committer by an existing Website Team Member or Committer. ### Website Team Member Website Team Members are community members who have shown that they are committed to the continued maintenance of [eslint.org](https://eslint.org/) through ongoing engagement with the community. Website Team Members are given push access to the `eslint.org` GitHub repository and must abide by the project's [Contribution Guidelines](../contribute/). Website Team Members: - Are expected to work at least one hour per week triaging issues and reviewing pull requests. - Are expected to work at least two hours total per week on ESLint. - May invoice for the hours they spend working on ESLint at a rate of $50 USD per hour. - Are expected to check in on the `#team` Discord channel once per week day (excluding holidays and other time off) for team updates. - Are expected to work on public branches of the source repository and submit pull requests from that branch to the main branch. - Are expected to delete their public branches when they are no longer necessary. - Must submit pull requests for all changes. - Have their work reviewed by Reviewers and TSC members before acceptance into the repository. - May label and close website-related issues (see [Manage Issues](../maintain/manage-issues)). - May merge some pull requests (see [Review Pull Requests](../maintain/review-pull-requests)). - May take time off whenever they want, and are expected to post in the `#team` Discord channel when they will be away for more than a couple of days. To become a Website Team Member: - One must have shown a willingness and ability to participate in the maintenance of [eslint.org](https://eslint.org/) as a team player. Typically, a potential Website Team Member will need to show that they have an understanding of the structure of the website and how it fits into the larger ESLint project's objectives and strategy. - Website Team Members are expected to be respectful of every community member and to work collaboratively in the spirit of inclusion. - Have submitted a minimum of 10 website-related pull requests. What's a website-related pull request? One that is made to the `eslint.org` repository or the `docs` directory in the `eslint` repository and requires little effort to accept because it's well documented and tested. New Website Team Members can be nominated by any existing Website Team Member or Committer. Once they have been nominated, there will be a vote by the TSC members. It is important to recognize that membership on the website team is a privilege, not a right. That privilege must be earned and once earned it can be removed by the TSC members by a standard TSC motion. However, under normal circumstances Website Team Members remain for as long as they wish to continue engaging with the project. ### Committers Committers are community members who have shown that they are committed to the continued development of the project through ongoing engagement with the community. Committers are given push access to the project's GitHub repos and must abide by the project's [Contribution Guidelines](../contribute/). Committers: - Are expected to work at least one hour per week triaging issues and reviewing pull requests. - Are expected to work at least two hours total per week on ESLint. - May invoice for the hours they spend working on ESLint at a rate of $50 USD per hour. - Are expected to check in on the `#team` Discord channel once per week day (excluding holidays and other time off) for team updates. - Are expected to work on public branches of the source repository and submit pull requests from that branch to the main branch. - Are expected to delete their public branches when they are no longer necessary. - Are expected to provide feedback on issues in the "Feedback Needed" column of the [Triage Board](https://github.com/orgs/eslint/projects/3/views/1). - Are expected to work on at least one issue in the "Ready to Implement" column of the [Triage Board](https://github.com/orgs/eslint/projects/3/views/1) that they didn't create each month. - Must submit pull requests for all changes. - Have their work reviewed by TSC members before acceptance into the repository. - May label and close issues (see [Manage Issues](../maintain/manage-issues)). - May merge some pull requests (see [Review Pull Requests](../maintain/review-pull-requests)). - May take time off whenever they want, and are expected to post in the `#team` Discord channel when they will be away for more than a couple of days. To become a Committer: - One must have shown a willingness and ability to participate in the project as a team player. Typically, a potential Committer will need to show that they have an understanding of and alignment with the project, its objectives, and its strategy. - Committers are expected to be respectful of every community member and to work collaboratively in the spirit of inclusion. - Have submitted a minimum of 10 qualifying pull requests. What's a qualifying pull request? One that carries significant technical weight and requires little effort to accept because it's well documented and tested. New Committers can be nominated by any existing Committer. Once they have been nominated, there will be a vote by the TSC members. It is important to recognize that committership is a privilege, not a right. That privilege must be earned and once earned it can be removed by the TSC members by a standard TSC motion. However, under normal circumstances committership exists for as long as the Committer wishes to continue engaging with the project. A Committer who shows an above-average level of contribution to the project, particularly with respect to its strategic direction and long-term health, may be nominated to become a reviewer, described below. ### Reviewers Reviewers are community members who have contributed a significant amount of time to the project through triaging of issues, fixing bugs, implementing enhancements/features, and are trusted community leaders. Reviewers may perform all of the duties of Committers, and also: - May merge external pull requests for accepted issues upon reviewing and approving the changes. - May merge their own pull requests once they have collected the feedback they deem necessary. (No pull request should be merged without at least one Committer/Reviewer/TSC member comment stating they've looked at the code.) - May invoice for the hours they spend working on ESLint at a rate of $80 USD per hour. To become a Reviewer: - Work in a helpful and collaborative way with the community. - Have given good feedback on others' submissions and displayed an overall understanding of the code quality standards for the project. - Commit to being a part of the community for the long-term. - Have submitted a minimum of 50 qualifying pull requests. A Committer is invited to become a Reviewer by existing Reviewers and TSC members. A nomination will result in discussion and then a decision by the TSC. ### Technical Steering Committee (TSC) The ESLint project is jointly governed by a Technical Steering Committee (TSC) which is responsible for high-level guidance of the project. The TSC has final authority over this project including: - Technical direction - Project governance and process (including this policy) - Contribution policy - GitHub repository hosting TSC seats are not time-limited. The size of the TSC can not be larger than five members. This size ensures adequate coverage of important areas of expertise balanced with the ability to make decisions efficiently. The TSC may add additional members to the TSC by a standard TSC motion. A TSC member may be removed from the TSC by voluntary resignation, by a standard TSC motion, or by missing four consecutive TSC meetings. In all cases, the TSC member will revert to Reviewer status unless they prefer Alumni status. Changes to TSC membership should be posted in the agenda, and may be suggested as any other agenda item (see "TSC Meetings" below). No more than 1/3 of the TSC members may be affiliated with the same employer. If removal or resignation of a TSC member, or a change of employment by a TSC member, creates a situation where more than 1/3 of the TSC membership shares an employer, then the situation must be immediately remedied by the resignation or removal of one or more TSC members affiliated with the over-represented employer(s). TSC members have additional responsibilities over and above those of a Reviewer. These responsibilities ensure the smooth running of the project. TSC members are expected to review code contributions, approve changes to this document, manage the copyrights within the project outputs, and attend regular TSC meetings. TSC members may perform all of the duties of Reviewers, and also: - May release new versions of all ESLint projects. - May participate in TSC meetings. - May propose budget items. - May propose new ESLint projects. There is no specific set of requirements or qualifications for TSC members beyond those that are expected of Reviewers. A Reviewer is invited to become a TSC member by existing TSC members. A nomination will result in discussion and then a decision by the TSC. #### TSC Meetings The TSC meets every other week in the TSC Meeting [Discord](https://eslint.org/chat) channel. The meeting is run by a designated moderator approved by the TSC. Items are added to the TSC agenda which are considered contentious or are modifications of governance, contribution policy, TSC membership, or release process. The intention of the agenda is not to approve or review all patches. That should happen continuously on GitHub and be handled by the larger group of Committers. Any community member, Committer, or Reviewer can ask that something be added to the next meeting's agenda by logging a GitHub Issue. Anyone can add the item to the agenda by adding the "tsc agenda" tag to the issue. Prior to each TSC meeting, the moderator will share the Agenda with members of the TSC. TSC members can add any items they like to the agenda at the beginning of each meeting. The moderator and the TSC cannot veto or remove items. No binding votes on TSC agenda items can take place without a quorum of TSC members present in the meeting. Quorum is achieved when more than half of the TSC members (minus non-attending members) are present. The TSC may invite persons or representatives from certain projects to participate in a non-voting capacity. The moderator is responsible for summarizing the discussion of each agenda item and sending it as a pull request after the meeting. ## Consensus Seeking Process The TSC follows a [Consensus Seeking](https://en.wikipedia.org/wiki/Consensus-seeking_decision-making) decision making model. When an agenda item has appeared to reach a consensus, the moderator will ask "Does anyone object?" as a final call for dissent from the consensus. If an agenda item cannot reach a consensus, a TSC member can call for either a closing vote or a vote to table the issue to the next meeting. The call for a vote must be approved by a majority of the TSC or else the discussion will continue. Simple majority wins. --- This work is a derivative of [YUI Contributor Model](https://github.com/yui/yui3/wiki/Contributor-Model) and the [Node.js Project Governance Model](https://github.com/nodejs/node/blob/main/GOVERNANCE.md). This work is licensed under a [Creative Commons Attribution-ShareAlike 2.0 UK: England & Wales License](https://creativecommons.org/licenses/by-sa/2.0/uk/). --- --- title: Contribute to ESLint eleventyNavigation: key: contribute to eslint title: Contribute to ESLint order: 4 --- One of the great things about open source projects is that anyone can contribute in any number of meaningful ways. ESLint couldn't exist without the help of the many contributors it's had since the project began, and we want you to feel like you can contribute and make a difference as well. This guide is intended for anyone who wants to contribute to an ESLint project. Please read it carefully as it answers a lot of the questions many newcomers have when first working with our projects. ## Read the [Code of Conduct](https://eslint.org/conduct) ESLint welcomes contributions from everyone and adheres to the [OpenJS Foundation Code of Conduct](https://eslint.org/conduct). We kindly request that you read over our code of conduct before contributing. ## [Report Bugs](report-bugs) Think you found a problem? We'd love to hear about it. This section explains how to submit a bug, the type of information we need to properly verify it, and the overall process. ## [Propose a New Rule](propose-new-rule) We get a lot of proposals for new rules in ESLint. This section explains how we determine which rules are accepted and what information you should provide to help us evaluate your proposal. ## [Propose a Rule Change](propose-rule-change) Want to make a change to an existing rule? This section explains the process and how we evaluate such proposals. ## [Request a Change](request-change) If you'd like to request a change other than a bug fix or new rule, this section explains that process. ## [Architecture](architecture) Learn about the architecture of the ESLint project. ## [Set up a Development Environment](development-environment) Developing for ESLint is a bit different than running it on the command line. This section shows you how to set up a development environment and get you ready to write code. ## [Run the Tests](tests) There are a lot of unit tests included with ESLint to make sure that we're keeping on top of code quality. This section explains how to run the unit tests. ## [Work on Issues](work-on-issue) Have some extra time and want to contribute? This section talks about the process of working on issues. ## [Submit a Pull Request](pull-requests) We're always looking for contributions from the community. This section explains the requirements for pull requests and the process of contributing code. ## [Contribute to Core Rules](core-rules) This section explains how to add to the core rules of ESLint. ## [Governance](governance) Describes the governance policy for ESLint, including the rights and privileges of individuals inside the project. ## [Report a Security Vulnerability](report-security-vulnerability) To report a security vulnerability in ESLint, please create an advisory on GitHub. ## Sign the CLA In order to submit code or documentation to an ESLint project, you will need to electronically sign our Contributor License Agreement. The CLA is the commonly used Apache-style template, and is you giving us permission to use your contribution. You only need to sign the CLA once for any OpenJS Foundation projects that use EasyCLA. You will be asked to sign the CLA in the first pull request you open. --- --- title: Package.json Conventions eleventyNavigation: key: package.json conventions parent: contribute to eslint title: Package.json Conventions order: 8 --- The following applies to the "scripts" section of `package.json` files. ## Names npm script names MUST contain only lower case letters, `:` to separate parts, `-` to separate words, and `+` to separate file extensions. Each part name SHOULD be either a full English word (e.g. `coverage` not `cov`) or a well-known initialism in all lowercase (e.g. `wasm`). Here is a summary of the proposal in ABNF. ```abnf name = life-cycle / main target? option* ":watch"? life-cycle = "prepare" / "preinstall" / "install" / "postinstall" / "prepublish" / "preprepare" / "prepare" / "postprepare" / "prepack" / "postpack" / "prepublishOnly" main = "build" / "lint" ":fix"? / "fmt" ":check"? / "release" / "start" / "test" / "fetch" target = ":" word ("-" word)* / extension ("+" extension)* option = ":" word ("-" word)* word = ALPHA + extension = ( ALPHA / DIGIT )+ ``` ## Order The script names MUST appear in the `package.json` file in alphabetical order. The other conventions outlined in this document ensure that alphabetical order will coincide with logical groupings. ## Main Script Names With the exception of [npm life cycle scripts](https://docs.npmjs.com/cli/v8/using-npm/scripts#life-cycle-scripts) all script names MUST begin with one of the following names. ### Build Scripts that generate a set of files from source code and / or data MUST have names that begin with `build`. If a package contains any `build:*` scripts, there MAY be a script named `build`. If so, SHOULD produce the same output as running each of the `build` scripts individually. It MUST produce a subset of the output from running those scripts. ### Fetch Scripts that generate a set of files from external data or resources MUST have names that begin with `fetch`. If a package contains any `fetch:*` scripts, there MAY be a script named `fetch`. If so, it SHOULD produce the same output as running each of the `fetch` scripts individually. It MUST produce a subset of the output from running those scripts. ### Release Scripts that have public side effects (publishing the website, committing to Git, etc.) MUST begin with `release`. ### Lint Scripts that statically analyze files (mostly, but not limited to running `eslint` itself) MUST have names that begin with `lint`. If a package contains any `lint:*` scripts, there SHOULD be a script named `lint` and it MUST run all of the checks that would have been run if each `lint:*` script was called individually. If fixing is available, a linter MUST NOT apply fixes UNLESS the script contains the `:fix` modifier (see below). ### Fmt Scripts that format source code MUST have names that begin with `fmt`. If a package contains any `fmt:*` scripts, there SHOULD be a script named `fmt` that applies formatting fixes to all source files. There SHOULD also be a script named `fmt:check` that validates code formatting without modifying files and exits non-zero if any files are out of compliance. ### Start A `start` script is used to start a server. As of this writing, no ESLint package has more than one `start` script, so there's no need `start` to have any modifiers. ### Test Scripts that execute code in order to ensure the actual behavior matches expected behavior MUST have names that begin with `test`. If a package contains any `test:*` scripts, there SHOULD be a script named `test` and it MUST run of all of the tests that would have been run if each `test:*` script was called individually. A test script SHOULD NOT include linting. A test script SHOULD report test coverage when possible. ## Modifiers One or more of the following modifiers MAY be appended to the standard script names above. If a target has modifiers, they MUST be in the order in which they appear below (e.g. `lint:fix:js:watch` not `lint:watch:js:fix`). ### Fix If it's possible for a linter to fix problems that it finds, add a copy of the script with `:fix` appended to the end that also fixes. ### Check If a script validates code or artifacts without making any modifications, append `:check` to the script name. This modifier is typically used for formatters (e.g., `fmt:check`) to verify that files conform to the expected format and to exit with a non-zero status if any issues are found. Scripts with the `:check` modifier MUST NOT alter any files or outputs. ### Target The name of the target of the action being run. In the case of a `build` script, it SHOULD identify the build artifact(s), e.g. "javascript" or "css" or "website". In the case of a `lint` or `test` script, it SHOULD identify the item(s) being linted or tested. In the case of a `start` script, it SHOULD identify which server is starting. A target MAY refer to a list of affected file extensions (such as `cjs` or `less`) delimited by a `+`. If there is more than one extension, the list SHOULD be alphabetized. When a file extension has variants (such as `cjs` for CommonJS and `mjs` for ESM), the common part of the extension MAY be used instead of explicitly listing out all of the variants (e.g. `js` instead of `cjs+jsx+mjs`). The target SHOULD NOT refer to name of the name of the tool that's performing the action (`eleventy`, `webpack`, etc.). ### Options Additional options that don't fit under the other modifiers. ### Watch If a script watches the filesystem and responds to changes, add `:watch` to the script name. --- --- title: Propose a New Rule eleventyNavigation: key: propose rule parent: contribute to eslint title: Propose a New Rule order: 2 --- ESLint is all about rules. For most of the project's lifetime, we've had over 200 rules, and that list continues to grow. However, we can't just accept any proposed rule because all rules need to work cohesively together. As such, we have some guidelines around which rules can be part of the ESLint core and which are better off as custom rules and plugins. **Note:** As of 2020, we only accept rules related to new ECMAScript features. We prefer that new rules be implemented in plugins. ## Core Rule Guidelines In general, ESLint core rules must be: 1. **Widely applicable.** The rules we distribute need to be of importance to a large number of developers. Individual preferences for uncommon patterns are not supported. 1. **Generic.** Rules cannot be so specific that users will have trouble understanding when to use them. A rule is typically too specific if describing what it does requires more than two "and"s (if a and b and c and d, then this rule warns). 1. **Atomic.** Rules must function completely on their own. Rules are expressly forbidden from knowing about the state or presence of other rules. 1. **Unique.** No two rules can produce the same warning. Overlapping rules confuse end users and there is an expectation that core ESLint rules do not overlap. 1. **Library agnostic.** Rules must be based solely on JavaScript runtime environments and not on specific libraries or frameworks. For example, core rules shouldn't only apply if you're using jQuery but we may have some rules that apply only if you're using Node.js (a runtime). 1. **No conflicts.** No rule must directly conflict with another rule. For example, if we have a rule requiring semicolons, we cannot also have a rule disallowing semicolons (which is why we have one rule, `semi`, that does both). Even though these are the formal criteria for inclusion, each rule is evaluated on its own basis. ## Proposing a Rule If you want to propose a new rule, please see how to [create a pull request](pull-requests) or submit an issue by filling out a [new rule template](https://github.com/eslint/eslint/issues/new/choose). We need all of this information in order to determine whether or not the rule is a good core rule candidate. ## Accepting a Rule In order for a rule to be accepted in the ESLint core, it must: 1. Fulfill all the criteria listed in the "Core Rule Guidelines" section. 1. Have an ESLint team member champion inclusion of the rule. 1. Be related to an ECMAScript feature that has reached stage 4 in the preceding 12 months. Keep in mind that we have over 200 rules, and that is daunting both for end users and the ESLint team (who has to maintain them). As such, any new rules must be deemed of high importance to be considered for inclusion in ESLint. ## Implementation is Your Responsibility The ESLint team doesn't implement new rules that are suggested by users because we have a limited number of people and need to focus on the overall roadmap. Once a rule is accepted, you are responsible for implementing and documenting the rule. You may, alternately, recruit another person to help you implement the rule. The ESLint team member who championed the rule is your resource to help guide you through the rest of this process. ## Alternative: Creating Your Own Rules Remember that ESLint is completely pluggable, which means you can create your own rules and distribute them using plugins. We did this on purpose because we don't want to be the gatekeepers for all possible rules. Even if we don't accept a rule into the core, that doesn't mean you can't have the exact rule that you want. See the [Custom Rules](../extend/custom-rules) and [Create Plugins](../extend/plugins) documentation for more information. --- --- title: Propose a Rule Change eleventyNavigation: key: propose rule change parent: contribute to eslint title: Propose a Rule Change order: 3 --- Occasionally, a core ESLint rule needs to be changed. This is not necessarily a bug, but rather, an enhancement that makes a rule more configurable. In those situations, we will consider making changes to rules. ## Proposing a Rule Change To propose a change to an existing rule, [create a pull request](pull-requests) or [new issue](https://github.com/eslint/eslint/issues/new/choose) and fill out the template. We need all of this information in order to determine whether or not the change is a good candidate for inclusion. ## Accepting a Rule Change In order for a rule change to be accepted into ESLint, it must: 1. Adhere to the [Core Rule Guidelines](propose-new-rule#core-rule-guidelines). 1. Have an ESLint team member champion the change. 1. Be important enough that rule is deemed incomplete without this change. ## Implementation is Your Responsibility The ESLint team doesn't implement rule changes that are suggested by users because we have a limited number of people and need to focus on the overall roadmap. Once a rule change is accepted, you are responsible for implementing and documenting it. You may, alternately, recruit another person to help you. The ESLint team member who championed the rule is your resource to help guide you through the rest of this process. --- --- title: Submit a Pull Request eleventyNavigation: key: submit pull request parent: contribute to eslint title: Submit a Pull Request order: 10 --- If you want to contribute to an ESLint repo, please use a GitHub pull request. This is the fastest way for us to evaluate your code and to merge it into the code base. Please don't file an issue with snippets of code. Doing so means that we need to manually merge the changes in and update any appropriate tests. That decreases the likelihood that your code is going to get included in a timely manner. Please use pull requests. ## Getting Started The ESLint project uses issues to track our work, so it's important to ensure that a proper issue is open: - **If there is an existing issue,** follow the instructions on [work on issues](work-on-issue). - **If there isn't an existing issue,** then open one to describe the change you want to make. Use the appropriate issue template. - **Exceptions:** Small changes, such as bug fixes, documentation updates, or package upgrades don't require an issue. Make sure the pull request description explains clearly what it is you're doing and why. After that, you're ready to start working on code. ## Working with Code ::: important If you'd like to work on a pull request and you've never submitted code before, make sure to set up a [development environment](./development-environment). ::: The process of submitting a pull request is fairly straightforward and generally follows the same pattern each time: 1. [Create a new branch](#step1). 2. [Make your changes](#step2). 3. [Rebase onto upstream](#step3). 4. [Run the tests](#step4). 5. [Double check your submission](#step5). 6. [Push your changes](#step6). 7. [Submit the pull request](#step7). Details about each step are found below. ### Step 1: Create a new branch The first step to sending a pull request is to create a new branch in your ESLint fork. Give the branch a descriptive name that describes what it is you're fixing, such as: ```shell git checkout -b issue1234 ``` You should do all of your development for the issue in this branch. **Note:** Do not combine fixes for multiple issues into one branch. Use a separate branch for each issue you're working on. ### Step 2: Make your changes Make the changes to the code and tests, following the [code conventions](./code-conventions) as you go. Once you have finished, commit the changes to your branch: ```shell git add -A git commit ``` All ESLint projects follow [Conventional Commits](https://www.conventionalcommits.org/) for our commit messages. (Note: we don’t support the optional scope in messages.) Here's an example commit message: ```txt tag: Short description of what you did Longer description here if necessary Fixes #1234 ``` The first line of the commit message (the summary) must have a specific format. This format is checked by our build tools. Although the commit message is not checked directly, it will be used to generate the title of a pull request, which will be checked when the pull request is submitted. The `tag` is one of the following: - `fix` - for a bug fix. - `feat` - either for a backwards-compatible enhancement or for a rule change that adds reported problems. - `fix!` - for a backwards-incompatible bug fix. - `feat!` - for a backwards-incompatible enhancement or feature. - `docs` - changes to documentation only. - `chore` - for changes that aren't user-facing. - `build` - changes to build process only. - `refactor` - a change that doesn't affect APIs or user experience. - `test` - just changes to test files. - `ci` - changes to our CI configuration files and scripts. - `perf` - a code change that improves performance. Use the [labels of the issue you are working on](work-on-issue#issue-labels) to determine the best tag. The message summary should be a one-sentence description of the change, and it must be 72 characters in length or shorter. If the pull request addresses an issue, then the issue number should be mentioned in the body of the commit message in the format `Fixes #1234`. If the commit doesn't completely fix the issue, then use `Refs #1234` instead of `Fixes #1234`. Here are some good commit message summary examples: ```txt build: Update Travis to only test Node 0.10 fix: Semi rule incorrectly flagging extra semicolon chore: Upgrade Esprima to 1.2, switch to using comment attachment ``` ### Step 3: Rebase onto upstream Before you send the pull request, be sure to rebase onto the upstream source. This ensures your code is running on the latest available code. ```shell git fetch upstream git rebase upstream/main ``` ### Step 4: Run the tests After rebasing, be sure to run all of the tests once again to make sure nothing broke: ```shell npm test ``` If there are any failing tests, update your code until all tests pass. ### Step 5: Double check your submission With your code ready to go, this is a good time to double-check your submission to make sure it follows our conventions. Here are the things to check: - The commit message is properly formatted. - The change introduces no functional regression. Be sure to run `npm test` to verify your changes before submitting a pull request. - Make separate pull requests for unrelated changes. Large pull requests with multiple unrelated changes may be closed without merging. - All changes must be accompanied by tests, even if the feature you're working on previously had no tests. - All user-facing changes must be accompanied by appropriate documentation. - Follow the [Code Conventions](./code-conventions). ### Step 6: Push your changes Next, push your changes to your clone: ```shell git push origin issue1234 ``` If you are unable to push because some references are old, do a forced push instead: ```shell git push -f origin issue1234 ``` ### Step 7: Send the pull request Now you're ready to send the pull request. Go to your ESLint fork and then follow the [GitHub documentation](https://help.github.com/articles/creating-a-pull-request) on how to send a pull request. In order to submit code or documentation to an ESLint project, you’ll be asked to sign our CLA when you send your first pull request. (Read more about the OpenJS Foundation CLA process at .) The pull request title is autogenerated from the summary of the first commit, but it can be edited before the pull request is submitted. The description of a pull request should explain what you did and how its effects can be seen. When a pull request is merged, its commits will be squashed into one single commit. The first line of the squashed commit message will contain the title of the pull request and the pull request number. The pull request title format is important because the titles are used to create a changelog for each release. The tag and the issue number help to create more consistent and useful changelogs. ## Following Up Once your pull request is sent, it's time for the team to review it. As such, please make sure to: 1. Monitor the status of the GitHub Actions CI build for your pull request. If it fails, please investigate why. We cannot merge pull requests that fail the CI build for any reason. 1. Respond to comments left on the pull request from team members. Remember, we want to help you land your code, so please be receptive to our feedback. 1. We may ask you to make changes, rebase, or squash your commits. ### Updating the Pull Request Title If your pull request title is in the incorrect format, you'll be asked to update it. You can do so via the GitHub user interface. ### Updating the Code If we ask you to make code changes, there's no need to close the pull request and create a new one. Just go back to the branch on your fork and make your changes. Then, when you're ready, you can add your changes into the branch: ```shell git add -A git commit git push origin issue1234 ``` When updating the code, it's usually better to add additional commits to your branch rather than amending the original commit, because reviewers can easily tell which changes were made in response to a particular review. When we merge pull requests, we will squash all the commits from your branch into a single commit on the `main` branch. The commit messages in subsequent commits do not need to be in any specific format because these commits do not show up in the changelog. ### Rebasing If your code is out-of-date, we might ask you to rebase. That means we want you to apply your changes on top of the latest upstream code. Make sure you have set up a [development environment](./development-environment) and then you can rebase using these commands: ```shell git fetch upstream git rebase upstream/main ``` You might find that there are merge conflicts when you attempt to rebase. Please [resolve the conflicts](https://help.github.com/articles/resolving-merge-conflicts-after-a-git-rebase/) and then do a forced push to your branch: ```shell git push origin issue1234 -f ``` --- --- title: Report Bugs eleventyNavigation: key: report bugs parent: contribute to eslint title: Report Bugs order: 1 --- If you think you've found a bug in ESLint, please [create a new issue](https://github.com/eslint/eslint/issues/new/choose) or a [pull request](pull-requests) on GitHub. Please include as much detail as possible to help us properly address your issue. If we need to triage issues and constantly ask people for more detail, that's time taken away from actually fixing issues. Help us be as efficient as possible by including a lot of detail in your issues. **Note:** If you just have a question that won't necessarily result in a change to ESLint, such as asking how something works or how to contribute, please open a [discussion](https://github.com/eslint/eslint/discussions) or stop by our [Discord server](https://eslint.org/chat) instead of filing an issue. --- --- title: Report a Security Vulnerability eleventyNavigation: key: report security vulnerability parent: contribute to eslint title: Report a Security Vulnerability order: 13 --- To report a security vulnerability in ESLint, please use our [create an advisory form](https://github.com/eslint/eslint/security/advisories/new) on GitHub. --- --- title: Request a Change eleventyNavigation: key: request change parent: contribute to eslint title: Request a Change order: 4 --- If you'd like to request a change to ESLint, please [create a new issue](https://github.com/eslint/eslint/issues/new/choose) on GitHub. Be sure to include the following information: 1. The version of ESLint you are using. 2. The problem you want to solve. 3. Your take on the correct solution to problem. If you're requesting a change to a rule, it's helpful to include this information as well: 1. What you did. 1. What you would like to happen. 1. What actually happened. Please include as much detail as possible to help us properly address your issue. If we need to triage issues and constantly ask people for more detail, that's time taken away from actually fixing issues. Help us be as efficient as possible by including a lot of detail in your issues. **Note:** If you just have a question that won't necessarily result in a change to ESLint, such as asking how something works or how to contribute, please open a [discussion](https://github.com/eslint/eslint/discussions) or stop by our [Discord server](https://eslint.org/chat) instead of filing an issue. --- --- title: Run the Tests eleventyNavigation: key: run tests parent: contribute to eslint title: Run the Tests order: 7 --- Most parts of ESLint have unit tests associated with them. Unit tests are written using [Mocha](https://mochajs.org/) and are required when making contributions to ESLint. You'll find all of the unit tests in the `tests` directory. When you first get the source code, you need to run `npm install` once initially to set ESLint for development. Once you've done that, you can run the tests via: ```shell npm test ``` This automatically starts Mocha and runs all tests in the `tests` directory. You need only add yours and it will automatically be picked up when running tests. ## Running Individual Tests If you want to quickly run just one test file, you can do so by running Mocha directly and passing in the filename. For example: ```shell npm run test:cli tests/lib/rules/no-undef.js ``` If you want to run just one or a subset of `RuleTester` test cases, add `only: true` to each test case or wrap the test case in `RuleTester.only(...)` to add it automatically: ```js ruleTester.run("my-rule", myRule, { valid: [ RuleTester.only("const valid = 42;"), // Other valid cases ], invalid: [ { code: "const invalid = 42;", only: true, }, // Other invalid cases ], }); ``` Running individual tests is useful when you're working on a specific bug and iterating on the solution. You should be sure to run `npm test` before submitting a pull request. `npm test` uses Mocha's `--forbid-only` option to prevent `only` tests from passing full test runs. ## More Control on Unit Testing `npm run test:cli` is an alias of the Mocha cli in `./node_modules/.bin/mocha`. [Options](https://mochajs.org/#command-line-usage) are available to be provided to help to better control the test to run. The default timeout for tests in `npm test` is 10000ms. You may change the timeout by providing `ESLINT_MOCHA_TIMEOUT` environment variable, for example: ```shell ESLINT_MOCHA_TIMEOUT=20000 npm test ``` --- --- title: Work on Issues eleventyNavigation: key: work on issues parent: contribute to eslint title: Work on Issues order: 9 --- Our public [issues tracker](https://github.com/eslint/eslint/issues) lists all of the things we plan on doing as well as suggestions from the community. Before starting to work on an issue, be sure you read through the rest of this page. ## Issue Labels We use labels to indicate the status of issues. The most complete documentation on the labels is found in the [Maintain ESLint documentation](../maintain/manage-issues#when-an-issue-or-pull-request-is-opened), but most contributors should find the information on this page sufficient. The most important questions that labels can help you, as a contributor, answer are: 1. **Is this issue ready for a pull request?** Issues that are ready for pull requests have the [`accepted`](https://github.com/eslint/eslint/labels/accepted) label, which indicates that the team has agreed to accept a pull request. Please do not send pull requests for issues that have not been marked as accepted. 2. **Is this issue right for a beginner?** If you have little or no experience contributing to ESLint, the [`good first issue`](https://github.com/eslint/eslint/labels/good%20first%20issue) label marks appropriate issues. Otherwise, the [`help wanted`](https://github.com/eslint/eslint/labels/help%20wanted) label is an invitation to work on the issue. If you have more experience, you can try working on other issues labeled [`accepted`](https://github.com/eslint/eslint/labels/accepted). 3. **What is this issue about?** Labels describing the nature of issues include `bug`, `enhancement`, `feature`, `question`, `rule`, `documentation`, `core`, `build`, `cli`, `infrastructure`, `breaking`, and `chore`. These are documented in [Maintain ESLint](../maintain/manage-issues#types-of-issues-and-pull-requests). 4. **What is the priority of this issue?** Because we have a lot of issues, we prioritize certain issues above others. The following is the list of priorities, from highest to lowest: 1. **Bugs** - problems with the project are actively affecting users. We want to get these resolved as quickly as possible. 1. **Documentation** - documentation issues are a type of bug in that they actively affect current users. As such, we want to address documentation issues as quickly as possible. 1. **Features** - new functionality that will aid users in the future. 1. **Enhancements** - requested improvements for existing functionality. 1. **Other** - anything else. ## Starting Work ::: important Before starting to work on an existing issue, please check if the issue has been assigned to anyone. If it has, then that person is already responsible for submitting a pull request and you should choose a different issue to work on. ::: ### Claiming an issue If you're going to work on an issue, please _claim_ the issue by adding a comment saying you're working on it and indicating when you think you will complete it. This helps us to avoid duplication of effort. Some examples of good claim comments are: - "I'll take a look at this over the weekend." - "I'm going to do this, give me two weeks." - "Working on this" (as in, I'm working on it right now) The team will validate your claim by assigning the issue to you. ### Offering help on a claimed issue If an issue has an assignee or has already been claimed by someone, please be respectful of that person's desire to complete the work and don't work on it unless you verify that they are no longer interested or would welcome the help. If there hasn't been activity on the issue after two weeks, you can express your interest in helping with the issue. For example: - "Are you still working on this? If not, I'd love to work on it." - "Do you need any help on this? I'm interested." It is up to the assignee to decide if they're going to continue working on the issue or if they'd like your help. If there is no response after a week, please contact a team member for help. ### Unclaiming an issue If you claimed an issue and find you can't finish the work, then add a comment letting people know, for example: - "Sorry, it looks like I don't have time to do this." - "I thought I knew enough to fix this, but it turns out I don't." No one will blame you for backing out of an issue if you are unable to complete it. We just want to keep the process moving along as efficiently as possible. --- --- title: Code Path Analysis Details --- ESLint's rules can use code paths. The code path is execution routes of programs. It forks/joins at such as `if` statements. ```js if (a && b) { foo(); } bar(); ``` :::img-container ![Code Path Example](../assets/images/code-path-analysis/helo.svg) ::: ::: tip You can view code path diagrams for any JavaScript code using [Code Explorer](http://explorer.eslint.org). ::: ## Objects Program is expressed with several code paths. A code path is expressed with objects of two kinds: `CodePath` and `CodePathSegment`. ### `CodePath` `CodePath` expresses whole of one code path. This object exists for each function and the global. This has references of both the initial segment and the final segments of a code path. `CodePath` has the following properties: - `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each code path. - `origin` (`string`) - The reason that the code path was started. May be `"program"`, `"function"`, `"class-field-initializer"`, or `"class-static-block"`. - `initialSegment` (`CodePathSegment`) - The initial segment of this code path. - `finalSegments` (`CodePathSegment[]`) - The final segments which includes both returned and thrown. - `returnedSegments` (`CodePathSegment[]`) - The final segments which includes only returned. - `thrownSegments` (`CodePathSegment[]`) - The final segments which includes only thrown. - `upper` (`CodePath|null`) - The code path of the upper function/global scope. - `childCodePaths` (`CodePath[]`) - Code paths of functions this code path contains. ### `CodePathSegment` `CodePathSegment` is a part of a code path. A code path is expressed with plural `CodePathSegment` objects, it's similar to doubly linked list. Difference from doubly linked list is what there are forking and merging (the next/prev are plural). `CodePathSegment` has the following properties: - `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each segment. - `nextSegments` (`CodePathSegment[]`) - The next segments. If forking, there are two or more. If final, there is nothing. - `prevSegments` (`CodePathSegment[]`) - The previous segments. If merging, there are two or more. If initial, there is nothing. - `reachable` (`boolean`) - A flag which shows whether or not it's reachable. This becomes `false` when preceded by `return`, `throw`, `break`, or `continue`. ## Events There are seven events related to code paths, and you can define event handlers by adding them alongside node visitors in the object exported from the `create()` method of your rule. ```js module.exports = { meta: { // ... }, create(context) { return { /** * This is called at the start of analyzing a code path. * In this time, the code path object has only the initial segment. * * @param {CodePath} codePath - The new code path. * @param {ASTNode} node - The current node. * @returns {void} */ onCodePathStart(codePath, node) { // do something with codePath }, /** * This is called at the end of analyzing a code path. * In this time, the code path object is complete. * * @param {CodePath} codePath - The completed code path. * @param {ASTNode} node - The current node. * @returns {void} */ onCodePathEnd(codePath, node) { // do something with codePath }, /** * This is called when a reachable code path segment was created. * It meant the code path is forked or merged. * In this time, the segment has the previous segments and has been * judged reachable or not. * * @param {CodePathSegment} segment - The new code path segment. * @param {ASTNode} node - The current node. * @returns {void} */ onCodePathSegmentStart(segment, node) { // do something with segment }, /** * This is called when a reachable code path segment was left. * In this time, the segment does not have the next segments yet. * * @param {CodePathSegment} segment - The left code path segment. * @param {ASTNode} node - The current node. * @returns {void} */ onCodePathSegmentEnd(segment, node) { // do something with segment }, /** * This is called when an unreachable code path segment was created. * It meant the code path is forked or merged. * In this time, the segment has the previous segments and has been * judged reachable or not. * * @param {CodePathSegment} segment - The new code path segment. * @param {ASTNode} node - The current node. * @returns {void} */ onUnreachableCodePathSegmentStart(segment, node) { // do something with segment }, /** * This is called when an unreachable code path segment was left. * In this time, the segment does not have the next segments yet. * * @param {CodePathSegment} segment - The left code path segment. * @param {ASTNode} node - The current node. * @returns {void} */ onUnreachableCodePathSegmentEnd(segment, node) { // do something with segment }, /** * This is called when a code path segment was looped. * Usually segments have each previous segments when created, * but when looped, a segment is added as a new previous segment into a * existing segment. * * @param {CodePathSegment} fromSegment - A code path segment of source. * @param {CodePathSegment} toSegment - A code path segment of destination. * @param {ASTNode} node - The current node. * @returns {void} */ onCodePathSegmentLoop(fromSegment, toSegment, node) { // do something with segment }, }; }, }; ``` ### About `onCodePathSegmentLoop` This event is always fired when the next segment has existed already. That timing is the end of loops mainly. For Example 1: ```js while (a) { a = foo(); } bar(); ``` 1. First, the analysis advances to the end of loop. :::img-container ![Loop Event's Example 1](../assets/images/code-path-analysis/loop-event-example-while-1.svg) ::: 2. Second, it creates the looping path. At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. It fires `onCodePathSegmentLoop` instead. :::img-container ![Loop Event's Example 2](../assets/images/code-path-analysis/loop-event-example-while-2.svg) ::: 3. Last, it advances to the end. :::img-container ![Loop Event's Example 3](../assets/images/code-path-analysis/loop-event-example-while-3.svg) ::: For example 2: ```js for (let i = 0; i < 10; ++i) { foo(i); } bar(); ``` 1. `for` statements are more complex. First, the analysis advances to `ForStatement.update`. The `update` segment is hovered at first. :::img-container ![Loop Event's Example 1](../assets/images/code-path-analysis/loop-event-example-for-1.svg) ::: 2. Second, it advances to `ForStatement.body`. Of course the `body` segment is preceded by the `test` segment. It keeps the `update` segment hovering. :::img-container ![Loop Event's Example 2](../assets/images/code-path-analysis/loop-event-example-for-2.svg) ::: 3. Third, it creates the looping path from `body` segment to `update` segment. At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. It fires `onCodePathSegmentLoop` instead. :::img-container ![Loop Event's Example 3](../assets/images/code-path-analysis/loop-event-example-for-3.svg) ::: 4. Fourth, also it creates the looping path from `update` segment to `test` segment. At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. It fires `onCodePathSegmentLoop` instead. :::img-container ![Loop Event's Example 4](../assets/images/code-path-analysis/loop-event-example-for-4.svg) ::: 5. Last, it advances to the end. :::img-container ![Loop Event's Example 5](../assets/images/code-path-analysis/loop-event-example-for-5.svg) ::: ## Usage Examples ### Track current segment position To track the current code path segment position, you can define a rule like this: ```js module.exports = { meta: { // ... }, create(context) { // tracks the code path we are currently in let currentCodePath; // tracks the segments we've traversed in the current code path let currentSegments; // tracks all current segments for all open paths const allCurrentSegments = []; return { onCodePathStart(codePath) { currentCodePath = codePath; allCurrentSegments.push(currentSegments); currentSegments = new Set(); }, onCodePathEnd(codePath) { currentCodePath = codePath.upper; currentSegments = allCurrentSegments.pop(); }, onCodePathSegmentStart(segment) { currentSegments.add(segment); }, onCodePathSegmentEnd(segment) { currentSegments.delete(segment); }, onUnreachableCodePathSegmentStart(segment) { currentSegments.add(segment); }, onUnreachableCodePathSegmentEnd(segment) { currentSegments.delete(segment); }, }; }, }; ``` In this example, the `currentCodePath` variable is used to access the code path that is currently being traversed and the `currentSegments` variable tracks the segments in that code path that have been traversed to that point. Note that `currentSegments` both starts and ends as an empty set, constantly being updated as the traversal progresses. Tracking the current segment position is helpful for analyzing the code path that led to a particular node, as in the next example. ### Find an unreachable node To find an unreachable node, track the current segment position and then use a node visitor to check if any of the segments are reachable. For example, the following looks for any `ExpressionStatement` that is unreachable. ```js function areAnySegmentsReachable(segments) { for (const segment of segments) { if (segment.reachable) { return true; } } return false; } module.exports = { meta: { // ... }, create(context) { // tracks the code path we are currently in let currentCodePath; // tracks the segments we've traversed in the current code path let currentSegments; // tracks all current segments for all open paths const allCurrentSegments = []; return { onCodePathStart(codePath) { currentCodePath = codePath; allCurrentSegments.push(currentSegments); currentSegments = new Set(); }, onCodePathEnd(codePath) { currentCodePath = codePath.upper; currentSegments = allCurrentSegments.pop(); }, onCodePathSegmentStart(segment) { currentSegments.add(segment); }, onCodePathSegmentEnd(segment) { currentSegments.delete(segment); }, onUnreachableCodePathSegmentStart(segment) { currentSegments.add(segment); }, onUnreachableCodePathSegmentEnd(segment) { currentSegments.delete(segment); }, ExpressionStatement(node) { // check all the code path segments that led to this node if (!areAnySegmentsReachable(currentSegments)) { context.report({ message: "Unreachable!", node }); } }, }; }, }; ``` See Also: [no-unreachable](https://github.com/eslint/eslint/blob/HEAD/lib/rules/no-unreachable.js), [no-fallthrough](https://github.com/eslint/eslint/blob/HEAD/lib/rules/no-fallthrough.js), [consistent-return](https://github.com/eslint/eslint/blob/HEAD/lib/rules/consistent-return.js) ### Check if a function is called in every path This example checks whether or not the parameter `cb` is called in every path. Instances of `CodePath` and `CodePathSegment` are shared to every rule. So a rule must not modify those instances. Please use a map of information instead. ```js function hasCb(node, context) { if (node.type.indexOf("Function") !== -1) { const sourceCode = context.sourceCode; return sourceCode.getDeclaredVariables(node).some(function (v) { return v.type === "Parameter" && v.name === "cb"; }); } return false; } function isCbCalled(info) { return info.cbCalled; } module.exports = { meta: { // ... }, create(context) { let funcInfo; const funcInfoStack = []; const segmentInfoMap = Object.create(null); return { // Checks `cb`. onCodePathStart(codePath, node) { funcInfoStack.push(funcInfo); funcInfo = { codePath: codePath, hasCb: hasCb(node, context), currentSegments: new Set(), }; }, onCodePathEnd(codePath, node) { funcInfo = funcInfoStack.pop(); // Checks `cb` was called in every paths. const cbCalled = codePath.finalSegments.every( function (segment) { const info = segmentInfoMap[segment.id]; return info.cbCalled; }, ); if (!cbCalled) { context.report({ message: "`cb` should be called in every path.", node: node, }); } }, // Manages state of code paths and tracks traversed segments onCodePathSegmentStart(segment) { funcInfo.currentSegments.add(segment); // Ignores if `cb` doesn't exist. if (!funcInfo.hasCb) { return; } // Initialize state of this path. const info = (segmentInfoMap[segment.id] = { cbCalled: false, }); // If there are the previous paths, merges state. // Checks `cb` was called in every previous path. if (segment.prevSegments.length > 0) { info.cbCalled = segment.prevSegments.every(isCbCalled); } }, // Tracks unreachable segment traversal onUnreachableCodePathSegmentStart(segment) { funcInfo.currentSegments.add(segment); }, // Tracks reachable segment traversal onCodePathSegmentEnd(segment) { funcInfo.currentSegments.delete(segment); }, // Tracks unreachable segment traversal onUnreachableCodePathSegmentEnd(segment) { funcInfo.currentSegments.delete(segment); }, // Checks reachable or not. CallExpression(node) { // Ignores if `cb` doesn't exist. if (!funcInfo.hasCb) { return; } // Sets marks that `cb` was called. const callee = node.callee; if (callee.type === "Identifier" && callee.name === "cb") { funcInfo.currentSegments.forEach(segment => { const info = segmentInfoMap[segment.id]; info.cbCalled = true; }); } }, }; }, }; ``` See Also: [constructor-super](https://github.com/eslint/eslint/blob/HEAD/lib/rules/constructor-super.js), [no-this-before-super](https://github.com/eslint/eslint/blob/HEAD/lib/rules/no-this-before-super.js) ## Code Path Examples ### Hello World ```js console.log("Hello world!"); ``` :::img-container ![Hello World](../assets/images/code-path-analysis/example-hello-world.svg) ::: ### `IfStatement` ```js if (a) { foo(); } else { bar(); } ``` :::img-container ![`IfStatement`](../assets/images/code-path-analysis/example-ifstatement.svg) ::: ### `IfStatement` (chain) ```js if (a) { foo(); } else if (b) { bar(); } else if (c) { hoge(); } ``` :::img-container ![`IfStatement` (chain)](../assets/images/code-path-analysis/example-ifstatement-chain.svg) ::: ### `SwitchStatement` ```js switch (a) { case 0: foo(); break; case 1: case 2: bar(); // fallthrough case 3: hoge(); break; } ``` :::img-container ![`SwitchStatement`](../assets/images/code-path-analysis/example-switchstatement.svg) ::: ### `SwitchStatement` (has `default`) ```js switch (a) { case 0: foo(); break; case 1: case 2: bar(); // fallthrough case 3: hoge(); break; default: fuga(); break; } ``` :::img-container ![`SwitchStatement` (has `default`)](../assets/images/code-path-analysis/example-switchstatement-has-default.svg) ::: ### `TryStatement` (try-catch) ```js try { foo(); if (a) { throw new Error(); } bar(); } catch (err) { hoge(err); } last(); ``` It creates the paths from `try` block to `catch` block at: - `throw` statements. - The first throwable node (e.g. a function call) in the `try` block. - The end of the `try` block. :::img-container ![`TryStatement` (try-catch)](../assets/images/code-path-analysis/example-trystatement-try-catch.svg) ::: ### `TryStatement` (try-finally) ```js try { foo(); bar(); } finally { fuga(); } last(); ``` If there is not `catch` block, `finally` block has two current segments. At this time when running the previous example to find unreachable nodes, `currentSegments.length` is `2`. One is the normal path, and another is the leaving path (`throw` or `return`). :::img-container ![`TryStatement` (try-finally)](../assets/images/code-path-analysis/example-trystatement-try-finally.svg) ::: ### `TryStatement` (try-catch-finally) ```js try { foo(); bar(); } catch (err) { hoge(err); } finally { fuga(); } last(); ``` :::img-container ![`TryStatement` (try-catch-finally)](../assets/images/code-path-analysis/example-trystatement-try-catch-finally.svg) ::: ### `WhileStatement` ```js while (a) { foo(); if (b) { continue; } bar(); } ``` :::img-container ![`WhileStatement`](../assets/images/code-path-analysis/example-whilestatement.svg) ::: ### `DoWhileStatement` ```js do { foo(); bar(); } while (a); ``` :::img-container ![`DoWhileStatement`](../assets/images/code-path-analysis/example-dowhilestatement.svg) ::: ### `ForStatement` ```js for (let i = 0; i < 10; ++i) { foo(); if (b) { break; } bar(); } ``` :::img-container ![`ForStatement`](../assets/images/code-path-analysis/example-forstatement.svg) ::: ### `ForStatement` (for ever) ```js for (;;) { foo(); } bar(); ``` :::img-container ![`ForStatement` (for ever)](../assets/images/code-path-analysis/example-forstatement-for-ever.svg) ::: ### `ForInStatement` ```js for (let key in obj) { foo(key); } ``` :::img-container ![`ForInStatement`](../assets/images/code-path-analysis/example-forinstatement.svg) ::: ### When there is a function ```js function foo(a) { if (a) { return; } bar(); } foo(false); ``` It creates two code paths. - The global's :::img-container ![When there is a function](../assets/images/code-path-analysis/example-when-there-is-a-function-g.svg) ::: - The function's :::img-container ![When there is a function](../assets/images/code-path-analysis/example-when-there-is-a-function-f.svg) ::: --- --- title: Custom Formatters eleventyNavigation: key: custom formatters parent: extend eslint title: Custom Formatters order: 4 --- Custom formatters let you display linting results in a format that best fits your needs, whether that's in a specific file format, a certain display style, or a format optimized for a particular tool. ESLint also has [built-in formatters](../use/formatters/) that you can use. You can include custom formatters in your project directly or create an npm package to distribute them separately. ## Creating a Custom Formatter Each formatter is a function that receives a `results` object and a `context` as arguments and returns a string. For example, the following is how the built-in [JSON formatter](../use/formatters/#json) is implemented: ```js //my-awesome-formatter.js module.exports = function (results, context) { return JSON.stringify(results, null, 2); }; ``` A formatter can also be an async function (from ESLint v8.4.0), the following shows a simple example: ```js //my-awesome-formatter.js module.exports = async function (results) { const formatted = await asyncTask(); return formatted; }; ``` To run ESLint with this formatter, you can use the [`-f` (or `--format`)](../use/command-line-interface#-f---format) command line flag. You must begin the path to a locally defined custom formatter with a period (`.`), such as `./my-awesome-formatter.js` or `../formatters/my-awesome-formatter.js`. ```bash eslint -f ./my-awesome-formatter.js src/ ``` The remainder of this section contains reference information on how to work with custom formatter functions. ### The `results` Argument The `results` object passed into a formatter is an array of [`LintResult`](../integrate/nodejs-api#-lintresult-type) objects containing the linting results for individual files. Here's an example output: ```js [ { filePath: "/path/to/a/file.js", messages: [ { ruleId: "curly", severity: 2, message: "Expected { after 'if' condition.", line: 2, column: 1, }, { ruleId: "no-process-exit", severity: 2, message: "Don't use process.exit(); throw an error instead.", line: 3, column: 1, }, ], errorCount: 2, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, source: "var err = doStuff();\nif (err) console.log('failed tests: ' + err);\nprocess.exit(1);\n", }, { filePath: "/path/to/Gruntfile.js", messages: [], errorCount: 0, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, }, ]; ``` ### The `context` Argument The formatter function receives a `context` object as its second argument. The object has the following properties: - `color` (optional): If `--color` was set, this property is `true`. If `--no-color` was set, it is `false`. If neither option was provided, the property is omitted. - `cwd`: The current working directory. This value comes from the `cwd` constructor option of the [ESLint](../integrate/nodejs-api#-new-eslintoptions) class. - `maxWarningsExceeded` (optional): If `--max-warnings` was set and the number of warnings exceeded the limit, this property's value is an object containing two properties: - `maxWarnings`: the value of the `--max-warnings` option - `foundWarnings`: the number of lint warnings - `rulesMeta`: The `meta` property values of rules. See the [Custom Rules](custom-rules) page for more information about rules. For example, here's what the object would look like if the rule `no-extra-semi` had been run: ```js { cwd: "/path/to/cwd", maxWarningsExceeded: { maxWarnings: 5, foundWarnings: 6 }, rulesMeta: { "no-extra-semi": { type: "suggestion", docs: { description: "disallow unnecessary semicolons", recommended: true, url: "https://eslint.org/docs/rules/no-extra-semi" }, fixable: "code", schema: [], messages: { unexpected: "Unnecessary semicolon." } } }, } ``` **Note:** if a linting is executed by the deprecated `CLIEngine` class, the `context` argument may be a different value because it is up to the API users. Please check whether the `context` argument is an expected value or not if you want to support legacy environments. ### Passing Arguments to Formatters While formatter functions do not receive arguments in addition to the results object and the context, it is possible to pass additional data into custom formatters using the methods described below. #### Using Environment Variables Custom formatters have access to environment variables and so can change their behavior based on environment variable data. Here's an example that uses a `FORMATTER_SKIP_WARNINGS` environment variable to determine whether to show warnings in the results: ```js module.exports = function (results) { var skipWarnings = process.env.FORMATTER_SKIP_WARNINGS === "true"; var results = results || []; var summary = results.reduce( function (seq, current) { current.messages.forEach(function (msg) { var logMessage = { filePath: current.filePath, ruleId: msg.ruleId, message: msg.message, line: msg.line, column: msg.column, }; if (msg.severity === 1) { logMessage.type = "warning"; seq.warnings.push(logMessage); } if (msg.severity === 2) { logMessage.type = "error"; seq.errors.push(logMessage); } }); return seq; }, { errors: [], warnings: [], }, ); if (summary.errors.length > 0 || summary.warnings.length > 0) { var warnings = !skipWarnings ? summary.warnings : []; // skip the warnings in that case var lines = summary.errors .concat(warnings) .map(function (msg) { return ( "\n" + msg.type + " " + msg.ruleId + "\n " + msg.filePath + ":" + msg.line + ":" + msg.column ); }) .join("\n"); return lines + "\n"; } }; ``` You would run ESLint with this custom formatter and an environment variable set like this: ```bash FORMATTER_SKIP_WARNINGS=true eslint -f ./my-awesome-formatter.js src/ ``` The output would be: ```bash error space-infix-ops src/configs/bundler.js:6:8 error semi src/configs/bundler.js:6:10 ``` #### Complex Argument Passing If you find the custom formatter pattern doesn't provide enough options for the way you'd like to format ESLint results, the best option is to use ESLint's built-in [JSON formatter](../use/formatters/#json) and pipe the output to a second program. For example: ```bash eslint -f json src/ | your-program-that-reads-JSON --option ``` In this example, the `your-program-that-reads-json` program can accept the raw JSON of ESLint results and process it before outputting its own format of the results. You can pass as many command line arguments to that program as are necessary to customize the output. ### Formatting for Terminals Modern terminals like [iTerm2](https://www.iterm2.com/) or [Guake](http://guake-project.org/) expect a specific results format to automatically open filenames when they are clicked. Most terminals support this format for that purpose: ```bash file:line:column ``` ## Packaging a Custom Formatter Custom formatters can be distributed through npm packages. To do so, create an npm package with a name in the format `eslint-formatter-*`, where `*` is the name of your formatter (such as `eslint-formatter-awesome`). Projects should then install the package and use the custom formatter with the [`-f` (or `--format`)](../use/command-line-interface#-f---format) flag like this: ```bash eslint -f awesome src/ ``` Because ESLint knows to look for packages beginning with `eslint-formatter-` when the specified formatter doesn't begin with a period, you do not need to type `eslint-formatter-` when using a packaged custom formatter. Tips for the `package.json` of a custom formatter: - The [`main`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#main) entry point must be the JavaScript file implementing your custom formatter. - Add these [`keywords`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#keywords) to help users find your formatter: - `"eslint"` - `"eslint-formatter"` - `"eslintformatter"` See all [custom formatters on npm](https://www.npmjs.com/search?q=eslint-formatter). ## Examples ### Summary Formatter A formatter that only reports on the total count of errors and warnings will look like this: ```javascript module.exports = function (results, context) { // accumulate the errors and warnings var summary = results.reduce( function (seq, current) { seq.errors += current.errorCount; seq.warnings += current.warningCount; return seq; }, { errors: 0, warnings: 0 }, ); if (summary.errors > 0 || summary.warnings > 0) { return ( "Errors: " + summary.errors + ", Warnings: " + summary.warnings + "\n" ); } return ""; }; ``` Run `eslint` with the above summary formatter: ```bash eslint -f ./my-awesome-formatter.js src/ ``` Will produce the following output: ```bash Errors: 2, Warnings: 4 ``` ### Detailed Formatter A more complex report could look like this: ```javascript module.exports = function (results, context) { var results = results || []; var summary = results.reduce( function (seq, current) { current.messages.forEach(function (msg) { var logMessage = { filePath: current.filePath, ruleId: msg.ruleId, ruleUrl: context.rulesMeta[msg.ruleId].docs.url, message: msg.message, line: msg.line, column: msg.column, }; if (msg.severity === 1) { logMessage.type = "warning"; seq.warnings.push(logMessage); } if (msg.severity === 2) { logMessage.type = "error"; seq.errors.push(logMessage); } }); return seq; }, { errors: [], warnings: [], }, ); if (summary.errors.length > 0 || summary.warnings.length > 0) { var lines = summary.errors .concat(summary.warnings) .map(function (msg) { return ( "\n" + msg.type + " " + msg.ruleId + (msg.ruleUrl ? " (" + msg.ruleUrl + ")" : "") + "\n " + msg.filePath + ":" + msg.line + ":" + msg.column ); }) .join("\n"); return lines + "\n"; } }; ``` When you run ESLint with this custom formatter: ```bash eslint -f ./my-awesome-formatter.js src/ ``` The output is: ```bash error space-infix-ops (https://eslint.org/docs/rules/space-infix-ops) src/configs/bundler.js:6:8 error semi (https://eslint.org/docs/rules/semi) src/configs/bundler.js:6:10 warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars) src/configs/bundler.js:5:6 warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars) src/configs/bundler.js:6:6 warning no-shadow (https://eslint.org/docs/rules/no-shadow) src/configs/bundler.js:65:32 warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars) src/configs/clean.js:3:6 ``` --- --- title: Custom Parsers eleventyNavigation: key: custom parsers parent: extend eslint title: Custom Parsers order: 5 --- {%- from 'components/npm_tabs.macro.html' import npm_tabs with context %} ESLint custom parsers let you extend ESLint to support linting new non-standard JavaScript language features or custom syntax in your code. A parser is responsible for taking your code and transforming it into an abstract syntax tree (AST) that ESLint can then analyze and lint. ## Creating a Custom Parser ### Methods in Custom Parsers A custom parser is a JavaScript object with either a `parse()` or `parseForESLint()` method. The `parse` method only returns the AST, whereas `parseForESLint()` also returns additional values that let the parser customize the behavior of ESLint even more. Both methods should be instance (own) properties and take in the source code as the first argument, and an optional configuration object as the second argument, which is provided as [`parserOptions`](../use/configure/language-options#specifying-parser-options) in a configuration file. ```javascript // customParser.js const espree = require("espree"); // Logs the duration it takes to parse each file. function parse(code, options) { const label = `Parsing file "${options.filePath}"`; console.time(label); const ast = espree.parse(code, options); console.timeEnd(label); return ast; // Only the AST is returned. } module.exports = { parse }; ``` ### `parse` Return Object The `parse` method should simply return the [AST](#ast-specification) object. ### `parseForESLint` Return Object The `parseForESLint` method should return an object that contains the required property `ast` and optional properties `services`, `scopeManager`, and `visitorKeys`. - `ast` should contain the [AST](#ast-specification) object. - `services` can contain any parser-dependent services (such as type checkers for nodes). The value of the `services` property is available to rules as `context.sourceCode.parserServices`. Default is an empty object. - `scopeManager` can be a [ScopeManager](./scope-manager-interface) object. Custom parsers can use customized scope analysis for experimental/enhancement syntaxes. The default is the `ScopeManager` object which is created by [eslint-scope](https://github.com/eslint/js/tree/main/packages/eslint-scope). - Support for `scopeManager` was added in ESLint v4.14.0. ESLint versions that support `scopeManager` will provide an `eslintScopeManager: true` property in `parserOptions`, which can be used for feature detection. - As of ESLint v10.0.0, `ScopeManager` must automatically resolve references to global variables declared in the code, and provide an instance method `addGlobals(names: string[])` that creates variables with the given names in the global scope and resolves references to them. - `visitorKeys` can be an object to customize AST traversal. The keys of the object are the type of AST nodes. Each value is an array of the property names which should be traversed. The default is [KEYS of `eslint-visitor-keys`](https://github.com/eslint/js/tree/main/packages/eslint-visitor-keys#evkkeys). - Support for `visitorKeys` was added in ESLint v4.14.0. ESLint versions that support `visitorKeys` will provide an `eslintVisitorKeys: true` property in `parserOptions`, which can be used for feature detection. ### Meta Data in Custom Parsers For easier debugging and more effective caching of custom parsers, it's recommended to provide a name and version in a `meta` object at the root of your custom parsers, like this: ```js // preferred location of name and version module.exports = { meta: { name: "eslint-parser-custom", version: "1.2.3", }, }; ``` The `meta.name` property should match the npm package name for your custom parser and the `meta.version` property should match the npm package version for your custom parser. The easiest way to accomplish this is by reading this information from your `package.json`. ## AST Specification The AST that custom parsers should create is based on [ESTree](https://github.com/estree/estree). The AST requires some additional properties about detail information of the source code. ### All Nodes All nodes must have `range` property. - `range` (`number[]`) is an array of two numbers. Both numbers are a 0-based index which is the position in the array of source code characters. The first is the start position of the node, the second is the end position of the node. `code.slice(node.range[0], node.range[1])` must be the text of the node. This range does not include spaces/parentheses which are around the node. - `loc` (`SourceLocation`) must not be `null`. [The `loc` property is defined as nullable by ESTree](https://github.com/estree/estree/blob/25834f7247d44d3156030f8e8a2d07644d771fdb/es5.md#node-objects), but ESLint requires this property. The `SourceLocation#source` property can be `undefined`. ESLint does not use the `SourceLocation#source` property. The `parent` property of all nodes must be rewritable. Before any rules have access to the AST, ESLint sets each node's `parent` property to its parent node while traversing. ### The `Program` Node The `Program` node must have `tokens` and `comments` properties. Both properties are an array of the below `Token` interface. ```ts interface Token { type: string; loc: SourceLocation; // See the "All Nodes" section for details of the `range` property. range: [number, number]; value: string; } ``` - `tokens` (`Token[]`) is the array of tokens which affect the behavior of programs. Arbitrary spaces can exist between tokens, so rules check the `Token#range` to detect spaces between tokens. This must be sorted by `Token#range[0]`. - `comments` (`Token[]`) is the array of comment tokens. This must be sorted by `Token#range[0]`. The range indexes of all tokens and comments must not overlap with the range of other tokens and comments. ### The `Literal` Node The `Literal` node must have `raw` property. - `raw` (`string`) is the source code of this literal. This is the same as `code.slice(node.range[0], node.range[1])`. ## Packaging a Custom Parser To publish your custom parser to npm, perform the following: 1. Create a custom parser following the [Creating a Custom Parser](#creating-a-custom-parser) section above. 1. [Create an npm package](https://docs.npmjs.com/creating-node-js-modules) for the custom parser. 1. In your `package.json` file, set the [`main`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#main) field as the file that exports your custom parser. 1. [Publish the npm package.](https://docs.npmjs.com/creating-and-publishing-unscoped-public-packages) For more information on publishing an npm package, refer to the [npm documentation](https://docs.npmjs.com/). Once you've published the npm package, you can use it by adding the package to your project. For example: {{ npm_tabs({ command: "install", packages: ["eslint-parser-myparser"], args: ["--save-dev"] }) }} Then add the custom parser to your ESLint configuration file with the `languageOptions.parser` property. For example: ```js // eslint.config.js const { defineConfig } = require("eslint/config"); const myparser = require("eslint-parser-myparser"); module.exports = defineConfig([ { languageOptions: { parser: myparser, }, // ... rest of configuration }, ]); ``` When using legacy configuration, specify the `parser` property as a string: ```js // .eslintrc.js module.exports = { parser: "eslint-parser-myparser", // ... rest of configuration }; ``` To learn more about using ESLint parsers in your project, refer to [Configure a Parser](../use/configure/parser). ## Example For a complex example of a custom parser, refer to the [`@typescript-eslint/parser`](https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/parser) source code. A simple custom parser that provides a `context.sourceCode.parserServices.foo()` method to rules. ```javascript // awesome-custom-parser.js var espree = require("espree"); function parseForESLint(code, options) { return { ast: espree.parse(code, options), services: { foo: function () { console.log("foo"); }, }, scopeManager: null, visitorKeys: null, }; } module.exports = { parseForESLint }; ``` Include the custom parser in an ESLint configuration file: ```js // eslint.config.js const { defineConfig } = require("eslint/config"); module.exports = defineConfig([ { languageOptions: { parser: require("./path/to/awesome-custom-parser"), }, }, ]); ``` Or if using legacy configuration: ```js // .eslintrc.json { "parser": "./path/to/awesome-custom-parser.js" } ``` --- --- title: Custom Processors (Deprecated) --- ::: warning This documentation is for custom processors using the deprecated eslintrc configuration format. [View the updated documentation](custom-processors). ::: You can also create custom processors that tell ESLint how to process files other than standard JavaScript. For example, you could write a custom processor to extract and process JavaScript from Markdown files ([@eslint/markdown](https://www.npmjs.com/package/@eslint/markdown) includes a custom processor for this). ## Custom Processor Specification In order to create a custom processor, the object exported from your module has to conform to the following interface: ```js module.exports = { processors: { "processor-name": { meta: { name: "eslint-processor-name", version: "1.2.3", }, // takes text of the file and filename preprocess: function (text, filename) { // here, you can strip out any non-JS content // and split into multiple strings to lint return [ // return an array of code blocks to lint { text: code1, filename: "0.js" }, { text: code2, filename: "1.js" }, ]; }, // takes a Message[][] and filename postprocess: function (messages, filename) { // `messages` argument contains two-dimensional array of Message objects // where each top-level array item contains array of lint messages related // to the text that was returned in array from preprocess() method // you need to return a one-dimensional array of the messages you want to keep return [].concat(...messages); }, supportsAutofix: true, // (optional, defaults to false) }, }, }; ``` **The `preprocess` method** takes the file contents and filename as arguments, and returns an array of code blocks to lint. The code blocks will be linted separately but still be registered to the filename. A code block has two properties `text` and `filename`. The `text` property is the content of the block and the `filename` property is the name of the block. The name of the block can be anything, but should include the file extension, which tells the linter how to process the current block. The linter checks the [`--ext` CLI option](../use/command-line-interface#--ext) to see if the current block should be linted and resolves `overrides` configs to check how to process the current block. It's up to the plugin to decide if it needs to return just one part of the non-JavaScript file or multiple pieces. For example in the case of processing `.html` files, you might want to return just one item in the array by combining all scripts. However, for `.md` files, you can return multiple items because each JavaScript block might be independent. **The `postprocess` method** takes a two-dimensional array of arrays of lint messages and the filename. Each item in the input array corresponds to the part that was returned from the `preprocess` method. The `postprocess` method must adjust the locations of all errors to correspond to locations in the original, unprocessed code, and aggregate them into a single flat array and return it. Reported problems have the following location information in each lint message: ```typescript type LintMessage = { /// The 1-based line number where the message occurs. line?: number; /// The 1-based column number where the message occurs. column?: number; /// The 1-based line number of the end location. endLine?: number; /// The 1-based column number of the end location. endColumn?: number; /// If `true`, this is a fatal error. fatal?: boolean; /// Information for an autofix. fix: Fix; /// The error message. message: string; /// The ID of the rule which generated the message, or `null` if not applicable. ruleId: string | null; /// The severity of the message. severity: 0 | 1 | 2; /// Information for suggestions. suggestions?: Suggestion[]; }; type Fix = { range: [number, number]; text: string; }; type Suggestion = { desc?: string; messageId?: string; fix: Fix; }; ``` By default, ESLint does not perform autofixes when a custom processor is used, even when the `--fix` flag is enabled on the command line. To allow ESLint to autofix code when using your processor, you should take the following additional steps: 1. Update the `postprocess` method to additionally transform the `fix` property of reported problems. All autofixable problems have a `fix` property, which is an object with the following schema: ```typescript { range: [number, number], text: string } ``` The `range` property contains two indexes in the code, referring to the start and end location of a contiguous section of text that will be replaced. The `text` property refers to the text that will replace the given range. In the initial list of problems, the `fix` property will refer to a fix in the processed JavaScript. The `postprocess` method should transform the object to refer to a fix in the original, unprocessed file. 2. Add a `supportsAutofix: true` property to the processor. You can have both rules and custom processors in a single plugin. You can also have multiple processors in one plugin. To support multiple extensions, add each one to the `processors` element and point them to the same object. **The `meta` object** helps ESLint cache the processor and provide more friendly debug message. The `meta.name` property should match the processor name and the `meta.version` property should match the npm package version for your processors. The easiest way to accomplish this is by reading this information from your `package.json`. ## Specifying Processor in Config Files To use a processor, add its ID to a `processor` section in the config file. Processor ID is a concatenated string of plugin name and processor name with a slash as a separator. This can also be added to a `overrides` section of the config, to specify which processors should handle which files. For example: ```yml plugins: - a-plugin overrides: - files: "*.md" processor: a-plugin/markdown ``` See [Specify a Processor](../use/configure/plugins#specify-a-processor) in the Plugin Configuration documentation for more details. ## File Extension-named Processor ::: warning This feature is deprecated and only works in eslintrc-style configuration files. Flat config files do not automatically apply processors; you need to explicitly set the `processor` property. ::: If a custom processor name starts with `.`, ESLint handles the processor as a **file extension-named processor**. ESLint applies the processor to files with that filename extension automatically. Users don't need to specify the file extension-named processors in their config files. For example: ```js module.exports = { processors: { // This processor will be applied to `*.md` files automatically. // Also, you can use this processor as "plugin-id/.md" explicitly. ".md": { preprocess(text, filename) { /* ... */ }, postprocess(messageLists, filename) { /* ... */ } } // This processor will not be applied to any files automatically. // To use this processor, you must explicitly specify it // in your configuration as "plugin-id/markdown". "markdown": { preprocess(text, filename) { /* ... */ }, postprocess(messageLists, filename) { /* ... */ } } } } ``` You can also use the same custom processor with multiple filename extensions. The following example shows using the same processor for both `.md` and `.mdx` files: ```js const myCustomProcessor = { /* processor methods */ }; module.exports = { // The same custom processor is applied to both // `.md` and `.mdx` files. processors: { ".md": myCustomProcessor, ".mdx": myCustomProcessor, }, }; ``` --- --- title: Custom Processors eleventyNavigation: key: custom processors parent: create plugins title: Custom Processors order: 3 --- You can also create custom processors that tell ESLint how to process files other than standard JavaScript. For example, you could write a custom processor to extract and process JavaScript from Markdown files ([@eslint/markdown](https://www.npmjs.com/package/@eslint/markdown) includes a custom processor for this). ::: tip This page explains how to create a custom processor for use with the flat config format. For the deprecated eslintrc format, [see the deprecated documentation](custom-processors-deprecated). ::: ## Custom Processor Specification In order to create a custom processor, the object exported from your module has to conform to the following interface: ```js const plugin = { meta: { name: "eslint-plugin-example", version: "1.2.3", }, processors: { "processor-name": { meta: { name: "eslint-processor-name", version: "1.2.3", }, // takes text of the file and filename preprocess(text, filename) { // here, you can strip out any non-JS content // and split into multiple strings to lint return [ // return an array of code blocks to lint { text: code1, filename: "0.js" }, { text: code2, filename: "1.js" }, ]; }, // takes a Message[][] and filename postprocess(messages, filename) { // `messages` argument contains two-dimensional array of Message objects // where each top-level array item contains array of lint messages related // to the text that was returned in array from preprocess() method // you need to return a one-dimensional array of the messages you want to keep return [].concat(...messages); }, supportsAutofix: true, // (optional, defaults to false) }, }, }; // for ESM export default plugin; // OR for CommonJS module.exports = plugin; ``` **The `preprocess` method** takes the file contents and filename as arguments, and returns an array of code blocks to lint. The code blocks will be linted separately but still be registered to the filename. A code block has two properties `text` and `filename`. The `text` property is the content of the block and the `filename` property is the name of the block. The name of the block can be anything, but should include the file extension, which tells ESLint how to process the current block. ESLint checks matching `files` entries in the project's config to determine if the code blocks should be linted. It's up to the plugin to decide if it needs to return just one part of the non-JavaScript file or multiple pieces. For example in the case of processing `.html` files, you might want to return just one item in the array by combining all scripts. However, for `.md` files, you can return multiple items because each JavaScript block might be independent. **The `postprocess` method** takes a two-dimensional array of arrays of lint messages and the filename. Each item in the input array corresponds to the part that was returned from the `preprocess` method. The `postprocess` method must adjust the locations of all errors to correspond to locations in the original, unprocessed code, and aggregate them into a single flat array and return it. Reported problems have the following location information in each lint message: ```typescript type LintMessage = { /// The 1-based line number where the message occurs. line?: number; /// The 1-based column number where the message occurs. column?: number; /// The 1-based line number of the end location. endLine?: number; /// The 1-based column number of the end location. endColumn?: number; /// If `true`, this is a fatal error. fatal?: boolean; /// Information for an autofix. fix: Fix; /// The error message. message: string; /// The ID of the rule which generated the message, or `null` if not applicable. ruleId: string | null; /// The severity of the message. severity: 0 | 1 | 2; /// Information for suggestions. suggestions?: Suggestion[]; }; type Fix = { range: [number, number]; text: string; }; type Suggestion = { desc?: string; messageId?: string; fix: Fix; }; ``` By default, ESLint does not perform autofixes when a custom processor is used, even when the `--fix` flag is enabled on the command line. To allow ESLint to autofix code when using your processor, you should take the following additional steps: 1. Update the `postprocess` method to additionally transform the `fix` property of reported problems. All autofixable problems have a `fix` property, which is an object with the following schema: ```typescript { range: [number, number], text: string } ``` The `range` property contains two indexes in the code, referring to the start and end location of a contiguous section of text that will be replaced. The `text` property refers to the text that will replace the given range. In the initial list of problems, the `fix` property will refer to a fix in the processed JavaScript. The `postprocess` method should transform the object to refer to a fix in the original, unprocessed file. 2. Add a `supportsAutofix: true` property to the processor. You can have both rules and custom processors in a single plugin. You can also have multiple processors in one plugin. To support multiple extensions, add each one to the `processors` element and point them to the same object. ### How `meta` Objects are Used The `meta` object helps ESLint cache configurations that use a processor and to provide more friendly debug messages. #### Plugin `meta` Object The [plugin `meta` object](plugins#meta-data-in-plugins) provides information about the plugin itself. When a processor is specified using the string format `plugin-name/processor-name`, ESLint automatically uses the plugin `meta` to generate a name for the processor. This is the most common case for processors. Example: ```js // eslint.config.js import { defineConfig } from "eslint/config"; import example from "eslint-plugin-example"; export default defineConfig([ { files: ["**/*.txt"], // apply processor to text files plugins: { example, }, processor: "example/processor-name", }, // ... other configs ]); ``` In this example, the processor name is `"example/processor-name"`, and that's the value that will be used for serializing configurations. #### Processor `meta` Object Each processor can also specify its own `meta` object. This information is used when the processor object is passed directly to `processor` in a configuration. In that case, ESLint doesn't know which plugin the processor belongs to. The `meta.name` property should match the processor name and the `meta.version` property should match the npm package version for your processors. The easiest way to accomplish this is by reading this information from your `package.json`. Example: ```js // eslint.config.js import { defineConfig } from "eslint/config"; import example from "eslint-plugin-example"; export default defineConfig([ { files: ["**/*.txt"], processor: example.processors["processor-name"], }, // ... other configs ]); ``` In this example, specifying `example.processors["processor-name"]` directly uses the processor's own `meta` object, which must be defined to ensure proper handling when the processor is not referenced through the plugin name. #### Why Both Meta Objects are Needed It is recommended that both the plugin and each processor provide their respective meta objects. This ensures that features relying on meta objects, such as `--print-config` and `--cache`, work correctly regardless of how the processor is specified in the configuration. ## Specifying Processor in Config Files In order to use a processor from a plugin in a configuration file, import the plugin and include it in the `plugins` key, specifying a namespace. Then, use that namespace to reference the processor in the `processor` configuration, like this: ```js // eslint.config.js import { defineConfig } from "eslint/config"; import example from "eslint-plugin-example"; export default defineConfig([ { files: ["**/*.txt"], plugins: { example, }, processor: "example/processor-name", }, ]); ``` See [Specify a Processor](../use/configure/plugins#specify-a-processor) in the Plugin Configuration documentation for more details. --- --- title: Custom Rule Tutorial eleventyNavigation: key: custom rule tutorial parent: create plugins title: Custom Rule Tutorial order: 1 --- {%- from 'components/npm_tabs.macro.html' import npm_tabs with context %} {%- from 'components/npx_tabs.macro.html' import npx_tabs %} This tutorial covers how to create a custom rule for ESLint and distribute it with a plugin. You can create custom rules to validate if your code meets a certain expectation, and determine what to do if it does not meet that expectation. Plugins package custom rules and other configuration, allowing you to easily share and reuse them in different projects. To learn more about custom rules and plugins refer to the following documentation: - [Custom Rules](custom-rules) - [Plugins](plugins) ## Why Create a Custom Rule? Create a custom rule if the ESLint [built-in rules](../rules/) and community-published custom rules do not meet your needs. You might create a custom rule to enforce a best practice for your company or project, prevent a particular bug from recurring, or ensure compliance with a style guide. Before creating a custom rule that isn't specific to your company or project, it's worth searching the web to see if someone has published a plugin with a custom rule that solves your use case. It's quite possible the rule may already exist. ## Prerequisites Before you begin, make sure you have the following installed in your development environment: - [Node.js](https://nodejs.org/en/download/) - [npm](https://www.npmjs.com/) This tutorial also assumes that you have a basic understanding of ESLint and ESLint rules. ## The Custom Rule The custom rule in this tutorial requires that all `const` variables named `foo` are assigned the string literal `"bar"`. The rule is defined in the file `enforce-foo-bar.js`. The rule also suggests replacing any other value assigned to `const foo` with `"bar"`. For example, say you had the following `foo.js` file: ```javascript // foo.js const foo = "baz123"; ``` Running ESLint with the rule would flag `"baz123"` as an incorrect value for variable `foo`. If ESLint is running in autofix mode, then ESLint would fix the file to contain the following: ```javascript // foo.js const foo = "bar"; ``` ## Step 1: Set up Your Project First, create a new project for your custom rule. Create a new directory, initiate a new npm project in it, and create a new file for the custom rule: ```shell mkdir eslint-custom-rule-example # create directory cd eslint-custom-rule-example # enter the directory npm init -y # init new npm project touch enforce-foo-bar.js # create file enforce-foo-bar.js ``` ## Step 2: Stub Out the Rule File In the `enforce-foo-bar.js` file, add some scaffolding for the `enforce-foo-bar` custom rule. Also, add a `meta` object with some basic information about the rule. ```javascript // enforce-foo-bar.js module.exports = { meta: { // TODO: add metadata }, create(context) { return { // TODO: add callback function(s) }; }, }; ``` ## Step 3: Add Rule Metadata Before writing the rule, add some metadata to the rule object. ESLint uses this information when running the rule. Start by exporting an object with a `meta` property containing the rule's metadata, such as the rule type, documentation, and fixability. In this case, the rule type is "problem," the description is "Enforce that a variable named `foo` can only be assigned a value of 'bar'.", and the rule is fixable by modifying the code. ```javascript // enforce-foo-bar.js module.exports = { meta: { type: "problem", docs: { description: "Enforce that a variable named `foo` can only be assigned a value of 'bar'.", }, fixable: "code", schema: [], }, create(context) { return { // TODO: add callback function(s) }; }, }; ``` To learn more about rule metadata, refer to [Rule Structure](custom-rules#rule-structure). ## Step 4: Add Rule Visitor Methods Define the rule's `create` function, which accepts a `context` object and returns an object with a property for each syntax node type you want to handle. In this case, you want to handle `VariableDeclarator` nodes. You can choose any [ESTree node type](https://github.com/estree/estree) or [selector](selectors). ::: tip You can view the AST for any JavaScript code using [Code Explorer](http://explorer.eslint.org). This is helpful in determining the type of nodes you'd like to target. ::: Inside the `VariableDeclarator` visitor method, check if the node represents a `const` variable declaration, if its name is `foo`, and if it's not assigned to the string `"bar"`. You do this by evaluating the `node` passed to the `VariableDeclaration` method. If the `const foo` declaration is assigned a value of `"bar"`, then the rule does nothing. If `const foo` **is not** assigned a value of `"bar"`, then `context.report()` reports an error to ESLint. The error report includes information about the error and how to fix it. ```javascript // enforce-foo-bar.js {% raw %} module.exports = { meta: { type: "problem", docs: { description: "Enforce that a variable named `foo` can only be assigned a value of 'bar'." }, fixable: "code", schema: [] }, create(context) { return { // Performs action in the function on every variable declarator VariableDeclarator(node) { // Check if a `const` variable declaration if (node.parent.kind === "const") { // Check if variable name is `foo` if (node.id.type === "Identifier" && node.id.name === "foo") { // Check if value of variable is "bar" if (node.init && node.init.type === "Literal" && node.init.value !== "bar") { /* * Report error to ESLint. Error message uses * a message placeholder to include the incorrect value * in the error message. * Also includes a `fix(fixer)` function that replaces * any values assigned to `const foo` with "bar". */ context.report({ node, message: 'Value other than "bar" assigned to `const foo`. Unexpected value: {{ notBar }}.', data: { notBar: node.init.value }, fix(fixer) { return fixer.replaceText(node.init, '"bar"'); } }); } } } } }; } }; {% endraw %} ``` ## Step 5: Set up Testing With the rule written, you can test it to make sure it's working as expected. ESLint provides the built-in [`RuleTester`](../integrate/nodejs-api#ruletester) class to test rules. You do not need to use third-party testing libraries to test ESLint rules, but `RuleTester` works seamlessly with tools like Mocha and Jest. Next, create the file for the tests, `enforce-foo-bar.test.js`: ```shell touch enforce-foo-bar.test.js ``` You will use the `eslint` package in the test file. Install it as a development dependency: {{ npm_tabs({ command: "install", packages: ["eslint"], args: ["--save-dev"] }) }} And add a test script to your `package.json` file to run the tests: ```javascript // package.json { // ...other configuration "scripts": { "test": "node enforce-foo-bar.test.js" }, // ...other configuration } ``` ## Step 6: Write the Test To write the test using `RuleTester`, import the class and your custom rule into the `enforce-foo-bar.test.js` file. The `RuleTester#run()` method tests the rule against valid and invalid test cases. If the rule fails to pass any of the test scenarios, this method throws an error. `RuleTester` requires that at least one valid and one invalid test scenario be present. ```javascript // enforce-foo-bar.test.js const { RuleTester } = require("eslint"); const fooBarRule = require("./enforce-foo-bar"); const ruleTester = new RuleTester({ // Must use at least ecmaVersion 2015 because // that's when `const` variables were introduced. languageOptions: { ecmaVersion: 2015 }, }); // Throws error if the tests in ruleTester.run() do not pass ruleTester.run( "enforce-foo-bar", // rule name fooBarRule, // rule code { // checks // 'valid' checks cases that should pass valid: [ { code: "const foo = 'bar';", }, ], // 'invalid' checks cases that should not pass invalid: [ { code: "const foo = 'baz';", output: 'const foo = "bar";', errors: 1, }, ], }, ); console.log("All tests passed!"); ``` Run the test with the following command: ```shell npm test ``` If the test passes, you should see the following in your console: ```shell All tests passed! ``` ## Step 7: Bundle the Custom Rule in a Plugin Now that you've written the custom rule and validated that it works, you can include it in a plugin. Using a plugin, you can share the rule in an npm package to use in other projects. Create the file for the plugin: ```shell touch eslint-plugin-example.js ``` And now write the plugin code. Plugins are just exported JavaScript objects. To include a rule in a plugin, include it in the plugin's `rules` object, which contains key-value pairs of rule names and their source code. To learn more about creating plugins, refer to [Create Plugins](plugins). ```javascript // eslint-plugin-example.js const fooBarRule = require("./enforce-foo-bar"); const plugin = { rules: { "enforce-foo-bar": fooBarRule } }; module.exports = plugin; ``` ## Step 8: Use the Plugin Locally You can use a locally defined plugin to execute the custom rule in your project. To use a local plugin, specify the path to the plugin in the `plugins` property of your ESLint configuration file. You might want to use a locally defined plugin in one of the following scenarios: - You want to test the plugin before publishing it to npm. - You want to use a plugin, but do not want to publish it to npm. Before you can add the plugin to the project, create an ESLint configuration for your project using a [flat configuration file](../use/configure/configuration-files), `eslint.config.js`: ```shell touch eslint.config.js ``` Then, add the following code to `eslint.config.js`: ```javascript // eslint.config.js "use strict"; // Import the `defineConfig` helper function const { defineConfig } = require("eslint/config"); // Import the ESLint plugin locally const eslintPluginExample = require("./eslint-plugin-example"); module.exports = defineConfig([ { files: ["**/*.js"], languageOptions: { sourceType: "commonjs", ecmaVersion: "latest", }, // Using the eslint-plugin-example plugin defined locally plugins: { example: eslintPluginExample }, rules: { "example/enforce-foo-bar": "error", }, }, ]); ``` Before you can test the rule, you must create a file to test the rule on. Create a file `example.js`: ```shell touch example.js ``` Add the following code to `example.js`: ```javascript // example.js function correctFooBar() { const foo = "bar"; } function incorrectFoo() { const foo = "baz"; // Problem! } ``` Now you're ready to test the custom rule with the locally defined plugin. Run ESLint on `example.js`: {{ npx_tabs({ package: "eslint", args: ["example.js"] }) }} This produces the following output in the terminal: ```text //eslint-custom-rule-example/example.js 8:11 error Value other than "bar" assigned to `const foo`. Unexpected value: baz example/enforce-foo-bar ✖ 1 problem (1 error, 0 warnings) 1 error and 0 warnings potentially fixable with the `--fix` option. ``` ## Step 9: Publish the Plugin To publish a plugin containing a rule to npm, you need to configure the `package.json`. Add the following in the corresponding fields: 1. `"name"`: A unique name for the package. No other package on npm can have the same name. 1. `"main"`: The relative path to the plugin file. Following this example, the path is `"eslint-plugin-example.js"`. 1. `"description"`: A description of the package that's viewable on npm. 1. `"peerDependencies"`: Add `"eslint": ">=9.0.0"` as a peer dependency. Any version greater than or equal to that is necessary to use the plugin. Declaring `eslint` as a peer dependency requires that users add the package to the project separately from the plugin. 1. `"keywords"`: Include the standard keywords `["eslint", "eslintplugin", "eslint-plugin"]` to make the package easy to find. You can add any other keywords that might be relevant to your plugin as well. A complete annotated example of what a plugin's `package.json` file should look like: ```javascript // package.json { // Name npm package. // Add your own package name. eslint-plugin-example is taken! "name": "eslint-plugin-example", "version": "1.0.0", "description": "ESLint plugin for enforce-foo-bar rule.", "main": "eslint-plugin-example.js", // plugin entry point "scripts": { "test": "node enforce-foo-bar.test.js" }, // Add eslint>=9.0.0 as a peer dependency. "peerDependencies": { "eslint": ">=9.0.0" }, // Add these standard keywords to make plugin easy to find! "keywords": [ "eslint", "eslintplugin", "eslint-plugin" ], "author": "", "license": "ISC", "devDependencies": { "eslint": "^9.0.0" } } ``` To publish the package, run `npm publish` and follow the CLI prompts. You should see the package live on npm! ## Step 10: Use the Published Custom Rule Next, you can use the published plugin. Run the following command in your project to download the package: {{ npm_tabs({ command: "install", packages: ["eslint-plugin-example"], args: ["--save-dev"], comment: "Add your package name here" }) }} Update the `eslint.config.js` to use the packaged version of the plugin: ```javascript // eslint.config.js "use strict"; // Import the plugin downloaded from npm const eslintPluginExample = require("eslint-plugin-example"); // ... rest of configuration ``` Now you're ready to test the custom rule. Run ESLint on the `example.js` file you created in step 8, now with the downloaded plugin: {{ npx_tabs({ package: "eslint", args: ["example.js"] }) }} This produces the following output in the terminal: ```text //eslint-custom-rule-example/example.js 8:11 error Value other than "bar" assigned to `const foo`. Unexpected value: baz example/enforce-foo-bar ✖ 1 problem (1 error, 0 warnings) 1 error and 0 warnings potentially fixable with the `--fix` option. ``` As you can see in the above message, you can actually fix the issue with the `--fix` flag, correcting the variable assignment to be `"bar"`. Run ESLint again with the `--fix` flag: {{ npx_tabs({ package: "eslint", args: ["example.js", "--fix"] }) }} There is no error output in the terminal when you run this, but you can see the fix applied in `example.js`. You should see the following: ```javascript // example.js // ... rest of file function incorrectFoo() { const foo = "bar"; // Fixed! } ``` ## Summary In this tutorial, you've made a custom rule that requires all `const` variables named `foo` to be assigned the string `"bar"` and suggests replacing any other value assigned to `const foo` with `"bar"`. You've also added the rule to a plugin, and published the plugin on npm. Through doing this, you've learned the following practices which you can apply to create other custom rules and plugins: 1. Creating a custom ESLint rule 1. Testing the custom rule 1. Bundling the rule in a plugin 1. Publishing the plugin 1. Using the rule from the plugin ## View the Tutorial Code You can view the annotated source code for the tutorial [here](https://github.com/eslint/eslint/tree/main/docs/_examples/custom-rule-tutorial-code). --- --- title: Working with Rules (Deprecated) --- As of ESLint v9.0.0, the function-style rule format that was current in ESLint <= 2.13.1 is no longer supported. [This is the most recent rule format](./custom-rules). --- --- title: Custom Rules eleventyNavigation: key: custom rules parent: create plugins title: Custom Rules order: 2 --- You can create custom rules to use with ESLint. You might want to create a custom rule if the [core rules](../rules/) do not cover your use case. Here's the basic format of a custom rule: ```js // customRule.js module.exports = { meta: { type: "suggestion", docs: { description: "Description of the rule", }, fixable: "code", schema: [], // no options }, create: function (context) { return { // callback functions }; }, }; ``` ::: warning The core rules shipped in the `eslint` package are not considered part of the public API and are not designed to be extended from. Building on top of these rules is fragile and will most likely result in your rules breaking completely at some point in the future. If you're interested in creating a rule that is similar to a core rule, you should first copy the rule file into your project and proceed from there. ::: ## Rule Structure The source file for a rule exports an object with the following properties. Both custom rules and core rules follow this format. `meta`: (`object`) Contains metadata for the rule: - `type`: (`string`) Indicates the type of rule, which is one of `"problem"`, `"suggestion"`, or `"layout"`: - `"problem"`: The rule is identifying code that either will cause an error or may cause a confusing behavior. Developers should consider this a high priority to resolve. - `"suggestion"`: The rule is identifying something that could be done in a better way but no errors will occur if the code isn't changed. - `"layout"`: The rule cares primarily about whitespace, semicolons, commas, and parentheses, all the parts of the program that determine how the code looks rather than how it executes. These rules work on parts of the code that aren't specified in the AST. - `docs`: (`object`) Properties often used for documentation generation and tooling. Required for core rules and optional for custom rules. Custom rules can include additional properties here as needed. - `description`: (`string`) Provides a short description of the rule. For core rules, this is used in [rules index](../rules/). - `recommended`: (`unknown`) For core rules, this is a boolean value specifying whether the rule is enabled by the `recommended` config from `@eslint/js`. - `url`: (`string`) Specifies the URL at which the full documentation can be accessed. Code editors often use this to provide a helpful link on highlighted rule violations. - `messages`: (`object`) An object containing violation and suggestion messages for the rule. Required for core rules and optional for custom rules. Messages can be referenced by their keys (`messageId`s) in `context.report()` calls. See [`messageId`s](#messageids) for more information. - `fixable`: (`string`) Either `"code"` or `"whitespace"` if the `--fix` option on the [command line](../use/command-line-interface#--fix) automatically fixes problems reported by the rule. **Important:** the `fixable` property is mandatory for fixable rules. If this property isn't specified, ESLint will throw an error whenever the rule attempts to produce a fix. Omit the `fixable` property if the rule is not fixable. - `hasSuggestions`: (`boolean`) Specifies whether rules can return suggestions (defaults to `false` if omitted). **Important:** the `hasSuggestions` property is mandatory for rules that provide suggestions. If this property isn't set to `true`, ESLint will throw an error whenever the rule attempts to produce a suggestion. Omit the `hasSuggestions` property if the rule does not provide suggestions. - `schema`: (`object | array | false`) Specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../use/configure/rules). Mandatory when the rule has options. - `defaultOptions`: (`array`) Specifies [default options](#option-defaults) for the rule. If present, any user-provided options in their config will be merged on top of them recursively. - `deprecated`: (`boolean | DeprecatedInfo`) Indicates whether the rule has been deprecated. You may omit the `deprecated` property if the rule has not been deprecated. There is a dedicated page for the [DeprecatedInfo](./rule-deprecation) - `replacedBy`: (`array`, **Deprecated** Use `meta.deprecated.replacedBy` instead.) In the case of a deprecated rule, specify replacement rule(s). `create()`: Returns an object with methods that ESLint calls to "visit" nodes while traversing the abstract syntax tree (AST as defined by [ESTree](https://github.com/estree/estree)) of JavaScript code: - If a key is a node type or a [selector](./selectors), ESLint calls that **visitor** function while going **down** the tree. - If a key is a node type or a [selector](./selectors) plus `:exit`, ESLint calls that **visitor** function while going **up** the tree. - If a key is an event name, ESLint calls that **handler** function for [code path analysis](code-path-analysis). A rule can use the current node and its surrounding tree to report or fix problems. Here are methods for the [array-callback-return](../rules/array-callback-return) rule: ```js function checkLastSegment (node) { // report problem for function if last code path segment is reachable } module.exports = { meta: { ... }, create: function(context) { // declare the state of the rule return { ReturnStatement: function(node) { // at a ReturnStatement node while going down }, // at a function expression node while going up: "FunctionExpression:exit": checkLastSegment, "ArrowFunctionExpression:exit": checkLastSegment, onCodePathStart: function (codePath, node) { // at the start of analyzing a code path }, onCodePathEnd: function(codePath, node) { // at the end of analyzing a code path } }; } }; ``` ::: tip You can view the complete AST for any JavaScript code using [Code Explorer](http://explorer.eslint.org). ::: ## The Context Object The `context` object is the only argument of the `create` method in a rule. For example: ```js // customRule.js module.exports = { meta: { ... }, // `context` object is the argument create(context) { // ... } }; ``` As the name implies, the `context` object contains information that is relevant to the context of the rule. The `context` object has the following properties: - `id`: (`string`) The rule ID. - `filename`: (`string`) The filename associated with the source. - `physicalFilename`: (`string`) When linting a file, it provides the full path of the file on disk without any code block information. When linting text, it provides the value passed to `—stdin-filename` or `` if not specified. - `cwd`: (`string`) The `cwd` option passed to the [Linter](../integrate/nodejs-api#linter). It is a path to a directory that should be considered the current working directory. - `options`: (`array`) An array of the [configured options](../use/configure/rules) for this rule. This array does not include the rule severity (see the [dedicated section](#accessing-options-passed-to-a-rule)). - `sourceCode`: (`object`) A `SourceCode` object that you can use to work with the source that was passed to ESLint (see [Accessing the Source Code](#accessing-the-source-code)). - `settings`: (`object`) The [shared settings](../use/configure/configuration-files#configuring-shared-settings) from the configuration. - `languageOptions`: (`object`) more details for each property [here](../use/configure/language-options) - `sourceType`: (`'script' | 'module' | 'commonjs'`) The mode for the current file. - `ecmaVersion`: (`number`) The ECMA version used to parse the current file. - `parser`: (`object`): The parser used to parse the current file. - `parserOptions`: (`object`) The parser options configured for this file. - `globals`: (`object`) The specified globals. Additionally, the `context` object has the following methods: - `report(descriptor)`. Reports a problem in the code (see the [dedicated section](#reporting-problems)). **Note:** Earlier versions of ESLint supported additional methods on the `context` object. Those methods were removed in the new format and should not be relied upon. ### Reporting Problems The main method you'll use when writing custom rules is `context.report()`, which publishes a warning or error (depending on the configuration being used). This method accepts a single argument, which is an object containing the following properties: - `messageId`: (`string`) The ID of the message (see [messageIds](#messageids)) (recommended over `message`). - `message`: (`string`) The problem message (alternative to `messageId`). - `node`: (optional `object`) This can be an AST node, a token, or a comment related to the problem. If present and `loc` is not specified, then the starting location of the node is used as the location of the problem. - `loc`: (optional `object`) Specifies the location of the problem. If both `loc` and `node` are specified, then the location is used from `loc` instead of `node`. - `start`: An object of the start location. - `line`: (`number`) The 1-based line number at which the problem occurred. - `column`: (`number`) The 0-based column number at which the problem occurred. - `end`: An object of the end location. - `line`: (`number`) The 1-based line number at which the problem occurred. - `column`: (`number`) The 0-based column number at which the problem occurred. - `data`: (optional `object`) [Placeholder](#using-message-placeholders) data for `message`. - `fix(fixer)`: (optional `function`) Applies a [fix](#applying-fixes) to resolve the problem. Note that at least one of `node` or `loc` is required. The simplest example is to use just `node` and `message`: ```js context.report({ node: node, message: "Unexpected identifier", }); ``` The node contains all the information necessary to figure out the line and column number of the offending text as well as the source text representing the node. #### Using Message Placeholders You can also use placeholders in the message and provide `data`: ```js {% raw %} context.report({ node: node, message: "Unexpected identifier: {{ identifier }}", data: { identifier: node.name } }); {% endraw %} ``` Note that leading and trailing whitespace is optional in message parameters. The node contains all the information necessary to figure out the line and column number of the offending text as well as the source text representing the node. #### `messageId`s `messageId`s are the recommended approach to reporting messages in `context.report()` calls because of the following benefits: - Rule violation messages can be stored in a central `meta.messages` object for convenient management. - Rule violation messages do not need to be repeated in both the rule file and rule test file. - As a result, the barrier for changing rule violation messages is lower, encouraging more frequent contributions to improve and optimize them for the greatest clarity and usefulness. Rule file: ```js {% raw %} // avoid-name.js module.exports = { meta: { messages: { avoidName: "Avoid using variables named '{{ name }}'" } }, create(context) { return { Identifier(node) { if (node.name === "foo") { context.report({ node, messageId: "avoidName", data: { name: "foo", } }); } } }; } }; {% endraw %} ``` In the file to lint: ```javascript // someFile.js var foo = 2; // ^ error: Avoid using variables named 'foo' ``` In your tests: ```javascript // avoid-name.test.js var rule = require("../../../lib/rules/avoid-name"); var RuleTester = require("eslint").RuleTester; var ruleTester = new RuleTester(); ruleTester.run("avoid-name", rule, { valid: ["bar", "baz"], invalid: [ { code: "foo", errors: [ { messageId: "avoidName", }, ], }, ], }); ``` #### Applying Fixes If you'd like ESLint to attempt to fix the problem you're reporting, you can do so by specifying the `fix` function when using `context.report()`. The `fix` function receives a single argument, a `fixer` object, that you can use to apply a fix. For example: ```js context.report({ node: node, message: "Missing semicolon", fix(fixer) { return fixer.insertTextAfter(node, ";"); }, }); ``` Here, the `fix()` function is used to insert a semicolon after the node. Note that a fix is not immediately applied, and may not be applied at all if there are conflicts with other fixes. After applying fixes, ESLint will run all the enabled rules again on the fixed code, potentially applying more fixes. This process will repeat up to 10 times, or until no more fixable problems are found. Afterward, any remaining problems will be reported as usual. **Important:** The `meta.fixable` property is mandatory for fixable rules. ESLint will throw an error if a rule that implements `fix` functions does not [export](#rule-structure) the `meta.fixable` property. The `fixer` object has the following methods: - `insertTextAfter(nodeOrToken, text)`: Insert text after the given node or token. - `insertTextAfterRange(range, text)`: Insert text after the given range. - `insertTextBefore(nodeOrToken, text)`: Insert text before the given node or token. - `insertTextBeforeRange(range, text)`: Insert text before the given range. - `remove(nodeOrToken)`: Remove the given node or token. - `removeRange(range)`: Remove text in the given range. - `replaceText(nodeOrToken, text)`: Replace the text in the given node or token. - `replaceTextRange(range, text)`: Replace the text in the given range. A `range` is a two-item array containing character indices inside the source code. The first item is the start of the range (inclusive) and the second item is the end of the range (exclusive). Every node and token has a `range` property to identify the source code range they represent. The above methods return a `fixing` object. The `fix()` function can return the following values: - A `fixing` object. - An array which includes `fixing` objects. - An iterable object which enumerates `fixing` objects. Especially, the `fix()` function can be a generator. If you make a `fix()` function which returns multiple `fixing` objects, those `fixing` objects must not overlap. Best practices for fixes: 1. Avoid any fixes that could change the runtime behavior of code and cause it to stop working. 1. Make fixes as small as possible. Fixes that are unnecessarily large could conflict with other fixes, and prevent them from being applied. 1. Only make one fix per message. This is enforced because you must return the result of the fixer operation from `fix()`. 1. Since all rules are run again after the initial round of fixes is applied, it's not necessary for a rule to check whether the code style of a fix will cause errors to be reported by another rule. - For example, suppose a fixer would like to surround an object key with quotes, but it's not sure whether the user would prefer single or double quotes. ```js { foo: 1 } // should get fixed to either { 'foo': 1 } // or { "foo": 1 } ``` - This fixer can just select a quote type arbitrarily. If it guesses wrong, the resulting code will be automatically reported and fixed by the [`quotes`](../rules/quotes) rule. Note: Making fixes as small as possible is a best practice, but in some cases it may be correct to extend the range of the fix in order to intentionally prevent other rules from making fixes in a surrounding range in the same pass. For instance, if replacement text declares a new variable, it can be useful to prevent other changes in the scope of the variable as they might cause name collisions. The following example replaces `node` and also ensures that no other fixes will be applied in the range of `node.parent` in the same pass: ```js context.report({ node, message, *fix(fixer) { yield fixer.replaceText(node, replacementText); // extend range of the fix to the range of `node.parent` yield fixer.insertTextBefore(node.parent, ""); yield fixer.insertTextAfter(node.parent, ""); }, }); ``` #### Conflicting Fixes Conflicting fixes are fixes that apply different changes to the same part of the source code. There is no way to specify which of the conflicting fixes is applied. For example, if two fixes want to modify characters 0 through 5, only one is applied. #### Providing Suggestions In some cases fixes aren't appropriate to be automatically applied, for example, if a fix potentially changes functionality or if there are multiple valid ways to fix a rule depending on the implementation intent (see the best practices for [applying fixes](#applying-fixes) listed above). In these cases, there is an alternative `suggest` option on `context.report()` that allows other tools, such as editors, to expose helpers for users to manually apply a suggestion. To provide suggestions, use the `suggest` key in the report argument with an array of suggestion objects. The suggestion objects represent individual suggestions that could be applied and require either a `desc` key string that describes what applying the suggestion would do or a `messageId` key (see [below](#suggestion-messageids)), and a `fix` key that is a function defining the suggestion result. This `fix` function follows the same API as regular fixes (described above in [applying fixes](#applying-fixes)). ```js {% raw %} context.report({ node: node, message: "Unnecessary escape character: \\{{character}}.", data: { character }, suggest: [ { desc: "Remove the `\\`. This maintains the current functionality.", fix: function(fixer) { return fixer.removeRange(range); } }, { desc: "Replace the `\\` with `\\\\` to include the actual backslash character.", fix: function(fixer) { return fixer.insertTextBeforeRange(range, "\\"); } } ] }); {% endraw %} ``` **Important:** The `meta.hasSuggestions` property is mandatory for rules that provide suggestions. ESLint will throw an error if a rule attempts to produce a suggestion but does not [export](#rule-structure) this property. **Note:** Suggestions are applied as stand-alone changes, without triggering multipass fixes. Each suggestion should focus on a singular change in the code and should not try to conform to user-defined styles. For example, if a suggestion is adding a new statement into the codebase, it should not try to match correct indentation or conform to user preferences on the presence/absence of semicolons. All of those things can be corrected by multipass autofix when the user triggers it. Best practices for suggestions: 1. Don't try to do too much and suggest large refactors that could introduce a lot of breaking changes. 1. As noted above, don't try to conform to user-defined styles. Suggestions are intended to provide fixes. ESLint will automatically remove the whole suggestion from the linting output if the suggestion's `fix` function returned `null` or an empty array/sequence. #### Suggestion `messageId`s Instead of using a `desc` key for suggestions a `messageId` can be used instead. This works the same way as `messageId`s for the overall error (see [messageIds](#messageids)). Here is an example of how to use a suggestion `messageId` in a rule: ```js {% raw %} module.exports = { meta: { messages: { unnecessaryEscape: "Unnecessary escape character: \\{{character}}.", removeEscape: "Remove the `\\`. This maintains the current functionality.", escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character." }, hasSuggestions: true }, create: function(context) { // ... context.report({ node: node, messageId: 'unnecessaryEscape', data: { character }, suggest: [ { messageId: "removeEscape", // suggestion messageId fix: function(fixer) { return fixer.removeRange(range); } }, { messageId: "escapeBackslash", // suggestion messageId fix: function(fixer) { return fixer.insertTextBeforeRange(range, "\\"); } } ] }); } }; {% endraw %} ``` #### Placeholders in Suggestion Messages You can also use placeholders in the suggestion message. This works the same way as placeholders for the overall error (see [using message placeholders](#using-message-placeholders)). Please note that you have to provide `data` on the suggestion's object. Suggestion messages cannot use properties from the overall error's `data`. ```js {% raw %} module.exports = { meta: { messages: { unnecessaryEscape: "Unnecessary escape character: \\{{character}}.", removeEscape: "Remove `\\` before {{character}}.", }, hasSuggestions: true }, create: function(context) { // ... context.report({ node: node, messageId: "unnecessaryEscape", data: { character }, // data for the unnecessaryEscape overall message suggest: [ { messageId: "removeEscape", data: { character }, // data for the removeEscape suggestion message fix: function(fixer) { return fixer.removeRange(range); } } ] }); } }; {% endraw %} ``` ### Accessing Options Passed to a Rule Some rules require options in order to function correctly. These options appear in configuration (`.eslintrc`, command line interface, or comments). For example: ```json { "quotes": ["error", "double"] } ``` The `quotes` rule in this example has one option, `"double"` (the `error` is the error level). You can retrieve the options for a rule by using `context.options`, which is an array containing every configured option for the rule. In this case, `context.options[0]` would contain `"double"`: ```js module.exports = { meta: { schema: [ { enum: ["single", "double", "backtick"], }, ], }, create: function (context) { var isDouble = context.options[0] === "double"; // ... }, }; ``` Since `context.options` is just an array, you can use it to determine how many options have been passed as well as retrieving the actual options themselves. Keep in mind that the error level is not part of `context.options`, as the error level cannot be known or modified from inside a rule. When using options, make sure that your rule has some logical defaults in case the options are not provided. Rules with options must specify a [schema](#options-schemas). ### Accessing the Source Code The `SourceCode` object is the main object for getting more information about the source code being linted. You can retrieve the `SourceCode` object at any time by using the `context.sourceCode` property: ```js module.exports = { create: function (context) { var sourceCode = context.sourceCode; // ... }, }; ``` Once you have an instance of `SourceCode`, you can use the following methods on it to work with the code: - `getText(node)`: Returns the source code for the given node. Omit `node` to get the whole source (see the [dedicated section](#accessing-the-source-text)). - `getAllComments()`: Returns an array of all comments in the source (see the [dedicated section](#accessing-comments)). - `getCommentsBefore(nodeOrToken)`: Returns an array of comment tokens that occur directly before the given node or token (see the [dedicated section](#accessing-comments)). - `getCommentsAfter(nodeOrToken)`: Returns an array of comment tokens that occur directly after the given node or token (see the [dedicated section](#accessing-comments)). - `getCommentsInside(node)`: Returns an array of all comment tokens inside a given node (see the [dedicated section](#accessing-comments)). - `isSpaceBetween(nodeOrToken, nodeOrToken)`: Returns true if there is a whitespace character between the two tokens or, if given a node, the last token of the first node and the first token of the second node. - `isGlobalReference(node)`: Returns true if the identifier references a global variable configured via `languageOptions.globals`, `/* global */` comments, or `ecmaVersion`, and not declared by a local binding. - `getFirstToken(node, skipOptions)`: Returns the first token representing the given node. - `getFirstTokens(node, countOptions)`: Returns the first `count` tokens representing the given node. - `getLastToken(node, skipOptions)`: Returns the last token representing the given node. - `getLastTokens(node, countOptions)`: Returns the last `count` tokens representing the given node. - `getTokenAfter(nodeOrToken, skipOptions)`: Returns the first token after the given node or token. - `getTokensAfter(nodeOrToken, countOptions)`: Returns `count` tokens after the given node or token. - `getTokenBefore(nodeOrToken, skipOptions)`: Returns the first token before the given node or token. - `getTokensBefore(nodeOrToken, countOptions)`: Returns `count` tokens before the given node or token. - `getFirstTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions)`: Returns the first token between two nodes or tokens. - `getFirstTokensBetween(nodeOrToken1, nodeOrToken2, countOptions)`: Returns the first `count` tokens between two nodes or tokens. - `getLastTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions)`: Returns the last token between two nodes or tokens. - `getLastTokensBetween(nodeOrToken1, nodeOrToken2, countOptions)`: Returns the last `count` tokens between two nodes or tokens. - `getTokens(node)`: Returns all tokens for the given node. - `getTokensBetween(nodeOrToken1, nodeOrToken2)`: Returns all tokens between two nodes. - `getTokenByRangeStart(index, rangeOptions)`: Returns the token whose range starts at the given index in the source. - `getNodeByRangeIndex(index)`: Returns the deepest node in the AST containing the given source index. - `getLocFromIndex(index)`: Returns an object with `line` and `column` properties, corresponding to the location of the given source index. `line` is 1-based and `column` is 0-based. - `getIndexFromLoc(loc)`: Returns the index of a given location in the source code, where `loc` is an object with a 1-based `line` key and a 0-based `column` key. - `commentsExistBetween(nodeOrToken1, nodeOrToken2)`: Returns `true` if comments exist between two nodes. - `getAncestors(node)`: Returns an array of the ancestors of the given node, starting at the root of the AST and continuing through the direct parent of the given node. This array does not include the given node itself. - `getDeclaredVariables(node)`: Returns a list of [variables](./scope-manager-interface#variable-interface) declared by the given node. This information can be used to track references to variables. - If the node is a `VariableDeclaration`, all variables declared in the declaration are returned. - If the node is a `VariableDeclarator`, all variables declared in the declarator are returned. - If the node is a `FunctionDeclaration` or `FunctionExpression`, the variable for the function name is returned, in addition to variables for the function parameters. - If the node is an `ArrowFunctionExpression`, variables for the parameters are returned. - If the node is a `ClassDeclaration` or a `ClassExpression`, the variable for the class name is returned. - If the node is a `CatchClause`, the variable for the exception is returned. - If the node is an `ImportDeclaration`, variables for all of its specifiers are returned. - If the node is an `ImportSpecifier`, `ImportDefaultSpecifier`, or `ImportNamespaceSpecifier`, the declared variable is returned. - Otherwise, if the node does not declare any variables, an empty array is returned. - `getScope(node)`: Returns the [scope](./scope-manager-interface#scope-interface) of the given node. This information can be used to track references to variables. - `markVariableAsUsed(name, refNode)`: Marks a variable with the given name in a scope indicated by the given reference node as used. This affects the [no-unused-vars](../rules/no-unused-vars) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`. `skipOptions` is an object which has 3 properties; `skip`, `includeComments`, and `filter`. Default is `{skip: 0, includeComments: false, filter: null}`. - `skip`: (`number`) Positive integer, the number of skipping tokens. If `filter` option is given at the same time, it doesn't count filtered tokens as skipped. - `includeComments`: (`boolean`) The flag to include comment tokens into the result. - `filter(token)`: Function which gets a token as the first argument. If the function returns `false` then the result excludes the token. `countOptions` is an object which has 3 properties; `count`, `includeComments`, and `filter`. Default is `{count: 0, includeComments: false, filter: null}`. - `count`: (`number`) Positive integer, the maximum number of returning tokens. - `includeComments`: (`boolean`) The flag to include comment tokens into the result. - `filter(token)`: Function which gets a token as the first argument, if the function returns `false` then the result excludes the token. `rangeOptions` is an object that has 1 property, `includeComments`. Default is `{includeComments: false}`. - `includeComments`: (`boolean`) The flag to include comment tokens into the result. There are also some properties you can access: - `hasBOM`: (`boolean`) The flag to indicate whether the source code has Unicode BOM. - `text`: (`string`) The full text of the code being linted. Unicode BOM has been stripped from this text. - `ast`: (`object`) `Program` node of the AST for the code being linted. - `scopeManager`: [ScopeManager](./scope-manager-interface#scopemanager-interface) object of the code. - `visitorKeys`: (`object`) Visitor keys to traverse this AST. - `parserServices`: (`object`) Contains parser-provided services for rules. The default parser does not provide any services. However, if a rule is intended to be used with a custom parser, it could use `parserServices` to access anything provided by that parser. (For example, a TypeScript parser could provide the ability to get the computed type of a given node.) - `lines`: (`array`) Array of lines, split according to the specification's definition of line breaks. You should use a `SourceCode` object whenever you need to get more information about the code being linted. #### Accessing the Source Text If your rule needs to get the actual JavaScript source to work with, then use the `sourceCode.getText()` method. This method works as follows: ```js // get all source var source = sourceCode.getText(); // get source for just this AST node var nodeSource = sourceCode.getText(node); // get source for AST node plus previous two characters var nodeSourceWithPrev = sourceCode.getText(node, 2); // get source for AST node plus following two characters var nodeSourceWithFollowing = sourceCode.getText(node, 0, 2); ``` In this way, you can look for patterns in the JavaScript text itself when the AST isn't providing the appropriate data (such as the location of commas, semicolons, parentheses, etc.). #### Accessing Comments While comments are not technically part of the AST, ESLint provides the `sourceCode.getAllComments()`, `sourceCode.getCommentsBefore()`, `sourceCode.getCommentsAfter()`, and `sourceCode.getCommentsInside()` to access them. `sourceCode.getCommentsBefore()`, `sourceCode.getCommentsAfter()`, and `sourceCode.getCommentsInside()` are useful for rules that need to check comments in relation to a given node or token. Keep in mind that the results of these methods are calculated on demand. You can also access comments through many of `sourceCode`'s methods using the `includeComments` option. ### Options Schemas Rules with options must specify a `meta.schema` property, which is a [JSON Schema](https://json-schema.org/) format description of a rule's options which will be used by ESLint to validate configuration options and prevent invalid or unexpected inputs before they are passed to the rule in `context.options`. If your rule has options, it is strongly recommended that you specify a schema for options validation. However, it is possible to opt-out of options validation by setting `schema: false`, but doing so is discouraged as it increases the chance of bugs and mistakes. For rules that don't specify a `meta.schema` property, ESLint throws errors when any options are passed. If your rule doesn't have options, do not set `schema: false`, but simply omit the schema property or use `schema: []`, both of which prevent any options from being passed. When validating a rule's config, there are five steps: 1. If the rule config is not an array, then the value is wrapped into an array (e.g. `"off"` becomes `["off"]`); if the rule config is an array then it is used directly. 2. ESLint validates the first element of the rule config array as a severity (`"off"`, `"warn"`, `"error"`, `0`, `1`, `2`) 3. If the severity is `off` or `0`, then the rule is disabled and validation stops, ignoring any other elements of the rule config array. 4. If the rule is enabled, then any elements of the array after the severity are copied into the `context.options` array (e.g. a config of `["warn", "never", { someOption: 5 }]` results in `context.options = ["never", { someOption: 5 }]`) 5. The rule's schema validation is run on the `context.options` array. Note: this means that the rule schema cannot validate the severity. The rule schema only validates the array elements _after_ the severity in a rule config. There is no way for a rule to know what severity it is configured at. There are two formats for a rule's `schema`: - An array of JSON Schema objects - Each element will be checked against the same position in the `context.options` array. - If the `context.options` array has fewer elements than there are schemas, then the unmatched schemas are ignored. - If the `context.options` array has more elements than there are schemas, then the validation fails. - There are two important consequences to using this format: - It is _always valid_ for a user to provide no options to your rule (beyond severity). - If you specify an empty array, then it is _always an error_ for a user to provide any options to your rule (beyond severity). - A full JSON Schema object that will validate the `context.options` array - The schema should assume an array of options to validate even if your rule only accepts one option. - The schema can be arbitrarily complex, so you can validate completely different sets of potential options via `oneOf`, `anyOf` etc. - The supported version of JSON Schemas is [Draft-04](http://json-schema.org/draft-04/schema), so some newer features such as `if` or `$data` are unavailable. - At present, it is explicitly planned to not update schema support beyond this level due to ecosystem compatibility concerns. See [this comment](https://github.com/eslint/eslint/issues/13888#issuecomment-872591875) for further context. For example, the `yoda` rule accepts a primary mode argument of `"always"` or `"never"`, as well as an extra options object with an optional property `exceptRange`: ```js // Valid configuration: // "yoda": "warn" // "yoda": ["error"] // "yoda": ["error", "always"] // "yoda": ["error", "never", { "exceptRange": true }] // Invalid configuration: // "yoda": ["warn", "never", { "exceptRange": true }, 5] // "yoda": ["error", { "exceptRange": true }, "never"] module.exports = { meta: { schema: [ { enum: ["always", "never"], }, { type: "object", properties: { exceptRange: { type: "boolean" }, }, additionalProperties: false, }, ], }, }; ``` And here is the equivalent object-based schema: ```js // Valid configuration: // "yoda": "warn" // "yoda": ["error"] // "yoda": ["error", "always"] // "yoda": ["error", "never", { "exceptRange": true }] // Invalid configuration: // "yoda": ["warn", "never", { "exceptRange": true }, 5] // "yoda": ["error", { "exceptRange": true }, "never"] module.exports = { meta: { schema: { type: "array", minItems: 0, maxItems: 2, items: [ { enum: ["always", "never"], }, { type: "object", properties: { exceptRange: { type: "boolean" }, }, additionalProperties: false, }, ], }, }, }; ``` Object schemas can be more precise and restrictive in what is permitted. For example, the below schema always requires the first option to be specified (a number between 0 and 10), but the second option is optional, and can either be an object with some options explicitly set, or `"off"` or `"strict"`. ```js // Valid configuration: // "someRule": ["error", 6] // "someRule": ["error", 5, "strict"] // "someRule": ["warn", 10, { someNonOptionalProperty: true }] // Invalid configuration: // "someRule": "warn" // "someRule": ["error"] // "someRule": ["warn", 15] // "someRule": ["warn", 7, { }] // "someRule": ["error", 3, "on"] // "someRule": ["warn", 7, { someOtherProperty: 5 }] // "someRule": ["warn", 7, { someNonOptionalProperty: false, someOtherProperty: 5 }] module.exports = { meta: { schema: { type: "array", minItems: 1, // Can't specify only severity! maxItems: 2, items: [ { type: "number", minimum: 0, maximum: 10, }, { anyOf: [ { type: "object", properties: { someNonOptionalProperty: { type: "boolean" }, }, required: ["someNonOptionalProperty"], additionalProperties: false, }, { enum: ["off", "strict"], }, ], }, ], }, }, }; ``` Remember, rule options are always an array, so be careful not to specify a schema for a non-array type at the top level. If your schema does not specify an array at the top-level, users can _never_ enable your rule, as their configuration will always be invalid when the rule is enabled. Here's an example schema that will always fail validation: ```js // Possibly trying to validate ["error", { someOptionalProperty: true }] // but when the rule is enabled, config will always fail validation because the options are an array which doesn't match "object" module.exports = { meta: { schema: { type: "object", properties: { someOptionalProperty: { type: "boolean", }, }, additionalProperties: false, }, }, }; ``` **Note:** If your rule schema uses JSON schema [`$ref`](https://json-schema.org/understanding-json-schema/structuring.html#ref) properties, you must use the full JSON Schema object rather than the array of positional property schemas. This is because ESLint transforms the array shorthand into a single schema without updating references that makes them incorrect (they are ignored). To learn more about JSON Schema, we recommend looking at some examples on the [JSON Schema website](https://json-schema.org/learn/miscellaneous-examples), or reading the free [Understanding JSON Schema](https://json-schema.org/understanding-json-schema/) ebook. ### Option Defaults Rules may specify a `meta.defaultOptions` array of default values for any options. When the rule is enabled in a user configuration, ESLint will recursively merge any user-provided option elements on top of the default elements. For example, given the following defaults: ```js export default { meta: { defaultOptions: [ { alias: "basic", }, ], schema: [ { type: "object", properties: { alias: { type: "string", }, }, additionalProperties: false, }, ], }, create(context) { const [{ alias }] = context.options; return { /* ... */ }; }, }; ``` The rule would have a runtime `alias` value of `"basic"` unless the user configuration specifies a different value, such as with `["error", { alias: "complex" }]`. Each element of the options array is merged according to the following rules: - Any missing value or explicit user-provided `undefined` will fall back to a default option - User-provided arrays and primitive values other than `undefined` override a default option - User-provided objects will merge into a default option object and replace a non-object default otherwise Option defaults will also be validated against the rule's `meta.schema`. **Note:** ESLint internally uses [Ajv](https://ajv.js.org) for schema validation with its [`useDefaults` option](https://ajv.js.org/guide/modifying-data.html#assigning-defaults) enabled. Both user-provided and `meta.defaultOptions` options will override any defaults specified in a rule's schema. ESLint may disable Ajv's `useDefaults` in a future major version. ### Accessing Shebangs [Shebangs (#!)]() are represented by the unique tokens of type `"Shebang"`. They are treated as comments and can be accessed by the methods outlined in the [Accessing Comments](#accessing-comments) section, such as `sourceCode.getAllComments()`. ### Accessing Variable Scopes The `SourceCode#getScope(node)` method returns the scope of the given node. It is a useful method for finding information about the variables in a given scope and how they are used in other scopes. ::: tip You can view scope information for any JavaScript code using [Code Explorer](http://explorer.eslint.org). ::: #### Scope types The following table contains a list of AST node types and the scope type that they correspond to. For more information about the scope types, refer to the [`Scope` object documentation](./scope-manager-interface#scope-interface). | AST Node Type | Scope Type | | :------------------------ | :------------------- | | `Program` | `global` | | `FunctionDeclaration` | `function` | | `FunctionExpression` | `function` | | `ArrowFunctionExpression` | `function` | | `ClassDeclaration` | `class` | | `ClassExpression` | `class` | | `StaticBlock` | `class-static-block` | | `BlockStatement` ※1 | `block` | | `SwitchStatement` ※1 | `switch` | | `ForStatement` ※2 | `for` | | `ForInStatement` ※2 | `for` | | `ForOfStatement` ※2 | `for` | | `WithStatement` | `with` | | `CatchClause` | `catch` | | others | ※3 ※4 | **※1** Only if the configured parser provided the block-scope feature. The default parser provides the block-scope feature if `languageOptions.ecmaVersion` is not less than `6`.
**※2** Only if the `for` statement defines the iteration variable as a block-scoped variable (E.g., `for (let i = 0;;) {}`).
**※3** The scope of the closest ancestor node which has own scope. If the closest ancestor node has multiple scopes then it chooses the innermost scope (E.g., the `Program` node has a `global` scope and a `module` scope if `Program#sourceType` is `"module"`. The innermost scope is the `module` scope.).
**※4** Each `PropertyDefinition#value` node (it can be any expression node type), has a `class-field-initializer` scope. For example, in `class C { field = 1 }`, the `Literal` node that represents `1` has a `class-field-initializer` scope. If the node has other scopes, the `class-field-initializer` scope will be the outermost one. For example, in `class C { field = () => {} }`, the `ArrowFunctionExpression` node has two scopes: `class-field-initializer` and `function`. #### Scope Variables The `Scope#variables` property contains an array of [`Variable` objects](./scope-manager-interface#variable-interface). These are the variables declared in current scope. You can use these `Variable` objects to track references to a variable throughout the entire module. Inside of each `Variable`, the `Variable#references` property contains an array of [`Reference` objects](./scope-manager-interface#reference-interface). The `Reference` array contains all the locations where the variable is referenced in the module's source code. Also inside of each `Variable`, the `Variable#defs` property contains an array of [`Definition` objects](./scope-manager-interface#definition-interface). You can use the `Definitions` to find where the variable was defined. Global variables have the following additional properties: - `Variable#writeable` (`boolean | undefined`) ... If `true`, this global variable can be assigned arbitrary value. If `false`, this global variable is read-only. - `Variable#eslintExplicitGlobal` (`boolean | undefined`) ... If `true`, this global variable was defined by a `/* globals */` directive comment in the source code file. - `Variable#eslintExplicitGlobalComments` (`Comment[] | undefined`) ... The array of `/* globals */` directive comments which defined this global variable in the source code file. This property is `undefined` if there are no `/* globals */` directive comments. - `Variable#eslintImplicitGlobalSetting` (`"readonly" | "writable" | undefined`) ... The configured value in config files. This can be different from `variable.writeable` if there are `/* globals */` directive comments. For examples of using `SourceCode#getScope()` to track variables, refer to the source code for the following built-in rules: - [no-shadow](https://github.com/eslint/eslint/blob/main/lib/rules/no-shadow.js): Calls `sourceCode.getScope()` at the `Program` node and inspects all child scopes to make sure a variable name is not reused at a lower scope. ([no-shadow](../rules/no-shadow) documentation) - [no-redeclare](https://github.com/eslint/eslint/blob/main/lib/rules/no-redeclare.js): Calls `sourceCode.getScope()` at each scope to make sure that a variable is not declared twice in the same scope. ([no-redeclare](../rules/no-redeclare) documentation) ### Marking Variables as Used Certain ESLint rules, such as [`no-unused-vars`](../rules/no-unused-vars), check to see if a variable has been used. ESLint itself only knows about the standard rules of variable access and so custom ways of accessing variables may not register as "used". To help with this, you can use the `sourceCode.markVariableAsUsed()` method. This method takes two arguments: the name of the variable to mark as used and an option reference node indicating the scope in which you are working. Here's an example: ```js module.exports = { create: function (context) { var sourceCode = context.sourceCode; return { ReturnStatement(node) { // look in the scope of the function for myCustomVar and mark as used sourceCode.markVariableAsUsed("myCustomVar", node); // or: look in the global scope for myCustomVar and mark as used sourceCode.markVariableAsUsed("myCustomVar"); }, }; // ... }, }; ``` Here, the `myCustomVar` variable is marked as used relative to a `ReturnStatement` node, which means ESLint will start searching from the scope closest to that node. If you omit the second argument, then the top-level scope is used. (For ESM files, the top-level scope is the module scope; for CommonJS files, the top-level scope is the first function scope.) ### Accessing Code Paths ESLint analyzes code paths while traversing AST. You can access code path objects with seven events related to code paths. For more information, refer to [Code Path Analysis](code-path-analysis). ## Rule Unit Tests ESLint provides the [`RuleTester`](../integrate/nodejs-api#ruletester) utility to make it easy to write tests for rules. ## Rule Naming Conventions While you can give a custom rule any name you'd like, the core rules have naming conventions. It could be clearer to apply these same naming conventions to your custom rule. To learn more, refer to the [Core Rule Naming Conventions](../contribute/core-rules#rule-naming-conventions) documentation. ## Runtime Rules The thing that makes ESLint different from other linters is the ability to define custom rules at runtime. This is perfect for rules that are specific to your project or company and wouldn't make sense for ESLint to ship with or be included in a plugin. Just write your rules and include them at runtime. Runtime rules are written in the same format as all other rules. Create your rule as you would any other and then follow these steps: 1. Place all of your runtime rules in the same directory (e.g., `eslint_rules`). 2. Create a [configuration file](../use/configure/) and specify your rule ID error level under the `rules` key. Your rule will not run unless it has a value of `"warn"` or `"error"` in the configuration file. 3. Run the [command line interface](../use/command-line-interface) using the `--rulesdir` option to specify the location of your runtime rules. ## Profile Rule Performance ESLint has a built-in method to track the performance of individual rules. Setting the `TIMING` environment variable will trigger the display, upon linting completion, of the ten longest-running rules, along with their individual running time (rule creation + rule execution) and relative performance impact as a percentage of total rule processing time (rule creation + rule execution). ```bash $ TIMING=1 eslint lib Rule | Time (ms) | Relative :-----------------------|----------:|--------: no-multi-spaces | 52.472 | 6.1% camelcase | 48.684 | 5.7% no-irregular-whitespace | 43.847 | 5.1% valid-jsdoc | 40.346 | 4.7% handle-callback-err | 39.153 | 4.6% space-infix-ops | 35.444 | 4.1% no-undefined | 25.693 | 3.0% no-shadow | 22.759 | 2.7% no-empty-class | 21.976 | 2.6% semi | 19.359 | 2.3% ``` To test one rule explicitly, combine the `--no-config-lookup` and `--rule` options: ```bash $ TIMING=1 eslint --no-config-lookup --rule "quotes: [2, 'double']" lib Rule | Time (ms) | Relative :------|----------:|--------: quotes | 18.066 | 100.0% ``` To see a longer list of results (more than 10), set the environment variable to another value such as `TIMING=50` or `TIMING=all`. For more granular timing information (per file per rule), use the [`stats`](./stats) option instead. --- --- title: Extend ESLint eleventyNavigation: key: extend eslint title: Extend ESLint order: 2 --- This guide is intended for those who wish to extend the functionality of ESLint. In order to extend ESLint, it's recommended that: - You know JavaScript, since ESLint is written in JavaScript. - You have some familiarity with Node.js, since ESLint runs on it. - You're comfortable with command-line programs. If that sounds like you, then continue reading to get started. ## [Ways to Extend ESLint](ways-to-extend) This page summarizes the various ways that you can extend ESLint and how these extensions all fit together. ## [Create Plugins](plugins) You've developed custom rules for ESLint and you want to share them with the community. You can publish an ESLint plugin on npm. ## [Custom Rule Tutorial](custom-rule-tutorial) A tutorial that walks you through creating a custom rule for ESLint. ## [Custom Rules](custom-rules) This section explains how to create custom rules to use with ESLint. ## [Custom Formatters](custom-formatters) This section explains how you can create a custom formatter to control what ESLint outputs. ## [Custom Parsers](custom-parsers) If you don't want to use the default parser of ESLint, this section explains how to create custom parsers. ## [Custom Processors](custom-processors) This section explains how you can use a custom processor to have ESLint process files other than JavaScript. ## [Share Configurations](shareable-configs) This section explains how you can bundle and share ESLint configuration in a JavaScript package. --- --- title: Languages eleventyNavigation: key: languages parent: create plugins title: Languages order: 4 --- Starting with ESLint v9.7.0, you can extend ESLint with additional languages through plugins. While ESLint began as a linter strictly for JavaScript, the ESLint core is generic and can be used to lint any programming language. Each language is defined as an object that contains all of the parsing, evaluating, and traversal functionality required to lint a file. These languages are then distributed in plugins for use in user configurations. ## Language Requirements In order to create a language, you need: 1. **A parser.** The parser is the piece that converts plain text into a data structure. There is no specific format that ESLint requires the data structure to be in, so you can use any already-existing parser, or write your own. 1. **A `SourceCode` object.** The way ESLint works with an AST is through a `SourceCode` object. There are some required methods on each `SourceCode`, and you can also add more methods or properties that you'd like to expose to rules. 1. **A `Language` object.** The `Language` object contains information about the language itself along with methods for parsing and creating the `SourceCode` object. ### Parser Requirements for Languages To get started, make sure you have a parser that can be called from JavaScript. The parser must return a data structure representing the code that was parsed. Most parsers return an [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) (AST) to represent the code, but they can also return a [concrete syntax tree](https://en.wikipedia.org/wiki/Parse_tree) (CST). Whether an AST or CST is returned doesn't matter to ESLint, it only matters that there is a data structure to traverse. While there is no specific structure an AST or CST must follow, it's easier to integrate with ESLint when each node in the tree contains the following information: 1. **Type** - A property on each node representing the node type is required. For example, in JavaScript, the `type` property contains this information for each node. ESLint rules use node types to define the visitor methods, so it's important that each node can be identified by a string. The name of the property doesn't matter (discussed further below) so long as one exists. This property is typically named `type` or `kind` by most parsers. 1. **Location** - A property on each node representing the location of the node in the original source code is required. The location must contain: - The line on which the node starts - The column on which the node starts - The line on which the node ends - The column on which the node ends As with the node type, the property name doesn't matter. Two common property names are `loc` (as in [ESTree](https://github.com/estree/estree/blob/3851d4a6eae5e5473371893959b88b62007469e8/es5.md#node-objects)) and `position` (as in [Unist](https://github.com/syntax-tree/unist?tab=readme-ov-file#node)). This information is used by ESLint to report errors and rule violations. 1. **Range** - A property on each node representing the location of the node's source inside the source code is required. The range indicates the index at which the first character is found and the index after the last character, such that calling `code.slice(start, end)` returns the text that the node represents. Once again, no specific property name is required, and this information may even be merged with location information. ESTree uses the `range` property while Unist includes this information on `position` along with the location information. This information is used by ESLint to apply autofixes. ### The `SourceCode` Object ESLint holds information about source code in a `SourceCode` object. This object is the API used both by ESLint internally and by rules written to work on the code (via `context.sourceCode`). The `SourceCode` object must implement the `TextSourceCode` interface as defined in the [`@eslint/core`](https://npmjs.com/package/@eslint/core) package. A basic `SourceCode` object must implement the following: - `ast` - a property containing the AST or CST for the source code. - `text` - the text of the source code. - `getLoc(nodeOrToken)` - a method that returns the location of a given node or token. This must match the `loc` structure that ESTree uses. - `getRange(nodeOrToken)` - a method that returns the range of a given node or token. This must return an array where the first item is the start index and the second is the end index. - `traverse()` - a method that returns an iterable for traversing the AST or CST. The iterator must return objects that implement either `VisitTraversalStep` or `CallTraversalStep` from `@eslint/core`. The following optional members allow you to customize how ESLint interacts with the object: - `visitorKeys` - visitor keys that are specific to just this `SourceCode` object. Typically not necessary as `Language#visitorKeys` is used most of the time. - `applyLanguageOptions(languageOptions)` - if you have specific language options that need to be applied after parsing, you can do so in this method. - `getDisableDirectives()` - returns any disable directives in the code. ESLint uses this to apply disable directives and track unused directives. - `getInlineConfigNodes()` - returns any inline config nodes. ESLint uses this to report errors when `noInlineConfig` is enabled. - `applyInlineConfig()` - returns inline configuration elements to ESLint. ESLint uses this to alter the configuration of the file being linted. - `finalize()` - this method is called just before linting begins and is your last chance to modify `SourceCode`. If you've defined `applyLanguageOptions()` or `applyInlineConfig()`, then you may have additional changes to apply before the `SourceCode` object is ready. Additionally, the following members are common on `SourceCode` objects and are recommended to implement: - `lines` - the individual lines of the source code as an array of strings. - `getParent(node)` - returns the parent of the given node or `undefined` if the node is the root. - `getAncestors(node)` - returns an array of the ancestry of the node with the first item as the root of the tree and each subsequent item as the descendants of the root that lead to `node`. - `getText(node, beforeCount, afterCount)` - returns the string that represents the given node, and optionally, a specified number of characters before and after the node's range. See [`JSONSourceCode`](https://github.com/eslint/json/blob/main/src/languages/json-source-code.js) as an example of a basic `SourceCode` class. ::: tip The [`@eslint/plugin-kit`](https://npmjs.com/package/@eslint/plugin-kit) package contains multiple classes that aim to make creating a `SourceCode` object easier. The `TextSourceCodeBase` class, in particular, implements the `TextSourceCode` interface and provides some basic functionality typically found in `SourceCode` objects. ::: ### The `Language` Object The `Language` object contains all of the information about the programming language as well as methods for interacting with code written in that language. ESLint uses this object to determine how to deal with a particular file. The `Language` object must implement the `Language` interface as defined in the [`@eslint/core`](https://npmjs.com/package/@eslint/core) package. A basic `Language` object must implement the following: - `fileType` - should be `"text"` (in the future, we will also support `"binary"`) - `lineStart` - either 0 or 1 to indicate how the AST represents the first line in the file. ESLint uses this to correctly display error locations. - `columnStart` - either 0 or 1 to indicate how the AST represents the first column in each line. ESLint uses this to correctly display error locations. - `nodeTypeKey` - the name of the property that indicates the node type (usually `"type"` or `"kind"`). - `validateLanguageOptions(languageOptions)` - validates language options for the language. This method is expected to throw a validation error when an expected language option doesn't have the correct type or value. Unexpected language options should be silently ignored and no error should be thrown. This method is required even if the language doesn't specify any options. - `parse(file, context)` - parses the given file into an AST or CST, and can also include additional values meant for use in rules. Called internally by ESLint. - `createSourceCode(file, parseResult, context)` - creates a `SourceCode` object. Call internally by ESLint after `parse()`, and the second argument is the exact return value from `parse()`. The following optional members allow you to customize how ESLint interacts with the object: - `visitorKeys` - visitor keys that are specific to the AST or CST. This is used to optimize traversal of the AST or CST inside of ESLint. While not required, it is strongly recommended, especially for AST or CST formats that deviate significantly from ESTree format. - `defaultLanguageOptions` - default `languageOptions` when the language is used. User-specified `languageOptions` are merged with this object when calculating the config for the file being linted. - `matchesSelectorClass(className, node, ancestry)` - allows you to specify selector classes, such as `:expression`, that match more than one node. This method is called whenever an [esquery](https://github.com/estools/esquery) selector contains a `:` followed by an identifier. - `normalizeLanguageOptions(languageOptions)` - takes a validated language options object and normalizes its values. This is helpful for backwards compatibility when language options properties change and also to add custom serialization with a `toJSON()` method. See [`JSONLanguage`](https://github.com/eslint/json/blob/main/src/languages/json-language.js) as an example of a basic `Language` class. ## Publish a Language in a Plugin Languages are published in plugins similar to processors and rules. Define the `languages` key in your plugin as an object whose names are the language names and the values are the language objects. Here's an example: ```js import { myLanguage } from "../languages/my.js"; const plugin = { // preferred location of name and version meta: { name: "eslint-plugin-example", version: "1.2.3", }, languages: { my: myLanguage, }, rules: { // add rules here }, }; // for ESM export default plugin; // OR for CommonJS module.exports = plugin; ``` In order to use a language from a plugin in a configuration file, import the plugin and include it in the `plugins` key, specifying a namespace. Then, use that namespace to reference the language in the `language` configuration, like this: ```js // eslint.config.js import { defineConfig } from "eslint/config"; import example from "eslint-plugin-example"; export default defineConfig([ { files: ["**/*.my"], plugins: { example, }, language: "example/my", }, ]); ``` See [Specify a Language](../use/configure/plugins#specify-a-language) in the Plugin Configuration documentation for more details. --- --- title: Plugin Migration to Flat Config eleventyNavigation: key: plugin flat config parent: create plugins title: Migration to Flat Config order: 5 --- Beginning in ESLint v9.0.0, the default configuration system will be the new flat config system. In order for your plugins to work with flat config files, you'll need to make some changes to your existing plugins. ## Recommended Plugin Structure To make it easier to work with your plugin in the flat config system, it's recommended that you switch your existing plugin entrypoint to look like this: ```js const plugin = { meta: {}, configs: {}, rules: {}, processors: {}, }; // for ESM export default plugin; // OR for CommonJS module.exports = plugin; ``` This structure allows the most flexibility when making other changes discussed on this page. ## Adding Plugin Meta Information With the old eslintrc configuration system, ESLint could pull information about the plugin from the package name, but with flat config, ESLint no longer has access to the name of the plugin package. To replace that missing information, you should add a `meta` key that contains at least a `name` key, and ideally, a `version` key, such as: ```js const plugin = { meta: { name: "eslint-plugin-example", version: "1.0.0", }, configs: {}, rules: {}, processors: {}, }; // for ESM export default plugin; // OR for CommonJS module.exports = plugin; ``` If your plugin is published as an npm package, the `name` and `version` should be the same as in your `package.json` file; otherwise, you can assign any value you'd like. Without this meta information, your plugin will not be usable with the `--cache` and `--print-config` command line options. ## Migrating Rules for Flat Config No changes are necessary for the `rules` key in your plugin. Everything works the same as with the old eslintrc configuration system. ## Migrating Processors for Flat Config Each processor should specify a `meta` object. For more information, see the [full documentation](custom-processors). No other changes are necessary for the `processors` key in your plugin as long as you aren't using file extension-named processors. If you have any [file extension-named processors](custom-processors-deprecated#file-extension-named-processor), you must update the name to a valid identifier (numbers and letters). File extension-named processors were automatically applied in the old configuration system but are not automatically applied when using flat config. Here is an example of a file extension-named processor: ```js const plugin = { configs: {}, rules: {}, processors: { // no longer supported ".md": { preprocess() {}, postprocess() {}, }, }, }; // for ESM export default plugin; // OR for CommonJS module.exports = plugin; ``` The name `".md"` is no longer valid for a processor, so it must be replaced with a valid identifier such as `markdown`: ```js const plugin = { configs: {}, rules: {}, processors: { // works in both old and new config systems markdown: { preprocess() {}, postprocess() {}, }, }, }; // for ESM export default plugin; // OR for CommonJS module.exports = plugin; ``` In order to use this renamed processor, you'll also need to manually specify it inside of a config, such as: ```js import { defineConfig } from "eslint/config"; import example from "eslint-plugin-example"; export default defineConfig([ { files: ["**/*.md"], plugins: { example, }, processor: "example/markdown", }, ]); ``` You should update your plugin's documentation to advise your users if you have renamed a file extension-named processor. ## Migrating Configs for Flat Config If your plugin is exporting configs that refer back to your plugin, then you'll need to update your configs to flat config format. As part of the migration, you'll need to reference your plugin directly in the `plugins` key. For example, here is an exported config in the old configuration system format for a plugin named `eslint-plugin-example`: ```js // plugin name: eslint-plugin-example module.exports = { configs: { // the config referenced by example/recommended recommended: { plugins: ["example"], rules: { "example/rule1": "error", "example/rule2": "error" } } }, rules: { rule1: {}, rule2: {}; } }; ``` To migrate to flat config format, you'll need to move the configs to after the definition of the `plugin` variable in the recommended plugin structure, like this: ```js const plugin = { configs: {}, rules: {}, processors: {}, }; // assign configs here so we can reference `plugin` Object.assign(plugin.configs, { recommended: { plugins: { example: plugin, }, rules: { "example/rule1": "error", "example/rule2": "error", }, }, }); // for ESM export default plugin; // OR for CommonJS module.exports = plugin; ``` Your users can then use this exported config like this: ```js import { defineConfig } from "eslint/config"; import example from "eslint-plugin-example"; export default defineConfig([ // use recommended config and provide your own overrides { files: ["**/*.js", "**/*.cjs", "**/*.mjs"], plugins: { example, }, extends: ["example/recommended"], rules: { "example/rule1": "warn", }, }, ]); ``` If your config extends other configs, you can export an array: ```js const baseConfig = require("./base-config"); module.exports = { configs: { extendedConfig: [ baseConfig, { rules: { "example/rule1": "error", "example/rule2": "error", }, }, ], }, }; ``` You should update your documentation so your plugin users know how to reference the exported configs. For more information, see the [full documentation](https://eslint.org/docs/latest/extend/plugins#configs-in-plugins). ## Migrating Environments for Flat Config Environments are no longer supported in flat config, and so we recommend transitioning your environments into exported configs. For example, suppose you export a `mocha` environment like this: ```js // plugin name: eslint-plugin-example module.exports = { environments: { mocha: { globals: { it: true, xit: true, describe: true, xdescribe: true } } }, rules: { rule1: {}, rule2: {}; } }; ``` To migrate this environment into a config, you need to add a new key in the `plugin.configs` object that has a flat config object containing the same information, like this: ```js const plugin = { configs: {}, rules: {}, processors: {}, }; // assign configs here so we can reference `plugin` Object.assign(plugin.configs, { mocha: { languageOptions: { globals: { it: "writeable", xit: "writeable", describe: "writeable", xdescribe: "writeable", }, }, }, }); // for ESM export default plugin; // OR for CommonJS module.exports = plugin; ``` Your users can then use this exported config like this: ```js import { defineConfig } from "eslint/config"; import example from "eslint-plugin-example"; export default defineConfig([ { files: ["**/tests/*.js"], plugins: { example, }, // use the mocha globals extends: ["example/mocha"], // and provide your own overrides languageOptions: { globals: { it: "readonly", }, }, }, ]); ``` You should update your documentation so your plugin users know how to reference the exported configs. ## Backwards Compatibility If your plugin needs to work with both the old and new configuration systems, then you'll need to: 1. **Export a CommonJS entrypoint.** The old configuration system cannot load plugins that are published only in ESM format. If your source code is in ESM, then you'll need to use a bundler that can generate a CommonJS version and use the [`exports`](https://nodejs.org/api/packages.html#package-entry-points) key in your `package.json` file to ensure the CommonJS version can be found by Node.js. 1. **Keep the `environments` key.** If your plugin exports custom environments, you should keep those as they are and also export the equivalent flat configs as described above. The `environments` key is ignored when ESLint is running in flat config mode. 1. **Export both eslintrc and flat configs.** The `configs` key is only validated when a config is used, so you can provide both formats of configs in the `configs` key. We recommend prefixing older format configs with `legacy-`. For example, if your primary config is called `recommended` and is in flat config format, then you can also have a config named `legacy-recommended` that is the eslintrc config format. If you don’t want to update the config name, you can also create an additional entry in the configs object prefixed with `"flat/"` (for example, `"flat/recommended"`). See [Backwards Compatibility for Legacy Configs](plugins#backwards-compatibility-for-legacy-configs) for more details. ## Further Reading - [Overview of the flat config file format blog post](https://eslint.org/blog/2022/08/new-config-system-part-2/) - [API usage of new configuration system blog post](https://eslint.org/blog/2022/08/new-config-system-part-3/) - [Background to new configuration system blog post](https://eslint.org/blog/2022/08/new-config-system-part-1/) --- --- title: Create Plugins eleventyNavigation: key: create plugins parent: extend eslint title: Create Plugins order: 2 --- ESLint plugins extend ESLint with additional functionality. In most cases, you'll extend ESLint by creating plugins that encapsulate the additional functionality you want to share across multiple projects. ## Creating a plugin A plugin is a JavaScript object that exposes certain properties to ESLint: - `meta` - information about the plugin. - `configs` - an object containing named configurations. - `rules` - an object containing the definitions of custom rules. - `processors` - an object containing named processors. To get started, create a JavaScript file and export an object containing the properties you'd like ESLint to use. To make your plugin as easy to maintain as possible, we recommend that you format your plugin entrypoint file to look like this: ```js const plugin = { meta: {}, configs: {}, rules: {}, processors: {}, }; // for ESM export default plugin; // OR for CommonJS module.exports = plugin; ``` If you plan to distribute your plugin as an npm package, make sure that the module that exports the plugin object is the default export of your package. This will enable ESLint to import the plugin when it is specified in the command line in the [`--plugin` option](../use/command-line-interface#--plugin). ### Meta Data in Plugins For easier debugging and more effective caching of plugins, it's recommended to provide a `name`, `version`, and `namespace` in a `meta` object at the root of your plugin, like this: ```js const plugin = { // preferred location of name and version meta: { name: "eslint-plugin-example", version: "1.2.3", namespace: "example", }, rules: { // add rules here }, }; // for ESM export default plugin; // OR for CommonJS module.exports = plugin; ``` The `meta.name` property should match the npm package name for your plugin and the `meta.version` property should match the npm package version for your plugin. The `meta.namespace` property should match the prefix you'd like users to use for accessing the plugin's rules, processors, languages, and configs. The namespace is typically what comes after `eslint-plugin-` in your package name, which is why this example uses `"example"`. Providing a namespace allows the `defineConfig()` function to find your plugin even when a user assigns a different namespace in their config file. The easiest way to add the name and version is by reading this information from your `package.json`, as in this example: ```js import fs from "fs"; const pkg = JSON.parse( fs.readFileSync(new URL("./package.json", import.meta.url), "utf8"), ); const plugin = { // preferred location of name and version meta: { name: pkg.name, version: pkg.version, namespace: "example", }, rules: { // add rules here }, }; export default plugin; ``` ::: tip While there are no restrictions on plugin names, it helps others to find your plugin on [npm](https://npmjs.com) when you follow these naming conventions: - **Unscoped:** If your npm package name won't be scoped (doesn't begin with `@`), then the plugin name should begin with `eslint-plugin-`, such as `eslint-plugin-example`. - **Scoped:** If your npm package name will be scoped, then the plugin name should be in the format of `@/eslint-plugin-` such as `@jquery/eslint-plugin-jquery` or even `@/eslint-plugin` such as `@jquery/eslint-plugin`. ::: As an alternative, you can also expose `name` and `version` properties at the root of your plugin, such as: ```js const plugin = { // alternate location of name and version name: "eslint-plugin-example", version: "1.2.3", rules: { // add rules here }, }; // for ESM export default plugin; // OR for CommonJS module.exports = plugin; ``` ::: important While the `meta` object is the preferred way to provide the plugin name and version, this format is also acceptable and is provided for backward compatibility. ::: ### Rules in Plugins Plugins can expose custom rules for use in ESLint. To do so, the plugin must export a `rules` object containing a key-value mapping of rule ID to rule. The rule ID does not have to follow any naming convention except that it should not contain a `/` character (so it can just be `dollar-sign` but not `foo/dollar-sign`, for instance). To learn more about creating custom rules in plugins, refer to [Custom Rules](custom-rules). ```js const plugin = { meta: { name: "eslint-plugin-example", version: "1.2.3", }, rules: { "dollar-sign": { create(context) { // rule implementation ... }, }, }, }; // for ESM export default plugin; // OR for CommonJS module.exports = plugin; ``` In order to use a rule from a plugin in a configuration file, import the plugin and include it in the `plugins` key, specifying a namespace. Then, use that namespace to reference the rule in the `rules` configuration, like this: ```js // eslint.config.js import { defineConfig } from "eslint/config"; import example from "eslint-plugin-example"; export default defineConfig([ { files: ["**/*.js"], // any patterns you want to apply the config to plugins: { example, }, rules: { "example/dollar-sign": "error", }, }, ]); ``` ::: warning Namespaces that don't begin with `@` may not contain a `/`; namespaces that begin with `@` may contain a `/`. For example, `eslint/plugin` is not a valid namespace but `@eslint/plugin` is valid. This restriction is for backwards compatibility with eslintrc plugin naming restrictions. ::: ### Processors in Plugins Plugins can expose [processors](custom-processors) for use in configuration file by providing a `processors` object. Similar to rules, each key in the `processors` object is the name of a processor and each value is the processor object itself. Here's an example: ```js const plugin = { meta: { name: "eslint-plugin-example", version: "1.2.3", }, processors: { "processor-name": { preprocess(text, filename) { /* ... */ }, postprocess(messages, filename) { /* ... */ }, }, }, }; // for ESM export default plugin; // OR for CommonJS module.exports = plugin; ``` In order to use a processor from a plugin in a configuration file, import the plugin and include it in the `plugins` key, specifying a namespace. Then, use that namespace to reference the processor in the `processor` configuration, like this: ```js // eslint.config.js import { defineConfig } from "eslint/config"; import example from "eslint-plugin-example"; export default defineConfig([ { files: ["**/*.txt"], plugins: { example, }, processor: "example/processor-name", }, ]); ``` ### Configs in Plugins You can bundle configurations inside a plugin by specifying them under the `configs` key. This can be useful when you want to bundle a set of custom rules with a configuration that enables the recommended options. Multiple configurations are supported per plugin. You can include individual rules from a plugin in a config that's also included in the plugin. In the config, you must specify your plugin name in the `plugins` object as well as any rules you want to enable that are part of the plugin. Any plugin rules must be prefixed with the plugin namespace. Here's an example: ```js const plugin = { meta: { name: "eslint-plugin-example", version: "1.2.3", }, configs: {}, rules: { "dollar-sign": { create(context) { // rule implementation ... }, }, }, }; // assign configs here so we can reference `plugin` Object.assign(plugin.configs, { recommended: [ { plugins: { example: plugin, }, rules: { "example/dollar-sign": "error", }, languageOptions: { globals: { myGlobal: "readonly", }, parserOptions: { ecmaFeatures: { jsx: true, }, }, }, }, ], }); // for ESM export default plugin; // OR for CommonJS module.exports = plugin; ``` This plugin exports a `recommended` config that is an array with one config object. When there is just one config object, you can also export just the object without an enclosing array. In order to use a config from a plugin in a configuration file, import the plugin and use the `extends` key to reference the name of the config, like this: ```js // eslint.config.js import { defineConfig } from "eslint/config"; import example from "eslint-plugin-example"; export default defineConfig([ { files: ["**/*.js"], // any patterns you want to apply the config to plugins: { example, }, extends: ["example/recommended"], }, ]); ``` ::: important Plugins cannot force a specific configuration to be used. Users must manually include a plugin's configurations in their configuration file. ::: #### Backwards Compatibility for Legacy Configs If your plugin needs to export configs that work both with the current (flat config) system and the old (eslintrc) system, you can export both config types from the `configs` key. When exporting legacy configs, we recommend prefixing the name with `"legacy-"` (for example, `"legacy-recommended"`) to make it clear how the config should be used. If you're working on a plugin that has existed prior to ESLint v9.0.0, then you may already have legacy configs with names such as `"recommended"`. If you don't want to update the config name, you can also create an additional entry in the `configs` object prefixed with `"flat/"` (for example, `"flat/recommended"`). Here's an example: ```js const plugin = { meta: { name: "eslint-plugin-example", version: "1.2.3", }, configs: {}, rules: { "dollar-sign": { create(context) { // rule implementation ... }, }, }, }; // assign configs here so we can reference `plugin` Object.assign(plugin.configs, { // flat config format "flat/recommended": [ { plugins: { example: plugin, }, rules: { "example/dollar-sign": "error", }, languageOptions: { globals: { myGlobal: "readonly", }, parserOptions: { ecmaFeatures: { jsx: true, }, }, }, }, ], // eslintrc format recommended: { plugins: ["example"], rules: { "example/dollar-sign": "error", }, globals: { myGlobal: "readonly", }, parserOptions: { ecmaFeatures: { jsx: true, }, }, }, }); // for ESM export default plugin; // OR for CommonJS module.exports = plugin; ``` With this approach, both configuration systems recognize `"recommended"`. The old config system uses the `recommended` key while the current config system uses the `flat/recommended` key. The `defineConfig()` helper first looks at the `recommended` key, and if that is not in the correct format, it looks for the `flat/recommended` key. This allows you an upgrade path if you'd later like to rename `flat/recommended` to `recommended` when you no longer need to support the old config system. ## Testing a Plugin ESLint provides the [`RuleTester`](../integrate/nodejs-api#ruletester) utility to make it easy to test the rules of your plugin. ## Linting a Plugin ESLint plugins should be linted too! It's suggested to lint your plugin with the `recommended` configurations of: - [eslint](https://www.npmjs.com/package/eslint) - [eslint-plugin-eslint-plugin](https://www.npmjs.com/package/eslint-plugin-eslint-plugin) - [eslint-plugin-n](https://www.npmjs.com/package/eslint-plugin-n) ## Share Plugins In order to make your plugin available publicly, you have to publish it on npm. When doing so, please be sure to: 1. **List ESLint as a peer dependency.** Because plugins are intended for use with ESLint, it's important to add the `eslint` package as a peer dependency. To do so, manually edit your `package.json` file to include a `peerDependencies` block, like this: ```json { "peerDependencies": { "eslint": ">=9.0.0" } } ``` 2. **Specify keywords.** ESLint plugins should specify `eslint`, `eslintplugin` and `eslint-plugin` as [keywords](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#keywords) in your `package.json` file. --- --- title: Rule Deprecation --- The rule deprecation metadata describes whether a rule is deprecated and how the rule can be replaced if there is a replacement. The legacy format used the two top-level [rule meta](./custom-rules#rule-structure) properties `deprecated` (as a boolean only) and `replacedBy`. In the new format, `deprecated` is a boolean or an object of type `DeprecatedInfo`, and `replacedBy` should be defined inside `deprecated` instead of at the top-level. ## ◆ DeprecatedInfo type This type represents general information about a rule deprecation. Every property is optional. - `message` (`string`)
A general message presentable to the user. May contain why this rule is deprecated or how to replace the rule. - `url` (`string`)
An URL with more information about this rule deprecation. - `replacedBy` (`ReplacedByInfo[]`)
Information about the available replacements for the rule. This may be an empty array to explicitly state there is no replacement. - `deprecatedSince` (`string`)
[Semver](https://semver.org/) of the version deprecating the rule. - `availableUntil` (`string | null`)
[Semver](https://semver.org/) of the version likely to remove the rule, e.g. the next major version. The special value `null` means the rule will no longer be changed but will be kept available. ## ◆ ReplacedByInfo type The type describes a single possible replacement of a rule. Every property is optional. - `message` (`string`)
A general message about this rule replacement, e.g. - `url` (`string`)
An URL with more information about this rule replacement. - `plugin` (`ExternalSpecifier`)
Specifies which plugin has the replacement rule. The name should be the package name and should be "eslint" if the replacement is an ESLint core rule. This property should be omitted if the replacement is in the same plugin. - `rule` (`ExternalSpecifier`)
Specifies the replacement rule. May be omitted if the plugin only contains a single rule or has the same name as the rule. ### ◆ ExternalSpecifier type This type represents an external resource. Every property is optional. - `name` (`string`)
The package name for `plugin` and the rule id for `rule`. - `url` (`string`)
An URL pointing to documentation for the plugin / rule.. --- --- title: ScopeManager --- This document was written based on the implementation of [eslint-scope](https://github.com/eslint/js/tree/main/packages/eslint-scope), a fork of [escope](https://github.com/estools/escope), and deprecates some members ESLint is not using. --- ## ScopeManager interface `ScopeManager` object has all variable scopes. ### Fields #### scopes - **Type:** `Scope[]` - **Description:** All scopes. #### globalScope - **Type:** `Scope` - **Description:** The root scope. ### Methods #### acquire(node, inner = false) - **Parameters:** - `node` (`ASTNode`) ... An AST node to get their scope. - `inner` (`boolean`) ... If the node has multiple scope, this returns the outermost scope normally. If `inner` is `true` then this returns the innermost scope. Default is `false`. - **Return type:** `Scope | null` - **Description:** Get the scope of a given AST node. The gotten scope's `block` property is the node. This method never returns `function-expression-name` scope. If the node does not have their scope, this returns `null`. #### addGlobals(names) - **Parameters:** - `names` (`string[]`) ... Names of variables to add to the global scope. - **Return type:** `undefined` - **Description:** Adds variables to the global scope and resolves references to them. This method is used by the ESLint core and should never be used in rules. #### getDeclaredVariables(node) - **Parameters:** - `node` (`ASTNode`) ... An AST node to get their variables. - **Return type:** `Variable[]` - **Description:** Get the variables that a given AST node defines. The gotten variables' `def[].node`/`def[].parent` property is the node. If the node does not define any variable, this returns an empty array. ### Deprecated members Those members are defined but not used in ESLint. #### isModule() - **Parameters:** - **Return type:** `boolean` - **Description:** `true` if this program is module. #### isImpliedStrict() - **Parameters:** - **Return type:** `boolean` - **Description:** `true` if this program is strict mode implicitly. I.e., `options.impliedStrict === true`. #### isStrictModeSupported() - **Parameters:** - **Return type:** `boolean` - **Description:** `true` if this program supports strict mode. I.e., `options.ecmaVersion >= 5`. #### acquireAll(node) - **Parameters:** - `node` (`ASTNode`) ... An AST node to get their scope. - **Return type:** `Scope[] | null` - **Description:** Get the scopes of a given AST node. The gotten scopes' `block` property is the node. If the node does not have their scope, this returns `null`. --- ## Scope interface `Scope` object has all variables and references in the scope. ### Fields #### type - **Type:** `string` - **Description:** The type of this scope. This is one of `"block"`, `"catch"`, `"class"`, `"class-field-initializer"`, `"class-static-block"`, `"for"`, `"function"`, `"function-expression-name"`, `"global"`, `"module"`, `"switch"`, `"with"`. #### isStrict - **Type:** `boolean` - **Description:** `true` if this scope is strict mode. #### upper - **Type:** `Scope | null` - **Description:** The parent scope. If this is the global scope then this property is `null`. #### childScopes - **Type:** `Scope[]` - **Description:** The array of child scopes. This does not include grandchild scopes. #### variableScope - **Type:** `Scope` - **Description:** The nearest ancestor whose `type` is one of `"class-field-initializer"`, `"class-static-block"`, `"function"`, `"global"`, or `"module"`. For the aforementioned scopes this is a self-reference. > This represents the lowest enclosing function or top-level scope. Class field initializers and class static blocks are implicit functions. Historically, this was the scope which hosts variables that are defined by `var` declarations, and thus the name `variableScope`. #### block - **Type:** `ASTNode` - **Description:** The AST node which created this scope. #### variables - **Type:** `Variable[]` - **Description:** The array of all variables which are defined on this scope. This does not include variables which are defined in child scopes. #### set - **Type:** `Map` - **Description:** The map from variable names to variable objects. #### references - **Type:** `Reference[]` - **Description:** The array of all references on this scope. This does not include references in child scopes. #### through - **Type:** `Reference[]` - **Description:** The array of references which could not be resolved in this scope. #### functionExpressionScope - **Type:** `boolean` - **Description:** `true` if this scope is `"function-expression-name"` scope. #### implicit This field exists only in the root `Scope` object (the global scope). It provides information about implicit global variables. Implicit global variables are variables that are neither built-in nor explicitly declared, but created implicitly by assigning values to undeclared variables in non-strict code. `Variable` objects for these variables are not present in the root `Scope` object's fields `variables` and `set`. The value of the `implicit` field is an object with two properties. ##### variables - **Type:** `Variable[]` - **Description:** The array of all implicit global variables. ##### set - **Type:** `Map` - **Description:** The map from variable names to variable objects for implicit global variables. ::: tip In `Variable` objects that represent implicit global variables, `references` is always an empty array. You can find references to these variables in the `through` field of the root `Scope` object (the global scope), among other unresolved references. ::: ### Deprecated members Those members are defined but not used in ESLint. #### taints - **Type:** `Map` - **Description:** The map from variable names to `tainted` flag. #### dynamic - **Type:** `boolean` - **Description:** `true` if this scope is dynamic. I.e., the type of this scope is `"global"` or `"with"`. #### directCallToEvalScope - **Type:** `boolean` - **Description:** `true` if this scope contains `eval()` invocations. #### thisFound - **Type:** `boolean` - **Description:** `true` if this scope contains `this`. #### resolve(node) - **Parameters:** - `node` (`ASTNode`) ... An AST node to get their reference object. The type of the node must be `"Identifier"`. - **Return type:** `Reference | null` - **Description:** Returns `this.references.find(r => r.identifier === node)`. #### isStatic() - **Parameters:** - **Return type:** `boolean` - **Description:** Returns `!this.dynamic`. #### isArgumentsMaterialized() - **Parameters:** - **Return type:** `boolean` - **Description:** `true` if this is a `"function"` scope which has used `arguments` variable. #### isThisMaterialized() - **Parameters:** - **Return type:** `boolean` - **Description:** Returns `this.thisFound`. #### isUsedName(name) - **Parameters:** - `name` (`string`) ... The name to check. - **Return type:** `boolean` - **Description:** `true` if a given name is used in variable names or reference names. --- ## Variable interface `Variable` object is variable's information. ### Fields #### name - **Type:** `string` - **Description:** The name of this variable. #### scope - **Type:** `Scope` - **Description:** The scope in which this variable is defined. #### identifiers - **Type:** `ASTNode[]` - **Description:** The array of `Identifier` nodes which define this variable. If this variable is redeclared, this array includes two or more nodes. #### references - **Type:** `Reference[]` - **Description:** The array of the references of this variable. #### defs - **Type:** `Definition[]` - **Description:** The array of the definitions of this variable. ### Deprecated members Those members are defined but not used in ESLint. #### tainted - **Type:** `boolean` - **Description:** The `tainted` flag. (always `false`) #### stack - **Type:** `boolean` - **Description:** The `stack` flag. (I'm not sure what this means.) --- ## Reference interface `Reference` object is reference's information. ### Fields #### identifier - **Type:** `ASTNode` - **Description:** The `Identifier` or `JSXIdentifier` node of this reference. #### from - **Type:** `Scope` - **Description:** The `Scope` object that this reference is on. #### resolved - **Type:** `Variable | null` - **Description:** The `Variable` object that this reference refers. If such variable was not defined, this is `null`. #### writeExpr - **Type:** `ASTNode | null` - **Description:** The ASTNode object which is right-hand side. #### init - **Type:** `boolean` - **Description:** `true` if this writing reference is a variable initializer or a default value. ### Methods #### isWrite() - **Parameters:** - **Return type:** `boolean` - **Description:** `true` if this reference is writing. #### isRead() - **Parameters:** - **Return type:** `boolean` - **Description:** `true` if this reference is reading. #### isWriteOnly() - **Parameters:** - **Return type:** `boolean` - **Description:** `true` if this reference is writing but not reading. #### isReadOnly() - **Parameters:** - **Return type:** `boolean` - **Description:** `true` if this reference is reading but not writing. #### isReadWrite() - **Parameters:** - **Return type:** `boolean` - **Description:** `true` if this reference is reading and writing. ### Deprecated members Those members are defined but not used in ESLint. #### tainted - **Type:** `boolean` - **Description:** The `tainted` flag. (always `false`) #### flag - **Type:** `number` - **Description:** `1` is reading, `2` is writing, `3` is reading/writing. #### partial - **Type:** `boolean` - **Description:** The `partial` flag. #### isStatic() - **Parameters:** - **Return type:** `boolean` - **Description:** `true` if this reference is resolved statically. --- ## Definition interface `Definition` object is variable definition's information. ### Fields #### type - **Type:** `string` - **Description:** The type of this definition. One of `"CatchClause"`, `"ClassName"`, `"FunctionName"`, `"ImplicitGlobalVariable"`, `"ImportBinding"`, `"Parameter"`, and `"Variable"`. #### name - **Type:** `ASTNode` - **Description:** The `Identifier` node of this definition. #### node - **Type:** `ASTNode` - **Description:** The enclosing node of the name. | type | node | | :------------------------- | :------------------------------------------------------------------------- | | `"CatchClause"` | `CatchClause` | | `"ClassName"` | `ClassDeclaration` or `ClassExpression` | | `"FunctionName"` | `FunctionDeclaration` or `FunctionExpression` | | `"ImplicitGlobalVariable"` | `AssignmentExpression` or `ForInStatement` or `ForOfStatement` | | `"ImportBinding"` | `ImportSpecifier`, `ImportDefaultSpecifier`, or `ImportNamespaceSpecifier` | | `"Parameter"` | `FunctionDeclaration`, `FunctionExpression`, or `ArrowFunctionExpression` | | `"Variable"` | `VariableDeclarator` | #### parent - **Type:** `ASTNode | undefined | null` - **Description:** The enclosing statement node of the name. | type | parent | | :------------------------- | :-------------------- | | `"CatchClause"` | `null` | | `"ClassName"` | `null` | | `"FunctionName"` | `null` | | `"ImplicitGlobalVariable"` | `null` | | `"ImportBinding"` | `ImportDeclaration` | | `"Parameter"` | `null` | | `"Variable"` | `VariableDeclaration` | ### Deprecated members Those members are defined but not used in ESLint. #### index - **Type:** `number | undefined | null` - **Description:** The index in the declaration statement. #### kind - **Type:** `string | undefined | null` - **Description:** The kind of the declaration statement. --- --- title: Selectors --- Some rules and APIs allow the use of selectors to query an AST. This page is intended to: 1. Explain what selectors are 2. Describe the syntax for creating selectors 3. Describe what selectors can be used for ## What is a selector? A selector is a string that can be used to match nodes in an Abstract Syntax Tree (AST). This is useful for describing a particular syntax pattern in your code. The syntax for AST selectors is similar to the syntax for [CSS selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors). If you've used CSS selectors before, the syntax for AST selectors should be easy to understand. The simplest selector is just a node type. A node type selector will match all nodes with the given type. For example, consider the following program: ```js var foo = 1; bar.baz(); ``` The selector "`Identifier`" will match all `Identifier` nodes in the program. In this case, the selector will match the nodes for `foo`, `bar`, and `baz`. Selectors are not limited to matching against single node types. For example, the selector `VariableDeclarator > Identifier` will match all `Identifier` nodes that have a `VariableDeclarator` as a direct parent. In the program above, this will match the node for `foo`, but not the nodes for `bar` and `baz`. ## What syntax can selectors have? The following selectors are supported: - AST node type: `ForStatement` - wildcard (matches all nodes): `*` - attribute existence: `[attr]` - attribute value: `[attr="foo"]` or `[attr=123]` - attribute regex: `[attr=/foo.*/]` (with some [known issues](#known-issues)) - attribute conditions: `[attr!="foo"]`, `[attr>2]`, `[attr<3]`, `[attr>=2]`, or `[attr<=3]` - nested attribute: `[attr.level2="foo"]` - field: `FunctionDeclaration > Identifier.id` - First or last child: `:first-child` or `:last-child` - nth-child (no ax+b support): `:nth-child(2)` - nth-last-child (no ax+b support): `:nth-last-child(1)` - descendant: `FunctionExpression ReturnStatement` - child: `UnaryExpression > Literal` - following sibling: `VariableDeclaration ~ VariableDeclaration` - adjacent sibling: `ArrayExpression > Literal + SpreadElement` - negation: `:not(ForStatement)` - matches-any: `:matches([attr] > :first-child, :last-child)` - class of AST node: `:statement`, `:expression`, `:declaration`, `:function`, or `:pattern` This syntax is very powerful, and can be used to precisely select many syntactic patterns in your code. The examples in this section were adapted from the [esquery](https://github.com/estools/esquery) documentation. ## What can selectors be used for? If you're writing custom ESLint rules, you might be interested in using selectors to examine specific parts of the AST. If you're configuring ESLint for your codebase, you might be interested in restricting particular syntax patterns with selectors. ### Listening for selectors in rules When writing a custom ESLint rule, you can listen for nodes that match a particular selector as the AST is traversed. ```js module.exports = { create(context) { // ... return { // This listener will be called for all IfStatement nodes with blocks. "IfStatement > BlockStatement": function (blockStatementNode) { // ...your logic here }, // This listener will be called for all function declarations with more than 3 parameters. "FunctionDeclaration[params.length>3]": function ( functionDeclarationNode, ) { // ...your logic here }, }; }, }; ``` Adding `:exit` to the end of a selector will cause the listener to be called when the matching nodes are exited during traversal, rather than when they are entered. If two or more selectors match the same node, their listeners will be called in order of increasing specificity. The specificity of an AST selector is similar to the specificity of a CSS selector: - When comparing two selectors, the selector that contains more class selectors, attribute selectors, and pseudo-class selectors (excluding `:not()`) has higher specificity. - If the class/attribute/pseudo-class count is tied, the selector that contains more node type selectors has higher specificity. If multiple selectors have equal specificity, their listeners will be called in alphabetical order for that node. ### Restricting syntax with selectors With the [no-restricted-syntax](../rules/no-restricted-syntax) rule, you can restrict the usage of particular syntax in your code. For example, you can use the following configuration to disallow using `if` statements that do not have block statements as their body: ```json { "rules": { "no-restricted-syntax": [ "error", "IfStatement > :not(BlockStatement).consequent" ] } } ``` ...or equivalently, you can use this configuration: ```json { "rules": { "no-restricted-syntax": [ "error", "IfStatement[consequent.type!='BlockStatement']" ] } } ``` As another example, you can disallow calls to `require()`: ```json { "rules": { "no-restricted-syntax": [ "error", "CallExpression[callee.name='require']" ] } } ``` Or you can enforce that calls to `setTimeout` always have two arguments: ```json { "rules": { "no-restricted-syntax": [ "error", "CallExpression[callee.name='setTimeout'][arguments.length!=2]" ] } } ``` Using selectors in the `no-restricted-syntax` rule can give you a lot of control over problematic patterns in your codebase, without needing to write custom rules to detect each pattern. ### Known issues Due to a [bug](https://github.com/estools/esquery/issues/68) in [esquery](https://github.com/estools/esquery), regular expressions that contain a forward-slash character `/` aren't properly parsed, so `[value=/some\/path/]` will be a syntax error. As a [workaround](https://github.com/estools/esquery/issues/68), you can replace the `/` character with its unicode counterpart, like so: `[value=/some\u002Fpath/]`. For example, the following configuration disallows importing from `some/path`: ```json { "rules": { "no-restricted-syntax": [ "error", "ImportDeclaration[source.value=/^some\\u002Fpath$/]" ] } } ``` Note that the `\` character needs to be escaped (`\\`) in JSON and string literals. --- --- title: Share Configurations (Deprecated) --- ::: warning This documentation is for shareable configs using the deprecated eslintrc configuration format. [View the updated documentation](shareable-configs). ::: To share your ESLint configuration, create a **shareable config**. You can publish your shareable config on [npm](https://www.npmjs.com/) so that others can download and use it in their ESLint projects. This page explains how to create and publish a shareable config. ## Creating a Shareable Config Shareable configs are simply npm packages that export a configuration object. To start, [create a Node.js module](https://docs.npmjs.com/getting-started/creating-node-modules) like you normally would. The module name must take one of the following forms: - Begin with `eslint-config-`, such as `eslint-config-myconfig`. - Be an npm [scoped module](https://docs.npmjs.com/misc/scope). To create a scoped module, name or prefix the module with `@scope/eslint-config`, such as `@scope/eslint-config` or `@scope/eslint-config-myconfig`. In your module, export the shareable config from the module's [`main`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#main) entry point file. The default main entry point is `index.js`. For example: ```js // index.js module.exports = { globals: { MyGlobal: true, }, rules: { semi: [2, "always"], }, }; ``` Since the `index.js` file is just JavaScript, you can read these settings from a file or generate them dynamically. ## Publishing a Shareable Config Once your shareable config is ready, you can [publish it to npm](https://docs.npmjs.com/getting-started/publishing-npm-packages) to share it with others. We recommend using the `eslint` and `eslintconfig` [keywords](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#keywords) in the `package.json` file so others can easily find your module. You should declare your dependency on ESLint in the `package.json` using the [peerDependencies](https://docs.npmjs.com/files/package.json#peerdependencies) field. The recommended way to declare a dependency for future-proof compatibility is with the ">=" range syntax, using the lowest required ESLint version. For example: ```json { "peerDependencies": { "eslint": ">= 3" } } ``` If your shareable config depends on a plugin, you should also specify it as a `peerDependency` (plugins will be loaded relative to the end user's project, so the end user is required to install the plugins they need). However, if your shareable config depends on a [custom parser](custom-parsers) or another shareable config, you can specify these packages as `dependencies` in the `package.json`. You can also test your shareable config on your computer before publishing by linking your module globally. Type: ```bash npm link ``` Then, in your project that wants to use your shareable config, type: ```bash npm link eslint-config-myconfig ``` Be sure to replace `eslint-config-myconfig` with the actual name of your module. ## Using a Shareable Config To use a shareable config, include the config name in the `extends` field of a configuration file. For the value, use your module name. For example: ```json { "extends": "eslint-config-myconfig" } ``` You can also omit the `eslint-config-` and it is automatically assumed by ESLint: ```json { "extends": "myconfig" } ``` You cannot use shareable configs with the ESLint CLI [`--config`](../use/command-line-interface#-c---config) flag. ### npm Scoped Modules npm [scoped modules](https://docs.npmjs.com/misc/scope) are also supported in a number of ways. You can use the module name: ```json { "extends": "@scope/eslint-config" } ``` You can also omit the `eslint-config` and it is automatically assumed by ESLint: ```json { "extends": "@scope" } ``` The module name can also be customized. For example, if you have a package named `@scope/eslint-config-myconfig`, the configuration can be specified as: ```json { "extends": "@scope/eslint-config-myconfig" } ``` You could also omit `eslint-config` to specify the configuration as: ```json { "extends": "@scope/myconfig" } ``` ### Overriding Settings from Shareable Configs You can override settings from the shareable config by adding them directly into your `.eslintrc` file. ## Sharing Multiple Configs You can share multiple configs in the same npm package. Specify a default config for the package by following the directions in the [Creating a Shareable Config](#creating-a-shareable-config) section. You can specify additional shareable configs by adding a new file to your npm package and then referencing it from your ESLint config. As an example, you can create a file called `my-special-config.js` in the root of your npm package and export a config, such as: ```js // my-special-config.js module.exports = { rules: { quotes: [2, "double"], }, }; ``` Then, assuming you're using the package name `eslint-config-myconfig`, you can access the additional config via: ```json { "extends": "myconfig/my-special-config" } ``` When using [scoped modules](https://docs.npmjs.com/misc/scope) it is not possible to omit the `eslint-config` namespace. Doing so would result in resolution errors as explained above. Assuming the package name is `@scope/eslint-config`, the additional config can be accessed as: ```json { "extends": "@scope/eslint-config/my-special-config" } ``` Note that you can leave off the `.js` from the filename. **Important:** We strongly recommend always including a default config for your plugin to avoid errors. ## Local Config File Resolution If you need to make multiple configs that can extend each other and live in different directories, you can create a single shareable config that handles this scenario. As an example, let's assume you're using the package name `eslint-config-myconfig` and your package looks something like this: ```text myconfig ├── index.js └─┬ lib ├── defaults.js ├── dev.js ├── ci.js └─┬ ci ├── frontend.js ├── backend.js └── common.js ``` In the `index.js` file, you can do something like this: ```js module.exports = require("./lib/ci.js"); ``` Now inside the package you have `/lib/defaults.js`, which contains: ```js module.exports = { rules: { "no-console": 1, }, }; ``` Inside `/lib/ci.js` you have: ```js module.exports = require("./ci/backend"); ``` Inside `/lib/ci/common.js`: ```js module.exports = { rules: { "no-alert": 2, }, extends: "myconfig/lib/defaults", }; ``` Despite being in an entirely different directory, you'll see that all `extends` must use the full package path to the config file you wish to extend. Now inside `/lib/ci/backend.js`: ```js module.exports = { rules: { "no-console": 1, }, extends: "myconfig/lib/ci/common", }; ``` In the last file, once again see that to properly resolve your config, you need to include the full package path. ## Further Reading - [npm Developer Guide](https://docs.npmjs.com/misc/developers) --- --- title: Share Configurations eleventyNavigation: key: share configs parent: extend eslint title: Share Configurations order: 3 --- To share your ESLint configuration, create a **shareable config**. You can publish your shareable config on [npm](https://www.npmjs.com/) so that others can download and use it in their ESLint projects. This page explains how to create and publish a shareable config. ::: tip This page explains how to create a shareable config using the flat config format. For the deprecated eslintrc format, [see the deprecated documentation](shareable-configs-deprecated). ::: ## Creating a Shareable Config Shareable configs are simply npm packages that export a configuration object or array. To start, [create a Node.js module](https://docs.npmjs.com/getting-started/creating-node-modules) like you normally would. While you can name the package in any way that you'd like, we recommend using one of the following conventions to make your package easier to identify: - Begin with `eslint-config-`, such as `eslint-config-myconfig`. - For an npm [scoped module](https://docs.npmjs.com/misc/scope), name or prefix the module with `@scope/eslint-config`, such as `@scope/eslint-config` or `@scope/eslint-config-myconfig`. In your module, export the shareable config from the module's [`main`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#main) entry point file. The default main entry point is `index.js`. For example: ```js // index.js export default [ { languageOptions: { globals: { MyGlobal: true, }, }, rules: { semi: [2, "always"], }, }, ]; ``` Because the `index.js` file is just JavaScript, you can read these settings from a file or generate them dynamically. ::: tip Most of the time, you'll want to export an array of config objects from your shareable config. However, you can also export a single config object. Make sure your documentation clearly shows an example of how to use your shareable config inside of an `eslint.config.js` file to avoid user confusion. ::: ## Publishing a Shareable Config Once your shareable config is ready, you can [publish it to npm](https://docs.npmjs.com/getting-started/publishing-npm-packages) to share it with others. We recommend using the `eslint` and `eslintconfig` [keywords](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#keywords) in the `package.json` file so others can easily find your module. You should declare your dependency on ESLint in the `package.json` using the [peerDependencies](https://docs.npmjs.com/files/package.json#peerdependencies) field. The recommended way to declare a dependency for future-proof compatibility is with the ">=" range syntax, using the lowest required ESLint version. For example: ```json { "peerDependencies": { "eslint": ">= 9" } } ``` If your shareable config depends on a plugin or a custom parser, you should specify these packages as `dependencies` in your `package.json`. ## Using a Shareable Config To use a shareable config, import the package inside of an `eslint.config.js` file and add it into the exported array using `extends`, like this: ```js // eslint.config.js import { defineConfig } from "eslint/config"; import myconfig from "eslint-config-myconfig"; export default defineConfig([ { files: ["**/*.js"], extends: [myconfig], }, ]); ``` ::: warning It's not possible to use shareable configs with the ESLint CLI [`--config`](../use/command-line-interface#-c---config) flag. ::: ### Overriding Settings from Shareable Configs You can override settings from the shareable config by adding them directly into your `eslint.config.js` file after importing the shareable config. For example: ```js // eslint.config.js import { defineConfig } from "eslint/config"; import myconfig from "eslint-config-myconfig"; export default defineConfig([ { files: ["**/*.js"], extends: [myconfig], // anything from here will override myconfig rules: { "no-unused-vars": "warn", }, }, ]); ``` ## Sharing Multiple Configs Because shareable configs are just npm packages, you can export as many configs as you'd like from the same package. In addition to specifying a default config using the `main` entry in your `package.json`, you can specify additional shareable configs by adding a new file to your npm package and then referencing it from your `eslint.config.js` file. As an example, you can create a file called `my-special-config.js` in the root of your npm package and export a config, such as: ```js // my-special-config.js export default { rules: { quotes: [2, "double"], }, }; ``` Then, assuming you're using the package name `eslint-config-myconfig`, you can access the additional config via: ```js // eslint.config.js import { defineConfig } from "eslint/config"; import myconfig from "eslint-config-myconfig"; import mySpecialConfig from "eslint-config-myconfig/my-special-config.js"; export default defineConfig([ { files: ["**/*.js"], extends: [myconfig, mySpecialConfig], // anything from here will override myconfig rules: { "no-unused-vars": "warn", }, }, ]); ``` ::: important We strongly recommend always including a default export for your package to avoid confusion. ::: ## Further Reading - [npm Developer Guide](https://docs.npmjs.com/misc/developers) --- --- title: Stats Data eleventyNavigation: key: stats data parent: extend eslint title: Stats Data order: 6 --- {%- from 'components/npx_tabs.macro.html' import npx_tabs %} While an analysis of the overall rule performance for an ESLint run can be carried out by setting the [TIMING](./custom-rules#profile-rule-performance) environment variable, it can sometimes be useful to acquire more _granular_ timing data (lint time per file per rule) or collect other measures of interest. In particular, when developing new [custom plugins](./plugins) and evaluating/benchmarking new languages or rule sets. For these use cases, you can optionally collect runtime statistics from ESLint. ## Enable stats collection To enable collection of statistics, you can either: 1. Use the `--stats` CLI option. This will pass the stats data into the formatter used to output results from ESLint. (Note: not all formatters output stats data.) 1. Set `stats: true` as an option on the `ESLint` constructor. Enabling stats data adds a new `stats` key to each [LintResult](../integrate/nodejs-api#-lintresult-type) object containing data such as parse times, fix times, lint times per rule. As such, it is not available via stdout but made easily ingestible via a formatter using the CLI or via the Node.js API to cater to your specific needs. ## ◆ Stats type The `Stats` value is the timing information of each lint run. The `stats` property of the [LintResult](../integrate/nodejs-api#-lintresult-type) type contains it. It has the following properties: - `fixPasses` (`number`)
The number of times ESLint has applied at least one fix after linting. - `times` (`{ passes: TimePass[] }`)
The times spent on (parsing, fixing, linting) a file, where the linting refers to the timing information for each rule. - `TimePass` (`{ parse: ParseTime, rules?: Record, fix: FixTime, total: number }`)
An object containing the times spent on (parsing, fixing, linting) - `ParseTime` (`{ total: number }`)
The total time that is spent when parsing a file. - `RuleTime` (`{ total: number }`)
The total time that is spent on a rule. - `FixTime` (`{ total: number }`)
The total time that is spent on applying fixes to the code. ### CLI usage Let's consider the following example: ```js [file-to-fix.js] /*eslint no-regex-spaces: "error", wrap-regex: "error"*/ function a() { return / foo/.test("bar"); } ``` Run ESLint with `--stats` and output to JSON via the built-in [`json` formatter](../use/formatters/): {{ npx_tabs({ package: "eslint", args: ["file-to-fix.js", "--fix", "--stats", "-f", "json"] }) }} This yields the following `stats` entry as part of the formatted lint results object: ```json { "times": { "passes": [ { "parse": { "total": 3.975959 }, "rules": { "no-regex-spaces": { "total": 0.160792 }, "wrap-regex": { "total": 0.422626 } }, "fix": { "total": 0.080208 }, "total": 12.765959 }, { "parse": { "total": 0.623542 }, "rules": { "no-regex-spaces": { "total": 0.043084 }, "wrap-regex": { "total": 0.007959 } }, "fix": { "total": 0 }, "total": 1.148875 } ] }, "fixPasses": 1 } ``` Note, that for the simple example above, the sum of all rule times should be directly comparable to the first column of the TIMING output. Running the same command with `TIMING=all`, you can verify this: ```bash $ TIMING=all npx eslint file-to-fix.js --fix --stats -f json ... Rule | Time (ms) | Relative :---------------|----------:|--------: wrap-regex | 0.431 | 67.9% no-regex-spaces | 0.204 | 32.1% ``` ### API Usage You can achieve the same thing using the Node.js API by passing `stats: true` as an option to the `ESLint` constructor. For example: ```js const { ESLint } = require("eslint"); (async function main() { // 1. Create an instance. const eslint = new ESLint({ stats: true, fix: true }); // 2. Lint files. const results = await eslint.lintFiles(["file-to-fix.js"]); // 3. Format the results. const formatter = await eslint.loadFormatter("json"); const resultText = formatter.format(results); // 4. Output it. console.log(resultText); })().catch(error => { process.exitCode = 1; console.error(error); }); ``` --- --- title: Ways to Extend ESLint eleventyNavigation: key: ways to extend parent: extend eslint title: Ways to Extend ESLint order: 1 --- ESLint is highly pluggable and configurable. There are a variety of ways that you can extend ESLint's functionality. This page explains the ways to extend ESLint, and how these extensions all fit together. ## Plugins Plugins let you add your own ESLint custom rules and custom processors to a project. You can publish a plugin as an npm module. Plugins are useful because your project may require some ESLint configuration that isn't included in the core `eslint` package. For example, if you're using a frontend JavaScript library like [React](https://react.dev/) or framework like [Vue](https://vuejs.org/), these tools have some features that require custom rules outside the scope of the ESLint core rules. Often a plugin is paired with a configuration for ESLint that applies a set of features from the plugin to a project. You can include configurations in a plugin as well. For example, [`eslint-plugin-react`](https://www.npmjs.com/package/eslint-plugin-react) is an ESLint plugin that includes rules specifically for React projects. The rules include things like enforcing consistent usage of React component lifecycle methods and requiring the use of key props when rendering dynamic lists. To learn more about creating the extensions you can include in a plugin, refer to the following documentation: - [Custom Rules](custom-rules) - [Custom Processors](custom-processors) - [Configs in Plugins](plugins#configs-in-plugins) To learn more about bundling these extensions into a plugin, refer to [Plugins](plugins). ## Shareable Configs ESLint shareable configs are pre-defined configurations for ESLint that you can use in your projects. They bundle rules and other configuration together in an npm package. Anything that you can put in a configuration file can be put in a shareable config. You can either publish a shareable config independently or as part of a plugin. For example, a popular shareable config is [`eslint-config-airbnb`](https://www.npmjs.com/package/eslint-config-airbnb), which contains a variety of rules in addition to some [parser options](../use/configure/language-options#specifying-parser-options). This is a set of rules for ESLint that is designed to match the style guide used by the [Airbnb JavaScript style guide](https://github.com/airbnb/javascript). By using the `eslint-config-airbnb` shareable config, you can automatically enforce the Airbnb style guide in your project without having to manually configure each rule. To learn more about creating a shareable config, refer to [Share Configurations](shareable-configs). ## Custom Formatters Custom formatters take ESLint linting results and output the results in a format that you define. Custom formatters let you display linting results in a format that best fits your needs, whether that's in a specific file format, a certain display style, or a format optimized for a particular tool. You only need to create a custom formatter if the [built-in formatters](../use/formatters/) don't serve your use case. For example, the custom formatter [eslint-formatter-gitlab](https://www.npmjs.com/package/eslint-formatter-gitlab) can be used to display ESLint results in [GitLab](https://about.gitlab.com/) code quality reports. To learn more about creating a custom formatter, refer to [Custom Formatters](custom-formatters). ## Custom Parsers ESLint custom parsers are a way to extend ESLint to support the linting of new language features or custom syntax in your code. A parser is responsible for taking your code and transforming it into an abstract syntax tree (AST) that ESLint can then analyze and lint. ESLint ships with a built-in JavaScript parser (Espree), but custom parsers allow you to lint other languages or to extend the linting capabilities of the built-in parser. For example, the custom parser [@typescript-eslint/parser](https://typescript-eslint.io/packages/parser) extends ESLint to lint TypeScript code. Custom parsers can be also included in a plugin. To learn more about creating a custom parser, refer to [Custom Parsers](custom-parsers). --- --- title: Integrate ESLint eleventyNavigation: key: integrate eslint title: integrate ESLint order: 3 --- This guide is intended for those who wish to integrate the functionality of ESLint into other applications by using the ESLint API. In order to integrate ESLint, it's recommended that: - You know JavaScript since ESLint is written in JavaScript. - You have some familiarity with Node.js since ESLint runs on it. If that sounds like you, then continue reading to get started. ## [Integrate with the Node.js API Tutorial](integration-tutorial) This tutorial walks you through the process of creating a basic integration with ESLint using the Node.js API. ## [Node.js API Reference](nodejs-api) If you're interested in writing a tool that uses ESLint, then you can use the Node.js API to get programmatic access to functionality. --- --- title: Integrate with the Node.js API Tutorial eleventyNavigation: key: integrate with the node.js api tutorial parent: integrate eslint title: Integrate with the Node.js API Tutorial order: 1 --- {%- from 'components/npm_tabs.macro.html' import npm_tabs with context %} This guide walks you through integrating the `ESLint` class to lint files and retrieve results, which can be useful for creating integrations with other projects. ## Why Create an Integration? You might want to create an ESLint integration if you're creating developer tooling, such as the following: - **Code editors and IDEs**: Integrating ESLint with code editors and IDEs can provide real-time feedback on code quality and automatically highlight potential issues as you type. Many editors already have ESLint plugins available, but you may need to create a custom integration if the existing plugins do not meet your specific requirements. - **Custom linter tools**: If you're building a custom linter tool that combines multiple linters or adds specific functionality, you may want to integrate ESLint into your tool to provide JavaScript linting capabilities. - **Code review tools**: Integrating ESLint with code review tools can help automate the process of identifying potential issues in the codebase. - **Learning platforms**: If you are developing a learning platform or coding tutorial, integrating ESLint can provide real-time feedback to users as they learn JavaScript, helping them improve their coding skills and learn best practices. - **Developer tool integration**: If you're creating or extending a developer tool, such as a bundler or testing framework, you may want to integrate ESLint to provide linting capabilities. You can integrate ESLint directly into the tool or as a plugin. ## What You'll Build In this guide, you'll create a simple Node.js project that uses the `ESLint` class to lint files and retrieve results. ## Requirements This tutorial assumes you are familiar with JavaScript and Node.js. To follow this tutorial, you'll need to have the following: - Node.js (`^20.19.0`, `^22.13.0`, or `>=24`) - npm - A text editor ## Step 1: Setup First, create a new project directory: ```shell mkdir eslint-integration cd eslint-integration ``` Initialize the project with a `package.json` file: {{ npm_tabs({ command: "init", packages: [], args: ["-y"] }) }} Install the `eslint` package as a dependency (**not** as a dev dependency): {{ npm_tabs({ command: "install", packages: ["eslint"], args: [] }) }} Create a new file called `example-eslint-integration.js` in the project root: ```shell touch example-eslint-integration.js ``` ## Step 2: Import and Configure the `ESLint` Instance Import the `ESLint` class from the `eslint` package and create a new instance. You can customize the ESLint configuration by passing an options object to the `ESLint` constructor: ```javascript // example-eslint-integration.js const { ESLint } = require("eslint"); // Create an instance of ESLint with the configuration passed to the function function createESLintInstance(overrideConfig) { return new ESLint({ overrideConfigFile: true, overrideConfig, fix: true, }); } ``` ## Step 3: Lint and Fix Files To lint a file, use the `lintFiles` method of the `ESLint` instance. The `filePaths` argument passed to `ESLint#lintFiles()` can be a string or an array of strings, representing the file path(s) you want to lint. The file paths can be globs or filenames. The static method `ESLint.outputFixes()` takes the linting results from the call to `ESLint#lintFiles()`, and then writes the fixed code back to the source files. ```javascript // example-eslint-integration.js // ... previous step's code to instantiate the ESLint instance // Lint the specified files and return the results async function lintAndFix(eslint, filePaths) { const results = await eslint.lintFiles(filePaths); // Apply automatic fixes and output fixed code await ESLint.outputFixes(results); return results; } ``` ## Step 4: Output Results Define a function to output the linting results to the console. This should be specific to your integration's needs. For example, you could report the linting results to a user interface. In this example, we'll simply log the results to the console: ```javascript // example-eslint-integration.js // ... previous step's code to instantiate the ESLint instance // and get linting results. // Log results to console if there are any problems function outputLintingResults(results) { // Identify the number of problems found const problems = results.reduce( (acc, result) => acc + result.errorCount + result.warningCount, 0, ); if (problems > 0) { console.log("Linting errors found!"); console.log(results); } else { console.log("No linting errors found."); } return results; } ``` ## Step 5: Put It All Together Put the above functions together in a new function called `lintFiles`. This function will be the main entry point for your integration: ```javascript // example-eslint-integration.js // Put previous functions all together async function lintFiles(filePaths) { // The ESLint configuration. Alternatively, you could load the configuration // from a .eslintrc file or just use the default config. const overrideConfig = { languageOptions: { ecmaVersion: 2018, sourceType: "commonjs", }, rules: { "no-console": "error", "no-unused-vars": "warn", }, }; const eslint = createESLintInstance(overrideConfig); const results = await lintAndFix(eslint, filePaths); return outputLintingResults(results); } // Export integration module.exports = { lintFiles }; ``` Here's the complete code example for `example-eslint-integration.js`: ```javascript const { ESLint } = require("eslint"); // Create an instance of ESLint with the configuration passed to the function function createESLintInstance(overrideConfig) { return new ESLint({ overrideConfigFile: true, overrideConfig, fix: true, }); } // Lint the specified files and return the results async function lintAndFix(eslint, filePaths) { const results = await eslint.lintFiles(filePaths); // Apply automatic fixes and output fixed code await ESLint.outputFixes(results); return results; } // Log results to console if there are any problems function outputLintingResults(results) { // Identify the number of problems found const problems = results.reduce( (acc, result) => acc + result.errorCount + result.warningCount, 0, ); if (problems > 0) { console.log("Linting errors found!"); console.log(results); } else { console.log("No linting errors found."); } return results; } // Put previous functions all together async function lintFiles(filePaths) { // The ESLint configuration. Alternatively, you could load the configuration // from an eslint.config.js file or just use the default config. const overrideConfig = { languageOptions: { ecmaVersion: 2018, sourceType: "commonjs", }, rules: { "no-console": "error", "no-unused-vars": "warn", }, }; const eslint = createESLintInstance(overrideConfig); const results = await lintAndFix(eslint, filePaths); return outputLintingResults(results); } // Export integration module.exports = { lintFiles }; ``` ## Conclusion In this tutorial, we have covered the essentials of using the `ESLint` class to lint files and retrieve results in your projects. This knowledge can be applied to create custom integrations, such as code editor plugins, to provide real-time feedback on code quality. ## View the Tutorial Code You can view the annotated source code for the tutorial [here](https://github.com/eslint/eslint/tree/main/docs/_examples/integration-tutorial-code). --- --- title: Node.js API Reference eleventyNavigation: key: node.js api parent: integrate eslint title: Node.js API Reference order: 2 --- While ESLint is designed to be run on the command line, it's possible to use ESLint programmatically through the Node.js API. The purpose of the Node.js API is to allow plugin and tool authors to use the ESLint functionality directly, without going through the command line interface. **Note:** Use undocumented parts of the API at your own risk. Only those parts that are specifically mentioned in this document are approved for use and will remain stable and reliable. Anything left undocumented is unstable and may change or be removed at any point. ## ESLint class The `ESLint` class is the primary class to use in Node.js applications. This class depends on the Node.js [`fs`](https://nodejs.org/api/fs.html) module and the file system, so you cannot use it in browsers. If you want to lint code on browsers, use the [`Linter`](#linter) class instead. Here's a simple example of using the `ESLint` class: ```js const { ESLint } = require("eslint"); (async function main() { // 1. Create an instance. const eslint = new ESLint(); // 2. Lint files. const results = await eslint.lintFiles(["lib/**/*.js"]); // 3. Format the results. const formatter = await eslint.loadFormatter("stylish"); const resultText = formatter.format(results); // 4. Output it. console.log(resultText); })().catch(error => { process.exitCode = 1; console.error(error); }); ``` Here's an example that autofixes lint problems: ```js const { ESLint } = require("eslint"); (async function main() { // 1. Create an instance with the `fix` option. const eslint = new ESLint({ fix: true }); // 2. Lint files. This doesn't modify target files. const results = await eslint.lintFiles(["lib/**/*.js"]); // 3. Modify the files with the fixed code. await ESLint.outputFixes(results); // 4. Format the results. const formatter = await eslint.loadFormatter("stylish"); const resultText = formatter.format(results); // 5. Output it. console.log(resultText); })().catch(error => { process.exitCode = 1; console.error(error); }); ``` And here is an example of using the `ESLint` class with [`lintText`](#-eslintlinttextcode-options) API: ```js const { ESLint } = require("eslint"); const testCode = ` const name = "eslint"; if(true) { console.log("constant condition warning") }; `; (async function main() { // 1. Create an instance const eslint = new ESLint({ overrideConfigFile: true, overrideConfig: { languageOptions: { ecmaVersion: 2018, sourceType: "commonjs", }, }, }); // 2. Lint text. const results = await eslint.lintText(testCode); // 3. Format the results. const formatter = await eslint.loadFormatter("stylish"); const resultText = formatter.format(results); // 4. Output it. console.log(resultText); })().catch(error => { process.exitCode = 1; console.error(error); }); ``` ### ◆ new ESLint(options) ```js const eslint = new ESLint(options); ``` Create a new `ESLint` instance. #### Parameters The `ESLint` constructor takes an `options` object. If you omit the `options` object then it uses default values for all options. The `options` object has the following properties. ##### File Enumeration - `options.cwd` (`string`)
Default is `process.cwd()`. The working directory. This must be an absolute path. - `options.errorOnUnmatchedPattern` (`boolean`)
Default is `true`. Unless set to `false`, the [`eslint.lintFiles()`][eslint-lintfiles] method will throw an error when no target files are found. - `options.globInputPaths` (`boolean`)
Default is `true`. If `false` is present, the [`eslint.lintFiles()`][eslint-lintfiles] method doesn't interpret glob patterns. - `options.ignore` (`boolean`)
Default is `true`. If `false` is present, the [`eslint.lintFiles()`][eslint-lintfiles] method doesn't respect `ignorePatterns` in your configuration. - `options.ignorePatterns` (`string[] | null`)
Default is `null`. Ignore file patterns to use in addition to config ignores. These patterns are relative to `cwd`. - `options.passOnNoPatterns` (`boolean`)
Default is `false`. When set to `true`, missing patterns cause the linting operation to short circuit and not report any failures. - `options.warnIgnored` (`boolean`)
Default is `true`. Show warnings when the file list includes ignored files. ##### Linting - `options.allowInlineConfig` (`boolean`)
Default is `true`. If `false` is present, ESLint suppresses directive comments in source code. If this option is `false`, it overrides the `noInlineConfig` setting in your configurations. - `options.baseConfig` (`Config | Config[] | null`)
Default is `null`. [Configuration object], extended by all configurations used with this instance. You can use this option to define the default settings that will be used if your configuration files don't configure it. - `options.overrideConfig` (`Config | Config[] | null`)
Default is `null`. [Configuration object], added after any existing configuration and therefore applies after what's contained in your configuration file (if used). - `options.overrideConfigFile` (`null | true | string`)
Default is `null`. By default, ESLint searches for a configuration file. When this option is set to `true`, ESLint does not search for a configuration file. When this option is set to a `string` value, ESLint does not search for a configuration file, and uses the provided value as the path to the configuration file. - `options.plugins` (`Record | null`)
Default is `null`. The plugin implementations that ESLint uses for the `plugins` setting of your configuration. This is a map-like object. Those keys are plugin IDs and each value is implementation. - `options.ruleFilter` (`({ruleId: string, severity: number}) => boolean`)
Default is `() => true`. A predicate function that filters rules to be run. This function is called with an object containing `ruleId` and `severity`, and returns `true` if the rule should be run. - `options.stats` (`boolean`)
Default is `false`. When set to `true`, additional statistics are added to the lint results (see [Stats type](../extend/stats#-stats-type)). ##### Autofix - `options.fix` (`boolean | (message: LintMessage) => boolean`)
Default is `false`. If `true` is present, the [`eslint.lintFiles()`][eslint-lintfiles] and [`eslint.lintText()`][eslint-linttext] methods work in autofix mode. If a predicate function is present, the methods pass each lint message to the function, then use only the lint messages for which the function returned `true`. - `options.fixTypes` (`("directive" | "problem" | "suggestion" | "layout")[] | null`)
Default is `null`. The types of the rules that the [`eslint.lintFiles()`][eslint-lintfiles] and [`eslint.lintText()`][eslint-linttext] methods use for autofix. ##### Cache-related - `options.cache` (`boolean`)
Default is `false`. If `true` is present, the [`eslint.lintFiles()`][eslint-lintfiles] method caches lint results and uses it if each target file is not changed. Please mind that ESLint doesn't clear the cache when you upgrade ESLint plugins. In that case, you have to remove the cache file manually. The [`eslint.lintText()`][eslint-linttext] method doesn't use caches even if you pass the `options.filePath` to the method. - `options.cacheLocation` (`string`)
Default is `.eslintcache`. The [`eslint.lintFiles()`][eslint-lintfiles] method writes caches into this file. - `options.cacheStrategy` (`string`)
Default is `"metadata"`. Strategy for the cache to use for detecting changed files. Can be either `"metadata"` or `"content"`. ##### Other Options - `options.concurrency` (`number | "auto" | "off"`)
Default is `"off"`. By default, ESLint lints all files in the calling thread. If this option specifies an integer, ESLint will use up to that number of worker threads to lint files concurrently. `"auto"` chooses a setting automatically. When this option is specified all other options must be [cloneable](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm). - `options.flags` (`string[]`)
Default is `[]`. The feature flags to enable for this instance. ### ◆ eslint.lintFiles(patterns) ```js const results = await eslint.lintFiles(patterns); ``` This method lints the files that match the glob patterns and then returns the results. #### Parameters - `patterns` (`string | string[]`)
The lint target files. This can contain any of file paths, directory paths, and glob patterns. #### Return Value - (`Promise`)
The promise that will be fulfilled with an array of [LintResult] objects. ### ◆ eslint.lintText(code, options) ```js const results = await eslint.lintText(code, options); ``` This method lints the given source code text and then returns the results. By default, this method uses the configuration that applies to files in the current working directory (the `cwd` constructor option). If you want to use a different configuration, pass `options.filePath`, and ESLint will load the same configuration that [`eslint.lintFiles()`][eslint-lintfiles] would use for a file at `options.filePath`. If the `options.filePath` value is configured to be ignored, this method returns an empty array. If the `options.warnIgnored` option is set along with the `options.filePath` option, this method returns a [LintResult] object. In that case, the result may contain a warning that indicates the file was ignored. #### Parameters The second parameter `options` is omittable. - `code` (`string`)
The source code text to check. - `options.filePath` (`string`)
Optional. The path to the file of the source code text. If omitted, the `result.filePath` becomes the string `""`. - `options.warnIgnored` (`boolean`)
Optional, defaults to `options.warnIgnored` passed to the constructor. If `true` is present and the `options.filePath` is a file ESLint should ignore, this method returns a lint result contains a warning message. #### Return Value - (`Promise`)
The promise that will be fulfilled with an array of [LintResult] objects. This is an array (despite there being only one lint result) in order to keep the interfaces between this and the [`eslint.lintFiles()`][eslint-lintfiles] method similar. ### ◆ eslint.getRulesMetaForResults(results) ```js const results = await eslint.lintFiles(patterns); const rulesMeta = eslint.getRulesMetaForResults(results); ``` This method returns an object containing meta information for each rule that triggered a lint error in the given `results`. #### Parameters - `results` (`LintResult[]`)
An array of [LintResult] objects returned from a call to `ESLint#lintFiles()` or `ESLint#lintText()`. #### Return Value - (`Object`)
An object whose property names are the rule IDs from the `results` and whose property values are the rule's meta information (if available). ### ◆ eslint.calculateConfigForFile(filePath) ```js const config = await eslint.calculateConfigForFile(filePath); ``` This method calculates the configuration for a given file, which can be useful for debugging purposes. #### Parameters - `filePath` (`string`)
The path to the file whose configuration you would like to calculate. Directory paths are forbidden because ESLint cannot handle the `overrides` setting. #### Return Value - (`Promise`)
The promise that will be fulfilled with a configuration object. ### ◆ eslint.findConfigFile(filePath) ```js const configFilePath = await eslint.findConfigFile(filePath); ``` This method finds the configuration file that this `ESLint` instance would use based on the options passed to the constructor. #### Parameters - `filePath` (`string`)
Optional. The path of a file for which to find the associated config file. If omitted, ESLint determines the config file based on the current working directory of this instance. #### Return Value - (`Promise`)
The promise that will be fulfilled with the absolute path to the config file being used, or `undefined` when no config file is used (for example, when `overrideConfigFile: true` is set). ### ◆ eslint.isPathIgnored(filePath) ```js const isPathIgnored = await eslint.isPathIgnored(filePath); ``` This method checks if a given file is ignored by your configuration. #### Parameters - `filePath` (`string`)
The path to the file you want to check. #### Return Value - (`Promise`)
The promise that will be fulfilled with whether the file is ignored or not. If the file is ignored, then it will return `true`. ### ◆ eslint.loadFormatter(nameOrPath) ```js const formatter = await eslint.loadFormatter(nameOrPath); ``` This method loads a formatter. Formatters convert lint results to a human- or machine-readable string. #### Parameters - `nameOrPath` (`string | undefined`)
The path to the file you want to check. The following values are allowed: - `undefined`. In this case, loads the `"stylish"` built-in formatter. - A name of [built-in formatters][builtin-formatters]. - A name of [third-party formatters][third-party-formatters]. For examples: - `"foo"` will load `eslint-formatter-foo`. - `"@foo"` will load `@foo/eslint-formatter`. - `"@foo/bar"` will load `@foo/eslint-formatter-bar`. - A path to the file that defines a formatter. The path must contain one or more path separators (`/`) in order to distinguish if it's a path or not. For example, start with `./`. #### Return Value - (`Promise`)
The promise that will be fulfilled with a [LoadedFormatter] object. ### ◆ eslint.hasFlag(flagName) This method is used to determine if a given feature flag is set, as in this example: ```js if (eslint.hasFlag("x_feature")) { // handle flag } ``` #### Parameters - `flagName` (`string`)
The flag to check. #### Return Value - (`boolean`)
True if the flag is enabled. ### ◆ ESLint.version ```js const version = ESLint.version; ``` The version string of ESLint. E.g. `"7.0.0"`. This is a static property. ### ◆ ESLint.defaultConfig ```js const defaultConfig = ESLint.defaultConfig; ``` The default configuration that ESLint uses internally. This is provided for tooling that wants to calculate configurations using the same defaults as ESLint. Keep in mind that the default configuration may change from version to version, so you shouldn't rely on any particular keys or values to be present. This is a static property. ### ◆ ESLint.fromOptionsModule(optionsURL) ```js const eslint = await ESLint.fromOptionsModule(optionsURL); ``` This method creates an instance of the `ESLint` class with options loaded from a module, for example: ```js // eslint-options.js import config from "./my-eslint-config.js"; export default { concurrency: "auto", overrideConfig: config, overrideConfigFile: true, stats: true, }; ``` ```js // main.js ... const optionsURL = new URL("./eslint-options.js", import.meta.url); const eslint = await ESLint.fromOptionsModule(optionsURL); ... ``` The `concurrency` option requires all other options to be cloneable so that they can be passed to worker threads, but this restriction does not apply when options are loaded from a module, because in that case worker threads are passed the module URL instead of the options object. This is a static method. #### Parameters - `optionsURL` (`URL`)
The URL of the options module. This can be any valid URL, like a file URL or a data URL. #### Return Value - (`Promise`)
A new instance of the `ESLint` class. ### ◆ ESLint.outputFixes(results) ```js await ESLint.outputFixes(results); ``` This method writes code modified by ESLint's autofix feature into its respective file. If any of the modified files don't exist, this method does nothing. This is a static method. #### Parameters - `results` (`LintResult[]`)
The [LintResult] objects to write. #### Return Value - (`Promise`)
The promise that will be fulfilled after all files are written. ### ◆ ESLint.getErrorResults(results) ```js const filteredResults = ESLint.getErrorResults(results); ``` This method copies the given results and removes warnings. The returned value contains only errors. This is a static method. #### Parameters - `results` (`LintResult[]`)
The [LintResult] objects to filter. #### Return Value - (`LintResult[]`)
The filtered [LintResult] objects. ### ◆ LintResult type The `LintResult` value is the information of the linting result of each file. The [`eslint.lintFiles()`][eslint-lintfiles] and [`eslint.lintText()`][eslint-linttext] methods return it. It has the following properties: - `filePath` (`string`)
The absolute path to the file of this result. This is the string `""` if the file path is unknown (when you didn't pass the `options.filePath` option to the [`eslint.lintText()`][eslint-linttext] method). - `messages` (`LintMessage[]`)
The array of [LintMessage] objects. - `suppressedMessages` (`SuppressedLintMessage[]`)
The array of [SuppressedLintMessage] objects. - `fixableErrorCount` (`number`)
The number of errors that can be fixed automatically by the `fix` constructor option. - `fixableWarningCount` (`number`)
The number of warnings that can be fixed automatically by the `fix` constructor option. - `errorCount` (`number`)
The number of errors. This includes fixable errors and fatal errors. - `fatalErrorCount` (`number`)
The number of fatal errors. - `warningCount` (`number`)
The number of warnings. This includes fixable warnings. - `output` (`string | undefined`)
The modified source code text. This property is undefined if any fixable messages didn't exist. - `source` (`string | undefined`)
The original source code text. This property is undefined if any messages didn't exist or the `output` property exists. - `stats` (`Stats | undefined`)
The [Stats](../extend/stats#-stats-type) object. This contains the lint performance statistics collected with the `stats` option. - `usedDeprecatedRules` (`{ ruleId: string; replacedBy: string[]; info: DeprecatedInfo | undefined }[]`)
The information about the deprecated rules that were used to check this file. The `info` property is set to `rule.meta.deprecated` if the rule uses the [new `deprecated` property](../extend/rule-deprecation). ### ◆ LintMessage type The `LintMessage` value is the information of each linting error. The `messages` property of the [LintResult] type contains it. It has the following properties: - `ruleId` (`string` | `null`)
The rule name that generates this lint message. If this message is generated by the ESLint core rather than rules, this is `null`. - `severity` (`1 | 2`)
The severity of this message. `1` means warning and `2` means error. - `fatal` (`boolean | undefined`)
`true` if this is a fatal error unrelated to a rule, like a parsing error. - `message` (`string`)
The error message. - `messageId` (`string | undefined`)
The message ID of the lint error. This property is undefined if the rule does not use message IDs. - `line` (`number | undefined`)
The 1-based line number of the begin point of this message. - `column` (`number | undefined`)
The 1-based column number of the begin point of this message. - `endLine` (`number | undefined`)
The 1-based line number of the end point of this message. This property is undefined if this message is not a range. - `endColumn` (`number | undefined`)
The 1-based column number of the end point of this message. This property is undefined if this message is not a range. - `fix` (`EditInfo | undefined`)
The [EditInfo] object of autofix. This property is undefined if this message is not fixable. - `suggestions` (`{ desc: string; fix: EditInfo; messageId?: string; data?: object }[] | undefined`)
The list of suggestions. Each suggestion is the pair of a description and an [EditInfo] object to fix code. API users such as editor integrations can choose one of them to fix the problem of this message. This property is undefined if this message doesn't have any suggestions. ### ◆ SuppressedLintMessage type The `SuppressedLintMessage` value is the information of each suppressed linting error. The `suppressedMessages` property of the [LintResult] type contains it. It has the following properties: - `ruleId` (`string` | `null`)
Same as `ruleId` in [LintMessage] type. - `severity` (`1 | 2`)
Same as `severity` in [LintMessage] type. - `fatal` (`boolean | undefined`)
Same as `fatal` in [LintMessage] type. - `message` (`string`)
Same as `message` in [LintMessage] type. - `messageId` (`string | undefined`)
Same as `messageId` in [LintMessage] type. - `line` (`number | undefined`)
Same as `line` in [LintMessage] type. - `column` (`number | undefined`)
Same as `column` in [LintMessage] type. - `endLine` (`number | undefined`)
Same as `endLine` in [LintMessage] type. - `endColumn` (`number | undefined`)
Same as `endColumn` in [LintMessage] type. - `fix` (`EditInfo | undefined`)
Same as `fix` in [LintMessage] type. - `suggestions` (`{ desc: string; fix: EditInfo; messageId?: string; data?: object }[] | undefined`)
Same as `suggestions` in [LintMessage] type. - `suppressions` (`{ kind: string; justification: string}[]`)
The list of suppressions. Each suppression is the pair of a kind and a justification. ### ◆ EditInfo type The `EditInfo` value is information to edit text. The `fix` and `suggestions` properties of [LintMessage] type contain it. It has following properties: - `range` (`[number, number]`)
The pair of 0-based indices in source code text to remove. - `text` (`string`)
The text to add. This edit information means replacing the range of the `range` property by the `text` property value. It's like `sourceCodeText.slice(0, edit.range[0]) + edit.text + sourceCodeText.slice(edit.range[1])`. Therefore, it's an add if the `range[0]` and `range[1]` property values are the same value, and it's removal if the `text` property value is empty string. ### ◆ LoadedFormatter type The `LoadedFormatter` value is the object to convert the [LintResult] objects to text. The [eslint.loadFormatter()][eslint-loadformatter] method returns it. It has the following method: - `format` (`(results: LintResult[], resultsMeta?: ResultsMeta) => string | Promise`)
The method to convert the [LintResult] objects to text. `resultsMeta` is an optional parameter that is primarily intended for use by the ESLint CLI and can contain `color` and `maxWarningsExceeded` properties that would be passed through the [`context`](../extend/custom-formatters#the-context-argument) object when this method calls the underlying formatter function. Note that ESLint automatically generates `cwd` and `rulesMeta` properties of the `context` object, so you typically don't need to pass in the second argument when calling this method. --- ## loadESLint() The `loadESLint()` function is used for integrations that wish to support different ESLint versions. This function returns the correct `ESLint` class implementation based on the arguments provided: ```js const { loadESLint } = require("eslint"); // loads the default ESLint that the CLI would use based on process.cwd() const DefaultESLint = await loadESLint(); // loads the flat config version specifically const FlatESLint = await loadESLint({ useFlatConfig: true }); // loads the legacy version specifically if possible, otherwise falls back to flat config version const LegacyESLint = await loadESLint({ useFlatConfig: false }); ``` You can then use the returned constructor to instantiate a new `ESLint` instance, like this: ```js // loads the default ESLint that the CLI would use based on process.cwd() const DefaultESLint = await loadESLint(); const eslint = new DefaultESLint(); ``` If you're ever unsure which config system the returned constructor uses, check the `configType` property, which is either `"flat"` or `"eslintrc"`: ```js // loads the default ESLint that the CLI would use based on process.cwd() const DefaultESLint = await loadESLint(); if (DefaultESLint.configType === "flat") { // do something specific to flat config } ``` **If you don't need to support both the old and new configuration systems, then it's recommended to just use the `ESLint` constructor directly.** ## SourceCode The `SourceCode` type represents the parsed source code that ESLint executes on. It's used internally in ESLint and is also available so that already-parsed code can be used. You can create a new instance of `SourceCode` by passing in the text string representing the code and an abstract syntax tree (AST) in [ESTree](https://github.com/estree/estree) format (including location information, range information, comments, and tokens): ```js const SourceCode = require("eslint").SourceCode; const code = new SourceCode("var foo = bar;", ast); ``` The `SourceCode` constructor throws an error if the AST is missing any of the required information. The `SourceCode` constructor strips Unicode BOM. Please note the AST also should be parsed from stripped text. ```js const SourceCode = require("eslint").SourceCode; const code = new SourceCode("\uFEFFvar foo = bar;", ast); assert(code.hasBOM === true); assert(code.text === "var foo = bar;"); ``` ### SourceCode#splitLines() This is a static function on `SourceCode` that is used to split the source code text into an array of lines. ```js const SourceCode = require("eslint").SourceCode; const code = "var a = 1;\nvar b = 2;"; // split code into an array const codeLines = SourceCode.splitLines(code); /* Value of codeLines will be [ "var a = 1;", "var b = 2;" ] */ ``` --- ## Linter The `Linter` object does the actual evaluation of the JavaScript code. It doesn't do any filesystem operations, it simply parses and reports on the code. In particular, the `Linter` object does not process configuration files. Unless you are working in the browser, you probably want to use the [ESLint class](#eslint-class) instead. The `Linter` is a constructor, and you can create a new instance by passing in the options you want to use. The available options are: - `cwd` - Path to a directory that should be considered as the current working directory. It is accessible to rules from `context.cwd` (see [The Context Object](../extend/custom-rules#the-context-object)). If `cwd` is `undefined`, it will be normalized to `process.cwd()` if the global `process` object is defined (for example, in the Node.js runtime) , or `undefined` otherwise. For example: ```js const Linter = require("eslint").Linter; const linter1 = new Linter({ cwd: "path/to/project" }); const linter2 = new Linter(); ``` In this example, rules run on `linter1` will get `path/to/project` from `context.cwd`. Those run on `linter2` will get `process.cwd()` if the global `process` object is defined or `undefined` otherwise (e.g. on the browser ). ### Linter#verify The most important method on `Linter` is `verify()`, which initiates linting of the given text. This method accepts three arguments: - `code` - the source code to lint (a string or instance of `SourceCode`). - `config` - a [Configuration object] or an array of configuration objects. - **Note**: If you want to lint text and have your configuration be read from the file system, use [`ESLint#lintFiles()`][eslint-lintfiles] or [`ESLint#lintText()`][eslint-linttext] instead. - `options` - (optional) Additional options for this run. - `filename` - (optional) the filename to associate with the source code. - `preprocess` - (optional) A function that [Processors in Plugins](../extend/plugins#processors-in-plugins) documentation describes as the `preprocess` method. - `postprocess` - (optional) A function that [Processors in Plugins](../extend/plugins#processors-in-plugins) documentation describes as the `postprocess` method. - `filterCodeBlock` - (optional) A function that decides which code blocks the linter should adopt. The function receives two arguments. The first argument is the virtual filename of a code block. The second argument is the text of the code block. If the function returned `true` then the linter adopts the code block. If the function was omitted, the linter adopts only `*.js` code blocks. If you provided a `filterCodeBlock` function, it overrides this default behavior, so the linter doesn't adopt `*.js` code blocks automatically. - `disableFixes` - (optional) when set to `true`, the linter doesn't make either the `fix` or `suggestions` property of the lint result. - `allowInlineConfig` - (optional) set to `false` to disable inline comments from changing ESLint rules. - `reportUnusedDisableDirectives` - (optional) when set to `true`, adds reported errors for unused `eslint-disable` and `eslint-enable` directives when no problems would be reported in the disabled area anyway. - `ruleFilter` - (optional) A function predicate that decides which rules should run. It receives an object containing `ruleId` and `severity`, and returns `true` if the rule should be run. If the third argument is a string, it is interpreted as the `filename`. You can call `verify()` like this: ```js const Linter = require("eslint").Linter; const linter = new Linter(); const messages = linter.verify( "var foo;", { rules: { semi: 2, }, }, { filename: "foo.js" }, ); // or using SourceCode const Linter = require("eslint").Linter, linter = new Linter(), SourceCode = require("eslint").SourceCode; const code = new SourceCode("var foo = bar;", ast); const messages = linter.verify( code, { rules: { semi: 2, }, }, { filename: "foo.js" }, ); ``` The `verify()` method returns an array of objects containing information about the linting warnings and errors. Here's an example: ```js [ { fatal: false, ruleId: "semi", severity: 2, line: 1, column: 23, message: "Expected a semicolon.", fix: { range: [1, 15], text: ";", }, }, ]; ``` The information available for each linting message is: - `column` - the column on which the error occurred. - `fatal` - usually omitted, but will be set to true if there's a parsing error (not related to a rule). - `line` - the line on which the error occurred. - `message` - the message that should be output. - `messageId` - the ID of the message used to generate the message (this property is omitted if the rule does not use message IDs). - `ruleId` - the ID of the rule that triggered the messages (or null if `fatal` is true). - `severity` - either 1 or 2, depending on your configuration. - `endColumn` - the end column of the range on which the error occurred (this property is omitted if it's not range). - `endLine` - the end line of the range on which the error occurred (this property is omitted if it's not range). - `fix` - an object describing the fix for the problem (this property is omitted if no fix is available). - `suggestions` - an array of objects describing possible lint fixes for editors to programmatically enable (see details in the [Working with Rules docs](../extend/custom-rules#providing-suggestions)). You can get the suppressed messages from the previous run by `getSuppressedMessages()` method. If there is not a previous run, `getSuppressedMessage()` will return an empty list. ```js const Linter = require("eslint").Linter; const linter = new Linter(); const messages = linter.verify( "var foo = bar; // eslint-disable-line -- Need to suppress", { rules: { semi: ["error", "never"], }, }, { filename: "foo.js" }, ); const suppressedMessages = linter.getSuppressedMessages(); console.log(suppressedMessages[0].suppressions); // [{ "kind": "directive", "justification": "Need to suppress" }] ``` You can also get an instance of the `SourceCode` object used inside of `linter` by using the `getSourceCode()` method: ```js const Linter = require("eslint").Linter; const linter = new Linter(); const messages = linter.verify( "var foo = bar;", { rules: { semi: 2, }, }, { filename: "foo.js" }, ); const code = linter.getSourceCode(); console.log(code.text); // "var foo = bar;" ``` In this way, you can retrieve the text and AST used for the last run of `linter.verify()`. ### Linter#verifyAndFix() This method is similar to verify except that it also runs autofixing logic, similar to the `--fix` flag on the command line. The result object will contain the autofixed code, along with any remaining linting messages for the code that were not autofixed. ```js const Linter = require("eslint").Linter; const linter = new Linter(); const messages = linter.verifyAndFix("var foo", { rules: { semi: 2, }, }); ``` Output object from this method: ```js { fixed: true, output: "var foo;", messages: [] } ``` The information available is: - `fixed` - True, if the code was fixed. - `output` - Fixed code text (might be the same as input if no fixes were applied). - `messages` - Collection of all messages for the given code (It has the same information as explained above under `verify` block). ### Linter#version/Linter.version Each instance of `Linter` has a `version` property containing the semantic version number of ESLint that the `Linter` instance is from. ```js const Linter = require("eslint").Linter; const linter = new Linter(); linter.version; // => '9.0.0' ``` There is also a `Linter.version` property that you can read without instantiating `Linter`: ```js const Linter = require("eslint").Linter; Linter.version; // => '9.0.0' ``` ### Linter#getTimes() This method is used to get the times spent on (parsing, fixing, linting) a file. See `times` property of the [Stats](../extend/stats#-stats-type) object. ### Linter#getFixPassCount() This method is used to get the number of autofix passes made. See `fixPasses` property of the [Stats](../extend/stats#-stats-type) object. ### Linter#hasFlag() This method is used to determine if a given feature flag is set, as in this example: ```js const Linter = require("eslint").Linter; const linter = new Linter({ flags: ["x_feature"] }); console.log(linter.hasFlag("x_feature")); // true ``` --- ## RuleTester `eslint.RuleTester` is a utility to write tests for ESLint rules. It is used internally for the bundled rules that come with ESLint, and it can also be used by plugins. Example usage: ```js "use strict"; const rule = require("../../../lib/rules/my-rule"), RuleTester = require("eslint").RuleTester; const ruleTester = new RuleTester(); ruleTester.run("my-rule", rule, { valid: [ { code: "var foo = true", options: [{ allowFoo: true }], }, ], invalid: [ { code: "var invalidVariable = true", errors: [{ message: "Unexpected invalid variable." }], }, { code: "var invalidVariable = true", errors: [{ message: /^Unexpected.+variable/ }], }, ], // optional assertionOptions: { requireMessage: true, requireLocation: false, }, }); ``` The `RuleTester` constructor accepts an optional object argument, which can be used to specify defaults for your test cases. For example, if all of your test cases use ES2015, you can set it as a default: ```js const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 2015 } }); ``` ::: tip If you don't specify any options to the `RuleTester` constructor, then it uses the ESLint defaults (`languageOptions: { ecmaVersion: "latest", sourceType: "module" }`). ::: ### RuleTester.setDefaultConfig(config) ```js const RuleTester = require("eslint").RuleTester; // Apply a default config for subsequently created RuleTester instances RuleTester.setDefaultConfig({ languageOptions: { ecmaVersion: 2022, sourceType: "module" }, }); const ruleTester = new RuleTester(); // picks up defaults above ``` Sets the default configuration used by `RuleTester` instances created after this call. This is a static method. #### Parameters - `config` (`Config`)
A [Configuration object] applied by default to all tests. It is applied before per-instance `RuleTester` constructor options and before per-test configuration. Throws a `TypeError` if `config` is not an object. ### RuleTester.getDefaultConfig() ```js const currentDefaultConfig = RuleTester.getDefaultConfig(); ``` Returns the current default configuration used by `RuleTester`. This is a static method. #### Return Value - (`Config`)
The current default configuration object. ### RuleTester.resetDefaultConfig() ```js RuleTester.resetDefaultConfig(); ``` Resets the default configuration back to ESLint's built-in defaults for subsequently created `RuleTester` instances. This is a static method. ### RuleTester#run() The `RuleTester#run()` method is used to run the tests. It should be passed the following arguments: - The name of the rule (string). - The rule object itself (see ["working with rules"](../extend/custom-rules)). - An object containing `valid` and `invalid` properties, each of which is an array containing test cases. - In this object, you can also pass the `assertionOptions` property to configure requirements for assertions of `invalid` test cases to enforce consistency. A test case is an object with the following properties: - `name` (string, optional): The name to use for the test case, to make it easier to find. - `code` (string, required): The source code that the rule should be run on. - `options` (array, optional): The options passed to the rule. The rule severity should not be included in this list. - `before` (function, optional): Function to execute before testing the case. - `after` (function, optional): Function to execute after testing the case regardless of its result. - `filename` (string, optional): The filename for the given case (useful for rules that make assertions about filenames). - `only` (boolean, optional): Run this case exclusively for debugging in supported test frameworks. In addition to the properties above, invalid test cases can also have the following properties: - `errors` (number or array, required): Asserts some properties of the errors that the rule is expected to produce when run on this code. If this is a number, asserts the number of errors produced. Otherwise, this should be a list of objects, each containing information about a single reported error. The following properties can be used for an error (all are optional unless otherwise noted): - `message` (string/regexp): The message for the error. Must provide this or `messageId`. - `messageId` (string): The ID for the error. Must provide this or `message`. See [testing errors with messageId](#testing-errors-with-messageid) for details. - `data` (object): Placeholder data which can be used in combination with `messageId`. - `line` (number): The 1-based line number of the reported location. - `column` (number): The 1-based column number of the reported location. - `endLine` (number): The 1-based line number of the end of the reported location. - `endColumn` (number): The 1-based column number of the end of the reported location. - `suggestions` (array): An array of objects with suggestion details to check. Required if the rule produces suggestions. See [Testing Suggestions](#testing-suggestions) for details. If a string is provided as an error instead of an object, the string is used to assert the `message` of the error. - `output` (string, required if the rule fixes code): Asserts the output that will be produced when using this rule for a single pass of autofixing (e.g. with the `--fix` command line flag). If this is `null` or omitted, asserts that none of the reported problems suggest autofixes. Any additional properties of a test case will be passed directly to the linter as config options. For example, a test case can have a `languageOptions` property to configure parser behavior: ```js { code: "let foo;", languageOptions: { ecmaVersion: 2015 } } ``` If a valid test case only uses the `code` property, it can optionally be provided as a string containing the code, rather than an object with a `code` key. You can optionally configure the following `assertionOptions` that apply to all error assertions in that call: - `requireMessage` (boolean/`"message"`/`"messageId"`, optional): - If `true`, each `errors` block must check the expected error messages, either via string/regexp values in the `errors` array, or via `message`/`messageId` in error objects. - If `"message"`, each `errors` block must check the expected error messages, either via string/regexp values in the `errors` array, or via `message` in error objects. - If `"messageId"`, each `errors` block must check the expected error messages via `messageId` in error objects. - `requireLocation` (boolean, optional): If `true`, each `errors` block must be an array of objects, and each object must contain location properties `line`, `column`, `endLine`, and `endColumn`. Properties `endLine` and `endColumn` may be omitted if the actual error does not contain them. ### Testing Errors with `messageId` If the rule under test uses `messageId`s, you can use `messageId` property in a test case to assert reported error's `messageId` instead of its `message`. ```js { code: "let foo;", errors: [{ messageId: "unexpected" }] } ``` For messages with placeholders, a test case can also use `data` property to additionally assert reported error's `message`. ```js { code: "let foo;", errors: [{ messageId: "unexpected", data: { name: "foo" } }] } ``` Please note that `data` in a test case does not assert `data` passed to `context.report`. Instead, it is used to form the expected message text which is then compared with the received `message`. ### Testing Fixes The result of applying fixes can be tested by using the `output` property of an invalid test case. The `output` property should be used only when you expect a fix to be applied to the specified `code`; you can safely omit `output` if no changes are expected to the code. Here's an example: ```js ruleTester.run("my-rule-for-no-foo", rule, { valid: [], invalid: [ { code: "var foo;", output: "var bar;", errors: [ { messageId: "shouldBeBar", line: 1, column: 5, }, ], }, ], }); ``` At the end of this invalid test case, `RuleTester` expects a fix to be applied that results in the code changing from `var foo;` to `var bar;`. If the output after applying the fix doesn't match, then the test fails. ::: important ESLint makes its best attempt at applying all fixes, but there is no guarantee that all fixes will be applied. As such, you should aim for testing each type of fix in a separate `RuleTester` test case rather than one test case to test multiple fixes. When there is a conflict between two fixes (because they apply to the same section of code) `RuleTester` applies only the first fix. ::: ### Testing Suggestions Suggestions can be tested by defining a `suggestions` key on an errors object. If this is a number, it asserts the number of suggestions provided for the error. Otherwise, this should be an array of objects, each containing information about a single provided suggestion. The following properties can be used: - `desc` (string): The suggestion `desc` value. Must provide this or `messageId`. - `messageId` (string): The suggestion `messageId` value for suggestions that use `messageId`s. Must provide this or `desc`. - `data` (object): Placeholder data which can be used in combination with `messageId`. - `output` (string, required): A code string representing the result of applying the suggestion fix to the input code. Example: ```js ruleTester.run("my-rule-for-no-foo", rule, { valid: [], invalid: [ { code: "var foo;", errors: [ { suggestions: [ { desc: "Rename identifier 'foo' to 'bar'", output: "var bar;", }, ], }, ], }, ], }); ``` `messageId` and `data` properties in suggestion test objects work the same way as in error test objects. See [testing errors with messageId](#testing-errors-with-messageid) for details. ```js ruleTester.run("my-rule-for-no-foo", rule, { valid: [], invalid: [ { code: "var foo;", errors: [ { suggestions: [ { messageId: "renameFoo", data: { newName: "bar" }, output: "var bar;", }, ], }, ], }, ], }); ``` ### Customizing RuleTester `RuleTester` depends on two functions to run tests: `describe` and `it`. These functions can come from various places: 1. If `RuleTester.describe` and `RuleTester.it` have been set to function values, `RuleTester` will use `RuleTester.describe` and `RuleTester.it` to run tests. You can use this to customize the behavior of `RuleTester` to match a test framework that you're using. If `RuleTester.itOnly` has been set to a function value, `RuleTester` will call `RuleTester.itOnly` instead of `RuleTester.it` to run cases with `only: true`. If `RuleTester.itOnly` is not set but `RuleTester.it` has an `only` function property, `RuleTester` will fall back to `RuleTester.it.only`. 2. Otherwise, if `describe` and `it` are present as globals, `RuleTester` will use `globalThis.describe` and `globalThis.it` to run tests and `globalThis.it.only` to run cases with `only: true`. This allows `RuleTester` to work when using frameworks like [Mocha](https://mochajs.org/) without any additional configuration. 3. Otherwise, `RuleTester#run` will simply execute all of the tests in sequence, and will throw an error if one of them fails. This means you can simply execute a test file that calls `RuleTester.run` using `Node.js`, without needing a testing framework. `RuleTester#run` calls the `describe` function with two arguments: a string describing the rule, and a callback function. The callback calls the `it` function with a string describing the test case, and a test function. The test function will return successfully if the test passes, and throw an error if the test fails. The signature for `only` is the same as `it`. `RuleTester` calls either `it` or `only` for every case even when some cases have `only: true`, and the test framework is responsible for implementing test case exclusivity. (Note that this is the standard behavior for test suites when using frameworks like [Mocha](https://mochajs.org/); this information is only relevant if you plan to customize `RuleTester.describe`, `RuleTester.it`, or `RuleTester.itOnly`.) Example of customizing `RuleTester`: ```js "use strict"; const RuleTester = require("eslint").RuleTester, test = require("my-test-runner"), myRule = require("../../../lib/rules/my-rule"); RuleTester.describe = function (text, method) { RuleTester.it.title = text; return method.call(this); }; RuleTester.it = function (text, method) { test(RuleTester.it.title + ": " + text, method); }; // then use RuleTester as documented const ruleTester = new RuleTester(); ruleTester.run("my-rule", myRule, { valid: [ // valid test cases ], invalid: [ // invalid test cases ], }); ``` [configuration object]: ../use/configure/ [builtin-formatters]: ../use/formatters/ [third-party-formatters]: https://www.npmjs.com/search?q=eslintformatter [eslint-lintfiles]: #-eslintlintfilespatterns [eslint-linttext]: #-eslintlinttextcode-options [eslint-loadformatter]: #-eslintloadformatternameorpath [lintresult]: #-lintresult-type [lintmessage]: #-lintmessage-type [suppressedlintmessage]: #-suppressedlintmessage-type [editinfo]: #-editinfo-type [loadedformatter]: #-loadedformatter-type --- --- title: alert --- The alert message comes in three different types: a warning, a tip, and an important note. ## Usage There is a shortcode for each type of alert. The shortcode expects you to provide the text and URL for the “Learn more” link. ```html { % warning "This rule has been removed in version x.xx", "/link/to/learn/more" % } { % tip "Kind reminder to do something maybe", "/link/to/learn/more" % } { % important "This rule has been deprecated in version x.xx", "/link/to/learn/more" % } ``` ## Examples {% warning "warning text", "/" %} {% tip "tip text", "/" %} {% important "text", "/" %} --- --- title: Buttons --- {% from 'components/button.macro.html' import button %} There are three types of buttons: primary, secondary, and "ghost". The button styles can be applied to buttons and/or links that look like buttons. To render the proper semantic element, provide the kind of behavior that is expected: `action` or `link` value. If the button performs an action, it is rendered as a `button`. If the button links somewhere, it renders as a ``. The button macro will default to `link`, which will render an <a> tag that looks like a button. If you provide `action` as a value for `behavior`, it indicates that it is a button _that performs an action_ and is therefore rendered as a `