diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index ed5a4c968013..000000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,459 +0,0 @@ -// @ts-check -/** @type {import('./packages/utils/src/ts-eslint/Linter').Linter.Config} */ -module.exports = { - root: true, - plugins: [ - '@typescript-eslint', - '@typescript-eslint/internal', - 'deprecation', - 'eslint-comments', - 'eslint-plugin', - 'import', - 'jest', - 'jsdoc', - 'simple-import-sort', - 'unicorn', - ], - env: { - es2020: true, - node: true, - }, - extends: [ - 'eslint:recommended', - 'plugin:eslint-plugin/recommended', - 'plugin:jsdoc/recommended-typescript-error', - 'plugin:@typescript-eslint/strict-type-checked', - 'plugin:@typescript-eslint/stylistic-type-checked', - ], - parserOptions: { - allowAutomaticSingleRunInference: true, - cacheLifetime: { - // we pretty well never create/change tsconfig structure - so no need to ever evict the cache - // in the rare case that we do - just need to manually restart their IDE. - glob: 'Infinity', - }, - project: [ - './tsconfig.eslint.json', - './packages/*/tsconfig.json', - /** - * We are currently in the process of transitioning to nx's out of the box structure and - * so need to manually specify converted packages' tsconfig.build.json and tsconfig.spec.json - * files here for now in addition to the tsconfig.json glob pattern. - * - * TODO(#4665): Clean this up once all packages have been transitioned. - */ - './packages/scope-manager/tsconfig.build.json', - './packages/scope-manager/tsconfig.spec.json', - ], - tsconfigRootDir: __dirname, - }, - rules: { - // make sure we're not leveraging any deprecated APIs - 'deprecation/deprecation': 'error', - - // TODO(#7130): Investigate changing these in or removing these from presets - '@typescript-eslint/no-confusing-void-expression': 'off', - - // - // our plugin :D - // - - '@typescript-eslint/ban-ts-comment': [ - 'error', - { - 'ts-expect-error': 'allow-with-description', - 'ts-ignore': true, - 'ts-nocheck': true, - 'ts-check': false, - minimumDescriptionLength: 5, - }, - ], - '@typescript-eslint/consistent-type-imports': [ - 'error', - { prefer: 'type-imports', disallowTypeAnnotations: true }, - ], - '@typescript-eslint/explicit-function-return-type': [ - 'error', - { allowIIFEs: true }, - ], - '@typescript-eslint/no-explicit-any': 'error', - '@typescript-eslint/no-non-null-assertion': 'off', - 'no-constant-condition': 'off', - '@typescript-eslint/no-unnecessary-condition': [ - 'error', - { allowConstantLoopConditions: true }, - ], - '@typescript-eslint/no-var-requires': 'off', - '@typescript-eslint/prefer-literal-enum-member': [ - 'error', - { - allowBitwiseExpressions: true, - }, - ], - '@typescript-eslint/prefer-string-starts-ends-with': [ - 'error', - { - allowSingleElementEquality: 'always', - }, - ], - '@typescript-eslint/unbound-method': 'off', - '@typescript-eslint/restrict-template-expressions': [ - 'error', - { - allowNumber: true, - allowBoolean: true, - allowAny: true, - allowNullish: true, - allowRegExp: true, - }, - ], - '@typescript-eslint/no-unused-vars': [ - 'error', - { varsIgnorePattern: '^_', argsIgnorePattern: '^_' }, - ], - '@typescript-eslint/prefer-nullish-coalescing': [ - 'error', - { - ignoreConditionalTests: true, - ignorePrimitives: true, - }, - ], - - // - // Internal repo rules - // - - '@typescript-eslint/internal/no-poorly-typed-ts-props': 'error', - '@typescript-eslint/internal/no-typescript-default-import': 'error', - '@typescript-eslint/internal/prefer-ast-types-enum': 'error', - - // - // eslint-base - // - - curly: ['error', 'all'], - eqeqeq: [ - 'error', - 'always', - { - null: 'never', - }, - ], - 'logical-assignment-operators': 'error', - 'no-else-return': 'error', - 'no-mixed-operators': 'error', - 'no-console': 'error', - 'no-process-exit': 'error', - 'no-fallthrough': [ - 'error', - { commentPattern: '.*intentional fallthrough.*' }, - ], - 'one-var': ['error', 'never'], - - // - // eslint-plugin-eslint-comment - // - - // require a eslint-enable comment for every eslint-disable comment - 'eslint-comments/disable-enable-pair': [ - 'error', - { - allowWholeFile: true, - }, - ], - // disallow a eslint-enable comment for multiple eslint-disable comments - 'eslint-comments/no-aggregating-enable': 'error', - // disallow duplicate eslint-disable comments - 'eslint-comments/no-duplicate-disable': 'error', - // disallow eslint-disable comments without rule names - 'eslint-comments/no-unlimited-disable': 'error', - // disallow unused eslint-disable comments - 'eslint-comments/no-unused-disable': 'error', - // disallow unused eslint-enable comments - 'eslint-comments/no-unused-enable': 'error', - // disallow ESLint directive-comments - 'eslint-comments/no-use': [ - 'error', - { - allow: [ - 'eslint-disable', - 'eslint-disable-line', - 'eslint-disable-next-line', - 'eslint-enable', - 'global', - ], - }, - ], - - // - // eslint-plugin-import - // - - // disallow non-import statements appearing before import statements - 'import/first': 'error', - // Require a newline after the last import/require in a group - 'import/newline-after-import': 'error', - // Forbid import of modules using absolute paths - 'import/no-absolute-path': 'error', - // disallow AMD require/define - 'import/no-amd': 'error', - // forbid default exports - we want to standardize on named exports so that imported names are consistent - 'import/no-default-export': 'error', - // disallow imports from duplicate paths - 'import/no-duplicates': 'error', - // Forbid the use of extraneous packages - 'import/no-extraneous-dependencies': [ - 'error', - { - devDependencies: true, - peerDependencies: true, - optionalDependencies: false, - }, - ], - // Forbid mutable exports - 'import/no-mutable-exports': 'error', - // Prevent importing the default as if it were named - 'import/no-named-default': 'error', - // Prohibit named exports - 'import/no-named-export': 'off', // we want everything to be a named export - // Forbid a module from importing itself - 'import/no-self-import': 'error', - // Require modules with a single export to use a default export - 'import/prefer-default-export': 'off', // we want everything to be named - - // enforce a sort order across the codebase - 'simple-import-sort/imports': 'error', - - // - // eslint-plugin-jsdoc - // - - // We often use @remarks or other ad-hoc tag names - 'jsdoc/check-tag-names': 'off', - // https://github.com/gajus/eslint-plugin-jsdoc/issues/1169 - 'jsdoc/check-param-names': 'off', - // https://github.com/gajus/eslint-plugin-jsdoc/issues/1175 - 'jsdoc/require-jsdoc': 'off', - 'jsdoc/require-param': 'off', - 'jsdoc/require-returns': 'off', - 'jsdoc/require-yields': 'off', - 'jsdoc/tag-lines': 'off', - - // - // eslint-plugin-unicorn - // - - 'unicorn/no-typeof-undefined': 'error', - }, - overrides: [ - { - files: ['*.js'], - extends: ['plugin:@typescript-eslint/disable-type-checked'], - rules: { - // turn off other type-aware rules - 'deprecation/deprecation': 'off', - '@typescript-eslint/internal/no-poorly-typed-ts-props': 'off', - - // turn off rules that don't apply to JS code - '@typescript-eslint/explicit-function-return-type': 'off', - }, - }, - // all test files - { - files: [ - './packages/*/tests/**/*.spec.ts', - './packages/*/tests/**/*.test.ts', - './packages/*/tests/**/spec.ts', - './packages/*/tests/**/test.ts', - './packages/parser/tests/**/*.ts', - './packages/integration-tests/tools/integration-test-base.ts', - './packages/integration-tests/tools/pack-packages.ts', - ], - env: { - 'jest/globals': true, - }, - rules: { - '@typescript-eslint/no-empty-function': [ - 'error', - { allow: ['arrowFunctions'] }, - ], - '@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', - 'eslint-plugin/consistent-output': 'off', // Might eventually be removed from `eslint-plugin/recommended`: https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/issues/284 - 'jest/no-disabled-tests': 'error', - 'jest/no-focused-tests': 'error', - 'jest/no-alias-methods': 'error', - 'jest/no-identical-title': 'error', - 'jest/no-jasmine-globals': 'error', - 'jest/no-test-prefixes': 'error', - 'jest/no-done-callback': 'error', - 'jest/no-test-return-statement': 'error', - 'jest/prefer-to-be': 'error', - 'jest/prefer-to-contain': 'error', - 'jest/prefer-to-have-length': 'error', - 'jest/prefer-spy-on': 'error', - 'jest/valid-expect': 'error', - 'jest/no-deprecated-functions': 'error', - }, - }, - // test utility scripts and website js files - { - files: ['tests/**/*.js'], - rules: { - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/no-unsafe-call': 'off', - '@typescript-eslint/no-unsafe-member-access': 'off', - '@typescript-eslint/no-unsafe-return': 'off', - '@typescript-eslint/restrict-plus-operands': 'off', - }, - }, - // plugin source files - { - files: [ - './packages/eslint-plugin-internal/**/*.ts', - './packages/eslint-plugin/**/*.ts', - ], - rules: { - '@typescript-eslint/internal/no-typescript-estree-import': 'error', - }, - }, - // plugin rule source files - { - files: [ - './packages/eslint-plugin-internal/src/rules/**/*.ts', - './packages/eslint-plugin/src/configs/**/*.ts', - './packages/eslint-plugin/src/rules/**/*.ts', - ], - rules: { - 'eslint-plugin/require-meta-docs-description': [ - 'error', - { pattern: '^(Enforce|Require|Disallow) .+[^. ]$' }, - ], - - // specifically for rules - default exports makes the tooling easier - 'import/no-default-export': 'off', - - 'no-restricted-syntax': [ - 'error', - { - selector: - 'ExportDefaultDeclaration Property[key.name="create"] MemberExpression[object.name="context"][property.name="options"]', - message: - "Retrieve options from create's second parameter so that defaultOptions are applied.", - }, - ], - }, - }, - // plugin rule tests - { - files: [ - './packages/eslint-plugin-internal/tests/rules/**/*.test.ts', - './packages/eslint-plugin/tests/rules/**/*.test.ts', - './packages/eslint-plugin/tests/eslint-rules/**/*.test.ts', - ], - rules: { - '@typescript-eslint/internal/plugin-test-formatting': 'error', - }, - }, - // files which list all the things - { - files: ['./packages/eslint-plugin/src/rules/index.ts'], - rules: { - // enforce alphabetical ordering - 'sort-keys': 'error', - 'import/order': ['error', { alphabetize: { order: 'asc' } }], - }, - }, - // tools and tests - { - files: [ - '**/tools/**/*.*t*', - '**/tests/**/*.ts', - './packages/repo-tools/**/*.*t*', - './packages/integration-tests/**/*.*t*', - ], - rules: { - // allow console logs in tools and tests - 'no-console': 'off', - }, - }, - // generated files - { - files: [ - './packages/scope-manager/src/lib/*.ts', - './packages/eslint-plugin/src/configs/*.ts', - ], - rules: { - '@typescript-eslint/internal/no-poorly-typed-ts-props': 'off', - '@typescript-eslint/internal/no-typescript-default-import': 'off', - '@typescript-eslint/internal/prefer-ast-types-enum': 'off', - }, - }, - // ast spec specific standardization - { - files: ['./packages/ast-spec/src/**/*.ts'], - rules: { - // disallow ALL unused vars - '@typescript-eslint/no-unused-vars': 'error', - '@typescript-eslint/sort-type-constituents': 'error', - }, - }, - { - files: ['./packages/ast-spec/**/*.ts'], - rules: { - 'no-restricted-imports': [ - 'error', - { - name: '@typescript-eslint/typescript-estree', - message: - 'To prevent nx build errors, all `typescript-estree` imports should be done via `packages/ast-spec/tests/util/parsers/typescript-estree-import.ts`.', - }, - ], - }, - }, - { - files: ['rollup.config.ts'], - rules: { - 'import/no-default-export': 'off', - }, - }, - { - files: ['./packages/website/**/*.{ts,tsx,mts,cts,js,jsx}'], - extends: [ - 'plugin:jsx-a11y/recommended', - 'plugin:react/recommended', - 'plugin:react-hooks/recommended', - ], - plugins: ['jsx-a11y', 'react', 'react-hooks'], - rules: { - '@typescript-eslint/internal/prefer-ast-types-enum': 'off', - 'import/no-default-export': 'off', - 'react/jsx-no-target-blank': 'off', - 'react/no-unescaped-entities': 'off', - 'react-hooks/exhaustive-deps': 'warn', // TODO: enable it later - }, - settings: { - react: { - version: 'detect', - }, - }, - }, - { - files: ['./packages/website/src/**/*.{ts,tsx}'], - rules: { - 'import/no-default-export': 'off', - // allow console logs in the website to help with debugging things in production - 'no-console': 'off', - }, - }, - { - files: ['./packages/website-eslint/src/mock/**/*.js', '*.d.ts'], - rules: { - // mocks and declaration files have to mirror their original package - 'import/no-default-export': 'off', - }, - }, - ], -}; diff --git a/.github/replies.yml b/.github/replies.yml index 352c3fe57cf3..71728ef51148 100644 --- a/.github/replies.yml +++ b/.github/replies.yml @@ -20,6 +20,21 @@ replies: \ If you need it sooner, please try the `canary` tag on NPM. name: Fix Has Been Merged + - body: | + πŸ‘‹ thanks for sending this over @{{ author }}! + \ + > ```md\ + > - [] Steps in Contributing were taken\ + > ```\ + \ + We ask that people file issues for non-trivial changes like this one for a few reasons:\ + \ + * The templates ask reporters to provide info we'd need to triage requests, including a full reproduction - which is also useful if we later have to spelunk through the repository's history\ + * We want to raise visibility for the changes to let folks have a chance to discuss nuances before work is done\ + * Even if you're at the 'expert' level where specifically you know to check for duplicates and understand the codebase well enough to make these changes, most users aren't - so it sets a negative precedent\ + \ + Could you please [file an issue](https://github.com/typescript-eslint/typescript-eslint/issues/new/choose) explaining why you'd like this?\ + name: Needs Issue - body: | Thanks for the report @!\ This might be a valid issue, but we can't tell because you haven't filled in enough information.\ diff --git a/.github/workflows/a11y-alt-bot.yml b/.github/workflows/a11y-alt-bot.yml index 3d36d24e9598..2c4bf7d28a6f 100644 --- a/.github/workflows/a11y-alt-bot.yml +++ b/.github/workflows/a11y-alt-bot.yml @@ -23,4 +23,4 @@ jobs: runs-on: ubuntu-latest steps: - name: Get action 'github/accessibility-alt-text-bot' - uses: github/accessibility-alt-text-bot@v1.4.0 + uses: github/accessibility-alt-text-bot@v1.5.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index bcec49860f42..4bc21e319232 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,47 @@ +## 7.3.0 (2024-03-18) + + +### πŸš€ Features + +- **eslint-plugin:** [restrict-template-expressions] add `allowArray` option ([#8389](https://github.com/typescript-eslint/typescript-eslint/pull/8389)) +- **eslint-plugin:** add meta.docs.recommended setting for strict config options ([#8364](https://github.com/typescript-eslint/typescript-eslint/pull/8364)) +- **eslint-plugin:** add rule `use-unknown-in-catch-callback-variables` ([#8383](https://github.com/typescript-eslint/typescript-eslint/pull/8383)) +- **eslint-plugin:** [prefer-reduce-type-parameter] supports tuple, union, intersection ([#8642](https://github.com/typescript-eslint/typescript-eslint/pull/8642)) +- **eslint-plugin-internal:** add internal lint rule no-relative-paths-to-internal-packages ([#8596](https://github.com/typescript-eslint/typescript-eslint/pull/8596)) +- **typescript-estree:** disallow switch statements with multiple default cases ([#8411](https://github.com/typescript-eslint/typescript-eslint/pull/8411)) +- **utils:** add parser name to thrown parser error message ([#8484](https://github.com/typescript-eslint/typescript-eslint/pull/8484)) + +### 🩹 Fixes + +- correct `engines.node` constraints in `package.json` ([#8671](https://github.com/typescript-eslint/typescript-eslint/pull/8671)) +- **eslint-plugin:** [unbound-method] check method definition in object literal using longhand form ([#8637](https://github.com/typescript-eslint/typescript-eslint/pull/8637)) +- **eslint-plugin:** [consistent-type-imports] handle imports without specifiers ([#8308](https://github.com/typescript-eslint/typescript-eslint/pull/8308)) +- **eslint-plugin:** [no-redundant-type-constituents] incorrectly marks & string as redundant ([#8282](https://github.com/typescript-eslint/typescript-eslint/pull/8282)) +- **eslint-plugin:** [no-unnecessary-qualifier] handle merge namespace with enum ([#8591](https://github.com/typescript-eslint/typescript-eslint/pull/8591)) +- **eslint-plugin:** [no-unused-expressions] false negatives when using assertions ([#8668](https://github.com/typescript-eslint/typescript-eslint/pull/8668)) +- **eslint-plugin:** [ban-ts-comment] more accurate handling of multiline comments ([#8416](https://github.com/typescript-eslint/typescript-eslint/pull/8416)) +- **eslint-plugin:** [explicit-function-return-type, explicit-module-boundary-types] improved checking for allowHigherOrderFunctions option ([#8508](https://github.com/typescript-eslint/typescript-eslint/pull/8508)) +- **eslint-plugin:** [class-literal-property-style] ignore property assigned in constructor ([#8412](https://github.com/typescript-eslint/typescript-eslint/pull/8412)) +- **eslint-plugin:** [no-unnecessary-type-assertion] fix false negative for const variable declarations ([#8558](https://github.com/typescript-eslint/typescript-eslint/pull/8558)) +- **typescript-estree:** fix the issue of single run inferring in the pnpm repo ([#3811](https://github.com/typescript-eslint/typescript-eslint/pull/3811), [#8702](https://github.com/typescript-eslint/typescript-eslint/pull/8702)) + +### ❀️ Thank You + +- Abraham Guo +- Alexu +- Arka Pratim Chaudhuri @arka1002 +- auvred @auvred +- Derrick Isaacson +- fnx @DMartens +- Josh Goldberg ✨ +- Kirk Waiblinger @kirkwaiblinger +- Marta Cardoso @up201304504 +- MichaΓ«l De Boey +- Tristan Rasmussen +- YeonJuan @yeonjuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 7.2.0 (2024-03-11) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 2821e067af97..64431cfdeba1 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -90,6 +90,7 @@ Thanks goes to these wonderful people:
Pavel Birukov

Shahar "Dawn" Or

kirkwaiblinger
+ diff --git a/docs/contributing/Pull_Requests.mdx b/docs/contributing/Pull_Requests.mdx index 4a6194c64a03..0edca7e6988c 100644 --- a/docs/contributing/Pull_Requests.mdx +++ b/docs/contributing/Pull_Requests.mdx @@ -51,7 +51,7 @@ Within the body of your PR, make sure you reference the issue that you have work Must be one of the following: - +{/* Keep this synchronized with /.github/workflows/semantic-pr-titles.yml */} - `docs` - if you only change documentation, and not shipped code - `feat` - for any new functionality additions diff --git a/docs/maintenance/Pull_Requests.mdx b/docs/maintenance/Pull_Requests.mdx index 4ed8fa9c513f..c6901f451f72 100644 --- a/docs/maintenance/Pull_Requests.mdx +++ b/docs/maintenance/Pull_Requests.mdx @@ -10,7 +10,7 @@ Many users are new to open source and/or typed linting. It's imperative we give them a positive, uplifting experience. :::tip -If you're ever unsure on any part of PR reviews, don't hesitate to loop in a maintainer that has more context to help! +If you're ever unsure on any part of PR reviews, don't hesitate to loop in a maintainer who has more context to help! ::: ## Governance @@ -24,7 +24,7 @@ Per the scales from [Contribution Tiers > Pointing](./Contributor_Tiers.mdx#poin - "Unusual"-categorized: require two maintainer approvals. These include significant refactors with cross-project and public API ramifications. -## PR Flow +## PR Review Flow :::note We include a set of common responses to PRs in [`.github/replies.yml`](https://github.com/typescript-eslint/typescript-eslint/blob/main/.github/replies.yml), intended to be used with the [Refined Saved Replies](https://github.com/JoshuaKGoldberg/refined-saved-replies) extension. @@ -32,7 +32,97 @@ Don't treat these as exact responses you must use: they're just a starting copy+ Please do adopt your specific responses to your personal tone and to match the thread for non-straightforward PRs. ::: -TODO: This will be filled out... soon! +Open pull requests ideally switch between two states: + +- Ready for review: either newly created or after [review is re-requested](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/about-pull-request-reviews#re-requesting-a-review) +- Waiting for author activity: either by virtue of [being a draft](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests#draft-pull-requests) or having the [`awaiting response` label](https://github.com/typescript-eslint/typescript-eslint/pulls?q=is%3Apr+is%3Aopen+label%3A%22awaiting+response%22) + +Add the `awaiting response` label to a PR whenever you post a review. +It will be automatically removed if the author re-requests review. + +### Be Kind + +Above all else, be _encouraging_ and _friendly_ in tone. + +- Phrase feedback as opportunities for improvement rather than chastising. +- Call out any positive aspects you see - especially in larger pull requests. +- Use happy emojis frequently. +- If you have the energy, post a fun (but not inappropriate) GIF with your review. + - We often use the _GIFs for Github_ extension: available as [GIFs for GitHub (Chrome)](https://chrome.google.com/webstore/detail/gifs-for-github/dkgjnpbipbdaoaadbdhpiokaemhlphep) and [GIFs for GitHub (Firefox)](https://addons.mozilla.org/en-US/firefox/addon/gifs-for-github). + +Pull requests are the first time many community members meaningfully interact with us - or, oftentimes, any open source maintainers at all. +It's important that we provide a positive, uplifting experience. ❀️ + +### Reviewing a PR + +Before reviewing a pull request, try to look back over the backing issue to (re-)familiarize yourself with it. +You may find it useful to: + +- Attempt to simplify the provided reduction (["code golf"](https://en.wikipedia.org/wiki/Code_golf)) +- Look back through previous issues and PRs around the same area of code / lint rule + +If there's no backing issue: + +- If the PR is a straightforward docs or tooling fix that doesn't impact users, it's ok to review it as-is +- Otherwise, add the `please fill out the template` label and post a comment like the _Needs Issue_ template + +Upon completing your review, if the build is passing and you feel comfortable merging it in, go ahead and squash merge. +Otherwise, add the `1 approval` label. + +#### Common Things to Look For + +- Style: that can you read through the code easily, there are comments for necessary tricky areas, and not unnecessary comments otherwise. + - Try not to nitpick things that don't matter. + - If something is standard in the repo but not enforced, consider mentioning it as a non-blocking comment and filing an issue to enforce it. +- Thoroughness: does it handle relevant edge cases? Commonly: + - Generics and type parameters (see: `getConstrainedTypeAtLocation`). + - Parenthesis and whitespace (see: `getWrappingFixer`). + - Unions and intersections (see: `unionTypeParts` and `intersectionTypeParts`). +- Unit tests: + - All lines are covered per the Codecov / `yarn jest path/to/impacted/file --coverage` report. + - Both "positive" and "negative" ("valid" and "invalid") cases exist, if reasonably possible to test for. + - Fixes and suggestions, if present, don't remove `//` or `/*` comments + - `invalid` lint rule errors include line and column information + - Valid syntax edge cases don't cause the rule to crash, even if they cause a type error in TypeScript + +#### Individual Comments + +Post about one comment per area of praise note, suggested change, or non-actionable note. +It's fine to use multiple ancillary comments to indicate _"(also here)"_ notes, but don't overwhelm with requests. + +:::tip +If you're posting more than a couple types of comments, consider prefixing them with category indicators such as `[Docs]`, `[Praise]`, `[Testing]`, etc. +This helps avoid the review feeling like a huge slog of big requests. +::: + +Be clear in each of your comments what you're looking for or saying. +Err on the side of providing more information than you think is needed. + +Try to default to a questioning tone for points that aren't clear bugs. +Encourage authors to think on your suggestions: _"I think {xyz}, but am not sure - what do you think?"_. + +#### Preliminary or Repeat Reviews + +Sometimes you may want to post a preliminary review, and/or realize later on you missed something in an earlier review. +Both are reasonable and fine. + +For preliminary reviews, be clear with what scale you're reviewing at: _"Reviewing on architecture now, will look in more detail once it's settled"_. + +For repeat reviews, be clear when it's something you missed earlier and/or there's new information. +Don't apologize if the missed information was only made clear because of requested changes - this is part of the review process! + +### Other States + +#### External Blockers + +If the PR is blocked on something, such as an external API or discussion in an issue, add the appropriate `blocked by *` label. +Explain why in a comment that links to at least a tracking issue for the blocker. + +#### Out-of-Band Questions + +Authors sometimes separately ask questions as comments on PRs, sometimes including an `@` tag. +Put back the `awaiting response` label if you answer the questions. +Don't worry if you miss some of these questions; we prefer going through the formal re-requesting review to make sure the `awaiting response` label is removed as needed. ## Pruning Old PRs @@ -44,10 +134,12 @@ Our flow for PRs that have been waiting for >=1 month is: 3. Wait 2 weeks 4. If they still haven't responded, close the PR with a message like the _Pruning Stale PR_ template -## Suggestions +## Searching for PRs + +Assorted other queries for PRs include: -Consider adding fun to PR reviews with GIFs. -The benefits of using GIFs in PR reviews are that it adds a touch of humor and lightheartedness to the process, builds camaraderie and team spirit, and can help break the ice by making users feel welcomed. -Please remember to use appropriate and respectful GIFs and use them sparingly; GIFs should enhance the conversation, not detract from it. +- [All PRs you commented on](https://github.com/typescript-eslint/typescript-eslint/pulls?q=is%3Aopen+is%3Apr+commenter%3A%40me) +- [All non-draft, non-`awaiting response` PRs](https://github.com/typescript-eslint/typescript-eslint/pulls?q=is%3Aopen+is%3Apr+-label%3A%22awaiting+response%22+-is%3Adraft) +- [All non-draft, non-`awaiting response` PRs not blocked or already approved](https://github.com/typescript-eslint/typescript-eslint/pulls?q=is%3Aopen+is%3Apr+-label%3A%22awaiting+response%22+-is%3Adraft+-label%3A%22blocked+by+another+PR%22+-label%3A%22blocked+by+another+issue%22+-label%3A%22blocked+by+external+API%22+-review%3Aapproved+) -> We like to use the _GIFs for Github_ extension: available as [GIFs for GitHub (Chrome)](https://chrome.google.com/webstore/detail/gifs-for-github/dkgjnpbipbdaoaadbdhpiokaemhlphep) and [GIFs for GitHub (Firefox)](https://addons.mozilla.org/en-US/firefox/addon/gifs-for-github). ✨ +If you have time, consider occasionally looking through old PRs to make sure there aren't any questions left unanswered for weeks. diff --git a/docs/packages/Parser.mdx b/docs/packages/Parser.mdx index 8d39a8fb0bdd..7724454ddafd 100644 --- a/docs/packages/Parser.mdx +++ b/docs/packages/Parser.mdx @@ -90,7 +90,7 @@ More details can be found in the [TypeScript handbook's JSX docs](https://www.ty **NOTE:** this setting does not affect known file types (`.js`, `.mjs`, `.cjs`, `.jsx`, `.ts`, `.mts`, `.cts`, `.tsx`, `.json`) because the TypeScript compiler has its own internal handling for known file extensions. - +{/* https://github.com/microsoft/TypeScript/blob/d6e483b8dabd8fd37c00954c3f2184bb7f1eb90c/src/compiler/utilities.ts#L6281-L6285 */} The exact behavior is as follows: diff --git a/eslint.config.mjs b/eslint.config.mjs index 01f94ffd0ade..8783a4194f32 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -175,6 +175,8 @@ export default tseslint.config( // '@typescript-eslint/internal/no-poorly-typed-ts-props': 'error', + '@typescript-eslint/internal/no-relative-paths-to-internal-packages': + 'error', '@typescript-eslint/internal/no-typescript-default-import': 'error', '@typescript-eslint/internal/prefer-ast-types-enum': 'error', diff --git a/nx.json b/nx.json index f18f497922fe..81e4becfc178 100644 --- a/nx.json +++ b/nx.json @@ -3,10 +3,6 @@ "nxCloudAccessToken": "YjIzMmMxMWItMjhiMS00NWY2LTk1NWYtYWU3YWQ0YjE4YjBlfHJlYWQ=", "release": { "changelog": { - "git": { - "commit": true, - "tag": true - }, "workspaceChangelog": { "createRelease": "github", "renderer": "tools/release/changelog-renderer" @@ -16,10 +12,7 @@ } }, "version": { - "generatorOptions": { - "currentVersionResolver": "git-tag", - "specifierSource": "conventional-commits" - } + "conventionalCommits": true } }, "targetDefaults": { @@ -31,6 +24,7 @@ "test": { "inputs": [ "default", + "^production", "{workspaceRoot}/jest.config.js", "{workspaceRoot}/jest.config.base.js" ], @@ -40,6 +34,7 @@ "@nx/jest:jest": { "inputs": [ "default", + "^production", "{workspaceRoot}/jest.config.js", "{workspaceRoot}/jest.config.base.js" ], @@ -63,7 +58,6 @@ "{workspaceRoot}/eslint.config.js", "{workspaceRoot}/eslint.config.mjs", "{workspaceRoot}/yarn.lock", - "{workspaceRoot}/.eslintignore", { "dependentTasksOutputFiles": "**/*.js", "transitive": false @@ -86,6 +80,12 @@ "runtime": "yarn -v" } ], - "production": ["default", "!{projectRoot}/src/test-setup.[jt]s"] + "production": [ + "default", + "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", + "!{projectRoot}/tsconfig.spec.json", + "!{projectRoot}/jest.config.[jt]s", + "!{projectRoot}/src/test-setup.[jt]s" + ] } } diff --git a/package.json b/package.json index 487cdbbcdeff..9cc1bfbebf9b 100644 --- a/package.json +++ b/package.json @@ -55,16 +55,16 @@ "devDependencies": { "@actions/core": "^1.10.0", "@actions/github": "^6.0.0", - "@babel/code-frame": "^7.22.13", - "@babel/core": "^7.23.3", - "@babel/eslint-parser": "^7.23.3", - "@babel/parser": "^7.23.3", - "@babel/types": "^7.23.3", + "@babel/code-frame": "^7.23.5", + "@babel/core": "^7.24.0", + "@babel/eslint-parser": "^7.23.10", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "^8.56.0", - "@nx/eslint": "18.0.7", - "@nx/jest": "18.0.7", - "@nx/workspace": "18.0.7", + "@nx/eslint": "18.0.8", + "@nx/jest": "18.0.8", + "@nx/workspace": "18.0.8", "@swc/core": "^1.4.0", "@swc/jest": "^0.2.26", "@types/babel__code-frame": "^7.0.3", @@ -110,7 +110,7 @@ "markdownlint-cli": "^0.39.0", "ncp": "^2.0.0", "netlify": "^13.1.9", - "nx": "18.0.7", + "nx": "18.0.8", "prettier": "3.2.5", "pretty-format": "^29.6.2", "raw-loader": "^4.0.2", diff --git a/packages/ast-spec/CHANGELOG.md b/packages/ast-spec/CHANGELOG.md index 6a23570ab3ed..bed0d16706b5 100644 --- a/packages/ast-spec/CHANGELOG.md +++ b/packages/ast-spec/CHANGELOG.md @@ -1,3 +1,33 @@ +## 7.3.0 (2024-03-18) + + +### πŸš€ Features + +- **typescript-estree:** disallow switch statements with multiple default cases + + +### 🩹 Fixes + +- correct `engines.node` constraints in `package.json` + + +### ❀️ Thank You + +- Abraham Guo +- Alexu +- Arka Pratim Chaudhuri +- auvred +- Derrick Isaacson +- fnx +- Josh Goldberg ✨ +- Kirk Waiblinger +- Marta Cardoso +- MichaΓ«l De Boey +- Tristan Rasmussen +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 7.2.0 (2024-03-11) diff --git a/packages/ast-spec/package.json b/packages/ast-spec/package.json index 713244373f39..6dfa2dd64525 100644 --- a/packages/ast-spec/package.json +++ b/packages/ast-spec/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/ast-spec", - "version": "7.2.0", + "version": "7.3.0", "description": "Complete specification for the TypeScript-ESTree AST", "private": true, "keywords": [ @@ -9,7 +9,7 @@ "estree" ], "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "files": [ "dist", diff --git a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/type-import-type-with-type-parameters-in-type-reference/snapshots/3-Babel-AST.shot b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/type-import-type-with-type-parameters-in-type-reference/snapshots/3-Babel-AST.shot index 6f7e2955b89e..b4505bb0e2bb 100644 --- a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/type-import-type-with-type-parameters-in-type-reference/snapshots/3-Babel-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/type-import-type-with-type-parameters-in-type-reference/snapshots/3-Babel-AST.shot @@ -44,6 +44,7 @@ Program { end: { column: 20, line: 3 }, }, }, + options: null, qualifier: Identifier { type: "Identifier", name: "B", diff --git a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/type-import-type-with-type-parameters-in-type-reference/snapshots/5-AST-Alignment-AST.shot b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/type-import-type-with-type-parameters-in-type-reference/snapshots/5-AST-Alignment-AST.shot index 7a44f8974008..aa00744ad655 100644 --- a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/type-import-type-with-type-parameters-in-type-reference/snapshots/5-AST-Alignment-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/type-import-type-with-type-parameters-in-type-reference/snapshots/5-AST-Alignment-AST.shot @@ -47,24 +47,25 @@ exports[`AST Fixtures legacy-fixtures basics type-import-type-with-type-paramete - type: 'Literal', - raw: '\\'\\'', - value: '', -+ argument: Literal { -+ type: 'Literal', -+ raw: '\\'\\'', -+ value: '', - +- - range: [91, 93], - loc: { - start: { column: 18, line: 3 }, - end: { column: 20, line: 3 }, - }, - }, -- ++ argument: Literal { ++ type: 'Literal', ++ raw: '\\'\\'', ++ value: '', + range: [91, 93], loc: { start: { column: 18, line: 3 }, end: { column: 20, line: 3 }, }, }, ++ options: null, qualifier: Identifier { type: 'Identifier', - decorators: Array [], diff --git a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/type-import-type/snapshots/3-Babel-AST.shot b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/type-import-type/snapshots/3-Babel-AST.shot index 0fac83d7f207..237c1b3dcc9e 100644 --- a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/type-import-type/snapshots/3-Babel-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/type-import-type/snapshots/3-Babel-AST.shot @@ -31,6 +31,7 @@ Program { end: { column: 26, line: 3 }, }, }, + options: null, range: [89, 100], loc: { @@ -77,6 +78,7 @@ Program { end: { column: 19, line: 4 }, }, }, + options: null, qualifier: Identifier { type: "Identifier", name: "X", diff --git a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/type-import-type/snapshots/5-AST-Alignment-AST.shot b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/type-import-type/snapshots/5-AST-Alignment-AST.shot index 62d99f330fd3..c6c274e6ae8e 100644 --- a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/type-import-type/snapshots/5-AST-Alignment-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/type-import-type/snapshots/5-AST-Alignment-AST.shot @@ -33,18 +33,18 @@ exports[`AST Fixtures legacy-fixtures basics type-import-type AST Alignment - AS - type: 'Literal', - raw: '\\'A\\'', - value: 'A', -+ argument: Literal { -+ type: 'Literal', -+ raw: '\\'A\\'', -+ value: 'A', - +- - range: [96, 99], - loc: { - start: { column: 23, line: 3 }, - end: { column: 26, line: 3 }, - }, - }, -- ++ argument: Literal { ++ type: 'Literal', ++ raw: '\\'A\\'', ++ value: 'A', + range: [96, 99], loc: { start: { column: 23, line: 3 }, @@ -53,6 +53,7 @@ exports[`AST Fixtures legacy-fixtures basics type-import-type AST Alignment - AS }, - qualifier: null, - typeArguments: null, ++ options: null, range: [89, 100], loc: { @@ -115,6 +116,7 @@ exports[`AST Fixtures legacy-fixtures basics type-import-type AST Alignment - AS end: { column: 19, line: 4 }, }, }, ++ options: null, qualifier: Identifier { type: 'Identifier', - decorators: Array [], diff --git a/packages/ast-spec/src/statement/SwitchStatement/fixtures/_error_/multiple-default-cases/fixture.ts b/packages/ast-spec/src/statement/SwitchStatement/fixtures/_error_/multiple-default-cases/fixture.ts new file mode 100644 index 000000000000..698850476ff6 --- /dev/null +++ b/packages/ast-spec/src/statement/SwitchStatement/fixtures/_error_/multiple-default-cases/fixture.ts @@ -0,0 +1,3 @@ +switch (true) { + default: default: +} diff --git a/packages/ast-spec/src/statement/SwitchStatement/fixtures/_error_/multiple-default-cases/snapshots/1-TSESTree-Error.shot b/packages/ast-spec/src/statement/SwitchStatement/fixtures/_error_/multiple-default-cases/snapshots/1-TSESTree-Error.shot new file mode 100644 index 000000000000..ee04bea45a48 --- /dev/null +++ b/packages/ast-spec/src/statement/SwitchStatement/fixtures/_error_/multiple-default-cases/snapshots/1-TSESTree-Error.shot @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AST Fixtures statement SwitchStatement _error_ multiple-default-cases TSESTree - Error 1`] = ` +"TSError +> 1 | switch (true) { + | ^^^^^^^^^^^^^^^ +> 2 | default: default: + | ^^^^^^^^^^^^^^^^^^ +> 3 | } + | ^^ A 'default' clause cannot appear more than once in a 'switch' statement. + 4 |" +`; diff --git a/packages/ast-spec/src/statement/SwitchStatement/fixtures/_error_/multiple-default-cases/snapshots/2-Babel-Error.shot b/packages/ast-spec/src/statement/SwitchStatement/fixtures/_error_/multiple-default-cases/snapshots/2-Babel-Error.shot new file mode 100644 index 000000000000..36271efa74ad --- /dev/null +++ b/packages/ast-spec/src/statement/SwitchStatement/fixtures/_error_/multiple-default-cases/snapshots/2-Babel-Error.shot @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AST Fixtures statement SwitchStatement _error_ multiple-default-cases Babel - Error 1`] = `[SyntaxError: Multiple default clauses. (2:10)]`; diff --git a/packages/ast-spec/src/statement/SwitchStatement/fixtures/_error_/multiple-default-cases/snapshots/3-Alignment-Error.shot b/packages/ast-spec/src/statement/SwitchStatement/fixtures/_error_/multiple-default-cases/snapshots/3-Alignment-Error.shot new file mode 100644 index 000000000000..37a7c334d633 --- /dev/null +++ b/packages/ast-spec/src/statement/SwitchStatement/fixtures/_error_/multiple-default-cases/snapshots/3-Alignment-Error.shot @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AST Fixtures statement SwitchStatement _error_ multiple-default-cases Error Alignment 1`] = `"Both errored"`; diff --git a/packages/eslint-plugin-internal/CHANGELOG.md b/packages/eslint-plugin-internal/CHANGELOG.md index 54d9b42e7d4a..14e3c0d5922c 100644 --- a/packages/eslint-plugin-internal/CHANGELOG.md +++ b/packages/eslint-plugin-internal/CHANGELOG.md @@ -1,3 +1,28 @@ +## 7.3.0 (2024-03-18) + + +### πŸš€ Features + +- **eslint-plugin-internal:** add internal lint rule no-relative-paths-to-internal-packages + + +### ❀️ Thank You + +- Abraham Guo +- Alexu +- Arka Pratim Chaudhuri +- auvred +- Derrick Isaacson +- fnx +- Josh Goldberg ✨ +- Kirk Waiblinger +- Marta Cardoso +- MichaΓ«l De Boey +- Tristan Rasmussen +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 7.2.0 (2024-03-11) diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json index e9ca9dc41ff9..e644765974bd 100644 --- a/packages/eslint-plugin-internal/package.json +++ b/packages/eslint-plugin-internal/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin-internal", - "version": "7.2.0", + "version": "7.3.0", "private": true, "main": "dist/index.js", "types": "index.d.ts", @@ -15,10 +15,10 @@ }, "dependencies": { "@prettier/sync": "^0.5.0", - "@typescript-eslint/rule-tester": "7.2.0", - "@typescript-eslint/scope-manager": "7.2.0", - "@typescript-eslint/type-utils": "7.2.0", - "@typescript-eslint/utils": "7.2.0", + "@typescript-eslint/rule-tester": "7.3.0", + "@typescript-eslint/scope-manager": "7.3.0", + "@typescript-eslint/type-utils": "7.3.0", + "@typescript-eslint/utils": "7.3.0", "prettier": "^3.0.3" }, "devDependencies": { diff --git a/packages/eslint-plugin-internal/src/rules/index.ts b/packages/eslint-plugin-internal/src/rules/index.ts index c1143e7a6f8d..a99744c4b134 100644 --- a/packages/eslint-plugin-internal/src/rules/index.ts +++ b/packages/eslint-plugin-internal/src/rules/index.ts @@ -1,6 +1,7 @@ import type { Linter } from '@typescript-eslint/utils/ts-eslint'; import noPoorlyTypedTsProps from './no-poorly-typed-ts-props'; +import noRelativePathsToInternalPackages from './no-relative-paths-to-internal-packages'; import noTypescriptDefaultImport from './no-typescript-default-import'; import noTypescriptEstreeImport from './no-typescript-estree-import'; import pluginTestFormatting from './plugin-test-formatting'; @@ -8,6 +9,7 @@ import preferASTTypesEnum from './prefer-ast-types-enum'; export default { 'no-poorly-typed-ts-props': noPoorlyTypedTsProps, + 'no-relative-paths-to-internal-packages': noRelativePathsToInternalPackages, 'no-typescript-default-import': noTypescriptDefaultImport, 'no-typescript-estree-import': noTypescriptEstreeImport, 'plugin-test-formatting': pluginTestFormatting, diff --git a/packages/eslint-plugin-internal/src/rules/no-relative-paths-to-internal-packages.ts b/packages/eslint-plugin-internal/src/rules/no-relative-paths-to-internal-packages.ts new file mode 100644 index 000000000000..e27ed2125f8e --- /dev/null +++ b/packages/eslint-plugin-internal/src/rules/no-relative-paths-to-internal-packages.ts @@ -0,0 +1,76 @@ +import path from 'path'; + +import { createRule } from '../util'; + +export const REPO_ROOT = path.resolve(__dirname, '../../../..'); +export const PACKAGES_DIR = path.join(REPO_ROOT, 'packages'); + +export default createRule({ + name: __filename, + meta: { + type: 'problem', + docs: { + recommended: 'recommended', + description: 'Disallow relative paths to internal packages', + }, + messages: { + noRelativePathsToInternalPackages: + 'Use absolute paths instead of relative paths to import modules in other internal packages.', + }, + schema: [], + fixable: 'code', + }, + + defaultOptions: [], + + create(context) { + return { + ImportDeclaration(node): void { + const importSource = node.source; + if ( + importSource.value.startsWith('@typescript-eslint') || + !importSource.value.startsWith('.') + ) { + return; + } + + // The idea here is to check if the import source resolves to a different + // package than the package the file is currently in. This lets us not flag + // relative paths to modules inside a package called 'utils' but still flag + // if importing the '@typescript-eslint/utils' package with a relative path. + + const pathOfFileFromPackagesDir = path.relative( + PACKAGES_DIR, + context.physicalFilename, + ); + const packageOfFile = pathOfFileFromPackagesDir.split(path.sep)[0]; + const absolutePathOfImport = path.resolve( + path.dirname(context.physicalFilename), + importSource.value, + ); + const pathOfImportFromPackagesDir = path.relative( + PACKAGES_DIR, + absolutePathOfImport, + ); + const packageOfImport = pathOfImportFromPackagesDir.split(path.sep)[0]; + + if (packageOfImport !== packageOfFile) { + context.report({ + node: importSource, + messageId: 'noRelativePathsToInternalPackages', + fix: fixer => { + // Force the output path to be separated with '/' to get consistent + // results on windows. + const platformIndependentRelativePathOfImportFromPackagesDir = + pathOfImportFromPackagesDir.split(path.sep).join('/'); + return fixer.replaceText( + importSource, + `'@typescript-eslint/${platformIndependentRelativePathOfImportFromPackagesDir}'`, + ); + }, + }); + } + }, + }; + }, +}); diff --git a/packages/eslint-plugin-internal/tests/rules/no-relative-paths-to-internal-packages.test.ts b/packages/eslint-plugin-internal/tests/rules/no-relative-paths-to-internal-packages.test.ts new file mode 100644 index 000000000000..2e1cbf99e216 --- /dev/null +++ b/packages/eslint-plugin-internal/tests/rules/no-relative-paths-to-internal-packages.test.ts @@ -0,0 +1,113 @@ +import { RuleTester } from '@typescript-eslint/rule-tester'; +import path from 'path'; + +import rule, { + PACKAGES_DIR, +} from '../../src/rules/no-relative-paths-to-internal-packages'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('no-relative-paths-to-internal-packages', rule, { + valid: [ + "import { parse } from '@typescript-eslint/typescript-estree';", + "import { something } from 'not/a/relative/path';", + { + filename: path.resolve( + PACKAGES_DIR, + 'eslint-plugin/src/rules/my-awesome-rule.ts', + ), + code: "import { something } from './utils';", + }, + { + code: "import type { ValueOf } from './utils';", + filename: path.resolve( + PACKAGES_DIR, + 'ast-spec/src/expression/AssignmentExpression/spec.ts', + ), + }, + { + code: "import type { ValueOf } from '../../utils';", + filename: path.resolve( + PACKAGES_DIR, + 'ast-spec/src/expression/AssignmentExpression/spec.ts', + ), + }, + { + code: "import type { ValueOf } from '../../../utils';", + filename: path.resolve( + PACKAGES_DIR, + 'ast-spec/src/expression/AssignmentExpression/spec.ts', + ), + }, + ], + invalid: [ + { + code: "import { parse } from '../../../typescript-estree';", + filename: path.resolve( + PACKAGES_DIR, + 'eslint-plugin/src/rules/my-awesome-rule.ts', + ), + output: `import { parse } from '@typescript-eslint/typescript-estree';`, + errors: [ + { + messageId: 'noRelativePathsToInternalPackages', + line: 1, + }, + ], + }, + { + code: "import { parse } from '../../../typescript-estree/inner-module';", + filename: path.resolve( + PACKAGES_DIR, + 'eslint-plugin/src/rules/my-awesome-rule.ts', + ), + output: `import { parse } from '@typescript-eslint/typescript-estree/inner-module';`, + errors: [ + { + messageId: 'noRelativePathsToInternalPackages', + line: 1, + }, + ], + }, + { + code: "import type { ValueOf } from '../../../../utils';", + output: "import type { ValueOf } from '@typescript-eslint/utils';", + filename: path.resolve( + PACKAGES_DIR, + 'ast-spec/src/expression/AssignmentExpression/spec.ts', + ), + errors: [ + { + messageId: 'noRelativePathsToInternalPackages', + line: 1, + }, + ], + }, + { + code: ` +import type { + MemberExpressionComputedName, + MemberExpressionNonComputedName, +} from '../../../types/src/generated/ast-spec'; + `, + output: ` +import type { + MemberExpressionComputedName, + MemberExpressionNonComputedName, +} from '@typescript-eslint/types/src/generated/ast-spec'; + `, + filename: path.resolve( + PACKAGES_DIR, + 'eslint-plugin/src/rules/prefer-find.ts', + ), + errors: [ + { + messageId: 'noRelativePathsToInternalPackages', + line: 5, + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 5cede611e35c..8aaf51923203 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,3 +1,57 @@ +## 7.3.0 (2024-03-18) + + +### πŸš€ Features + +- **eslint-plugin:** [restrict-template-expressions] add `allowArray` option + +- **eslint-plugin:** add meta.docs.recommended setting for strict config options + +- **eslint-plugin:** add rule `use-unknown-in-catch-callback-variables` + +- **eslint-plugin:** [prefer-reduce-type-parameter] supports tuple, union, intersection + + +### 🩹 Fixes + +- correct `engines.node` constraints in `package.json` + +- **eslint-plugin:** [unbound-method] check method definition in object literal using longhand form + +- **eslint-plugin:** [consistent-type-imports] handle imports without specifiers + +- **eslint-plugin:** [no-redundant-type-constituents] incorrectly marks & string as redundant + +- **eslint-plugin:** [no-unnecessary-qualifier] handle merge namespace with enum + +- **eslint-plugin:** [no-unused-expressions] false negatives when using assertions + +- **eslint-plugin:** [ban-ts-comment] more accurate handling of multiline comments + +- **eslint-plugin:** [explicit-function-return-type, explicit-module-boundary-types] improved checking for allowHigherOrderFunctions option + +- **eslint-plugin:** [class-literal-property-style] ignore property assigned in constructor + +- **eslint-plugin:** [no-unnecessary-type-assertion] fix false negative for const variable declarations + + +### ❀️ Thank You + +- Abraham Guo +- Alexu +- Arka Pratim Chaudhuri +- auvred +- Derrick Isaacson +- fnx +- Josh Goldberg ✨ +- Kirk Waiblinger +- Marta Cardoso +- MichaΓ«l De Boey +- Tristan Rasmussen +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 7.2.0 (2024-03-11) diff --git a/packages/eslint-plugin/docs/rules/TEMPLATE.md b/packages/eslint-plugin/docs/rules/TEMPLATE.md index 3fb311ad2a16..49947c330085 100644 --- a/packages/eslint-plugin/docs/rules/TEMPLATE.md +++ b/packages/eslint-plugin/docs/rules/TEMPLATE.md @@ -2,6 +2,9 @@ description: '' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/RULE_NAME_REPLACEME** for documentation. @@ -10,20 +13,23 @@ description: '' To fill out: tell us more about this rule. - - -### ❌ Incorrect + + ```ts // To fill out: incorrect code ``` -### βœ… Correct + + ```ts // To fill out: correct code ``` + + + ## When Not To Use It To fill out: why wouldn't you want to use this rule? diff --git a/packages/eslint-plugin/docs/rules/adjacent-overload-signatures.md b/packages/eslint-plugin/docs/rules/adjacent-overload-signatures.mdx similarity index 91% rename from packages/eslint-plugin/docs/rules/adjacent-overload-signatures.md rename to packages/eslint-plugin/docs/rules/adjacent-overload-signatures.mdx index 50b96b83639c..60f62b2f8a11 100644 --- a/packages/eslint-plugin/docs/rules/adjacent-overload-signatures.md +++ b/packages/eslint-plugin/docs/rules/adjacent-overload-signatures.mdx @@ -2,6 +2,9 @@ description: 'Require that function overload signatures be consecutive.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/adjacent-overload-signatures** for documentation. @@ -12,9 +15,8 @@ If Signatures placed elsewhere in the type are easier to be missed by future dev ## Examples - - -### ❌ Incorrect + + ```ts declare namespace Foo { @@ -51,7 +53,8 @@ export function bar(): void; export function foo(sn: string | number): void; ``` -### βœ… Correct + + ```ts declare namespace Foo { @@ -88,6 +91,9 @@ export function foo(n: number): void; export function foo(sn: string | number): void; ``` + + + ## When Not To Use It It can sometimes be useful to place overload signatures alongside other meaningful parts of a type. @@ -96,4 +102,4 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates ## Related To -- [`unified-signatures`](./unified-signatures.md) +- [`unified-signatures`](./unified-signatures.mdx) diff --git a/packages/eslint-plugin/docs/rules/array-type.md b/packages/eslint-plugin/docs/rules/array-type.mdx similarity index 92% rename from packages/eslint-plugin/docs/rules/array-type.md rename to packages/eslint-plugin/docs/rules/array-type.mdx index e04635d607e8..3985ccc7e2ac 100644 --- a/packages/eslint-plugin/docs/rules/array-type.md +++ b/packages/eslint-plugin/docs/rules/array-type.mdx @@ -2,6 +2,9 @@ description: 'Require consistently using either `T[]` or `Array` for arrays.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/array-type** for documentation. @@ -18,50 +21,55 @@ The default config will enforce that all mutable and readonly arrays use the `'a Always use `T[]` or `readonly T[]` for all array types. - - -#### ❌ Incorrect + + ```ts option='{ "default": "array" }' const x: Array = ['a', 'b']; const y: ReadonlyArray = ['a', 'b']; ``` -#### βœ… Correct + + ```ts option='{ "default": "array" }' const x: string[] = ['a', 'b']; const y: readonly string[] = ['a', 'b']; ``` + + + ### `"generic"` Always use `Array` or `ReadonlyArray` for all array types. - - -#### ❌ Incorrect + + ```ts option='{ "default": "generic" }' const x: string[] = ['a', 'b']; const y: readonly string[] = ['a', 'b']; ``` -#### βœ… Correct + + ```ts option='{ "default": "generic" }' const x: Array = ['a', 'b']; const y: ReadonlyArray = ['a', 'b']; ``` + + + ### `"array-simple"` Use `T[]` or `readonly T[]` for simple types (i.e. types which are just primitive names or type references). Use `Array` or `ReadonlyArray` for all other types (union types, intersection types, object types, function types, etc). - - -#### ❌ Incorrect + + ```ts option='{ "default": "array-simple" }' const a: (string | number)[] = ['a', 'b']; @@ -72,7 +80,8 @@ const e: Array = ['a', 'b']; const f: ReadonlyArray = ['a', 'b']; ``` -#### βœ… Correct + + ```ts option='{ "default": "array-simple" }' const a: Array = ['a', 'b']; @@ -83,6 +92,9 @@ const e: string[] = ['a', 'b']; const f: readonly string[] = ['a', 'b']; ``` + + + ## Combination Matrix This matrix lists all possible option combinations and their expected results for different types of Arrays. diff --git a/packages/eslint-plugin/docs/rules/await-thenable.md b/packages/eslint-plugin/docs/rules/await-thenable.mdx similarity index 89% rename from packages/eslint-plugin/docs/rules/await-thenable.md rename to packages/eslint-plugin/docs/rules/await-thenable.mdx index fa02a9f286d5..3ad4c18de91d 100644 --- a/packages/eslint-plugin/docs/rules/await-thenable.md +++ b/packages/eslint-plugin/docs/rules/await-thenable.mdx @@ -2,6 +2,9 @@ description: 'Disallow awaiting a value that is not a Thenable.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/await-thenable** for documentation. @@ -14,9 +17,8 @@ While doing so is valid JavaScript, it is often a programmer error, such as forg ## Examples - - -### ❌ Incorrect + + ```ts await 'value'; @@ -25,7 +27,8 @@ const createValue = () => 'value'; await createValue(); ``` -### βœ… Correct + + ```ts await Promise.resolve('value'); @@ -34,6 +37,9 @@ const createValue = async () => 'value'; await createValue(); ``` + + + ## When Not To Use It If you want to allow code to `await` non-Promise values. diff --git a/packages/eslint-plugin/docs/rules/ban-ts-comment.md b/packages/eslint-plugin/docs/rules/ban-ts-comment.mdx similarity index 88% rename from packages/eslint-plugin/docs/rules/ban-ts-comment.md rename to packages/eslint-plugin/docs/rules/ban-ts-comment.mdx index cd62195fd47f..91d9a9051b8a 100644 --- a/packages/eslint-plugin/docs/rules/ban-ts-comment.md +++ b/packages/eslint-plugin/docs/rules/ban-ts-comment.mdx @@ -2,6 +2,9 @@ description: 'Disallow `@ts-` comments or require descriptions after directives.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/ban-ts-comment** for documentation. @@ -29,9 +32,8 @@ By default, only `@ts-check` is allowed, as it enables rather than suppresses er A value of `true` for a particular directive means that this rule will report if it finds any usage of said directive. - - -#### ❌ Incorrect + + ```ts option='{ "ts-ignore": true }' if (false) { @@ -46,7 +48,8 @@ if (false) { } ``` -#### βœ… Correct + + ```ts option='{ "ts-ignore": true }' if (false) { @@ -55,15 +58,17 @@ if (false) { } ``` + + + ### `allow-with-description` A value of `'allow-with-description'` for a particular directive means that this rule will report if it finds a directive that does not have a description following the directive (on the same line). For example, with `{ 'ts-expect-error': 'allow-with-description' }`: - - -#### ❌ Incorrect + + ```ts option='{ "ts-expect-error": "allow-with-description" }' if (false) { @@ -76,7 +81,8 @@ if (false) { } ``` -#### βœ… Correct + + ```ts option='{ "ts-expect-error": "allow-with-description" }' if (false) { @@ -91,39 +97,43 @@ if (false) { } ``` + + ### `descriptionFormat` For each directive type, you can specify a custom format in the form of a regular expression. Only description that matches the pattern will be allowed. For example, with `{ 'ts-expect-error': { descriptionFormat: '^: TS\\d+ because .+$' } }`: - + + -#### ❌ Incorrect - - +{/* prettier-ignore */} ```ts option='{ "ts-expect-error": { "descriptionFormat": "^: TS\\\\d+ because .+$" } }' // @ts-expect-error: the library definition is wrong const a = doSomething('hello'); ``` -#### βœ… Correct + + - +{/* prettier-ignore */} ```ts option='{ "ts-expect-error": { "descriptionFormat": "^: TS\\\\d+ because .+$" } }' // @ts-expect-error: TS1234 because the library definition is wrong const a = doSomething('hello'); ``` + + + ### `minimumDescriptionLength` Use `minimumDescriptionLength` to set a minimum length for descriptions when using the `allow-with-description` option for a directive. For example, with `{ 'ts-expect-error': 'allow-with-description', minimumDescriptionLength: 10 }` the following pattern is: - - -#### ❌ Incorrect + + ```ts option='{ "ts-expect-error": "allow-with-description", "minimumDescriptionLength": 10 }' if (false) { @@ -132,7 +142,8 @@ if (false) { } ``` -#### βœ… Correct + + ```ts option='{ "ts-expect-error": "allow-with-description", "minimumDescriptionLength": 10 }' if (false) { @@ -141,6 +152,9 @@ if (false) { } ``` + + + ## When Not To Use It If your projectΒ or its dependencies were not architected with strong type safety in mind, it can be difficult to always adhere to proper TypeScript semantics. diff --git a/packages/eslint-plugin/docs/rules/ban-tslint-comment.md b/packages/eslint-plugin/docs/rules/ban-tslint-comment.mdx similarity index 85% rename from packages/eslint-plugin/docs/rules/ban-tslint-comment.md rename to packages/eslint-plugin/docs/rules/ban-tslint-comment.mdx index c2529de6179f..c6aa3b9bf6e5 100644 --- a/packages/eslint-plugin/docs/rules/ban-tslint-comment.md +++ b/packages/eslint-plugin/docs/rules/ban-tslint-comment.mdx @@ -2,6 +2,9 @@ description: 'Disallow `// tslint:` comments.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/ban-tslint-comment** for documentation. @@ -12,9 +15,8 @@ Useful when migrating from TSLint to ESLint. Once TSLint has been removed, this ## Examples - - -### ❌ Incorrect + + ```ts /* tslint:disable */ @@ -26,7 +28,8 @@ someCode(); // tslint:disable-line // tslint:disable-next-line:rule1 rule2 rule3... ``` -### βœ… Correct + + ```ts // This is a comment that just happens to mention tslint @@ -34,6 +37,9 @@ someCode(); // tslint:disable-line someCode(); // This is a comment that just happens to mention tslint ``` + + + ## When Not To Use It If you are still using TSLint alongside ESLint. diff --git a/packages/eslint-plugin/docs/rules/ban-types.md b/packages/eslint-plugin/docs/rules/ban-types.mdx similarity index 95% rename from packages/eslint-plugin/docs/rules/ban-types.md rename to packages/eslint-plugin/docs/rules/ban-types.mdx index 0539fb2dd2d8..da30f4959116 100644 --- a/packages/eslint-plugin/docs/rules/ban-types.md +++ b/packages/eslint-plugin/docs/rules/ban-types.mdx @@ -2,6 +2,9 @@ description: 'Disallow certain types.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/ban-types** for documentation. @@ -16,9 +19,8 @@ Note that it does not ban the corresponding runtime objects from being used. Examples of code with the default options: - - -### ❌ Incorrect + + ```ts // use lower-case primitives for consistency @@ -39,7 +41,8 @@ const curly1: {} = 1; const curly2: {} = { a: 'string' }; ``` -### βœ… Correct + + ```ts // use lower-case primitives for consistency @@ -60,6 +63,9 @@ const curly1: number = 1; const curly2: Record<'a', string> = { a: 'string' }; ``` + + + ## Options The default options provide a set of "best practices", intended to provide safety and standardization in your codebase: @@ -75,7 +81,7 @@ The default options provide a set of "best practices", intended to provide safet
Default Options - +{/* Inject default options */}
diff --git a/packages/eslint-plugin/docs/rules/block-spacing.md b/packages/eslint-plugin/docs/rules/block-spacing.mdx similarity index 87% rename from packages/eslint-plugin/docs/rules/block-spacing.md rename to packages/eslint-plugin/docs/rules/block-spacing.mdx index 9e6ac779d0ce..de933a031de5 100644 --- a/packages/eslint-plugin/docs/rules/block-spacing.md +++ b/packages/eslint-plugin/docs/rules/block-spacing.mdx @@ -2,6 +2,9 @@ description: 'Disallow or enforce spaces inside of blocks after opening block and before closing block.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/block-spacing** for documentation. diff --git a/packages/eslint-plugin/docs/rules/brace-style.md b/packages/eslint-plugin/docs/rules/brace-style.mdx similarity index 85% rename from packages/eslint-plugin/docs/rules/brace-style.md rename to packages/eslint-plugin/docs/rules/brace-style.mdx index 66f39643ddca..a1e4cb18fc7c 100644 --- a/packages/eslint-plugin/docs/rules/brace-style.md +++ b/packages/eslint-plugin/docs/rules/brace-style.mdx @@ -2,6 +2,9 @@ description: 'Enforce consistent brace style for blocks.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/brace-style** for documentation. diff --git a/packages/eslint-plugin/docs/rules/camelcase.md b/packages/eslint-plugin/docs/rules/camelcase.md index 2a85ab90c5e4..d5ee33340745 100644 --- a/packages/eslint-plugin/docs/rules/camelcase.md +++ b/packages/eslint-plugin/docs/rules/camelcase.md @@ -1,13 +1,11 @@ :::danger Deprecated -This rule has been deprecated in favour of the [`naming-convention`](./naming-convention.md) rule. +This rule has been deprecated in favour of the [`naming-convention`](./naming-convention.mdx) rule. ::: - +so end-users will only be able to get to this page from the search bar. --> diff --git a/packages/eslint-plugin/docs/rules/class-literal-property-style.md b/packages/eslint-plugin/docs/rules/class-literal-property-style.mdx similarity index 90% rename from packages/eslint-plugin/docs/rules/class-literal-property-style.md rename to packages/eslint-plugin/docs/rules/class-literal-property-style.mdx index 8567bc3ca83b..d980d3b92470 100644 --- a/packages/eslint-plugin/docs/rules/class-literal-property-style.md +++ b/packages/eslint-plugin/docs/rules/class-literal-property-style.mdx @@ -2,6 +2,9 @@ description: 'Enforce that literals on classes are exposed in a consistent style.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/class-literal-property-style** for documentation. @@ -15,10 +18,8 @@ By default this rule prefers the `fields` style as it means JS doesn't have to s ## Options :::note - This rule only checks for constant _literal_ values (string, template string, number, bigint, boolean, regexp, null). It does not check objects or arrays, because a readonly field behaves differently to a getter in those cases. It also does not check functions, as it is a common pattern to use readonly fields with arrow function values as auto-bound methods. This is because these types can be mutated and carry with them more complex implications about their usage. - ::: ### `"fields"` @@ -27,9 +28,8 @@ This style checks for any getter methods that return literal values, and require Examples of code with the `fields` style: - - -#### ❌ Incorrect + + ```ts option='"fields"' class Mx { @@ -43,7 +43,8 @@ class Mx { } ``` -#### βœ… Correct + + ```ts option='"fields"' class Mx { @@ -60,17 +61,19 @@ class Mx { } ``` + + + ### `"getters"` This style checks for any `readonly` fields that are assigned literal values, and requires them to be defined as getters instead. -This style pairs well with the [`@typescript-eslint/prefer-readonly`](prefer-readonly.md) rule, +This style pairs well with the [`@typescript-eslint/prefer-readonly`](prefer-readonly.mdx) rule, as it will identify fields that can be `readonly`, and thus should be made into getters. Examples of code with the `getters` style: - - -#### ❌ Incorrect + + ```ts option='"getters"' class Mx { @@ -80,7 +83,8 @@ class Mx { } ``` -#### βœ… Correct + + ```ts option='"getters"' class Mx { @@ -100,6 +104,9 @@ class Mx { } ``` + + + ## When Not To Use It When you have no strong preference, or do not wish to enforce a particular style for how literal values are exposed by your classes. diff --git a/packages/eslint-plugin/docs/rules/class-methods-use-this.md b/packages/eslint-plugin/docs/rules/class-methods-use-this.mdx similarity index 87% rename from packages/eslint-plugin/docs/rules/class-methods-use-this.md rename to packages/eslint-plugin/docs/rules/class-methods-use-this.mdx index 8864b44cf3aa..3b4cb7cf138d 100644 --- a/packages/eslint-plugin/docs/rules/class-methods-use-this.md +++ b/packages/eslint-plugin/docs/rules/class-methods-use-this.mdx @@ -2,6 +2,9 @@ description: 'Enforce that class methods utilize `this`.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/class-methods-use-this** for documentation. @@ -51,7 +54,7 @@ It's important to note that this option does not only apply to members defined i #### `true` -Example of a correct code when `ignoreClassesThatImplementAnInterface` is set to `true`: +Example of correct code when `ignoreClassesThatImplementAnInterface` is set to `true`: ```ts option='{ "ignoreClassesThatImplementAnInterface": true }' showPlaygroundButton class X implements Y { @@ -62,11 +65,10 @@ class X implements Y { #### `'public-fields'` -Example of a incorrect code when `ignoreClassesThatImplementAnInterface` is set to `'public-fields'`: - - +Example of incorrect code when `ignoreClassesThatImplementAnInterface` is set to `'public-fields'`: -##### ❌ Incorrect + + ```ts class X implements Y { @@ -81,7 +83,8 @@ class X implements Y { } ``` -##### βœ… Correct + + ```ts class X implements Y { @@ -90,6 +93,9 @@ class X implements Y { } ``` + + + ## When Not To Use It If your project dynamically changes `this` scopes around in a way TypeScript has difficulties modeling, this rule may not be viable to use. diff --git a/packages/eslint-plugin/docs/rules/comma-dangle.md b/packages/eslint-plugin/docs/rules/comma-dangle.mdx similarity index 92% rename from packages/eslint-plugin/docs/rules/comma-dangle.md rename to packages/eslint-plugin/docs/rules/comma-dangle.mdx index 80057954ff67..fa934f5ee4bf 100644 --- a/packages/eslint-plugin/docs/rules/comma-dangle.md +++ b/packages/eslint-plugin/docs/rules/comma-dangle.mdx @@ -2,6 +2,9 @@ description: 'Require or disallow trailing commas.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/comma-dangle** for documentation. diff --git a/packages/eslint-plugin/docs/rules/comma-spacing.md b/packages/eslint-plugin/docs/rules/comma-spacing.mdx similarity index 84% rename from packages/eslint-plugin/docs/rules/comma-spacing.md rename to packages/eslint-plugin/docs/rules/comma-spacing.mdx index ccbd46842946..249f8933e656 100644 --- a/packages/eslint-plugin/docs/rules/comma-spacing.md +++ b/packages/eslint-plugin/docs/rules/comma-spacing.mdx @@ -2,6 +2,9 @@ description: 'Enforce consistent spacing before and after commas.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/comma-spacing** for documentation. diff --git a/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md b/packages/eslint-plugin/docs/rules/consistent-generic-constructors.mdx similarity index 88% rename from packages/eslint-plugin/docs/rules/consistent-generic-constructors.md rename to packages/eslint-plugin/docs/rules/consistent-generic-constructors.mdx index 6056a58dfec4..486147a04551 100644 --- a/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md +++ b/packages/eslint-plugin/docs/rules/consistent-generic-constructors.mdx @@ -2,6 +2,9 @@ description: 'Enforce specifying generic type arguments on type annotation or constructor name of a constructor call.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/consistent-generic-constructors** for documentation. @@ -29,16 +32,16 @@ Keeping to one side consistently improve code readability. ### `constructor` - - -#### ❌ Incorrect + + ```ts option='"constructor"' const map: Map = new Map(); const set: Set = new Set(); ``` -#### βœ… Correct + + ```ts option='"constructor"' const map = new Map(); @@ -48,18 +51,21 @@ const set = new Set(); const set: Set = new Set(); ``` -### `type-annotation` + + - +### `type-annotation` -#### ❌ Incorrect + + ```ts option='"type-annotation"' const map = new Map(); const set = new Set(); ``` -#### βœ… Correct + + ```ts option='"type-annotation"' const map: Map = new Map(); @@ -68,6 +74,9 @@ const set = new Set(); const set: Set = new Set(); ``` + + + ## When Not To Use It You can turn this rule off if you don't want to enforce one kind of generic constructor style over the other. diff --git a/packages/eslint-plugin/docs/rules/consistent-indexed-object-style.md b/packages/eslint-plugin/docs/rules/consistent-indexed-object-style.mdx similarity index 85% rename from packages/eslint-plugin/docs/rules/consistent-indexed-object-style.md rename to packages/eslint-plugin/docs/rules/consistent-indexed-object-style.mdx index 82321b4fe61e..661b1b317b51 100644 --- a/packages/eslint-plugin/docs/rules/consistent-indexed-object-style.md +++ b/packages/eslint-plugin/docs/rules/consistent-indexed-object-style.mdx @@ -2,6 +2,9 @@ description: 'Require or disallow the `Record` type.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/consistent-indexed-object-style** for documentation. @@ -29,9 +32,8 @@ Keeping to one declaration form consistently improve code readability. ### `record` - - -#### ❌ Incorrect + + ```ts option='"record"' interface Foo { @@ -43,23 +45,27 @@ type Foo = { }; ``` -#### βœ… Correct + + ```ts option='"record"' type Foo = Record; ``` -### `index-signature` + + - +### `index-signature` -#### ❌ Incorrect + + ```ts option='"index-signature"' type Foo = Record; ``` -#### βœ… Correct + + ```ts option='"index-signature"' interface Foo { @@ -71,6 +77,9 @@ type Foo = { }; ``` + + + ## When Not To Use It This rule is purely a stylistic rule for maintaining consistency in your project. diff --git a/packages/eslint-plugin/docs/rules/consistent-return.md b/packages/eslint-plugin/docs/rules/consistent-return.mdx similarity index 83% rename from packages/eslint-plugin/docs/rules/consistent-return.md rename to packages/eslint-plugin/docs/rules/consistent-return.mdx index 03f74840e26f..73eb72515c5e 100644 --- a/packages/eslint-plugin/docs/rules/consistent-return.md +++ b/packages/eslint-plugin/docs/rules/consistent-return.mdx @@ -2,6 +2,9 @@ description: 'Require `return` statements to either always or never specify values.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/consistent-return** for documentation. @@ -9,9 +12,8 @@ description: 'Require `return` statements to either always or never specify valu This rule extends the base [`eslint/consistent-return`](https://eslint.org/docs/rules/consistent-return) rule. This version adds support for functions that return `void` or `Promise`. - - -### ❌ Incorrect + + ```ts function foo(): undefined {} @@ -26,7 +28,8 @@ async function baz(flag: boolean): Promise { } ``` -### βœ… Correct + + ```ts function foo(): void {} @@ -40,3 +43,6 @@ async function baz(flag: boolean): Promise { return; } ``` + + + diff --git a/packages/eslint-plugin/docs/rules/consistent-type-assertions.md b/packages/eslint-plugin/docs/rules/consistent-type-assertions.mdx similarity index 93% rename from packages/eslint-plugin/docs/rules/consistent-type-assertions.md rename to packages/eslint-plugin/docs/rules/consistent-type-assertions.mdx index 7bd4412470e5..225c35e92195 100644 --- a/packages/eslint-plugin/docs/rules/consistent-type-assertions.md +++ b/packages/eslint-plugin/docs/rules/consistent-type-assertions.mdx @@ -2,6 +2,9 @@ description: 'Enforce consistent usage of type assertions.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/consistent-type-assertions** for documentation. @@ -49,9 +52,8 @@ Assertions to `any` are also ignored by this option. Examples of code for `{ assertionStyle: 'as', objectLiteralTypeAssertions: 'never' }`: - - -#### ❌ Incorrect + + ```ts option='{ "assertionStyle": "as", "objectLiteralTypeAssertions": "never" }' const x = { foo: 1 } as T; @@ -61,7 +63,8 @@ function bar() { } ``` -#### βœ… Correct + + ```ts option='{ "assertionStyle": "as", "objectLiteralTypeAssertions": "never" }' const x: T = { foo: 1 }; @@ -73,13 +76,13 @@ function bar(): T { } ``` - + + Examples of code for `{ assertionStyle: 'as', objectLiteralTypeAssertions: 'allow-as-parameter' }`: - - -#### ❌ Incorrect + + ```ts option='{ "assertionStyle": "as", "objectLiteralTypeAssertions": "allow-as-parameter" }' const x = { foo: 1 } as T; @@ -89,7 +92,8 @@ function bar() { } ``` -#### βœ… Correct + + ```tsx option='{ "assertionStyle": "as", "objectLiteralTypeAssertions": "allow-as-parameter" }' const x: T = { foo: 1 }; @@ -103,7 +107,8 @@ function bar() { const foo = ; ``` - + + ## When Not To Use It diff --git a/packages/eslint-plugin/docs/rules/consistent-type-definitions.md b/packages/eslint-plugin/docs/rules/consistent-type-definitions.mdx similarity index 87% rename from packages/eslint-plugin/docs/rules/consistent-type-definitions.md rename to packages/eslint-plugin/docs/rules/consistent-type-definitions.mdx index 4394e82f39ce..353bd1cf6258 100644 --- a/packages/eslint-plugin/docs/rules/consistent-type-definitions.md +++ b/packages/eslint-plugin/docs/rules/consistent-type-definitions.mdx @@ -2,6 +2,9 @@ description: 'Enforce type definitions to consistently use either `interface` or `type`.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/consistent-type-definitions** for documentation. @@ -32,15 +35,15 @@ Using the same type declaration style consistently helps with code readability. ### `interface` - - -#### ❌ Incorrect + + ```ts option='"interface"' type T = { x: number }; ``` -#### βœ… Correct + + ```ts option='"interface"' type T = string; @@ -51,11 +54,13 @@ interface T { } ``` -### `type` + + - +### `type` -#### ❌ Incorrect + + ```ts option='"type"' interface T { @@ -63,12 +68,16 @@ interface T { } ``` -#### βœ… Correct + + ```ts option='"type"' type T = { x: number }; ``` + + + ## When Not To Use It If you specifically want to use an interface or type literal for stylistic reasons, you can avoid this rule. diff --git a/packages/eslint-plugin/docs/rules/consistent-type-exports.md b/packages/eslint-plugin/docs/rules/consistent-type-exports.mdx similarity index 89% rename from packages/eslint-plugin/docs/rules/consistent-type-exports.md rename to packages/eslint-plugin/docs/rules/consistent-type-exports.mdx index 61f520845c5f..be1ab45c2b7e 100644 --- a/packages/eslint-plugin/docs/rules/consistent-type-exports.md +++ b/packages/eslint-plugin/docs/rules/consistent-type-exports.mdx @@ -2,6 +2,9 @@ description: 'Enforce consistent usage of type exports.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/consistent-type-exports** for documentation. @@ -13,9 +16,8 @@ This allows transpilers to drop exports without knowing the types of the depende ## Examples - - -### ❌ Incorrect + + ```ts interface ButtonProps { @@ -29,7 +31,8 @@ class Button implements ButtonProps { export { Button, ButtonProps }; ``` -### βœ… Correct + + ```ts interface ButtonProps { @@ -44,6 +47,9 @@ export { Button }; export type { ButtonProps }; ``` + + + ## Options ### `fixMixedExportsWithInlineTypeSpecifier` @@ -79,21 +85,24 @@ export type { T }; export { x }; ``` - - -### ❌ Incorrect + + ```ts option='{ "fixMixedExportsWithInlineTypeSpecifier": true }' export { Button } from 'some-library'; export type { ButtonProps } from 'some-library'; ``` -### βœ… Correct + + ```ts option='{ "fixMixedExportsWithInlineTypeSpecifier": true }' export { Button, type ButtonProps } from 'some-library'; ``` + + + ## When Not To Use It If you use `--isolatedModules` the compiler would error if a type is not re-exported using `export type`. diff --git a/packages/eslint-plugin/docs/rules/consistent-type-imports.md b/packages/eslint-plugin/docs/rules/consistent-type-imports.mdx similarity index 94% rename from packages/eslint-plugin/docs/rules/consistent-type-imports.md rename to packages/eslint-plugin/docs/rules/consistent-type-imports.mdx index 97e6ba147c26..354b777c6605 100644 --- a/packages/eslint-plugin/docs/rules/consistent-type-imports.md +++ b/packages/eslint-plugin/docs/rules/consistent-type-imports.mdx @@ -2,6 +2,9 @@ description: 'Enforce consistent usage of type imports.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/consistent-type-imports** for documentation. @@ -45,9 +48,8 @@ This option defines the expected type modifier to be added when an import is det - `separate-type-imports` will add the type keyword after the import keyword `import type { A } from '...'`. It is the default. - `inline-type-imports` will inline the type keyword `import { type A } from '...'` and is only available in TypeScript 4.5 and onwards. See [documentation here](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html#type-modifiers-on-import-names 'TypeScript 4.5 documentation on type modifiers and import names'). - - -#### ❌ Incorrect + + ```ts import { Foo } from 'Foo'; @@ -56,7 +58,8 @@ type T = Foo; const x: Bar = 1; ``` -#### βœ… With `separate-type-imports` + + ```ts option='{ "fixStyle": "separate-type-imports" }' import type { Foo } from 'Foo'; @@ -65,7 +68,8 @@ type T = Foo; const x: Bar = 1; ``` -#### βœ… With `inline-type-imports` + + ```ts option='{ "fixStyle": "inline-type-imports" }' import { type Foo } from 'Foo'; @@ -74,7 +78,8 @@ type T = Foo; const x: Bar = 1; ``` - + + ### `disallowTypeAnnotations` @@ -103,6 +108,6 @@ We recommend picking a single option for this rule that works best for your proj ## Related To -- [`no-import-type-side-effects`](./no-import-type-side-effects.md) -- [`import/consistent-type-specifier-style`](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/consistent-type-specifier-style.md) +- [`no-import-type-side-effects`](./no-import-type-side-effects.mdx) +- [`import/consistent-type-specifier-style`](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/consistent-type-specifier-style.mdx) - [`import/no-duplicates` with `{"prefer-inline": true}`](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-duplicates.md#inline-type-imports) diff --git a/packages/eslint-plugin/docs/rules/default-param-last.md b/packages/eslint-plugin/docs/rules/default-param-last.mdx similarity index 86% rename from packages/eslint-plugin/docs/rules/default-param-last.md rename to packages/eslint-plugin/docs/rules/default-param-last.mdx index 909f9ac10d0f..bc50cad62136 100644 --- a/packages/eslint-plugin/docs/rules/default-param-last.md +++ b/packages/eslint-plugin/docs/rules/default-param-last.mdx @@ -2,6 +2,9 @@ description: 'Enforce default parameters to be last.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/default-param-last** for documentation. @@ -9,9 +12,8 @@ description: 'Enforce default parameters to be last.' This rule extends the base [`eslint/default-param-last`](https://eslint.org/docs/rules/default-param-last) rule. It adds support for optional parameters. - - -### ❌ Incorrect + + ```ts /* eslint @typescript-eslint/default-param-last: "error" */ @@ -33,7 +35,8 @@ class Foo { } ``` -### βœ… Correct + + ```ts /* eslint @typescript-eslint/default-param-last: "error" */ @@ -56,3 +59,6 @@ class Foo { ) {} } ``` + + + diff --git a/packages/eslint-plugin/docs/rules/dot-notation.md b/packages/eslint-plugin/docs/rules/dot-notation.mdx similarity index 97% rename from packages/eslint-plugin/docs/rules/dot-notation.md rename to packages/eslint-plugin/docs/rules/dot-notation.mdx index 8d939219bb2d..ba23d98b366f 100644 --- a/packages/eslint-plugin/docs/rules/dot-notation.md +++ b/packages/eslint-plugin/docs/rules/dot-notation.mdx @@ -2,6 +2,9 @@ description: 'Enforce dot notation whenever possible.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/dot-notation** for documentation. diff --git a/packages/eslint-plugin/docs/rules/explicit-function-return-type.md b/packages/eslint-plugin/docs/rules/explicit-function-return-type.mdx similarity index 88% rename from packages/eslint-plugin/docs/rules/explicit-function-return-type.md rename to packages/eslint-plugin/docs/rules/explicit-function-return-type.mdx index d33de3e02b0e..f664314cd462 100644 --- a/packages/eslint-plugin/docs/rules/explicit-function-return-type.md +++ b/packages/eslint-plugin/docs/rules/explicit-function-return-type.mdx @@ -2,6 +2,9 @@ description: 'Require explicit return types on functions and class methods.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/explicit-function-return-type** for documentation. @@ -16,9 +19,8 @@ This rule enforces that functions do have an explicit return type annotation. ## Examples - - -### ❌ Incorrect + + ```ts // Should indicate that no value is returned (void) @@ -42,7 +44,8 @@ class Test { } ``` -### βœ… Correct + + ```ts // No return value should be expected (void) @@ -66,6 +69,9 @@ class Test { } ``` + + + ## Options ### Configuring in a mixed JS/TS codebase @@ -94,9 +100,8 @@ If you are working on a codebase within which you lint non-TypeScript code (i.e. Examples of code for this rule with `{ allowExpressions: true }`: - - -#### ❌ Incorrect + + ```ts option='{ "allowExpressions": true }' function test() {} @@ -106,7 +111,8 @@ const fn = () => {}; export default () => {}; ``` -#### βœ… Correct + + ```ts option='{ "allowExpressions": true }' node.addEventListener('click', () => {}); @@ -116,13 +122,15 @@ node.addEventListener('click', function () {}); const foo = arr.map(i => i * i); ``` + + + ### `allowTypedFunctionExpressions` Examples of code for this rule with `{ allowTypedFunctionExpressions: true }`: - - -#### ❌ Incorrect + + ```ts option='{ "allowTypedFunctionExpressions": true }' let arrowFn = () => 'test'; @@ -136,7 +144,8 @@ let objectProp = { }; ``` -#### βœ… Correct + + ```ts option='{ "allowTypedFunctionExpressions": true }' type FuncType = () => string; @@ -174,13 +183,15 @@ functionWithObjectArg({ }); ``` + + + ### `allowHigherOrderFunctions` Examples of code for this rule with `{ allowHigherOrderFunctions: true }`: - - -#### ❌ Incorrect + + ```ts option='{ "allowHigherOrderFunctions": true }' var arrowFn = () => () => {}; @@ -190,7 +201,8 @@ function fn() { } ``` -#### βœ… Correct + + ```ts option='{ "allowHigherOrderFunctions": true }' var arrowFn = () => (): void => {}; @@ -200,33 +212,38 @@ function fn() { } ``` + + + ### `allowDirectConstAssertionInArrowFunctions` Examples of code for this rule with `{ allowDirectConstAssertionInArrowFunctions: true }`: - - -#### ❌ Incorrect + + ```ts option='{ "allowDirectConstAssertionInArrowFunctions": true }' const func = (value: number) => ({ type: 'X', value }) as any; const func = (value: number) => ({ type: 'X', value }) as Action; ``` -#### βœ… Correct + + ```ts option='{ "allowDirectConstAssertionInArrowFunctions": true }' const func = (value: number) => ({ foo: 'bar', value }) as const; const func = () => x as const; ``` + + + ### `allowConciseArrowFunctionExpressionsStartingWithVoid` Examples of code for this rule with `{ allowConciseArrowFunctionExpressionsStartingWithVoid: true }`: - - -#### ❌ Incorrect + + ```ts option='{ "allowConciseArrowFunctionExpressionsStartingWithVoid": true }' var join = (a: string, b: string) => `${a}${b}`; @@ -236,19 +253,22 @@ const log = (message: string) => { }; ``` -#### βœ… Correct + + ```ts option='{ "allowConciseArrowFunctionExpressionsStartingWithVoid": true }' var log = (message: string) => void console.log(message); ``` + + + ### `allowFunctionsWithoutTypeParameters` Examples of code for this rule with `{ allowFunctionsWithoutTypeParameters: true }`: - - -#### ❌ Incorrect + + ```ts option='{ "allowFunctionsWithoutTypeParameters": true }' function foo(t: T) { @@ -258,7 +278,8 @@ function foo(t: T) { const bar = (t: T) => t; ``` -#### βœ… Correct + + ```ts option='{ "allowFunctionsWithoutTypeParameters": true }' function foo(t: T): T { @@ -274,6 +295,9 @@ function allowedFunction(x: string) { const allowedArrow = (x: string) => x; ``` + + + ### `allowedNames` You may pass function/method names you would like this rule to ignore, like so: @@ -293,15 +317,15 @@ You may pass function/method names you would like this rule to ignore, like so: Examples of code for this rule with `{ allowIIFEs: true }`: - - -#### ❌ Incorrect + + ```ts option='{ "allowIIFEs": true }' var func = () => 'foo'; ``` -#### βœ… Correct + + ```ts option='{ "allowIIFEs": true }' var foo = (() => 'foo')(); @@ -311,6 +335,9 @@ var bar = (function () { })(); ``` + + + ## When Not To Use It If you don't find the added cost of explicitly writing function return types to be worth the visual clarity, or your project is not large enough for it to be a factor in type checking performance, then you will not need this rule. diff --git a/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md b/packages/eslint-plugin/docs/rules/explicit-member-accessibility.mdx similarity index 99% rename from packages/eslint-plugin/docs/rules/explicit-member-accessibility.md rename to packages/eslint-plugin/docs/rules/explicit-member-accessibility.mdx index 4b8c79aad85a..59eaa315afb1 100644 --- a/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md +++ b/packages/eslint-plugin/docs/rules/explicit-member-accessibility.mdx @@ -2,6 +2,9 @@ description: 'Require explicit accessibility modifiers on class properties and methods.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/explicit-member-accessibility** for documentation. diff --git a/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md b/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.mdx similarity index 90% rename from packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md rename to packages/eslint-plugin/docs/rules/explicit-module-boundary-types.mdx index 879e30638114..e73e12c97d85 100644 --- a/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md +++ b/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.mdx @@ -2,6 +2,9 @@ description: "Require explicit return and argument types on exported functions' and classes' public class methods." --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/explicit-module-boundary-types** for documentation. @@ -12,9 +15,8 @@ It can also improve TypeScript type checking performance on larger codebases. ## Examples - - -### ❌ Incorrect + + ```ts // Should indicate that no value is returned (void) @@ -37,7 +39,8 @@ export class Test { } ``` -### βœ… Correct + + ```ts // A function with no return value (void) @@ -65,6 +68,9 @@ function test() { } ``` + + + ## Options ### Configuring in a mixed JS/TS codebase @@ -93,27 +99,29 @@ If you are working on a codebase within which you lint non-TypeScript code (i.e. Examples of code for this rule with `{ allowArgumentsExplicitlyTypedAsAny: false }`: - - -#### ❌ Incorrect + + ```ts option='{ "allowArgumentsExplicitlyTypedAsAny": false }' export const func = (value: any): number => value + 1; ``` -#### βœ… Correct + + ```ts option='{ "allowArgumentsExplicitlyTypedAsAny": false }' export const func = (value: number): number => value + 1; ``` + + + ### `allowDirectConstAssertionInArrowFunctions` Examples of code for this rule with `{ allowDirectConstAssertionInArrowFunctions: false }`: - - -#### ❌ Incorrect + + ```ts option='{ "allowArgumentsExplicitlyTypedAsAny": false }' export const func = (value: number) => ({ type: 'X', value }); @@ -123,7 +131,8 @@ export const foo = () => ({ export const bar = () => 1; ``` -#### βœ… Correct + + ```ts option='{ "allowArgumentsExplicitlyTypedAsAny": false }' export const func = (value: number) => ({ type: 'X', value }) as const; @@ -134,6 +143,9 @@ export const foo = () => export const bar = () => 1 as const; ``` + + + ### `allowedNames` You may pass function/method names you would like this rule to ignore, like so: @@ -153,9 +165,8 @@ You may pass function/method names you would like this rule to ignore, like so: Examples of code for this rule with `{ allowHigherOrderFunctions: false }`: - - -#### ❌ Incorrect + + ```ts option='{ "allowHigherOrderFunctions": false }' export const arrowFn = () => () => {}; @@ -169,7 +180,8 @@ export function foo(outer: string) { } ``` -#### βœ… Correct + + ```ts option='{ "allowHigherOrderFunctions": false }' export const arrowFn = () => (): void => {}; @@ -183,13 +195,15 @@ export function foo(outer: string) { } ``` + + + ### `allowTypedFunctionExpressions` Examples of code for this rule with `{ allowTypedFunctionExpressions: false }`: - - -#### ❌ Incorrect + + ```ts option='{ "allowTypedFunctionExpressions": false }' export let arrowFn = () => 'test'; @@ -205,7 +219,8 @@ export let objectProp = { export const foo = bar => {}; ``` -#### βœ… Correct + + ```ts option='{ "allowTypedFunctionExpressions": false }' type FuncType = () => string; @@ -236,6 +251,9 @@ type FooType = (bar: string) => void; export const foo: FooType = bar => {}; ``` + + + ## When Not To Use It If your project is not used by downstream consumers that are sensitive to API types, you can disable this rule. @@ -246,4 +264,4 @@ If your project is not used by downstream consumers that are sensitive to API ty ## Related To -- [explicit-function-return-type](./explicit-function-return-type.md) +- [explicit-function-return-type](./explicit-function-return-type.mdx) diff --git a/packages/eslint-plugin/docs/rules/func-call-spacing.md b/packages/eslint-plugin/docs/rules/func-call-spacing.mdx similarity index 86% rename from packages/eslint-plugin/docs/rules/func-call-spacing.md rename to packages/eslint-plugin/docs/rules/func-call-spacing.mdx index d9acf9fa9de5..defc8d976d7d 100644 --- a/packages/eslint-plugin/docs/rules/func-call-spacing.md +++ b/packages/eslint-plugin/docs/rules/func-call-spacing.mdx @@ -2,6 +2,9 @@ description: 'Require or disallow spacing between function identifiers and their invocations.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/func-call-spacing** for documentation. diff --git a/packages/eslint-plugin/docs/rules/indent.md b/packages/eslint-plugin/docs/rules/indent.mdx similarity index 87% rename from packages/eslint-plugin/docs/rules/indent.md rename to packages/eslint-plugin/docs/rules/indent.mdx index 450b80783282..cf74735986a9 100644 --- a/packages/eslint-plugin/docs/rules/indent.md +++ b/packages/eslint-plugin/docs/rules/indent.mdx @@ -2,6 +2,9 @@ description: 'Enforce consistent indentation.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/indent** for documentation. diff --git a/packages/eslint-plugin/docs/rules/init-declarations.md b/packages/eslint-plugin/docs/rules/init-declarations.mdx similarity index 85% rename from packages/eslint-plugin/docs/rules/init-declarations.md rename to packages/eslint-plugin/docs/rules/init-declarations.mdx index 3a456d36b0a6..911aeb18c204 100644 --- a/packages/eslint-plugin/docs/rules/init-declarations.md +++ b/packages/eslint-plugin/docs/rules/init-declarations.mdx @@ -2,6 +2,9 @@ description: 'Require or disallow initialization in variable declarations.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/init-declarations** for documentation. diff --git a/packages/eslint-plugin/docs/rules/key-spacing.md b/packages/eslint-plugin/docs/rules/key-spacing.mdx similarity index 86% rename from packages/eslint-plugin/docs/rules/key-spacing.md rename to packages/eslint-plugin/docs/rules/key-spacing.mdx index 4a0884d60bbe..da0ebae30511 100644 --- a/packages/eslint-plugin/docs/rules/key-spacing.md +++ b/packages/eslint-plugin/docs/rules/key-spacing.mdx @@ -2,6 +2,9 @@ description: 'Enforce consistent spacing between property names and type annotations in types and interfaces.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/key-spacing** for documentation. diff --git a/packages/eslint-plugin/docs/rules/keyword-spacing.md b/packages/eslint-plugin/docs/rules/keyword-spacing.mdx similarity index 85% rename from packages/eslint-plugin/docs/rules/keyword-spacing.md rename to packages/eslint-plugin/docs/rules/keyword-spacing.mdx index a57774b5beda..c092b46a84f6 100644 --- a/packages/eslint-plugin/docs/rules/keyword-spacing.md +++ b/packages/eslint-plugin/docs/rules/keyword-spacing.mdx @@ -2,6 +2,9 @@ description: 'Enforce consistent spacing before and after keywords.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/keyword-spacing** for documentation. diff --git a/packages/eslint-plugin/docs/rules/lines-around-comment.md b/packages/eslint-plugin/docs/rules/lines-around-comment.mdx similarity index 95% rename from packages/eslint-plugin/docs/rules/lines-around-comment.md rename to packages/eslint-plugin/docs/rules/lines-around-comment.mdx index 33a04c78f7ab..bf612ceaaea8 100644 --- a/packages/eslint-plugin/docs/rules/lines-around-comment.md +++ b/packages/eslint-plugin/docs/rules/lines-around-comment.mdx @@ -2,6 +2,9 @@ description: 'Require empty lines around comments.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/lines-around-comment** for documentation. diff --git a/packages/eslint-plugin/docs/rules/lines-between-class-members.md b/packages/eslint-plugin/docs/rules/lines-between-class-members.mdx similarity index 95% rename from packages/eslint-plugin/docs/rules/lines-between-class-members.md rename to packages/eslint-plugin/docs/rules/lines-between-class-members.mdx index 1eabe79d4689..c093b715ee9e 100644 --- a/packages/eslint-plugin/docs/rules/lines-between-class-members.md +++ b/packages/eslint-plugin/docs/rules/lines-between-class-members.mdx @@ -2,6 +2,9 @@ description: 'Require or disallow an empty line between class members.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/lines-between-class-members** for documentation. diff --git a/packages/eslint-plugin/docs/rules/max-params.md b/packages/eslint-plugin/docs/rules/max-params.mdx similarity index 86% rename from packages/eslint-plugin/docs/rules/max-params.md rename to packages/eslint-plugin/docs/rules/max-params.mdx index 03854473cf36..3e1d80bb1563 100644 --- a/packages/eslint-plugin/docs/rules/max-params.md +++ b/packages/eslint-plugin/docs/rules/max-params.mdx @@ -2,6 +2,9 @@ description: 'Enforce a maximum number of parameters in function definitions.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/max-params** for documentation. diff --git a/packages/eslint-plugin/docs/rules/member-delimiter-style.md b/packages/eslint-plugin/docs/rules/member-delimiter-style.mdx similarity index 94% rename from packages/eslint-plugin/docs/rules/member-delimiter-style.md rename to packages/eslint-plugin/docs/rules/member-delimiter-style.mdx index 6ac58c573cee..7e3c5547cbc1 100644 --- a/packages/eslint-plugin/docs/rules/member-delimiter-style.md +++ b/packages/eslint-plugin/docs/rules/member-delimiter-style.mdx @@ -2,13 +2,16 @@ description: 'Require a specific member delimiter style for interfaces and type literals.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/member-delimiter-style** for documentation. TypeScript allows three delimiters between members in interfaces and type aliases: - +{/* prettier-ignore */} ```ts interface Foo { // Semicolons (default, preferred in TypeScript): @@ -104,11 +107,10 @@ For example, to require commas for `type`s, and semicolons for multiline `interf Examples of code for this rule with the default config: - - -### ❌ Incorrect + + - +{/* prettier-ignore */} ```ts // missing semicolon delimiter interface Foo { @@ -135,9 +137,10 @@ type FooBar = { name: string, greet(): string } type FooBar = { name: string; greet(): string; } ``` -### βœ… Correct + + - +{/* prettier-ignore */} ```ts interface Foo { name: string; @@ -156,6 +159,9 @@ type Bar = { name: string } type FooBar = { name: string; greet(): string } ``` + + + ## When Not To Use It If you specifically want to use both member delimiter kinds for stylistic reasons, or don't wish to enforce one style over the other, you can avoid this rule. diff --git a/packages/eslint-plugin/docs/rules/member-ordering.md b/packages/eslint-plugin/docs/rules/member-ordering.mdx similarity index 96% rename from packages/eslint-plugin/docs/rules/member-ordering.md rename to packages/eslint-plugin/docs/rules/member-ordering.mdx index fc505ff9d276..58d3bb0ff3ff 100644 --- a/packages/eslint-plugin/docs/rules/member-ordering.md +++ b/packages/eslint-plugin/docs/rules/member-ordering.mdx @@ -2,6 +2,9 @@ description: 'Require a consistent member declaration order.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/member-ordering** for documentation. @@ -285,9 +288,8 @@ It also ignores accessibility and scope. } ``` - - -#### ❌ Incorrect + + ```ts option='{ "default": ["signature", "method", "constructor", "field"] }' interface Foo { @@ -344,7 +346,8 @@ const Foo = class { }; ``` -#### βœ… Correct + + ```ts option='{ "default": ["signature", "method", "constructor", "field"] }' interface Foo { @@ -400,6 +403,9 @@ const Foo = class { }; ``` + + + ### Classes #### Public Instance Methods Before Public Static Fields @@ -420,9 +426,8 @@ It doesn't apply to interfaces or type literals as accessibility and scope are n } ``` - - -##### ❌ Incorrect + + ```ts option='{ "default": ["public-instance-method", "public-static-field"] }' class Foo { @@ -460,7 +465,8 @@ const Foo = class { }; ``` -##### βœ… Correct + + ```ts option='{ "default": ["public-instance-method", "public-static-field"] }' class Foo { @@ -498,6 +504,9 @@ const Foo = class { }; ``` + + + #### Static Fields Before Instance Fields This config specifies that static fields should come before instance fields, with public static fields first. @@ -514,9 +523,8 @@ It doesn't apply to interfaces or type literals as accessibility and scope are n } ``` - - -##### ❌ Incorrect + + ```ts option='{ "default": ["public-static-field", "static-field", "instance-field"] }' class Foo { @@ -551,7 +559,8 @@ const foo = class { }; ``` -##### βœ… Correct + + ```ts option='{ "default": ["public-static-field", "static-field", "instance-field"] }' class Foo { @@ -585,6 +594,9 @@ const foo = class { }; ``` + + + #### Class Declarations This config only specifies an order for classes: methods, then the constructor, then fields. @@ -603,9 +615,8 @@ Default settings will be used for class declarations and all other syntax constr } ``` - - -##### ❌ Incorrect + + ```ts option='{ "classes": ["method", "constructor", "field"] }' class Foo { @@ -620,7 +631,8 @@ class Foo { } ``` -##### βœ… Correct + + ```ts option='{ "classes": ["method", "constructor", "field"] }' class Foo { @@ -635,6 +647,9 @@ class Foo { } ``` + + + #### Class Expressions This config only specifies an order for classes expressions: methods, then the constructor, then fields. @@ -653,9 +668,8 @@ Default settings will be used for class declarations and all other syntax constr } ``` - - -##### ❌ Incorrect + + ```ts option='{ "classExpressions": ["method", "constructor", "field"] }' const foo = class { @@ -670,7 +684,8 @@ const foo = class { }; ``` -##### βœ… Correct + + ```ts option='{ "classExpressions": ["method", "constructor", "field"] }' const foo = class { @@ -685,6 +700,9 @@ const foo = class { }; ``` + + + ### Interfaces This config only specifies an order for interfaces: signatures, then methods, then constructors, then fields. @@ -707,9 +725,8 @@ These member types are the only ones allowed for `interfaces`. } ``` - - -#### ❌ Incorrect + + ```ts option='{ "interfaces": ["signature", "method", "constructor", "field"] }' interface Foo { @@ -723,7 +740,8 @@ interface Foo { } ``` -#### βœ… Correct + + ```ts option='{ "interfaces": ["signature", "method", "constructor", "field"] }' interface Foo { @@ -737,6 +755,9 @@ interface Foo { } ``` + + + ### Type Literals This config only specifies an order for type literals: signatures, then methods, then constructors, then fields. @@ -759,9 +780,8 @@ These member types are the only ones allowed for `typeLiterals`. } ``` - - -#### ❌ Incorrect + + ```ts option='{ "typeLiterals": ["signature", "method", "constructor", "field"] }' type Foo = { @@ -775,7 +795,8 @@ type Foo = { }; ``` -#### βœ… Correct + + ```ts option='{ "typeLiterals": ["signature", "method", "constructor", "field"] }' type Foo = { @@ -789,11 +810,13 @@ type Foo = { }; ``` + + + ### Sorting Options #### Sorting Alphabetically Within Member Groups -This config specifies that within each `memberTypes` group, members are in an alphabetic case-sensitive order. The default member order will be applied if `memberTypes` is not specified. You can see the default order in [Default Configuration](#default-configuration). @@ -813,9 +836,8 @@ You can see the default order in [Default Configuration](#default-configuration) } ``` - - -##### ❌ Incorrect + + ```ts option='{"default":{"order":"alphabetically"}}' interface Foo { @@ -829,7 +851,8 @@ interface Foo { } ``` -##### βœ… Correct + + ```ts option='{"default":{"order":"alphabetically"}}' interface Foo { @@ -843,6 +866,9 @@ interface Foo { } ``` + + + #### Sorting Alphabetically Within Custom Member Groups This config specifies that within each custom `memberTypes` group, members are in an alphabetic case-sensitive order. @@ -864,9 +890,8 @@ This config specifies that within each custom `memberTypes` group, members are i } ``` - - -##### ❌ Incorrect + + ```ts option='{"default":{"memberTypes":["method","field"],"order":"alphabetically"}}' interface Foo { @@ -880,7 +905,8 @@ interface Foo { } ``` -##### βœ… Correct + + ```ts option='{"default":{"memberTypes":["method","field"],"order":"alphabetically"}}' interface Foo { @@ -894,9 +920,11 @@ interface Foo { } ``` + + + #### Sorting Alphabetically Case Insensitive Within Member Groups -This config specifies that within each `memberTypes` group, members are in an alphabetic case-insensitive order. The default member order will be applied if `memberTypes` is not specified. You can see the default order in [Default Configuration](#default-configuration). @@ -916,9 +944,8 @@ You can see the default order in [Default Configuration](#default-configuration) } ``` - - -##### ❌ Incorrect + + ```ts option='{"default":{"order":"alphabetically-case-insensitive"}}' interface Foo { @@ -932,7 +959,8 @@ interface Foo { } ``` -##### βœ… Correct + + ```ts option='{"default":{"order":"alphabetically-case-insensitive"}}' interface Foo { @@ -946,6 +974,9 @@ interface Foo { } ``` + + + #### Sorting Alphabetically Ignoring Member Groups This config specifies that members are all sorted in an alphabetic case-sensitive order. @@ -963,9 +994,8 @@ It ignores any member group types completely by specifying `"never"` for `member } ``` - - -##### ❌ Incorrect + + ```ts option='{ "default": { "memberTypes": "never", "order": "alphabetically" } }' interface Foo { @@ -978,7 +1008,8 @@ interface Foo { } ``` -##### βœ… Correct + + ```ts option='{ "default": { "memberTypes": "never", "order": "alphabetically" } }' interface Foo { @@ -991,6 +1022,9 @@ interface Foo { } ``` + + + #### Sorting Optional Members First or Last The `optionalityOrder` option may be enabled to place all optional members in a group at the beginning or end of that group. @@ -1014,9 +1048,8 @@ This config places all optional members before all required members: } ``` - - -##### ❌ Incorrect + + ```ts option='{ "default": { "optionalityOrder": "optional-first", "order": "alphabetically" } }' interface Foo { @@ -1026,7 +1059,8 @@ interface Foo { } ``` -##### βœ… Correct + + ```ts option='{ "default": { "optionalityOrder": "optional-first", "order": "alphabetically" } }' interface Foo { @@ -1036,7 +1070,8 @@ interface Foo { } ``` - + + This config places all required members before all optional members: @@ -1057,9 +1092,8 @@ This config places all required members before all optional members: } ``` - - -##### ❌ Incorrect + + ```ts option='{ "default": { "optionalityOrder": "required-first", "order": "alphabetically" } }' interface Foo { @@ -1069,7 +1103,8 @@ interface Foo { } ``` -##### βœ… Correct + + ```ts option='{ "default": { "optionalityOrder": "required-first", "order": "alphabetically" } }' interface Foo { @@ -1079,7 +1114,8 @@ interface Foo { } ``` - + + ## All Supported Options diff --git a/packages/eslint-plugin/docs/rules/method-signature-style.md b/packages/eslint-plugin/docs/rules/method-signature-style.mdx similarity index 90% rename from packages/eslint-plugin/docs/rules/method-signature-style.md rename to packages/eslint-plugin/docs/rules/method-signature-style.mdx index b834d383d649..0536bff803a5 100644 --- a/packages/eslint-plugin/docs/rules/method-signature-style.md +++ b/packages/eslint-plugin/docs/rules/method-signature-style.mdx @@ -2,6 +2,9 @@ description: 'Enforce using a particular method signature syntax.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/method-signature-style** for documentation. @@ -42,9 +45,8 @@ The default is `"property"`. Examples of code with `property` option. - - -#### ❌ Incorrect + + ```ts option='"property"' interface T1 { @@ -60,7 +62,8 @@ interface T3 { } ``` -#### βœ… Correct + + ```ts option='"property"' interface T1 { @@ -77,13 +80,15 @@ interface T3 { } ``` + + + ### `method` Examples of code with `method` option. - - -#### ❌ Incorrect + + ```ts option='"method"' interface T1 { @@ -94,7 +99,8 @@ type T2 = { }; ``` -#### βœ… Correct + + ```ts option='"method"' interface T1 { @@ -105,6 +111,9 @@ type T2 = { }; ``` + + + ## When Not To Use It If you don't want to enforce a particular style for object/interface function types, and/or if you don't use `strictFunctionTypes`, then you don't need this rule. diff --git a/packages/eslint-plugin/docs/rules/naming-convention.md b/packages/eslint-plugin/docs/rules/naming-convention.mdx similarity index 99% rename from packages/eslint-plugin/docs/rules/naming-convention.md rename to packages/eslint-plugin/docs/rules/naming-convention.mdx index f1d143e5b033..35d750e5dae0 100644 --- a/packages/eslint-plugin/docs/rules/naming-convention.md +++ b/packages/eslint-plugin/docs/rules/naming-convention.mdx @@ -2,6 +2,9 @@ description: 'Enforce naming conventions for everything across a codebase.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/naming-convention** for documentation. diff --git a/packages/eslint-plugin/docs/rules/no-array-constructor.md b/packages/eslint-plugin/docs/rules/no-array-constructor.mdx similarity index 77% rename from packages/eslint-plugin/docs/rules/no-array-constructor.md rename to packages/eslint-plugin/docs/rules/no-array-constructor.mdx index a1c0c197d8d7..d234918631b5 100644 --- a/packages/eslint-plugin/docs/rules/no-array-constructor.md +++ b/packages/eslint-plugin/docs/rules/no-array-constructor.mdx @@ -2,6 +2,9 @@ description: 'Disallow generic `Array` constructors.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-array-constructor** for documentation. @@ -9,16 +12,16 @@ description: 'Disallow generic `Array` constructors.' This rule extends the base [`eslint/no-array-constructor`](https://eslint.org/docs/rules/no-array-constructor) rule. It adds support for the generically typed `Array` constructor (`new Array()`). - - -### ❌ Incorrect + + ```ts Array(0, 1, 2); new Array(0, 1, 2); ``` -### βœ… Correct + + ```ts Array(0, 1, 2); @@ -27,3 +30,6 @@ new Array(x, y, z); Array(500); new Array(someOtherArray.length); ``` + + + diff --git a/packages/eslint-plugin/docs/rules/no-array-delete.md b/packages/eslint-plugin/docs/rules/no-array-delete.mdx similarity index 86% rename from packages/eslint-plugin/docs/rules/no-array-delete.md rename to packages/eslint-plugin/docs/rules/no-array-delete.mdx index 3abbbc6ececc..38e170fc5224 100644 --- a/packages/eslint-plugin/docs/rules/no-array-delete.md +++ b/packages/eslint-plugin/docs/rules/no-array-delete.mdx @@ -2,6 +2,9 @@ description: 'Disallow using the `delete` operator on array values.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-array-delete** for documentation. @@ -15,9 +18,8 @@ the recommended way to remove an element from an array is by using the ## Examples - - -### ❌ Incorrect + + ```ts declare const arr: number[]; @@ -25,7 +27,8 @@ declare const arr: number[]; delete arr[0]; ``` -### βœ… Correct + + ```ts declare const arr: number[]; @@ -33,7 +36,8 @@ declare const arr: number[]; arr.splice(0, 1); ``` - + + ## When Not To Use It diff --git a/packages/eslint-plugin/docs/rules/no-base-to-string.md b/packages/eslint-plugin/docs/rules/no-base-to-string.mdx similarity index 90% rename from packages/eslint-plugin/docs/rules/no-base-to-string.md rename to packages/eslint-plugin/docs/rules/no-base-to-string.mdx index 470e49121c3a..5e6b531e4384 100644 --- a/packages/eslint-plugin/docs/rules/no-base-to-string.md +++ b/packages/eslint-plugin/docs/rules/no-base-to-string.mdx @@ -2,6 +2,9 @@ description: 'Require `.toString()` to only be called on objects which provide useful information when stringified.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-base-to-string** for documentation. @@ -15,9 +18,8 @@ This rule reports on stringified values that aren't primitives and don't define ## Examples - - -### ❌ Incorrect + + ```ts // Passing an object or class instance to string concatenation: @@ -32,7 +34,8 @@ value + ''; ({}).toString(); ``` -### βœ… Correct + + ```ts // These types all have useful .toString()s @@ -56,6 +59,9 @@ const literalWithToString = { `Value: ${literalWithToString}`; ``` + + + ## Options ### `ignoredTypeNames` @@ -80,8 +86,8 @@ If you don't mind a risk of `"[object Object]"` or incorrect type coercions in y ## Related To -- [`restrict-plus-operands`](./restrict-plus-operands.md) -- [`restrict-template-expressions`](./restrict-template-expressions.md) +- [`restrict-plus-operands`](./restrict-plus-operands.mdx) +- [`restrict-template-expressions`](./restrict-template-expressions.mdx) ## Further Reading diff --git a/packages/eslint-plugin/docs/rules/no-confusing-non-null-assertion.md b/packages/eslint-plugin/docs/rules/no-confusing-non-null-assertion.mdx similarity index 87% rename from packages/eslint-plugin/docs/rules/no-confusing-non-null-assertion.md rename to packages/eslint-plugin/docs/rules/no-confusing-non-null-assertion.mdx index 34d474cf853c..0ff48e14c926 100644 --- a/packages/eslint-plugin/docs/rules/no-confusing-non-null-assertion.md +++ b/packages/eslint-plugin/docs/rules/no-confusing-non-null-assertion.mdx @@ -2,6 +2,9 @@ description: 'Disallow non-null assertion in locations that may be confusing.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-confusing-non-null-assertion** for documentation. @@ -18,9 +21,8 @@ This rule flags confusing `!` assertions and suggests either removing them or wr ## Examples - - -### ❌ Incorrect + + ```ts interface Foo { @@ -33,9 +35,10 @@ const isEqualsBar = foo.bar! == 'hello'; const isEqualsNum = 1 + foo.num! == 2; ``` -### βœ… Correct + + - +{/* prettier-ignore */} ```ts interface Foo { bar?: string; @@ -47,6 +50,9 @@ const isEqualsBar = foo.bar == 'hello'; const isEqualsNum = (1 + foo.num!) == 2; ``` + + + ## When Not To Use It If you don't care about this confusion, then you will not need this rule. diff --git a/packages/eslint-plugin/docs/rules/no-confusing-void-expression.md b/packages/eslint-plugin/docs/rules/no-confusing-void-expression.mdx similarity index 95% rename from packages/eslint-plugin/docs/rules/no-confusing-void-expression.md rename to packages/eslint-plugin/docs/rules/no-confusing-void-expression.mdx index ef2b51b64724..043b07521c6b 100644 --- a/packages/eslint-plugin/docs/rules/no-confusing-void-expression.md +++ b/packages/eslint-plugin/docs/rules/no-confusing-void-expression.mdx @@ -2,6 +2,9 @@ description: 'Require expressions of type void to appear in statement position.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-confusing-void-expression** for documentation. @@ -14,9 +17,8 @@ This rule prevents `void` type expressions from being used in misleading locatio ## Examples - - -### ❌ Incorrect + + ```ts // somebody forgot that `alert` doesn't return anything @@ -36,7 +38,8 @@ function doSomething() { } ``` -### βœ… Correct + + ```ts // just a regular void function in a statement position @@ -67,6 +70,9 @@ cond || console.error('false'); cond ? console.log('true') : console.error('false'); ``` + + + ## Options ### `ignoreArrowShorthand` diff --git a/packages/eslint-plugin/docs/rules/no-dupe-class-members.md b/packages/eslint-plugin/docs/rules/no-dupe-class-members.mdx similarity index 75% rename from packages/eslint-plugin/docs/rules/no-dupe-class-members.md rename to packages/eslint-plugin/docs/rules/no-dupe-class-members.mdx index cc67ddad93a7..804479b20a97 100644 --- a/packages/eslint-plugin/docs/rules/no-dupe-class-members.md +++ b/packages/eslint-plugin/docs/rules/no-dupe-class-members.mdx @@ -2,11 +2,14 @@ description: 'Disallow duplicate class members.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-dupe-class-members** for documentation. -import TypeScriptOverlap from "@site/src/components/TypeScriptOverlap"; +import TypeScriptOverlap from '@site/src/components/TypeScriptOverlap'; diff --git a/packages/eslint-plugin/docs/rules/no-duplicate-enum-values.md b/packages/eslint-plugin/docs/rules/no-duplicate-enum-values.mdx similarity index 89% rename from packages/eslint-plugin/docs/rules/no-duplicate-enum-values.md rename to packages/eslint-plugin/docs/rules/no-duplicate-enum-values.mdx index 4e5488cdcba9..65162a62a6dc 100644 --- a/packages/eslint-plugin/docs/rules/no-duplicate-enum-values.md +++ b/packages/eslint-plugin/docs/rules/no-duplicate-enum-values.mdx @@ -2,6 +2,9 @@ description: 'Disallow duplicate enum member values.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-duplicate-enum-values** for documentation. @@ -15,9 +18,8 @@ This rule disallows defining an enum with multiple members initialized to the sa > This rule only enforces on enum members initialized with string or number literals. > Members without an initializer or initialized with an expression are not checked by this rule. - - -### ❌ Incorrect + + ```ts enum E { @@ -33,7 +35,8 @@ enum E { } ``` -### βœ… Correct + + ```ts enum E { @@ -49,6 +52,9 @@ enum E { } ``` + + + ## When Not To Use It It can sometimes be useful to include duplicate enum members for very specific use cases. diff --git a/packages/eslint-plugin/docs/rules/no-duplicate-imports.md b/packages/eslint-plugin/docs/rules/no-duplicate-imports.mdx similarity index 92% rename from packages/eslint-plugin/docs/rules/no-duplicate-imports.md rename to packages/eslint-plugin/docs/rules/no-duplicate-imports.mdx index ae1d957d57c0..1953792c8818 100644 --- a/packages/eslint-plugin/docs/rules/no-duplicate-imports.md +++ b/packages/eslint-plugin/docs/rules/no-duplicate-imports.mdx @@ -1,6 +1,6 @@ :::danger Deprecated -This rule has been deprecated in favour of the [`import/no-duplicates`](https://github.com/import-js/eslint-plugin-import/blob/HEAD/docs/rules/no-duplicates.md) rule. +This rule has been deprecated in favour of the [`import/no-duplicates`](https://github.com/import-js/eslint-plugin-import/blob/HEAD/docs/rules/no-duplicates.mdx) rule. ::: diff --git a/packages/eslint-plugin/docs/rules/no-duplicate-type-constituents.md b/packages/eslint-plugin/docs/rules/no-duplicate-type-constituents.mdx similarity index 92% rename from packages/eslint-plugin/docs/rules/no-duplicate-type-constituents.md rename to packages/eslint-plugin/docs/rules/no-duplicate-type-constituents.mdx index 744537d0900e..754d59087ddf 100644 --- a/packages/eslint-plugin/docs/rules/no-duplicate-type-constituents.md +++ b/packages/eslint-plugin/docs/rules/no-duplicate-type-constituents.mdx @@ -2,6 +2,9 @@ description: 'Disallow duplicate constituents of union or intersection types.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-duplicate-type-constituents** for documentation. @@ -14,9 +17,8 @@ This rule disallows duplicate union or intersection constituents. We consider types to be duplicate if they evaluate to the same result in the type system. For example, given `type A = string` and `type T = string | A`, this rule would flag that `A` is the same type as `string`. - - -### ❌ Incorrect + + ```ts type T1 = 'A' | 'A'; @@ -32,7 +34,8 @@ type StringB = string; type T5 = StringA | StringB; ``` -### βœ… Correct + + ```ts type T1 = 'A' | 'B'; @@ -48,6 +51,9 @@ type NumberB = number; type T5 = StringA | NumberB; ``` + + + ## Options ### `ignoreIntersections` diff --git a/packages/eslint-plugin/docs/rules/no-dynamic-delete.md b/packages/eslint-plugin/docs/rules/no-dynamic-delete.mdx similarity index 90% rename from packages/eslint-plugin/docs/rules/no-dynamic-delete.md rename to packages/eslint-plugin/docs/rules/no-dynamic-delete.mdx index 1fd8f9123455..65542c903357 100644 --- a/packages/eslint-plugin/docs/rules/no-dynamic-delete.md +++ b/packages/eslint-plugin/docs/rules/no-dynamic-delete.mdx @@ -2,6 +2,9 @@ description: 'Disallow using the `delete` operator on computed key expressions.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-dynamic-delete** for documentation. @@ -14,9 +17,8 @@ Using `Object`s with added and removed keys can cause occasional edge case bugs, ## Examples - - -### ❌ Incorrect + + ```ts // Can be replaced with the constant equivalents, such as container.aaa @@ -29,7 +31,8 @@ delete container[name]; delete container[name.toUpperCase()]; ``` -### βœ… Correct + + ```ts const container: { [i: string]: number } = { @@ -44,6 +47,9 @@ delete container[7]; delete container['-Infinity']; ``` + + + ## When Not To Use It When you know your keys are safe to delete, this rule can be unnecessary. diff --git a/packages/eslint-plugin/docs/rules/no-empty-function.md b/packages/eslint-plugin/docs/rules/no-empty-function.mdx similarity index 97% rename from packages/eslint-plugin/docs/rules/no-empty-function.md rename to packages/eslint-plugin/docs/rules/no-empty-function.mdx index 30e8109b05b6..9da88ae0580f 100644 --- a/packages/eslint-plugin/docs/rules/no-empty-function.md +++ b/packages/eslint-plugin/docs/rules/no-empty-function.mdx @@ -2,6 +2,9 @@ description: 'Disallow empty functions.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-empty-function** for documentation. diff --git a/packages/eslint-plugin/docs/rules/no-empty-interface.md b/packages/eslint-plugin/docs/rules/no-empty-interface.mdx similarity index 88% rename from packages/eslint-plugin/docs/rules/no-empty-interface.md rename to packages/eslint-plugin/docs/rules/no-empty-interface.mdx index f665acd4ae9a..ad240237ddb8 100644 --- a/packages/eslint-plugin/docs/rules/no-empty-interface.md +++ b/packages/eslint-plugin/docs/rules/no-empty-interface.mdx @@ -2,6 +2,9 @@ description: 'Disallow the declaration of empty interfaces.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-empty-interface** for documentation. @@ -13,9 +16,8 @@ This rule aims to ensure that only meaningful interfaces are declared in the cod ## Examples - - -### ❌ Incorrect + + ```ts // an empty interface @@ -28,7 +30,8 @@ interface Bar extends Foo {} interface Baz {} ``` -### βœ… Correct + + ```ts // an interface with any number of members @@ -46,7 +49,8 @@ interface Bar { interface Baz extends Foo, Bar {} ``` - + + ## Options diff --git a/packages/eslint-plugin/docs/rules/no-explicit-any.md b/packages/eslint-plugin/docs/rules/no-explicit-any.mdx similarity index 90% rename from packages/eslint-plugin/docs/rules/no-explicit-any.md rename to packages/eslint-plugin/docs/rules/no-explicit-any.mdx index 5b6ddf7d5327..52f7108d165b 100644 --- a/packages/eslint-plugin/docs/rules/no-explicit-any.md +++ b/packages/eslint-plugin/docs/rules/no-explicit-any.mdx @@ -2,6 +2,9 @@ description: 'Disallow the `any` type.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-explicit-any** for documentation. @@ -19,9 +22,8 @@ Preferable alternatives to `any` include: ## Examples - - -### ❌ Incorrect + + ```ts const age: any = 'seventeen'; @@ -59,7 +61,8 @@ function greet(param: Array): string {} function greet(param: Array): Array {} ``` -### βœ… Correct + + ```ts const age: number = 17; @@ -97,6 +100,9 @@ function greet(param: Array): string {} function greet(param: Array): Array {} ``` + + + ## Options ### `fixToUnknown` @@ -154,11 +160,11 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates ## Related To -- [`no-unsafe-argument`](./no-unsafe-argument.md) -- [`no-unsafe-assignment`](./no-unsafe-assignment.md) -- [`no-unsafe-call`](./no-unsafe-call.md) -- [`no-unsafe-member-access`](./no-unsafe-member-access.md) -- [`no-unsafe-return`](./no-unsafe-return.md) +- [`no-unsafe-argument`](./no-unsafe-argument.mdx) +- [`no-unsafe-assignment`](./no-unsafe-assignment.mdx) +- [`no-unsafe-call`](./no-unsafe-call.mdx) +- [`no-unsafe-member-access`](./no-unsafe-member-access.mdx) +- [`no-unsafe-return`](./no-unsafe-return.mdx) ## Further Reading diff --git a/packages/eslint-plugin/docs/rules/no-extra-non-null-assertion.md b/packages/eslint-plugin/docs/rules/no-extra-non-null-assertion.mdx similarity index 80% rename from packages/eslint-plugin/docs/rules/no-extra-non-null-assertion.md rename to packages/eslint-plugin/docs/rules/no-extra-non-null-assertion.mdx index 5e71dc011a70..5f108b7cd3ec 100644 --- a/packages/eslint-plugin/docs/rules/no-extra-non-null-assertion.md +++ b/packages/eslint-plugin/docs/rules/no-extra-non-null-assertion.mdx @@ -2,6 +2,9 @@ description: 'Disallow extra non-null assertions.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-extra-non-null-assertion** for documentation. @@ -11,9 +14,8 @@ Using the operator any more than once on a single value does nothing. ## Examples - - -### ❌ Incorrect + + ```ts const foo: { bar: number } | null = null; @@ -32,7 +34,8 @@ function foo(bar?: { n: number }) { } ``` -### βœ… Correct + + ```ts const foo: { bar: number } | null = null; @@ -51,4 +54,7 @@ function foo(bar?: { n: number }) { } ``` - + + + +{/* Intentionally Omitted: When Not To Use It */} diff --git a/packages/eslint-plugin/docs/rules/no-extra-parens.md b/packages/eslint-plugin/docs/rules/no-extra-parens.mdx similarity index 84% rename from packages/eslint-plugin/docs/rules/no-extra-parens.md rename to packages/eslint-plugin/docs/rules/no-extra-parens.mdx index e0afc65731c8..4c56e6ecd279 100644 --- a/packages/eslint-plugin/docs/rules/no-extra-parens.md +++ b/packages/eslint-plugin/docs/rules/no-extra-parens.mdx @@ -2,6 +2,9 @@ description: 'Disallow unnecessary parentheses.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-extra-parens** for documentation. diff --git a/packages/eslint-plugin/docs/rules/no-extra-semi.md b/packages/eslint-plugin/docs/rules/no-extra-semi.mdx similarity index 93% rename from packages/eslint-plugin/docs/rules/no-extra-semi.md rename to packages/eslint-plugin/docs/rules/no-extra-semi.mdx index b20f22d949d2..ec23c28eca8d 100644 --- a/packages/eslint-plugin/docs/rules/no-extra-semi.md +++ b/packages/eslint-plugin/docs/rules/no-extra-semi.mdx @@ -2,6 +2,9 @@ description: 'Disallow unnecessary semicolons.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-extra-semi** for documentation. diff --git a/packages/eslint-plugin/docs/rules/no-extraneous-class.md b/packages/eslint-plugin/docs/rules/no-extraneous-class.mdx similarity index 87% rename from packages/eslint-plugin/docs/rules/no-extraneous-class.md rename to packages/eslint-plugin/docs/rules/no-extraneous-class.mdx index 451b01ae4a5d..a457ccf962b0 100644 --- a/packages/eslint-plugin/docs/rules/no-extraneous-class.md +++ b/packages/eslint-plugin/docs/rules/no-extraneous-class.mdx @@ -2,6 +2,9 @@ description: 'Disallow classes used as namespaces.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-extraneous-class** for documentation. @@ -22,9 +25,8 @@ Those classes can generally be replaced with a standalone function. ## Examples - - -### ❌ Incorrect + + ```ts class StaticConstants { @@ -42,7 +44,8 @@ class HelloWorldLogger { } ``` -### βœ… Correct + + ```ts export const version = 42; @@ -56,15 +59,17 @@ function logHelloWorld() { } ``` + + + ## Alternatives ### Individual Exports (Recommended) Instead of using a static utility class we recommend you individually export the utilities from your module. - - -#### ❌ Incorrect + + ```ts export class Utilities { @@ -82,7 +87,8 @@ export class Utilities { } ``` -#### βœ… Correct + + ```ts export function util1() { @@ -98,6 +104,9 @@ export function util3() { } ``` + + + ### Namespace Imports (Not Recommended) If you strongly prefer to have all constructs from a module available as properties of a single object, you can `import * as` the module. @@ -109,9 +118,8 @@ However, namespace imports are impacted by these downsides: - They also don't play as well with tree shaking in modern bundlers - They require a name prefix before each property's usage - - -#### ❌ Incorrect + + ```ts // utilities.ts @@ -127,7 +135,8 @@ import { Utilities } from './utilities'; Utilities.sayHello(); ``` -#### ⚠️ Namespace Imports + + ```ts // utilities.ts @@ -141,7 +150,8 @@ import * as utilities from './utilities'; utilities.sayHello(); ``` -#### βœ… Standalone Imports + + ```ts // utilities.ts @@ -155,6 +165,9 @@ import { sayHello } from './utilities'; sayHello(); ``` + + + ### Notes on Mutating Variables One case you need to be careful of is exporting mutable variables. @@ -164,9 +177,8 @@ This means that importers can only ever read the first value they are assigned a Needing to write to an exported variable is very rare and is generally considered a code smell. If you do need it you can accomplish it using getter and setter functions: - - -#### ❌ Incorrect + + ```ts export class Utilities { @@ -178,7 +190,8 @@ export class Utilities { } ``` -#### βœ… Correct + + ```ts let mutableCount = 1; @@ -192,6 +205,9 @@ export function incrementCount() { } ``` + + + ## Options This rule normally bans classes that are empty (have no constructor or fields). @@ -201,15 +217,15 @@ The rule's options each add an exemption for a specific type of class. `allowConstructorOnly` adds an exemption for classes that have only a constructor and no fields. - - -#### ❌ Incorrect + + ```ts option='{ "allowConstructorOnly": true }' class NoFields {} ``` -#### βœ… Correct + + ```ts option='{ "allowConstructorOnly": true }' class NoFields { @@ -219,13 +235,15 @@ class NoFields { } ``` + + + ### `allowEmpty` The `allowEmpty` option adds an exemption for classes that are entirely empty. - - -#### ❌ Incorrect + + ```ts option='{ "allowEmpty": true }' class NoFields { @@ -235,12 +253,16 @@ class NoFields { } ``` -#### βœ… Correct + + ```ts option='{ "allowEmpty": true }' class NoFields {} ``` + + + ### `allowStaticOnly` The `allowStaticOnly` option adds an exemption for classes that only contain static members. @@ -250,15 +272,15 @@ We strongly recommend against the `allowStaticOnly` exemption. It works against this rule's primary purpose of discouraging classes used only for static members. ::: - - -#### ❌ Incorrect + + ```ts option='{ "allowStaticOnly": true }' class EmptyClass {} ``` -#### βœ… Correct + + ```ts option='{ "allowStaticOnly": true }' class NotEmptyClass { @@ -266,13 +288,15 @@ class NotEmptyClass { } ``` + + + ### `allowWithDecorator` The `allowWithDecorator` option adds an exemption for classes that contain a member decorated with a `@` decorator. - - -#### ❌ Incorrect + + ```ts option='{ "allowWithDecorator": true }' class Constants { @@ -280,7 +304,8 @@ class Constants { } ``` -#### βœ… Correct + + ```ts option='{ "allowWithDecorator": true }' class Constants { @@ -289,6 +314,9 @@ class Constants { } ``` + + + ## When Not To Use It If your project was set up before modern class and namespace practices, and you don't have the time to switch over, you might not be practically able to use this rule. diff --git a/packages/eslint-plugin/docs/rules/no-floating-promises.md b/packages/eslint-plugin/docs/rules/no-floating-promises.mdx similarity index 91% rename from packages/eslint-plugin/docs/rules/no-floating-promises.md rename to packages/eslint-plugin/docs/rules/no-floating-promises.mdx index da46d7f98d18..939eccae62f7 100644 --- a/packages/eslint-plugin/docs/rules/no-floating-promises.md +++ b/packages/eslint-plugin/docs/rules/no-floating-promises.mdx @@ -2,6 +2,9 @@ description: 'Require Promise-like statements to be handled appropriately.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-floating-promises** for documentation. @@ -26,14 +29,13 @@ This rule also reports when an Array containing Promises is created and not prop :::tip `no-floating-promises` only detects unhandled Promise _statements_. -See [`no-misused-promises`](./no-misused-promises.md) for detecting code that provides Promises to _logical_ locations such as if statements. +See [`no-misused-promises`](./no-misused-promises.mdx) for detecting code that provides Promises to _logical_ locations such as if statements. ::: ## Examples - - -### ❌ Incorrect + + ```ts const promise = new Promise((resolve, reject) => resolve('value')); @@ -51,7 +53,8 @@ Promise.reject('value').finally(); [1, 2, 3].map(async x => x + 1); ``` -### βœ… Correct + + ```ts const promise = new Promise((resolve, reject) => resolve('value')); @@ -72,6 +75,9 @@ await Promise.reject('value').finally(() => {}); await Promise.all([1, 2, 3].map(async x => x + 1)); ``` + + + ## Options ### `ignoreVoid` @@ -117,7 +123,7 @@ You might consider using `void`s and/or [ESLint disable comments](https://eslint ## Related To -- [`no-misused-promises`](./no-misused-promises.md) +- [`no-misused-promises`](./no-misused-promises.mdx) ## Further Reading diff --git a/packages/eslint-plugin/docs/rules/no-for-in-array.md b/packages/eslint-plugin/docs/rules/no-for-in-array.mdx similarity index 92% rename from packages/eslint-plugin/docs/rules/no-for-in-array.md rename to packages/eslint-plugin/docs/rules/no-for-in-array.mdx index 4ba6003f7669..d3a1b01daa4b 100644 --- a/packages/eslint-plugin/docs/rules/no-for-in-array.md +++ b/packages/eslint-plugin/docs/rules/no-for-in-array.mdx @@ -2,6 +2,9 @@ description: 'Disallow iterating over an array with a for-in loop.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-for-in-array** for documentation. @@ -17,9 +20,8 @@ You may have confused for-in with for-of, which iterates over the elements of th ## Examples - - -### ❌ Incorrect + + ```ts declare const array: string[]; @@ -33,7 +35,8 @@ for (const i in array) { } ``` -### βœ… Correct + + ```ts declare const array: string[]; @@ -55,6 +58,9 @@ for (const [i, value] of array.entries()) { } ``` + + + ## When Not To Use It If your project is a rare one that intentionally loops over string indices of arrays, you can turn off this rule. diff --git a/packages/eslint-plugin/docs/rules/no-implied-eval.md b/packages/eslint-plugin/docs/rules/no-implied-eval.mdx similarity index 94% rename from packages/eslint-plugin/docs/rules/no-implied-eval.md rename to packages/eslint-plugin/docs/rules/no-implied-eval.mdx index 10f787d6d43f..8e7050d200b4 100644 --- a/packages/eslint-plugin/docs/rules/no-implied-eval.md +++ b/packages/eslint-plugin/docs/rules/no-implied-eval.mdx @@ -2,6 +2,9 @@ description: 'Disallow the use of `eval()`-like methods.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-implied-eval** for documentation. @@ -29,9 +32,8 @@ The best practice is to avoid using `new Function()` or `execScript()` and alway This rule aims to eliminate implied `eval()` through the use of `new Function()`, `setTimeout()`, `setInterval()`, `setImmediate()` or `execScript()`. - - -### ❌ Incorrect + + ```ts /* eslint @typescript-eslint/no-implied-eval: "error" */ @@ -59,7 +61,8 @@ setTimeout(fn(), 100); const fn = new Function('a', 'b', 'return a + b'); ``` -### βœ… Correct + + ```ts /* eslint @typescript-eslint/no-implied-eval: "error" */ @@ -96,6 +99,9 @@ class Foo { setTimeout(Foo.fn, 100); ``` + + + ## When Not To Use It If your project is a rare one that needs to allow `new Function()` or `setTimeout()`, `setInterval()`, `setImmediate()` and `execScript()` with string arguments, then you can disable this rule. diff --git a/packages/eslint-plugin/docs/rules/no-import-type-side-effects.md b/packages/eslint-plugin/docs/rules/no-import-type-side-effects.mdx similarity index 90% rename from packages/eslint-plugin/docs/rules/no-import-type-side-effects.md rename to packages/eslint-plugin/docs/rules/no-import-type-side-effects.mdx index fedd08cb19f0..6d0d39a3cd37 100644 --- a/packages/eslint-plugin/docs/rules/no-import-type-side-effects.md +++ b/packages/eslint-plugin/docs/rules/no-import-type-side-effects.mdx @@ -2,6 +2,9 @@ description: 'Enforce the use of top-level import type qualifier when an import only has specifiers with inline type qualifiers.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-import-type-side-effects** for documentation. @@ -27,9 +30,8 @@ For the rare case of needing to import for side effects, this may be desirable - This rule enforces that you use a top-level `type` qualifier for imports when it only imports specifiers with an inline `type` qualifier - - -### ❌ Incorrect + + ```ts import { type A } from 'mod'; @@ -38,7 +40,8 @@ import { type A, type B } from 'mod'; import { type A as AA, type B as BB } from 'mod'; ``` -### βœ… Correct + + ```ts import type { A } from 'mod'; @@ -63,12 +66,15 @@ import type T, { U } from 'mod'; import T, { type U } from 'mod'; ``` + + + ## When Not To Use It If you're not using TypeScript 5.0's `verbatimModuleSyntax` option and your project is built with a bundler that manages import side effects for you, this rule may not be as useful for you. ## Related To -- [`consistent-type-imports`](./consistent-type-imports.md) -- [`import/consistent-type-specifier-style`](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/consistent-type-specifier-style.md) +- [`consistent-type-imports`](./consistent-type-imports.mdx) +- [`import/consistent-type-specifier-style`](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/consistent-type-specifier-style.mdx) - [`import/no-duplicates` with `{"prefer-inline": true}`](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-duplicates.md#inline-type-imports) diff --git a/packages/eslint-plugin/docs/rules/no-inferrable-types.md b/packages/eslint-plugin/docs/rules/no-inferrable-types.mdx similarity index 93% rename from packages/eslint-plugin/docs/rules/no-inferrable-types.md rename to packages/eslint-plugin/docs/rules/no-inferrable-types.mdx index 235ffb80b5ba..ff21a3538a35 100644 --- a/packages/eslint-plugin/docs/rules/no-inferrable-types.md +++ b/packages/eslint-plugin/docs/rules/no-inferrable-types.mdx @@ -2,6 +2,9 @@ description: 'Disallow explicit type declarations for variables or parameters initialized to a number, string, or boolean.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-inferrable-types** for documentation. @@ -12,9 +15,8 @@ Doing so adds unnecessary verbosity to code -making it harder to read- and in so ## Examples - - -### ❌ Incorrect + + ```ts const a: bigint = 10n; @@ -42,7 +44,8 @@ class Foo { function fn(a: number = 5, b: boolean = true) {} ``` -### βœ… Correct + + ```ts const a = 10n; @@ -70,7 +73,8 @@ class Foo { function fn(a = 5, b = true) {} ``` - + + ## Options diff --git a/packages/eslint-plugin/docs/rules/no-invalid-this.md b/packages/eslint-plugin/docs/rules/no-invalid-this.mdx similarity index 75% rename from packages/eslint-plugin/docs/rules/no-invalid-this.md rename to packages/eslint-plugin/docs/rules/no-invalid-this.mdx index a9e8bcaeb52f..518b93fcbe26 100644 --- a/packages/eslint-plugin/docs/rules/no-invalid-this.md +++ b/packages/eslint-plugin/docs/rules/no-invalid-this.mdx @@ -2,11 +2,14 @@ description: 'Disallow `this` keywords outside of classes or class-like objects.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-invalid-this** for documentation. -import TypeScriptOverlap from "@site/src/components/TypeScriptOverlap"; +import TypeScriptOverlap from '@site/src/components/TypeScriptOverlap'; diff --git a/packages/eslint-plugin/docs/rules/no-invalid-void-type.md b/packages/eslint-plugin/docs/rules/no-invalid-void-type.mdx similarity index 95% rename from packages/eslint-plugin/docs/rules/no-invalid-void-type.md rename to packages/eslint-plugin/docs/rules/no-invalid-void-type.mdx index 426e878c67d4..d73d09512264 100644 --- a/packages/eslint-plugin/docs/rules/no-invalid-void-type.md +++ b/packages/eslint-plugin/docs/rules/no-invalid-void-type.mdx @@ -2,6 +2,9 @@ description: 'Disallow `void` type outside of generic or return types.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-invalid-void-type** for documentation. @@ -15,9 +18,8 @@ Attempting to use a `void` type outside of a return type or generic type argumen ## Examples - - -### ❌ Incorrect + + ```ts type PossibleValues = string | number | void; @@ -38,7 +40,8 @@ class MyClass { } ``` -### βœ… Correct + + ```ts type NoOp = () => void; @@ -52,6 +55,9 @@ async function promiseMeSomething(): Promise {} type stillVoid = void | never; ``` + + + ## Options ### `allowInGenericTypeArguments` diff --git a/packages/eslint-plugin/docs/rules/no-loop-func.md b/packages/eslint-plugin/docs/rules/no-loop-func.mdx similarity index 85% rename from packages/eslint-plugin/docs/rules/no-loop-func.md rename to packages/eslint-plugin/docs/rules/no-loop-func.mdx index 9060422c9054..0eb9ff68c5c4 100644 --- a/packages/eslint-plugin/docs/rules/no-loop-func.md +++ b/packages/eslint-plugin/docs/rules/no-loop-func.mdx @@ -2,6 +2,9 @@ description: 'Disallow function declarations that contain unsafe references inside loop statements.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-loop-func** for documentation. diff --git a/packages/eslint-plugin/docs/rules/no-loss-of-precision.md b/packages/eslint-plugin/docs/rules/no-loss-of-precision.mdx similarity index 86% rename from packages/eslint-plugin/docs/rules/no-loss-of-precision.md rename to packages/eslint-plugin/docs/rules/no-loss-of-precision.mdx index fb93f3819c49..07fb1f0deb56 100644 --- a/packages/eslint-plugin/docs/rules/no-loss-of-precision.md +++ b/packages/eslint-plugin/docs/rules/no-loss-of-precision.mdx @@ -2,6 +2,9 @@ description: 'Disallow literal numbers that lose precision.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-loss-of-precision** for documentation. diff --git a/packages/eslint-plugin/docs/rules/no-magic-numbers.md b/packages/eslint-plugin/docs/rules/no-magic-numbers.mdx similarity index 98% rename from packages/eslint-plugin/docs/rules/no-magic-numbers.md rename to packages/eslint-plugin/docs/rules/no-magic-numbers.mdx index 211add8302a7..c8a2058791ee 100644 --- a/packages/eslint-plugin/docs/rules/no-magic-numbers.md +++ b/packages/eslint-plugin/docs/rules/no-magic-numbers.mdx @@ -2,6 +2,9 @@ description: 'Disallow magic numbers.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-magic-numbers** for documentation. diff --git a/packages/eslint-plugin/docs/rules/no-meaningless-void-operator.md b/packages/eslint-plugin/docs/rules/no-meaningless-void-operator.mdx similarity index 84% rename from packages/eslint-plugin/docs/rules/no-meaningless-void-operator.md rename to packages/eslint-plugin/docs/rules/no-meaningless-void-operator.mdx index e57606d5ec9d..9df265bc987c 100644 --- a/packages/eslint-plugin/docs/rules/no-meaningless-void-operator.md +++ b/packages/eslint-plugin/docs/rules/no-meaningless-void-operator.mdx @@ -2,13 +2,16 @@ description: 'Disallow the `void` operator except when used to discard a value.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-meaningless-void-operator** for documentation. `void` in TypeScript refers to a function return that is meant to be ignored. The `void` operator is a useful tool to convey the programmer's intent to discard a value. -For example, it is recommended as one way of suppressing [`@typescript-eslint/no-floating-promises`](./no-floating-promises.md) instead of adding `.catch()` to a promise. +For example, it is recommended as one way of suppressing [`@typescript-eslint/no-floating-promises`](./no-floating-promises.mdx) instead of adding `.catch()` to a promise. This rule helps an authors catch API changes where previously a value was being discarded at a call site, but the callee changed so it no longer returns a value. When combined with [no-unused-expressions](https://eslint.org/docs/rules/no-unused-expressions), it also helps _readers_ of the code by ensuring consistency: a statement that looks like `void foo();` is **always** discarding a return value, and a statement that looks like `foo();` is **never** discarding a return value. @@ -16,9 +19,10 @@ This rule reports on any `void` operator whose argument is already of type `void ## Examples - +## Examples -### ❌ Incorrect + + ```ts void (() => {})(); @@ -27,7 +31,8 @@ function foo() {} void foo(); ``` -### βœ… Correct + + ```ts (() => {})(); @@ -42,6 +47,9 @@ function bar(x: number) { void bar(); // discarding a number ``` + + + ## Options ### `checkNever` diff --git a/packages/eslint-plugin/docs/rules/no-misused-new.md b/packages/eslint-plugin/docs/rules/no-misused-new.mdx similarity index 88% rename from packages/eslint-plugin/docs/rules/no-misused-new.md rename to packages/eslint-plugin/docs/rules/no-misused-new.mdx index b3fc829f42d8..e1e02c42a3ea 100644 --- a/packages/eslint-plugin/docs/rules/no-misused-new.md +++ b/packages/eslint-plugin/docs/rules/no-misused-new.mdx @@ -2,6 +2,9 @@ description: 'Enforce valid definition of `new` and `constructor`.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-misused-new** for documentation. @@ -14,9 +17,8 @@ This rule reports when a class defines a method named `new` or an interface defi ## Examples - - -### ❌ Incorrect + + ```ts declare class C { @@ -29,7 +31,8 @@ interface I { } ``` -### βœ… Correct + + ```ts declare class C { @@ -41,6 +44,9 @@ interface I { } ``` + + + ## When Not To Use It If you intentionally want a class with a `new` method, and you're confident nobody working in your code will mistake it with a constructor, you might not want this rule. diff --git a/packages/eslint-plugin/docs/rules/no-misused-promises.md b/packages/eslint-plugin/docs/rules/no-misused-promises.mdx similarity index 91% rename from packages/eslint-plugin/docs/rules/no-misused-promises.md rename to packages/eslint-plugin/docs/rules/no-misused-promises.mdx index fc4ba7e460e3..ff044b712d12 100644 --- a/packages/eslint-plugin/docs/rules/no-misused-promises.md +++ b/packages/eslint-plugin/docs/rules/no-misused-promises.mdx @@ -2,6 +2,9 @@ description: 'Disallow Promises in places not designed to handle them.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-misused-promises** for documentation. @@ -12,7 +15,7 @@ functions are handled/awaited. :::tip `no-misused-promises` only detects code that provides Promises to incorrect _logical_ locations. -See [`no-floating-promises`](./no-floating-promises.md) for detecting unhandled Promise _statements_. +See [`no-floating-promises`](./no-floating-promises.mdx) for detecting unhandled Promise _statements_. ::: ## Options @@ -36,9 +39,10 @@ Doing so prevents the rule from looking at code like `if (somePromise)`. Examples of code for this rule with `checksConditionals: true`: - +## Examples -#### ❌ Incorrect + + ```ts option='{ "checksConditionals": true }' const promise = Promise.resolve('value'); @@ -54,7 +58,8 @@ while (promise) { } ``` -#### βœ… Correct + + ```ts option='{ "checksConditionals": true }' const promise = Promise.resolve('value'); @@ -71,7 +76,8 @@ while (await promise) { } ``` - + + ### `checksVoidReturn` @@ -116,9 +122,8 @@ For example, if you don't mind that passing a `() => Promise` to a `() => Examples of code for this rule with `checksVoidReturn: true`: - - -#### ❌ Incorrect + + ```ts option='{ "checksVoidReturn": true }' [1, 2, 3].forEach(async value => { @@ -138,7 +143,8 @@ eventEmitter.on('some-event', async () => { }); ``` -#### βœ… Correct + + ```ts option='{ "checksVoidReturn": true }' // for-of puts `await` in outer context @@ -180,7 +186,8 @@ eventEmitter.on('some-event', () => { }); ``` - + + ### `checksSpreads` @@ -199,9 +206,8 @@ If you don't want to check object spreads, you can add this configuration: Examples of code for this rule with `checksSpreads: true`: - - -#### ❌ Incorrect + + ```ts option='{ "checksSpreads": true }' const getData = () => someAsyncOperation({ myArg: 'foo' }); @@ -215,7 +221,8 @@ const getData2 = async () => { return { foo: 42, ...getData2() }; ``` -#### βœ… Correct + + ```ts option='{ "checksSpreads": true }' const getData = () => someAsyncOperation({ myArg: 'foo' }); @@ -229,7 +236,8 @@ const getData2 = async () => { return { foo: 42, ...(await getData2()) }; ``` - + + ## When Not To Use It @@ -243,4 +251,4 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates ## Related To -- [`no-floating-promises`](./no-floating-promises.md) +- [`no-floating-promises`](./no-floating-promises.mdx) diff --git a/packages/eslint-plugin/docs/rules/no-mixed-enums.md b/packages/eslint-plugin/docs/rules/no-mixed-enums.mdx similarity index 84% rename from packages/eslint-plugin/docs/rules/no-mixed-enums.md rename to packages/eslint-plugin/docs/rules/no-mixed-enums.mdx index 96ccbddf4e0d..5e16833604d4 100644 --- a/packages/eslint-plugin/docs/rules/no-mixed-enums.md +++ b/packages/eslint-plugin/docs/rules/no-mixed-enums.mdx @@ -2,6 +2,9 @@ description: 'Disallow enums from having both number and string members.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-mixed-enums** for documentation. @@ -12,9 +15,8 @@ Mixing enum member types is generally considered confusing and a bad practice. ## Examples - - -### ❌ Incorrect + + ```ts enum Status { @@ -24,7 +26,8 @@ enum Status { } ``` -### βœ… Correct (Explicit Numbers) + + ```ts enum Status { @@ -34,7 +37,8 @@ enum Status { } ``` -### βœ… Correct (Implicit Numbers) + + ```ts enum Status { @@ -44,7 +48,8 @@ enum Status { } ``` -### βœ… Correct (Strings) + + ```ts enum Status { @@ -54,6 +59,9 @@ enum Status { } ``` + + + ## Iteration Pitfalls of Mixed Enum Member Values Enum values may be iterated over using `Object.entries`/`Object.keys`/`Object.values`. diff --git a/packages/eslint-plugin/docs/rules/no-namespace.md b/packages/eslint-plugin/docs/rules/no-namespace.mdx similarity index 85% rename from packages/eslint-plugin/docs/rules/no-namespace.md rename to packages/eslint-plugin/docs/rules/no-namespace.mdx index c5e77612aa3b..3d4f14fe9151 100644 --- a/packages/eslint-plugin/docs/rules/no-namespace.md +++ b/packages/eslint-plugin/docs/rules/no-namespace.mdx @@ -2,6 +2,9 @@ description: 'Disallow TypeScript namespaces.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-namespace** for documentation. @@ -16,9 +19,8 @@ ES2015 module syntax is now preferred (`import`/`export`). Examples of code with the default options: - - -### ❌ Incorrect + + ```ts module foo {} @@ -28,7 +30,8 @@ declare module foo {} declare namespace foo {} ``` -### βœ… Correct + + ```ts declare module 'foo' {} @@ -36,7 +39,8 @@ declare module 'foo' {} // anything inside a d.ts file ``` - + + ## Options @@ -44,16 +48,16 @@ declare module 'foo' {} Examples of code with the `{ "allowDeclarations": true }` option: - - -#### ❌ Incorrect + + ```ts option='{ "allowDeclarations": true }' module foo {} namespace foo {} ``` -#### βœ… Correct + + ```ts option='{ "allowDeclarations": true }' declare module 'foo' {} @@ -69,13 +73,13 @@ declare module foo { } ``` - + + Examples of code for the `{ "allowDeclarations": false }` option: - - -#### ❌ Incorrect + + ```ts option='{ "allowDeclarations": false }' module foo {} @@ -84,19 +88,22 @@ declare module foo {} declare namespace foo {} ``` -#### βœ… Correct + + ```ts option='{ "allowDeclarations": false }' declare module 'foo' {} ``` + + + ### `allowDefinitionFiles` Examples of code for the `{ "allowDefinitionFiles": true }` option: - - -#### ❌ Incorrect + + ```ts option='{ "allowDefinitionFiles": true }' // if outside a d.ts file @@ -110,7 +117,8 @@ declare module foo {} declare namespace foo {} ``` -#### βœ… Correct + + ```ts option='{ "allowDefinitionFiles": true }' declare module 'foo' {} @@ -118,6 +126,9 @@ declare module 'foo' {} // anything inside a d.ts file ``` + + + ## When Not To Use It If your project was architected before modern modules and namespaces, it may be difficult to migrate off of namespaces. diff --git a/packages/eslint-plugin/docs/rules/no-non-null-asserted-nullish-coalescing.md b/packages/eslint-plugin/docs/rules/no-non-null-asserted-nullish-coalescing.mdx similarity index 90% rename from packages/eslint-plugin/docs/rules/no-non-null-asserted-nullish-coalescing.md rename to packages/eslint-plugin/docs/rules/no-non-null-asserted-nullish-coalescing.mdx index f7ac17e740d4..3c66cdc77eb3 100644 --- a/packages/eslint-plugin/docs/rules/no-non-null-asserted-nullish-coalescing.md +++ b/packages/eslint-plugin/docs/rules/no-non-null-asserted-nullish-coalescing.mdx @@ -2,6 +2,9 @@ description: 'Disallow non-null assertions in the left operand of a nullish coalescing operator.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-non-null-asserted-nullish-coalescing** for documentation. @@ -11,9 +14,8 @@ Using a `!` non-null assertion type operator in the left operand of a nullish co ## Examples - - -### ❌ Incorrect + + ```ts foo! ?? bar; @@ -29,7 +31,8 @@ x = foo(); x! ?? ''; ``` -### βœ… Correct + + ```ts foo ?? bar; @@ -43,6 +46,9 @@ let x: string; x! ?? ''; ``` + + + ## When Not To Use It If your project's types don't yet fully describe whether certain values may be nullable, such as if you're transitioning to `strictNullChecks`, this rule might create many false reports. diff --git a/packages/eslint-plugin/docs/rules/no-non-null-asserted-optional-chain.md b/packages/eslint-plugin/docs/rules/no-non-null-asserted-optional-chain.mdx similarity index 88% rename from packages/eslint-plugin/docs/rules/no-non-null-asserted-optional-chain.md rename to packages/eslint-plugin/docs/rules/no-non-null-asserted-optional-chain.mdx index f758fb2ba7e7..0a46583e7585 100644 --- a/packages/eslint-plugin/docs/rules/no-non-null-asserted-optional-chain.md +++ b/packages/eslint-plugin/docs/rules/no-non-null-asserted-optional-chain.mdx @@ -2,6 +2,9 @@ description: 'Disallow non-null assertions after an optional chain expression.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-non-null-asserted-optional-chain** for documentation. @@ -13,22 +16,25 @@ Using a `!` non-null assertion to assert the result of an `?.` optional chain ex ## Examples - - -### ❌ Incorrect + + ```ts foo?.bar!; foo?.bar()!; ``` -### βœ… Correct + + ```ts foo?.bar; foo?.bar(); ``` + + + ## When Not To Use It If your project's types don't yet fully describe whether certain values may be nullable, such as if you're transitioning to `strictNullChecks`, this rule might create many false reports. diff --git a/packages/eslint-plugin/docs/rules/no-non-null-assertion.md b/packages/eslint-plugin/docs/rules/no-non-null-assertion.mdx similarity index 88% rename from packages/eslint-plugin/docs/rules/no-non-null-assertion.md rename to packages/eslint-plugin/docs/rules/no-non-null-assertion.mdx index 1a676212fd80..b97063319393 100644 --- a/packages/eslint-plugin/docs/rules/no-non-null-assertion.md +++ b/packages/eslint-plugin/docs/rules/no-non-null-assertion.mdx @@ -2,6 +2,9 @@ description: 'Disallow non-null assertions using the `!` postfix operator.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-non-null-assertion** for documentation. @@ -12,9 +15,8 @@ It's generally better to structure program logic so that TypeScript understands ## Examples - - -### ❌ Incorrect + + ```ts interface Example { @@ -25,7 +27,8 @@ declare const example: Example; const includesBaz = example.property!.includes('baz'); ``` -### βœ… Correct + + ```ts interface Example { @@ -36,6 +39,9 @@ declare const example: Example; const includesBaz = example.property?.includes('baz') ?? false; ``` + + + ## When Not To Use It If your project's types don't yet fully describe whether certain values may be nullable, such as if you're transitioning to `strictNullChecks`, this rule might create many false reports. diff --git a/packages/eslint-plugin/docs/rules/no-parameter-properties.md b/packages/eslint-plugin/docs/rules/no-parameter-properties.mdx similarity index 100% rename from packages/eslint-plugin/docs/rules/no-parameter-properties.md rename to packages/eslint-plugin/docs/rules/no-parameter-properties.mdx diff --git a/packages/eslint-plugin/docs/rules/no-redeclare.md b/packages/eslint-plugin/docs/rules/no-redeclare.mdx similarity index 93% rename from packages/eslint-plugin/docs/rules/no-redeclare.md rename to packages/eslint-plugin/docs/rules/no-redeclare.mdx index 6c53550fec34..8c896acb20c9 100644 --- a/packages/eslint-plugin/docs/rules/no-redeclare.md +++ b/packages/eslint-plugin/docs/rules/no-redeclare.mdx @@ -2,11 +2,14 @@ description: 'Disallow variable redeclaration.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-redeclare** for documentation. -import TypeScriptOverlap from "@site/src/components/TypeScriptOverlap"; +import TypeScriptOverlap from '@site/src/components/TypeScriptOverlap'; diff --git a/packages/eslint-plugin/docs/rules/no-redundant-type-constituents.md b/packages/eslint-plugin/docs/rules/no-redundant-type-constituents.mdx similarity index 94% rename from packages/eslint-plugin/docs/rules/no-redundant-type-constituents.md rename to packages/eslint-plugin/docs/rules/no-redundant-type-constituents.mdx index a18b06b4f42b..4a4d7d34cf98 100644 --- a/packages/eslint-plugin/docs/rules/no-redundant-type-constituents.md +++ b/packages/eslint-plugin/docs/rules/no-redundant-type-constituents.mdx @@ -2,6 +2,9 @@ description: 'Disallow members of unions and intersections that do nothing or override type information.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-redundant-type-constituents** for documentation. @@ -24,9 +27,8 @@ Within `&` intersections: ## Examples - - -### ❌ Incorrect + + ```ts type UnionAny = any | 'foo'; @@ -46,7 +48,8 @@ type IntersectionNumberLiteral = number & 1; type IntersectionStringLiteral = string & 'foo'; ``` -### βœ… Correct + + ```ts type UnionAny = any; @@ -66,6 +69,9 @@ type IntersectionNumberLiteral = 1; type IntersectionStringLiteral = 'foo'; ``` + + + ## Limitations This rule plays it safe and only works with bottom types, top types, and comparing literal types to primitive types. diff --git a/packages/eslint-plugin/docs/rules/no-require-imports.md b/packages/eslint-plugin/docs/rules/no-require-imports.mdx similarity index 83% rename from packages/eslint-plugin/docs/rules/no-require-imports.md rename to packages/eslint-plugin/docs/rules/no-require-imports.mdx index d67d639b9cb8..b5cc06dbd432 100644 --- a/packages/eslint-plugin/docs/rules/no-require-imports.md +++ b/packages/eslint-plugin/docs/rules/no-require-imports.mdx @@ -2,6 +2,9 @@ description: 'Disallow invocation of `require()`.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-require-imports** for documentation. @@ -10,9 +13,8 @@ Prefer the newer ES6-style imports over `require()`. ## Examples - - -### ❌ Incorrect + + ```ts const lib1 = require('lib1'); @@ -20,7 +22,8 @@ const { lib2 } = require('lib2'); import lib3 = require('lib3'); ``` -### βœ… Correct + + ```ts import * as lib1 from 'lib1'; @@ -28,6 +31,9 @@ import { lib2 } from 'lib2'; import * as lib3 from 'lib3'; ``` + + + ## Options ### `allow` @@ -36,20 +42,23 @@ A array of strings. These strings will be compiled into regular expressions with With `{allow: ['/package\\.json$']}`: - - -### ❌ Incorrect + + ```ts console.log(require('../data.json').version); ``` -### βœ… Correct + + ```ts console.log(require('../package.json').version); ``` + + + ## When Not To Use It If your project frequently uses older CommonJS `require`s, then this rule might not be applicable to you. @@ -57,4 +66,4 @@ If only a subset of your project uses `require`s then you might consider using [ ## Related To -- [`no-var-requires`](./no-var-requires.md) +- [`no-var-requires`](./no-var-requires.mdx) diff --git a/packages/eslint-plugin/docs/rules/no-restricted-imports.md b/packages/eslint-plugin/docs/rules/no-restricted-imports.mdx similarity index 92% rename from packages/eslint-plugin/docs/rules/no-restricted-imports.md rename to packages/eslint-plugin/docs/rules/no-restricted-imports.mdx index 1fe6333b6682..d74494d41e18 100644 --- a/packages/eslint-plugin/docs/rules/no-restricted-imports.md +++ b/packages/eslint-plugin/docs/rules/no-restricted-imports.mdx @@ -2,6 +2,9 @@ description: 'Disallow specified modules when loaded by `import`.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-restricted-imports** for documentation. @@ -46,9 +49,8 @@ When set to `true`, the rule will allow [Type-Only Imports](https://www.typescri Examples of code with the above config: - - -#### ❌ Incorrect + + ```ts option='{"paths":[{"name":"import-foo","message":"Please use import-bar instead.","allowTypeImports":true},{"name":"import-baz","message":"Please use import-quux instead.","allowTypeImports":true}]}' import foo from 'import-foo'; @@ -58,7 +60,8 @@ import baz from 'import-baz'; export { Baz } from 'import-baz'; ``` -#### βœ… Correct + + ```ts option='{"paths":[{"name":"import-foo","message":"Please use import-bar instead.","allowTypeImports":true},{"name":"import-baz","message":"Please use import-quux instead.","allowTypeImports":true}]}' import { foo } from 'other-module'; @@ -69,3 +72,6 @@ export type { Foo } from 'import-foo'; import type baz from 'import-baz'; export type { Baz } from 'import-baz'; ``` + + + diff --git a/packages/eslint-plugin/docs/rules/no-shadow.md b/packages/eslint-plugin/docs/rules/no-shadow.mdx similarity index 97% rename from packages/eslint-plugin/docs/rules/no-shadow.md rename to packages/eslint-plugin/docs/rules/no-shadow.mdx index d4eb49fa84ad..2e9328907161 100644 --- a/packages/eslint-plugin/docs/rules/no-shadow.md +++ b/packages/eslint-plugin/docs/rules/no-shadow.mdx @@ -2,6 +2,9 @@ description: 'Disallow variable declarations from shadowing variables declared in the outer scope.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-shadow** for documentation. @@ -46,7 +49,7 @@ function f() { :::note -_Shadowing_ specifically refers to two identical identifiers that are in different, nested scopes. This is different from _redeclaration_, which is when two identical identifiers are in the same scope. Redeclaration is covered by the [`no-redeclare`](./no-redeclare.md) rule instead. +_Shadowing_ specifically refers to two identical identifiers that are in different, nested scopes. This is different from _redeclaration_, which is when two identical identifiers are in the same scope. Redeclaration is covered by the [`no-redeclare`](./no-redeclare.mdx) rule instead. ::: diff --git a/packages/eslint-plugin/docs/rules/no-this-alias.md b/packages/eslint-plugin/docs/rules/no-this-alias.mdx similarity index 85% rename from packages/eslint-plugin/docs/rules/no-this-alias.md rename to packages/eslint-plugin/docs/rules/no-this-alias.mdx index 2b1222923d3b..af07e4353d3f 100644 --- a/packages/eslint-plugin/docs/rules/no-this-alias.md +++ b/packages/eslint-plugin/docs/rules/no-this-alias.mdx @@ -2,6 +2,9 @@ description: 'Disallow aliasing `this`.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-this-alias** for documentation. @@ -11,9 +14,8 @@ or not managing scope well. ## Examples - - -### ❌ Incorrect + + ```ts const self = this; @@ -23,7 +25,8 @@ setTimeout(function () { }); ``` -### βœ… Correct + + ```ts setTimeout(() => { @@ -31,6 +34,9 @@ setTimeout(() => { }); ``` + + + ## Options ### `allowDestructuring` @@ -41,9 +47,8 @@ You can explicitly disallow them by setting `allowDestructuring` to `false`. Examples of code for the `{ "allowDestructuring": false }` option: - - -#### ❌ Incorrect + + ```ts option='{ "allowDestructuring": false }' class ComponentLike { @@ -59,7 +64,8 @@ class ComponentLike { } ``` -#### βœ… Correct + + ```ts option='{ "allowDestructuring": false }' class ComponentLike { @@ -73,6 +79,9 @@ class ComponentLike { } ``` + + + ### `allowedNames` `no-this-alias` can alternately be used to allow only a specific list of names as `this` aliases. @@ -80,9 +89,8 @@ We recommend against this except as a transitory step towards fixing all rule vi Examples of code for the `{ "allowedNames": ["self"] }` option: - - -#### ❌ Incorrect + + ```ts option='{ "allowedNames": ["self"] }' class Example { @@ -92,7 +100,8 @@ class Example { } ``` -#### βœ… Correct + + ```ts option='{ "allowedNames": ["self"] }' class Example { @@ -102,6 +111,9 @@ class Example { } ``` + + + ## When Not To Use It If your project is structured in a way that it needs to assign `this` to variables, this rule is likely not for you. diff --git a/packages/eslint-plugin/docs/rules/no-throw-literal.md b/packages/eslint-plugin/docs/rules/no-throw-literal.mdx similarity index 90% rename from packages/eslint-plugin/docs/rules/no-throw-literal.md rename to packages/eslint-plugin/docs/rules/no-throw-literal.mdx index 91bb246ce52e..b5d57c5533f5 100644 --- a/packages/eslint-plugin/docs/rules/no-throw-literal.md +++ b/packages/eslint-plugin/docs/rules/no-throw-literal.mdx @@ -2,6 +2,9 @@ description: 'Disallow throwing literals as exceptions.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-throw-literal** for documentation. @@ -15,9 +18,8 @@ This rule restricts what can be thrown as an exception. When it was first create This rule is aimed at maintaining consistency when throwing exception by disallowing to throw literals and other expressions which cannot possibly be an `Error` object. - - -### ❌ Incorrect + + ```ts throw 'error'; @@ -48,7 +50,8 @@ const foo = { throw foo.bar; ``` -### βœ… Correct + + ```ts throw new Error(); @@ -83,6 +86,9 @@ class CustomError extends Error { throw new CustomError(); ``` + + + ## Options This rule adds the following options: @@ -106,4 +112,4 @@ const defaultOptions: Options = { }; ``` - +{/* Intentionally Omitted: When Not To Use It */} diff --git a/packages/eslint-plugin/docs/rules/no-type-alias.md b/packages/eslint-plugin/docs/rules/no-type-alias.mdx similarity index 99% rename from packages/eslint-plugin/docs/rules/no-type-alias.md rename to packages/eslint-plugin/docs/rules/no-type-alias.mdx index 32caada41dc6..64822ea75a92 100644 --- a/packages/eslint-plugin/docs/rules/no-type-alias.md +++ b/packages/eslint-plugin/docs/rules/no-type-alias.mdx @@ -2,13 +2,16 @@ description: 'Disallow type aliases.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-type-alias** for documentation. :::danger Deprecated -This rule has been deprecated in favour of the [`@typescript-eslint/consistent-type-definitions`](./consistent-type-definitions.md) rule. +This rule has been deprecated in favour of the [`@typescript-eslint/consistent-type-definitions`](./consistent-type-definitions.mdx) rule. TypeScript type aliases are a commonly necessary language feature; banning it altogether is oftentimes counterproductive. ::: @@ -616,7 +619,7 @@ type Foo = Partial; type Foo = Omit; ``` - +{/* Intentionally Omitted: When Not To Use It */} ## Further Reading diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-boolean-literal-compare.md b/packages/eslint-plugin/docs/rules/no-unnecessary-boolean-literal-compare.mdx similarity index 92% rename from packages/eslint-plugin/docs/rules/no-unnecessary-boolean-literal-compare.md rename to packages/eslint-plugin/docs/rules/no-unnecessary-boolean-literal-compare.mdx index 09d85216ba69..d0ca9544a581 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-boolean-literal-compare.md +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-boolean-literal-compare.mdx @@ -2,6 +2,9 @@ description: 'Disallow unnecessary equality comparisons against boolean literals.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-unnecessary-boolean-literal-compare** for documentation. @@ -21,9 +24,8 @@ However, the implementation of the rule does not distinguish between strict and Any example below that uses `===` would be treated the same way if `==` was used, and `!==` would be treated the same way if `!=` was used. ::: - - -### ❌ Incorrect + + ```ts declare const someCondition: boolean; @@ -31,7 +33,8 @@ if (someCondition === true) { } ``` -### βœ… Correct + + ```ts declare const someCondition: boolean; @@ -47,6 +50,9 @@ if (someStringBoolean === true) { } ``` + + + ## Options This rule always checks comparisons between a boolean variable and a boolean @@ -57,9 +63,8 @@ are **not** checked by default. Examples of code for this rule with `{ allowComparingNullableBooleansToTrue: false }`: - - -#### ❌ Incorrect + + ```ts option='{ "allowComparingNullableBooleansToTrue": false }' declare const someUndefinedCondition: boolean | undefined; @@ -71,7 +76,8 @@ if (someNullCondition !== true) { } ``` -#### βœ… Correct + + ```ts option='{ "allowComparingNullableBooleansToTrue": false }' declare const someUndefinedCondition: boolean | undefined; @@ -83,13 +89,15 @@ if (!someNullCondition) { } ``` + + + ### `allowComparingNullableBooleansToFalse` Examples of code for this rule with `{ allowComparingNullableBooleansToFalse: false }`: - - -#### ❌ Incorrect + + ```ts option='{ "allowComparingNullableBooleansToFalse": false }' declare const someUndefinedCondition: boolean | undefined; @@ -101,7 +109,8 @@ if (someNullCondition !== false) { } ``` -#### βœ… Correct + + ```ts option='{ "allowComparingNullableBooleansToFalse": false }' declare const someUndefinedCondition: boolean | undefined; @@ -113,6 +122,9 @@ if (!(someNullCondition ?? true)) { } ``` + + + ## Fixer | Comparison | Fixer Output | Notes | diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-condition.md b/packages/eslint-plugin/docs/rules/no-unnecessary-condition.mdx similarity index 90% rename from packages/eslint-plugin/docs/rules/no-unnecessary-condition.md rename to packages/eslint-plugin/docs/rules/no-unnecessary-condition.mdx index 59c9c126d2d8..9124bbd46e11 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-condition.md +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-condition.mdx @@ -2,6 +2,9 @@ description: 'Disallow conditionals where the type is always truthy or always falsy.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-unnecessary-condition** for documentation. @@ -17,9 +20,8 @@ The following expressions are checked: ## Examples - - -### ❌ Incorrect + + ```ts function head(items: T[]) { @@ -47,7 +49,8 @@ function bar(arg: string) { ].filter(t => t); // number[] is always truthy ``` -### βœ… Correct + + ```ts function head(items: T[]) { @@ -71,6 +74,9 @@ function bar(arg?: string | null) { [0, 1, 2, 3].filter(t => t); // number can be truthy or falsy ``` + + + ## Options ### `allowConstantLoopConditions` @@ -116,4 +122,4 @@ if (condition) { ## Related To - ESLint: [no-constant-condition](https://eslint.org/docs/rules/no-constant-condition) - `no-unnecessary-condition` is essentially a stronger version of `no-constant-condition`, but requires type information. -- [strict-boolean-expressions](./strict-boolean-expressions.md) - a more opinionated version of `no-unnecessary-condition`. `strict-boolean-expressions` enforces a specific code style, while `no-unnecessary-condition` is about correctness. +- [strict-boolean-expressions](./strict-boolean-expressions.mdx) - a more opinionated version of `no-unnecessary-condition`. `strict-boolean-expressions` enforces a specific code style, while `no-unnecessary-condition` is about correctness. diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-qualifier.md b/packages/eslint-plugin/docs/rules/no-unnecessary-qualifier.mdx similarity index 84% rename from packages/eslint-plugin/docs/rules/no-unnecessary-qualifier.md rename to packages/eslint-plugin/docs/rules/no-unnecessary-qualifier.mdx index 8c698145a93a..aaf5edb8f547 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-qualifier.md +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-qualifier.mdx @@ -2,6 +2,9 @@ description: 'Disallow unnecessary namespace qualifiers.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-unnecessary-qualifier** for documentation. @@ -12,9 +15,8 @@ This rule reports when an enum or namespace qualifier is unnecessary. ## Examples - - -### ❌ Incorrect + + ```ts enum A { @@ -30,7 +32,8 @@ namespace A { } ``` -### βœ… Correct + + ```ts enum A { @@ -46,6 +49,9 @@ namespace A { } ``` + + + ## When Not To Use It If you explicitly prefer to use fully qualified names, such as for explicitness, then you don't need to use this rule. diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-type-arguments.md b/packages/eslint-plugin/docs/rules/no-unnecessary-type-arguments.mdx similarity index 88% rename from packages/eslint-plugin/docs/rules/no-unnecessary-type-arguments.md rename to packages/eslint-plugin/docs/rules/no-unnecessary-type-arguments.mdx index c13586740883..875b4f4793a5 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-type-arguments.md +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-type-arguments.mdx @@ -2,6 +2,9 @@ description: 'Disallow type arguments that are equal to the default.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-unnecessary-type-arguments** for documentation. @@ -20,9 +23,8 @@ This rule reports when an explicitly specified type argument is the default for ## Examples - - -### ❌ Incorrect + + ```ts function f() {} @@ -46,7 +48,8 @@ interface I {} class Impl implements I {} ``` -### βœ… Correct + + ```ts function f() {} @@ -74,6 +77,9 @@ interface I {} class Impl implements I {} ``` + + + ## When Not To Use It If you prefer explicitly specifying type parameters even when they are equal to the default, you can skip this rule. diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.md b/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.mdx similarity index 87% rename from packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.md rename to packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.mdx index 7f428c056243..3182474d9b6a 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.md +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.mdx @@ -2,6 +2,9 @@ description: 'Disallow type assertions that do not change the type of an expression.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-unnecessary-type-assertion** for documentation. @@ -12,9 +15,8 @@ This rule reports when a type assertion does not change the type of an expressio ## Examples - - -### ❌ Incorrect + + ```ts const foo = 3; @@ -35,13 +37,18 @@ type Foo = 3; const foo = 3 as Foo; ``` +```ts +const foo = 'foo' as const; +``` + ```ts function foo(x: number): number { return x!; // unnecessary non-null } ``` -### βœ… Correct + + ```ts const foo = 3; @@ -52,7 +59,7 @@ const foo = 3 as number; ``` ```ts -const foo = 'foo' as const; +let foo = 'foo' as const; ``` ```ts @@ -61,6 +68,9 @@ function foo(x: number | undefined): number { } ``` + + + ## Options ### `typesToIgnore` diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-type-constraint.md b/packages/eslint-plugin/docs/rules/no-unnecessary-type-constraint.mdx similarity index 87% rename from packages/eslint-plugin/docs/rules/no-unnecessary-type-constraint.md rename to packages/eslint-plugin/docs/rules/no-unnecessary-type-constraint.mdx index e5dedc0ee141..ba52d4b9e06e 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-type-constraint.md +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-type-constraint.mdx @@ -2,6 +2,9 @@ description: 'Disallow unnecessary constraints on generic types.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-unnecessary-type-constraint** for documentation. @@ -12,9 +15,8 @@ It is therefore redundant to `extend` from `any` or `unknown`. ## Examples - - -### ❌ Incorrect + + ```ts interface FooAny {} @@ -34,7 +36,8 @@ const QuuxAny = () => {}; function QuuzAny() {} ``` -### βœ… Correct + + ```ts interface Foo {} @@ -50,6 +53,9 @@ const Quux = () => {}; function Quuz() {} ``` + + + ## When Not To Use It If you don't care about the specific styles of your type constraints, or never use them in the first place, then you will not need this rule. diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-argument.md b/packages/eslint-plugin/docs/rules/no-unsafe-argument.mdx similarity index 87% rename from packages/eslint-plugin/docs/rules/no-unsafe-argument.md rename to packages/eslint-plugin/docs/rules/no-unsafe-argument.mdx index 7605273f27ba..a86575008075 100644 --- a/packages/eslint-plugin/docs/rules/no-unsafe-argument.md +++ b/packages/eslint-plugin/docs/rules/no-unsafe-argument.mdx @@ -2,6 +2,9 @@ description: 'Disallow calling a function with a value with type `any`.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-unsafe-argument** for documentation. @@ -20,9 +23,8 @@ For example, it will error if you pass `Set` as an argument to a parameter ## Examples - - -### ❌ Incorrect + + ```ts declare function foo(arg1: string, arg2: number, arg3: string): void; @@ -49,7 +51,8 @@ declare function baz(arg1: Set, arg2: Map): void; foo(new Set(), new Map()); ``` -### βœ… Correct + + ```ts declare function foo(arg1: string, arg2: number, arg3: string): void; @@ -67,7 +70,8 @@ declare function baz(arg1: Set, arg2: Map): void; foo(new Set(), new Map()); ``` - + + There are cases where the rule allows passing an argument of `any` to `unknown`. @@ -86,8 +90,8 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates ## Related To -- [`no-explicit-any`](./no-explicit-any.md) -- [`no-unsafe-assignment`](./no-unsafe-assignment.md) -- [`no-unsafe-call`](./no-unsafe-call.md) -- [`no-unsafe-member-access`](./no-unsafe-member-access.md) -- [`no-unsafe-return`](./no-unsafe-return.md) +- [`no-explicit-any`](./no-explicit-any.mdx) +- [`no-unsafe-assignment`](./no-unsafe-assignment.mdx) +- [`no-unsafe-call`](./no-unsafe-call.mdx) +- [`no-unsafe-member-access`](./no-unsafe-member-access.mdx) +- [`no-unsafe-return`](./no-unsafe-return.mdx) diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-assignment.md b/packages/eslint-plugin/docs/rules/no-unsafe-assignment.mdx similarity index 86% rename from packages/eslint-plugin/docs/rules/no-unsafe-assignment.md rename to packages/eslint-plugin/docs/rules/no-unsafe-assignment.mdx index c506ea1bcba7..84a1d0ee47e9 100644 --- a/packages/eslint-plugin/docs/rules/no-unsafe-assignment.md +++ b/packages/eslint-plugin/docs/rules/no-unsafe-assignment.mdx @@ -2,6 +2,9 @@ description: 'Disallow assigning a value with type `any` to variables and properties.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-unsafe-assignment** for documentation. @@ -19,9 +22,8 @@ For example, it will error if you assign `Set` to a variable declared as `S ## Examples - - -### ❌ Incorrect + + ```ts const x = 1 as any, @@ -46,7 +48,8 @@ const x: Set = new Set(); const x: Set>> = new Set>>(); ``` -### βœ… Correct + + ```ts const x = 1, @@ -69,7 +72,8 @@ const x: Set = new Set(); const x: Set>> = new Set>>(); ``` - + + There are cases where the rule allows assignment of `any` to `unknown`. @@ -89,8 +93,8 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates ## Related To -- [`no-explicit-any`](./no-explicit-any.md) -- [`no-unsafe-argument`](./no-unsafe-argument.md) -- [`no-unsafe-call`](./no-unsafe-call.md) -- [`no-unsafe-member-access`](./no-unsafe-member-access.md) -- [`no-unsafe-return`](./no-unsafe-return.md) +- [`no-explicit-any`](./no-explicit-any.mdx) +- [`no-unsafe-argument`](./no-unsafe-argument.mdx) +- [`no-unsafe-call`](./no-unsafe-call.mdx) +- [`no-unsafe-member-access`](./no-unsafe-member-access.mdx) +- [`no-unsafe-return`](./no-unsafe-return.mdx) diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-call.md b/packages/eslint-plugin/docs/rules/no-unsafe-call.mdx similarity index 78% rename from packages/eslint-plugin/docs/rules/no-unsafe-call.md rename to packages/eslint-plugin/docs/rules/no-unsafe-call.mdx index 140c92a5ed33..3e56c1e0f092 100644 --- a/packages/eslint-plugin/docs/rules/no-unsafe-call.md +++ b/packages/eslint-plugin/docs/rules/no-unsafe-call.mdx @@ -2,6 +2,9 @@ description: 'Disallow calling a value with type `any`.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-unsafe-call** for documentation. @@ -16,9 +19,8 @@ This rule disallows calling any value that is typed as `any`. ## Examples - - -### ❌ Incorrect + + ```ts declare const anyVar: any; @@ -37,7 +39,8 @@ anyVar`foo`; nestedAny.prop`foo`; ``` -### βœ… Correct + + ```ts declare const typedVar: () => void; @@ -53,6 +56,9 @@ new Map(); String.raw`foo`; ``` + + + ## When Not To Use It If your codebase has many existing `any`s or areas of unsafe code, it may be difficult to enable this rule. @@ -61,8 +67,8 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates ## Related To -- [`no-explicit-any`](./no-explicit-any.md) -- [`no-unsafe-argument`](./no-unsafe-argument.md) -- [`no-unsafe-assignment`](./no-unsafe-assignment.md) -- [`no-unsafe-member-access`](./no-unsafe-member-access.md) -- [`no-unsafe-return`](./no-unsafe-return.md) +- [`no-explicit-any`](./no-explicit-any.mdx) +- [`no-unsafe-argument`](./no-unsafe-argument.mdx) +- [`no-unsafe-assignment`](./no-unsafe-assignment.mdx) +- [`no-unsafe-member-access`](./no-unsafe-member-access.mdx) +- [`no-unsafe-return`](./no-unsafe-return.mdx) diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-declaration-merging.md b/packages/eslint-plugin/docs/rules/no-unsafe-declaration-merging.mdx similarity index 89% rename from packages/eslint-plugin/docs/rules/no-unsafe-declaration-merging.md rename to packages/eslint-plugin/docs/rules/no-unsafe-declaration-merging.mdx index 441df05a2d47..d03605f91e2c 100644 --- a/packages/eslint-plugin/docs/rules/no-unsafe-declaration-merging.md +++ b/packages/eslint-plugin/docs/rules/no-unsafe-declaration-merging.mdx @@ -2,6 +2,9 @@ description: 'Disallow unsafe declaration merging.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-unsafe-declaration-merging** for documentation. @@ -25,9 +28,8 @@ foo.nums.push(1); // Runtime Error: Cannot read properties of undefined. ## Examples - - -### ❌ Incorrect + + ```ts interface Foo {} @@ -35,7 +37,8 @@ interface Foo {} class Foo {} ``` -### βœ… Correct + + ```ts interface Foo {} @@ -49,6 +52,9 @@ namespace Qux {} function Qux() {} ``` + + + ## When Not To Use It If your project intentionally defines classes and interfaces with unsafe declaration merging patterns, this rule might not be for you. diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md b/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.mdx similarity index 92% rename from packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md rename to packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.mdx index 008d355d4e2a..7988317b5b6d 100644 --- a/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md +++ b/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.mdx @@ -2,6 +2,9 @@ description: 'Disallow comparing an enum value with a non-enum value.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-unsafe-enum-comparison** for documentation. @@ -26,9 +29,8 @@ The above code snippet should instead be written as `vegetable === Vegetable.Asp ## Examples - - -### ❌ Incorrect + + ```ts enum Fruit { @@ -50,7 +52,8 @@ declare let vegetable: Vegetable; vegetable === 'asparagus'; ``` -### βœ… Correct + + ```ts enum Fruit { @@ -72,7 +75,8 @@ declare let vegetable: Vegetable; vegetable === Vegetable.Asparagus; ``` - + + ## When Not To Use It diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-member-access.md b/packages/eslint-plugin/docs/rules/no-unsafe-member-access.mdx similarity index 80% rename from packages/eslint-plugin/docs/rules/no-unsafe-member-access.md rename to packages/eslint-plugin/docs/rules/no-unsafe-member-access.mdx index 9ca36ee3566d..4b41fb56baac 100644 --- a/packages/eslint-plugin/docs/rules/no-unsafe-member-access.md +++ b/packages/eslint-plugin/docs/rules/no-unsafe-member-access.mdx @@ -2,6 +2,9 @@ description: 'Disallow member access on a value with type `any`.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-unsafe-member-access** for documentation. @@ -16,9 +19,8 @@ This rule disallows member access on any variable that is typed as `any`. ## Examples - - -### ❌ Incorrect + + ```ts declare const anyVar: any; @@ -41,7 +43,8 @@ arr[anyVar]; nestedAny[anyVar]; ``` -### βœ… Correct + + ```ts declare const properlyTyped: { prop: { a: string } }; @@ -59,6 +62,9 @@ arr[idx]; arr[idx++]; ``` + + + ## When Not To Use It If your codebase has many existing `any`s or areas of unsafe code, it may be difficult to enable this rule. @@ -67,8 +73,8 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates ## Related To -- [`no-explicit-any`](./no-explicit-any.md) -- [`no-unsafe-argument`](./no-unsafe-argument.md) -- [`no-unsafe-assignment`](./no-unsafe-assignment.md) -- [`no-unsafe-call`](./no-unsafe-call.md) -- [`no-unsafe-return`](./no-unsafe-return.md) +- [`no-explicit-any`](./no-explicit-any.mdx) +- [`no-unsafe-argument`](./no-unsafe-argument.mdx) +- [`no-unsafe-assignment`](./no-unsafe-assignment.mdx) +- [`no-unsafe-call`](./no-unsafe-call.mdx) +- [`no-unsafe-return`](./no-unsafe-return.mdx) diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-return.md b/packages/eslint-plugin/docs/rules/no-unsafe-return.mdx similarity index 86% rename from packages/eslint-plugin/docs/rules/no-unsafe-return.md rename to packages/eslint-plugin/docs/rules/no-unsafe-return.mdx index 1137264d5f41..c5ca28700e6a 100644 --- a/packages/eslint-plugin/docs/rules/no-unsafe-return.md +++ b/packages/eslint-plugin/docs/rules/no-unsafe-return.mdx @@ -2,6 +2,9 @@ description: 'Disallow returning a value with type `any` from a function.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-unsafe-return** for documentation. @@ -19,9 +22,8 @@ For example, it will error if you return `Set` from a function declared as ## Examples - - -### ❌ Incorrect + + ```ts function foo1() { @@ -62,7 +64,8 @@ type TAssign = () => Set; const assignability2: TAssign = () => new Set([true]); ``` -### βœ… Correct + + ```ts function foo1() { @@ -82,7 +85,8 @@ type TAssign = () => Set; const assignability2: TAssign = () => new Set(['foo']); ``` - + + There are cases where the rule allows to return `any` to `unknown`. @@ -106,8 +110,8 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates ## Related To -- [`no-explicit-any`](./no-explicit-any.md) -- [`no-unsafe-argument`](./no-unsafe-argument.md) -- [`no-unsafe-assignment`](./no-unsafe-assignment.md) -- [`no-unsafe-call`](./no-unsafe-call.md) -- [`no-unsafe-member-access`](./no-unsafe-member-access.md) +- [`no-explicit-any`](./no-explicit-any.mdx) +- [`no-unsafe-argument`](./no-unsafe-argument.mdx) +- [`no-unsafe-assignment`](./no-unsafe-assignment.mdx) +- [`no-unsafe-call`](./no-unsafe-call.mdx) +- [`no-unsafe-member-access`](./no-unsafe-member-access.mdx) diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-unary-minus.md b/packages/eslint-plugin/docs/rules/no-unsafe-unary-minus.mdx similarity index 76% rename from packages/eslint-plugin/docs/rules/no-unsafe-unary-minus.md rename to packages/eslint-plugin/docs/rules/no-unsafe-unary-minus.mdx index c4093c54df80..307914ef33ab 100644 --- a/packages/eslint-plugin/docs/rules/no-unsafe-unary-minus.md +++ b/packages/eslint-plugin/docs/rules/no-unsafe-unary-minus.mdx @@ -2,6 +2,9 @@ description: 'Require unary negation to take a number.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-unsafe-unary-minus** for documentation. @@ -17,9 +20,8 @@ This rule restricts the unary `-` operator to `number | bigint`. ## Examples - - -### ❌ Incorrect + + ```ts declare const a: string; @@ -29,7 +31,8 @@ declare const b: {}; -b; ``` -### βœ… Correct + + ```ts -42; @@ -51,4 +54,7 @@ declare const e: 1 | 2; -e; ``` - + + + +{/* Intentionally Omitted: When Not To Use It */} diff --git a/packages/eslint-plugin/docs/rules/no-unused-expressions.md b/packages/eslint-plugin/docs/rules/no-unused-expressions.mdx similarity index 85% rename from packages/eslint-plugin/docs/rules/no-unused-expressions.md rename to packages/eslint-plugin/docs/rules/no-unused-expressions.mdx index 6bec1ef7d66a..2f0805ba8de6 100644 --- a/packages/eslint-plugin/docs/rules/no-unused-expressions.md +++ b/packages/eslint-plugin/docs/rules/no-unused-expressions.mdx @@ -2,6 +2,9 @@ description: 'Disallow unused expressions.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-unused-expressions** for documentation. diff --git a/packages/eslint-plugin/docs/rules/no-unused-vars.md b/packages/eslint-plugin/docs/rules/no-unused-vars.mdx similarity index 97% rename from packages/eslint-plugin/docs/rules/no-unused-vars.md rename to packages/eslint-plugin/docs/rules/no-unused-vars.mdx index aaccc6e03398..3a65df4f27c7 100644 --- a/packages/eslint-plugin/docs/rules/no-unused-vars.md +++ b/packages/eslint-plugin/docs/rules/no-unused-vars.mdx @@ -2,6 +2,9 @@ description: 'Disallow unused variables.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-unused-vars** for documentation. diff --git a/packages/eslint-plugin/docs/rules/no-use-before-define.md b/packages/eslint-plugin/docs/rules/no-use-before-define.mdx similarity index 92% rename from packages/eslint-plugin/docs/rules/no-use-before-define.md rename to packages/eslint-plugin/docs/rules/no-use-before-define.mdx index 8c0876d97b54..9b431874446e 100644 --- a/packages/eslint-plugin/docs/rules/no-use-before-define.md +++ b/packages/eslint-plugin/docs/rules/no-use-before-define.mdx @@ -2,6 +2,9 @@ description: 'Disallow the use of variables before they are defined.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-use-before-define** for documentation. @@ -35,9 +38,8 @@ If this is `false`, this rule will ignore references to enums, when the referenc Examples of code for the `{ "enums": true }` option: - - -#### ❌ Incorrect + + ```ts option='{ "enums": true }' const x = Foo.FOO; @@ -47,7 +49,8 @@ enum Foo { } ``` -#### βœ… Correct + + ```ts option='{ "enums": false }' function foo() { @@ -59,6 +62,9 @@ enum Foo { } ``` + + + ### `typedefs` If this is `true`, this rule warns every reference to a type before the type declaration. diff --git a/packages/eslint-plugin/docs/rules/no-useless-constructor.md b/packages/eslint-plugin/docs/rules/no-useless-constructor.mdx similarity index 92% rename from packages/eslint-plugin/docs/rules/no-useless-constructor.md rename to packages/eslint-plugin/docs/rules/no-useless-constructor.mdx index df0e3ecce66e..4003fefc211a 100644 --- a/packages/eslint-plugin/docs/rules/no-useless-constructor.md +++ b/packages/eslint-plugin/docs/rules/no-useless-constructor.mdx @@ -2,6 +2,9 @@ description: 'Disallow unnecessary constructors.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-useless-constructor** for documentation. diff --git a/packages/eslint-plugin/docs/rules/no-useless-empty-export.md b/packages/eslint-plugin/docs/rules/no-useless-empty-export.mdx similarity index 88% rename from packages/eslint-plugin/docs/rules/no-useless-empty-export.md rename to packages/eslint-plugin/docs/rules/no-useless-empty-export.mdx index 1720a223de56..27d66d9d0bb4 100644 --- a/packages/eslint-plugin/docs/rules/no-useless-empty-export.md +++ b/packages/eslint-plugin/docs/rules/no-useless-empty-export.mdx @@ -2,6 +2,9 @@ description: "Disallow empty exports that don't change anything in a module file." --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-useless-empty-export** for documentation. @@ -18,9 +21,8 @@ This rule reports an `export {}` that doesn't do anything in a file already usin ## Examples - - -### ❌ Incorrect + + ```ts export const value = 'Hello, world!'; @@ -32,7 +34,8 @@ import 'some-other-module'; export {}; ``` -### βœ… Correct + + ```ts export const value = 'Hello, world!'; @@ -42,6 +45,9 @@ export const value = 'Hello, world!'; import 'some-other-module'; ``` + + + ## When Not To Use It If you don't mind an empty `export {}` at the bottom of files, you likely don't need this rule. diff --git a/packages/eslint-plugin/docs/rules/no-useless-template-literals.md b/packages/eslint-plugin/docs/rules/no-useless-template-literals.mdx similarity index 86% rename from packages/eslint-plugin/docs/rules/no-useless-template-literals.md rename to packages/eslint-plugin/docs/rules/no-useless-template-literals.mdx index f641e8d1f823..b229a4b1ff67 100644 --- a/packages/eslint-plugin/docs/rules/no-useless-template-literals.md +++ b/packages/eslint-plugin/docs/rules/no-useless-template-literals.mdx @@ -2,6 +2,9 @@ description: 'Disallow unnecessary template literals.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-useless-template-literals** for documentation. @@ -10,9 +13,8 @@ This rule reports template literals that can be simplified to a normal string li ## Examples - - -### ❌ Incorrect + + ```ts const ab1 = `${'a'}${'b'}`; @@ -29,7 +31,8 @@ declare const intersectionWithString: string & { _brand: 'test-brand' }; const wrappedIntersection = `${intersectionWithString}`; ``` -### βœ… Correct + + ```ts const ab1 = 'ab'; @@ -46,7 +49,8 @@ declare const intersectionWithString: string & { _brand: 'test-brand' }; const wrappedIntersection = intersectionWithString; ``` - + + ## When Not To Use It @@ -54,4 +58,4 @@ When you want to allow string expressions inside template literals. ## Related To -- [`restrict-template-expressions`](./restrict-template-expressions.md) +- [`restrict-template-expressions`](./restrict-template-expressions.mdx) diff --git a/packages/eslint-plugin/docs/rules/no-var-requires.md b/packages/eslint-plugin/docs/rules/no-var-requires.mdx similarity index 84% rename from packages/eslint-plugin/docs/rules/no-var-requires.md rename to packages/eslint-plugin/docs/rules/no-var-requires.mdx index 0bd0b2b564b9..725cb0f7a835 100644 --- a/packages/eslint-plugin/docs/rules/no-var-requires.md +++ b/packages/eslint-plugin/docs/rules/no-var-requires.mdx @@ -2,6 +2,9 @@ description: 'Disallow `require` statements except in import statements.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/no-var-requires** for documentation. @@ -10,9 +13,8 @@ In other words, the use of forms such as `var foo = require("foo")` are banned. ## Examples - - -### ❌ Incorrect + + ```ts var foo = require('foo'); @@ -20,7 +22,8 @@ const foo = require('foo'); let foo = require('foo'); ``` -### βœ… Correct + + ```ts import foo = require('foo'); @@ -28,6 +31,9 @@ require('foo'); import foo from 'foo'; ``` + + + ## Options ### `allow` @@ -36,20 +42,23 @@ A array of strings. These strings will be compiled into regular expressions with With `{allow: ['/package\\.json$']}`: - - -### ❌ Incorrect + + ```ts const foo = require('../data.json'); ``` -### βœ… Correct + + ```ts const foo = require('../package.json'); ``` + + + ## When Not To Use It If your project frequently uses older CommonJS `require`s, then this rule might not be applicable to you. @@ -57,4 +66,4 @@ If only a subset of your project uses `require`s then you might consider using [ ## Related To -- [`no-require-imports`](./no-require-imports.md) +- [`no-require-imports`](./no-require-imports.mdx) diff --git a/packages/eslint-plugin/docs/rules/non-nullable-type-assertion-style.md b/packages/eslint-plugin/docs/rules/non-nullable-type-assertion-style.mdx similarity index 86% rename from packages/eslint-plugin/docs/rules/non-nullable-type-assertion-style.md rename to packages/eslint-plugin/docs/rules/non-nullable-type-assertion-style.mdx index 0dc6527ff0d3..05185e692893 100644 --- a/packages/eslint-plugin/docs/rules/non-nullable-type-assertion-style.md +++ b/packages/eslint-plugin/docs/rules/non-nullable-type-assertion-style.mdx @@ -2,6 +2,9 @@ description: 'Enforce non-null assertions over explicit type casts.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/non-nullable-type-assertion-style** for documentation. @@ -16,9 +19,8 @@ This rule reports when an `as` cast is doing the same job as a `!` would, and su ## Examples - - -### ❌ Incorrect + + ```ts const maybe = Math.random() > 0.5 ? '' : undefined; @@ -27,7 +29,8 @@ const definitely = maybe as string; const alsoDefinitely = maybe; ``` -### βœ… Correct + + ```ts const maybe = Math.random() > 0.5 ? '' : undefined; @@ -36,6 +39,9 @@ const definitely = maybe!; const alsoDefinitely = maybe!; ``` + + + ## When Not To Use It If you don't mind having unnecessarily verbose type assertions, you can avoid this rule. diff --git a/packages/eslint-plugin/docs/rules/object-curly-spacing.md b/packages/eslint-plugin/docs/rules/object-curly-spacing.mdx similarity index 84% rename from packages/eslint-plugin/docs/rules/object-curly-spacing.md rename to packages/eslint-plugin/docs/rules/object-curly-spacing.mdx index 91b2421375ed..af82a39f986b 100644 --- a/packages/eslint-plugin/docs/rules/object-curly-spacing.md +++ b/packages/eslint-plugin/docs/rules/object-curly-spacing.mdx @@ -2,6 +2,9 @@ description: 'Enforce consistent spacing inside braces.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/object-curly-spacing** for documentation. diff --git a/packages/eslint-plugin/docs/rules/padding-line-between-statements.md b/packages/eslint-plugin/docs/rules/padding-line-between-statements.mdx similarity index 93% rename from packages/eslint-plugin/docs/rules/padding-line-between-statements.md rename to packages/eslint-plugin/docs/rules/padding-line-between-statements.mdx index 8cf091b4e25f..e1bf6365703d 100644 --- a/packages/eslint-plugin/docs/rules/padding-line-between-statements.md +++ b/packages/eslint-plugin/docs/rules/padding-line-between-statements.mdx @@ -2,6 +2,9 @@ description: 'Require or disallow padding lines between statements.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/padding-line-between-statements** for documentation. diff --git a/packages/eslint-plugin/docs/rules/parameter-properties.md b/packages/eslint-plugin/docs/rules/parameter-properties.mdx similarity index 89% rename from packages/eslint-plugin/docs/rules/parameter-properties.md rename to packages/eslint-plugin/docs/rules/parameter-properties.mdx index 87a467344a03..b9915dd65e68 100644 --- a/packages/eslint-plugin/docs/rules/parameter-properties.md +++ b/packages/eslint-plugin/docs/rules/parameter-properties.mdx @@ -2,6 +2,9 @@ description: 'Require or disallow parameter properties in class constructors.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/parameter-properties** for documentation. @@ -59,9 +62,8 @@ In `"parameter-property"` mode, the rule will issue a report when: Examples of code for this rule with no options at all: - - -#### ❌ Incorrect + + ```ts class Foo { @@ -93,7 +95,8 @@ class Foo { } ``` -#### βœ… Correct + + ```ts class Foo { @@ -101,13 +104,15 @@ class Foo { } ``` + + + ### readonly Examples of code for the `{ "allow": ["readonly"] }` options: - - -#### ❌ Incorrect + + ```ts option='{ "allow": ["readonly"] }' class Foo { @@ -135,7 +140,8 @@ class Foo { } ``` -#### βœ… Correct + + ```ts option='{ "allow": ["readonly"] }' class Foo { @@ -147,13 +153,15 @@ class Foo { } ``` + + + ### private Examples of code for the `{ "allow": ["private"] }` options: - - -#### ❌ Incorrect + + ```ts option='{ "allow": ["private"] }' class Foo { @@ -181,7 +189,8 @@ class Foo { } ``` -#### βœ… Correct + + ```ts option='{ "allow": ["private"] }' class Foo { @@ -193,13 +202,15 @@ class Foo { } ``` + + + ### protected Examples of code for the `{ "allow": ["protected"] }` options: - - -#### ❌ Incorrect + + ```ts option='{ "allow": ["protected"] }' class Foo { @@ -227,7 +238,8 @@ class Foo { } ``` -#### βœ… Correct + + ```ts option='{ "allow": ["protected"] }' class Foo { @@ -239,13 +251,15 @@ class Foo { } ``` + + + ### public Examples of code for the `{ "allow": ["public"] }` options: - - -#### ❌ Incorrect + + ```ts option='{ "allow": ["public"] }' class Foo { @@ -273,7 +287,8 @@ class Foo { } ``` -#### βœ… Correct + + ```ts option='{ "allow": ["public"] }' class Foo { @@ -285,13 +300,15 @@ class Foo { } ``` + + + ### private readonly Examples of code for the `{ "allow": ["private readonly"] }` options: - - -#### ❌ Incorrect + + ```ts option='{ "allow": ["private readonly"] }' class Foo { @@ -319,7 +336,8 @@ class Foo { } ``` -#### βœ… Correct + + ```ts option='{ "allow": ["private readonly"] }' class Foo { @@ -331,13 +349,15 @@ class Foo { } ``` + + + ### protected readonly Examples of code for the `{ "allow": ["protected readonly"] }` options: - - -#### ❌ Incorrect + + ```ts option='{ "allow": ["protected readonly"] }' class Foo { @@ -365,7 +385,8 @@ class Foo { } ``` -#### βœ… Correct + + ```ts option='{ "allow": ["protected readonly"] }' class Foo { @@ -377,13 +398,15 @@ class Foo { } ``` + + + ### public readonly Examples of code for the `{ "allow": ["public readonly"] }` options: - - -#### ❌ Incorrect + + ```ts option='{ "allow": ["public readonly"] }' class Foo { @@ -411,7 +434,8 @@ class Foo { } ``` -#### βœ… Correct + + ```ts option='{ "allow": ["public readonly"] }' class Foo { @@ -423,13 +447,15 @@ class Foo { } ``` + + + ### `"parameter-property"` Examples of code for the `{ "prefer": "parameter-property" }` option: - - -#### ❌ Incorrect + + ```ts option='{ "prefer": "parameter-property" }' class Foo { @@ -454,7 +480,8 @@ class Foo { } ``` -#### βœ… Correct + + ```ts option='{ "prefer": "parameter-property" }' class Foo { @@ -480,6 +507,9 @@ class Foo { } ``` + + + ## When Not To Use It If you don't care about which style of parameter properties in constructors is used in your classes, then you will not need this rule. diff --git a/packages/eslint-plugin/docs/rules/prefer-as-const.md b/packages/eslint-plugin/docs/rules/prefer-as-const.mdx similarity index 88% rename from packages/eslint-plugin/docs/rules/prefer-as-const.md rename to packages/eslint-plugin/docs/rules/prefer-as-const.mdx index 7bd979a84f72..e7237661d230 100644 --- a/packages/eslint-plugin/docs/rules/prefer-as-const.md +++ b/packages/eslint-plugin/docs/rules/prefer-as-const.mdx @@ -2,6 +2,9 @@ description: 'Enforce the use of `as const` over literal type.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/prefer-as-const** for documentation. @@ -16,9 +19,8 @@ This rule reports when an `as` with an explicit literal type can be replaced wit ## Examples - - -### ❌ Incorrect + + ```ts let bar: 2 = 2; @@ -26,7 +28,8 @@ let foo = <'bar'>'bar'; let foo = { bar: 'baz' as 'baz' }; ``` -### βœ… Correct + + ```ts let foo = 'bar'; @@ -37,7 +40,8 @@ let foo = 'bar'; let foo = { bar: 'baz' }; ``` - + + ## When Not To Use It diff --git a/packages/eslint-plugin/docs/rules/prefer-destructuring.md b/packages/eslint-plugin/docs/rules/prefer-destructuring.mdx similarity index 80% rename from packages/eslint-plugin/docs/rules/prefer-destructuring.md rename to packages/eslint-plugin/docs/rules/prefer-destructuring.mdx index 5980b81dbae8..9e3d43b2229a 100644 --- a/packages/eslint-plugin/docs/rules/prefer-destructuring.md +++ b/packages/eslint-plugin/docs/rules/prefer-destructuring.mdx @@ -2,6 +2,9 @@ description: 'Require destructuring from arrays and/or objects.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/prefer-destructuring** for documentation. @@ -11,35 +14,37 @@ description: 'Require destructuring from arrays and/or objects.' This rule extends the base [`eslint/prefer-destructuring`](https://eslint.org/docs/latest/rules/prefer-destructuring) rule. It adds support for TypeScript's type annotations in variable declarations. - + -### `eslint/prefer-destructuring` + ```ts const x: string = obj.x; // This is incorrect and the auto fixer provides following untyped fix. // const { x } = obj; ``` -### `@typescript-eslint/prefer-destructuring` + + ```ts const x: string = obj.x; // This is correct by default. You can also forbid this by an option. ``` - + + And it infers binding patterns more accurately thanks to the type checker. - - -### ❌ Incorrect + + ```ts const x = ['a']; const y = x[0]; ``` -### βœ… Correct + + ```ts const x = { 0: 'a' }; @@ -49,6 +54,9 @@ const y = x[0]; It is correct when `enforceForRenamedProperties` is not true. Valid destructuring syntax is renamed style like `{ 0: y } = x` rather than `[y] = x` because `x` is not iterable. + + + ## Options This rule adds the following options: @@ -76,16 +84,19 @@ When set to `true`, type annotated variable declarations are enforced to use des Examples with `{ enforceForDeclarationWithTypeAnnotation: true }`: - - -### ❌ Incorrect + + ```ts const x: string = obj.x; ``` -### βœ… Correct + + ```ts const { x }: { x: string } = obj; ``` + + + diff --git a/packages/eslint-plugin/docs/rules/prefer-enum-initializers.md b/packages/eslint-plugin/docs/rules/prefer-enum-initializers.mdx similarity index 86% rename from packages/eslint-plugin/docs/rules/prefer-enum-initializers.md rename to packages/eslint-plugin/docs/rules/prefer-enum-initializers.mdx index 3d9b9b2e801c..1c208ed4031a 100644 --- a/packages/eslint-plugin/docs/rules/prefer-enum-initializers.md +++ b/packages/eslint-plugin/docs/rules/prefer-enum-initializers.mdx @@ -2,6 +2,9 @@ description: 'Require each enum member value to be explicitly initialized.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/prefer-enum-initializers** for documentation. @@ -15,9 +18,8 @@ This rule recommends having each `enum` member value explicitly initialized. ## Examples - - -### ❌ Incorrect + + ```ts enum Status { @@ -37,7 +39,8 @@ enum Color { } ``` -### βœ… Correct + + ```ts enum Status { @@ -57,6 +60,9 @@ enum Color { } ``` + + + ## When Not To Use It If you don't care about `enum`s having implicit values you can safely disable this rule. diff --git a/packages/eslint-plugin/docs/rules/prefer-find.md b/packages/eslint-plugin/docs/rules/prefer-find.mdx similarity index 89% rename from packages/eslint-plugin/docs/rules/prefer-find.md rename to packages/eslint-plugin/docs/rules/prefer-find.mdx index 62de96826809..66089f555807 100644 --- a/packages/eslint-plugin/docs/rules/prefer-find.md +++ b/packages/eslint-plugin/docs/rules/prefer-find.mdx @@ -2,6 +2,9 @@ description: 'Enforce the use of Array.prototype.find() over Array.prototype.filter() followed by [0] when looking for a single result.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/prefer-find** for documentation. @@ -18,9 +21,8 @@ Therefore, when fixing errors from this rule, be sure that your `.filter()` call ::: - - -### ❌ Incorrect + + ```ts [1, 2, 3].filter(x => x > 1)[0]; @@ -28,12 +30,16 @@ Therefore, when fixing errors from this rule, be sure that your `.filter()` call [1, 2, 3].filter(x => x > 1).at(0); ``` -### βœ… Correct + + ```ts [1, 2, 3].find(x => x > 1); ``` + + + ## When Not To Use It If you intentionally use patterns like `.filter(callback)[0]` to execute side effects in `callback` on all array elements, you will want to avoid this rule. diff --git a/packages/eslint-plugin/docs/rules/prefer-for-of.md b/packages/eslint-plugin/docs/rules/prefer-for-of.mdx similarity index 81% rename from packages/eslint-plugin/docs/rules/prefer-for-of.md rename to packages/eslint-plugin/docs/rules/prefer-for-of.mdx index ceaeecce1e8a..0399c781ddd7 100644 --- a/packages/eslint-plugin/docs/rules/prefer-for-of.md +++ b/packages/eslint-plugin/docs/rules/prefer-for-of.mdx @@ -2,6 +2,9 @@ description: 'Enforce the use of `for-of` loop over the standard `for` loop where possible.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/prefer-for-of** for documentation. @@ -14,9 +17,8 @@ This rule recommends a for-of loop when the loop index is only used to read from ## Examples - - -### ❌ Incorrect + + ```ts declare const array: string[]; @@ -26,7 +28,8 @@ for (let i = 0; i < array.length; i++) { } ``` -### βœ… Correct + + ```ts declare const array: string[]; @@ -41,4 +44,7 @@ for (let i = 0; i < array.length; i++) { } ``` - + + + +{/* Intentionally Omitted: When Not To Use It */} diff --git a/packages/eslint-plugin/docs/rules/prefer-function-type.md b/packages/eslint-plugin/docs/rules/prefer-function-type.mdx similarity index 92% rename from packages/eslint-plugin/docs/rules/prefer-function-type.md rename to packages/eslint-plugin/docs/rules/prefer-function-type.mdx index c3e84ac491c1..e6a698843cc0 100644 --- a/packages/eslint-plugin/docs/rules/prefer-function-type.md +++ b/packages/eslint-plugin/docs/rules/prefer-function-type.mdx @@ -2,6 +2,9 @@ description: 'Enforce using function types instead of interfaces with call signatures.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/prefer-function-type** for documentation. @@ -17,9 +20,8 @@ This rule suggests using a function type instead of an interface or object type ## Examples - - -### ❌ Incorrect + + ```ts interface Example { @@ -40,7 +42,8 @@ interface ReturnsSelf { } ``` -### βœ… Correct + + ```ts type Example = () => string; @@ -82,6 +85,9 @@ interface Overloaded { type Intersection = ((data: string) => number) & ((id: number) => string); ``` + + + ## When Not To Use It If you specifically want to use an interface or type literal with a single call signature for stylistic reasons, you can avoid this rule. diff --git a/packages/eslint-plugin/docs/rules/prefer-includes.md b/packages/eslint-plugin/docs/rules/prefer-includes.mdx similarity index 90% rename from packages/eslint-plugin/docs/rules/prefer-includes.md rename to packages/eslint-plugin/docs/rules/prefer-includes.mdx index 9a5db1fbf894..eb79a9258cc9 100644 --- a/packages/eslint-plugin/docs/rules/prefer-includes.md +++ b/packages/eslint-plugin/docs/rules/prefer-includes.mdx @@ -2,6 +2,9 @@ description: 'Enforce `includes` method over `indexOf` method.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/prefer-includes** for documentation. @@ -17,9 +20,8 @@ Additionally, this rule reports the tests of simple regular expressions in favor ## Examples - - -### ❌ Incorrect + + ```ts const str: string; @@ -42,7 +44,8 @@ userDefined.indexOf(value) >= 0; /example/.test(str); ``` -### βœ… Correct + + ```ts const str: string; @@ -72,4 +75,7 @@ declare const mismatchExample: { mismatchExample.indexOf(value) >= 0; ``` - + + + +{/* Intentionally Omitted: When Not To Use It */} diff --git a/packages/eslint-plugin/docs/rules/prefer-literal-enum-member.md b/packages/eslint-plugin/docs/rules/prefer-literal-enum-member.mdx similarity index 90% rename from packages/eslint-plugin/docs/rules/prefer-literal-enum-member.md rename to packages/eslint-plugin/docs/rules/prefer-literal-enum-member.mdx index de1ca2942b2e..a2088d0d1956 100644 --- a/packages/eslint-plugin/docs/rules/prefer-literal-enum-member.md +++ b/packages/eslint-plugin/docs/rules/prefer-literal-enum-member.mdx @@ -2,6 +2,9 @@ description: 'Require all enum members to be literal values.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/prefer-literal-enum-member** for documentation. @@ -30,9 +33,8 @@ This rule reports when an enum member is given a value that is not a literal. ## Examples - - -### ❌ Incorrect + + ```ts const str = 'Test'; @@ -45,7 +47,8 @@ enum Invalid { } ``` -### βœ… Correct + + ```ts enum Valid { @@ -57,7 +60,8 @@ enum Valid { } ``` - + + ## Options @@ -67,9 +71,8 @@ When set to `true` will allow you to use bitwise expressions in enum initializer Examples of code for the `{ "allowBitwiseExpressions": true }` option: - - -### ❌ Incorrect + + ```ts option='{ "allowBitwiseExpressions": true }' const x = 1; @@ -84,7 +87,8 @@ enum Foo { } ``` -### βœ… Correct + + ```ts option='{ "allowBitwiseExpressions": true }' enum Foo { @@ -98,6 +102,9 @@ enum Foo { } ``` + + + ## When Not To Use It If you want use anything other than simple literals as an enum value, this rule might not be for you. diff --git a/packages/eslint-plugin/docs/rules/prefer-namespace-keyword.md b/packages/eslint-plugin/docs/rules/prefer-namespace-keyword.mdx similarity index 89% rename from packages/eslint-plugin/docs/rules/prefer-namespace-keyword.md rename to packages/eslint-plugin/docs/rules/prefer-namespace-keyword.mdx index 97af2be93fa6..02dc462820ff 100644 --- a/packages/eslint-plugin/docs/rules/prefer-namespace-keyword.md +++ b/packages/eslint-plugin/docs/rules/prefer-namespace-keyword.mdx @@ -2,6 +2,9 @@ description: 'Require using `namespace` keyword over `module` keyword to declare custom TypeScript modules.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/prefer-namespace-keyword** for documentation. @@ -18,15 +21,15 @@ This rule reports when the `module` keyword is used instead of `namespace`. ## Examples - - -### ❌ Incorrect + + ```ts module Example {} ``` -### βœ… Correct + + ```ts namespace Example {} @@ -34,7 +37,8 @@ namespace Example {} declare module 'foo' {} ``` - + + ## When Not To Use It diff --git a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.mdx similarity index 99% rename from packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md rename to packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.mdx index 0db387ab9a21..3215b83dc832 100644 --- a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md +++ b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.mdx @@ -2,6 +2,9 @@ description: 'Enforce using the nullish coalescing operator instead of logical assignments or chaining.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/prefer-nullish-coalescing** for documentation. diff --git a/packages/eslint-plugin/docs/rules/prefer-optional-chain.md b/packages/eslint-plugin/docs/rules/prefer-optional-chain.mdx similarity index 79% rename from packages/eslint-plugin/docs/rules/prefer-optional-chain.md rename to packages/eslint-plugin/docs/rules/prefer-optional-chain.mdx index d09d5b046793..c9a63b6dc8d1 100644 --- a/packages/eslint-plugin/docs/rules/prefer-optional-chain.md +++ b/packages/eslint-plugin/docs/rules/prefer-optional-chain.mdx @@ -2,6 +2,9 @@ description: 'Enforce using concise optional chain expressions instead of chained logical ands, negated logical ors, or empty objects.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/prefer-optional-chain** for documentation. @@ -14,9 +17,8 @@ This rule reports on code where an `&&` operator can be safely replaced with `?. ## Examples - - -### ❌ Incorrect + + ```ts foo && foo.a && foo.a.b && foo.a.b.c; @@ -41,7 +43,8 @@ foo && foo.a.b.c.d.e; ``` -### βœ… Correct + + ```ts foo?.a?.b?.c; @@ -55,7 +58,8 @@ foo?.a?.b?.c?.d?.e; !foo?.bar?.baz?.(); ``` - + + ## Options @@ -64,7 +68,7 @@ Specifically the argument of the not operator (`!loose`) or a bare value in a lo ### `allowPotentiallyUnsafeFixesThatModifyTheReturnTypeIKnowWhatImDoing` -When this option is `true`, the rule will not provide an auto-fixer for cases where the return type of the expression would change. For example for the expression `!foo || foo.bar` the return type of the expression is `true | T`, however for the equivalent optional chain `foo?.bar` the return type of the expression is `undefined | T`. Thus changing the code from a logical expression to an optional chain expression has altered the type of the expression. +When this option is `true`, the rule will provide an auto-fixer for cases where the return type of the expression would change. For example for the expression `!foo || foo.bar` the return type of the expression is `true | T`, however for the equivalent optional chain `foo?.bar` the return type of the expression is `undefined | T`. Thus changing the code from a logical expression to an optional chain expression has altered the type of the expression. In some cases this distinction _may_ matter - which is why these fixers are considered unsafe - they may break the build! For example in the following code: @@ -87,9 +91,9 @@ When this option is `false` unsafe cases will have suggestion fixers provided in When this option is `true` the rule will check operands that are typed as `any` when inspecting "loose boolean" operands. - + -#### ❌ Incorrect for `checkAny: true` + ```ts option='{ "checkAny": true }' declare const thing: any; @@ -97,7 +101,9 @@ declare const thing: any; thing && thing.toString(); ``` -#### βœ… Correct for `checkAny: false` + + + for `checkAny: false` ```ts option='{ "checkAny": false }' declare const thing: any; @@ -105,15 +111,16 @@ declare const thing: any; thing && thing.toString(); ``` - + + ### `checkUnknown` When this option is `true` the rule will check operands that are typed as `unknown` when inspecting "loose boolean" operands. - + -#### ❌ Incorrect for `checkUnknown: true` + ```ts option='{ "checkUnknown": true }' declare const thing: unknown; @@ -121,7 +128,9 @@ declare const thing: unknown; thing && thing.toString(); ``` -#### βœ… Correct for `checkUnknown: false` + + + for `checkUnknown: false` ```ts option='{ "checkUnknown": false }' declare const thing: unknown; @@ -129,15 +138,16 @@ declare const thing: unknown; thing && thing.toString(); ``` - + + ### `checkString` When this option is `true` the rule will check operands that are typed as `string` when inspecting "loose boolean" operands. - + -#### ❌ Incorrect for `checkString: true` + ```ts option='{ "checkString": true }' declare const thing: string; @@ -145,7 +155,9 @@ declare const thing: string; thing && thing.toString(); ``` -#### βœ… Correct for `checkString: false` + + + for `checkString: false` ```ts option='{ "checkString": false }' declare const thing: string; @@ -153,15 +165,16 @@ declare const thing: string; thing && thing.toString(); ``` - + + ### `checkNumber` When this option is `true` the rule will check operands that are typed as `number` when inspecting "loose boolean" operands. - + -#### ❌ Incorrect for `checkNumber: true` + ```ts option='{ "checkNumber": true }' declare const thing: number; @@ -169,7 +182,9 @@ declare const thing: number; thing && thing.toString(); ``` -#### βœ… Correct for `checkNumber: false` + + + for `checkNumber: false` ```ts option='{ "checkNumber": false }' declare const thing: number; @@ -177,15 +192,16 @@ declare const thing: number; thing && thing.toString(); ``` - + + ### `checkBoolean` When this option is `true` the rule will check operands that are typed as `boolean` when inspecting "loose boolean" operands. - + -#### ❌ Incorrect for `checkBoolean: true` + ```ts option='{ "checkBoolean": true }' declare const thing: boolean; @@ -193,7 +209,9 @@ declare const thing: boolean; thing && thing.toString(); ``` -#### βœ… Correct for `checkBoolean: false` + + + for `checkBoolean: false` ```ts option='{ "checkBoolean": false }' declare const thing: boolean; @@ -201,15 +219,16 @@ declare const thing: boolean; thing && thing.toString(); ``` - + + ### `checkBigInt` When this option is `true` the rule will check operands that are typed as `bigint` when inspecting "loose boolean" operands. - + -#### ❌ Incorrect for `checkBigInt: true` + ```ts option='{ "checkBigInt": true }' declare const thing: bigint; @@ -217,7 +236,9 @@ declare const thing: bigint; thing && thing.toString(); ``` -#### βœ… Correct for `checkBigInt: false` + + + for `checkBigInt: false` ```ts option='{ "checkBigInt": false }' declare const thing: bigint; @@ -225,22 +246,25 @@ declare const thing: bigint; thing && thing.toString(); ``` - + + ### `requireNullish` When this option is `true` the rule will skip operands that are not typed with `null` and/or `undefined` when inspecting "loose boolean" operands. - + -#### ❌ Incorrect for `requireNullish: true` + ```ts option='{ "requireNullish": true }' declare const thing1: string | null; thing1 && thing1.toString(); ``` -#### βœ… Correct for `requireNullish: true` + + + for `requireNullish: true` ```ts option='{ "requireNullish": true }' declare const thing1: string | null; @@ -250,7 +274,8 @@ declare const thing2: string; thing2 && thing2.toString(); ``` - + + ## When Not To Use It diff --git a/packages/eslint-plugin/docs/rules/prefer-promise-reject-errors.md b/packages/eslint-plugin/docs/rules/prefer-promise-reject-errors.mdx similarity index 85% rename from packages/eslint-plugin/docs/rules/prefer-promise-reject-errors.md rename to packages/eslint-plugin/docs/rules/prefer-promise-reject-errors.mdx index 28e465cf00f1..3a5f34dd9432 100644 --- a/packages/eslint-plugin/docs/rules/prefer-promise-reject-errors.md +++ b/packages/eslint-plugin/docs/rules/prefer-promise-reject-errors.mdx @@ -2,6 +2,9 @@ description: 'Require using Error objects as Promise rejection reasons.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/prefer-promise-reject-errors** for documentation. @@ -11,9 +14,8 @@ It uses type information to enforce that `Promise`s are only rejected with `Erro ## Examples - - -### ❌ Incorrect + + ```ts Promise.reject('error'); @@ -29,7 +31,8 @@ new Promise((resolve, reject) => { }); ``` -### βœ… Correct + + ```ts Promise.reject(new Error()); @@ -48,3 +51,6 @@ new Promise((resolve, reject) => { return reject(new CustomError()); }); ``` + + + diff --git a/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.md b/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.mdx similarity index 95% rename from packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.md rename to packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.mdx index e75ec860faa9..322fb804b366 100644 --- a/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.md +++ b/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.mdx @@ -2,6 +2,9 @@ description: 'Require function parameters to be typed as `readonly` to prevent accidental mutation of inputs.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/prefer-readonly-parameter-types** for documentation. @@ -21,9 +24,8 @@ A type is considered readonly if: ## Examples - - -### ❌ Incorrect + + ```ts function array1(arg: string[]) {} // array is not readonly @@ -65,7 +67,8 @@ interface Foo { } ``` -### βœ… Correct + + ```ts function array1(arg: readonly string[]) {} @@ -130,6 +133,9 @@ interface Foo { } ``` + + + ## Options ### `allow` @@ -157,9 +163,8 @@ Examples of code for this rule with: } ``` - - -#### ❌ Incorrect + + ```ts option='{"allow":["$",{"from":"file","name":"Foo"},{"from":"lib","name":"HTMLElement"},{"from":"package","name":"Bar","package":"bar-lib"}]}' interface ThisIsMutable { @@ -203,7 +208,8 @@ function fn2(arg: HTMLElement) {} function fn3(arg: Bar) {} ``` -#### βœ… Correct + + ```ts option='{"allow":["$",{"from":"file","name":"Foo"},{"from":"lib","name":"HTMLElement"},{"from":"package","name":"Bar","package":"bar-lib"}]}' interface Foo { @@ -246,6 +252,9 @@ import { Foo } from './foo'; function fn(arg: Foo) {} ``` + + + ### `checkParameterProperties` This option allows you to enable or disable the checking of parameter properties. @@ -253,9 +262,8 @@ Because parameter properties create properties on the class, it may be undesirab Examples of code for this rule with `{checkParameterProperties: true}`: - - -#### ❌ Incorrect + + ```ts option='{ "checkParameterProperties": true }' class Foo { @@ -263,7 +271,8 @@ class Foo { } ``` -#### βœ… Correct + + ```ts option='{ "checkParameterProperties": true }' class Foo { @@ -271,7 +280,8 @@ class Foo { } ``` - + + Examples of **correct** code for this rule with `{checkParameterProperties: false}`: @@ -290,9 +300,8 @@ This option allows you to ignore parameters which don't explicitly specify a typ Examples of code for this rule with `{ignoreInferredTypes: true}`: - - -#### ❌ Incorrect + + ```ts option='{ "ignoreInferredTypes": true }' import { acceptsCallback, CallbackOptions } from 'external-dependency'; @@ -315,7 +324,8 @@ export const acceptsCallback: AcceptsCallback; -#### βœ… Correct + + ```ts option='{ "ignoreInferredTypes": true }' import { acceptsCallback } from 'external-dependency'; @@ -338,15 +348,17 @@ export const acceptsCallback: AcceptsCallback; + + + ### `treatMethodsAsReadonly` This option allows you to treat all mutable methods as though they were readonly. This may be desirable when you are never reassigning methods. Examples of code for this rule with `{treatMethodsAsReadonly: false}`: - - -#### ❌ Incorrect + + ```ts option='{ "treatMethodsAsReadonly": false }' type MyType = { @@ -356,7 +368,8 @@ type MyType = { function foo(arg: MyType) {} ``` -#### βœ… Correct + + ```ts option='{ "treatMethodsAsReadonly": false }' type MyType = Readonly<{ @@ -372,7 +385,8 @@ type MyOtherType = { function bar(arg: MyOtherType) {} ``` - + + Examples of **correct** code for this rule with `{treatMethodsAsReadonly: true}`: diff --git a/packages/eslint-plugin/docs/rules/prefer-readonly.md b/packages/eslint-plugin/docs/rules/prefer-readonly.mdx similarity index 89% rename from packages/eslint-plugin/docs/rules/prefer-readonly.md rename to packages/eslint-plugin/docs/rules/prefer-readonly.mdx index bd201c0d6bfe..a3fe49e0336c 100644 --- a/packages/eslint-plugin/docs/rules/prefer-readonly.md +++ b/packages/eslint-plugin/docs/rules/prefer-readonly.mdx @@ -2,6 +2,9 @@ description: "Require private members to be marked as `readonly` if they're never modified outside of the constructor." --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/prefer-readonly** for documentation. @@ -13,9 +16,8 @@ This rule reports on private members are marked as `readonly` if they're never m ## Examples - - -### ❌ Incorrect + + ```ts class Container { @@ -34,7 +36,8 @@ class Container { } ``` -### βœ… Correct + + ```ts class Container { @@ -60,6 +63,9 @@ class Container { } ``` + + + ## Options ### `onlyInlineLambdas` @@ -77,9 +83,8 @@ You may pass `"onlyInlineLambdas": true` as a rule option within an object to re Example of code for the `{ "onlyInlineLambdas": true }` options: - - -#### ❌ Incorrect + + ```ts option='{ "onlyInlineLambdas": true }' class Container { @@ -89,7 +94,8 @@ class Container { } ``` -#### βœ… Correct + + ```ts option='{ "onlyInlineLambdas": true }' class Container { @@ -97,6 +103,9 @@ class Container { } ``` + + + ## When Not To Use It If you aren't trying to enforce strong immutability guarantees, this rule may be too restrictive for your project. diff --git a/packages/eslint-plugin/docs/rules/prefer-reduce-type-parameter.md b/packages/eslint-plugin/docs/rules/prefer-reduce-type-parameter.mdx similarity index 92% rename from packages/eslint-plugin/docs/rules/prefer-reduce-type-parameter.md rename to packages/eslint-plugin/docs/rules/prefer-reduce-type-parameter.mdx index 5419248f8b5e..eff53ee96c84 100644 --- a/packages/eslint-plugin/docs/rules/prefer-reduce-type-parameter.md +++ b/packages/eslint-plugin/docs/rules/prefer-reduce-type-parameter.mdx @@ -2,6 +2,9 @@ description: 'Enforce using type parameter when calling `Array#reduce` instead of casting.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/prefer-reduce-type-parameter** for documentation. @@ -23,9 +26,8 @@ It will suggest instead pass the asserted type to `Array#reduce` as a generic ty ## Examples - - -### ❌ Incorrect + + ```ts [1, 2, 3].reduce((arr, num) => arr.concat(num * 2), [] as number[]); @@ -39,7 +41,8 @@ It will suggest instead pass the asserted type to `Array#reduce` as a generic ty ); ``` -### βœ… Correct + + ```ts [1, 2, 3].reduce((arr, num) => arr.concat(num * 2), []); @@ -53,6 +56,9 @@ It will suggest instead pass the asserted type to `Array#reduce` as a generic ty ); ``` + + + ## When Not To Use It This rule can sometimes be difficult to work around when creating objects using a `.reduce`. diff --git a/packages/eslint-plugin/docs/rules/prefer-regexp-exec.md b/packages/eslint-plugin/docs/rules/prefer-regexp-exec.mdx similarity index 87% rename from packages/eslint-plugin/docs/rules/prefer-regexp-exec.md rename to packages/eslint-plugin/docs/rules/prefer-regexp-exec.mdx index 0d9f127be384..1536638f26cb 100644 --- a/packages/eslint-plugin/docs/rules/prefer-regexp-exec.md +++ b/packages/eslint-plugin/docs/rules/prefer-regexp-exec.mdx @@ -2,6 +2,9 @@ description: 'Enforce `RegExp#exec` over `String#match` if no global flag is provided.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/prefer-regexp-exec** for documentation. @@ -15,9 +18,8 @@ This rule reports when a `String#match` call can be replaced with an equivalent ## Examples - - -### ❌ Incorrect + + ```ts 'something'.match(/thing/); @@ -29,7 +31,8 @@ const search = /thing/; text.match(search); ``` -### βœ… Correct + + ```ts /thing/.exec('something'); @@ -41,6 +44,9 @@ const search = /thing/; search.exec(text); ``` + + + ## When Not To Use It If you prefer consistent use of `String#match` for both with `g` flag and without it, you can turn this rule off. diff --git a/packages/eslint-plugin/docs/rules/prefer-return-this-type.md b/packages/eslint-plugin/docs/rules/prefer-return-this-type.mdx similarity index 91% rename from packages/eslint-plugin/docs/rules/prefer-return-this-type.md rename to packages/eslint-plugin/docs/rules/prefer-return-this-type.mdx index b09c03ba52fc..1c45bf8c29a1 100644 --- a/packages/eslint-plugin/docs/rules/prefer-return-this-type.md +++ b/packages/eslint-plugin/docs/rules/prefer-return-this-type.mdx @@ -2,6 +2,9 @@ description: 'Enforce that `this` is used when only `this` type is returned.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/prefer-return-this-type** for documentation. @@ -38,9 +41,8 @@ cat.eat().meow(); ## Examples - - -### ❌ Incorrect + + ```ts class Foo { @@ -56,7 +58,8 @@ class Foo { } ``` -### βœ… Correct + + ```ts class Foo { @@ -82,6 +85,9 @@ class Derived extends Base { } ``` + + + ## When Not To Use It If you don't use method chaining or explicit return values, you can safely turn this rule off. diff --git a/packages/eslint-plugin/docs/rules/prefer-string-starts-ends-with.md b/packages/eslint-plugin/docs/rules/prefer-string-starts-ends-with.mdx similarity index 92% rename from packages/eslint-plugin/docs/rules/prefer-string-starts-ends-with.md rename to packages/eslint-plugin/docs/rules/prefer-string-starts-ends-with.mdx index 907909f78579..cae79c26679b 100644 --- a/packages/eslint-plugin/docs/rules/prefer-string-starts-ends-with.md +++ b/packages/eslint-plugin/docs/rules/prefer-string-starts-ends-with.mdx @@ -2,6 +2,9 @@ description: 'Enforce using `String#startsWith` and `String#endsWith` over other equivalent methods of checking substrings.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/prefer-string-starts-ends-with** for documentation. @@ -14,9 +17,8 @@ This rule reports when a string method can be replaced safely with `String#start ## Examples - - -### ❌ Incorrect + + ```ts declare const foo: string; @@ -40,7 +42,8 @@ foo.match(/bar$/) != null; /bar$/.test(foo); ``` -### βœ… Correct + + ```ts declare const foo: string; @@ -52,7 +55,8 @@ foo.startsWith('bar'); foo.endsWith('bar'); ``` - + + ## Options diff --git a/packages/eslint-plugin/docs/rules/prefer-ts-expect-error.md b/packages/eslint-plugin/docs/rules/prefer-ts-expect-error.mdx similarity index 92% rename from packages/eslint-plugin/docs/rules/prefer-ts-expect-error.md rename to packages/eslint-plugin/docs/rules/prefer-ts-expect-error.mdx index 84dde77f4793..17ade492147b 100644 --- a/packages/eslint-plugin/docs/rules/prefer-ts-expect-error.md +++ b/packages/eslint-plugin/docs/rules/prefer-ts-expect-error.mdx @@ -2,6 +2,9 @@ description: 'Enforce using `@ts-expect-error` over `@ts-ignore`.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/prefer-ts-expect-error** for documentation. @@ -16,9 +19,8 @@ This is dangerous, as if a new error arises on that line it'll be suppressed by This rule reports any usage of `@ts-ignore`, including a fixer to replace with `@ts-expect-error`. - - -### ❌ Incorrect + + ```ts // @ts-ignore @@ -39,7 +41,8 @@ const isOptionEnabled = (key: string): boolean => { }; ``` -### βœ… Correct + + ```ts // @ts-expect-error @@ -60,6 +63,9 @@ const isOptionEnabled = (key: string): boolean => { }; ``` + + + ## When Not To Use It If you are compiling against multiple versions of TypeScript and using `@ts-ignore` to ignore version-specific type errors, this rule might get in your way. diff --git a/packages/eslint-plugin/docs/rules/promise-function-async.md b/packages/eslint-plugin/docs/rules/promise-function-async.mdx similarity index 91% rename from packages/eslint-plugin/docs/rules/promise-function-async.md rename to packages/eslint-plugin/docs/rules/promise-function-async.mdx index c85c00af40fc..7a2a39559e4d 100644 --- a/packages/eslint-plugin/docs/rules/promise-function-async.md +++ b/packages/eslint-plugin/docs/rules/promise-function-async.mdx @@ -2,6 +2,9 @@ description: 'Require any function or method that returns a Promise to be marked async.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/promise-function-async** for documentation. @@ -21,9 +24,8 @@ This rule's practice removes a requirement for creating code to handle both case Examples of code for this rule - - -### ❌ Incorrect + + ```ts const arrowFunctionReturnsPromise = () => Promise.resolve('value'); @@ -37,7 +39,8 @@ function functionReturnsUnionWithPromiseImplicitly(p: boolean) { } ``` -### βœ… Correct + + ```ts const arrowFunctionReturnsPromise = async () => Promise.resolve('value'); @@ -58,6 +61,9 @@ async function functionReturnsUnionWithPromiseImplicitly(p: boolean) { } ``` + + + ## Options ### `allowAny` @@ -67,20 +73,23 @@ If you want additional safety, consider turning this option off, as it makes the Examples of code with `{ "allowAny": false }`: - - -#### ❌ Incorrect + + ```ts option='{ "allowAny": false }' const returnsAny = () => ({}) as any; ``` -#### βœ… Correct + + ```ts option='{ "allowAny": false }' const returnsAny = async () => ({}) as any; ``` + + + ### `allowedPromiseNames` For projects that use constructs other than the global built-in `Promise` for asynchronous code. @@ -88,9 +97,8 @@ This option allows specifying string names of classes or interfaces that cause a Examples of code with `{ "allowedPromiseNames": ["Bluebird"] }`: - - -#### ❌ Incorrect + + ```ts option='{ "allowedPromiseNames": ["Bluebird"] }' import { Bluebird } from 'bluebird'; @@ -98,7 +106,8 @@ import { Bluebird } from 'bluebird'; const returnsBluebird = () => new Bluebird(() => {}); ``` -#### βœ… Correct + + ```ts option='{ "allowedPromiseNames": ["Bluebird"] }' import { Bluebird } from 'bluebird'; @@ -106,6 +115,9 @@ import { Bluebird } from 'bluebird'; const returnsBluebird = async () => new Bluebird(() => {}); ``` + + + ### `checkArrowFunctions` Whether to check arrow functions. diff --git a/packages/eslint-plugin/docs/rules/quotes.md b/packages/eslint-plugin/docs/rules/quotes.mdx similarity index 85% rename from packages/eslint-plugin/docs/rules/quotes.md rename to packages/eslint-plugin/docs/rules/quotes.mdx index 62c6051c4094..800bb2b24580 100644 --- a/packages/eslint-plugin/docs/rules/quotes.md +++ b/packages/eslint-plugin/docs/rules/quotes.mdx @@ -2,6 +2,9 @@ description: 'Enforce the consistent use of either backticks, double, or single quotes.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/quotes** for documentation. diff --git a/packages/eslint-plugin/docs/rules/require-array-sort-compare.md b/packages/eslint-plugin/docs/rules/require-array-sort-compare.mdx similarity index 87% rename from packages/eslint-plugin/docs/rules/require-array-sort-compare.md rename to packages/eslint-plugin/docs/rules/require-array-sort-compare.mdx index ddef5a699839..5c23955cc823 100644 --- a/packages/eslint-plugin/docs/rules/require-array-sort-compare.md +++ b/packages/eslint-plugin/docs/rules/require-array-sort-compare.mdx @@ -2,6 +2,9 @@ description: 'Require `Array#sort` and `Array#toSorted` calls to always provide a `compareFunction`.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/require-array-sort-compare** for documentation. @@ -21,9 +24,8 @@ This rule reports on any call to the sort methods that do not provide a `compare This rule aims to ensure all calls of the native sort methods provide a `compareFunction`, while ignoring calls to user-defined methods. - - -### ❌ Incorrect + + ```ts const array: any[]; @@ -35,7 +37,8 @@ array.sort(); stringArray.sort(); ``` -### βœ… Correct + + ```ts const array: any[]; @@ -47,15 +50,17 @@ array.sort((a, b) => a.localeCompare(b)); userDefinedType.sort(); ``` + + + ## Options ### `ignoreStringArrays` Examples of code for this rule with `{ ignoreStringArrays: true }`: - - -#### ❌ Incorrect + + ```ts option='{ "ignoreStringArrays": true }' const one = 1; @@ -64,7 +69,8 @@ const three = 3; [one, two, three].sort(); ``` -#### βœ… Correct + + ```ts option='{ "ignoreStringArrays": true }' const one = '1'; @@ -73,6 +79,9 @@ const three = '3'; [one, two, three].sort(); ``` + + + ## When Not To Use It If you intentionally want your arrays to be always sorted in a string-like manner, you can turn this rule off safely. diff --git a/packages/eslint-plugin/docs/rules/require-await.md b/packages/eslint-plugin/docs/rules/require-await.mdx similarity index 89% rename from packages/eslint-plugin/docs/rules/require-await.md rename to packages/eslint-plugin/docs/rules/require-await.mdx index e2ca2af1d53c..a600d9198271 100644 --- a/packages/eslint-plugin/docs/rules/require-await.md +++ b/packages/eslint-plugin/docs/rules/require-await.mdx @@ -2,6 +2,9 @@ description: 'Disallow async functions which have no `await` expression.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/require-await** for documentation. diff --git a/packages/eslint-plugin/docs/rules/restrict-plus-operands.md b/packages/eslint-plugin/docs/rules/restrict-plus-operands.mdx similarity index 84% rename from packages/eslint-plugin/docs/rules/restrict-plus-operands.md rename to packages/eslint-plugin/docs/rules/restrict-plus-operands.mdx index 868d1bdefc01..228ac7785094 100644 --- a/packages/eslint-plugin/docs/rules/restrict-plus-operands.md +++ b/packages/eslint-plugin/docs/rules/restrict-plus-operands.mdx @@ -2,6 +2,9 @@ description: 'Require both operands of addition to be the same type and be `bigint`, `number`, or `string`.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/restrict-plus-operands** for documentation. @@ -13,22 +16,25 @@ This rule reports when a `+` operation combines two values of different types, o ## Examples - - -### ❌ Incorrect + + ```ts let foo = 1n + 1; let fn = (a: string, b: never) => a + b; ``` -### βœ… Correct + + ```ts let foo = 1n + 1n; let fn = (a: string, b: string) => a + b; ``` + + + ## Options :::caution @@ -70,49 +76,54 @@ Safer alternatives to using the `allow*` options include: Examples of code for this rule with `{ allowAny: true }`: - - -#### ❌ Incorrect + + ```ts option='{ "allowAny": true }' let fn = (a: number, b: []) => a + b; let fn = (a: string, b: []) => a + b; ``` -#### βœ… Correct + + ```ts option='{ "allowAny": true }' let fn = (a: number, b: any) => a + b; let fn = (a: string, b: any) => a + b; ``` + + + ### `allowBoolean` Examples of code for this rule with `{ allowBoolean: true }`: - - -#### ❌ Incorrect + + ```ts option='{ "allowBoolean": true }' let fn = (a: number, b: unknown) => a + b; let fn = (a: string, b: unknown) => a + b; ``` -#### βœ… Correct + + ```ts option='{ "allowBoolean": true }' let fn = (a: number, b: boolean) => a + b; let fn = (a: string, b: boolean) => a + b; ``` + + + ### `allowNullish` Examples of code for this rule with `{ allowNullish: true }`: - - -#### ❌ Incorrect + + ```ts option='{ "allowNullish": true }' let fn = (a: number, b: unknown) => a + b; @@ -121,7 +132,8 @@ let fn = (a: string, b: unknown) => a + b; let fn = (a: string, b: never) => a + b; ``` -#### βœ… Correct + + ```ts option='{ "allowNullish": true }' let fn = (a: number, b: undefined) => a + b; @@ -130,51 +142,59 @@ let fn = (a: string, b: undefined) => a + b; let fn = (a: string, b: null) => a + b; ``` + + + ### `allowNumberAndString` Examples of code for this rule with `{ allowNumberAndString: true }`: - - -#### ❌ Incorrect + + ```ts option='{ "allowNumberAndString": true }' let fn = (a: number, b: unknown) => a + b; let fn = (a: number, b: never) => a + b; ``` -#### βœ… Correct + + ```ts option='{ "allowNumberAndString": true }' let fn = (a: number, b: string) => a + b; let fn = (a: number, b: number | string) => a + b; ``` + + + ### `allowRegExp` Examples of code for this rule with `{ allowRegExp: true }`: - - -#### ❌ Incorrect + + ```ts option='{ "allowRegExp": true }' let fn = (a: number, b: RegExp) => a + b; ``` -#### βœ… Correct + + ```ts option='{ "allowRegExp": true }' let fn = (a: string, b: RegExp) => a + b; ``` + + + ### `skipCompoundAssignments` Examples of code for this rule with `{ skipCompoundAssignments: false }`: - - -#### ❌ Incorrect + + ```ts option='{ "skipCompoundAssignments": true }' let foo: string | undefined; @@ -184,7 +204,8 @@ let bar: string = ''; bar += 0; ``` -#### βœ… Correct + + ```ts option='{ "skipCompoundAssignments": true }' let foo: number = 0; @@ -194,14 +215,17 @@ let bar = ''; bar += 'test'; ``` + + + ## When Not To Use It If you don't mind a risk of `"[object Object]"` or incorrect type coercions in your values, then you will not need this rule. ## Related To -- [`no-base-to-string`](./no-base-to-string.md) -- [`restrict-template-expressions`](./restrict-template-expressions.md) +- [`no-base-to-string`](./no-base-to-string.mdx) +- [`restrict-template-expressions`](./restrict-template-expressions.mdx) ## Further Reading diff --git a/packages/eslint-plugin/docs/rules/restrict-template-expressions.md b/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx similarity index 80% rename from packages/eslint-plugin/docs/rules/restrict-template-expressions.md rename to packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx index e7e295c443a9..c37de8fa7429 100644 --- a/packages/eslint-plugin/docs/rules/restrict-template-expressions.md +++ b/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx @@ -2,6 +2,9 @@ description: 'Enforce template literal expressions to be of `string` type.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/restrict-template-expressions** for documentation. @@ -12,19 +15,18 @@ This rule reports on values used in a template literal string that aren't string :::note -This rule intentionally does not allow objects with a custom `toString()` method to be used in template literals, because the stringification result may not be user-friendly. +The default settings of this rule intentionally do not allow objects with a custom `toString()` method to be used in template literals, because the stringification result may not be user-friendly. For example, arrays have a custom [`toString()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toString) method, which only calls `join()` internally, which joins the array elements with commas. This means that (1) array elements are not necessarily stringified to useful results (2) the commas don't have spaces after them, making the result not user-friendly. The best way to format arrays is to use [`Intl.ListFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat), which even supports adding the "and" conjunction where necessary. -You must explicitly call `object.toString()` if you want to use this object in a template literal. -The [`no-base-to-string`](./no-base-to-string.md) rule can be used to guard this case against producing `"[object Object]"` by accident. +You must explicitly call `object.toString()` if you want to use this object in a template literal, or turn on the `allowArray` option to specifically allow arrays. +The [`no-base-to-string`](./no-base-to-string.mdx) rule can be used to guard this case against producing `"[object Object]"` by accident. ::: ## Examples - - -### ❌ Incorrect + + ```ts const arg1 = [1, 2]; @@ -34,7 +36,8 @@ const arg2 = { name: 'Foo' }; const msg2 = `arg2 = ${arg2 || null}`; ``` -### βœ… Correct + + ```ts const arg = 'foo'; @@ -45,6 +48,9 @@ const stringWithKindProp: string & { _kind?: 'MyString' } = 'foo'; const msg3 = `stringWithKindProp = ${stringWithKindProp}`; ``` + + + ## Options ### `allowNumber` @@ -111,11 +117,20 @@ const arg = 'something'; const msg1 = typeof arg === 'string' ? arg : `arg = ${arg}`; ``` +### `allowArray` + +Examples of additional **correct** code for this rule with `{ allowArray: true }`: + +```ts option='{ "allowArray": true }' showPlaygroundButton +const arg = ['foo', 'bar']; +const msg1 = `arg = ${arg}`; +``` + ## When Not To Use It If you're not worried about incorrectly stringifying non-string values in template literals, then you likely don't need this rule. ## Related To -- [`no-base-to-string`](./no-base-to-string.md) -- [`restrict-plus-operands`](./restrict-plus-operands.md) +- [`no-base-to-string`](./no-base-to-string.mdx) +- [`restrict-plus-operands`](./restrict-plus-operands.mdx) diff --git a/packages/eslint-plugin/docs/rules/return-await.md b/packages/eslint-plugin/docs/rules/return-await.mdx similarity index 92% rename from packages/eslint-plugin/docs/rules/return-await.md rename to packages/eslint-plugin/docs/rules/return-await.mdx index 9a6cca55c36b..3e0599dc1f0e 100644 --- a/packages/eslint-plugin/docs/rules/return-await.md +++ b/packages/eslint-plugin/docs/rules/return-await.mdx @@ -2,6 +2,9 @@ description: 'Enforce consistent returning of awaited values.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/return-await** for documentation. @@ -33,9 +36,8 @@ Specifically: Examples of code with `in-try-catch`: - - -#### ❌ Incorrect + + ```ts option='"in-try-catch"' async function invalidInTryCatch1() { @@ -81,7 +83,8 @@ async function invalidInTryCatch6() { } ``` -#### βœ… Correct + + ```ts option='"in-try-catch"' async function validInTryCatch1() { @@ -127,15 +130,17 @@ async function validInTryCatch6() { } ``` + + + ### `always` Requires that all returned promises are `await`ed. Examples of code with `always`: - - -#### ❌ Incorrect + + ```ts option='"always"' async function invalidAlways1() { @@ -153,7 +158,8 @@ async function invalidAlways3() { } ``` -#### βœ… Correct + + ```ts option='"always"' async function validAlways1() { @@ -171,15 +177,17 @@ async function validAlways3() { } ``` + + + ### `never` Disallows all `await`ing any returned promises. Examples of code with `never`: - - -#### ❌ Incorrect + + ```ts option='"never"' async function invalidNever1() { @@ -197,7 +205,8 @@ async function invalidNever3() { } ``` -#### βœ… Correct + + ```ts option='"never"' async function validNever1() { @@ -214,3 +223,6 @@ async function validNever3() { return 'value'; } ``` + + + diff --git a/packages/eslint-plugin/docs/rules/semi.md b/packages/eslint-plugin/docs/rules/semi.mdx similarity index 71% rename from packages/eslint-plugin/docs/rules/semi.md rename to packages/eslint-plugin/docs/rules/semi.mdx index da30f0d9ddb3..a0cd3e3653b4 100644 --- a/packages/eslint-plugin/docs/rules/semi.md +++ b/packages/eslint-plugin/docs/rules/semi.mdx @@ -2,6 +2,9 @@ description: 'Require or disallow semicolons instead of ASI.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/semi** for documentation. @@ -9,4 +12,4 @@ description: 'Require or disallow semicolons instead of ASI.' This rule extends the base [`eslint/semi`](https://eslint.org/docs/rules/semi) rule. It adds support for TypeScript features that require semicolons. -See also the [`@typescript-eslint/member-delimiter-style`](member-delimiter-style.md) rule, which allows you to specify the delimiter for `type` and `interface` members. +See also the [`@typescript-eslint/member-delimiter-style`](member-delimiter-style.mdx) rule, which allows you to specify the delimiter for `type` and `interface` members. diff --git a/packages/eslint-plugin/docs/rules/sort-type-constituents.md b/packages/eslint-plugin/docs/rules/sort-type-constituents.mdx similarity index 87% rename from packages/eslint-plugin/docs/rules/sort-type-constituents.md rename to packages/eslint-plugin/docs/rules/sort-type-constituents.mdx index 10fe95e2bbe4..011fcf6d6a02 100644 --- a/packages/eslint-plugin/docs/rules/sort-type-constituents.md +++ b/packages/eslint-plugin/docs/rules/sort-type-constituents.mdx @@ -2,6 +2,9 @@ description: 'Enforce constituents of a type union/intersection to be sorted alphabetically.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/sort-type-constituents** for documentation. @@ -18,9 +21,8 @@ This rule reports on any types that aren't sorted alphabetically. ## Examples - - -### ❌ Incorrect + + ```ts type T1 = B | A; @@ -50,7 +52,8 @@ type T4 = | any; ``` -### βœ… Correct + + ```ts type T1 = A | B; @@ -80,6 +83,9 @@ type T4 = | [1, 2, 4]; ``` + + + ## Options ### `checkIntersections` @@ -88,40 +94,46 @@ Whether to check intersection types (`&`). Examples of code with `{ "checkIntersections": true }` (the default): - - -#### ❌ Incorrect + + ```ts option='{ "checkIntersections": true }' type ExampleIntersection = B & A; ``` -#### βœ… Correct + + ```ts option='{ "checkIntersections": true }' type ExampleIntersection = A & B; ``` + + + ### `checkUnions` Whether to check union types (`|`). Examples of code with `{ "checkUnions": true }` (the default): - - -#### ❌ Incorrect + + ```ts option='{ "checkUnions": true }' type ExampleUnion = B | A; ``` -#### βœ… Correct + + ```ts option='{ "checkUnions": true }' type ExampleUnion = A | B; ``` + + + ### `groupOrder` Each constituent of the type is placed into a group, and then the rule sorts alphabetically within each group. @@ -142,20 +154,23 @@ The ordering of groups is determined by this option. For example, configuring the rule with `{ "groupOrder": ["literal", "nullish" ]}`: - - -#### ❌ Incorrect + + ```ts option='{ "groupOrder": ["literal", "nullish" ]}' type ExampleGroup = null | 123; ``` -#### βœ… Correct + + ```ts option='{ "groupOrder": ["literal", "nullish" ]}' type ExampleGroup = 123 | null; ``` + + + ## When Not To Use It This rule is purely a stylistic rule for maintaining consistency in your project. diff --git a/packages/eslint-plugin/docs/rules/space-before-blocks.md b/packages/eslint-plugin/docs/rules/space-before-blocks.mdx similarity index 81% rename from packages/eslint-plugin/docs/rules/space-before-blocks.md rename to packages/eslint-plugin/docs/rules/space-before-blocks.mdx index 14497f7b62a7..ef48550d16d7 100644 --- a/packages/eslint-plugin/docs/rules/space-before-blocks.md +++ b/packages/eslint-plugin/docs/rules/space-before-blocks.mdx @@ -2,6 +2,9 @@ description: 'Enforce consistent spacing before blocks.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/space-before-blocks** for documentation. @@ -9,9 +12,8 @@ description: 'Enforce consistent spacing before blocks.' This rule extends the base [`eslint/space-before-blocks`](https://eslint.org/docs/rules/space-before-blocks) rule. It adds support for interfaces and enums. - - -### ❌ Incorrect + + ```ts @@ -25,7 +27,8 @@ interface State{ } ``` -### βœ… Correct + + ```ts enum Breakpoint { @@ -38,6 +41,9 @@ interface State { } ``` + + + ## Options In case a more specific options object is passed these blocks will follow `classes` configuration option. diff --git a/packages/eslint-plugin/docs/rules/space-before-function-paren.md b/packages/eslint-plugin/docs/rules/space-before-function-paren.mdx similarity index 86% rename from packages/eslint-plugin/docs/rules/space-before-function-paren.md rename to packages/eslint-plugin/docs/rules/space-before-function-paren.mdx index 92180ced2bc7..8fd3e915bc42 100644 --- a/packages/eslint-plugin/docs/rules/space-before-function-paren.md +++ b/packages/eslint-plugin/docs/rules/space-before-function-paren.mdx @@ -2,6 +2,9 @@ description: 'Enforce consistent spacing before function parenthesis.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/space-before-function-paren** for documentation. diff --git a/packages/eslint-plugin/docs/rules/space-infix-ops.md b/packages/eslint-plugin/docs/rules/space-infix-ops.mdx similarity index 85% rename from packages/eslint-plugin/docs/rules/space-infix-ops.md rename to packages/eslint-plugin/docs/rules/space-infix-ops.mdx index b6b0ecda788c..6293ca22e2f5 100644 --- a/packages/eslint-plugin/docs/rules/space-infix-ops.md +++ b/packages/eslint-plugin/docs/rules/space-infix-ops.mdx @@ -2,6 +2,9 @@ description: 'Require spacing around infix operators.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/space-infix-ops** for documentation. diff --git a/packages/eslint-plugin/docs/rules/strict-boolean-expressions.md b/packages/eslint-plugin/docs/rules/strict-boolean-expressions.mdx similarity index 96% rename from packages/eslint-plugin/docs/rules/strict-boolean-expressions.md rename to packages/eslint-plugin/docs/rules/strict-boolean-expressions.mdx index f0f702d03d51..7b7afb2793db 100644 --- a/packages/eslint-plugin/docs/rules/strict-boolean-expressions.md +++ b/packages/eslint-plugin/docs/rules/strict-boolean-expressions.mdx @@ -2,6 +2,9 @@ description: 'Disallow certain types in boolean expressions.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/strict-boolean-expressions** for documentation. @@ -21,9 +24,8 @@ The following nodes are considered boolean expressions and their type is checked ## Examples - - -### ❌ Incorrect + + ```ts // nullable numbers are considered unsafe by default @@ -55,7 +57,8 @@ while (obj) { } ``` -### βœ… Correct + + ```tsx // Using logical operator short-circuiting is allowed @@ -85,6 +88,9 @@ function foo(bool?: boolean) { const foo = (arg: any) => (Boolean(arg) ? 1 : 0); ``` + + + ## Options ### `allowString` @@ -187,4 +193,4 @@ If you prefer more succinct checks over more precise boolean logic, this rule mi ## Related To -- [no-unnecessary-condition](./no-unnecessary-condition.md) - Similar rule which reports always-truthy and always-falsy values in conditions +- [no-unnecessary-condition](./no-unnecessary-condition.mdx) - Similar rule which reports always-truthy and always-falsy values in conditions diff --git a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.md b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx similarity index 93% rename from packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.md rename to packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx index 1c53690ae77a..a4de1ae68776 100644 --- a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.md +++ b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx @@ -2,6 +2,9 @@ description: 'Require switch-case statements to be exhaustive.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/switch-exhaustiveness-check** for documentation. @@ -50,17 +53,14 @@ switch (value) { Since `value` is a non-union type it requires the switch case to have a default clause only with `requireDefaultForNonUnion` enabled. - - ## Examples When the switch doesn't have exhaustive cases, either filling them all out or adding a default will correct the rule's complaint. Here are some examples of code working with a union of literals: - - -### ❌ Incorrect + + ```ts type Day = @@ -82,7 +82,8 @@ switch (day) { } ``` -### βœ… Correct + + ```ts type Day = @@ -122,7 +123,8 @@ switch (day) { } ``` -### βœ… Correct + + ```ts type Day = @@ -146,13 +148,13 @@ switch (day) { } ``` - + + Likewise, here are some examples of code working with an enum: - - -### ❌ Incorrect + + ```ts enum Fruit { @@ -170,7 +172,8 @@ switch (fruit) { } ``` -### βœ… Correct + + ```ts enum Fruit { @@ -196,7 +199,8 @@ switch (fruit) { } ``` -### βœ… Correct + + ```ts enum Fruit { @@ -218,7 +222,8 @@ switch (fruit) { } ``` - + + ## When Not To Use It diff --git a/packages/eslint-plugin/docs/rules/triple-slash-reference.md b/packages/eslint-plugin/docs/rules/triple-slash-reference.mdx similarity index 84% rename from packages/eslint-plugin/docs/rules/triple-slash-reference.md rename to packages/eslint-plugin/docs/rules/triple-slash-reference.mdx index 364d4f6063fe..43a043671f55 100644 --- a/packages/eslint-plugin/docs/rules/triple-slash-reference.md +++ b/packages/eslint-plugin/docs/rules/triple-slash-reference.mdx @@ -2,6 +2,9 @@ description: 'Disallow certain triple slash directives in favor of ES6-style import declarations.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/triple-slash-reference** for documentation. @@ -19,9 +22,8 @@ Specifying `'always'` disables this lint rule for that kind of reference. When set to `'never'`, bans `/// ` and enforces using an `import` instead: - - -#### ❌ Incorrect + + ```ts option='{ "lib": "never" }' /// @@ -29,19 +31,22 @@ When set to `'never'`, bans `/// ` and enforces using an globalThis.value; ``` -#### βœ… Correct + + ```ts option='{ "lib": "never" }' import { value } from 'code'; ``` + + + ### `path` When set to `'never'`, bans `/// ` and enforces using an `import` instead: - - -#### ❌ Incorrect + + ```ts option='{ "path": "never" }' /// @@ -49,19 +54,22 @@ When set to `'never'`, bans `/// ` and enforces using an globalThis.value; ``` -#### βœ… Correct + + ```ts option='{ "path": "never" }' import { value } from 'code'; ``` + + + ### `types` When set to `'never'`, bans `/// ` and enforces using an `import` instead: - - -#### ❌ Incorrect + + ```ts option='{ "types": "never" }' /// @@ -69,20 +77,21 @@ When set to `'never'`, bans `/// ` and enforces using a globalThis.value; ``` -#### βœ… Correct + + ```ts option='{ "types": "never" }' import { value } from 'code'; ``` - + + The `types` option may alternately be given a `"prefer-import"` value. Doing so indicates the rule should only report if there is already an `import` from the same location: - - -#### ❌ Incorrect + + ```ts option='{ "types": "prefer-import" }' /// @@ -92,12 +101,16 @@ import { valueA } from 'code'; globalThis.valueB; ``` -#### βœ… Correct + + ```ts option='{ "types": "prefer-import" }' import { valueA, valueB } from 'code'; ``` + + + ## When Not To Use It Most modern TypeScript projects generally use `import` statements to bring in types. diff --git a/packages/eslint-plugin/docs/rules/type-annotation-spacing.md b/packages/eslint-plugin/docs/rules/type-annotation-spacing.mdx similarity index 85% rename from packages/eslint-plugin/docs/rules/type-annotation-spacing.md rename to packages/eslint-plugin/docs/rules/type-annotation-spacing.mdx index d7017c7c5d5d..f2d884b837a5 100644 --- a/packages/eslint-plugin/docs/rules/type-annotation-spacing.md +++ b/packages/eslint-plugin/docs/rules/type-annotation-spacing.mdx @@ -2,13 +2,16 @@ description: 'Require consistent spacing around type annotations.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/type-annotation-spacing** for documentation. Spacing around type annotations improves readability of the code. Although the most commonly used style guideline for type annotations in TypeScript prescribes adding a space after the colon, but not before it, it is subjective to the preferences of a project. For example: - +{/* prettier-ignore */} ```ts // with space after, but not before (default if no option is specified) let foo: string = "bar"; @@ -39,11 +42,10 @@ type Foo = (string: name) =>string; This rule aims to enforce specific spacing patterns around type annotations and function types in type literals. - - -### ❌ Incorrect + + - +{/* prettier-ignore */} ```ts let foo:string = "bar"; let foo :string = "bar"; @@ -70,9 +72,10 @@ type Foo = () =>{}; type Foo = ()=> {}; ``` -### βœ… Correct + + - +{/* prettier-ignore */} ```ts let foo: string = "bar"; @@ -85,6 +88,9 @@ class Foo { type Foo = () => {}; ``` + + + ## Options ### after @@ -93,11 +99,10 @@ type Foo = () => {}; { "before": false, "after": true } ``` - + + -#### ❌ Incorrect - - +{/* prettier-ignore */} ```ts option='{ "before": false, "after": true }' let foo:string = "bar"; let foo :string = "bar"; @@ -124,9 +129,10 @@ type Foo = () =>{}; type Foo = () => {}; ``` -#### βœ… Correct + + - +{/* prettier-ignore */} ```ts option='{ "before": false, "after": true }' let foo: string = "bar"; @@ -139,17 +145,19 @@ class Foo { type Foo = ()=> {}; ``` + + + ### before ```json { "before": true, "after": true } ``` - - -#### ❌ Incorrect + + - +{/* prettier-ignore */} ```ts option='{ "before": true, "after": true }' let foo: string = "bar"; let foo:string = "bar"; @@ -176,9 +184,10 @@ type Foo = () =>{}; type Foo = ()=> {}; ``` -#### βœ… Correct + + - +{/* prettier-ignore */} ```ts option='{ "before": true, "after": true }' let foo : string = "bar"; @@ -191,6 +200,9 @@ class Foo { type Foo = () => {}; ``` + + + ### overrides - colon ```json @@ -201,11 +213,10 @@ type Foo = () => {}; } ``` - - -#### ❌ Incorrect + + - +{/* prettier-ignore */} ```ts option='{"before":false,"after":false,"overrides":{"colon":{"before":true,"after":true}}}' let foo: string = "bar"; let foo:string = "bar"; @@ -232,9 +243,10 @@ type Foo = ()=> {}; type Foo = () => {}; ``` -#### βœ… Correct + + - +{/* prettier-ignore */} ```ts option='{"before":false,"after":false,"overrides":{"colon":{"before":true,"after":true}}}' let foo : string = "bar"; @@ -251,6 +263,9 @@ type Foo = { type Foo = ()=>{}; ``` + + + ### overrides - arrow ```json @@ -261,11 +276,10 @@ type Foo = ()=>{}; } ``` - + + -#### ❌ Incorrect - - +{/* prettier-ignore */} ```ts option='{"before":false,"after":false,"overrides":{"arrow":{"before":true,"after":true}}}' let foo: string = "bar"; let foo : string = "bar"; @@ -292,9 +306,10 @@ type Foo = () =>{}; type Foo = ()=> {}; ``` -#### βœ… Correct + + - +{/* prettier-ignore */} ```ts option='{"before":false,"after":false,"overrides":{"arrow":{"before":true,"after":true}}}' let foo:string = "bar"; @@ -307,6 +322,9 @@ class Foo { type Foo = () => {}; ``` + + + ## When Not To Use It If you don't want to enforce spacing for your type annotations, you can safely turn this rule off. diff --git a/packages/eslint-plugin/docs/rules/typedef.md b/packages/eslint-plugin/docs/rules/typedef.mdx similarity index 87% rename from packages/eslint-plugin/docs/rules/typedef.md rename to packages/eslint-plugin/docs/rules/typedef.mdx index 59c1b440523f..cfbe5fa1a8c5 100644 --- a/packages/eslint-plugin/docs/rules/typedef.md +++ b/packages/eslint-plugin/docs/rules/typedef.mdx @@ -2,6 +2,9 @@ description: 'Require type annotations in certain places.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/typedef** for documentation. @@ -25,7 +28,7 @@ class ContainsText { } ``` -> To enforce type definitions existing on call signatures, use [`explicit-function-return-type`](./explicit-function-return-type.md), or [`explicit-module-boundary-types`](./explicit-module-boundary-types.md). +> To enforce type definitions existing on call signatures, use [`explicit-function-return-type`](./explicit-function-return-type.mdx), or [`explicit-module-boundary-types`](./explicit-module-boundary-types.mdx). :::caution @@ -63,16 +66,16 @@ Whether to enforce type annotations on variables declared using array destructur Examples of code with `{ "arrayDestructuring": true }`: - - -#### ❌ Incorrect + + ```ts option='{ "arrayDestructuring": true }' const [a] = [1]; const [b, c] = [1, 2]; ``` -#### βœ… Correct + + ```ts option='{ "arrayDestructuring": true }' const [a]: number[] = [1]; @@ -83,15 +86,17 @@ for (const [key, val] of new Map([['key', 1]])) { } ``` + + + ### `arrowParameter` Whether to enforce type annotations for parameters of arrow functions. Examples of code with `{ "arrowParameter": true }`: - - -#### ❌ Incorrect + + ```ts option='{ "arrowParameter": true }' const logsSize = size => console.log(size); @@ -103,7 +108,8 @@ const mapper = { }; ``` -#### βœ… Correct + + ```ts option='{ "arrowParameter": true }' const logsSize = (size: number) => console.log(size); @@ -115,15 +121,17 @@ const mapper = { }; ``` + + + ### `memberVariableDeclaration` Whether to enforce type annotations on member variables of classes. Examples of code with `{ "memberVariableDeclaration": true }`: - - -#### ❌ Incorrect + + ```ts option='{ "memberVariableDeclaration": true }' class ContainsText { @@ -132,7 +140,8 @@ class ContainsText { } ``` -#### βœ… Correct + + ```ts option='{ "memberVariableDeclaration": true }' class ContainsText { @@ -141,22 +150,25 @@ class ContainsText { } ``` + + + ### `objectDestructuring` Whether to enforce type annotations on variables declared using object destructuring. Examples of code with `{ "objectDestructuring": true }`: - - -#### ❌ Incorrect + + ```ts option='{ "objectDestructuring": true }' const { length } = 'text'; const [b, c] = Math.random() ? [1, 2] : [3, 4]; ``` -#### βœ… Correct + + ```ts option='{ "objectDestructuring": true }' const { length }: { length: number } = 'text'; @@ -166,15 +178,17 @@ for (const { key, val } of [{ key: 'key', val: 1 }]) { } ``` + + + ### `parameter` Whether to enforce type annotations for parameters of functions and methods. Examples of code with `{ "parameter": true }`: - - -#### ❌ Incorrect + + ```ts option='{ "parameter": true }' function logsSize(size): void { @@ -202,7 +216,8 @@ class Logger { } ``` -#### βœ… Correct + + ```ts option='{ "parameter": true }' function logsSize(size: number): void { @@ -230,15 +245,17 @@ class Logger { } ``` + + + ### `propertyDeclaration` Whether to enforce type annotations for properties of interfaces and types. Examples of code with `{ "propertyDeclaration": true }`: - - -#### ❌ Incorrect + + ```ts option='{ "propertyDeclaration": true }' type Members = { @@ -247,7 +264,8 @@ type Members = { }; ``` -#### βœ… Correct + + ```ts option='{ "propertyDeclaration": true }' type Members = { @@ -256,15 +274,17 @@ type Members = { }; ``` + + + ### `variableDeclaration` Whether to enforce type annotations for variable declarations, excluding array and object destructuring. Examples of code with `{ "variableDeclaration": true }`: - - -#### ❌ Incorrect + + ```ts option='{ "variableDeclaration": true }' const text = 'text'; @@ -272,7 +292,8 @@ let initialText = 'text'; let delayedText; ``` -#### βœ… Correct + + ```ts option='{ "variableDeclaration": true }' const text: string = 'text'; @@ -280,21 +301,24 @@ let initialText: string = 'text'; let delayedText: string; ``` + + + ### `variableDeclarationIgnoreFunction` Ignore variable declarations for non-arrow and arrow functions. Examples of code with `{ "variableDeclaration": true, "variableDeclarationIgnoreFunction": true }`: - - -#### ❌ Incorrect + + ```ts option='{ "variableDeclaration": true, "variableDeclarationIgnoreFunction": true }' const text = 'text'; ``` -#### βœ… Correct + + ```ts option='{ "variableDeclaration": true, "variableDeclarationIgnoreFunction": true }' const a = (): void => {}; @@ -308,6 +332,9 @@ class Foo { } ``` + + + ## When Not To Use It If you are using stricter TypeScript compiler options, particularly `--noImplicitAny` and/or `--strictPropertyInitialization`, you likely don't need this rule. diff --git a/packages/eslint-plugin/docs/rules/unbound-method.md b/packages/eslint-plugin/docs/rules/unbound-method.mdx similarity index 90% rename from packages/eslint-plugin/docs/rules/unbound-method.md rename to packages/eslint-plugin/docs/rules/unbound-method.mdx index 331394884211..8270bed6fb12 100644 --- a/packages/eslint-plugin/docs/rules/unbound-method.md +++ b/packages/eslint-plugin/docs/rules/unbound-method.mdx @@ -2,6 +2,9 @@ description: 'Enforce unbound methods are called with their expected scope.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/unbound-method** for documentation. @@ -13,14 +16,13 @@ Otherwise, passing class methods around as values can remove type safety by fail This rule reports when a class method is referenced in an unbound manner. :::note Tip -If you're working with `jest`, you can use [`eslint-plugin-jest`'s version of this rule](https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/unbound-method.md) to lint your test files, which knows when it's ok to pass an unbound method to `expect` calls. +If you're working with `jest`, you can use [`eslint-plugin-jest`'s version of this rule](https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/unbound-method.mdx) to lint your test files, which knows when it's ok to pass an unbound method to `expect` calls. ::: ## Examples - - -### ❌ Incorrect + + ```ts class MyClass { @@ -47,7 +49,8 @@ const arith = { const { double } = arith; ``` -### βœ… Correct + + ```ts class MyClass { @@ -77,6 +80,9 @@ const arith = { const { double } = arith; ``` + + + ## Options ### `ignoreStatic` @@ -102,4 +108,4 @@ If your project dynamically changes `this` scopes around in a way TypeScript has One likely difficult pattern is if your code intentionally waits to bind methods after use, such as by passing a `scope: this` along with the method. You might consider using [ESLint disable comments](https://eslint.org/docs/latest/use/configure/rules#using-configuration-comments-1) for those specific situations instead of completely disabling this rule. -If you're wanting to use `toBeCalled` and similar matches in `jest` tests, you can disable this rule for your test files in favor of [`eslint-plugin-jest`'s version of this rule](https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/unbound-method.md). +If you're wanting to use `toBeCalled` and similar matches in `jest` tests, you can disable this rule for your test files in favor of [`eslint-plugin-jest`'s version of this rule](https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/unbound-method.mdx). diff --git a/packages/eslint-plugin/docs/rules/unified-signatures.md b/packages/eslint-plugin/docs/rules/unified-signatures.mdx similarity index 87% rename from packages/eslint-plugin/docs/rules/unified-signatures.md rename to packages/eslint-plugin/docs/rules/unified-signatures.mdx index 54f04d2385ec..6e2fd8d5f804 100644 --- a/packages/eslint-plugin/docs/rules/unified-signatures.md +++ b/packages/eslint-plugin/docs/rules/unified-signatures.mdx @@ -2,6 +2,9 @@ description: 'Disallow two overloads that could be unified into one with a union or an optional/rest parameter.' --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + > πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ > > See **https://typescript-eslint.io/rules/unified-signatures** for documentation. @@ -14,9 +17,8 @@ This rule reports when function overload signatures can be replaced by a single ## Examples - - -### ❌ Incorrect + + ```ts function x(x: number): void; @@ -28,7 +30,8 @@ function y(): void; function y(...x: number[]): void; ``` -### βœ… Correct + + ```ts function x(x: number | string): void; @@ -45,28 +48,34 @@ function f(...a: number[]): void; function f(...a: string[]): void; ``` + + + ## Options ### `ignoreDifferentlyNamedParameters` Examples of code for this rule with `ignoreDifferentlyNamedParameters`: - - -### ❌ Incorrect + + ```ts option='{ "ignoreDifferentlyNamedParameters": true }' function f(a: number): void; function f(a: string): void; ``` -### βœ… Correct + + ```ts option='{ "ignoreDifferentlyNamedParameters": true }' function f(a: number): void; function f(b: string): void; ``` + + + ## When Not To Use It This is purely a stylistic rule to help with readability of function signature overloads. @@ -74,4 +83,4 @@ You can turn it off if you don't want to consistently keep them next to each oth ## Related To -- [`adjacent-overload-signatures`](./adjacent-overload-signatures.md) +- [`adjacent-overload-signatures`](./adjacent-overload-signatures.mdx) diff --git a/packages/eslint-plugin/docs/rules/use-unknown-in-catch-callback-variable.mdx b/packages/eslint-plugin/docs/rules/use-unknown-in-catch-callback-variable.mdx new file mode 100644 index 000000000000..8192950add5f --- /dev/null +++ b/packages/eslint-plugin/docs/rules/use-unknown-in-catch-callback-variable.mdx @@ -0,0 +1,84 @@ +--- +description: 'Enforce typing arguments in `.catch()` callbacks as `unknown`.' +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +> πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘ +> +> See **https://typescript-eslint.io/rules/use-unknown-in-catch-callback-variable** for documentation. + +This rule enforces that you always use the `unknown` type for the parameter of a `Promise.prototype.catch()` callback. + + + + +```ts +Promise.reject(new Error('I will reject!')).catch(err => { + console.log(err); +}); + +Promise.reject(new Error('I will reject!')).catch((err: any) => { + console.log(err); +}); + +Promise.reject(new Error('I will reject!')).catch((err: Error) => { + console.log(err); +}); +``` + + + + +```ts +Promise.reject(new Error('I will reject!')).catch((err: unknown) => { + console.log(err); +}); +``` + + + + +The reason for this rule is to enable programmers to impose constraints on `Promise` error handling analogously to what TypeScript provides for ordinary exception handling. + +For ordinary exceptions, TypeScript treats the `catch` variable as `any` by default. However, `unknown` would be a more accurate type, so TypeScript [introduced the `useUnknownInCatchVariables` compiler option](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-4.html#defaulting-to-the-unknown-type-in-catch-variables---useunknownincatchvariables) to treat the `catch` variable as `unknown` instead. + +```ts +try { + throw x; +} catch (err) { + // err has type 'any' with useUnknownInCatchVariables: false + // err has type 'unknown' with useUnknownInCatchVariables: true +} +``` + +The Promise analog of the `try-catch` block, [`Promise.prototype.catch()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch), is not affected by the `useUnknownInCatchVariables` compiler option, and its "`catch` variable" will always have the type `any`. + +```ts +Promise.reject(x).catch(err => { + // err has type 'any' regardless of `useUnknownInCatchVariables` +}); +``` + +However, you can still provide an explicit type annotation, which lets you achieve the same effect as the `useUnknownInCatchVariables` option does for synchronous `catch` variables. + +```ts +Promise.reject(x).catch((err: unknown) => { + // err has type 'unknown' +}); +``` + +:::info +There is actually a way to have the `catch()` callback variable use the `unknown` type _without_ an explicit type annotation at the call sites, but it has the drawback that it involves overriding global type declarations. +For example, the library [better-TypeScript-lib](https://github.com/uhyo/better-typescript-lib) sets this up globally for your project (see [the relevant lines in the better-TypeScript-lib source code](https://github.com/uhyo/better-typescript-lib/blob/c294e177d1cc2b1d1803febf8192a4c83a1fe028/lib/lib.es5.d.ts#L635) for details on how). + +For further reading on this, you may also want to look into +[the discussion in the proposal for this rule](https://github.com/typescript-eslint/typescript-eslint/issues/7526#issuecomment-1690600813) and [this TypeScript issue on typing catch callback variables as unknown](https://github.com/microsoft/TypeScript/issues/45602). +::: + +## When Not To Use It + +If your codebase is not yet able to enable `useUnknownInCatchVariables`, it likely would be similarly difficult to enable this rule. + +If you have modified the global type declarations in order to make `catch()` callbacks use the `unknown` type without an explicit type annotation, you do not need this rule. diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index c3127b8bc119..9f526b25cc8b 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "7.2.0", + "version": "7.3.0", "description": "TypeScript plugin for ESLint", "files": [ "dist", @@ -28,7 +28,7 @@ } }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "repository": { "type": "git", @@ -61,10 +61,10 @@ }, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.2.0", - "@typescript-eslint/type-utils": "7.2.0", - "@typescript-eslint/utils": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0", + "@typescript-eslint/scope-manager": "7.3.0", + "@typescript-eslint/type-utils": "7.3.0", + "@typescript-eslint/utils": "7.3.0", + "@typescript-eslint/visitor-keys": "7.3.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -76,8 +76,8 @@ "@types/debug": "*", "@types/marked": "*", "@types/natural-compare": "*", - "@typescript-eslint/rule-schema-to-typescript-types": "7.2.0", - "@typescript-eslint/rule-tester": "7.2.0", + "@typescript-eslint/rule-schema-to-typescript-types": "7.3.0", + "@typescript-eslint/rule-tester": "7.3.0", "ajv": "^6.12.6", "chalk": "^5.3.0", "cross-env": "^7.0.3", diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index c66afe869db4..b8711f0cc6a7 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -155,5 +155,6 @@ export = { '@typescript-eslint/typedef': 'error', '@typescript-eslint/unbound-method': 'error', '@typescript-eslint/unified-signatures': 'error', + '@typescript-eslint/use-unknown-in-catch-callback-variable': 'error', }, } satisfies ClassicConfig.Config; diff --git a/packages/eslint-plugin/src/configs/disable-type-checked.ts b/packages/eslint-plugin/src/configs/disable-type-checked.ts index f322a903637c..392e01ce304a 100644 --- a/packages/eslint-plugin/src/configs/disable-type-checked.ts +++ b/packages/eslint-plugin/src/configs/disable-type-checked.ts @@ -62,5 +62,6 @@ export = { '@typescript-eslint/strict-boolean-expressions': 'off', '@typescript-eslint/switch-exhaustiveness-check': 'off', '@typescript-eslint/unbound-method': 'off', + '@typescript-eslint/use-unknown-in-catch-callback-variable': 'off', }, } satisfies ClassicConfig.Config; diff --git a/packages/eslint-plugin/src/configs/strict-type-checked-only.ts b/packages/eslint-plugin/src/configs/strict-type-checked-only.ts index 4188f5f73465..cbfaa5b73b80 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked-only.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked-only.ts @@ -15,7 +15,7 @@ export = { '@typescript-eslint/no-base-to-string': 'error', '@typescript-eslint/no-confusing-void-expression': 'error', '@typescript-eslint/no-duplicate-type-constituents': 'error', - '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: false }], '@typescript-eslint/no-for-in-array': 'error', 'no-implied-eval': 'off', '@typescript-eslint/no-implied-eval': 'error', @@ -43,8 +43,28 @@ export = { '@typescript-eslint/prefer-return-this-type': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', - '@typescript-eslint/restrict-plus-operands': 'error', - '@typescript-eslint/restrict-template-expressions': 'error', + '@typescript-eslint/restrict-plus-operands': [ + 'error', + { + allowAny: false, + allowBoolean: false, + allowNullish: false, + allowNumberAndString: false, + allowRegExp: false, + }, + ], + '@typescript-eslint/restrict-template-expressions': [ + 'error', + { + allowAny: false, + allowBoolean: false, + allowNullish: false, + allowNumber: false, + allowRegExp: false, + allowNever: false, + }, + ], '@typescript-eslint/unbound-method': 'error', + '@typescript-eslint/use-unknown-in-catch-callback-variable': 'error', }, } satisfies ClassicConfig.Config; diff --git a/packages/eslint-plugin/src/configs/strict-type-checked.ts b/packages/eslint-plugin/src/configs/strict-type-checked.ts index 836a0ec76e7c..6d838db31566 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked.ts @@ -11,7 +11,10 @@ export = { extends: ['./configs/base', './configs/eslint-recommended'], rules: { '@typescript-eslint/await-thenable': 'error', - '@typescript-eslint/ban-ts-comment': 'error', + '@typescript-eslint/ban-ts-comment': [ + 'error', + { minimumDescriptionLength: 10 }, + ], '@typescript-eslint/ban-types': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', @@ -24,7 +27,7 @@ export = { '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-extraneous-class': 'error', - '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: false }], '@typescript-eslint/no-for-in-array': 'error', 'no-implied-eval': 'off', '@typescript-eslint/no-implied-eval': 'error', @@ -71,10 +74,30 @@ export = { '@typescript-eslint/prefer-ts-expect-error': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', - '@typescript-eslint/restrict-plus-operands': 'error', - '@typescript-eslint/restrict-template-expressions': 'error', + '@typescript-eslint/restrict-plus-operands': [ + 'error', + { + allowAny: false, + allowBoolean: false, + allowNullish: false, + allowNumberAndString: false, + allowRegExp: false, + }, + ], + '@typescript-eslint/restrict-template-expressions': [ + 'error', + { + allowAny: false, + allowBoolean: false, + allowNullish: false, + allowNumber: false, + allowRegExp: false, + allowNever: false, + }, + ], '@typescript-eslint/triple-slash-reference': 'error', '@typescript-eslint/unbound-method': 'error', '@typescript-eslint/unified-signatures': 'error', + '@typescript-eslint/use-unknown-in-catch-callback-variable': 'error', }, } satisfies ClassicConfig.Config; diff --git a/packages/eslint-plugin/src/configs/strict.ts b/packages/eslint-plugin/src/configs/strict.ts index e49b8cbcadc1..dbf57cc2c3f6 100644 --- a/packages/eslint-plugin/src/configs/strict.ts +++ b/packages/eslint-plugin/src/configs/strict.ts @@ -10,7 +10,10 @@ import type { ClassicConfig } from '@typescript-eslint/utils/ts-eslint'; export = { extends: ['./configs/base', './configs/eslint-recommended'], rules: { - '@typescript-eslint/ban-ts-comment': 'error', + '@typescript-eslint/ban-ts-comment': [ + 'error', + { minimumDescriptionLength: 10 }, + ], '@typescript-eslint/ban-types': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', diff --git a/packages/eslint-plugin/src/rules/ban-ts-comment.ts b/packages/eslint-plugin/src/rules/ban-ts-comment.ts index 5a5aeef35273..b554510f57d3 100644 --- a/packages/eslint-plugin/src/rules/ban-ts-comment.ts +++ b/packages/eslint-plugin/src/rules/ban-ts-comment.ts @@ -1,7 +1,7 @@ -import type { TSESLint } from '@typescript-eslint/utils'; +import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_TOKEN_TYPES } from '@typescript-eslint/utils'; -import { createRule, getStringLength } from '../util'; +import { createRule, getStringLength, nullThrows } from '../util'; type DirectiveConfig = | boolean @@ -16,7 +16,7 @@ interface Options { minimumDescriptionLength?: number; } -export const defaultMinimumDescriptionLength = 3; +const defaultMinimumDescriptionLength = 3; type MessageIds = | 'tsDirectiveComment' @@ -25,6 +25,11 @@ type MessageIds = | 'tsDirectiveCommentRequiresDescription' | 'replaceTsIgnoreWithTsExpectError'; +interface MatchedTSDirective { + directive: string; + description: string; +} + export default createRule<[Options], MessageIds>({ name: 'ban-ts-comment', meta: { @@ -32,7 +37,10 @@ export default createRule<[Options], MessageIds>({ docs: { description: 'Disallow `@ts-` comments or require descriptions after directives', - recommended: 'recommended', + recommended: { + recommended: true, + strict: [{ minimumDescriptionLength: 10 }], + }, }, messages: { tsDirectiveComment: @@ -95,14 +103,18 @@ export default createRule<[Options], MessageIds>({ }, ], create(context, [options]) { + // https://github.com/microsoft/TypeScript/blob/6f1ad5ad8bec5671f7e951a3524b62d82ec4be68/src/compiler/parser.ts#L10591 + const singleLinePragmaRegEx = + /^\/\/\/?\s*@ts-(?check|nocheck)(?.*)$/; + /* The regex used are taken from the ones used in the official TypeScript repo - - https://github.com/microsoft/TypeScript/blob/408c760fae66080104bc85c449282c2d207dfe8e/src/compiler/scanner.ts#L288-L296 + https://github.com/microsoft/TypeScript/blob/6f1ad5ad8bec5671f7e951a3524b62d82ec4be68/src/compiler/scanner.ts#L340-L348 */ const commentDirectiveRegExSingleLine = - /^\/*\s*@ts-(?expect-error|ignore|check|nocheck)(?.*)/; + /^\/*\s*@ts-(?expect-error|ignore)(?.*)/; const commentDirectiveRegExMultiLine = - /^\s*(?:\/|\*)*\s*@ts-(?expect-error|ignore|check|nocheck)(?.*)/; + /^\s*(?:\/|\*)*\s*@ts-(?expect-error|ignore)(?.*)/; const descriptionFormats = new Map(); for (const directive of [ @@ -117,22 +129,66 @@ export default createRule<[Options], MessageIds>({ } } + function execDirectiveRegEx( + regex: RegExp, + str: string, + ): MatchedTSDirective | null { + const match = regex.exec(str); + if (!match) { + return null; + } + + const { directive, description } = nullThrows( + match.groups, + 'RegExp should contain groups', + ); + return { + directive: nullThrows( + directive, + 'RegExp should contain "directive" group', + ), + description: nullThrows( + description, + 'RegExp should contain "description" group', + ), + }; + } + + function findDirectiveInComment( + comment: TSESTree.Comment, + ): MatchedTSDirective | null { + if (comment.type === AST_TOKEN_TYPES.Line) { + const matchedPragma = execDirectiveRegEx( + singleLinePragmaRegEx, + `//${comment.value}`, + ); + if (matchedPragma) { + return matchedPragma; + } + + return execDirectiveRegEx( + commentDirectiveRegExSingleLine, + comment.value, + ); + } + + const commentLines = comment.value.split('\n'); + return execDirectiveRegEx( + commentDirectiveRegExMultiLine, + commentLines[commentLines.length - 1], + ); + } + return { Program(): void { const comments = context.sourceCode.getAllComments(); comments.forEach(comment => { - const regExp = - comment.type === AST_TOKEN_TYPES.Line - ? commentDirectiveRegExSingleLine - : commentDirectiveRegExMultiLine; - - const match = regExp.exec(comment.value); + const match = findDirectiveInComment(comment); if (!match) { return; } - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const { directive, description } = match.groups!; + const { directive, description } = match; const fullDirective = `ts-${directive}` as keyof Options; @@ -174,12 +230,14 @@ export default createRule<[Options], MessageIds>({ option === 'allow-with-description' || (typeof option === 'object' && option.descriptionFormat) ) { - const { - minimumDescriptionLength = defaultMinimumDescriptionLength, - } = options; + const { minimumDescriptionLength } = options; const format = descriptionFormats.get(fullDirective); if ( - getStringLength(description.trim()) < minimumDescriptionLength + getStringLength(description.trim()) < + nullThrows( + minimumDescriptionLength, + 'Expected minimumDescriptionLength to be set', + ) ) { context.report({ data: { directive, minimumDescriptionLength }, diff --git a/packages/eslint-plugin/src/rules/class-literal-property-style.ts b/packages/eslint-plugin/src/rules/class-literal-property-style.ts index 483ad9a40807..416267d2835e 100644 --- a/packages/eslint-plugin/src/rules/class-literal-property-style.ts +++ b/packages/eslint-plugin/src/rules/class-literal-property-style.ts @@ -1,7 +1,13 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import { createRule, getStaticStringValue } from '../util'; +import { + createRule, + getStaticStringValue, + isAssignee, + isFunction, + nullThrows, +} from '../util'; type Options = ['fields' | 'getters']; type MessageIds = @@ -15,6 +21,11 @@ interface NodeWithModifiers { static: boolean; } +interface PropertiesInfo { + properties: TSESTree.PropertyDefinition[]; + excludeSet: Set; +} + const printNodeModifiers = ( node: NodeWithModifiers, final: 'get' | 'readonly', @@ -65,10 +76,71 @@ export default createRule({ }, defaultOptions: ['fields'], create(context, [style]) { - function getMethodName(node: TSESTree.MethodDefinition): string { - return ( - getStaticStringValue(node.key) ?? context.sourceCode.getText(node.key) + const propertiesInfoStack: PropertiesInfo[] = []; + + function getStringValue(node: TSESTree.Node): string { + return getStaticStringValue(node) ?? context.sourceCode.getText(node); + } + + function enterClassBody(): void { + propertiesInfoStack.push({ + properties: [], + excludeSet: new Set(), + }); + } + + function exitClassBody(): void { + const { properties, excludeSet } = nullThrows( + propertiesInfoStack.pop(), + 'Stack should exist on class exit', ); + + properties.forEach(node => { + const { value } = node; + if (!value || !isSupportedLiteral(value)) { + return; + } + + const name = getStringValue(node.key); + if (excludeSet.has(name)) { + return; + } + + context.report({ + node: node.key, + messageId: 'preferGetterStyle', + suggest: [ + { + messageId: 'preferGetterStyleSuggestion', + fix(fixer): TSESLint.RuleFix { + const name = context.sourceCode.getText(node.key); + + let text = ''; + text += printNodeModifiers(node, 'get'); + text += node.computed ? `[${name}]` : name; + text += `() { return ${context.sourceCode.getText(value)}; }`; + + return fixer.replaceText(node, text); + }, + }, + ], + }); + }); + } + + function excludeAssignedProperty(node: TSESTree.MemberExpression): void { + if (isAssignee(node)) { + const { excludeSet } = + propertiesInfoStack[propertiesInfoStack.length - 1]; + + const name = + getStaticStringValue(node.property) ?? + context.sourceCode.getText(node.property); + + if (name) { + excludeSet.add(name); + } + } } return { @@ -94,14 +166,14 @@ export default createRule({ return; } - const name = getMethodName(node); + const name = getStringValue(node.key); if (node.parent.type === AST_NODE_TYPES.ClassBody) { const hasDuplicateKeySetter = node.parent.body.some(element => { return ( element.type === AST_NODE_TYPES.MethodDefinition && element.kind === 'set' && - getMethodName(element) === name + getStringValue(element.key) === name ); }); if (hasDuplicateKeySetter) { @@ -132,37 +204,33 @@ export default createRule({ }, }), ...(style === 'getters' && { + ClassBody: enterClassBody, + 'ClassBody:exit': exitClassBody, + 'MethodDefinition[kind="constructor"] ThisExpression'( + node: TSESTree.ThisExpression, + ): void { + if (node.parent.type === AST_NODE_TYPES.MemberExpression) { + let parent: TSESTree.Node | undefined = node.parent; + + while (!isFunction(parent)) { + parent = parent.parent; + } + + if ( + parent.parent.type === AST_NODE_TYPES.MethodDefinition && + parent.parent.kind === 'constructor' + ) { + excludeAssignedProperty(node.parent); + } + } + }, PropertyDefinition(node): void { if (!node.readonly || node.declare) { return; } - - const { value } = node; - - if (!value || !isSupportedLiteral(value)) { - return; - } - - context.report({ - node: node.key, - messageId: 'preferGetterStyle', - suggest: [ - { - messageId: 'preferGetterStyleSuggestion', - fix(fixer): TSESLint.RuleFix { - const name = context.sourceCode.getText(node.key); - - let text = ''; - - text += printNodeModifiers(node, 'get'); - text += node.computed ? `[${name}]` : name; - text += `() { return ${context.sourceCode.getText(value)}; }`; - - return fixer.replaceText(node, text); - }, - }, - ], - }); + const { properties } = + propertiesInfoStack[propertiesInfoStack.length - 1]; + properties.push(node); }, }), }; diff --git a/packages/eslint-plugin/src/rules/consistent-type-imports.ts b/packages/eslint-plugin/src/rules/consistent-type-imports.ts index d1fd6444561b..c06097716fb5 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-imports.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-imports.ts @@ -136,6 +136,7 @@ export default createRule({ } else { if ( !sourceImports.valueOnlyNamedImport && + node.specifiers.length && node.specifiers.every( specifier => specifier.type === AST_NODE_TYPES.ImportSpecifier, diff --git a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts index d135f4c1a0d3..9904dc2e5336 100644 --- a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts +++ b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts @@ -1,7 +1,8 @@ import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import { createRule } from '../util'; +import { createRule, nullThrows } from '../util'; +import type { FunctionInfo } from '../util/explicitReturnTypeUtils'; import { ancestorHasReturnType, checkFunctionReturnType, @@ -22,6 +23,11 @@ type Options = [ ]; type MessageIds = 'missingReturnType'; +type FunctionNode = + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression; + export default createRule({ name: 'explicit-function-return-type', meta: { @@ -98,6 +104,22 @@ export default createRule({ }, ], create(context, [options]) { + const functionInfoStack: FunctionInfo[] = []; + + function enterFunction(node: FunctionNode): void { + functionInfoStack.push({ + node, + returns: [], + }); + } + + function popFunctionInfo(exitNodeType: string): FunctionInfo { + return nullThrows( + functionInfoStack.pop(), + `Stack should exist on ${exitNodeType} exit`, + ); + } + function isAllowedFunction( node: | TSESTree.ArrowFunctionExpression @@ -168,41 +190,49 @@ export default createRule({ return node.parent.type === AST_NODE_TYPES.CallExpression; } - return { - 'ArrowFunctionExpression, FunctionExpression'( - node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, - ): void { - if ( - options.allowConciseArrowFunctionExpressionsStartingWithVoid && - node.type === AST_NODE_TYPES.ArrowFunctionExpression && - node.expression && - node.body.type === AST_NODE_TYPES.UnaryExpression && - node.body.operator === 'void' - ) { - return; - } + function exitFunctionExpression( + node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, + ): void { + const info = popFunctionInfo('function expression'); - if (isAllowedFunction(node)) { - return; - } + if ( + options.allowConciseArrowFunctionExpressionsStartingWithVoid && + node.type === AST_NODE_TYPES.ArrowFunctionExpression && + node.expression && + node.body.type === AST_NODE_TYPES.UnaryExpression && + node.body.operator === 'void' + ) { + return; + } - if ( - options.allowTypedFunctionExpressions && - (isValidFunctionExpressionReturnType(node, options) || - ancestorHasReturnType(node)) - ) { - return; - } + if (isAllowedFunction(node)) { + return; + } - checkFunctionReturnType(node, options, context.sourceCode, loc => - context.report({ - node, - loc, - messageId: 'missingReturnType', - }), - ); - }, - FunctionDeclaration(node): void { + if ( + options.allowTypedFunctionExpressions && + (isValidFunctionExpressionReturnType(node, options) || + ancestorHasReturnType(node)) + ) { + return; + } + + checkFunctionReturnType(info, options, context.sourceCode, loc => + context.report({ + node, + loc, + messageId: 'missingReturnType', + }), + ); + } + + return { + 'ArrowFunctionExpression, FunctionExpression, FunctionDeclaration': + enterFunction, + 'ArrowFunctionExpression:exit': exitFunctionExpression, + 'FunctionExpression:exit': exitFunctionExpression, + 'FunctionDeclaration:exit'(node): void { + const info = popFunctionInfo('function declaration'); if (isAllowedFunction(node)) { return; } @@ -210,7 +240,7 @@ export default createRule({ return; } - checkFunctionReturnType(node, options, context.sourceCode, loc => + checkFunctionReturnType(info, options, context.sourceCode, loc => context.report({ node, loc, @@ -218,6 +248,9 @@ export default createRule({ }), ); }, + ReturnStatement(node): void { + functionInfoStack.at(-1)?.returns.push(node); + }, }; }, }); diff --git a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts index f04a71de8b63..c8981e403941 100644 --- a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts +++ b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts @@ -5,6 +5,7 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, isFunction } from '../util'; import type { FunctionExpression, + FunctionInfo, FunctionNode, } from '../util/explicitReturnTypeUtils'; import { @@ -101,13 +102,31 @@ export default createRule({ // tracks all of the functions we've already checked const checkedFunctions = new Set(); - // tracks functions that were found whilst traversing - const foundFunctions: FunctionNode[] = []; + const functionStack: FunctionNode[] = []; + const functionReturnsMap = new Map< + FunctionNode, + TSESTree.ReturnStatement[] + >(); // all nodes visited, avoids infinite recursion for cyclic references // (such as class member referring to itself) const alreadyVisited = new Set(); + function getReturnsInFunction( + node: FunctionNode, + ): TSESTree.ReturnStatement[] { + return functionReturnsMap.get(node) ?? []; + } + + function enterFunction(node: FunctionNode): void { + functionStack.push(node); + functionReturnsMap.set(node, []); + } + + function exitFunction(): void { + functionStack.pop(); + } + /* # How the rule works: @@ -120,10 +139,10 @@ export default createRule({ */ return { - ExportDefaultDeclaration(node): void { + 'ExportDefaultDeclaration:exit'(node): void { checkNode(node.declaration); }, - 'ExportNamedDeclaration:not([source])'( + 'ExportNamedDeclaration:not([source]):exit'( node: TSESTree.ExportNamedDeclaration, ): void { if (node.declaration) { @@ -134,21 +153,25 @@ export default createRule({ } } }, - TSExportAssignment(node): void { + 'TSExportAssignment:exit'(node): void { checkNode(node.expression); }, - 'ArrowFunctionExpression, FunctionDeclaration, FunctionExpression'( - node: FunctionNode, - ): void { - foundFunctions.push(node); - }, + 'ArrowFunctionExpression, FunctionDeclaration, FunctionExpression': + enterFunction, + 'ArrowFunctionExpression:exit': exitFunction, + 'FunctionDeclaration:exit': exitFunction, + 'FunctionExpression:exit': exitFunction, 'Program:exit'(): void { - for (const func of foundFunctions) { - if (isExportedHigherOrderFunction(func)) { - checkNode(func); + for (const [node, returns] of functionReturnsMap) { + if (isExportedHigherOrderFunction({ node, returns })) { + checkNode(node); } } }, + ReturnStatement(node): void { + const current = functionStack[functionStack.length - 1]; + functionReturnsMap.get(current)?.push(node); + }, }; function checkParameters( @@ -265,7 +288,9 @@ export default createRule({ return false; } - function isExportedHigherOrderFunction(node: FunctionNode): boolean { + function isExportedHigherOrderFunction({ + node, + }: FunctionInfo): boolean { let current: TSESTree.Node | undefined = node.parent; while (current) { if (current.type === AST_NODE_TYPES.ReturnStatement) { @@ -274,9 +299,12 @@ export default createRule({ continue; } + if (!isFunction(current)) { + return false; + } + const returns = getReturnsInFunction(current); if ( - !isFunction(current) || - !doesImmediatelyReturnFunctionExpression(current) + !doesImmediatelyReturnFunctionExpression({ node: current, returns }) ) { return false; } @@ -335,8 +363,10 @@ export default createRule({ switch (node.type) { case AST_NODE_TYPES.ArrowFunctionExpression: - case AST_NODE_TYPES.FunctionExpression: - return checkFunctionExpression(node); + case AST_NODE_TYPES.FunctionExpression: { + const returns = getReturnsInFunction(node); + return checkFunctionExpression({ node, returns }); + } case AST_NODE_TYPES.ArrayExpression: for (const element of node.elements) { @@ -360,8 +390,10 @@ export default createRule({ } return; - case AST_NODE_TYPES.FunctionDeclaration: - return checkFunction(node); + case AST_NODE_TYPES.FunctionDeclaration: { + const returns = getReturnsInFunction(node); + return checkFunction({ node, returns }); + } case AST_NODE_TYPES.MethodDefinition: case AST_NODE_TYPES.TSAbstractMethodDefinition: @@ -419,7 +451,10 @@ export default createRule({ checkParameters(node); } - function checkFunctionExpression(node: FunctionExpression): void { + function checkFunctionExpression({ + node, + returns, + }: FunctionInfo): void { if (checkedFunctions.has(node)) { return; } @@ -434,7 +469,7 @@ export default createRule({ } checkFunctionExpressionReturnType( - node, + { node, returns }, options, context.sourceCode, loc => { @@ -449,7 +484,10 @@ export default createRule({ checkParameters(node); } - function checkFunction(node: TSESTree.FunctionDeclaration): void { + function checkFunction({ + node, + returns, + }: FunctionInfo): void { if (checkedFunctions.has(node)) { return; } @@ -459,13 +497,18 @@ export default createRule({ return; } - checkFunctionReturnType(node, options, context.sourceCode, loc => { - context.report({ - node, - loc, - messageId: 'missingReturnType', - }); - }); + checkFunctionReturnType( + { node, returns }, + options, + context.sourceCode, + loc => { + context.report({ + node, + loc, + messageId: 'missingReturnType', + }); + }, + ); checkParameters(node); } diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 6d94605135b5..3534b6780c2c 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -141,6 +141,7 @@ import typeAnnotationSpacing from './type-annotation-spacing'; import typedef from './typedef'; import unboundMethod from './unbound-method'; import unifiedSignatures from './unified-signatures'; +import useUnknownInCatchCallbackVariable from './use-unknown-in-catch-callback-variable'; export default { 'adjacent-overload-signatures': adjacentOverloadSignatures, @@ -284,4 +285,5 @@ export default { typedef: typedef, 'unbound-method': unboundMethod, 'unified-signatures': unifiedSignatures, + 'use-unknown-in-catch-callback-variable': useUnknownInCatchCallbackVariable, } satisfies Linter.PluginRules; diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index b3ac65296992..9e439118747f 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -50,7 +50,10 @@ export default createRule({ docs: { description: 'Require Promise-like statements to be handled appropriately', - recommended: 'recommended', + recommended: { + recommended: true, + strict: [{ ignoreVoid: false }], + }, requiresTypeChecking: true, }, hasSuggestions: true, diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index 754832a3c1b3..1a58d884dc7c 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -3,7 +3,11 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; -import { createRule, getParserServices } from '../util'; +import { + createRule, + getParserServices, + isRestParameterDeclaration, +} from '../util'; type Options = [ { @@ -549,7 +553,7 @@ function voidFunctionArguments( // If this is a array 'rest' parameter, check all of the argument indices // from the current argument to the end. - if (decl && ts.isParameter(decl) && decl.dotDotDotToken) { + if (decl && isRestParameterDeclaration(decl)) { if (checker.isArrayType(type)) { // Unwrap 'Array' to 'MaybeVoidFunction', // so that we'll handle it in the same way as a non-rest diff --git a/packages/eslint-plugin/src/rules/no-redundant-type-constituents.ts b/packages/eslint-plugin/src/rules/no-redundant-type-constituents.ts index a9b22e71de11..0c6d6c826123 100644 --- a/packages/eslint-plugin/src/rules/no-redundant-type-constituents.ts +++ b/packages/eslint-plugin/src/rules/no-redundant-type-constituents.ts @@ -272,6 +272,10 @@ export default createRule({ PrimitiveTypeFlag, TSESTree.TypeNode[] >(); + const seenUnionTypes = new Map< + TSESTree.TypeNode, + TypeFlagsWithName[] + >(); function checkIntersectionBottomAndTopTypes( { typeFlags, typeName }: TypeFlagsWithName, @@ -323,8 +327,58 @@ export default createRule({ } } } + // if any typeNode is TSTypeReference and typePartFlags have more than 1 element, than the referenced type is definitely a union. + if (typePartFlags.length >= 2) { + seenUnionTypes.set(typeNode, typePartFlags); + } + } + /** + * @example + * ```ts + * type F = "a"|2|"b"; + * type I = F & string; + * ``` + * This function checks if all the union members of `F` are assignable to the other member of `I`. If every member is assignable, then its reported else not. + */ + const checkIfUnionsAreAssignable = (): undefined => { + for (const [typeRef, typeValues] of seenUnionTypes) { + let primitive: number | undefined = undefined; + for (const { typeFlags } of typeValues) { + if ( + seenPrimitiveTypes.has( + literalToPrimitiveTypeFlags[ + typeFlags as keyof typeof literalToPrimitiveTypeFlags + ], + ) + ) { + primitive = + literalToPrimitiveTypeFlags[ + typeFlags as keyof typeof literalToPrimitiveTypeFlags + ]; + } else { + primitive = undefined; + break; + } + } + if (Number.isInteger(primitive)) { + context.report({ + data: { + literal: typeValues.map(name => name.typeName).join(' | '), + primitive: + primitiveTypeFlagNames[ + primitive as keyof typeof primitiveTypeFlagNames + ], + }, + messageId: 'primitiveOverridden', + node: typeRef, + }); + } + } + }; + if (seenUnionTypes.size > 0) { + checkIfUnionsAreAssignable(); + return; } - // For each primitive type of all the seen primitive types, // if there was a literal type seen that overrides it, // report each of the primitive type's type nodes diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts b/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts index 34f973667c51..329b26d87aee 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts @@ -93,9 +93,8 @@ export default createRule({ accessedSymbol.flags, context.sourceCode.getText(name), ); - return ( - fromScope === undefined || symbolsAreEqual(accessedSymbol, fromScope) - ); + + return !!fromScope && symbolsAreEqual(accessedSymbol, fromScope); } function visitNamespaceAccess( diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index 9212ed15d5f6..151318e7f9bf 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -61,37 +61,6 @@ export default createRule({ const checker = services.program.getTypeChecker(); const compilerOptions = services.program.getCompilerOptions(); - /** - * Sometimes tuple types don't have ObjectFlags.Tuple set, like when they're being matched against an inferred type. - * So, in addition, check if there are integer properties 0..n and no other numeric keys - */ - function couldBeTupleType(type: ts.ObjectType): boolean { - const properties = type.getProperties(); - - if (properties.length === 0) { - return false; - } - let i = 0; - - for (; i < properties.length; ++i) { - const name = properties[i].name; - - if (String(i) !== name) { - if (i === 0) { - // if there are no integer properties, this is not a tuple - return false; - } - break; - } - } - for (; i < properties.length; ++i) { - if (String(+properties[i].name) === properties[i].name) { - return false; // if there are any other numeric properties, this is not a tuple - } - } - return true; - } - /** * Returns true if there's a chance the variable has been used before a value has been assigned to it */ @@ -139,6 +108,13 @@ export default createRule({ ); } + function isConstVariableDeclaration(node: TSESTree.Node): boolean { + return ( + node.type === AST_NODE_TYPES.VariableDeclaration && + node.kind === 'const' + ); + } + return { TSNonNullExpression(node): void { if ( @@ -236,28 +212,21 @@ export default createRule({ if ( options.typesToIgnore?.includes( context.sourceCode.getText(node.typeAnnotation), - ) || - isConstAssertion(node.typeAnnotation) + ) ) { return; } const castType = services.getTypeAtLocation(node); - - if ( - isTypeFlagSet(castType, ts.TypeFlags.Literal) || - (tsutils.isObjectType(castType) && - (tsutils.isObjectFlagSet(castType, ts.ObjectFlags.Tuple) || - couldBeTupleType(castType))) - ) { - // It's not always safe to remove a cast to a literal type or tuple - // type, as those types are sometimes widened without the cast. - return; - } - const uncastType = services.getTypeAtLocation(node.expression); + const typeIsUnchanged = uncastType === castType; + + const wouldSameTypeBeInferred = castType.isLiteral() + ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + isConstVariableDeclaration(node.parent.parent!) + : !isConstAssertion(node.typeAnnotation); - if (uncastType === castType) { + if (typeIsUnchanged && wouldSameTypeBeInferred) { context.report({ node, messageId: 'unnecessaryAssertion', diff --git a/packages/eslint-plugin/src/rules/no-unsafe-argument.ts b/packages/eslint-plugin/src/rules/no-unsafe-argument.ts index a04440f383c3..177954392bb7 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-argument.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-argument.ts @@ -1,10 +1,11 @@ import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as ts from 'typescript'; +import type * as ts from 'typescript'; import { createRule, getParserServices, + isRestParameterDeclaration, isTypeAnyArrayType, isTypeAnyType, isUnsafeAssignment, @@ -59,7 +60,7 @@ class FunctionSignature { const type = checker.getTypeOfSymbolAtLocation(param, tsNode); const decl = param.getDeclarations()?.[0]; - if (decl && ts.isParameter(decl) && decl.dotDotDotToken) { + if (decl && isRestParameterDeclaration(decl)) { // is a rest param if (checker.isArrayType(type)) { restType = { diff --git a/packages/eslint-plugin/src/rules/no-unused-expressions.ts b/packages/eslint-plugin/src/rules/no-unused-expressions.ts index b1605c0f92d9..83c2ddd6c527 100644 --- a/packages/eslint-plugin/src/rules/no-unused-expressions.ts +++ b/packages/eslint-plugin/src/rules/no-unused-expressions.ts @@ -57,9 +57,14 @@ export default createRule({ return; } + const expressionType = node.expression.type; + if ( - node.expression.type === - TSESTree.AST_NODE_TYPES.TSInstantiationExpression + expressionType === + TSESTree.AST_NODE_TYPES.TSInstantiationExpression || + expressionType === TSESTree.AST_NODE_TYPES.TSAsExpression || + expressionType === TSESTree.AST_NODE_TYPES.TSNonNullExpression || + expressionType === TSESTree.AST_NODE_TYPES.TSTypeAssertion ) { rules.ExpressionStatement({ ...node, diff --git a/packages/eslint-plugin/src/rules/prefer-for-of.ts b/packages/eslint-plugin/src/rules/prefer-for-of.ts index 0f6bba8d5430..18622a04e2ec 100644 --- a/packages/eslint-plugin/src/rules/prefer-for-of.ts +++ b/packages/eslint-plugin/src/rules/prefer-for-of.ts @@ -1,7 +1,7 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import { createRule } from '../util'; +import { createRule, isAssignee } from '../util'; export default createRule({ name: 'prefer-for-of', @@ -102,60 +102,6 @@ export default createRule({ ); } - function isAssignee(node: TSESTree.Node): boolean { - const parent = node.parent; - if (!parent) { - return false; - } - - // a[i] = 1, a[i] += 1, etc. - if ( - parent.type === AST_NODE_TYPES.AssignmentExpression && - parent.left === node - ) { - return true; - } - - // delete a[i] - if ( - parent.type === AST_NODE_TYPES.UnaryExpression && - parent.operator === 'delete' && - parent.argument === node - ) { - return true; - } - - // a[i]++, --a[i], etc. - if ( - parent.type === AST_NODE_TYPES.UpdateExpression && - parent.argument === node - ) { - return true; - } - - // [a[i]] = [0] - if (parent.type === AST_NODE_TYPES.ArrayPattern) { - return true; - } - - // [...a[i]] = [0] - if (parent.type === AST_NODE_TYPES.RestElement) { - return true; - } - - // ({ foo: a[i] }) = { foo: 0 } - if ( - parent.type === AST_NODE_TYPES.Property && - parent.value === node && - parent.parent.type === AST_NODE_TYPES.ObjectExpression && - isAssignee(parent.parent) - ) { - return true; - } - - return false; - } - function isIndexOnlyUsedWithArray( body: TSESTree.Statement, indexVar: TSESLint.Scope.Variable, diff --git a/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts b/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts index 9a196e7be0d0..7eb781ed0d18 100644 --- a/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts +++ b/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts @@ -1,5 +1,7 @@ import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import * as tsutils from 'ts-api-utils'; +import type * as ts from 'typescript'; import { createRule, @@ -51,6 +53,16 @@ export default createRule({ const services = getParserServices(context); const checker = services.program.getTypeChecker(); + function isArrayType(type: ts.Type): boolean { + return tsutils + .unionTypeParts(type) + .every(unionPart => + tsutils + .intersectionTypeParts(unionPart) + .every(t => checker.isArrayType(t) || checker.isTupleType(t)), + ); + } + return { 'CallExpression > MemberExpression.callee'( callee: MemberExpressionWithCallExpressionParent, @@ -72,7 +84,7 @@ export default createRule({ ); // Check the owner type of the `reduce` method. - if (checker.isArrayType(calleeObjType)) { + if (isArrayType(calleeObjType)) { context.report({ messageId: 'preferTypeParameter', node: secondArg, diff --git a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts index 9e17fa486b52..1953c15c70cb 100644 --- a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts +++ b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts @@ -31,7 +31,18 @@ export default createRule({ docs: { description: 'Require both operands of addition to be the same type and be `bigint`, `number`, or `string`', - recommended: 'recommended', + recommended: { + recommended: true, + strict: [ + { + allowAny: false, + allowBoolean: false, + allowNullish: false, + allowNumberAndString: false, + allowRegExp: false, + }, + ], + }, requiresTypeChecking: true, }, messages: { diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 63a0b171306a..cc719fe7fb7b 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -1,6 +1,7 @@ import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as ts from 'typescript'; +import type { Type, TypeChecker } from 'typescript'; +import { TypeFlags } from 'typescript'; import { createRule, @@ -12,15 +13,44 @@ import { isTypeNeverType, } from '../util'; +type OptionTester = ( + type: Type, + checker: TypeChecker, + recursivelyCheckType: (type: Type) => boolean, +) => boolean; + +const testTypeFlag = + (flagsToCheck: TypeFlags): OptionTester => + type => + isTypeFlagSet(type, flagsToCheck); + +const optionTesters = ( + [ + ['Any', isTypeAnyType], + [ + 'Array', + (type, checker, recursivelyCheckType): boolean => + checker.isArrayType(type) && + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + recursivelyCheckType(type.getNumberIndexType()!), + ], + // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum + ['Boolean', testTypeFlag(TypeFlags.BooleanLike)], + ['Nullish', testTypeFlag(TypeFlags.Null | TypeFlags.Undefined)], + ['Number', testTypeFlag(TypeFlags.NumberLike | TypeFlags.BigIntLike)], + [ + 'RegExp', + (type, checker): boolean => getTypeName(checker, type) === 'RegExp', + ], + ['Never', isTypeNeverType], + ] satisfies [string, OptionTester][] +).map(([type, tester]) => ({ + type, + option: `allow${type}` as const, + tester, +})); type Options = [ - { - allowAny?: boolean; - allowBoolean?: boolean; - allowNullish?: boolean; - allowNumber?: boolean; - allowRegExp?: boolean; - allowNever?: boolean; - }, + { [Type in (typeof optionTesters)[number]['option']]?: boolean }, ]; type MessageId = 'invalidType'; @@ -32,7 +62,19 @@ export default createRule({ docs: { description: 'Enforce template literal expressions to be of `string` type', - recommended: 'recommended', + recommended: { + recommended: true, + strict: [ + { + allowAny: false, + allowBoolean: false, + allowNullish: false, + allowNumber: false, + allowRegExp: false, + allowNever: false, + }, + ], + }, requiresTypeChecking: true, }, messages: { @@ -42,38 +84,15 @@ export default createRule({ { type: 'object', additionalProperties: false, - properties: { - allowAny: { - description: - 'Whether to allow `any` typed values in template expressions.', - type: 'boolean', - }, - allowBoolean: { - description: - 'Whether to allow `boolean` typed values in template expressions.', - type: 'boolean', - }, - allowNullish: { - description: - 'Whether to allow `nullish` typed values in template expressions.', - type: 'boolean', - }, - allowNumber: { - description: - 'Whether to allow `number` typed values in template expressions.', - type: 'boolean', - }, - allowRegExp: { - description: - 'Whether to allow `regexp` typed values in template expressions.', - type: 'boolean', - }, - allowNever: { - description: - 'Whether to allow `never` typed values in template expressions.', - type: 'boolean', - }, - }, + properties: Object.fromEntries( + optionTesters.map(({ option, type }) => [ + option, + { + description: `Whether to allow \`${type.toLowerCase()}\` typed values in template expressions.`, + type: 'boolean', + }, + ]), + ), }, ], }, @@ -89,47 +108,9 @@ export default createRule({ create(context, [options]) { const services = getParserServices(context); const checker = services.program.getTypeChecker(); - - function isUnderlyingTypePrimitive(type: ts.Type): boolean { - if (isTypeFlagSet(type, ts.TypeFlags.StringLike)) { - return true; - } - - if ( - options.allowNumber && - isTypeFlagSet(type, ts.TypeFlags.NumberLike | ts.TypeFlags.BigIntLike) - ) { - return true; - } - - if ( - options.allowBoolean && - isTypeFlagSet(type, ts.TypeFlags.BooleanLike) - ) { - return true; - } - - if (options.allowAny && isTypeAnyType(type)) { - return true; - } - - if (options.allowRegExp && getTypeName(checker, type) === 'RegExp') { - return true; - } - - if ( - options.allowNullish && - isTypeFlagSet(type, ts.TypeFlags.Null | ts.TypeFlags.Undefined) - ) { - return true; - } - - if (options.allowNever && isTypeNeverType(type)) { - return true; - } - - return false; - } + const enabledOptionTesters = optionTesters.filter( + ({ option }) => options[option], + ); return { TemplateLiteral(node: TSESTree.TemplateLiteral): void { @@ -144,12 +125,7 @@ export default createRule({ expression, ); - if ( - !isInnerUnionOrIntersectionConformingTo( - expressionType, - isUnderlyingTypePrimitive, - ) - ) { + if (!recursivelyCheckType(expressionType)) { context.report({ node: expression, messageId: 'invalidType', @@ -160,23 +136,21 @@ export default createRule({ }, }; - function isInnerUnionOrIntersectionConformingTo( - type: ts.Type, - predicate: (underlyingType: ts.Type) => boolean, - ): boolean { - return rec(type); - - function rec(innerType: ts.Type): boolean { - if (innerType.isUnion()) { - return innerType.types.every(rec); - } - - if (innerType.isIntersection()) { - return innerType.types.some(rec); - } + function recursivelyCheckType(innerType: Type): boolean { + if (innerType.isUnion()) { + return innerType.types.every(recursivelyCheckType); + } - return predicate(innerType); + if (innerType.isIntersection()) { + return innerType.types.some(recursivelyCheckType); } + + return ( + isTypeFlagSet(innerType, TypeFlags.StringLike) || + enabledOptionTesters.some(({ tester }) => + tester(innerType, checker, recursivelyCheckType), + ) + ); } }, }); diff --git a/packages/eslint-plugin/src/rules/unbound-method.ts b/packages/eslint-plugin/src/rules/unbound-method.ts index 97a17c620957..9b416ea4a570 100644 --- a/packages/eslint-plugin/src/rules/unbound-method.ts +++ b/packages/eslint-plugin/src/rules/unbound-method.ts @@ -132,7 +132,7 @@ export default createRule({ const services = getParserServices(context); const currentSourceFile = services.program.getSourceFile(context.filename); - function checkMethodAndReport( + function checkIfMethodAndReport( node: TSESTree.Node, symbol: ts.Symbol | undefined, ): void { @@ -140,7 +140,10 @@ export default createRule({ return; } - const { dangerous, firstParamIsThis } = checkMethod(symbol, ignoreStatic); + const { dangerous, firstParamIsThis } = checkIfMethod( + symbol, + ignoreStatic, + ); if (dangerous) { context.report({ messageId: @@ -168,7 +171,7 @@ export default createRule({ return; } - checkMethodAndReport(node, services.getSymbolAtLocation(node)); + checkIfMethodAndReport(node, services.getSymbolAtLocation(node)); }, 'VariableDeclarator, AssignmentExpression'( node: TSESTree.AssignmentExpression | TSESTree.VariableDeclarator, @@ -200,7 +203,7 @@ export default createRule({ return; } - checkMethodAndReport( + checkIfMethodAndReport( property.key, initTypes.getProperty(property.key.name), ); @@ -212,10 +215,15 @@ export default createRule({ }, }); -function checkMethod( +interface CheckMethodResult { + dangerous: boolean; + firstParamIsThis?: boolean; +} + +function checkIfMethod( symbol: ts.Symbol, ignoreStatic: boolean, -): { dangerous: boolean; firstParamIsThis?: boolean } { +): CheckMethodResult { const { valueDeclaration } = symbol; if (!valueDeclaration) { // working around https://github.com/microsoft/TypeScript/issues/31294 @@ -229,37 +237,56 @@ function checkMethod( (valueDeclaration as ts.PropertyDeclaration).initializer?.kind === ts.SyntaxKind.FunctionExpression, }; + case ts.SyntaxKind.PropertyAssignment: { + const assignee = (valueDeclaration as ts.PropertyAssignment).initializer; + if (assignee.kind !== ts.SyntaxKind.FunctionExpression) { + return { + dangerous: false, + }; + } + return checkMethod(assignee as ts.FunctionExpression, ignoreStatic); + } case ts.SyntaxKind.MethodDeclaration: case ts.SyntaxKind.MethodSignature: { - const decl = valueDeclaration as - | ts.MethodDeclaration - | ts.MethodSignature; - const firstParam = decl.parameters.at(0); - const firstParamIsThis = - firstParam?.name.kind === ts.SyntaxKind.Identifier && - // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison - firstParam.name.escapedText === 'this'; - const thisArgIsVoid = - firstParamIsThis && firstParam.type?.kind === ts.SyntaxKind.VoidKeyword; - - return { - dangerous: - !thisArgIsVoid && - !( - ignoreStatic && - tsutils.includesModifier( - getModifiers(valueDeclaration), - ts.SyntaxKind.StaticKeyword, - ) - ), - firstParamIsThis, - }; + return checkMethod( + valueDeclaration as ts.MethodDeclaration | ts.MethodSignature, + ignoreStatic, + ); } } return { dangerous: false }; } +function checkMethod( + valueDeclaration: + | ts.MethodDeclaration + | ts.MethodSignature + | ts.FunctionExpression, + ignoreStatic: boolean, +): CheckMethodResult { + const firstParam = valueDeclaration.parameters.at(0); + const firstParamIsThis = + firstParam?.name.kind === ts.SyntaxKind.Identifier && + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison + firstParam.name.escapedText === 'this'; + const thisArgIsVoid = + firstParamIsThis && firstParam.type?.kind === ts.SyntaxKind.VoidKeyword; + + return { + dangerous: + !thisArgIsVoid && + !( + ignoreStatic && + tsutils.includesModifier( + getModifiers(valueDeclaration), + ts.SyntaxKind.StaticKeyword, + ) + ), + firstParamIsThis, + }; +} + function isSafeUse(node: TSESTree.Node): boolean { const parent = node.parent; diff --git a/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts b/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts new file mode 100644 index 000000000000..b899b23c391d --- /dev/null +++ b/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts @@ -0,0 +1,356 @@ +import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import type { + ReportDescriptor, + Scope, +} from '@typescript-eslint/utils/ts-eslint'; +import * as tsutils from 'ts-api-utils'; +import type * as ts from 'typescript'; + +import { + createRule, + getParserServices, + getStaticValue, + isParenlessArrowFunction, + isRestParameterDeclaration, + nullThrows, +} from '../util'; + +type MessageIds = + | 'useUnknown' + | 'useUnknownSpreadArgs' + | 'useUnknownArrayDestructuringPattern' + | 'useUnknownObjectDestructuringPattern' + | 'addUnknownTypeAnnotationSuggestion' + | 'addUnknownRestTypeAnnotationSuggestion' + | 'wrongTypeAnnotationSuggestion' + | 'wrongRestTypeAnnotationSuggestion'; + +const useUnknownMessageBase = + 'Prefer the safe `: unknown` for a catch callback variable.'; + +export default createRule<[], MessageIds>({ + name: 'use-unknown-in-catch-callback-variable', + meta: { + docs: { + description: + 'Enforce typing arguments in `.catch()` callbacks as `unknown`', + requiresTypeChecking: true, + recommended: 'strict', + }, + type: 'suggestion', + messages: { + useUnknown: useUnknownMessageBase, + useUnknownArrayDestructuringPattern: + useUnknownMessageBase + ' The thrown error may not be iterable.', + useUnknownObjectDestructuringPattern: + useUnknownMessageBase + + ' The thrown error may be nullable, or may not have the expected shape.', + useUnknownSpreadArgs: + useUnknownMessageBase + + ' The argument list may contain a handler that does not use `unknown` for the catch callback variable.', + addUnknownTypeAnnotationSuggestion: + 'Add an explicit `: unknown` type annotation to the catch variable.', + addUnknownRestTypeAnnotationSuggestion: + 'Add an explicit `: [unknown]` type annotation to the catch rest variable.', + wrongTypeAnnotationSuggestion: + 'Change existing type annotation to `: unknown`.', + wrongRestTypeAnnotationSuggestion: + 'Change existing type annotation to `: [unknown]`.', + }, + fixable: 'code', + schema: [], + hasSuggestions: true, + }, + + defaultOptions: [], + + create(context) { + const services = getParserServices(context); + const checker = services.program.getTypeChecker(); + + function isPromiseCatchAccess(node: TSESTree.Expression): boolean { + if ( + !( + node.type === AST_NODE_TYPES.MemberExpression && + isStaticMemberAccessOfValue(node, 'catch') + ) + ) { + return false; + } + + const objectTsNode = services.esTreeNodeToTSNodeMap.get(node.object); + const tsNode = services.esTreeNodeToTSNodeMap.get(node); + return tsutils.isThenableType( + checker, + tsNode, + checker.getTypeAtLocation(objectTsNode), + ); + } + + function isFlaggableHandlerType(type: ts.Type): boolean { + for (const unionPart of tsutils.unionTypeParts(type)) { + const callSignatures = tsutils.getCallSignaturesOfType(unionPart); + if (callSignatures.length === 0) { + // Ignore any non-function components to the type. Those are not this rule's problem. + continue; + } + + for (const callSignature of callSignatures) { + const firstParam = callSignature.parameters.at(0); + if (!firstParam) { + // it's not an issue if there's no catch variable at all. + continue; + } + + let firstParamType = checker.getTypeOfSymbol(firstParam); + + const decl = firstParam.valueDeclaration; + if (decl != null && isRestParameterDeclaration(decl)) { + if (checker.isArrayType(firstParamType)) { + firstParamType = checker.getTypeArguments(firstParamType)[0]; + } else if (checker.isTupleType(firstParamType)) { + firstParamType = checker.getTypeArguments(firstParamType)[0]; + } else { + // a rest arg that's not an array or tuple should definitely be flagged. + return true; + } + } + + if (!tsutils.isIntrinsicUnknownType(firstParamType)) { + return true; + } + } + } + + return false; + } + + /** + * If passed an ordinary expression, this will check it as expected. + * + * If passed a spread element, it treats it as the union of unwrapped array/tuple type. + */ + function shouldFlagArgument( + node: TSESTree.Expression | TSESTree.SpreadElement, + ): boolean { + const argument = services.esTreeNodeToTSNodeMap.get(node); + const typeOfArgument = checker.getTypeAtLocation(argument); + return isFlaggableHandlerType(typeOfArgument); + } + + function shouldFlagMultipleSpreadArgs( + argumentsList: TSESTree.CallExpressionArgument[], + ): boolean { + // One could try to be clever about unpacking fixed length tuples and stuff + // like that, but there's no need, since this is all invalid use of `.catch` + // anyway at the end of the day. Instead, we'll just check whether any of the + // possible args types would violate the rule on its own. + return argumentsList.some(argument => shouldFlagArgument(argument)); + } + + function shouldFlagSingleSpreadArg(node: TSESTree.SpreadElement): boolean { + const spreadArgs = services.esTreeNodeToTSNodeMap.get(node.argument); + + const spreadArgsType = checker.getTypeAtLocation(spreadArgs); + + if (checker.isArrayType(spreadArgsType)) { + const arrayType = checker.getTypeArguments(spreadArgsType)[0]; + return isFlaggableHandlerType(arrayType); + } + + if (checker.isTupleType(spreadArgsType)) { + const firstType = checker.getTypeArguments(spreadArgsType).at(0); + if (!firstType) { + // empty spread args. Suspect code, but not a problem for this rule. + return false; + } + return isFlaggableHandlerType(firstType); + } + + return true; + } + + /** + * Analyzes the syntax of the catch argument and makes a best effort to pinpoint + * why it's reporting, and to come up with a suggested fix if possible. + * + * This function is explicitly operating under the assumption that the + * rule _is reporting_, so it is not guaranteed to be sound to call otherwise. + */ + function refineReportForNormalArgumentIfPossible( + argument: TSESTree.Expression, + ): undefined | Partial> { + // Only know how to be helpful if a function literal has been provided. + if ( + !( + argument.type === AST_NODE_TYPES.ArrowFunctionExpression || + argument.type === AST_NODE_TYPES.FunctionExpression + ) + ) { + return undefined; + } + + const catchVariableOuterWithIncorrectTypes = nullThrows( + argument.params.at(0), + 'There should have been at least one parameter for the rule to have flagged.', + ); + + // Function expressions can't have parameter properties; those only exist in constructors. + const catchVariableOuter = + catchVariableOuterWithIncorrectTypes as Exclude< + typeof catchVariableOuterWithIncorrectTypes, + TSESTree.TSParameterProperty + >; + const catchVariableInner = + catchVariableOuter.type === AST_NODE_TYPES.AssignmentPattern + ? catchVariableOuter.left + : catchVariableOuter; + + switch (catchVariableInner.type) { + case AST_NODE_TYPES.Identifier: { + const catchVariableTypeAnnotation = catchVariableInner.typeAnnotation; + if (catchVariableTypeAnnotation == null) { + return { + node: catchVariableOuter, + suggest: [ + { + messageId: 'addUnknownTypeAnnotationSuggestion', + fix: (fixer: TSESLint.RuleFixer): TSESLint.RuleFix[] => { + if ( + argument.type === + AST_NODE_TYPES.ArrowFunctionExpression && + isParenlessArrowFunction(argument, context.sourceCode) + ) { + return [ + fixer.insertTextBefore(catchVariableInner, '('), + fixer.insertTextAfter(catchVariableInner, ': unknown)'), + ]; + } + + return [ + fixer.insertTextAfter(catchVariableInner, ': unknown'), + ]; + }, + }, + ], + }; + } + + return { + node: catchVariableOuter, + suggest: [ + { + messageId: 'wrongTypeAnnotationSuggestion', + fix: (fixer: TSESLint.RuleFixer): TSESLint.RuleFix => + fixer.replaceText(catchVariableTypeAnnotation, ': unknown'), + }, + ], + }; + } + case AST_NODE_TYPES.ArrayPattern: { + return { + node: catchVariableOuter, + messageId: 'useUnknownArrayDestructuringPattern', + }; + } + case AST_NODE_TYPES.ObjectPattern: { + return { + node: catchVariableOuter, + messageId: 'useUnknownObjectDestructuringPattern', + }; + } + case AST_NODE_TYPES.RestElement: { + const catchVariableTypeAnnotation = catchVariableInner.typeAnnotation; + if (catchVariableTypeAnnotation == null) { + return { + node: catchVariableOuter, + suggest: [ + { + messageId: 'addUnknownRestTypeAnnotationSuggestion', + fix: (fixer): TSESLint.RuleFix => + fixer.insertTextAfter(catchVariableInner, ': [unknown]'), + }, + ], + }; + } + return { + node: catchVariableOuter, + suggest: [ + { + messageId: 'wrongRestTypeAnnotationSuggestion', + fix: (fixer): TSESLint.RuleFix => + fixer.replaceText(catchVariableTypeAnnotation, ': [unknown]'), + }, + ], + }; + } + } + } + + return { + CallExpression(node): void { + if (node.arguments.length === 0 || !isPromiseCatchAccess(node.callee)) { + return; + } + + const firstArgument = node.arguments[0]; + + // Deal with some special cases around spread element args. + // promise.catch(...handlers), promise.catch(...handlers, ...moreHandlers). + if (firstArgument.type === AST_NODE_TYPES.SpreadElement) { + if (node.arguments.length === 1) { + if (shouldFlagSingleSpreadArg(firstArgument)) { + context.report({ + node: firstArgument, + messageId: 'useUnknown', + }); + } + } else if (shouldFlagMultipleSpreadArgs(node.arguments)) { + context.report({ + node, + messageId: 'useUnknownSpreadArgs', + }); + } + return; + } + + // First argument is an "ordinary" argument (i.e. not a spread argument) + // promise.catch(f), promise.catch(() => {}), promise.catch(, <>) + if (shouldFlagArgument(firstArgument)) { + // We are now guaranteed to report, but we have a bit of work to do + // to determine exactly where, and whether we can fix it. + const overrides = + refineReportForNormalArgumentIfPossible(firstArgument); + context.report({ + node: firstArgument, + messageId: 'useUnknown', + ...overrides, + }); + } + }, + }; + }, +}); + +/** + * Answers whether the member expression looks like + * `x.memberName`, `x['memberName']`, + * or even `const mn = 'memberName'; x[mn]` (or optional variants thereof). + */ +function isStaticMemberAccessOfValue( + memberExpression: + | TSESTree.MemberExpressionComputedName + | TSESTree.MemberExpressionNonComputedName, + value: string, + scope?: Scope.Scope | undefined, +): boolean { + if (!memberExpression.computed) { + // x.memberName case. + return memberExpression.property.name === value; + } + + // x['memberName'] cases. + const staticValueResult = getStaticValue(memberExpression.property, scope); + return staticValueResult != null && value === staticValueResult.value; +} diff --git a/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts b/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts index 512ff62202b9..1cfc834c1c1b 100644 --- a/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts +++ b/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts @@ -1,5 +1,9 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'; +import { + AST_NODE_TYPES, + ASTUtils, + ESLintUtils, +} from '@typescript-eslint/utils'; import { isConstructor, isSetter, isTypeAssertion } from './astUtils'; import { getFunctionHeadLoc } from './getFunctionHeadLoc'; @@ -9,6 +13,11 @@ type FunctionExpression = | TSESTree.FunctionExpression; type FunctionNode = FunctionExpression | TSESTree.FunctionDeclaration; +export interface FunctionInfo { + node: T; + returns: TSESTree.ReturnStatement[]; +} + /** * Checks if a node is a variable declarator with a type annotation. * ``` @@ -134,26 +143,22 @@ function isPropertyOfObjectWithType( * ``` */ function doesImmediatelyReturnFunctionExpression({ - body, -}: FunctionNode): boolean { - // Check if body is a block with a single statement - if (body.type === AST_NODE_TYPES.BlockStatement && body.body.length === 1) { - const [statement] = body.body; - - // Check if that statement is a return statement with an argument - if ( - statement.type === AST_NODE_TYPES.ReturnStatement && - !!statement.argument - ) { - // If so, check that returned argument as body - body = statement.argument; - } + node, + returns, +}: FunctionInfo): boolean { + if ( + node.type === AST_NODE_TYPES.ArrowFunctionExpression && + ASTUtils.isFunction(node.body) + ) { + return true; } - // Check if the body being returned is a function expression - return ( - body.type === AST_NODE_TYPES.ArrowFunctionExpression || - body.type === AST_NODE_TYPES.FunctionExpression + if (returns.length === 0) { + return false; + } + + return returns.every( + node => node.argument && ASTUtils.isFunction(node.argument), ); } @@ -251,12 +256,12 @@ function isValidFunctionExpressionReturnType( * Check that the function expression or declaration is valid. */ function isValidFunctionReturnType( - node: FunctionNode, + { node, returns }: FunctionInfo, options: Options, ): boolean { if ( options.allowHigherOrderFunctions && - doesImmediatelyReturnFunctionExpression(node) + doesImmediatelyReturnFunctionExpression({ node, returns }) ) { return true; } @@ -272,12 +277,12 @@ function isValidFunctionReturnType( * Checks if a function declaration/expression has a return type. */ function checkFunctionReturnType( - node: FunctionNode, + { node, returns }: FunctionInfo, options: Options, sourceCode: TSESLint.SourceCode, report: (loc: TSESTree.SourceLocation) => void, ): void { - if (isValidFunctionReturnType(node, options)) { + if (isValidFunctionReturnType({ node, returns }, options)) { return; } @@ -288,16 +293,16 @@ function checkFunctionReturnType( * Checks if a function declaration/expression has a return type. */ function checkFunctionExpressionReturnType( - node: FunctionExpression, + info: FunctionInfo, options: Options, sourceCode: TSESLint.SourceCode, report: (loc: TSESTree.SourceLocation) => void, ): void { - if (isValidFunctionExpressionReturnType(node, options)) { + if (isValidFunctionExpressionReturnType(info.node, options)) { return; } - checkFunctionReturnType(node, options, sourceCode, report); + checkFunctionReturnType(info, options, sourceCode, report); } /** diff --git a/packages/eslint-plugin/src/util/index.ts b/packages/eslint-plugin/src/util/index.ts index f6b583000ce5..1f8d657cd542 100644 --- a/packages/eslint-plugin/src/util/index.ts +++ b/packages/eslint-plugin/src/util/index.ts @@ -15,6 +15,7 @@ export * from './isUndefinedIdentifier'; export * from './misc'; export * from './objectIterators'; export * from './types'; +export * from './isAssignee'; // this is done for convenience - saves migrating all of the old rules export * from '@typescript-eslint/type-utils'; diff --git a/packages/eslint-plugin/src/util/isAssignee.ts b/packages/eslint-plugin/src/util/isAssignee.ts new file mode 100644 index 000000000000..390243152503 --- /dev/null +++ b/packages/eslint-plugin/src/util/isAssignee.ts @@ -0,0 +1,56 @@ +import type { TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; + +export function isAssignee(node: TSESTree.Node): boolean { + const parent = node.parent; + if (!parent) { + return false; + } + + // a[i] = 1, a[i] += 1, etc. + if ( + parent.type === AST_NODE_TYPES.AssignmentExpression && + parent.left === node + ) { + return true; + } + + // delete a[i] + if ( + parent.type === AST_NODE_TYPES.UnaryExpression && + parent.operator === 'delete' && + parent.argument === node + ) { + return true; + } + + // a[i]++, --a[i], etc. + if ( + parent.type === AST_NODE_TYPES.UpdateExpression && + parent.argument === node + ) { + return true; + } + + // [a[i]] = [0] + if (parent.type === AST_NODE_TYPES.ArrayPattern) { + return true; + } + + // [...a[i]] = [0] + if (parent.type === AST_NODE_TYPES.RestElement) { + return true; + } + + // ({ foo: a[i] }) = { foo: 0 } + if ( + parent.type === AST_NODE_TYPES.Property && + parent.value === node && + parent.parent.type === AST_NODE_TYPES.ObjectExpression && + isAssignee(parent.parent) + ) { + return true; + } + + return false; +} diff --git a/packages/eslint-plugin/src/util/misc.ts b/packages/eslint-plugin/src/util/misc.ts index 8c3604d77d98..9ba55c9d87f7 100644 --- a/packages/eslint-plugin/src/util/misc.ts +++ b/packages/eslint-plugin/src/util/misc.ts @@ -7,6 +7,8 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as ts from 'typescript'; +import { isParenthesized } from './astUtils'; + const DEFINITION_EXTENSIONS = [ ts.Extension.Dts, ts.Extension.Dcts, @@ -215,6 +217,19 @@ function typeNodeRequiresParentheses( ); } +function isRestParameterDeclaration(decl: ts.Declaration): boolean { + return ts.isParameter(decl) && decl.dotDotDotToken != null; +} + +function isParenlessArrowFunction( + node: TSESTree.ArrowFunctionExpression, + sourceCode: TSESLint.SourceCode, +): boolean { + return ( + node.params.length === 1 && !isParenthesized(node.params[0], sourceCode) + ); +} + export { arrayGroupByToMap, arraysAreEqual, @@ -226,6 +241,8 @@ export { getNameFromIndexSignature, getNameFromMember, isDefinitionFile, + isRestParameterDeclaration, + isParenlessArrowFunction, MemberNameType, RequireKeys, typeNodeRequiresParentheses, diff --git a/packages/eslint-plugin/tests/configs.test.ts b/packages/eslint-plugin/tests/configs.test.ts index 23f8d5ffd35c..db9130e18011 100644 --- a/packages/eslint-plugin/tests/configs.test.ts +++ b/packages/eslint-plugin/tests/configs.test.ts @@ -23,7 +23,9 @@ function entriesToObject(value: [string, T][]): Record { }, {}); } -function filterRules(values: Record): [string, string][] { +function filterRules( + values: Record, +): [string, string | unknown[]][] { return Object.entries(values).filter(([name]) => name.startsWith(RULE_NAME_PREFIX), ); @@ -39,7 +41,7 @@ function filterAndMapRuleConfigs({ excludeDeprecated, typeChecked, recommendations, -}: FilterAndMapRuleConfigsSettings = {}): [string, string][] { +}: FilterAndMapRuleConfigsSettings = {}): [string, unknown][] { let result = Object.entries(rules); if (excludeDeprecated) { @@ -55,16 +57,41 @@ function filterAndMapRuleConfigs({ } if (recommendations) { - result = result.filter(([, rule]) => - recommendations.includes(rule.meta.docs?.recommended), - ); + result = result.filter(([, rule]) => { + switch (typeof rule.meta.docs?.recommended) { + case 'undefined': + return false; + case 'object': + return Object.keys(rule.meta.docs.recommended).some(recommended => + recommendations.includes(recommended as RuleRecommendation), + ); + case 'string': + return recommendations.includes(rule.meta.docs.recommended); + } + }); } - return result.map(([name]) => [`${RULE_NAME_PREFIX}${name}`, 'error']); + const highestRecommendation = recommendations?.filter(Boolean).at(-1); + + return result.map(([name, rule]) => { + const customRecommendation = + highestRecommendation && + typeof rule.meta.docs?.recommended === 'object' && + rule.meta.docs.recommended[ + highestRecommendation as 'recommended' | 'strict' + ]; + + return [ + `${RULE_NAME_PREFIX}${name}`, + customRecommendation && typeof customRecommendation !== 'boolean' + ? ['error', customRecommendation[0]] + : 'error', + ]; + }); } function itHasBaseRulesOverriden( - unfilteredConfigRules: Record, + unfilteredConfigRules: Record, ): void { it('has the base rules overriden by the appropriate extension rules', () => { const ruleNames = new Set(Object.keys(unfilteredConfigRules)); @@ -166,7 +193,7 @@ describe('recommended-type-checked-only.ts', () => { }); describe('strict.ts', () => { - const unfilteredConfigRules: Record = + const unfilteredConfigRules: Record = plugin.configs.strict.rules; it('contains all strict rules, excluding type checked ones', () => { @@ -185,7 +212,7 @@ describe('strict.ts', () => { }); describe('strict-type-checked.ts', () => { - const unfilteredConfigRules: Record = + const unfilteredConfigRules: Record = plugin.configs['strict-type-checked'].rules; it('contains all strict rules', () => { @@ -202,7 +229,7 @@ describe('strict-type-checked.ts', () => { }); describe('strict-type-checked-only.ts', () => { - const unfilteredConfigRules: Record = + const unfilteredConfigRules: Record = plugin.configs['strict-type-checked-only'].rules; it('contains only type-checked strict rules', () => { @@ -221,7 +248,7 @@ describe('strict-type-checked-only.ts', () => { }); describe('stylistic.ts', () => { - const unfilteredConfigRules: Record = + const unfilteredConfigRules: Record = plugin.configs.stylistic.rules; it('contains all stylistic rules, excluding deprecated or type checked ones', () => { diff --git a/packages/eslint-plugin/tests/docs.test.ts b/packages/eslint-plugin/tests/docs.test.ts index 9d99ca159103..b3000aa3a721 100644 --- a/packages/eslint-plugin/tests/docs.test.ts +++ b/packages/eslint-plugin/tests/docs.test.ts @@ -53,8 +53,8 @@ describe('Validating rule docs', () => { // 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', - 'no-parameter-properties.md', + 'no-duplicate-imports.mdx', + 'no-parameter-properties.mdx', ]); const rulesWithComplexOptions = new Set(['array-type', 'member-ordering']); @@ -64,7 +64,7 @@ describe('Validating rule docs', () => { .readdirSync(docsRoot) .filter(rule => !ignoredFiles.has(rule)); const ruleFiles = Object.keys(rules) - .map(rule => `${rule}.md`) + .map(rule => `${rule}.mdx`) .sort(); expect(files.sort()).toEqual(ruleFiles); @@ -73,11 +73,11 @@ describe('Validating rule docs', () => { for (const [ruleName, rule] of rulesData) { const { description } = rule.meta.docs!; - describe(`${ruleName}.md`, () => { - const filePath = path.join(docsRoot, `${ruleName}.md`); + describe(`${ruleName}.mdx`, () => { + const filePath = path.join(docsRoot, `${ruleName}.mdx`); const { fullText, tokens } = parseMarkdownFile(filePath); - test(`${ruleName}.md must start with frontmatter description`, () => { + test(`${ruleName}.mdx must start with frontmatter description`, () => { expect(tokens[0]).toMatchObject({ raw: '---\n', type: 'hr', @@ -91,8 +91,8 @@ describe('Validating rule docs', () => { }); }); - test(`${ruleName}.md must next have a blockquote directing to website`, () => { - expect(tokens[2]).toMatchObject({ + test(`${ruleName}.mdx must next have a blockquote directing to website`, () => { + expect(tokens[4]).toMatchObject({ text: [ `πŸ›‘ This file is source code, not the primary documentation location! πŸ›‘`, ``, @@ -139,7 +139,7 @@ describe('Validating rule docs', () => { ); for (const requiredHeading of requiredHeadings) { - const omissionComment = ``; + const omissionComment = `{/* Intentionally Omitted: ${requiredHeading} */}`; if ( !headingTexts.has(requiredHeading) && diff --git a/packages/eslint-plugin/tests/rules/ban-ts-comment.test.ts b/packages/eslint-plugin/tests/rules/ban-ts-comment.test.ts index 640f618200f8..51447bfe6130 100644 --- a/packages/eslint-plugin/tests/rules/ban-ts-comment.test.ts +++ b/packages/eslint-plugin/tests/rules/ban-ts-comment.test.ts @@ -13,6 +13,24 @@ ruleTester.run('ts-expect-error', rule, { /* @ts-expect-error running with long description in a block */ + `, + ` +/* @ts-expect-error not on the last line + */ + `, + ` +/** + * @ts-expect-error not on the last line + */ + `, + ` +/* not on the last line + * @ts-expect-error + */ + `, + ` +/* @ts-expect-error + * not on the last line */ `, { code: '// @ts-expect-error', @@ -26,6 +44,17 @@ ruleTester.run('ts-expect-error', rule, { }, ], }, + { + code: ` +/* + * @ts-expect-error here is why the error is expected */ + `, + options: [ + { + 'ts-expect-error': 'allow-with-description', + }, + ], + }, { code: '// @ts-expect-error exactly 21 characters', options: [ @@ -35,6 +64,18 @@ ruleTester.run('ts-expect-error', rule, { }, ], }, + { + code: ` +/* + * @ts-expect-error exactly 21 characters*/ + `, + options: [ + { + 'ts-expect-error': 'allow-with-description', + minimumDescriptionLength: 21, + }, + ], + }, { code: '// @ts-expect-error: TS1234 because xyz', options: [ @@ -46,6 +87,20 @@ ruleTester.run('ts-expect-error', rule, { }, ], }, + { + code: ` +/* + * @ts-expect-error: TS1234 because xyz */ + `, + options: [ + { + 'ts-expect-error': { + descriptionFormat: '^: TS\\d+ because .+$', + }, + minimumDescriptionLength: 10, + }, + ], + }, { code: noFormat`// @ts-expect-error πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦`, options: [ @@ -83,8 +138,37 @@ ruleTester.run('ts-expect-error', rule, { { code: ` /* -@ts-expect-error -*/ +@ts-expect-error */ + `, + options: [{ 'ts-expect-error': true }], + errors: [ + { + data: { directive: 'expect-error' }, + messageId: 'tsDirectiveComment', + line: 2, + column: 1, + }, + ], + }, + { + code: ` +/** on the last line + @ts-expect-error */ + `, + options: [{ 'ts-expect-error': true }], + errors: [ + { + data: { directive: 'expect-error' }, + messageId: 'tsDirectiveComment', + line: 2, + column: 1, + }, + ], + }, + { + code: ` +/** on the last line + * @ts-expect-error */ `, options: [{ 'ts-expect-error': true }], errors: [ @@ -96,6 +180,109 @@ ruleTester.run('ts-expect-error', rule, { }, ], }, + { + code: ` +/** + * @ts-expect-error: TODO */ + `, + options: [ + { + 'ts-expect-error': 'allow-with-description', + minimumDescriptionLength: 10, + }, + ], + errors: [ + { + data: { directive: 'expect-error', minimumDescriptionLength: 10 }, + messageId: 'tsDirectiveCommentRequiresDescription', + line: 2, + column: 1, + }, + ], + }, + { + code: ` +/** + * @ts-expect-error: TS1234 because xyz */ + `, + options: [ + { + 'ts-expect-error': { + descriptionFormat: '^: TS\\d+ because .+$', + }, + minimumDescriptionLength: 25, + }, + ], + errors: [ + { + data: { directive: 'expect-error', minimumDescriptionLength: 25 }, + messageId: 'tsDirectiveCommentRequiresDescription', + line: 2, + column: 1, + }, + ], + }, + { + code: ` +/** + * @ts-expect-error: TS1234 */ + `, + options: [ + { + 'ts-expect-error': { + descriptionFormat: '^: TS\\d+ because .+$', + }, + }, + ], + errors: [ + { + data: { directive: 'expect-error', format: '^: TS\\d+ because .+$' }, + messageId: 'tsDirectiveCommentDescriptionNotMatchPattern', + line: 2, + column: 1, + }, + ], + }, + { + code: ` +/** + * @ts-expect-error : TS1234 */ + `, + options: [ + { + 'ts-expect-error': { + descriptionFormat: '^: TS\\d+ because .+$', + }, + }, + ], + errors: [ + { + data: { directive: 'expect-error', format: '^: TS\\d+ because .+$' }, + messageId: 'tsDirectiveCommentDescriptionNotMatchPattern', + line: 2, + column: 1, + }, + ], + }, + { + code: ` +/** + * @ts-expect-error πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦ */ + `, + options: [ + { + 'ts-expect-error': 'allow-with-description', + }, + ], + errors: [ + { + data: { directive: 'expect-error', minimumDescriptionLength: 3 }, + messageId: 'tsDirectiveCommentRequiresDescription', + line: 2, + column: 1, + }, + ], + }, { code: '/** @ts-expect-error */', options: [{ 'ts-expect-error': true }], @@ -280,6 +467,29 @@ ruleTester.run('ts-ignore', rule, { }, ], }, + ` +/* + @ts-ignore +*/ + `, + ` +/* @ts-ignore not on the last line + */ + `, + ` +/** + * @ts-ignore not on the last line + */ + `, + ` +/* not on the last line + * @ts-expect-error + */ + `, + ` +/* @ts-ignore + * not on the last line */ + `, { code: '// @ts-ignore: TS1234 because xyz', options: [ @@ -299,6 +509,52 @@ ruleTester.run('ts-ignore', rule, { }, ], }, + { + code: ` +/* + * @ts-ignore here is why the error is expected */ + `, + options: [ + { + 'ts-ignore': 'allow-with-description', + }, + ], + }, + { + code: '// @ts-ignore exactly 21 characters', + options: [ + { + 'ts-ignore': 'allow-with-description', + minimumDescriptionLength: 21, + }, + ], + }, + { + code: ` +/* + * @ts-ignore exactly 21 characters*/ + `, + options: [ + { + 'ts-ignore': 'allow-with-description', + minimumDescriptionLength: 21, + }, + ], + }, + { + code: ` +/* + * @ts-ignore: TS1234 because xyz */ + `, + options: [ + { + 'ts-ignore': { + descriptionFormat: '^: TS\\d+ because .+$', + }, + minimumDescriptionLength: 10, + }, + ], + }, ], invalid: [ { @@ -373,8 +629,7 @@ ruleTester.run('ts-ignore', rule, { { code: ` /* - @ts-ignore -*/ + @ts-ignore */ `, options: [{ 'ts-ignore': true }], errors: [ @@ -387,8 +642,53 @@ ruleTester.run('ts-ignore', rule, { messageId: 'replaceTsIgnoreWithTsExpectError', output: ` /* - @ts-expect-error -*/ + @ts-expect-error */ + `, + }, + ], + }, + ], + }, + { + code: ` +/** on the last line + @ts-ignore */ + `, + options: [{ 'ts-ignore': true }], + errors: [ + { + messageId: 'tsIgnoreInsteadOfExpectError', + line: 2, + column: 1, + suggestions: [ + { + messageId: 'replaceTsIgnoreWithTsExpectError', + output: ` +/** on the last line + @ts-expect-error */ + `, + }, + ], + }, + ], + }, + { + code: ` +/** on the last line + * @ts-ignore */ + `, + options: [{ 'ts-ignore': true }], + errors: [ + { + messageId: 'tsIgnoreInsteadOfExpectError', + line: 2, + column: 1, + suggestions: [ + { + messageId: 'replaceTsIgnoreWithTsExpectError', + output: ` +/** on the last line + * @ts-expect-error */ `, }, ], @@ -412,6 +712,64 @@ ruleTester.run('ts-ignore', rule, { }, ], }, + { + code: ` +/** + * @ts-ignore: TODO */ + `, + options: [ + { + 'ts-expect-error': 'allow-with-description', + minimumDescriptionLength: 10, + }, + ], + errors: [ + { + messageId: 'tsIgnoreInsteadOfExpectError', + line: 2, + column: 1, + suggestions: [ + { + messageId: 'replaceTsIgnoreWithTsExpectError', + output: ` +/** + * @ts-expect-error: TODO */ + `, + }, + ], + }, + ], + }, + { + code: ` +/** + * @ts-ignore: TS1234 because xyz */ + `, + options: [ + { + 'ts-expect-error': { + descriptionFormat: '^: TS\\d+ because .+$', + }, + minimumDescriptionLength: 25, + }, + ], + errors: [ + { + messageId: 'tsIgnoreInsteadOfExpectError', + line: 2, + column: 1, + suggestions: [ + { + messageId: 'replaceTsIgnoreWithTsExpectError', + output: ` +/** + * @ts-expect-error: TS1234 because xyz */ + `, + }, + ], + }, + ], + }, { code: '// @ts-ignore: Suppress next line', errors: [ @@ -623,6 +981,19 @@ ruleTester.run('ts-nocheck', rule, { }, ], }, + '//// @ts-nocheck - pragma comments may contain 2 or 3 leading slashes', + ` +/** + @ts-nocheck +*/ + `, + ` +/* + @ts-nocheck +*/ + `, + '/** @ts-nocheck */', + '/* @ts-nocheck */', ], invalid: [ { @@ -648,46 +1019,6 @@ ruleTester.run('ts-nocheck', rule, { }, ], }, - { - code: '/* @ts-nocheck */', - options: [{ 'ts-nocheck': true }], - errors: [ - { - data: { directive: 'nocheck' }, - messageId: 'tsDirectiveComment', - line: 1, - column: 1, - }, - ], - }, - { - code: ` -/* - @ts-nocheck -*/ - `, - options: [{ 'ts-nocheck': true }], - errors: [ - { - data: { directive: 'nocheck' }, - messageId: 'tsDirectiveComment', - line: 2, - column: 1, - }, - ], - }, - { - code: '/** @ts-nocheck */', - options: [{ 'ts-nocheck': true }], - errors: [ - { - data: { directive: 'nocheck' }, - messageId: 'tsDirectiveComment', - line: 1, - column: 1, - }, - ], - }, { code: '// @ts-nocheck: Suppress next line', errors: [ @@ -699,17 +1030,6 @@ ruleTester.run('ts-nocheck', rule, { }, ], }, - { - code: '/////@ts-nocheck: Suppress next line', - errors: [ - { - data: { directive: 'nocheck' }, - messageId: 'tsDirectiveComment', - line: 1, - column: 1, - }, - ], - }, { code: ` if (false) { @@ -849,31 +1169,17 @@ ruleTester.run('ts-check', rule, { }, ], }, - ], - invalid: [ { - code: '// @ts-check', + code: '//// @ts-check - pragma comments may contain 2 or 3 leading slashes', options: [{ 'ts-check': true }], - errors: [ - { - data: { directive: 'check' }, - messageId: 'tsDirectiveComment', - line: 1, - column: 1, - }, - ], }, { - code: '/* @ts-check */', + code: ` +/** + @ts-check +*/ + `, options: [{ 'ts-check': true }], - errors: [ - { - data: { directive: 'check' }, - messageId: 'tsDirectiveComment', - line: 1, - column: 1, - }, - ], }, { code: ` @@ -882,29 +1188,19 @@ ruleTester.run('ts-check', rule, { */ `, options: [{ 'ts-check': true }], - errors: [ - { - data: { directive: 'check' }, - messageId: 'tsDirectiveComment', - line: 2, - column: 1, - }, - ], }, { code: '/** @ts-check */', options: [{ 'ts-check': true }], - errors: [ - { - data: { directive: 'check' }, - messageId: 'tsDirectiveComment', - line: 1, - column: 1, - }, - ], }, { - code: '// @ts-check: Suppress next line', + code: '/* @ts-check */', + options: [{ 'ts-check': true }], + }, + ], + invalid: [ + { + code: '// @ts-check', options: [{ 'ts-check': true }], errors: [ { @@ -916,9 +1212,8 @@ ruleTester.run('ts-check', rule, { ], }, { - code: '/////@ts-check: Suppress next line', + code: '// @ts-check: Suppress next line', options: [{ 'ts-check': true }], - errors: [ { data: { directive: 'check' }, diff --git a/packages/eslint-plugin/tests/rules/class-literal-property-style.test.ts b/packages/eslint-plugin/tests/rules/class-literal-property-style.test.ts index 1959b1cdaa8d..9e6519c10db0 100644 --- a/packages/eslint-plugin/tests/rules/class-literal-property-style.test.ts +++ b/packages/eslint-plugin/tests/rules/class-literal-property-style.test.ts @@ -220,6 +220,45 @@ class Mx { `, options: ['getters'], }, + { + code: ` + class A { + private readonly foo: string = 'bar'; + constructor(foo: string) { + this.foo = foo; + } + } + `, + options: ['getters'], + }, + { + code: ` + class A { + private readonly foo: string = 'bar'; + constructor(foo: string) { + this['foo'] = foo; + } + } + `, + options: ['getters'], + }, + { + code: ` + class A { + private readonly foo: string = 'bar'; + constructor(foo: string) { + const bar = new (class { + private readonly foo: string = 'baz'; + constructor() { + this.foo = 'qux'; + } + })(); + this['foo'] = foo; + } + } + `, + options: ['getters'], + }, ], invalid: [ { @@ -660,5 +699,126 @@ class Mx { ], options: ['getters'], }, + { + code: ` +class A { + private readonly foo: string = 'bar'; + constructor(foo: string) { + const bar = new (class { + private readonly foo: string = 'baz'; + constructor() { + this.foo = 'qux'; + } + })(); + } +} + `, + options: ['getters'], + errors: [ + { + messageId: 'preferGetterStyle', + column: 20, + line: 3, + suggestions: [ + { + messageId: 'preferGetterStyleSuggestion', + output: ` +class A { + private get foo() { return 'bar'; } + constructor(foo: string) { + const bar = new (class { + private readonly foo: string = 'baz'; + constructor() { + this.foo = 'qux'; + } + })(); + } +} + `, + }, + ], + }, + ], + }, + { + code: ` +class A { + private readonly ['foo']: string = 'bar'; + constructor(foo: string) { + const bar = new (class { + private readonly foo: string = 'baz'; + constructor() {} + })(); + + if (bar) { + this.foo = 'baz'; + } + } +} + `, + options: ['getters'], + errors: [ + { + messageId: 'preferGetterStyle', + column: 24, + line: 6, + suggestions: [ + { + messageId: 'preferGetterStyleSuggestion', + output: ` +class A { + private readonly ['foo']: string = 'bar'; + constructor(foo: string) { + const bar = new (class { + private get foo() { return 'baz'; } + constructor() {} + })(); + + if (bar) { + this.foo = 'baz'; + } + } +} + `, + }, + ], + }, + ], + }, + { + code: ` +class A { + private readonly foo: string = 'bar'; + constructor(foo: string) { + function func() { + this.foo = 'aa'; + } + } +} + `, + options: ['getters'], + errors: [ + { + messageId: 'preferGetterStyle', + column: 20, + line: 3, + suggestions: [ + { + messageId: 'preferGetterStyleSuggestion', + output: ` +class A { + private get foo() { return 'bar'; } + constructor(foo: string) { + function func() { + this.foo = 'aa'; + } + } +} + `, + }, + ], + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts index 117a7a374d33..9e01955b4a5c 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts @@ -2230,6 +2230,49 @@ let baz: D; }, ], }, + // https://github.com/typescript-eslint/typescript-eslint/issues/7209 + { + code: ` +import 'foo'; +import type { Foo, Bar } from 'foo'; +@deco +class A { + constructor(foo: Foo) {} +} + `, + output: ` +import 'foo'; +import { Foo} from 'foo'; +import type { Bar } from 'foo'; +@deco +class A { + constructor(foo: Foo) {} +} + `, + errors: [{ messageId: 'aImportInDecoMeta', line: 3, column: 1 }], + parserOptions: withMetaConfigParserOptions, + }, + { + code: ` +import {} from 'foo'; +import type { Foo, Bar } from 'foo'; +@deco +class A { + constructor(foo: Foo) {} +} + `, + output: ` +import {} from 'foo'; +import { Foo} from 'foo'; +import type { Bar } from 'foo'; +@deco +class A { + constructor(foo: Foo) {} +} + `, + errors: [{ messageId: 'aImportInDecoMeta', line: 3, column: 1 }], + parserOptions: withMetaConfigParserOptions, + }, { code: ` import A from 'foo'; diff --git a/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts b/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts index 8b0a05d6b5b4..2036927e0ca9 100644 --- a/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts +++ b/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts @@ -8,6 +8,7 @@ const ruleTester = new RuleTester({ ruleTester.run('explicit-function-return-type', rule, { valid: [ + 'return;', { code: ` function test(): void { @@ -267,6 +268,17 @@ const myObj = { }, { code: ` +() => { + const foo = 'foo'; + return function (): string { + return foo; + }; +}; + `, + options: [{ allowHigherOrderFunctions: true }], + }, + { + code: ` function fn() { return (): void => {}; } @@ -276,6 +288,31 @@ function fn() { { code: ` function fn() { + return function (): void {}; +} + `, + options: [{ allowHigherOrderFunctions: true }], + }, + { + code: ` +function fn() { + const bar = () => (): number => 1; + return function (): void {}; +} + `, + options: [{ allowHigherOrderFunctions: true }], + }, + { + code: ` +function fn(arg: boolean) { + if (arg) { + return () => (): number => 1; + } else { + return function (): string { + return 'foo'; + }; + } + return function (): void {}; } `, @@ -1230,6 +1267,43 @@ function fn() { }, { code: ` +function fn() { + const bar = () => (): number => 1; + const baz = () => () => 'baz'; + return function (): void {}; +} + `, + options: [{ allowHigherOrderFunctions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 4, + endLine: 4, + column: 24, + endColumn: 26, + }, + ], + }, + { + code: ` +function fn(arg: boolean) { + if (arg) return 'string'; + return function (): void {}; +} + `, + options: [{ allowHigherOrderFunctions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + endLine: 2, + column: 1, + endColumn: 12, + }, + ], + }, + { + code: ` function FunctionDeclaration() { return function FunctionExpression_Within_FunctionDeclaration() { return function FunctionExpression_Within_FunctionExpression() { diff --git a/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts b/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts index 8a945ceca631..b96cb31d74ed 100644 --- a/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts +++ b/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts @@ -316,6 +316,28 @@ export default () => () => { }, { code: ` +export default () => () => { + const foo = 'foo'; + return (): void => { + return; + }; +}; + `, + options: [{ allowHigherOrderFunctions: true }], + }, + { + code: ` +export default () => () => { + const foo = () => (): string => 'foo'; + return (): void => { + return; + }; +}; + `, + options: [{ allowHigherOrderFunctions: true }], + }, + { + code: ` export class Accumulator { private count: number = 0; @@ -1728,6 +1750,27 @@ export function foo(outer) { }, ], }, + { + code: ` +export function foo(outer: boolean) { + if (outer) { + return 'string'; + } + return function (inner): void {}; +} + `, + options: [{ allowHigherOrderFunctions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + column: 8, + data: { + name: 'inner', + }, + }, + ], + }, // test a few different argument patterns { code: ` diff --git a/packages/eslint-plugin/tests/rules/no-array-constructor.test.ts b/packages/eslint-plugin/tests/rules/no-array-constructor.test.ts index 4f3589400893..6bc1d719e976 100644 --- a/packages/eslint-plugin/tests/rules/no-array-constructor.test.ts +++ b/packages/eslint-plugin/tests/rules/no-array-constructor.test.ts @@ -7,7 +7,7 @@ const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', }); -const messageId = 'useLiteral' as const; +const messageId = 'useLiteral'; ruleTester.run('no-array-constructor', rule, { valid: [ diff --git a/packages/eslint-plugin/tests/rules/no-redundant-type-constituents.test.ts b/packages/eslint-plugin/tests/rules/no-redundant-type-constituents.test.ts index 94d26e6f197f..b0a9ca844da9 100644 --- a/packages/eslint-plugin/tests/rules/no-redundant-type-constituents.test.ts +++ b/packages/eslint-plugin/tests/rules/no-redundant-type-constituents.test.ts @@ -161,6 +161,10 @@ ruleTester.run('no-redundant-type-constituents', rule, { type B = \`\${string}\`; type T = B & null; `, + ` + type T = 'a' | 1 | 'b'; + type U = T & string; + `, ], invalid: [ @@ -785,5 +789,46 @@ ruleTester.run('no-redundant-type-constituents', rule, { }, ], }, + { + code: ` + type T = 'a' | 'b'; + type U = T & string; + `, + errors: [ + { + column: 18, + data: { + literal: '"a" | "b"', + primitive: 'string', + }, + messageId: 'primitiveOverridden', + }, + ], + }, + { + code: ` + type S = 1 | 2; + type T = 'a' | 'b'; + type U = S & T & string & number; + `, + errors: [ + { + column: 18, + data: { + literal: '1 | 2', + primitive: 'number', + }, + messageId: 'primitiveOverridden', + }, + { + column: 22, + data: { + literal: '"a" | "b"', + primitive: 'string', + }, + messageId: 'primitiveOverridden', + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-qualifier.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-qualifier.test.ts index 5f118f55ff69..d03236ac2189 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-qualifier.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-qualifier.test.ts @@ -57,6 +57,30 @@ namespace X { ` namespace X { const z = X.y; +} + `, + ` +enum Foo { + One, +} + +namespace Foo { + export function bar() { + return Foo.One; + } +} + `, + ` +namespace Foo { + export enum Foo { + One, + } +} + +namespace Foo { + export function bar() { + return Foo.One; + } } `, ], diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts index 3eb36636a839..cde4f7a21659 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts @@ -6,6 +6,7 @@ import rule from '../../src/rules/no-unnecessary-type-assertion'; const rootDir = path.resolve(__dirname, '../fixtures/'); const ruleTester = new RuleTester({ parserOptions: { + EXPERIMENTAL_useProjectService: false, sourceType: 'module', tsconfigRootDir: rootDir, project: './tsconfig.json', @@ -13,6 +14,11 @@ const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', }); +const optionsWithOnUncheckedIndexedAccess = { + tsconfigRootDir: rootDir, + project: './tsconfig.noUncheckedIndexedAccess.json', +}; + ruleTester.run('no-unnecessary-type-assertion', rule, { valid: [ ` @@ -25,10 +31,34 @@ if ( const name = member.id as TSESTree.StringLiteral; } `, + ` + const c = 1; + let z = c as number; + `, + ` + const c = 1; + let z = c as const; + `, + ` + const c = 1; + let z = c as 1; + `, + ` + type Bar = 'bar'; + const data = { + x: 'foo' as 'foo', + y: 'bar' as Bar, + }; + `, + "[1, 2, 3, 4, 5].map(x => [x, 'A' + x] as [number, string]);", + ` + let x: Array<[number, string]> = [1, 2, 3, 4, 5].map( + x => [x, 'A' + x] as [number, string], + ); + `, + 'let y = 1 as 1;', 'const foo = 3 as number;', 'const foo = 3;', - 'const foo = <3>3;', - 'const foo = 3 as 3;', ` type Tuple = [3, 'hi', 'bye']; const foo = [3, 'hi', 'bye'] as Tuple; @@ -174,9 +204,6 @@ const c = [...a, ...b] as const; { code: 'const a = [1, 2] as const;', }, - { - code: "const a = 'a' as const;", - }, { code: "const a = { foo: 'foo' } as const;", }, @@ -190,9 +217,6 @@ const c = [...a, ...b]; { code: 'const a = [1, 2];', }, - { - code: "const a = 'a';", - }, { code: "const a = { foo: 'foo' };", }, @@ -242,9 +266,62 @@ const item = arr[0] as object; declare const arr: (object | undefined)[]; const item = arr[0]; `, + { + code: ` +function foo(item: string) {} +function bar(items: string[]) { + for (let i = 0; i < items.length; i++) { + foo(items[i]!); + } +} + `, + parserOptions: optionsWithOnUncheckedIndexedAccess, + }, ], invalid: [ + { + code: "const a = 'a' as const;", + output: "const a = 'a';", + errors: [{ messageId: 'unnecessaryAssertion', line: 1 }], + }, + { + code: "const a = 'a';", + output: "const a = 'a';", + errors: [{ messageId: 'unnecessaryAssertion', line: 1 }], + }, + { + code: 'const foo = <3>3;', + output: 'const foo = 3;', + errors: [{ messageId: 'unnecessaryAssertion', line: 1, column: 13 }], + }, + { + code: 'const foo = 3 as 3;', + output: 'const foo = 3;', + errors: [{ messageId: 'unnecessaryAssertion', line: 1, column: 13 }], + }, + { + code: ` + type Foo = 3; + const foo = 3; + `, + output: ` + type Foo = 3; + const foo = 3; + `, + errors: [{ messageId: 'unnecessaryAssertion', line: 3, column: 21 }], + }, + { + code: ` + type Foo = 3; + const foo = 3 as Foo; + `, + output: ` + type Foo = 3; + const foo = 3; + `, + errors: [{ messageId: 'unnecessaryAssertion', line: 3, column: 21 }], + }, { code: ` const foo = 3; @@ -815,5 +892,31 @@ const foo = (3 + 5); }, ], }, + // onUncheckedIndexedAccess = false + { + code: ` +function foo(item: string) {} +function bar(items: string[]) { + for (let i = 0; i < items.length; i++) { + foo(items[i]!); + } +} + `, + output: ` +function foo(item: string) {} +function bar(items: string[]) { + for (let i = 0; i < items.length; i++) { + foo(items[i]); + } +} + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + line: 5, + column: 9, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/no-unused-expressions.test.ts b/packages/eslint-plugin/tests/rules/no-unused-expressions.test.ts index eb5413f49e3a..44476c4c642f 100644 --- a/packages/eslint-plugin/tests/rules/no-unused-expressions.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unused-expressions.test.ts @@ -325,5 +325,61 @@ Foo; }, ]), }, + { + code: ` +declare const foo: number | undefined; +foo; + `, + errors: error([ + { + line: 3, + endLine: 3, + column: 1, + endColumn: 5, + }, + ]), + }, + { + code: ` +declare const foo: number | undefined; +foo as any; + `, + errors: error([ + { + line: 3, + endLine: 3, + column: 1, + endColumn: 12, + }, + ]), + }, + { + code: ` +declare const foo: number | undefined; +foo; + `, + errors: error([ + { + line: 3, + endLine: 3, + column: 1, + endColumn: 10, + }, + ]), + }, + { + code: ` +declare const foo: number | undefined; +foo!; + `, + errors: error([ + { + line: 3, + endLine: 3, + column: 1, + endColumn: 6, + }, + ]), + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/prefer-reduce-type-parameter.test.ts b/packages/eslint-plugin/tests/rules/prefer-reduce-type-parameter.test.ts index 43aadcec074b..d93212ae43f3 100644 --- a/packages/eslint-plugin/tests/rules/prefer-reduce-type-parameter.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-reduce-type-parameter.test.ts @@ -39,6 +39,24 @@ ruleTester.run('prefer-reduce-type-parameter', rule, { '[1, 2, 3].reduce((sum, num) => sum + num, 0);', '[1, 2, 3].reduce((a, s) => a.concat(s * 2), []);', '[1, 2, 3]?.reduce((a, s) => a.concat(s * 2), []);', + ` + declare const tuple: [number, number, number]; + tuple.reduce((a, s) => a.concat(s * 2), []); + `, + ` + type Reducer = { reduce: (callback: (arg: any) => any, arg: any) => any }; + declare const tuple: [number, number, number] | Reducer; + tuple.reduce(a => { + return a.concat(1); + }, [] as number[]); + `, + ` + type Reducer = { reduce: (callback: (arg: any) => any, arg: any) => any }; + declare const arrayOrReducer: number[] & Reducer; + arrayOrReducer.reduce(a => { + return a.concat(1); + }, [] as number[]); + `, ], invalid: [ { @@ -206,5 +224,56 @@ function f(a: U) { }, ], }, + { + code: ` +declare const tuple: [number, number, number]; +tuple.reduce((a, s) => a.concat(s * 2), [] as number[]); + `, + output: ` +declare const tuple: [number, number, number]; +tuple.reduce((a, s) => a.concat(s * 2), []); + `, + errors: [ + { + messageId: 'preferTypeParameter', + column: 41, + line: 3, + }, + ], + }, + { + code: ` +declare const tupleOrArray: [number, number, number] | number[]; +tupleOrArray.reduce((a, s) => a.concat(s * 2), [] as number[]); + `, + output: ` +declare const tupleOrArray: [number, number, number] | number[]; +tupleOrArray.reduce((a, s) => a.concat(s * 2), []); + `, + errors: [ + { + messageId: 'preferTypeParameter', + column: 48, + line: 3, + }, + ], + }, + { + code: ` +declare const tuple: [number, number, number] & number[]; +tuple.reduce((a, s) => a.concat(s * 2), [] as number[]); + `, + output: ` +declare const tuple: [number, number, number] & number[]; +tuple.reduce((a, s) => a.concat(s * 2), []); + `, + errors: [ + { + messageId: 'preferTypeParameter', + column: 41, + line: 3, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts b/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts index 1701f9a44960..698471ae00a3 100644 --- a/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts +++ b/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts @@ -131,6 +131,30 @@ ruleTester.run('restrict-template-expressions', rule, { } `, }, + // allowArray + { + options: [{ allowArray: true }], + code: ` + const arg = []; + const msg = \`arg = \${arg}\`; + `, + }, + { + options: [{ allowArray: true }], + code: ` + const arg = []; + const msg = \`arg = \${arg || 'default'}\`; + `, + }, + { + options: [{ allowArray: true }], + code: ` + const arg = []; + function test(arg: T) { + return \`arg = \${arg}\`; + } + `, + }, // allowAny { options: [{ allowAny: true }], @@ -341,6 +365,20 @@ ruleTester.run('restrict-template-expressions', rule, { ], options: [{ allowNullish: false }], }, + { + code: ` + const msg = \`arg = \${[, 2]}\`; + `, + errors: [ + { + messageId: 'invalidType', + data: { type: '(number | undefined)[]' }, + line: 2, + column: 30, + }, + ], + options: [{ allowNullish: false, allowArray: true }], + }, { code: ` declare const arg: number; @@ -369,11 +407,7 @@ ruleTester.run('restrict-template-expressions', rule, { column: 30, }, ], - options: [ - { - allowBoolean: false, - }, - ], + options: [{ allowBoolean: false }], }, { options: [{ allowNumber: true, allowBoolean: true, allowNullish: true }], diff --git a/packages/eslint-plugin/tests/rules/unbound-method.test.ts b/packages/eslint-plugin/tests/rules/unbound-method.test.ts index d9e8cfaf70d5..81f4f126fd32 100644 --- a/packages/eslint-plugin/tests/rules/unbound-method.test.ts +++ b/packages/eslint-plugin/tests/rules/unbound-method.test.ts @@ -58,6 +58,23 @@ ruleTester.run('unbound-method', rule, { '[5.2, 7.1, 3.6].map(Math.floor);', 'const x = console.log;', 'const x = Object.defineProperty;', + ` + const o = { + f: function (this: void) {}, + }; + const f = o.f; + `, + ` + const { alert } = window; + `, + ` + let b = window.blur; + `, + ` + function foo() {} + const fooObject = { foo }; + const { foo: bar } = fooObject; + `, ...[ 'instance.bound();', 'instance.unbound();', @@ -644,5 +661,20 @@ const { b, a } = values; }, ], }, + // https://github.com/typescript-eslint/typescript-eslint/issues/8636 + { + code: ` +const objectLiteral = { + f: function () {}, +}; +const f = objectLiteral.f; + `, + errors: [ + { + line: 5, + messageId: 'unboundWithoutThisAnnotation', + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/use-unknown-in-catch-callback-variable.test.ts b/packages/eslint-plugin/tests/rules/use-unknown-in-catch-callback-variable.test.ts new file mode 100644 index 000000000000..80fe9e3cee9b --- /dev/null +++ b/packages/eslint-plugin/tests/rules/use-unknown-in-catch-callback-variable.test.ts @@ -0,0 +1,735 @@ +import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; + +import rule from '../../src/rules/use-unknown-in-catch-callback-variable'; +import { getFixturesRootDir } from '../RuleTester'; + +const rootDir = getFixturesRootDir(); + +const ruleTester = new RuleTester({ + parserOptions: { + tsconfigRootDir: rootDir, + project: './tsconfig.json', + }, + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('use-unknown-in-catch-callback-variable', rule, { + valid: [ + ` + Promise.resolve().catch((err: unknown) => { + throw err; + }); + `, + ` + Promise.resolve().catch(() => { + throw new Error(); + }); + `, + ` + Promise.reject(new Error()).catch("not this rule's problem"); + `, + ` + declare const crappyHandler: (() => void) | 2; + Promise.reject(new Error()).catch(crappyHandler); + `, + + ` + Promise.resolve().catch((...args: [unknown]) => { + throw args[0]; + }); + `, + ` + Promise.resolve().catch((...args: [a: unknown]) => { + const err = args[0]; + }); + `, + + ` + Promise.resolve().catch((...args: readonly unknown[]) => { + throw args[0]; + }); + `, + + ` + declare const notAPromise: { catch: (f: Function) => void }; + notAPromise.catch((...args: [a: string, string]) => { + throw args[0]; + }); + `, + ` + declare const catchArgs: [(x: unknown) => void]; + Promise.reject(new Error()).catch(...catchArgs); + `, + + ` + declare const catchArgs: [ + string | (() => never), + (shouldntFlag: string) => void, + number, + ]; + Promise.reject(new Error()).catch(...catchArgs); + `, + ` + declare const catchArgs: ['not callable']; + Promise.reject(new Error()).catch(...catchArgs); + `, + ` + declare const emptySpread: []; + Promise.reject(new Error()).catch(...emptySpread); + `, + ` + Promise.resolve().catch( + ( + ...err: [unknown, string | ((number | unknown) & { b: () => void }), string] + ) => { + throw err; + }, + ); + `, + ` + declare const notAMemberExpression: (...args: any[]) => {}; + notAMemberExpression( + 'This helps get 100% code cov', + "but doesn't test anything useful related to the rule.", + ); + `, + ` + Promise.resolve().catch((...[args]: [unknown]) => { + console.log(args); + }); + `, + ` + Promise.resolve().catch((...{ find }: [unknown]) => { + console.log(find); + }); + `, + ], + + invalid: [ + { + code: ` +Promise.resolve().catch((err: Error) => { + throw err; +}); + `, + errors: [ + { + line: 2, + messageId: 'useUnknown', + suggestions: [ + { + messageId: 'wrongTypeAnnotationSuggestion', + output: ` +Promise.resolve().catch((err: unknown) => { + throw err; +}); + `, + }, + ], + }, + ], + }, + + { + code: ` +Promise.resolve().catch((e, ...rest: []) => { + throw err; +}); + `, + errors: [ + { + line: 2, + messageId: 'useUnknown', + suggestions: [ + { + messageId: 'addUnknownTypeAnnotationSuggestion', + output: ` +Promise.resolve().catch((e: unknown, ...rest: []) => { + throw err; +}); + `, + }, + ], + }, + ], + }, + + { + code: ` +Promise.resolve().catch( + (err: string | ((number | unknown) & { b: () => void })) => { + throw err; + }, +); + `, + errors: [ + { + line: 3, + messageId: 'useUnknown', + suggestions: [ + { + messageId: 'wrongTypeAnnotationSuggestion', + output: ` +Promise.resolve().catch( + (err: unknown) => { + throw err; + }, +); + `, + }, + ], + }, + ], + }, + + { + code: ` +Promise.resolve().catch( + ( + ...err: [ + unknown[], + string | ((number | unknown) & { b: () => void }), + string, + ] + ) => { + throw err; + }, +); + `, + errors: [ + { + line: 4, + messageId: 'useUnknown', + suggestions: [ + { + messageId: 'wrongRestTypeAnnotationSuggestion', + output: ` +Promise.resolve().catch( + ( + ...err: [unknown] + ) => { + throw err; + }, +); + `, + }, + ], + }, + ], + }, + + { + code: ` +Promise.resolve().catch(function (err: string) { + throw err; +}); + `, + errors: [ + { + line: 2, + messageId: 'useUnknown', + suggestions: [ + { + messageId: 'wrongTypeAnnotationSuggestion', + output: ` +Promise.resolve().catch(function (err: unknown) { + throw err; +}); + `, + }, + ], + }, + ], + }, + + { + code: ` +Promise.resolve().catch(function (err /* awkward spot for comment */) { + throw err; +}); + `, + errors: [ + { + line: 2, + messageId: 'useUnknown', + suggestions: [ + { + messageId: 'addUnknownTypeAnnotationSuggestion', + output: ` +Promise.resolve().catch(function (err: unknown /* awkward spot for comment */) { + throw err; +}); + `, + }, + ], + }, + ], + }, + + { + code: ` +type InvalidHandler = (arg: any) => void; +Promise.resolve().catch(( + function (err /* awkward spot for comment */) { + throw err; + } +)); + `, + errors: [ + { + line: 3, + messageId: 'useUnknown', + // We should _not_ make a suggestion due to the type assertion. + suggestions: null, + }, + ], + }, + + { + code: ` +Promise.resolve().catch(function namedCallback(err: string) { + throw err; +}); + `, + errors: [ + { + line: 2, + messageId: 'useUnknown', + suggestions: [ + { + messageId: 'wrongTypeAnnotationSuggestion', + output: ` +Promise.resolve().catch(function namedCallback(err: unknown) { + throw err; +}); + `, + }, + ], + }, + ], + }, + + { + code: ` +Promise.resolve().catch(err => { + throw err; +}); + `, + errors: [ + { + line: 2, + messageId: 'useUnknown', + suggestions: [ + { + messageId: 'addUnknownTypeAnnotationSuggestion', + output: ` +Promise.resolve().catch((err: unknown) => { + throw err; +}); + `, + }, + ], + }, + ], + }, + + { + code: ` +Promise.resolve().catch((err?) => { + throw err; +}); + `, + errors: [ + { + line: 2, + messageId: 'useUnknown', + suggestions: [ + { + messageId: 'addUnknownTypeAnnotationSuggestion', + output: ` +Promise.resolve().catch((err?: unknown) => { + throw err; +}); + `, + }, + ], + }, + ], + }, + + { + code: ` +Promise.resolve().catch((err?: string) => { + throw err; +}); + `, + errors: [ + { + line: 2, + messageId: 'useUnknown', + suggestions: [ + { + messageId: 'wrongTypeAnnotationSuggestion', + output: ` +Promise.resolve().catch((err?: unknown) => { + throw err; +}); + `, + }, + ], + }, + ], + }, + + { + code: noFormat` +Promise.resolve().catch(err/* with comment */=> { + throw err; +}); + `, + errors: [ + { + line: 2, + messageId: 'useUnknown', + suggestions: [ + { + messageId: 'addUnknownTypeAnnotationSuggestion', + output: ` +Promise.resolve().catch((err: unknown)/* with comment */=> { + throw err; +}); + `, + }, + ], + }, + ], + }, + + { + code: ` +Promise.resolve().catch((err = 2) => { + throw err; +}); + `, + errors: [ + { + line: 2, + messageId: 'useUnknown', + suggestions: [ + { + messageId: 'addUnknownTypeAnnotationSuggestion', + output: ` +Promise.resolve().catch((err: unknown = 2) => { + throw err; +}); + `, + }, + ], + }, + ], + }, + + { + code: ` +Promise.resolve().catch((err: any /* comment 1 */ = /* comment 2 */ 2) => { + throw err; +}); + `, + errors: [ + { + line: 2, + messageId: 'useUnknown', + suggestions: [ + { + messageId: 'wrongTypeAnnotationSuggestion', + output: ` +Promise.resolve().catch((err: unknown /* comment 1 */ = /* comment 2 */ 2) => { + throw err; +}); + `, + }, + ], + }, + ], + }, + + { + code: ` +Promise.resolve().catch((...args) => { + throw args[0]; +}); + `, + errors: [ + { + line: 2, + messageId: 'useUnknown', + suggestions: [ + { + messageId: 'addUnknownRestTypeAnnotationSuggestion', + output: ` +Promise.resolve().catch((...args: [unknown]) => { + throw args[0]; +}); + `, + }, + ], + }, + ], + }, + + { + code: ` +Promise.reject(new Error('I will reject!')).catch(([err]: [unknown]) => { + console.log(err); +}); + `, + errors: [ + { + line: 2, + messageId: 'useUnknownArrayDestructuringPattern', + suggestions: null, + }, + ], + }, + + { + code: ` +declare const yoloHandler: (x: any) => void; +Promise.reject(new Error('I will reject!')).catch(yoloHandler); + `, + errors: [ + { + line: 3, + messageId: 'useUnknown', + }, + ], + }, + + { + code: ` +declare let iPromiseImAPromise: Promise; +declare const catchArgs: [(x: any) => void]; +iPromiseImAPromise.catch(...catchArgs); + `, + errors: [ + { + line: 4, + messageId: 'useUnknown', + }, + ], + }, + + { + code: ` +declare const catchArgs: [ + string | (() => never) | ((shouldFlag: string) => void), + number, +]; +Promise.reject(new Error()).catch(...catchArgs); + `, + errors: [ + { + line: 6, + messageId: 'useUnknown', + }, + ], + }, + + { + code: ` +declare const you: []; +declare const cannot: []; +declare const fool: []; +declare const me: [(shouldFlag: Error) => void] | undefined; +Promise.resolve(undefined).catch(...you, ...cannot, ...fool, ...me!); + `, + errors: [ + { + line: 6, + messageId: 'useUnknownSpreadArgs', + }, + ], + }, + + { + code: ` +declare const really: undefined[]; +declare const dumb: []; +declare const code: (shouldFlag: Error) => void; +Promise.resolve(undefined).catch(...really, ...dumb, code); + `, + errors: [ + { + line: 5, + messageId: 'useUnknownSpreadArgs', + }, + ], + }, + + { + code: ` +Promise.resolve(' a string ').catch( + (a: any, b: () => any, c: (x: string & number) => void) => {}, +); + `, + errors: [ + { + line: 3, + messageId: 'useUnknown', + suggestions: [ + { + messageId: 'wrongTypeAnnotationSuggestion', + output: ` +Promise.resolve(' a string ').catch( + (a: unknown, b: () => any, c: (x: string & number) => void) => {}, +); + `, + }, + ], + }, + ], + }, + + { + code: ` +Promise.resolve('object destructuring').catch(({}) => {}); + `, + errors: [ + { + line: 2, + messageId: 'useUnknownObjectDestructuringPattern', + }, + ], + }, + + { + code: ` +Promise.resolve('object destructuring').catch(function ({ gotcha }) { + return null; +}); + `, + errors: [ + { + line: 2, + messageId: 'useUnknownObjectDestructuringPattern', + }, + ], + }, + + { + code: ` +Promise.resolve()['catch']((x: any) => 'return'); + `, + errors: [ + { + line: 2, + messageId: 'useUnknown', + suggestions: [ + { + messageId: 'wrongTypeAnnotationSuggestion', + output: ` +Promise.resolve()['catch']((x: unknown) => 'return'); + `, + }, + ], + }, + ], + }, + + { + code: ` +declare const x: any; +Promise.resolve().catch(...x); + `, + errors: [ + { + line: 3, + messageId: 'useUnknown', + suggestions: null, + }, + ], + }, + + { + code: ` +declare const x: ((x: any) => string)[]; +Promise.resolve('string promise').catch(...x); + `, + errors: [ + { + line: 3, + messageId: 'useUnknown', + suggestions: null, + }, + ], + }, + + { + code: ` +Promise.reject().catch((...x: any) => {}); + `, + errors: [ + { + line: 2, + messageId: 'useUnknown', + suggestions: [ + { + messageId: 'wrongRestTypeAnnotationSuggestion', + output: ` +Promise.reject().catch((...x: [unknown]) => {}); + `, + }, + ], + }, + ], + }, + + { + code: ` +Promise.resolve().catch((...[args]: [string]) => { + console.log(args); +}); + `, + errors: [ + { + line: 2, + messageId: 'useUnknown', + suggestions: [ + { + messageId: 'wrongRestTypeAnnotationSuggestion', + output: ` +Promise.resolve().catch((...[args]: [unknown]) => { + console.log(args); +}); + `, + }, + ], + }, + ], + }, + + { + code: ` +Promise.resolve().catch((...{ find }: [string]) => { + console.log(find); +}); + `, + errors: [ + { + line: 2, + messageId: 'useUnknown', + suggestions: [ + { + messageId: 'wrongRestTypeAnnotationSuggestion', + output: ` +Promise.resolve().catch((...{ find }: [unknown]) => { + console.log(find); +}); + `, + }, + ], + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/schema-snapshots/restrict-template-expressions.shot b/packages/eslint-plugin/tests/schema-snapshots/restrict-template-expressions.shot index 194ddc4260cb..1a4d828e764e 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/restrict-template-expressions.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/restrict-template-expressions.shot @@ -12,6 +12,10 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos "description": "Whether to allow \`any\` typed values in template expressions.", "type": "boolean" }, + "allowArray": { + "description": "Whether to allow \`array\` typed values in template expressions.", + "type": "boolean" + }, "allowBoolean": { "description": "Whether to allow \`boolean\` typed values in template expressions.", "type": "boolean" @@ -44,6 +48,8 @@ type Options = [ { /** Whether to allow \`any\` typed values in template expressions. */ allowAny?: boolean; + /** Whether to allow \`array\` typed values in template expressions. */ + allowArray?: boolean; /** Whether to allow \`boolean\` typed values in template expressions. */ allowBoolean?: boolean; /** Whether to allow \`never\` typed values in template expressions. */ diff --git a/packages/eslint-plugin/tests/schema-snapshots/use-unknown-in-catch-callback-variable.shot b/packages/eslint-plugin/tests/schema-snapshots/use-unknown-in-catch-callback-variable.shot new file mode 100644 index 000000000000..da0da2d8e489 --- /dev/null +++ b/packages/eslint-plugin/tests/schema-snapshots/use-unknown-in-catch-callback-variable.shot @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Rule schemas should be convertible to TS types for documentation purposes use-unknown-in-catch-callback-variable 1`] = ` +" +# SCHEMA: + +[] + + +# TYPES: + +/** No options declared */ +type Options = [];" +`; diff --git a/packages/eslint-plugin/tools/generate-breaking-changes.mts b/packages/eslint-plugin/tools/generate-breaking-changes.mts index dd57b2042bc5..acf1b3a47820 100644 --- a/packages/eslint-plugin/tools/generate-breaking-changes.mts +++ b/packages/eslint-plugin/tools/generate-breaking-changes.mts @@ -156,6 +156,6 @@ async function main(): Promise { ); } -main().catch(error => { +main().catch((error: unknown) => { console.error(error); }); diff --git a/packages/integration-tests/CHANGELOG.md b/packages/integration-tests/CHANGELOG.md index 8f6506a8a8f1..f685c956f2a5 100644 --- a/packages/integration-tests/CHANGELOG.md +++ b/packages/integration-tests/CHANGELOG.md @@ -1,3 +1,9 @@ +## 7.3.0 (2024-03-18) + +This was a version bump only for integration-tests to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 7.2.0 (2024-03-11) diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 2791a7e51b88..82c69b7f784c 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/integration-tests", - "version": "7.2.0", + "version": "7.3.0", "private": true, "scripts": { "format": "prettier --write \"./**/*.{ts,mts,cts,tsx,js,mjs,cjs,jsx,json,md,css}\" --ignore-path ../../.prettierignore", diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index 7d5dade65d30..261271097568 100644 --- a/packages/parser/CHANGELOG.md +++ b/packages/parser/CHANGELOG.md @@ -1,3 +1,28 @@ +## 7.3.0 (2024-03-18) + + +### 🩹 Fixes + +- correct `engines.node` constraints in `package.json` + + +### ❀️ Thank You + +- Abraham Guo +- Alexu +- Arka Pratim Chaudhuri +- auvred +- Derrick Isaacson +- fnx +- Josh Goldberg ✨ +- Kirk Waiblinger +- Marta Cardoso +- MichaΓ«l De Boey +- Tristan Rasmussen +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 7.2.0 (2024-03-11) diff --git a/packages/parser/package.json b/packages/parser/package.json index 6c63c8057979..1b2dc6649b56 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "7.2.0", + "version": "7.3.0", "description": "An ESLint custom parser which leverages TypeScript ESTree", "files": [ "dist", @@ -17,7 +17,7 @@ "./package.json": "./package.json" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "repository": { "type": "git", @@ -51,10 +51,10 @@ "eslint": "^8.56.0" }, "dependencies": { - "@typescript-eslint/scope-manager": "7.2.0", - "@typescript-eslint/types": "7.2.0", - "@typescript-eslint/typescript-estree": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0", + "@typescript-eslint/scope-manager": "7.3.0", + "@typescript-eslint/types": "7.3.0", + "@typescript-eslint/typescript-estree": "7.3.0", + "@typescript-eslint/visitor-keys": "7.3.0", "debug": "^4.3.4" }, "devDependencies": { diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index 41e1b1677489..c47c057ebbfc 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,3 +1,30 @@ +## 7.3.0 (2024-03-18) + + +### πŸš€ Features + +- **eslint-plugin:** add meta.docs.recommended setting for strict config options + +- **eslint-plugin:** add rule `use-unknown-in-catch-callback-variables` + + +### ❀️ Thank You + +- Abraham Guo +- Alexu +- Arka Pratim Chaudhuri +- auvred +- Derrick Isaacson +- fnx +- Josh Goldberg ✨ +- Kirk Waiblinger +- Marta Cardoso +- MichaΓ«l De Boey +- Tristan Rasmussen +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 7.2.0 (2024-03-11) diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index 135ef517de5e..a0a60983e172 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/repo-tools", - "version": "7.2.0", + "version": "7.3.0", "private": true, "scripts": { "//": "NOTE: intentionally no build step in this package", diff --git a/packages/repo-tools/src/generate-configs.mts b/packages/repo-tools/src/generate-configs.mts index 870b60c6de07..43802b959f4d 100644 --- a/packages/repo-tools/src/generate-configs.mts +++ b/packages/repo-tools/src/generate-configs.mts @@ -61,7 +61,10 @@ async function main(): Promise { config: PRETTIER_CONFIG_PATH, }); - type LinterConfigRules = Record; + type LinterConfigRules = Record< + string, + ClassicConfig.RuleLevel | [ClassicConfig.RuleLevel, ...unknown[]] + >; interface LinterConfig extends ClassicConfig.Config { extends?: string[] | string; @@ -93,8 +96,13 @@ async function main(): Promise { (a, b) => a[0].localeCompare(b[0]), ); - interface RuleFilter { + type GetRuleOptions = ( + rule: RuleModule, + ) => true | readonly unknown[] | undefined; + + interface ConfigRuleSettings { deprecated?: 'exclude'; + getOptions?: GetRuleOptions | undefined; typeChecked?: 'exclude' | 'include-only'; baseRuleForExtensionRule?: 'exclude'; forcedRuleLevel?: Linter.RuleLevel; @@ -106,7 +114,7 @@ async function main(): Promise { function reducer( config: LinterConfigRules, [key, value]: RuleEntry, - settings: RuleFilter = {}, + settings: ConfigRuleSettings = {}, ): LinterConfigRules { if (settings.deprecated && value.meta.deprecated) { return config; @@ -149,7 +157,14 @@ async function main(): Promise { '=', chalk.red('error'), ); - config[ruleName] = settings.forcedRuleLevel ?? 'error'; + + const ruleLevel = settings.forcedRuleLevel ?? 'error'; + const ruleOptions = settings.getOptions?.(value); + + config[ruleName] = + ruleOptions && ruleOptions !== true + ? [ruleLevel, ...ruleOptions] + : ruleLevel; return config; } @@ -247,20 +262,20 @@ async function main(): Promise { interface ExtendedConfigSettings { name: string; - filters?: RuleFilter; ruleEntries: readonly RuleEntry[]; + settings?: ConfigRuleSettings; } async function writeExtendedConfig({ - filters, name, ruleEntries, + settings, }: ExtendedConfigSettings): Promise { await writeConfig( () => ({ extends: [...CLASSIC_EXTENDS], rules: ruleEntries.reduce( - (config, entry) => reducer(config, entry, filters), + (config, entry) => reducer(config, entry, settings), {}, ), }), @@ -272,20 +287,34 @@ async function main(): Promise { ...recommendations: (RuleRecommendation | undefined)[] ): RuleEntry[] { return allRuleEntries.filter(([, rule]) => - recommendations.includes(rule.meta.docs?.recommended), + typeof rule.meta.docs?.recommended === 'object' + ? Object.keys(rule.meta.docs.recommended).some(level => + recommendations.includes(level as RuleRecommendation), + ) + : recommendations.includes(rule.meta.docs?.recommended), ); } + function createGetOptionsForLevel( + level: 'recommended' | 'strict', + ): GetRuleOptions { + return rule => + typeof rule.meta.docs?.recommended === 'object' + ? rule.meta.docs.recommended[level] + : undefined; + } + await writeExtendedConfig({ name: 'all', - filters: { + settings: { deprecated: 'exclude', }, ruleEntries: allRuleEntries, }); await writeExtendedConfig({ - filters: { + settings: { + getOptions: createGetOptionsForLevel('recommended'), typeChecked: 'exclude', }, name: 'recommended', @@ -295,10 +324,14 @@ async function main(): Promise { await writeExtendedConfig({ name: 'recommended-type-checked', ruleEntries: filterRuleEntriesTo('recommended'), + settings: { + getOptions: createGetOptionsForLevel('recommended'), + }, }); await writeExtendedConfig({ - filters: { + settings: { + getOptions: createGetOptionsForLevel('recommended'), typeChecked: 'include-only', }, name: 'recommended-type-checked-only', @@ -306,7 +339,8 @@ async function main(): Promise { }); await writeExtendedConfig({ - filters: { + settings: { + getOptions: createGetOptionsForLevel('strict'), typeChecked: 'exclude', }, name: 'strict', @@ -314,12 +348,16 @@ async function main(): Promise { }); await writeExtendedConfig({ + settings: { + getOptions: createGetOptionsForLevel('strict'), + }, name: 'strict-type-checked', ruleEntries: filterRuleEntriesTo('recommended', 'strict'), }); await writeExtendedConfig({ - filters: { + settings: { + getOptions: createGetOptionsForLevel('strict'), typeChecked: 'include-only', }, name: 'strict-type-checked-only', @@ -327,7 +365,7 @@ async function main(): Promise { }); await writeExtendedConfig({ - filters: { + settings: { typeChecked: 'exclude', }, name: 'stylistic', @@ -340,7 +378,7 @@ async function main(): Promise { }); await writeExtendedConfig({ - filters: { + settings: { typeChecked: 'include-only', }, name: 'stylistic-type-checked-only', @@ -367,6 +405,6 @@ async function main(): Promise { ); } -main().catch(error => { +main().catch((error: unknown) => { console.error(error); }); diff --git a/packages/repo-tools/src/generate-contributors.mts b/packages/repo-tools/src/generate-contributors.mts index db9d72550c52..220750350dd5 100644 --- a/packages/repo-tools/src/generate-contributors.mts +++ b/packages/repo-tools/src/generate-contributors.mts @@ -123,6 +123,7 @@ function writeTable(contributors: User[], perLine = 5): void { lines.push(' '); } + lines.push(' '); lines.push(''); lines.push(''); lines.push(''); @@ -168,7 +169,7 @@ async function main(): Promise { ); } -main().catch(error => { +main().catch((error: unknown) => { console.error(error); process.exitCode = 1; }); diff --git a/packages/repo-tools/src/generate-lib.mts b/packages/repo-tools/src/generate-lib.mts index bc8f7f5e3ce1..92dd4d279015 100644 --- a/packages/repo-tools/src/generate-lib.mts +++ b/packages/repo-tools/src/generate-lib.mts @@ -307,7 +307,7 @@ async function main(): Promise { console.log('Autofixed lint errors'); } -main().catch(e => { +main().catch((e: unknown) => { console.error(e); // eslint-disable-next-line no-process-exit process.exit(1); diff --git a/packages/repo-tools/src/generate-sponsors.mts b/packages/repo-tools/src/generate-sponsors.mts index 142029a61333..1a7b600758ec 100644 --- a/packages/repo-tools/src/generate-sponsors.mts +++ b/packages/repo-tools/src/generate-sponsors.mts @@ -189,7 +189,7 @@ async function stringifyObject( }); } -main().catch(error => { +main().catch((error: unknown) => { console.error(error); process.exitCode = 1; }); diff --git a/packages/repo-tools/src/postinstall.mts b/packages/repo-tools/src/postinstall.mts index 5facbf0defc9..b89cb17cc867 100644 --- a/packages/repo-tools/src/postinstall.mts +++ b/packages/repo-tools/src/postinstall.mts @@ -20,7 +20,8 @@ if (process.env.SKIP_POSTINSTALL) { process.exit(0); } -void (async function (): Promise { +// eslint-disable-next-line @typescript-eslint/no-floating-promises +(async function (): Promise { // make sure we're running from the workspace root const { default: { workspaceRoot }, diff --git a/packages/rule-schema-to-typescript-types/CHANGELOG.md b/packages/rule-schema-to-typescript-types/CHANGELOG.md index 5f618f25d1e0..4e74383341e6 100644 --- a/packages/rule-schema-to-typescript-types/CHANGELOG.md +++ b/packages/rule-schema-to-typescript-types/CHANGELOG.md @@ -1,3 +1,28 @@ +## 7.3.0 (2024-03-18) + + +### 🩹 Fixes + +- correct `engines.node` constraints in `package.json` + + +### ❀️ Thank You + +- Abraham Guo +- Alexu +- Arka Pratim Chaudhuri +- auvred +- Derrick Isaacson +- fnx +- Josh Goldberg ✨ +- Kirk Waiblinger +- Marta Cardoso +- MichaΓ«l De Boey +- Tristan Rasmussen +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 7.2.0 (2024-03-11) diff --git a/packages/rule-schema-to-typescript-types/package.json b/packages/rule-schema-to-typescript-types/package.json index 845bf3398597..0cd50f019374 100644 --- a/packages/rule-schema-to-typescript-types/package.json +++ b/packages/rule-schema-to-typescript-types/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/rule-schema-to-typescript-types", - "version": "7.2.0", + "version": "7.3.0", "private": true, "type": "commonjs", "exports": { @@ -11,7 +11,7 @@ "./package.json": "./package.json" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "repository": { "type": "git", @@ -33,8 +33,8 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/type-utils": "7.2.0", - "@typescript-eslint/utils": "7.2.0", + "@typescript-eslint/type-utils": "7.3.0", + "@typescript-eslint/utils": "7.3.0", "natural-compare": "^1.4.0", "prettier": "^3.0.3" }, diff --git a/packages/rule-tester/CHANGELOG.md b/packages/rule-tester/CHANGELOG.md index 0275045ccde7..6ef734ca8908 100644 --- a/packages/rule-tester/CHANGELOG.md +++ b/packages/rule-tester/CHANGELOG.md @@ -1,3 +1,33 @@ +## 7.3.0 (2024-03-18) + + +### πŸš€ Features + +- **eslint-plugin:** add meta.docs.recommended setting for strict config options + + +### 🩹 Fixes + +- correct `engines.node` constraints in `package.json` + + +### ❀️ Thank You + +- Abraham Guo +- Alexu +- Arka Pratim Chaudhuri +- auvred +- Derrick Isaacson +- fnx +- Josh Goldberg ✨ +- Kirk Waiblinger +- Marta Cardoso +- MichaΓ«l De Boey +- Tristan Rasmussen +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 7.2.0 (2024-03-11) diff --git a/packages/rule-tester/package.json b/packages/rule-tester/package.json index 24309cd264c3..7cac41b08254 100644 --- a/packages/rule-tester/package.json +++ b/packages/rule-tester/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/rule-tester", - "version": "7.2.0", + "version": "7.3.0", "description": "Tooling to test ESLint rules", "files": [ "dist", @@ -17,7 +17,7 @@ "./package.json": "./package.json" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "repository": { "type": "git", @@ -47,8 +47,8 @@ }, "//": "NOTE - AJV is out-of-date, but it's intentionally synced with ESLint - https://github.com/eslint/eslint/blob/ad9dd6a933fd098a0d99c6a9aa059850535c23ee/package.json#L70", "dependencies": { - "@typescript-eslint/typescript-estree": "7.2.0", - "@typescript-eslint/utils": "7.2.0", + "@typescript-eslint/typescript-estree": "7.3.0", + "@typescript-eslint/utils": "7.3.0", "ajv": "^6.10.0", "lodash.merge": "4.6.2", "semver": "^7.5.4" @@ -59,7 +59,7 @@ }, "devDependencies": { "@types/lodash.merge": "4.6.9", - "@typescript-eslint/parser": "7.2.0", + "@typescript-eslint/parser": "7.3.0", "chai": "^4.3.7", "mocha": "^10.0.0", "sinon": "^16.0.0", diff --git a/packages/rule-tester/src/utils/config-validator.ts b/packages/rule-tester/src/utils/config-validator.ts index ac90b8c21eea..ff616a0e57de 100644 --- a/packages/rule-tester/src/utils/config-validator.ts +++ b/packages/rule-tester/src/utils/config-validator.ts @@ -78,7 +78,8 @@ function validateRuleSchema( const validateRule = ruleValidators.get(rule); if (validateRule) { - void validateRule(localOptions); + // eslint-disable-next-line @typescript-eslint/no-floating-promises + validateRule(localOptions); if (validateRule.errors) { throw new Error( validateRule.errors diff --git a/packages/scope-manager/CHANGELOG.md b/packages/scope-manager/CHANGELOG.md index 19ec7800a57b..8ec6c5baa7b5 100644 --- a/packages/scope-manager/CHANGELOG.md +++ b/packages/scope-manager/CHANGELOG.md @@ -1,3 +1,28 @@ +## 7.3.0 (2024-03-18) + + +### 🩹 Fixes + +- correct `engines.node` constraints in `package.json` + + +### ❀️ Thank You + +- Abraham Guo +- Alexu +- Arka Pratim Chaudhuri +- auvred +- Derrick Isaacson +- fnx +- Josh Goldberg ✨ +- Kirk Waiblinger +- Marta Cardoso +- MichaΓ«l De Boey +- Tristan Rasmussen +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 7.2.0 (2024-03-11) diff --git a/packages/scope-manager/package.json b/packages/scope-manager/package.json index 8ff773fa5863..bffde1d03b3c 100644 --- a/packages/scope-manager/package.json +++ b/packages/scope-manager/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/scope-manager", - "version": "7.2.0", + "version": "7.3.0", "description": "TypeScript scope analyser for ESLint", "files": [ "dist", @@ -18,7 +18,7 @@ }, "types": "./dist/index.d.ts", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "repository": { "type": "git", @@ -45,12 +45,12 @@ "typecheck": "npx nx typecheck" }, "dependencies": { - "@typescript-eslint/types": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0" + "@typescript-eslint/types": "7.3.0", + "@typescript-eslint/visitor-keys": "7.3.0" }, "devDependencies": { "@types/glob": "*", - "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/typescript-estree": "7.3.0", "glob": "*", "jest-specific-snapshot": "*", "make-dir": "*", diff --git a/packages/type-utils/CHANGELOG.md b/packages/type-utils/CHANGELOG.md index e495b347a3e2..828192770c52 100644 --- a/packages/type-utils/CHANGELOG.md +++ b/packages/type-utils/CHANGELOG.md @@ -1,3 +1,28 @@ +## 7.3.0 (2024-03-18) + + +### 🩹 Fixes + +- correct `engines.node` constraints in `package.json` + + +### ❀️ Thank You + +- Abraham Guo +- Alexu +- Arka Pratim Chaudhuri +- auvred +- Derrick Isaacson +- fnx +- Josh Goldberg ✨ +- Kirk Waiblinger +- Marta Cardoso +- MichaΓ«l De Boey +- Tristan Rasmussen +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 7.2.0 (2024-03-11) diff --git a/packages/type-utils/package.json b/packages/type-utils/package.json index 07c8b61fb88c..c2041c6f6922 100644 --- a/packages/type-utils/package.json +++ b/packages/type-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/type-utils", - "version": "7.2.0", + "version": "7.3.0", "description": "Type utilities for working with TypeScript + ESLint together", "files": [ "dist", @@ -18,7 +18,7 @@ "./package.json": "./package.json" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "repository": { "type": "git", @@ -45,13 +45,13 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/typescript-estree": "7.2.0", - "@typescript-eslint/utils": "7.2.0", + "@typescript-eslint/typescript-estree": "7.3.0", + "@typescript-eslint/utils": "7.3.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, "devDependencies": { - "@typescript-eslint/parser": "7.2.0", + "@typescript-eslint/parser": "7.3.0", "ajv": "^6.10.0", "downlevel-dts": "*", "jest": "29.7.0", diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index d7f2d30d2b82..30c75d78da8d 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -1,3 +1,33 @@ +## 7.3.0 (2024-03-18) + + +### πŸš€ Features + +- **eslint-plugin:** add rule `use-unknown-in-catch-callback-variables` + + +### 🩹 Fixes + +- correct `engines.node` constraints in `package.json` + + +### ❀️ Thank You + +- Abraham Guo +- Alexu +- Arka Pratim Chaudhuri +- auvred +- Derrick Isaacson +- fnx +- Josh Goldberg ✨ +- Kirk Waiblinger +- Marta Cardoso +- MichaΓ«l De Boey +- Tristan Rasmussen +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 7.2.0 (2024-03-11) diff --git a/packages/types/package.json b/packages/types/package.json index bbc9b8e3362f..b1ec3380abf1 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/types", - "version": "7.2.0", + "version": "7.3.0", "description": "Types for the TypeScript-ESTree AST spec", "files": [ "dist", @@ -19,7 +19,7 @@ }, "types": "./dist/index.d.ts", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "repository": { "type": "git", diff --git a/packages/types/tools/copy-ast-spec.ts b/packages/types/tools/copy-ast-spec.ts index fe5412061c8e..04821aedfff2 100644 --- a/packages/types/tools/copy-ast-spec.ts +++ b/packages/types/tools/copy-ast-spec.ts @@ -83,7 +83,7 @@ async function main(): Promise { ]); } -main().catch(error => { +main().catch((error: unknown) => { console.error(error); process.exitCode = 1; }); diff --git a/packages/typescript-eslint/CHANGELOG.md b/packages/typescript-eslint/CHANGELOG.md index a507556db02c..cb08683822bb 100644 --- a/packages/typescript-eslint/CHANGELOG.md +++ b/packages/typescript-eslint/CHANGELOG.md @@ -1,3 +1,35 @@ +## 7.3.0 (2024-03-18) + + +### πŸš€ Features + +- **eslint-plugin:** add meta.docs.recommended setting for strict config options + +- **eslint-plugin:** add rule `use-unknown-in-catch-callback-variables` + + +### 🩹 Fixes + +- correct `engines.node` constraints in `package.json` + + +### ❀️ Thank You + +- Abraham Guo +- Alexu +- Arka Pratim Chaudhuri +- auvred +- Derrick Isaacson +- fnx +- Josh Goldberg ✨ +- Kirk Waiblinger +- Marta Cardoso +- MichaΓ«l De Boey +- Tristan Rasmussen +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 7.2.0 (2024-03-11) diff --git a/packages/typescript-eslint/package.json b/packages/typescript-eslint/package.json index 6031a8f11a25..5139077b1967 100644 --- a/packages/typescript-eslint/package.json +++ b/packages/typescript-eslint/package.json @@ -1,6 +1,6 @@ { "name": "typescript-eslint", - "version": "7.2.0", + "version": "7.3.0", "description": "Tooling which enables you to use TypeScript with ESLint", "files": [ "dist", @@ -18,7 +18,7 @@ }, "types": "./dist/index.d.ts", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "repository": { "type": "git", @@ -54,8 +54,8 @@ "eslint": "^8.56.0" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "7.2.0", - "@typescript-eslint/parser": "7.2.0" + "@typescript-eslint/eslint-plugin": "7.3.0", + "@typescript-eslint/parser": "7.3.0" }, "devDependencies": { "downlevel-dts": "*", diff --git a/packages/typescript-eslint/src/configs/all.ts b/packages/typescript-eslint/src/configs/all.ts index ab7e7d37528e..ad4a85cd524c 100644 --- a/packages/typescript-eslint/src/configs/all.ts +++ b/packages/typescript-eslint/src/configs/all.ts @@ -163,6 +163,7 @@ export default ( '@typescript-eslint/typedef': 'error', '@typescript-eslint/unbound-method': 'error', '@typescript-eslint/unified-signatures': 'error', + '@typescript-eslint/use-unknown-in-catch-callback-variable': 'error', }, }, ]; diff --git a/packages/typescript-eslint/src/configs/disable-type-checked.ts b/packages/typescript-eslint/src/configs/disable-type-checked.ts index 06364fb3b3e0..cf6107d6c531 100644 --- a/packages/typescript-eslint/src/configs/disable-type-checked.ts +++ b/packages/typescript-eslint/src/configs/disable-type-checked.ts @@ -64,6 +64,7 @@ export default ( '@typescript-eslint/strict-boolean-expressions': 'off', '@typescript-eslint/switch-exhaustiveness-check': 'off', '@typescript-eslint/unbound-method': 'off', + '@typescript-eslint/use-unknown-in-catch-callback-variable': 'off', }, languageOptions: { parserOptions: { project: false, program: null } }, }); diff --git a/packages/typescript-eslint/src/configs/strict-type-checked-only.ts b/packages/typescript-eslint/src/configs/strict-type-checked-only.ts index e1f60b92e692..4c52c09b7376 100644 --- a/packages/typescript-eslint/src/configs/strict-type-checked-only.ts +++ b/packages/typescript-eslint/src/configs/strict-type-checked-only.ts @@ -23,7 +23,10 @@ export default ( '@typescript-eslint/no-base-to-string': 'error', '@typescript-eslint/no-confusing-void-expression': 'error', '@typescript-eslint/no-duplicate-type-constituents': 'error', - '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/no-floating-promises': [ + 'error', + { ignoreVoid: false }, + ], '@typescript-eslint/no-for-in-array': 'error', 'no-implied-eval': 'off', '@typescript-eslint/no-implied-eval': 'error', @@ -51,9 +54,29 @@ export default ( '@typescript-eslint/prefer-return-this-type': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', - '@typescript-eslint/restrict-plus-operands': 'error', - '@typescript-eslint/restrict-template-expressions': 'error', + '@typescript-eslint/restrict-plus-operands': [ + 'error', + { + allowAny: false, + allowBoolean: false, + allowNullish: false, + allowNumberAndString: false, + allowRegExp: false, + }, + ], + '@typescript-eslint/restrict-template-expressions': [ + 'error', + { + allowAny: false, + allowBoolean: false, + allowNullish: false, + allowNumber: false, + allowRegExp: false, + allowNever: false, + }, + ], '@typescript-eslint/unbound-method': 'error', + '@typescript-eslint/use-unknown-in-catch-callback-variable': 'error', }, }, ]; diff --git a/packages/typescript-eslint/src/configs/strict-type-checked.ts b/packages/typescript-eslint/src/configs/strict-type-checked.ts index 91abadd4b563..e53f57934cea 100644 --- a/packages/typescript-eslint/src/configs/strict-type-checked.ts +++ b/packages/typescript-eslint/src/configs/strict-type-checked.ts @@ -19,7 +19,10 @@ export default ( { rules: { '@typescript-eslint/await-thenable': 'error', - '@typescript-eslint/ban-ts-comment': 'error', + '@typescript-eslint/ban-ts-comment': [ + 'error', + { minimumDescriptionLength: 10 }, + ], '@typescript-eslint/ban-types': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', @@ -32,7 +35,10 @@ export default ( '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-extraneous-class': 'error', - '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/no-floating-promises': [ + 'error', + { ignoreVoid: false }, + ], '@typescript-eslint/no-for-in-array': 'error', 'no-implied-eval': 'off', '@typescript-eslint/no-implied-eval': 'error', @@ -79,11 +85,31 @@ export default ( '@typescript-eslint/prefer-ts-expect-error': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', - '@typescript-eslint/restrict-plus-operands': 'error', - '@typescript-eslint/restrict-template-expressions': 'error', + '@typescript-eslint/restrict-plus-operands': [ + 'error', + { + allowAny: false, + allowBoolean: false, + allowNullish: false, + allowNumberAndString: false, + allowRegExp: false, + }, + ], + '@typescript-eslint/restrict-template-expressions': [ + 'error', + { + allowAny: false, + allowBoolean: false, + allowNullish: false, + allowNumber: false, + allowRegExp: false, + allowNever: false, + }, + ], '@typescript-eslint/triple-slash-reference': 'error', '@typescript-eslint/unbound-method': 'error', '@typescript-eslint/unified-signatures': 'error', + '@typescript-eslint/use-unknown-in-catch-callback-variable': 'error', }, }, ]; diff --git a/packages/typescript-eslint/src/configs/strict.ts b/packages/typescript-eslint/src/configs/strict.ts index c1eb5e29cf3d..dabfa2f78a92 100644 --- a/packages/typescript-eslint/src/configs/strict.ts +++ b/packages/typescript-eslint/src/configs/strict.ts @@ -18,7 +18,10 @@ export default ( eslintRecommendedConfig(plugin, parser), { rules: { - '@typescript-eslint/ban-ts-comment': 'error', + '@typescript-eslint/ban-ts-comment': [ + 'error', + { minimumDescriptionLength: 10 }, + ], '@typescript-eslint/ban-types': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', diff --git a/packages/typescript-eslint/tests/configs.test.ts b/packages/typescript-eslint/tests/configs.test.ts index 28c2f5a337e1..6d8add503b4b 100644 --- a/packages/typescript-eslint/tests/configs.test.ts +++ b/packages/typescript-eslint/tests/configs.test.ts @@ -45,7 +45,7 @@ function filterAndMapRuleConfigs({ excludeDeprecated, typeChecked, recommendations, -}: FilterAndMapRuleConfigsSettings = {}): [string, string][] { +}: FilterAndMapRuleConfigsSettings = {}): [string, unknown][] { let result = Object.entries(rules); if (excludeDeprecated) { @@ -61,12 +61,37 @@ function filterAndMapRuleConfigs({ } if (recommendations) { - result = result.filter(([, rule]) => - recommendations.includes(rule.meta.docs?.recommended), - ); + result = result.filter(([, rule]) => { + switch (typeof rule.meta.docs?.recommended) { + case 'undefined': + return false; + case 'object': + return Object.keys(rule.meta.docs.recommended).some(recommended => + recommendations.includes(recommended as RuleRecommendation), + ); + case 'string': + return recommendations.includes(rule.meta.docs.recommended); + } + }); } - return result.map(([name]) => [`${RULE_NAME_PREFIX}${name}`, 'error']); + const highestRecommendation = recommendations?.filter(Boolean).at(-1); + + return result.map(([name, rule]) => { + const customRecommendation = + highestRecommendation && + typeof rule.meta.docs?.recommended === 'object' && + rule.meta.docs.recommended[ + highestRecommendation as 'recommended' | 'strict' + ]; + + return [ + `${RULE_NAME_PREFIX}${name}`, + customRecommendation && typeof customRecommendation !== 'boolean' + ? ['error', customRecommendation[0]] + : 'error', + ]; + }); } function itHasBaseRulesOverriden( diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index 808542784a9c..850981a95c54 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/CHANGELOG.md @@ -1,3 +1,37 @@ +## 7.3.0 (2024-03-18) + + +### πŸš€ Features + +- **typescript-estree:** disallow switch statements with multiple default cases + + +### 🩹 Fixes + +- correct `engines.node` constraints in `package.json` + +- **eslint-plugin:** [no-unnecessary-type-assertion] fix false negative for const variable declarations + +- **typescript-estree:** fix the issue of single run inferring in the pnpm repo + + +### ❀️ Thank You + +- Abraham Guo +- Alexu +- Arka Pratim Chaudhuri +- auvred +- Derrick Isaacson +- fnx +- Josh Goldberg ✨ +- Kirk Waiblinger +- Marta Cardoso +- MichaΓ«l De Boey +- Tristan Rasmussen +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 7.2.0 (2024-03-11) diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index abaaae14f90c..7fb3d420e56e 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "7.2.0", + "version": "7.3.0", "description": "A parser that converts TypeScript source code into an ESTree compatible form", "files": [ "dist", @@ -22,7 +22,7 @@ }, "types": "./dist/index.d.ts", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "repository": { "type": "git", @@ -53,8 +53,8 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/types": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0", + "@typescript-eslint/types": "7.3.0", + "@typescript-eslint/visitor-keys": "7.3.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index a6ecf02df462..9d068cbf2432 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -839,6 +839,17 @@ export class Converter { }); case SyntaxKind.SwitchStatement: + if ( + node.caseBlock.clauses.filter( + switchCase => switchCase.kind === SyntaxKind.DefaultClause, + ).length > 1 + ) { + this.#throwError( + node, + "A 'default' clause cannot appear more than once in a 'switch' statement.", + ); + } + return this.createNode(node, { type: AST_NODE_TYPES.SwitchStatement, discriminant: this.convertChild(node.expression), diff --git a/packages/typescript-estree/src/parseSettings/inferSingleRun.ts b/packages/typescript-estree/src/parseSettings/inferSingleRun.ts index e8a9b020ce86..db64bc21435f 100644 --- a/packages/typescript-estree/src/parseSettings/inferSingleRun.ts +++ b/packages/typescript-estree/src/parseSettings/inferSingleRun.ts @@ -35,11 +35,17 @@ export function inferSingleRun(options: TSESTreeOptions | undefined): boolean { // Currently behind a flag while we gather real-world feedback if (options.allowAutomaticSingleRunInference) { + const possibleEslintBinPaths = [ + 'node_modules/.bin/eslint', // npm or yarn repo + 'node_modules/eslint/bin/eslint.js', // pnpm repo + ]; if ( // Default to single runs for CI processes. CI=true is set by most CI providers by default. process.env.CI === 'true' || // This will be true for invocations such as `npx eslint ...` and `./node_modules/.bin/eslint ...` - process.argv[1].endsWith(normalize('node_modules/.bin/eslint')) + possibleEslintBinPaths.some(path => + process.argv[1].endsWith(normalize(path)), + ) ) { return true; } diff --git a/packages/typescript-estree/tests/lib/persistentParse.test.ts b/packages/typescript-estree/tests/lib/persistentParse.test.ts index 710b9c54ab5a..dbfd2831dea5 100644 --- a/packages/typescript-estree/tests/lib/persistentParse.test.ts +++ b/packages/typescript-estree/tests/lib/persistentParse.test.ts @@ -124,7 +124,7 @@ function baseTests( it('allows parsing of deeply nested new files', () => { const PROJECT_DIR = setup(tsConfigIncludeAll, false); - const bazSlashBar = 'baz/bar' as const; + const bazSlashBar = 'baz/bar'; // parse once to: assert the config as correct, and to make sure the program is setup expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); @@ -149,7 +149,7 @@ function baseTests( fs.mkdirSync(path.join(PROJECT_DIR, 'src', 'bat')); fs.mkdirSync(path.join(PROJECT_DIR, 'src', 'bat', 'baz')); - const bazSlashBar = 'bat/baz/bar' as const; + const bazSlashBar = 'bat/baz/bar'; // write a new file and attempt to parse it writeFile(PROJECT_DIR, bazSlashBar); @@ -159,7 +159,7 @@ function baseTests( it('allows renaming of files', () => { const PROJECT_DIR = setup(tsConfigIncludeAll, true); - const bazSlashBar = 'baz/bar' as const; + const bazSlashBar = 'baz/bar'; // parse once to: assert the config as correct, and to make sure the program is setup expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); @@ -291,7 +291,7 @@ describe('persistent parse', () => { it('handles tsconfigs with no includes/excludes (nested)', () => { const PROJECT_DIR = setup({}, false); - const bazSlashBar = 'baz/bar' as const; + const bazSlashBar = 'baz/bar'; // parse once to: assert the config as correct, and to make sure the program is setup expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index 0a5078d1ef75..9830f3eaff3e 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -1,3 +1,35 @@ +## 7.3.0 (2024-03-18) + + +### πŸš€ Features + +- **utils:** add parser name to thrown parser error message + +- **eslint-plugin:** add meta.docs.recommended setting for strict config options + + +### 🩹 Fixes + +- correct `engines.node` constraints in `package.json` + + +### ❀️ Thank You + +- Abraham Guo +- Alexu +- Arka Pratim Chaudhuri +- auvred +- Derrick Isaacson +- fnx +- Josh Goldberg ✨ +- Kirk Waiblinger +- Marta Cardoso +- MichaΓ«l De Boey +- Tristan Rasmussen +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 7.2.0 (2024-03-11) diff --git a/packages/utils/package.json b/packages/utils/package.json index 2f7ac16ce759..771f9c409d46 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/utils", - "version": "7.2.0", + "version": "7.3.0", "description": "Utilities for working with TypeScript + ESLint together", "files": [ "dist", @@ -39,7 +39,7 @@ }, "types": "./dist/index.d.ts", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "repository": { "type": "git", @@ -69,16 +69,16 @@ "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.2.0", - "@typescript-eslint/types": "7.2.0", - "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/scope-manager": "7.3.0", + "@typescript-eslint/types": "7.3.0", + "@typescript-eslint/typescript-estree": "7.3.0", "semver": "^7.5.4" }, "peerDependencies": { "eslint": "^8.56.0" }, "devDependencies": { - "@typescript-eslint/parser": "7.2.0", + "@typescript-eslint/parser": "7.3.0", "downlevel-dts": "*", "jest": "29.7.0", "prettier": "^3.0.3", diff --git a/packages/utils/src/eslint-utils/RuleCreator.ts b/packages/utils/src/eslint-utils/RuleCreator.ts index 40ebe7f49120..784585415f05 100644 --- a/packages/utils/src/eslint-utils/RuleCreator.ts +++ b/packages/utils/src/eslint-utils/RuleCreator.ts @@ -10,12 +10,15 @@ import { applyDefault } from './applyDefault'; export type { RuleListener, RuleModule }; // we automatically add the url -export type NamedCreateRuleMetaDocs = Omit; -export type NamedCreateRuleMeta = Omit< - RuleMetaData, - 'docs' -> & { - docs: NamedCreateRuleMetaDocs; +export type NamedCreateRuleMetaDocs = Omit< + RuleMetaDataDocs, + 'url' +>; +export type NamedCreateRuleMeta< + MessageIds extends string, + Options extends readonly unknown[], +> = Omit, 'docs'> & { + docs: NamedCreateRuleMetaDocs; }; export interface RuleCreateAndOptions< @@ -33,14 +36,14 @@ export interface RuleWithMeta< Options extends readonly unknown[], MessageIds extends string, > extends RuleCreateAndOptions { - meta: RuleMetaData; + meta: RuleMetaData; } export interface RuleWithMetaAndName< Options extends readonly unknown[], MessageIds extends string, > extends RuleCreateAndOptions { - meta: NamedCreateRuleMeta; + meta: NamedCreateRuleMeta; name: string; } diff --git a/packages/utils/src/eslint-utils/getParserServices.ts b/packages/utils/src/eslint-utils/getParserServices.ts index e064d32aaca3..0fd908f4a2ec 100644 --- a/packages/utils/src/eslint-utils/getParserServices.ts +++ b/packages/utils/src/eslint-utils/getParserServices.ts @@ -91,14 +91,14 @@ function getParserServices( /* eslint-enable @typescript-eslint/unified-signatures */ function throwError(parserPath: string): never { - throw new Error( - parserPathSeemsToBeTSESLint(parserPath) - ? ERROR_MESSAGE_REQUIRES_PARSER_SERVICES - : [ - ERROR_MESSAGE_REQUIRES_PARSER_SERVICES, - ERROR_MESSAGE_UNKNOWN_PARSER, - ].join('\n'), - ); + const messages = [ + ERROR_MESSAGE_REQUIRES_PARSER_SERVICES, + `Parser: ${parserPath}`, + ]; + if (!parserPathSeemsToBeTSESLint(parserPath)) { + messages.push(ERROR_MESSAGE_UNKNOWN_PARSER); + } + throw new Error(messages.join('\n')); } export { getParserServices }; diff --git a/packages/utils/src/ts-eslint/Rule.ts b/packages/utils/src/ts-eslint/Rule.ts index 3c654cedc64e..c0a3678a00cc 100644 --- a/packages/utils/src/ts-eslint/Rule.ts +++ b/packages/utils/src/ts-eslint/Rule.ts @@ -8,7 +8,14 @@ import type { SourceCode } from './SourceCode'; export type RuleRecommendation = 'recommended' | 'strict' | 'stylistic'; -export interface RuleMetaDataDocs { +export interface RuleRecommendationAcrossConfigs< + Options extends readonly unknown[], +> { + recommended: true; + strict: Partial; +} + +export interface RuleMetaDataDocs { /** * Concise description of the rule */ @@ -18,7 +25,7 @@ export interface RuleMetaDataDocs { * Used by the build tools to generate the recommended and strict configs. * Exclude to not include it as a recommendation. */ - recommended?: RuleRecommendation; + recommended?: RuleRecommendation | RuleRecommendationAcrossConfigs; /** * The URL of the rule's docs */ @@ -36,7 +43,10 @@ export interface RuleMetaDataDocs { extendsBaseRule?: boolean | string; } -export interface RuleMetaData { +export interface RuleMetaData< + MessageIds extends string, + Options extends readonly unknown[], +> { /** * True if the rule is deprecated, false otherwise */ @@ -44,7 +54,7 @@ export interface RuleMetaData { /** * Documentation for the rule, unnecessary for custom rules/plugins */ - docs?: RuleMetaDataDocs; + docs?: RuleMetaDataDocs; /** * The fixer category. Omit if there is no fixer */ @@ -630,7 +640,7 @@ export interface RuleModule< /** * Metadata about the rule */ - meta: RuleMetaData; + meta: RuleMetaData; /** * Function which returns an object with methods that ESLint calls to β€œvisit” diff --git a/packages/utils/tests/eslint-utils/getParserServices.test.ts b/packages/utils/tests/eslint-utils/getParserServices.test.ts index 103acb91044b..b247727786ae 100644 --- a/packages/utils/tests/eslint-utils/getParserServices.test.ts +++ b/packages/utils/tests/eslint-utils/getParserServices.test.ts @@ -25,6 +25,16 @@ const createMockRuleContext = ( ...overrides, }) as unknown as UnknownRuleContext; +const requiresParserServicesMessageTemplate = + 'You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.\n' + + 'Parser: \\S*'; +const baseErrorRegex = new RegExp(requiresParserServicesMessageTemplate); +const unknownParserErrorRegex = new RegExp( + requiresParserServicesMessageTemplate + + '\n' + + 'Note: detected a parser other than @typescript-eslint/parser. Make sure the parser is configured to forward "parserOptions.project" to @typescript-eslint/parser.', +); + describe('getParserServices', () => { it('throws a standard error when parserOptions.esTreeNodeToTSNodeMap is missing and the parser is known', () => { const context = createMockRuleContext({ @@ -38,9 +48,7 @@ describe('getParserServices', () => { }); expect(() => ESLintUtils.getParserServices(context)).toThrow( - new Error( - 'You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.', - ), + baseErrorRegex, ); }); @@ -55,12 +63,8 @@ describe('getParserServices', () => { }, }, }); - expect(() => ESLintUtils.getParserServices(context)).toThrow( - new Error( - 'You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.\n' + - 'Note: detected a parser other than @typescript-eslint/parser. Make sure the parser is configured to forward "parserOptions.project" to @typescript-eslint/parser.', - ), + unknownParserErrorRegex, ); }); @@ -76,9 +80,7 @@ describe('getParserServices', () => { }); expect(() => ESLintUtils.getParserServices(context)).toThrow( - new Error( - 'You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.', - ), + baseErrorRegex, ); }); @@ -94,9 +96,7 @@ describe('getParserServices', () => { }); expect(() => ESLintUtils.getParserServices(context)).toThrow( - new Error( - 'You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.', - ), + baseErrorRegex, ); }); diff --git a/packages/visitor-keys/CHANGELOG.md b/packages/visitor-keys/CHANGELOG.md index 1cd4aa7d5422..ca56dfb62d98 100644 --- a/packages/visitor-keys/CHANGELOG.md +++ b/packages/visitor-keys/CHANGELOG.md @@ -1,3 +1,28 @@ +## 7.3.0 (2024-03-18) + + +### 🩹 Fixes + +- correct `engines.node` constraints in `package.json` + + +### ❀️ Thank You + +- Abraham Guo +- Alexu +- Arka Pratim Chaudhuri +- auvred +- Derrick Isaacson +- fnx +- Josh Goldberg ✨ +- Kirk Waiblinger +- Marta Cardoso +- MichaΓ«l De Boey +- Tristan Rasmussen +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 7.2.0 (2024-03-11) diff --git a/packages/visitor-keys/package.json b/packages/visitor-keys/package.json index b3a139aa5dc8..4a682c74183c 100644 --- a/packages/visitor-keys/package.json +++ b/packages/visitor-keys/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/visitor-keys", - "version": "7.2.0", + "version": "7.3.0", "description": "Visitor keys used to help traverse the TypeScript-ESTree AST", "files": [ "dist", @@ -19,7 +19,7 @@ }, "types": "./dist/index.d.ts", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "repository": { "type": "git", @@ -46,7 +46,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/types": "7.3.0", "eslint-visitor-keys": "^3.4.1" }, "devDependencies": { diff --git a/packages/website-eslint/CHANGELOG.md b/packages/website-eslint/CHANGELOG.md index ea9d8fdfa7f0..96f39307b3db 100644 --- a/packages/website-eslint/CHANGELOG.md +++ b/packages/website-eslint/CHANGELOG.md @@ -1,3 +1,28 @@ +## 7.3.0 (2024-03-18) + + +### 🩹 Fixes + +- correct `engines.node` constraints in `package.json` + + +### ❀️ Thank You + +- Abraham Guo +- Alexu +- Arka Pratim Chaudhuri +- auvred +- Derrick Isaacson +- fnx +- Josh Goldberg ✨ +- Kirk Waiblinger +- Marta Cardoso +- MichaΓ«l De Boey +- Tristan Rasmussen +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 7.2.0 (2024-03-11) diff --git a/packages/website-eslint/package.json b/packages/website-eslint/package.json index 3edb49842b28..3f434c3dc052 100644 --- a/packages/website-eslint/package.json +++ b/packages/website-eslint/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/website-eslint", - "version": "7.2.0", + "version": "7.3.0", "private": true, "description": "ESLint which works in browsers.", "files": [ @@ -14,7 +14,7 @@ } }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "scripts": { "build": "yarn tsx ./build.ts", @@ -23,16 +23,16 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/types": "7.2.0", - "@typescript-eslint/utils": "7.2.0" + "@typescript-eslint/types": "7.3.0", + "@typescript-eslint/utils": "7.3.0" }, "devDependencies": { "@eslint/js": "*", - "@typescript-eslint/eslint-plugin": "7.2.0", - "@typescript-eslint/parser": "7.2.0", - "@typescript-eslint/scope-manager": "7.2.0", - "@typescript-eslint/typescript-estree": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0", + "@typescript-eslint/eslint-plugin": "7.3.0", + "@typescript-eslint/parser": "7.3.0", + "@typescript-eslint/scope-manager": "7.3.0", + "@typescript-eslint/typescript-estree": "7.3.0", + "@typescript-eslint/visitor-keys": "7.3.0", "esbuild": "~0.20.0", "eslint": "*", "esquery": "*", diff --git a/packages/website/CHANGELOG.md b/packages/website/CHANGELOG.md index b82fdbb5ca27..acb0ee80d6c5 100644 --- a/packages/website/CHANGELOG.md +++ b/packages/website/CHANGELOG.md @@ -1,3 +1,30 @@ +## 7.3.0 (2024-03-18) + + +### πŸš€ Features + +- **eslint-plugin:** add meta.docs.recommended setting for strict config options + +- **eslint-plugin:** add rule `use-unknown-in-catch-callback-variables` + + +### ❀️ Thank You + +- Abraham Guo +- Alexu +- Arka Pratim Chaudhuri +- auvred +- Derrick Isaacson +- fnx +- Josh Goldberg ✨ +- Kirk Waiblinger +- Marta Cardoso +- MichaΓ«l De Boey +- Tristan Rasmussen +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 7.2.0 (2024-03-11) diff --git a/packages/website/docusaurus.config.js b/packages/website/docusaurus.config.js deleted file mode 100644 index 8e277f6f1c64..000000000000 --- a/packages/website/docusaurus.config.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -require('ts-node').register({ - scope: true, - scopeDir: __dirname, - transpileOnly: true, -}); - -module.exports = require('./docusaurusConfig'); diff --git a/packages/website/docusaurusConfig.ts b/packages/website/docusaurus.config.mts similarity index 96% rename from packages/website/docusaurusConfig.ts rename to packages/website/docusaurus.config.mts index 1598f96ffefa..080f169e03a0 100644 --- a/packages/website/docusaurusConfig.ts +++ b/packages/website/docusaurus.config.mts @@ -7,7 +7,6 @@ import npm2yarnPlugin from '@docusaurus/remark-plugin-npm2yarn'; import type { UserThemeConfig as ThemeCommonConfig } from '@docusaurus/theme-common'; import type { UserThemeConfig as AlgoliaThemeConfig } from '@docusaurus/theme-search-algolia'; import type { Config } from '@docusaurus/types'; -import tabsPlugin from 'remark-docusaurus-tabs'; import { version } from './package.json'; import { generatedRuleDocs } from './plugins/generated-rule-docs'; @@ -15,14 +14,11 @@ import { rulesMeta } from './rulesMeta'; const remarkPlugins: MDXPlugin[] = [[npm2yarnPlugin, { sync: true }]]; -const beforeDefaultRemarkPlugins: MDXPlugin[] = [tabsPlugin]; - const githubUrl = 'https://github.com/typescript-eslint/typescript-eslint'; const presetClassicOptions: PresetClassicOptions = { blog: { blogSidebarCount: 'ALL', - beforeDefaultRemarkPlugins: [...beforeDefaultRemarkPlugins], remarkPlugins, }, docs: { @@ -31,16 +27,12 @@ const presetClassicOptions: PresetClassicOptions = { sidebarPath: require.resolve('./sidebars/sidebar.rules.js'), routeBasePath: 'rules', editUrl: `${githubUrl}/edit/main/packages/website/`, - beforeDefaultRemarkPlugins: [ - ...beforeDefaultRemarkPlugins, - generatedRuleDocs, - ], + beforeDefaultRemarkPlugins: [generatedRuleDocs], remarkPlugins, exclude: ['TEMPLATE.md'], breadcrumbs: false, }, pages: { - beforeDefaultRemarkPlugins, remarkPlugins, }, theme: { @@ -54,7 +46,6 @@ const pluginContentDocsOptions: PluginContentDocsOptions = { routeBasePath: '/', sidebarPath: require.resolve('./sidebars/sidebar.base.js'), editUrl: `${githubUrl}/edit/main/packages/website/`, - beforeDefaultRemarkPlugins, remarkPlugins, breadcrumbs: false, }; @@ -294,6 +285,10 @@ const config: Config = { 'The tooling that enables ESLint and Prettier to support TypeScript.', url: 'https://typescript-eslint.io', baseUrl: '/', + + // See https://github.com/typescript-eslint/typescript-eslint/pull/8209#discussion_r1444033533 + onBrokenAnchors: 'ignore', + onBrokenLinks: 'throw', onBrokenMarkdownLinks: 'throw', favicon: 'img/favicon.ico', @@ -342,4 +337,4 @@ const config: Config = { ], }; -export = config; +export default config; diff --git a/packages/website/package.json b/packages/website/package.json index 729f135dc961..d31a21cbecb2 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -1,6 +1,6 @@ { "name": "website", - "version": "7.2.0", + "version": "7.3.0", "private": true, "scripts": { "build": "docusaurus build", @@ -16,16 +16,17 @@ "typecheck": "tsc -b ./tsconfig.json" }, "dependencies": { - "@babel/runtime": "^7.22.6", - "@docusaurus/core": "~2.4.1", - "@docusaurus/plugin-client-redirects": "~2.4.3", - "@docusaurus/plugin-pwa": "~2.4.1", - "@docusaurus/preset-classic": "~2.4.1", - "@docusaurus/remark-plugin-npm2yarn": "~2.4.1", - "@docusaurus/theme-common": "~2.4.1", - "@mdx-js/react": "1.6.22", - "@typescript-eslint/parser": "7.2.0", - "@typescript-eslint/website-eslint": "7.2.0", + "@babel/runtime": "^7.24.0", + "@docusaurus/core": "^3.1.1", + "@docusaurus/plugin-client-redirects": "^3.1.1", + "@docusaurus/plugin-pwa": "^3.1.1", + "@docusaurus/preset-classic": "^3.1.1", + "@docusaurus/remark-plugin-npm2yarn": "^3.1.1", + "@docusaurus/theme-common": "^3.1.1", + "@mdx-js/react": "^3.0.1", + "@prettier/sync": "*", + "@typescript-eslint/parser": "7.3.0", + "@typescript-eslint/website-eslint": "7.3.0", "clsx": "^2.0.0", "eslint": "*", "json-schema": "^0.4.0", @@ -37,34 +38,31 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-resizable-panels": "^0.0.63", - "remark-docusaurus-tabs": "^0.2.0", "semver": "^7.5.4", - "ts-node": "*", "typescript": "*" }, "resolutions": { "react": "^18.2.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "~2.4.1", + "@docusaurus/module-type-aliases": "^3.1.0", "@types/react": "*", "@types/react-helmet": "^6.1.6", "@types/react-router-dom": "^5.3.3", - "@typescript-eslint/eslint-plugin": "7.2.0", - "@typescript-eslint/rule-schema-to-typescript-types": "7.2.0", - "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/eslint-plugin": "7.3.0", + "@typescript-eslint/rule-schema-to-typescript-types": "7.3.0", + "@typescript-eslint/types": "7.3.0", "copy-webpack-plugin": "^11.0.0", "cross-fetch": "*", "globby": "^11.1.0", "make-dir": "*", - "monaco-editor": "~0.46.0", + "monaco-editor": "~0.47.0", "raw-loader": "^4.0.2", "rimraf": "*", "stylelint": "^15.10.1", "stylelint-config-recommended": "^13.0.0", "stylelint-config-standard": "^34.0.0", "stylelint-order": "^6.0.3", - "ts-node": "*", "tsx": "*", "webpack": "^5.88.1" }, diff --git a/packages/website/plugins/generated-rule-docs/addESLintHashToCodeBlocksMeta.ts b/packages/website/plugins/generated-rule-docs/addESLintHashToCodeBlocksMeta.ts index 458e7ff3d501..ac59bea53559 100644 --- a/packages/website/plugins/generated-rule-docs/addESLintHashToCodeBlocksMeta.ts +++ b/packages/website/plugins/generated-rule-docs/addESLintHashToCodeBlocksMeta.ts @@ -1,28 +1,40 @@ +import type { MdxJsxFlowElement } from 'mdast-util-mdx'; +import type * as unist from 'unist'; + import type { RuleDocsPage } from './RuleDocsPage'; import { convertToPlaygroundHash, nodeIsCode } from './utils'; const optionRegex = /option='(?