diff --git a/packages/eslint-plugin/docs/rules/README.md b/packages/eslint-plugin/docs/rules/README.md index 71da9b1fd153..7b4d4dde58ed 100644 --- a/packages/eslint-plugin/docs/rules/README.md +++ b/packages/eslint-plugin/docs/rules/README.md @@ -6,18 +6,55 @@ pagination_prev: null slug: / --- -`@typescript-eslint/eslint-plugin` includes over 100 rules that detect best practice violations, bugs, and/or stylistic issues specifically for TypeScript code. -See [Configs](/linting/configs) for how to enable recommended rules using configs. +`@typescript-eslint/eslint-plugin` includes over 100 rules that detect best practice violations, bugs, and/or stylistic issues specifically for TypeScript code. All of our rules are listed below. -## Supported Rules +:::tip +Instead of enabling rules one by one, we recommend using one of [our pre-defined configs](/linting/configs) to enable a large set of recommended rules. +::: + +## Rules + +The rules are listed in alphabetical order. You can optionally filter them based on these categories: import RulesTable from "@site/src/components/RulesTable"; - + + +## Filtering + +### Config Group (⚙️) + +"Config Group" refers to the [pre-defined config](/linting/configs) that includes the rule. Extending from a configuration preset allow for enabling a large set of recommended rules all at once. + +### Metadata + +- `🔧 fixable` refers to whether the rule contains an [ESLint `--fix` auto-fixer](https://eslint.org/docs/latest/use/command-line-interface#--fix). +- `💡 has suggestions` refers to whether the rule contains an ESLint suggestion fixer. + - Sometimes, it is not safe to automatically fix the code with an auto-fixer. But in these cases, we often have a good guess of what the correct fix should be, and we can provide it as a suggestion to the developer. +- `💭 requires type information` refers to whether the rule requires [typed linting](/linting/typed-linting). +- `🧱 extension rule` means that the rule is an extension of an [core ESLint rule](https://eslint.org/docs/latest/rules) (see [Extension Rules](#extension-rules)). +- `📐 formatting rule` means that the rule has to do with formatting. + - We [strongly recommend against using ESLint for formatting](/linting/troubleshooting/formatting). + - Soon, formatting rules will be moved to the [ESLint stylistic plugin](https://eslint.style). +- `💀 deprecated rule` means that the rule should no longer be used and will be removed from the plugin in a future version. ## Extension Rules -In some cases, ESLint provides a rule itself, but it doesn't support TypeScript syntax; either it crashes, or it ignores the syntax, or it falsely reports against it. -In these cases, we create what we call an extension rule; a rule within our plugin that has the same functionality, but also supports TypeScript. +Some core ESLint rules do not support TypeScript syntax: either they crash, ignore the syntax, or falsely report against it. +In these cases, we create what we call an "extension rule": a rule within our plugin that has the same functionality, but also supports TypeScript. + +Extension rules generally completely replace the base rule from ESLint core. +If the base rule is enabled in a config you extend from, you'll need to disable the base rule: + +```js +module.exports = { + extends: ['eslint:recommended'], + rules: { + // Note: you must disable the base rule as it can report incorrect errors + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': 'error', + }, +}; +``` - +[Search for `🧱 extension rule`s](?=extension#rules) in this page to see all extension rules. diff --git a/packages/eslint-plugin/docs/rules/camelcase.md b/packages/eslint-plugin/docs/rules/camelcase.md index aae38de928bb..2a85ab90c5e4 100644 --- a/packages/eslint-plugin/docs/rules/camelcase.md +++ b/packages/eslint-plugin/docs/rules/camelcase.md @@ -5,6 +5,9 @@ This rule has been deprecated in favour of the [`naming-convention`](./naming-co ::: diff --git a/packages/eslint-plugin/docs/rules/no-duplicate-imports.md b/packages/eslint-plugin/docs/rules/no-duplicate-imports.md index 9bf40498092f..ae1d957d57c0 100644 --- a/packages/eslint-plugin/docs/rules/no-duplicate-imports.md +++ b/packages/eslint-plugin/docs/rules/no-duplicate-imports.md @@ -5,6 +5,9 @@ This rule has been deprecated in favour of the [`import/no-duplicates`](https:// ::: diff --git a/packages/eslint-plugin/tests/docs.test.ts b/packages/eslint-plugin/tests/docs.test.ts index a2bef8cac839..ce9f3115a02f 100644 --- a/packages/eslint-plugin/tests/docs.test.ts +++ b/packages/eslint-plugin/tests/docs.test.ts @@ -42,7 +42,8 @@ describe('Validating rule docs', () => { const ignoredFiles = new Set([ 'README.md', 'TEMPLATE.md', - // these rule docs were left behind on purpose for legacy reasons + // These rule docs were left behind on purpose for legacy reasons. See the + // comments in the files for more information. 'camelcase.md', 'no-duplicate-imports.md', ]); diff --git a/packages/eslint-plugin/tools/generate-breaking-changes.mts b/packages/eslint-plugin/tools/generate-breaking-changes.mts index d4ec9233e6c3..05f4d8d5485a 100644 --- a/packages/eslint-plugin/tools/generate-breaking-changes.mts +++ b/packages/eslint-plugin/tools/generate-breaking-changes.mts @@ -141,7 +141,7 @@ async function main(): Promise { recommended === 'recommended' ? '🟩' : '', recommended === 'strict' ? '🔵' : '', recommended === 'stylistic' ? '🔸' : '', - meta.type === 'layout' ? 'layout 💩' : '(todo)', + meta.type === 'layout' ? 'layout 📐' : '(todo)', ]; }), ]), diff --git a/packages/website/sidebars/sidebar.rules.js b/packages/website/sidebars/sidebar.rules.js index e698d7cda05c..4e6887df8cd9 100644 --- a/packages/website/sidebars/sidebar.rules.js +++ b/packages/website/sidebars/sidebar.rules.js @@ -1,6 +1,3 @@ -const globby = require('globby'); -const path = require('path'); - const plugin = require('@typescript-eslint/eslint-plugin'); const rules = Object.entries(plugin.rules).map(([name, rule]) => { @@ -10,43 +7,6 @@ const rules = Object.entries(plugin.rules).map(([name, rule]) => { }; }); -const deprecatedRules = new Set(rules.filter(rule => rule.meta.deprecated)); - -const formattingRules = new Set( - rules.filter(rule => !rule.meta.deprecated && rule.meta.type === 'layout'), -); - -const extensionRules = new Set( - rules.filter( - rule => rule.meta.docs?.extendsBaseRule && !formattingRules.has(rule), - ), -); - -const typescriptRules = rules.filter( - rule => - !rule.meta.deprecated && - !extensionRules.has(rule) && - !deprecatedRules.has(rule) && - !formattingRules.has(rule), -); - -const paths = globby - .sync('*.md', { - cwd: path.join(__dirname, '../../eslint-plugin/docs/rules'), - }) - .map(item => { - return { - name: path.basename(item, '.md'), - }; - }) - .filter(item => { - return ( - item.name !== 'README' && - item.name !== 'TEMPLATE' && - !rules.some(a => a.name === item.name) - ); - }); - function createCategory(label, rules, additionalItems = []) { return { items: [ @@ -68,17 +28,8 @@ module.exports = { someSidebar: [ 'README', { - ...createCategory('TypeScript Rules', Array.from(typescriptRules)), - collapsed: false, - }, - { - ...createCategory('Extension Rules', Array.from(extensionRules)), + ...createCategory('Rules', rules), collapsed: false, }, - createCategory('Formatting Rules', Array.from(formattingRules)), - createCategory('Deprecated Rules', [ - ...Array.from(deprecatedRules), - ...paths, - ]), ], }; diff --git a/packages/website/src/components/RulesTable/index.tsx b/packages/website/src/components/RulesTable/index.tsx index f1270d34b73c..b4c1823b8c32 100644 --- a/packages/website/src/components/RulesTable/index.tsx +++ b/packages/website/src/components/RulesTable/index.tsx @@ -9,6 +9,18 @@ import { type HistorySelector, useHistorySelector, } from '../../hooks/useHistorySelector'; +import { + CONFIG_EMOJI, + DEPRECATED_RULE_EMOJI, + EXTENSION_RULE_EMOJI, + FIXABLE_EMOJI, + FORMATTING_RULE_EMOJI, + RECOMMENDED_CONFIG_EMOJI, + STRICT_CONFIG_EMOJI, + STYLISTIC_CONFIG_EMOJI, + SUGGESTIONS_EMOJI, + TYPE_INFORMATION_EMOJI, +} from '../constants'; import styles from './styles.module.css'; function interpolateCode( @@ -29,8 +41,9 @@ function RuleRow({ if (!rule.docs?.url) { return null; } - const { fixable, hasSuggestions } = rule; - const { recommended, requiresTypeChecking } = rule.docs; + const { fixable, hasSuggestions, type, deprecated } = rule; + const { recommended, requiresTypeChecking, extendsBaseRule } = rule.docs; + const formatting = type === 'layout'; return ( @@ -44,14 +57,14 @@ function RuleRow({ {(() => { switch (recommended) { case 'recommended': - return '✅'; + return RECOMMENDED_CONFIG_EMOJI; case 'strict': - return '🔒'; + return STRICT_CONFIG_EMOJI; case 'stylistic': - return '🎨'; + return STYLISTIC_CONFIG_EMOJI; default: - // for some reason the current version of babel loader won't elide this correctly - // recommended satisfies undefined; + // for some reason the current version of babel loader won't elide + // this correctly recommended satisfies undefined; return ''; } })()} @@ -68,14 +81,33 @@ function RuleRow({ : undefined } > - {fixable ? '🔧\n' : '\n'} - {hasSuggestions ? '💡' : ''} + {fixable ? FIXABLE_EMOJI : ''} +
+ {hasSuggestions ? SUGGESTIONS_EMOJI : ''} - {requiresTypeChecking ? '💭' : ''} + {requiresTypeChecking ? TYPE_INFORMATION_EMOJI : ''} + + + {extendsBaseRule ? EXTENSION_RULE_EMOJI : ''} + + + {formatting ? FORMATTING_RULE_EMOJI : ''} + + + {deprecated ? DEPRECATED_RULE_EMOJI : ''} ); @@ -132,56 +164,52 @@ function match(mode: FilterMode, value: boolean): boolean | undefined { return undefined; } -export default function RulesTable({ - ruleset, -}: { - ruleset: 'extension-rules' | 'supported-rules'; -}): React.JSX.Element { - const [filters, changeFilter] = useRulesFilters(ruleset); +export default function RulesTable(): React.JSX.Element { + const [filters, changeFilter] = useRulesFilters(); const rules = useRulesMeta(); - const extensionRules = ruleset === 'extension-rules'; const relevantRules = useMemo( () => - rules - .filter(r => !!extensionRules === !!r.docs?.extendsBaseRule) - .filter(r => { - const opinions = [ - match(filters.recommended, r.docs?.recommended === 'recommended'), - match( - filters.strict, - r.docs?.recommended === 'recommended' || - r.docs?.recommended === 'strict', - ), - match(filters.stylistic, r.docs?.recommended === 'stylistic'), - match(filters.fixable, !!r.fixable), - match(filters.suggestions, !!r.hasSuggestions), - match(filters.typeInformation, !!r.docs?.requiresTypeChecking), - ].filter((o): o is boolean => o !== undefined); - return opinions.every(o => o); - }), - [rules, extensionRules, filters], + rules.filter(r => { + const opinions = [ + match(filters.recommended, r.docs?.recommended === 'recommended'), + match( + filters.strict, + r.docs?.recommended === 'recommended' || + r.docs?.recommended === 'strict', + ), + match(filters.stylistic, r.docs?.recommended === 'stylistic'), + match(filters.fixable, !!r.fixable), + match(filters.suggestions, !!r.hasSuggestions), + match(filters.typeInformation, !!r.docs?.requiresTypeChecking), + match(filters.extension, !!r.docs?.extendsBaseRule), + match(filters.formatting, r.type === 'layout'), + match(filters.deprecated, !!r.deprecated), + ].filter((o): o is boolean => o !== undefined); + return opinions.every(o => o); + }), + [rules, filters], ); return ( <>
- Config Group + Config Group ({CONFIG_EMOJI})
    changeFilter('recommended', newMode)} - label="✅ recommended" + label={`${RECOMMENDED_CONFIG_EMOJI} recommended`} /> changeFilter('strict', newMode)} - label="🔒 strict" + label={`${STRICT_CONFIG_EMOJI} strict`} /> changeFilter('stylistic', newMode)} - label="🎨 stylistic" + label={`${STYLISTIC_CONFIG_EMOJI} stylistic`} />
@@ -191,29 +219,75 @@ export default function RulesTable({ changeFilter('fixable', newMode)} - label="🔧 fixable" + label={`${FIXABLE_EMOJI} fixable`} /> changeFilter('suggestions', newMode)} - label="💡 has suggestions" + label={`${SUGGESTIONS_EMOJI} has suggestions`} /> changeFilter('typeInformation', newMode) } - label="💭 requires type information" + label={`${TYPE_INFORMATION_EMOJI} requires type information`} + /> + changeFilter('extension', newMode)} + label={`${EXTENSION_RULE_EMOJI} extension`} + /> + changeFilter('formatting', newMode)} + label={`${FORMATTING_RULE_EMOJI} formatting`} + /> + changeFilter('deprecated', newMode)} + label={`${DEPRECATED_RULE_EMOJI} deprecated`} /> +

+ (These categories are explained in{' '} + more detail below.) +

- - - + + + + + + @@ -232,7 +306,10 @@ type FilterCategory = | 'strict' | 'stylistic' | 'suggestions' - | 'typeInformation'; + | 'typeInformation' + | 'extension' + | 'formatting' + | 'deprecated'; type FiltersState = Record; const neutralFiltersState: FiltersState = { recommended: 'neutral', @@ -241,14 +318,21 @@ const neutralFiltersState: FiltersState = { fixable: 'neutral', suggestions: 'neutral', typeInformation: 'neutral', + extension: 'neutral', + formatting: 'neutral', + deprecated: 'neutral', }; const selectSearch: HistorySelector = history => history.location.search; const getServerSnapshot = (): string => ''; +/** + * @param paramsKey Optional. Whether to include rules that match the particular + * search filter. Defaults to an empty string, which matches all rules. + */ function useRulesFilters( - paramsKey: string, + paramsKey = '', ): [FiltersState, (category: FilterCategory, mode: FilterMode) => void] { const history = useHistory(); const search = useHistorySelector(selectSearch, getServerSnapshot); diff --git a/packages/website/src/components/constants.ts b/packages/website/src/components/constants.ts new file mode 100644 index 000000000000..ec55bfa14585 --- /dev/null +++ b/packages/website/src/components/constants.ts @@ -0,0 +1,32 @@ +export const CONFIG_EMOJI = '⚙️'; + +export const RECOMMENDED_CONFIG_EMOJI = '✅'; +export const STRICT_CONFIG_EMOJI = '🔒'; +export const STYLISTIC_CONFIG_EMOJI = '🎨'; + +export const FIXABLE_EMOJI = '🔧'; +export const SUGGESTIONS_EMOJI = '💡'; + +/** + * This must be kept in sync with the emoji in the + * "generate-breaking-changes.mts" script. + */ +export const TYPE_INFORMATION_EMOJI = '💭'; + +/** + * This must be kept in sync with the emoji in the + * "generate-breaking-changes.mts" script. + */ +export const EXTENSION_RULE_EMOJI = '🧱'; + +/** + * This must be kept in sync with the emoji in the + * "generate-breaking-changes.mts" script. + */ +export const FORMATTING_RULE_EMOJI = '📐'; + +/** + * This must be kept in sync with the emoji in the + * "generate-breaking-changes.mts" script. + */ +export const DEPRECATED_RULE_EMOJI = '💀'; diff --git a/packages/website/src/theme/MDXComponents/RuleAttributes.tsx b/packages/website/src/theme/MDXComponents/RuleAttributes.tsx index 9094d8a980fc..365dd15b1e8d 100644 --- a/packages/website/src/theme/MDXComponents/RuleAttributes.tsx +++ b/packages/website/src/theme/MDXComponents/RuleAttributes.tsx @@ -3,14 +3,21 @@ import type { RuleMetaDataDocs } from '@site/../utils/dist/ts-eslint/Rule'; import { useRulesMeta } from '@site/src/hooks/useRulesMeta'; import React from 'react'; +import { + FIXABLE_EMOJI, + RECOMMENDED_CONFIG_EMOJI, + STRICT_CONFIG_EMOJI, + STYLISTIC_CONFIG_EMOJI, + SUGGESTIONS_EMOJI, +} from '../../components/constants'; import type { FeatureProps } from './Feature'; import { Feature } from './Feature'; import styles from './RuleAttributes.module.css'; const recommendations = { - recommended: ['✅', 'recommended'], - strict: ['🔒', 'strict'], - stylistic: ['🎨', 'stylistic'], + recommended: [RECOMMENDED_CONFIG_EMOJI, 'recommended'], + strict: [STRICT_CONFIG_EMOJI, 'strict'], + stylistic: [STYLISTIC_CONFIG_EMOJI, 'stylistic'], }; const getRecommendation = (docs: RuleMetaDataDocs): string[] => { @@ -63,7 +70,7 @@ export function RuleAttributes({ name }: { name: string }): React.ReactNode { . ), - emoji: '🔧', + emoji: FIXABLE_EMOJI, }); } @@ -78,7 +85,7 @@ export function RuleAttributes({ name }: { name: string }): React.ReactNode { . ), - emoji: '💡', + emoji: SUGGESTIONS_EMOJI, }); }
RuleConfigFixerTyped +
+ {CONFIG_EMOJI} +
+
+
+ {FIXABLE_EMOJI} +
+
+
+ {TYPE_INFORMATION_EMOJI} +
+
+
+ {EXTENSION_RULE_EMOJI} +
+
+
+ {FORMATTING_RULE_EMOJI} +
+
+
+ {DEPRECATED_RULE_EMOJI} +
+