Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork2.8k
docs: add guide for building ESLint plugins#9684
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
e33d784b498a37bf9ce80946a2665f5f0a9d7b4d7eda7f4fc01e72ab659ad366e286d107036ff0dae980File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -10,11 +10,12 @@ You should be familiar with [ESLint's developer guide](https://eslint.org/docs/d | ||
| ::: | ||
| As long as you are using `@typescript-eslint/parser` as the `parser` in your ESLint configuration, custom ESLint rules generally work the same way for JavaScript and TypeScript code. | ||
| The mainfour changes to custom rules writing are: | ||
| - [Utils Package](#utils-package): we recommend using `@typescript-eslint/utils` to create custom rules | ||
| - [AST Extensions](#ast-extensions): targeting TypeScript-specific syntax in your rule selectors | ||
| - [Typed Rules](#typed-rules): using the TypeScript type checker to inform rule logic | ||
| - [Testing](#testing): using `@typescript-eslint/rule-tester`'s `RuleTester` instead of ESLint core's | ||
| ## Utils Package | ||
| @@ -305,7 +306,6 @@ This rule bans for-of looping over an enum by using the TypeScript type checker | ||
| ```ts | ||
| import { ESLintUtils } from '@typescript-eslint/utils'; | ||
MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. There's really no need to ask users to load in a full third-party-package for a single enum helper function. Especially not for a package outside of the org's ownership. | ||
| import * as ts from 'typescript'; | ||
| export const rule = createRule({ | ||
| @@ -316,10 +316,10 @@ export const rule = createRule({ | ||
| const services = ESLintUtils.getParserServices(context); | ||
| // 2. Find the TS type for the ES node | ||
| const type = services.getTypeAtLocation(node.right); | ||
| // 3. Check the TS type's backing symbol for being an enum | ||
| if (type.symbol.flags &ts.SymbolFlags.Enum) { | ||
| context.report({ | ||
| messageId: 'loopOverEnum', | ||
| node: node.right, | ||
| @@ -348,11 +348,15 @@ Rules can retrieve their full backing TypeScript type checker with `services.pro | ||
| This can be necessary for TypeScript APIs not wrapped by the parser services. | ||
| ::: | ||
| ### Conditional Type Information | ||
| We recommend _against_ changing rule logic based solely on whether `services.program` exists. | ||
| In our experience, users are generally surprised when rules behave differently with or without type information. | ||
| Additionally, if they misconfigure their ESLint config, they may not realize why the rule started behaving differently. | ||
| Consider either gating type checking behind an explicit option for the rule or creating two versions of the rule instead. | ||
| :::tip | ||
| Documentation generators such as [`eslint-doc-generator`](https://github.com/bmish/eslint-doc-generator) can automatically indicate in a rule's docs whether it needs type information. | ||
| ::: | ||
| ## Testing | ||
| @@ -364,15 +368,13 @@ Below is a quick-start guide. For more in-depth docs and examples [see the `@typ | ||
| ### Testing Untyped Rules | ||
| For rules that don't need type information,no constructor parameters are necessary: | ||
| ```ts | ||
| import { RuleTester } from '@typescript-eslint/rule-tester'; | ||
| import rule from './my-rule'; | ||
| const ruleTester = new RuleTester(); | ||
| ruleTester.run('my-rule', rule, { | ||
| valid: [ | ||
| @@ -387,17 +389,21 @@ ruleTester.run('my-rule', rule, { | ||
| ### Testing Typed Rules | ||
| For rules that do need type information, `parserOptions` must be passed in as well. | ||
| We recommend using `parserOptions.projectService` with options to allow a defaultproject for each test file. | ||
| ```ts | ||
| import { RuleTester } from '@typescript-eslint/rule-tester'; | ||
| import rule from './my-typed-rule'; | ||
| const ruleTester = new RuleTester({ | ||
| languageOptions: { | ||
| parserOptions: { | ||
| projectService: { | ||
| allowDefaultProjectForFiles: ['*.ts*'], | ||
| defaultProject: 'tsconfig.json', | ||
| }, | ||
| tsconfigRootDir: __dirname, | ||
| }, | ||
| }, | ||
| }); | ||
| @@ -411,12 +417,4 @@ ruleTester.run('my-typed-rule', rule, { | ||
| }); | ||
| ``` | ||
| See [_Rule Tester_ > _Type-Aware Testing_](../packages/Rule_Tester.mdx#type-aware-testing) for more details. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| --- | ||
| id: eslint-plugins | ||
| sidebar_label: Building ESLint Plugins | ||
| title: ESLint Plugins | ||
| --- | ||
| :::important | ||
| This page describes how to write your own custom ESLint plugins using typescript-eslint. | ||
| You should be familiar with [ESLint's plugins guide](https://eslint.org/docs/latest/extend/plugins) and [typescript-eslint Custom Rules](./Custom_Rules.mdx) before writing custom plugins. | ||
| ::: | ||
| Custom plugins that support TypeScript code and typed linting look very similar to any other ESLint plugin. | ||
| Follow the same general steps as [ESLint's plugins guide > _Creating a plugin_](https://eslint.org/docs/latest/extend/plugins#creating-a-plugin) to set up your plugin. | ||
| The required differences are noted on this page. | ||
| :::tip | ||
| See [**`eslint-plugin-example-typed-linting`**](https://github.com/typescript-eslint/examples/tree/main/packages/eslint-plugin-example-typed-linting) for an example plugin that supports typed linting. | ||
| ::: | ||
| ## Package Dependencies | ||
| Your plugin should have the following `package.json` entries. | ||
| For all `@typescript-eslint` and `typescript-eslint` packages, keep them at the same semver versions. | ||
| As an example, you might set each of them to `^8.1.2` or `^7.12.0 || ^8.0.0`. | ||
| ### `dependencies` | ||
| [`@typescript-eslint/utils`](../packages/Utils.mdx) is required for the [`RuleCreator` factory to create rules](#rulecreator-usage). | ||
| ### `devDependencies` | ||
| [`@typescript-eslint/rule-tester`](../packages/Rule_Tester.mdx) is strongly recommended to be able to [test rules with our `RuleTester`](./Custom_Rules.mdx). | ||
| ### `peerDependencies` | ||
| Include the following to enforce the version range allowed without making users' package managers install them: | ||
| - `@typescript-eslint/parser` and any other parsers users are expected to be using | ||
| - `eslint` | ||
| - `typescript` | ||
| Those are all packages consumers are expected to be using already. | ||
| ## `RuleCreator` Usage | ||
| We recommend including at least the following three properties in your plugin's [`RuleCreator` extra rule docs types](./Custom_Rules.mdx#extra-rule-docs-types): | ||
| - `description: string`: a succinct description of what the rule does | ||
| - `recommended?: boolean`: whether the rule exists in your plugin's shared _"`recommended`"_ config | ||
| - `requiresTypeChecking?: boolean`: whether the rule will use type information, for documentation generators such as [`eslint-doc-generator`](https://github.com/bmish/eslint-doc-generator) | ||
| For example, from [`eslint-plugin-example-typed-linting`'s `utils.ts`](https://github.com/typescript-eslint/examples/blob/main/packages/eslint-plugin-example-typed-linting/src/utils.ts): | ||
| ```ts | ||
| import { ESLintUtils } from '@typescript-eslint/utils'; | ||
| export interface ExamplePluginDocs { | ||
| description: string; | ||
| recommended?: boolean; | ||
| requiresTypeChecking?: boolean; | ||
| } | ||
| export const createRule = ESLintUtils.RuleCreator<ExamplePluginDocs>( | ||
| name => | ||
| `https://github.com/your/eslint-plugin-example/tree/main/docs/${name}.md`, | ||
| ); | ||
| ``` | ||
| ## Type Checking and Configs | ||
| Most ESLint plugins export a _"`recommended`"_ [ESLint shared config](https://eslint.org/docs/latest/extend/shareable-configs). | ||
| Many ESLint users assume enabling a plugin's `recommended` config is enough to enable all its relevant rules. | ||
| However, at the same time, not all users want to or are able to enabled typed linting. | ||
| If your plugin's rules heavily use type information, it might be difficult to enable those in a `recommended` config. | ||
| You have roughly two options: | ||
| - Have your plugin's `recommended` config require enabling type information | ||
| - Have a separate config with a name like `recommendedTypeChecked` | ||
| Either way, explicitly mention the strategy taken in your docs. | ||
| :::info | ||
| Per [_Custom Rules_ > _Conditional Type Information_](./Custom_Rules.mdx#conditional-type-information), we recommend not changing rule logic based on whether type information is available. | ||
| ::: | ||
| :::tip | ||
| See [**`eslint-plugin-example-typed-linting`**](https://github.com/typescript-eslint/examples/tree/main/packages/eslint-plugin-example-typed-linting) for an example plugin that supports typed linting. | ||
| ::: |