Imported from eslint/eslint (
AGENTS.md). Install upstream withnpx skills add eslint/eslint. Copyright stays with the author.
AI Agent Instructions
This file provides guidance to AI agents when working with code in this repository.
AI Disclosure Requirement
ESLint's AI Usage Policy requires that AI-assisted contributions be disclosed. Whenever you (an AI) create content on GitHub for this project, you must include a disclosure, using the name of the model actually producing the content (for example, Claude Opus 5):
-
Issues — add a bold sentence to the top of body:
This issue was created with AI (Claude Opus 5).
-
Pull requests — add a bold sentence to the top of the description:
This pull request was created with AI (Claude Opus 5).
-
Comments, review comments, and review summaries — add a final paragraph in bold:
Created by AI (Claude Opus 5).
This applies to every AI-authored submission without exception, including follow-up comments on an existing thread. Note two related policy points: AI-generated PRs are only considered for issues labeled accepted, and maintainer feedback is expected to be answered by a human rather than fed back into an AI.
Commands
npm test # full suite: rule-file checks, mocha + coverage thresholds, fuzzer, license check
npm run test:cli tests/lib/rules/no-undef.js # run a single test file (alias for local mocha)
npm run lint # lint everything (JS, docs JS, docs Markdown, rule types, config files)
npm run lint:fix
npm run fmt # prettier --write . (prettier is the formatter; eslint does not handle style here)
npm run test:types # tsc against tests/lib/types
npm run test:browser # cypress against the webpack bundle
npm run test:performance
Useful details:
- Coverage gates are enforced in
npm test(99% statements/functions/lines, 98% branches). A change that lowers coverage below those thresholds fails the build even if all tests pass. - Mocha's default timeout is 10000ms; override with
ESLINT_MOCHA_TIMEOUT=20000 npm test. npm testruns mocha with--forbid-only, soonly: true/RuleTester.only(...)must be removed before pushing.- Task definitions live in
Makefile.js(shelljs-based), not a Makefile.npm run lint,npm test, etc. are thin wrappers aroundnode Makefile.js <target>. - The docs website is a separate workspace with its own scripts:
cd docs && npm startserves it locally. - A
lint-stagedpre-commit hook regenerates derived files. Editinglib/rules/*.jsregeneratespackages/js/src/configs/*.jsandlib/types/rules.d.ts; editingdocs/src/rules/*.mdregeneratesdocs/src/_data/further_reading_links.json. Don't hand-edit those generated files.
Architecture
Beyond lib/ (the source) and tests/ (which mirrors it), the top-level directories are bin/ (CLI entry point), conf/ (configuration data), docs/ (the documentation website), messages/ (verbose text for certain runtime errors), packages/ (separately published packages), templates/ (templates for generated files), and tools/ (build, release, and check scripts).
The layering is strict, and each layer is forbidden from doing what the layer below it does. Respect these boundaries — tests and reviews enforce them.
bin/eslint.js→lib/cli.js→lib/eslint/eslint.js→lib/linter/linter.js→lib/rules/*.jslib/cli.jsis the only place that reads argv, writes to the console, and sets exit codes. It may not callprocess.exit()directly.lib/eslint/(ESLintclass) owns all file system access: file/glob resolution, config loading, plugin and formatter loading. It must not print anything or use a formatter itself.lib/eslint/worker.jssupports multithreaded linting.lib/linter/(Linterclass) is pure and synchronous: no file I/O, no console, no Node-specific APIs, no async.verify()parses text, traverses the AST, and emits node-type events (plus:exitevents and code path analysis events fromlib/linter/code-path-analysis/) that rules subscribe to.lib/rules/rules are the most constrained layer: inspect the AST, report problems. Same prohibitions asLinter.lib/config/implements flat config:config-loader.jsfinds and loadseslint.config.js,flat-config-array.jsandflat-config-schema.jsnormalize and validate it,default-config.jssupplies base values.lib/languages/js/is the JavaScript language implementation, includingSourceCode. ESLint's language plugin abstraction means JS is one language among potential others, so language-specific logic belongs here rather than inLinter.lib/rule-tester/isRuleTester, a wrapper over Mocha-style globals used by essentially every rule test.lib/shared/is cross-cutting utilities (flags.jsfor feature flags,traverser.js, severity/naming/serialization helpers).lib/services/holds parser, processor, suppressions, and warning services used byESLint.packages/js(@eslint/js) publishes therecommendedandallconfigs, generated from rule metadata.packages/eslint-config-eslintis the config this repo lints itself with.
Rules
- Rule source:
lib/rules/<name>.js. Test:tests/lib/rules/<name>.js. Docs:docs/src/rules/<name>.md. All three are required, andnpm testfails if they aren't consistent. - New rules must be registered in
lib/rules/index.js, in the alphabetically-sortedLazyLoadingRuleMap. - Each rule exports
{ meta, create }.metacarriestype(problem|suggestion|layout),docs(description, recommended, url),schema(JSON Schema for the rule's options),fixable("code"or"whitespace") /hasSuggestions, andmessages. Report withmessageId, never a raw string. createreceives acontextobject and returns AST visitor methods; rules analyze the AST through the visitor pattern.- Define helper functions at module scope, not inside
create, so they aren't rebuilt per file. Factor common checks into helpers rather than recomputing them across visitors. - Fixable rules implement a fixer function that returns the corrections to apply.
- Shared AST helpers live in
lib/rules/utils/ast-utils.js. RuleTesteruses flat config (languageOptions.ecmaVersion, notparserOptions.ecmaVersion).
Rule documentation
Rule docs use frontmatter with title and rule_type, and generally contain a description of what the rule checks, a rule details section explaining when it reports, examples, a "When Not To Use It" section, and optionally version information and further resources.
Examples go in ::: incorrect / ::: correct containers, and each example includes its own /*eslint rule-name: "error"*/ comment. npm run lint:docs:rule-examples validates that these examples actually produce (or don't produce) the reported problems.
tools/internal-rules/ contains lint rules that check ESLint's own rule files (e.g. no-invalid-meta).
Testing
- Mocha with
const assert = require("chai").assert. Tests mirror the source tree undertests/. - Test files follow the same layout as source files:
@fileoverview/@authorheader, requirements, optional helpers, then the tests. Groupdescribeblocks by class and method, and set up mock contexts and configs before the assertions that use them. - Cover expected behavior, edge cases, error handling, and deprecated APIs kept for backward compatibility.
- Every new exported function and public class member needs tests, and every bug fix needs a test that fails without the fix.
- Never delete existing tests, even failing ones.
Conventions
- CommonJS (
"type": "commonjs"), Node^20.19.0 || ^22.13.0 || >=24. - Source files follow a fixed layout:
@fileoverview/@authorheader, requirements (imports), optional type definitions, optional helpers, then exports. Tools and scripts add a main section at the end. - Commits follow Conventional Commits without scopes:
fix:,feat:,fix!:,feat!:,docs:,chore:,build:,refactor:,test:,ci:,perf:. Summary ≤72 characters. Reference issues in the body withFixes #1234orRefs #1234. The PR title is checked in CI because it becomes the changelog entry.