diff --git a/docs/linting/Configurations.mdx b/docs/linting/Configurations.mdx index 3d56abb8af0..9048a1605a9 100644 --- a/docs/linting/Configurations.mdx +++ b/docs/linting/Configurations.mdx @@ -15,14 +15,14 @@ title: Configurations If your project does not enable [typed linting](./Typed_Linting.mdx), we suggest enabling the [`recommended`](#recommended) and [`stylistic`](#stylistic) configurations to start: -```json -{ - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "plugin:@typescript-eslint/stylistic" - ] -} +```js title=".eslintrc.js" +module.exports = { + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/stylistic', + ], +}; ``` > If a majority of developers working on your project are comfortable with TypeScript and typescript-eslint, consider replacing `recommended` with `strict`. @@ -31,14 +31,14 @@ If your project does not enable [typed linting](./Typed_Linting.mdx), we suggest If your project enables [typed linting](./Typed_Linting.mdx), we suggest enabling the [`recommended-type-checked`](#recommended-type-checked) and [`stylistic-type-checked`](#stylistic-type-checked) configurations to start: -```json -{ - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended-type-checked", - "plugin:@typescript-eslint/stylistic-type-checked" - ] -} +```js title=".eslintrc.js" +module.exports = { + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended-type-checked', + 'plugin:@typescript-eslint/stylistic-type-checked', + ], +}; ``` > If a majority of developers working on your project are comfortable with TypeScript and typescript-eslint, consider replacing `recommended-type-checked` with `strict-type-checked`. @@ -70,10 +70,10 @@ Recommended rules for code correctness that you can drop in without additional c These rules are those whose reports are almost always for a bad practice and/or likely bug. `recommended` also disables core ESLint rules known to conflict with typescript-eslint rules or cause issues in TypeScript codebases. -```json -{ - "extends": ["plugin:@typescript-eslint/recommended"] -} +```js title=".eslintrc.js" +module.exports = { + extends: ['plugin:@typescript-eslint/recommended'], +}; ``` See [`configs/recommended.ts`](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/configs/recommended.ts) for the exact contents of this config. @@ -83,10 +83,10 @@ See [`configs/recommended.ts`](https://github.com/typescript-eslint/typescript-e Contains all of `recommended` along with additional recommended rules that require type information. Rules newly added in this configuration are similarly useful to those in `recommended`. -```json -{ - "extends": ["plugin:@typescript-eslint/recommended-type-checked"] -} +```js title=".eslintrc.js" +module.exports = { + extends: ['plugin:@typescript-eslint/recommended-type-checked'], +}; ``` See [`configs/recommended-type-checked.ts`](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/configs/recommended-type-checked.ts) for the exact contents of this config. @@ -96,10 +96,10 @@ See [`configs/recommended-type-checked.ts`](https://github.com/typescript-eslint Contains all of `recommended`, as well as additional strict rules that can also catch bugs. Rules added in `strict` are more opinionated than recommended rules and might not apply to all projects. -```json -{ - "extends": ["plugin:@typescript-eslint/strict"] -} +```js title=".eslintrc.js" +module.exports = { + extends: ['plugin:@typescript-eslint/strict'], +}; ``` See [`configs/strict.ts`](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/configs/strict.ts) for the exact contents of this config. @@ -113,10 +113,10 @@ We recommend a TypeScript project extend from `plugin:@typescript-eslint/strict` Contains all of `recommended`, `recommended-type-checked`, and `strict`, along with additional strict rules that require type information. Rules newly added in this configuration are similarly useful (and opinionated) to those in `strict`. -```json -{ - "extends": ["plugin:@typescript-eslint/strict-type-checked"] -} +```js title=".eslintrc.js" +module.exports = { + extends: ['plugin:@typescript-eslint/strict-type-checked'], +}; ``` See [`configs/strict-type-checked.ts`](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/configs/strict-type-checked.ts) for the exact contents of this config. @@ -130,10 +130,10 @@ We recommend a TypeScript project extend from `plugin:@typescript-eslint/strict- Rules considered to be best practice for modern TypeScript codebases, but that do not impact program logic. These rules are generally opinionated about enforcing simpler code patterns. -```json -{ - "extends": ["plugin:@typescript-eslint/stylistic"] -} +```js title=".eslintrc.js" +module.exports = { + extends: ['plugin:@typescript-eslint/stylistic'], +}; ``` See [`configs/stylistic.ts`](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/configs/stylistic.ts) for the exact contents of this config. @@ -143,18 +143,17 @@ See [`configs/stylistic.ts`](https://github.com/typescript-eslint/typescript-esl Contains all of `stylistic`, along with additional stylistic rules that require type information. Rules newly added in this configuration are similarly opinionated to those in `stylistic`. -```json -{ - "extends": ["plugin:@typescript-eslint/stylistic-type-checked"] -} +```js title=".eslintrc.js" +module.exports = { + extends: ['plugin:@typescript-eslint/stylistic-type-checked'], +}; ``` See [`configs/stylistic-type-checked.ts`](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/configs/strict-type-checked.ts) for the exact contents of this config. ## Other Configurations -typescript-eslint includes a scattering of utility configurations used by the recommended configurations. -We don't recommend using these directly; instead, extend from an earlier recommended rule. +typescript-eslint includes a few utility configurations. ### `all` @@ -171,26 +170,51 @@ Many rules conflict with each other and/or are intended to be configured per-pro ### `base` A minimal ruleset that sets only the required parser and plugin options needed to run typescript-eslint. - - +We don't recommend using this directly; instead, extend from an earlier recommended rule. This config is automatically included if you use any of the recommended configurations. See [`configs/base.ts`](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/configs/base.ts) for the exact contents of this config. +### `disable-type-checked` + +A utility ruleset that will disable type-aware linting and all type-aware rules available in our project. +This config is useful if you'd like to have your base config concerned with type-aware linting, and then conditionally use [overrides](https://eslint.org/docs/latest/use/configure/configuration-files#configuration-based-on-glob-patterns) to disable type-aware linting on specific subsets of your codebase. + +See [`configs/disable-type-checked.ts`](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/configs/disable-type-checked.ts) for the exact contents of this config. + +:::info +If you use type-aware rules from other plugins, you will need to manually disable these rules or use a premade config they provide to disable them. +::: + +```js title=".eslintrc.js" +module.exports = { + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/eslint-recommended', + ], + overrides: [ + { + files: ['*.js'], + extends: ['plugin:@typescript-eslint/disable-type-checked'], + }, + ], +}; +``` + ### `eslint-recommended` This ruleset is meant to be used after extending `eslint:recommended`. It disables core ESLint rules that are already checked by the TypeScript compiler. Additionally, it enables rules that promote using the more modern constructs TypeScript allows for. -```jsonc -{ - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended" - ] -} +```js title=".eslintrc.js" +module.exports = { + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/eslint-recommended', + ], +}; ``` This config is automatically included if you use any of the recommended configurations. diff --git a/docs/linting/Typed_Linting.mdx b/docs/linting/Typed_Linting.mdx index cadeabcde3c..3be80a97aa8 100644 --- a/docs/linting/Typed_Linting.mdx +++ b/docs/linting/Typed_Linting.mdx @@ -55,7 +55,7 @@ module.exports = { }; ``` -See [the `@typescript-eslint/parser` docs for more details](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/README.md#parseroptionsproject). +See [the `@typescript-eslint/parser` docs for more details](../architecture/Parser.mdx#project). :::note If your project is a multi-package monorepo, see [our docs on configuring a monorepo](./typed-linting/Monorepos.mdx). @@ -63,6 +63,39 @@ If your project is a multi-package monorepo, see [our docs on configuring a mono ## FAQs +### How can I disable type-aware linting for a subset of files? + +You can combine ESLint's [overrides](https://eslint.org/docs/latest/use/configure/configuration-files#configuration-based-on-glob-patterns) config in conjunction with our [`disable-type-checked`](./Configurations.mdx#disable-type-checked) config to turn off type-aware linting on specific subsets of files. + +```js title=".eslintrc.js" +module.exports = { + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended-type-checked', + ], + plugins: ['@typescript-eslint'], + parser: '@typescript-eslint/parser', + parserOptions: { + project: true, + tsconfigRootDir: __dirname, + }, + root: true, + // Added lines start + overrides: [ + { + files: ['*.js'], + extends: ['plugin:@typescript-eslint/disable-type-checked'], + }, + ], + // Added lines end +}; +``` + +:::info +If you use type-aware rules from other plugins, you will need to manually disable these rules or use a premade config they provide to disable them. +::: + ### How is performance? Typed rules come with a catch. diff --git a/package.json b/package.json index f764612c05a..a031546d693 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "check-configs": "nx run-many --target=check-configs --parallel", "check-docs": "nx run-many --target=check-docs --parallel", "check-format": "prettier --list-different .", - "check-spelling": "cspell --config=.cspell.json \"**/*.{md,mdx,ts,mts,cts,js,cjs,mjs,tsx,jsx}\"", + "check-spelling": "cspell --config=.cspell.json \"**/*.{md,mdx,ts,mts,cts,js,cjs,mjs,tsx,jsx}\" --no-progress --show-context --show-suggestions", "clean": "lerna clean -y && nx run-many --target=clean", "format": "prettier --write .", "generate-contributors": "yarn tsx ./tools/generate-contributors.ts", diff --git a/packages/eslint-plugin/src/configs/disable-type-checked.ts b/packages/eslint-plugin/src/configs/disable-type-checked.ts new file mode 100644 index 00000000000..61f7e286b43 --- /dev/null +++ b/packages/eslint-plugin/src/configs/disable-type-checked.ts @@ -0,0 +1,53 @@ +// THIS CODE WAS AUTOMATICALLY GENERATED +// DO NOT EDIT THIS CODE BY HAND +// SEE https://typescript-eslint.io/linting/configs +// +// For developers working in the typescript-eslint monorepo: +// You can regenerate it using `yarn generate:configs` + +export = { + parserOptions: { project: null, program: null }, + rules: { + '@typescript-eslint/await-thenable': 'off', + '@typescript-eslint/consistent-type-exports': 'off', + '@typescript-eslint/dot-notation': 'off', + '@typescript-eslint/naming-convention': 'off', + '@typescript-eslint/no-base-to-string': 'off', + '@typescript-eslint/no-confusing-void-expression': 'off', + '@typescript-eslint/no-floating-promises': 'off', + '@typescript-eslint/no-for-in-array': 'off', + '@typescript-eslint/no-implied-eval': 'off', + '@typescript-eslint/no-meaningless-void-operator': 'off', + '@typescript-eslint/no-misused-promises': 'off', + '@typescript-eslint/no-redundant-type-constituents': 'off', + '@typescript-eslint/no-throw-literal': 'off', + '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'off', + '@typescript-eslint/no-unnecessary-condition': 'off', + '@typescript-eslint/no-unnecessary-qualifier': 'off', + '@typescript-eslint/no-unnecessary-type-arguments': 'off', + '@typescript-eslint/no-unnecessary-type-assertion': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/non-nullable-type-assertion-style': 'off', + '@typescript-eslint/prefer-includes': 'off', + '@typescript-eslint/prefer-nullish-coalescing': 'off', + '@typescript-eslint/prefer-readonly': 'off', + '@typescript-eslint/prefer-readonly-parameter-types': 'off', + '@typescript-eslint/prefer-reduce-type-parameter': 'off', + '@typescript-eslint/prefer-regexp-exec': 'off', + '@typescript-eslint/prefer-return-this-type': 'off', + '@typescript-eslint/prefer-string-starts-ends-with': 'off', + '@typescript-eslint/promise-function-async': 'off', + '@typescript-eslint/require-array-sort-compare': 'off', + '@typescript-eslint/require-await': 'off', + '@typescript-eslint/restrict-plus-operands': 'off', + '@typescript-eslint/restrict-template-expressions': 'off', + '@typescript-eslint/return-await': 'off', + '@typescript-eslint/strict-boolean-expressions': 'off', + '@typescript-eslint/switch-exhaustiveness-check': 'off', + '@typescript-eslint/unbound-method': 'off', + }, +}; diff --git a/packages/eslint-plugin/tools/generate-configs.ts b/packages/eslint-plugin/tools/generate-configs.ts index 1946bee239a..7e54b6d3286 100644 --- a/packages/eslint-plugin/tools/generate-configs.ts +++ b/packages/eslint-plugin/tools/generate-configs.ts @@ -40,9 +40,7 @@ async function main(): Promise { const prettierConfig = prettier.resolveConfig.sync(__dirname); interface LinterConfigRules { - [name: string]: - | TSESLint.Linter.RuleLevel - | TSESLint.Linter.RuleLevelAndOptions; + [name: string]: TSESLint.Linter.RuleLevel; } interface LinterConfig extends TSESLint.Linter.Config { @@ -79,9 +77,11 @@ async function main(): Promise { a[0].localeCompare(b[0]), ); - interface ruleFilter { + interface RuleFilter { deprecated?: 'exclude'; - typeChecked?: 'exclude'; + typeChecked?: 'exclude' | 'include-only'; + baseRuleForExtensionRule?: 'exclude'; + forcedRuleLevel?: TSESLint.Linter.RuleLevel; } /** @@ -90,7 +90,7 @@ async function main(): Promise { function reducer( config: LinterConfigRules, entry: [string, TSESLint.RuleModule], - settings: ruleFilter = {}, + settings: RuleFilter = {}, ): LinterConfigRules { const key = entry[0]; const value = entry[1]; @@ -107,9 +107,19 @@ async function main(): Promise { return config; } + if ( + settings.typeChecked === 'include-only' && + value.meta.docs?.requiresTypeChecking !== true + ) { + return config; + } + const ruleName = `${RULE_NAME_PREFIX}${key}`; - if (BASE_RULES_TO_BE_OVERRIDDEN.has(key)) { + if ( + settings.baseRuleForExtensionRule !== 'exclude' && + BASE_RULES_TO_BE_OVERRIDDEN.has(key) + ) { const baseRuleName = BASE_RULES_TO_BE_OVERRIDDEN.get(key)!; console.log( baseRuleName @@ -125,7 +135,7 @@ async function main(): Promise { '=', chalk.red('error'), ); - config[ruleName] = 'error'; + config[ruleName] = settings.forcedRuleLevel ?? 'error'; return config; } @@ -152,7 +162,7 @@ async function main(): Promise { interface ExtendedConfigSettings { extraExtends?: string[]; name: string; - filters?: ruleFilter; + filters?: RuleFilter; ruleEntries: RuleEntry[]; } @@ -250,6 +260,25 @@ async function main(): Promise { name: 'stylistic-type-checked', ruleEntries: filterRuleEntriesTo('stylistic'), }); + + writeConfig( + () => ({ + parserOptions: { + project: null, + program: null, + }, + rules: allRuleEntries.reduce( + (config, entry) => + reducer(config, entry, { + typeChecked: 'include-only', + baseRuleForExtensionRule: 'exclude', + forcedRuleLevel: 'off', + }), + {}, + ), + }), + 'disable-type-checked', + ); } main().catch(error => { diff --git a/packages/types/src/parser-options.ts b/packages/types/src/parser-options.ts index f77b601e0d0..e04fd6f2534 100644 --- a/packages/types/src/parser-options.ts +++ b/packages/types/src/parser-options.ts @@ -51,8 +51,8 @@ interface ParserOptions { extraFileExtensions?: string[]; filePath?: string; loc?: boolean; - program?: Program; - project?: string | string[] | true; + program?: Program | null; + project?: string | string[] | true | null; projectFolderIgnoreList?: (string | RegExp)[]; range?: boolean; sourceType?: SourceType; diff --git a/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts b/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts index d3b97d6102a..3e6f7a8ac4e 100644 --- a/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts +++ b/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts @@ -20,12 +20,16 @@ export function getProjectConfigFiles( ParseSettings, 'filePath' | 'tsconfigMatchCache' | 'tsconfigRootDir' >, - project: string | string[] | true | undefined, -): string[] | undefined { + project: string | string[] | true | undefined | null, +): string[] | null { if (project !== true) { - return project === undefined || Array.isArray(project) - ? project - : [project]; + if (project == null) { + return null; + } + if (Array.isArray(project)) { + return project; + } + return [project]; } log('Looking for tsconfig.json at or above file: %s', parseSettings.filePath); diff --git a/packages/typescript-estree/src/parseSettings/resolveProjectList.ts b/packages/typescript-estree/src/parseSettings/resolveProjectList.ts index 72e9539d2b9..34e1242c5e2 100644 --- a/packages/typescript-estree/src/parseSettings/resolveProjectList.ts +++ b/packages/typescript-estree/src/parseSettings/resolveProjectList.ts @@ -27,7 +27,7 @@ let RESOLUTION_CACHE: ExpiringCache | null = export function resolveProjectList( options: Readonly<{ cacheLifetime?: TSESTreeOptions['cacheLifetime']; - project: TSESTreeOptions['project']; + project: string[] | null; projectFolderIgnoreList: TSESTreeOptions['projectFolderIgnoreList']; singleRun: boolean; tsconfigRootDir: string; @@ -36,9 +36,7 @@ export function resolveProjectList( const sanitizedProjects: string[] = []; // Normalize and sanitize the project paths - if (typeof options.project === 'string') { - sanitizedProjects.push(options.project); - } else if (Array.isArray(options.project)) { + if (options.project != null) { for (const project of options.project) { if (typeof project === 'string') { sanitizedProjects.push(project); diff --git a/packages/typescript-estree/src/parser-options.ts b/packages/typescript-estree/src/parser-options.ts index e9dadb48f08..742864d4e47 100644 --- a/packages/typescript-estree/src/parser-options.ts +++ b/packages/typescript-estree/src/parser-options.ts @@ -124,7 +124,7 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { * or `true` to find the nearest tsconfig.json to the file. * If this is provided, type information will be returned. */ - project?: string | string[] | true; + project?: string | string[] | true | null; /** * If you provide a glob (or globs) to the project option, you can use this option to ignore certain folders from @@ -145,7 +145,7 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { * This overrides any program or programs that would have been computed from the `project` option. * All linted files must be part of the provided program(s). */ - programs?: ts.Program[]; + programs?: ts.Program[] | null; /** * @deprecated - this flag will be removed in the next major. diff --git a/packages/typescript-estree/tests/lib/getProjectConfigFiles.test.ts b/packages/typescript-estree/tests/lib/getProjectConfigFiles.test.ts index 7378508b001..6e049c37529 100644 --- a/packages/typescript-estree/tests/lib/getProjectConfigFiles.test.ts +++ b/packages/typescript-estree/tests/lib/getProjectConfigFiles.test.ts @@ -41,7 +41,7 @@ describe('getProjectConfigFiles', () => { const actual = getProjectConfigFiles(parseSettings, project); - expect(actual).toEqual(project); + expect(actual).toBeNull(); }); describe('when caching hits', () => {