diff --git a/.cspell.json b/.cspell.json index fd3b4030747d..fe74cdb9ba4c 100644 --- a/.cspell.json +++ b/.cspell.json @@ -49,6 +49,8 @@ "words": [ "Airbnb", "Airbnb's", + "ambiently", + "allowdefaultproject", "allowdefaultprojectforfiles", "ambiently", "Arka", @@ -64,20 +66,26 @@ "auvred", "Bachman", "backticks", + "Bekkhus", "bigint", "bivariant", + "Bjorn", "blockless", "blurple", "bradzacher", + "Burzyński", "camelcase", + "Cann", "canonicalize", "Cena", "Chaudhuri", + "Cheung", "codebases", "Codecov", "contravariant", "Crockford", "declarators", + "Dědič", "destructure", "destructured", "destructures", @@ -92,7 +100,10 @@ "esquery", "esrecurse", "estree", + "extrafileextensions", "falsiness", + "fisker", + "García", "globby", "Hasegawa", "Huchedé", @@ -102,14 +113,23 @@ "IIFEs", "jameshenry", "joshuakgoldberg", + "Katt", "kirkwaiblinger", "linebreaks", + "Lundberg", "lzstring", + "Marek", "markdownlint", + "Marminge", + "Mateusz", + "Menke", "metastring", + "Mishkin", + "Mugabo", "multipass", "Nandi", "necroing", + "Nicolò", "nocheck", "noninteractive", "Nrwl", @@ -128,27 +148,34 @@ "preact", "Premade", "prettier's", + "projectservice", "quasis", "Quickstart", + "Rebecca", "recurse", "redeclaration", "redeclarations", "redeclared", "reimplement", "resync", + "Ribaudo", "ROADMAP", + "Romain", "Rosenwasser", "ruleset", "rulesets", "serializers", "Sheetal", + "Simen", "Sourcegraph", "stringification", "stringifying", "stringly", "subclassing", + "Sukka", "superset", "thenables", + "Tobbe", "transpiled", "transpiles", "transpiling", @@ -166,6 +193,7 @@ "unprefixed", "upsert", "useprojectservice", + "Verité", "Waiblinger", "warnonunsupportedtypescriptversion", "Yukihiro", diff --git a/.github/actions/breaking-pr-check/index.js b/.github/actions/breaking-pr-check/index.js index f567fa396eb1..08544aff645f 100644 --- a/.github/actions/breaking-pr-check/index.js +++ b/.github/actions/breaking-pr-check/index.js @@ -1,5 +1,5 @@ // @ts-check -/* eslint-disable jsdoc/no-types */ +/* eslint-disable jsdoc/no-types, @typescript-eslint/no-require-imports */ const core = require('@actions/core'); const github = require('@actions/github'); diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92076445c2ba..a4e5de3a6b6a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -148,7 +148,7 @@ jobs: - name: Build uses: ./.github/actions/prepare-build - - name: Run integrations tests + - name: Run integration tests run: yarn test-integration env: CI: true @@ -220,8 +220,8 @@ jobs: # Sadly 1 day is the minimum retention-days: 1 - unit_tests_tsserver: - name: Run Unit Tests with Experimental TSServer + unit_tests_project_service: + name: Run Unit Tests with Project Service needs: [build] runs-on: ubuntu-latest strategy: @@ -245,7 +245,7 @@ jobs: run: npx nx test ${{ matrix.package }} --coverage=false env: CI: true - TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER: true + TYPESCRIPT_ESLINT_PROJECT_SERVICE: true upload_coverage: name: Upload Codecov Coverage diff --git a/.github/workflows/close-v8-issues.yml b/.github/workflows/close-v8-issues.yml new file mode 100644 index 000000000000..85b580e887fa --- /dev/null +++ b/.github/workflows/close-v8-issues.yml @@ -0,0 +1,18 @@ +name: Close issues related to a merged pull request based on v8 branch. + +on: + pull_request_target: + types: [closed] + branches: + - v8 + +jobs: + close_v8_issue_on_pr_merge: + permissions: + issues: write + runs-on: ubuntu-latest + steps: + - name: Closes issues related to a merged pull request. + uses: ldez/gha-mjolnir@v1.4.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.markdownlint.json b/.markdownlint.json index 71abe85dea29..8aeb536548d6 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -62,6 +62,7 @@ "allowed_elements": [ "a", "br", + "code", "details", "em", "figcaption", @@ -100,9 +101,8 @@ // MD044/proper-names - Proper names should have the correct capitalization "MD044": { "names": [ - "eslint-scope", - "eslint-plugin", - "eslint-recommended", + "eslint-", + "eslint.style", "ESLint", "JavaScript", "TSLint", diff --git a/CHANGELOG.md b/CHANGELOG.md index 468f357b39ba..90b3891db5c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,101 @@ +## 8.0.1 (2024-08-05) + + +### 🩹 Fixes + +- **eslint-plugin:** [no-unused-vars] ignore imports used only as types ([#9694](https://github.com/typescript-eslint/typescript-eslint/pull/9694)) + +### ❤️ Thank You + +- Jake Bailey @jakebailey + +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. + +# 8.0.0 (2024-07-31) + + +### 🚀 Features + +- stricter parent types for the AST ([#9560](https://github.com/typescript-eslint/typescript-eslint/pull/9560)) +- speed up non-type-aware linting with project service ([#8322](https://github.com/typescript-eslint/typescript-eslint/pull/8322)) +- v8 integration branch ([#9165](https://github.com/typescript-eslint/typescript-eslint/pull/9165)) +- **ast-spec:** remove deprecated type params ([#8933](https://github.com/typescript-eslint/typescript-eslint/pull/8933)) +- **eslint-plugin:** remove formatting/layout rules ([#8833](https://github.com/typescript-eslint/typescript-eslint/pull/8833)) +- **eslint-plugin:** [prefer-nullish-coalescing] change ignoreConditionalTests default to true ([#8872](https://github.com/typescript-eslint/typescript-eslint/pull/8872)) +- **eslint-plugin:** deprecate no-loss-of-precision extension rule ([#8832](https://github.com/typescript-eslint/typescript-eslint/pull/8832)) +- **eslint-plugin:** [no-unused-vars] align catch behavior to ESLint 9 ([#8971](https://github.com/typescript-eslint/typescript-eslint/pull/8971)) +- **eslint-plugin:** split no-empty-object-type out from ban-types and no-empty-interfaces ([#8977](https://github.com/typescript-eslint/typescript-eslint/pull/8977)) +- **eslint-plugin:** remove deprecated no-throw-literal rule ([#9092](https://github.com/typescript-eslint/typescript-eslint/pull/9092)) +- **eslint-plugin:** apply initial config changes for v8 ([#9079](https://github.com/typescript-eslint/typescript-eslint/pull/9079)) +- **eslint-plugin:** remove no-useless-template-literals ([#9207](https://github.com/typescript-eslint/typescript-eslint/pull/9207)) +- **eslint-plugin:** [no-floating-promises] add 'allowForKnownSafeCalls' option ([#9234](https://github.com/typescript-eslint/typescript-eslint/pull/9234)) +- **eslint-plugin:** replace ban-types with no-restricted-types, no-unsafe-function-type, no-wrapper-object-types ([#9102](https://github.com/typescript-eslint/typescript-eslint/pull/9102)) +- **eslint-plugin:** [no-unused-vars] add `reportUnusedIgnorePattern` option ([#9324](https://github.com/typescript-eslint/typescript-eslint/pull/9324)) +- **eslint-plugin:** [no-unused-vars] support `ignoreClassWithStaticInitBlock` ([#9325](https://github.com/typescript-eslint/typescript-eslint/pull/9325)) +- **eslint-plugin:** [no-unused-vars] handle comma operator for assignments, treat for-of the same as for-in ([#9326](https://github.com/typescript-eslint/typescript-eslint/pull/9326)) +- **eslint-plugin:** [no-unused-vars] report if var used only in typeof ([#9330](https://github.com/typescript-eslint/typescript-eslint/pull/9330)) +- **eslint-plugin:** [no-floating-promises] disable checkThenables by default for v8 ([#9559](https://github.com/typescript-eslint/typescript-eslint/pull/9559)) +- **eslint-plugin:** [return-await] add return-await to strict-type-checked preset ([#9604](https://github.com/typescript-eslint/typescript-eslint/pull/9604)) +- **eslint-plugin:** [no-unnecessary-type-parameters] promote to strict ([#9662](https://github.com/typescript-eslint/typescript-eslint/pull/9662)) +- **parser:** always enable comment, loc, range, tokens ([#8617](https://github.com/typescript-eslint/typescript-eslint/pull/8617)) +- **rule-tester:** support multipass fixes ([#8883](https://github.com/typescript-eslint/typescript-eslint/pull/8883)) +- **rule-tester:** switched to flat config ([#9603](https://github.com/typescript-eslint/typescript-eslint/pull/9603)) +- **type-utils:** remove getTokenAtPosition ([#9444](https://github.com/typescript-eslint/typescript-eslint/pull/9444)) +- **type-utils:** support intersection types in TypeOrValueSpecifier ([#9633](https://github.com/typescript-eslint/typescript-eslint/pull/9633)) +- **typescript-estree:** remove slow deprecated and isolated programs ([#8834](https://github.com/typescript-eslint/typescript-eslint/pull/8834)) +- **typescript-estree:** split TSMappedType typeParameter into constraint and key ([#7065](https://github.com/typescript-eslint/typescript-eslint/pull/7065)) +- **typescript-estree:** rename automaticSingleRunInference to disallowAutomaticSingleRunInference ([#8922](https://github.com/typescript-eslint/typescript-eslint/pull/8922)) +- **typescript-estree:** stabilize EXPERIMENTAL_useProjectService as projectService ([#9084](https://github.com/typescript-eslint/typescript-eslint/pull/9084)) +- **typescript-estree:** remove EXPERIMENTAL_useSourceOfProjectReferenceRedirect ([#9104](https://github.com/typescript-eslint/typescript-eslint/pull/9104)) +- **typescript-estree:** also remove projectService in withoutProjectParserOptions ([#9287](https://github.com/typescript-eslint/typescript-eslint/pull/9287)) +- **typescript-estree:** exposes ProjectService logs through the plugin ([#9337](https://github.com/typescript-eslint/typescript-eslint/pull/9337)) +- **utils:** add Linter configType constructor option ([#8999](https://github.com/typescript-eslint/typescript-eslint/pull/8999)) +- **utils:** swap LegacyESLint out for FlatESLint as ESLint export ([#8972](https://github.com/typescript-eslint/typescript-eslint/pull/8972)) +- **utils:** remove deprecated context helpers ([#9000](https://github.com/typescript-eslint/typescript-eslint/pull/9000)) +- **utils:** allow specifying additional rule meta.docs in RuleCreator ([#9025](https://github.com/typescript-eslint/typescript-eslint/pull/9025)) + +### 🩹 Fixes + +- correct eslint-plugin's peerDependency on parser@8 ([#9089](https://github.com/typescript-eslint/typescript-eslint/pull/9089)) +- bring back in allowdefaultprojectforfiles rename ([7dfceeeea](https://github.com/typescript-eslint/typescript-eslint/commit/7dfceeeea)) +- disable `projectService` in `disabled-type-checked` shared config ([#9460](https://github.com/typescript-eslint/typescript-eslint/pull/9460)) +- **eslint-plugin:** include alpha pre-releases in parser peer dependency ([#9099](https://github.com/typescript-eslint/typescript-eslint/pull/9099)) +- **eslint-plugin:** correct rules.d.ts types to not rely on non-existent imports ([#9339](https://github.com/typescript-eslint/typescript-eslint/pull/9339)) +- **eslint-plugin:** remove duplicate import `RuleModuleWithMetaDocs` ([#9465](https://github.com/typescript-eslint/typescript-eslint/pull/9465)) +- **eslint-plugin:** [no-unnecessary-template-expression] do not render escaped strings in autofixes ([#8688](https://github.com/typescript-eslint/typescript-eslint/pull/8688)) +- **eslint-plugin:** [no-unused-vars] incorporate upstream changes around caught errors report messages ([#9532](https://github.com/typescript-eslint/typescript-eslint/pull/9532)) +- **eslint-plugin:** [no-misused-promises] perf: avoid getting types of variables/functions if the annotated type is obviously not a function ([#9656](https://github.com/typescript-eslint/typescript-eslint/pull/9656)) +- **rule-tester:** set configType to eslintrc in Linter options ([#9178](https://github.com/typescript-eslint/typescript-eslint/pull/9178)) +- **rule-tester:** re-apply updates from main ([#9180](https://github.com/typescript-eslint/typescript-eslint/pull/9180)) +- **rule-tester:** provide Linter a cwd in its constructor ([#9678](https://github.com/typescript-eslint/typescript-eslint/pull/9678)) +- **type-utils:** also check declared modules for package names in TypeOrValueSpecifier ([#9500](https://github.com/typescript-eslint/typescript-eslint/pull/9500)) +- **types:** allow ProjectServiceOptions for projectService ([#9318](https://github.com/typescript-eslint/typescript-eslint/pull/9318)) +- **typescript-estree:** add TSEnumBody node for TSEnumDeclaration body ([#8920](https://github.com/typescript-eslint/typescript-eslint/pull/8920)) +- **typescript-estree:** enable dot globs for project by default ([#8818](https://github.com/typescript-eslint/typescript-eslint/pull/8818)) +- **typescript-estree:** pass extraFileExtensions to projectService ([#9051](https://github.com/typescript-eslint/typescript-eslint/pull/9051)) +- **typescript-estree:** only run projectService setHostConfiguration when needed ([#9336](https://github.com/typescript-eslint/typescript-eslint/pull/9336)) +- **typescript-estree:** specific error for parserOptions.project not including a file ([#9584](https://github.com/typescript-eslint/typescript-eslint/pull/9584)) +- **typescript-estree:** adds support for project services using extended config files ([#9306](https://github.com/typescript-eslint/typescript-eslint/pull/9306)) +- **typescript-estree:** factor tsconfigRootDir into allowDefaultProject ([#9675](https://github.com/typescript-eslint/typescript-eslint/pull/9675)) + +### ❤️ Thank You + +- Abraham Guo +- Alfred Ringstad @alfredringstad +- auvred @auvred +- Brad Zacher @bradzacher +- Christopher Aubut @higherorderfunctor +- Collin Bachman @bachmacintosh +- James Henry @JamesHenry +- Josh Goldberg +- Josh Goldberg ✨ +- Kirk Waiblinger @kirkwaiblinger +- StyleShit @StyleShit +- Victor Lin @yepitschunked +- Yukihiro Hasegawa @y-hsgw + +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.18.0 (2024-07-29) diff --git a/README.md b/README.md index 9e78bb7008ff..c9474769f51d 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,6 @@

👆

-

- Using ESLint v9? See: ESLint v9 Support. -

## Code Contributors diff --git a/docs/developers/Custom_Rules.mdx b/docs/developers/Custom_Rules.mdx index 2347d4db88a9..3fbd3ad48c37 100644 --- a/docs/developers/Custom_Rules.mdx +++ b/docs/developers/Custom_Rules.mdx @@ -99,6 +99,32 @@ export const rule = createRule({ }); ``` +#### Extra Rule Docs Types + +By default, rule `meta.docs` is allowed to contain only `description` and `url` as described in [ESLint's Custom Rules > Rule Structure docs](https://eslint.org/docs/latest/extend/custom-rules#rule-structure). +Additional docs properties may be added as a type argument to `ESLintUtils.RuleCreator`: + +```ts +interface MyPluginDocs { + recommended: boolean; +} + +const createRule = ESLintUtils.RuleCreator( + name => `https://example.com/rule/${name}`, +); + +createRule({ + // ... + meta: { + docs: { + description: '...', + recommended: true, + }, + // ... + }, +}); +``` + ### Undocumented Rules Although it is generally not recommended to create custom rules without documentation, if you are sure you want to do this you can use the `ESLintUtils.RuleCreator.withoutDocs` function to directly create a rule. diff --git a/docs/getting-started/Typed_Linting.mdx b/docs/getting-started/Typed_Linting.mdx index 08aa30b78ace..bc479720e737 100644 --- a/docs/getting-started/Typed_Linting.mdx +++ b/docs/getting-started/Typed_Linting.mdx @@ -25,7 +25,7 @@ export default tseslint.config( { languageOptions: { parserOptions: { - project: true, + projectService: true, tsconfigRootDir: import.meta.dirname, }, }, @@ -42,7 +42,7 @@ For CommonJS modules and/or older versions of Node.js, [use `__dirname` or an al In more detail: - `tseslint.configs.recommendedTypeChecked` is another [shared configuration](../users/Shared_Configurations.mdx) we provide. This one contains recommended rules that additionally require type information. -- `parserOptions.project: true` indicates to find the closest `tsconfig.json` for each source file (see [Parser#project](../packages/Parser.mdx#project)). +- `parserOptions.projectService: true` indicates to ask TypeScript's type checking service for each source file's type information (see [Parser#projectService](../packages/Parser.mdx#projectService)). - `parserOptions.tsconfigRootDir` tells our parser the absolute path of your project's root directory (see [Parser#tsconfigRootDir](../packages/Parser.mdx#tsconfigrootdir)). @@ -65,7 +65,7 @@ module.exports = { parser: '@typescript-eslint/parser', // Added lines start parserOptions: { - project: true, + projectService: true, tsconfigRootDir: __dirname, }, // Added lines end @@ -76,7 +76,7 @@ module.exports = { In more detail: - `plugin:@typescript-eslint/recommended-type-checked` is another [shared configuration](../users/Shared_Configurations.mdx) we provide. This one contains recommended rules that additionally require type information. -- `parserOptions.project: true` indicates to find the closest `tsconfig.json` for each source file (see [Parser#project](../packages/Parser.mdx#project)). +- `parserOptions.projectService: true` indicates to ask TypeScript's type checking service for each source file's type information (see [Parser#projectService](../packages/Parser.mdx#projectService)). - `parserOptions.tsconfigRootDir` tells our parser the absolute path of your project's root directory (see [Parser#tsconfigRootDir](../packages/Parser.mdx#tsconfigrootdir)). @@ -84,7 +84,7 @@ In more detail: :::caution Your ESLint config file may start receiving a parsing error about type information. -See [our TSConfig inclusion FAQ](../troubleshooting/faqs/General.mdx#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file). +See [our TSConfig inclusion FAQ](../troubleshooting/typed-linting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file). ::: With that done, run the same lint command you ran before. @@ -142,7 +142,11 @@ You can read more about the rules provided by typescript-eslint in our [rules do ### Can I customize the TSConfig used for typed linting? -The `project` option can be turned on with either: +Yes, but it's not recommended in most configurations. +`parserOptions.projectService` uses the same "project service" APIs used by editors such as VS Code to generate TypeScript's type information. +Using a different TSConfig runs the risk of providing different types for typed linting than what your editor or `tsc` see. + +If you absolutely must, the `parserOptions.project` option can be used instead of `parserOptions.projectService` with either: - `true`: to always use `tsconfig.json`s nearest to source files - `string | string[]`: any number of glob paths to match TSConfig files relative to `parserOptions.tsconfigRootDir`, or the current working directory if that is not provided @@ -182,7 +186,7 @@ module.exports = { -See [the `@typescript-eslint/parser` docs for more details](../packages/Parser.mdx#project). +See [the `@typescript-eslint/parser` `project` docs for more details](../packages/Parser.mdx#project). :::note If your project is a multi-package monorepo, see [our docs on configuring a monorepo](../troubleshooting/typed-linting/Monorepos.mdx). @@ -203,7 +207,8 @@ export default tseslint.config( { languageOptions: { parserOptions: { - project: true, + projectService: true, + tsconfigRootDir: import.meta.name, }, }, }, @@ -254,7 +259,7 @@ If you use type-aware rules from other plugins, you will need to manually disabl ### How is performance? Typed rules come with a catch. -By including `parserOptions.project` in your config, you incur the performance penalty of asking TypeScript to do a build of your project before ESLint can do its linting. +By using typed linting in your config, you incur the performance penalty of asking TypeScript to do a build of your project before ESLint can do its linting. For small projects this takes a negligible amount of time (a few seconds or less); for large projects, it can take longer. Most of our users do not mind this cost as the power and safety of type-aware static analysis rules is worth the tradeoff. diff --git a/docs/maintenance/Issues.mdx b/docs/maintenance/Issues.mdx index de21f0bbef95..e97767d6bf7b 100644 --- a/docs/maintenance/Issues.mdx +++ b/docs/maintenance/Issues.mdx @@ -163,7 +163,7 @@ For enhancements meant to limit which kinds of nodes the rule targets, mark the #### 🚀 New Rules We're generally accepting of new rules that meet the above feature request criteria. -The biggest exception is rules that can roughly be implemented with [`@typescript-eslint/ban-types`](/rules/ban-types) and/or [`no-restricted-syntax`](https://eslint.org/docs/latest/rules/no-restricted-syntax). +The biggest exception is rules that can roughly be implemented with [`@typescript-eslint/no-restricted-types`](/rules/no-restricted-types) and/or [`no-restricted-syntax`](https://eslint.org/docs/latest/rules/no-restricted-syntax). ## Pruning Old Issues diff --git a/docs/packages/Parser.mdx b/docs/packages/Parser.mdx index b443d4c36cdf..c75ca14a8855 100644 --- a/docs/packages/Parser.mdx +++ b/docs/packages/Parser.mdx @@ -35,10 +35,10 @@ The following additional configuration options are available by specifying them ```ts interface ParserOptions { - allowAutomaticSingleRunInference?: boolean; cacheLifetime?: { glob?: number | 'Infinity'; }; + disallowAutomaticSingleRunInference?: boolean; ecmaFeatures?: { jsx?: boolean; globalReturn?: boolean; @@ -54,28 +54,29 @@ interface ParserOptions { programs?: import('typescript').Program[]; project?: string | string[] | boolean | null; projectFolderIgnoreList?: string[]; + projectService?: boolean | ProjectServiceOptions; tsconfigRootDir?: string; warnOnUnsupportedTypeScriptVersion?: boolean; - EXPERIMENTAL_useProjectService?: boolean; } ``` -### `allowAutomaticSingleRunInference` +### `disallowAutomaticSingleRunInference` -> Default `process.env.TSESTREE_SINGLE_RUN` or `false`. +> Default `process.env.TSESTREE_SINGLE_RUN` or `true`. -Whether to use common heuristics to infer whether ESLint is being used as part of a single run (as opposed to `--fix` mode or in a persistent session such as an editor extension). +Whether to stop using common heuristics to infer whether ESLint is being used as part of a single run (as opposed to `--fix` mode or in a persistent session such as an editor extension). +In other words, typescript-eslint is faster by default, and this option disables an automatic performance optimization. When typescript-eslint handles TypeScript Program management behind the scenes for [linting with type information](../getting-started/Typed_Linting.mdx), this distinction is important for performance. There is significant overhead to managing TypeScript "Watch" Programs needed for the long-running use-case. Being able to assume the single run case allows typescript-eslint to faster immutable Programs instead. This setting's default value can be specified by setting a `TSESTREE_SINGLE_RUN` environment variable to `"false"` or `"true"`. -For example, `TSESTREE_SINGLE_RUN=true npx eslint .` will enable it. +For example, `TSESTREE_SINGLE_RUN=false npx eslint .` will disable it. -:::tip -We've seen `allowAutomaticSingleRunInference` improve linting speed in CI by up to 10-20%. -Our plan is to [enable `allowAutomaticSingleRunInference` by default in an upcoming major version](https://github.com/typescript-eslint/typescript-eslint/issues/8121). +:::note +We recommend leaving this option off if possible. +We've seen allowing automatic single run inference improve linting speed in CI by up to 10-20%. ::: ### `cacheLifetime` @@ -84,7 +85,7 @@ This option allows you to granularly control our internal cache expiry lengths. You can specify the number of seconds as an integer number, or the string 'Infinity' if you never want the cache to expire. -By default cache entries will be evicted after 30 seconds, or will persist indefinitely if the parser infers that it is a single run (see [`allowAutomaticSingleRunInference`](#allowAutomaticSingleRunInference)). +By default cache entries will be evicted after 30 seconds, or will persist indefinitely if the parser infers that it is a single run (see [`disallowAutomaticSingleRunInference`](#disallowAutomaticSingleRunInference)). ### `ecmaFeatures` @@ -151,6 +152,10 @@ This option allows you to provide one or more additional file extensions which s The default extensions are `['.js', '.mjs', '.cjs', '.jsx', '.ts', '.mts', '.cts', '.tsx']`. Add extensions starting with `.`, followed by the file extension. E.g. for a `.vue` file use `"extraFileExtensions": [".vue"]`. +:::note +See [Changes to `extraFileExtensions` with `projectService`](../troubleshooting/typed-linting/Performance.mdx#changes-to-extrafileextensions-with-projectservice) to avoid performance issues. +::: + ### `jsDocParsingMode` > Default if `parserOptions.project` is set, then `'all'`, otherwise `'none'` @@ -209,9 +214,13 @@ All linted files must be part of the provided program(s). ### `project` +:::note +We now recommend using [`projectService`](#projectservice) instead of `project` for easier configuration and faster linting. +::: + > Default `undefined`. -A path to your project's TSConfig. **This setting is required to use [rules which require type information](../getting-started/Typed_Linting.mdx)**. +A path to your project's TSConfig. **This setting or [`projectService`](#projectservice) are required to use [rules which require type information](../getting-started/Typed_Linting.mdx)**. Accepted value types: @@ -265,65 +274,26 @@ If this setting is specified, you must only lint files that are included in the } ``` -For an option that allows linting files outside of your TSConfig file(s), see [`EXPERIMENTAL_useProjectService`](#experimental_useprojectservice). - -### `projectFolderIgnoreList` - -> Default `["**/node_modules/**"]`. - -This option allows you to ignore folders from being included in your provided list of `project`s. -This is useful if you have configured glob patterns, but want to make sure you ignore certain folders. - -It accepts an array of globs to exclude from the `project` globs. - -For example, by default it will ensure that a glob like `./**/tsconfig.json` will not match any `tsconfig`s within your `node_modules` folder (some npm packages do not exclude their source files from their published packages). - -### `tsconfigRootDir` - -> Default `undefined`. - -This option allows you to provide the root directory for relative TSConfig paths specified in the `project` option above. -Doing so ensures running ESLint from a directory other than the root will still be able to find your TSConfig. - -### `warnOnUnsupportedTypeScriptVersion` - -> Default `true`. - -This option allows you to toggle the warning that the parser will give you if you use a version of TypeScript which is not explicitly supported. The warning message would look like this: - -```plaintext -============= - -WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree. +For an option that allows linting files outside of your TSConfig file(s), see [`projectService`](#projectService). -You may find that it works just fine, or you may not. + -SUPPORTED TYPESCRIPT VERSIONS: >=3.3.1 <5.1.0 - -YOUR TYPESCRIPT VERSION: 5.1.3 - -Please only submit bug reports when using the officially supported version. - -============= -``` - -### `EXPERIMENTAL_useProjectService` +### `projectService` > Default `false`. -An experimental alternative to `parserOptions.project`. -This directs the parser to use a more seamless TypeScript API to generate type information for rules. +Specifies using TypeScript APIs to generate type information for rules. It will automatically detect the TSConfig for each file (like `project: true`), and will also allow type information to be computed for JavaScript files without the `allowJs` compiler option (unlike `project: true`). -```js title="eslint.config.mjs" +```js title="eslint.config.js" export default [ { languageOptions: { parserOptions: { - EXPERIMENTAL_useProjectService: true, + projectService: true, }, }, }, @@ -337,7 +307,7 @@ export default [ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { - EXPERIMENTAL_useProjectService: true, + projectService: true, }, }; ``` @@ -345,17 +315,88 @@ module.exports = { -This option should bring two main benefits: +**This setting or [`project`](#project) are required to use [rules which require type information](../getting-started/Typed_Linting.mdx)**. + +This option brings two main benefits over the older `project`: - Simpler configurations: most projects shouldn't need to explicitly configure `project` paths or create `tsconfig.eslint.json`s - Improved performance: this API is optimized on the TypeScript side for speed - Initial versions of this option demonstrated performance changes in subsets of the typescript-eslint monorepo ranging from 11% slower to 70% faster -We're hopeful this option will eventually become the standard way to enable typed linting. -It switches the parser from manually creating TypeScript programs to instead calling the same "project services" API used by editors such as VS Code. -However, because it's so new and untested, we're keeping it under the `EXPERIMENTAL_` prefix for at least all of the `6.X` versions. +For more information, see: + +- [feat(typescript-estree): add EXPERIMENTAL_useProjectService option to use TypeScript project service](https://github.com/typescript-eslint/typescript-eslint/pull/6754) +- [docs: blog post on parserOptions.projectService](https://github.com/typescript-eslint/typescript-eslint/pull/8031) + +#### `ProjectServiceOptions` -See [feat(typescript-estree): add EXPERIMENTAL_useProjectService option to use TypeScript project service](https://github.com/typescript-eslint/typescript-eslint/pull/6754) for more information. +The behavior of `parserOptions.projectService` can be customized by setting it to an object. + +```js +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + projectService: { + allowDefaultProject: ['*.js'], + }, + }, +}; +``` + +##### `allowDefaultProject` + +Globs of files to allow running with the default project compiler options despite not being matched by the project service. + +##### `defaultProject` + +Path to a TSConfig to use instead of TypeScript's default project configuration. + +##### `maximumDefaultProjectFileMatchCount_THIS_WILL_SLOW_DOWN_LINTING` + +> Default: `8`. + +The maximum number of files [`allowDefaultProject`](#allowdefaultproject) may match. +Each file match slows down linting, so if you do need to use this, please file an informative issue on typescript-eslint explaining why - so we can help you avoid using it! + +### `projectFolderIgnoreList` + +> Default `["**/node_modules/**"]`. + +This option allows you to ignore folders from being included in your provided list of `project`s. +This is useful if you have configured glob patterns, but want to make sure you ignore certain folders. + +It accepts an array of globs to exclude from the `project` globs. + +For example, by default it will ensure that a glob like `./**/tsconfig.json` will not match any `tsconfig`s within your `node_modules` folder (some npm packages do not exclude their source files from their published packages). + +### `tsconfigRootDir` + +> Default `undefined`. + +This option allows you to provide the root directory for relative TSConfig paths specified in the `project` option above. +Doing so ensures running ESLint from a directory other than the root will still be able to find your TSConfig. + +### `warnOnUnsupportedTypeScriptVersion` + +> Default `true`. + +This option allows you to toggle the warning that the parser will give you if you use a version of TypeScript which is not explicitly supported. The warning message would look like this: + +```plaintext +============= + +WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree. + +You may find that it works just fine, or you may not. + +SUPPORTED TYPESCRIPT VERSIONS: >=3.3.1 <5.1.0 + +YOUR TYPESCRIPT VERSION: 5.1.3 + +Please only submit bug reports when using the officially supported version. + +============= +``` ## Utilities diff --git a/docs/packages/Rule_Tester.mdx b/docs/packages/Rule_Tester.mdx index 3d1f085c2352..3153c89e24b3 100644 --- a/docs/packages/Rule_Tester.mdx +++ b/docs/packages/Rule_Tester.mdx @@ -77,6 +77,15 @@ ruleTester.run('my-rule', rule, { /* ... */ ], }, + // Multi-pass fixes can be tested using the array form of output. + // Note: this is unique to typescript-eslint, and doesn't exist in ESLint core. + { + code: 'const d = 1;', + output: ['const e = 1;', 'const f = 1;'], + errors: [ + /* ... */ + ], + }, // suggestions can be tested via errors { @@ -137,9 +146,11 @@ You can then test your rule by providing the type-aware config: ```ts const ruleTester = new RuleTester({ // Added lines start - parserOptions: { - tsconfigRootDir: './path/to/your/folder/fixture', - project: './tsconfig.json', + languageOptions: { + parserOptions: { + tsconfigRootDir: './path/to/your/folder/fixture', + project: './tsconfig.json', + }, }, // Added lines end }); diff --git a/docs/packages/TypeScript_ESTree.mdx b/docs/packages/TypeScript_ESTree.mdx index 516148c4fde7..7e70ed1624c8 100644 --- a/docs/packages/TypeScript_ESTree.mdx +++ b/docs/packages/TypeScript_ESTree.mdx @@ -158,34 +158,53 @@ Parses the given string of code with the options provided and returns an ESTree- ```ts interface ParseAndGenerateServicesOptions extends ParseOptions { /** - * Causes the parser to error if the TypeScript compiler returns any unexpected syntax/semantic errors. + * Granular control of the expiry lifetime of our internal caches. + * You can specify the number of seconds as an integer number, or the string + * 'Infinity' if you never want the cache to expire. + * + * By default cache entries will be evicted after 30 seconds, or will persist + * indefinitely if `disallowAutomaticSingleRunInference = false` AND the parser + * infers that it is a single run. */ - errorOnTypeScriptSyntacticAndSemanticIssues?: boolean; + cacheLifetime?: { + /** + * Glob resolution for `parserOptions.project` values. + */ + glob?: number | 'Infinity'; + }; /** - * ***EXPERIMENTAL FLAG*** - Use this at your own risk. + * ESLint (and therefore typescript-eslint) is used in both "single run"/one-time contexts, + * such as an ESLint CLI invocation, and long-running sessions (such as continuous feedback + * on a file in an IDE). * - * Whether to create a shared TypeScript server to power program creation. + * When typescript-eslint handles TypeScript Program management behind the scenes, this distinction + * is important because there is significant overhead to managing the so called Watch Programs + * needed for the long-running use-case. + * + * By default, we will use common heuristics to infer whether ESLint is being + * used as part of a single run. This option disables those heuristics, and + * therefore the performance optimizations gained by them. + * + * In other words, typescript-eslint is faster by default, and this option + * disables an automatic performance optimization. * - * @see https://github.com/typescript-eslint/typescript-eslint/issues/6575 + * This setting's default value can be specified by setting a `TSESTREE_SINGLE_RUN` + * environment variable to `"false"` or `"true"`. + * Otherwise, the default value is `false`. */ - EXPERIMENTAL_useProjectService?: boolean | ProjectServiceOptions; + disallowAutomaticSingleRunInference?: boolean; /** - * ***EXPERIMENTAL FLAG*** - Use this at your own risk. - * - * Causes TS to use the source files for referenced projects instead of the compiled .d.ts files. - * This feature is not yet optimized, and is likely to cause OOMs for medium to large projects. - * - * This flag REQUIRES at least TS v3.9, otherwise it does nothing. - * - * @see https://github.com/typescript-eslint/typescript-eslint/issues/2094 + * Causes the parser to error if the TypeScript compiler returns any unexpected syntax/semantic errors. */ - EXPERIMENTAL_useSourceOfProjectReferenceRedirect?: boolean; + errorOnTypeScriptSyntacticAndSemanticIssues?: boolean; /** * When `project` is provided, this controls the non-standard file extensions which will be parsed. * It accepts an array of file extensions, each preceded by a `.`. + * + * NOTE: When used with {@link projectService}, full project reloads may occur. */ extraFileExtensions?: string[]; @@ -213,6 +232,8 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { * If this is provided, type information will be returned. * * If set to `false`, `null`, or `undefined`, type information will not be returned. + * + * Note that {@link projectService} is now preferred. */ project?: string[] | string | boolean | null; @@ -225,6 +246,11 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { */ projectFolderIgnoreList?: string[]; + /** + * Whether to create a shared TypeScript project service to power program creation. + */ + projectService?: boolean | ProjectServiceOptions; + /** * The absolute path to the root directory for all provided `project`s. */ @@ -236,45 +262,6 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { * All linted files must be part of the provided program(s). */ programs?: Program[]; - - /** - * @deprecated - this flag will be removed in the next major. - * Do not rely on the behavior provided by this flag. - */ - DEPRECATED__createDefaultProgram?: boolean; - - /** - * ESLint (and therefore typescript-eslint) is used in both "single run"/one-time contexts, - * such as an ESLint CLI invocation, and long-running sessions (such as continuous feedback - * on a file in an IDE). - * - * When typescript-eslint handles TypeScript Program management behind the scenes, this distinction - * is important because there is significant overhead to managing the so called Watch Programs - * needed for the long-running use-case. - * - * When allowAutomaticSingleRunInference is enabled, we will use common heuristics to infer - * whether or not ESLint is being used as part of a single run. - * - * This setting's default value can be specified by setting a `TSESTREE_SINGLE_RUN` - * environment variable to `"false"` or `"true"`. - */ - allowAutomaticSingleRunInference?: boolean; - - /** - * Granular control of the expiry lifetime of our internal caches. - * You can specify the number of seconds as an integer number, or the string - * 'Infinity' if you never want the cache to expire. - * - * By default cache entries will be evicted after 30 seconds, or will persist - * indefinitely if `allowAutomaticSingleRunInference = true` AND the parser - * infers that it is a single run. - */ - cacheLifetime?: { - /** - * Glob resolution for `parserOptions.project` values. - */ - glob?: number | 'Infinity'; - }; } /** @@ -284,7 +271,7 @@ interface ProjectServiceOptions { /** * Globs of files to allow running with the default project compiler options. */ - allowDefaultProjectForFiles?: string[]; + allowDefaultProject?: string[]; /** * Path to a TSConfig to use instead of TypeScript's default project configuration. @@ -292,7 +279,7 @@ interface ProjectServiceOptions { defaultProject?: string; /** - * The maximum number of files {@link allowDefaultProjectForFiles} may match. + * The maximum number of files {@link allowDefaultProject} may match. * Each file match slows down linting, so if you do need to use this, please * file an informative issue on typescript-eslint explaining why - so we can * help you avoid using it! @@ -382,3 +369,7 @@ const { ast, services } = parseAndGenerateServices(code, { If you encounter a bug with the parser that you want to investigate, you can turn on the debug logging via setting the environment variable: `DEBUG=typescript-eslint:*`. I.e. in this repo you can run: `DEBUG=typescript-eslint:* yarn lint`. + +This will include TypeScript server logs. +To turn off these logs, include `-typescript-eslint:typescript-estree:tsserver:*` when setting the environment variable. +I.e. for this repo change to: `DEBUG='typescript-eslint:*,-typescript-eslint:typescript-estree:tsserver:*' yarn lint`. diff --git a/docs/troubleshooting/faqs/Frameworks.mdx b/docs/troubleshooting/faqs/Frameworks.mdx index e364ff38c3ec..fc0e559b56fa 100644 --- a/docs/troubleshooting/faqs/Frameworks.mdx +++ b/docs/troubleshooting/faqs/Frameworks.mdx @@ -11,6 +11,10 @@ import TabItem from '@theme/TabItem'; You can use `parserOptions.extraFileExtensions` to specify an array of non-TypeScript extensions to allow, for example: +:::note +See [Changes to `extraFileExtensions` with `projectService`](../typed-linting/Performance.mdx#changes-to-extrafileextensions-with-projectservice) to avoid performance issues. +::: + @@ -20,10 +24,10 @@ export default tseslint.config( { languageOptions: { parserOptions: { - tsconfigRootDir: import.meta.dirname, - project: ['./tsconfig.json'], // Add this line extraFileExtensions: ['.vue'], + projectService: true, + tsconfigRootDir: import.meta.dirname, }, }, }, @@ -37,10 +41,10 @@ export default tseslint.config( module.exports = { // ... the rest of your config ... parserOptions: { - tsconfigRootDir: __dirname, - project: ['./tsconfig.json'], // Add this line extraFileExtensions: ['.vue'], + projectService: true, + tsconfigRootDir: __dirname, }, }; ``` diff --git a/docs/troubleshooting/typed-linting/Monorepos.mdx b/docs/troubleshooting/typed-linting/Monorepos.mdx index eb95d674084f..5924978b9947 100644 --- a/docs/troubleshooting/typed-linting/Monorepos.mdx +++ b/docs/troubleshooting/typed-linting/Monorepos.mdx @@ -7,13 +7,15 @@ title: Monorepo Configuration import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -If you're using a monorepo with `parserOptions.project`, these docs will help you figure out how to setup typed linting. -If you don't want to use typed linting, then you can stop here - you don't need to do anything special. - :::tip -The [new "project service" (`parserOptions.projectService`) in v8](/blog/announcing-typescript-eslint-v8-beta#project-service) requires no additional configuration for monorepos. +**The [new "project service" in v8](/blog/announcing-typescript-eslint-v8-beta#project-service) requires no additional configuration for monorepos.** + +If you're using `parserOptions.projectService`, you don't need this guide. ::: +If you're using a monorepo with `parserOptions.project`, these docs will help you figure out how to setup typed linting. +If you don't want to use typed linting, then you can stop here - you don't need to do anything special. + `parserOptions.project` configurations will look different based on which monorepo setup you use: 1. [One root `tsconfig.json`](#one-root-tsconfigjson) diff --git a/docs/troubleshooting/typed-linting/Performance.mdx b/docs/troubleshooting/typed-linting/Performance.mdx index 2264ad787c98..48407d5db6cd 100644 --- a/docs/troubleshooting/typed-linting/Performance.mdx +++ b/docs/troubleshooting/typed-linting/Performance.mdx @@ -35,9 +35,118 @@ Additionally, if you provide no `include` in your tsconfig, then it is the same Wide globs can cause TypeScript to parse things like build artifacts, which can heavily impact performance. Always ensure you provide globs targeted at the folders you are specifically wanting to lint. -## Wide includes in your ESLint options +## Project Service Issues -Specifying `tsconfig.json` paths in your ESLint commands is also likely to cause much more disk IO than expected. +### Changes to `extraFileExtensions` with `projectService` + +Using a different [`extraFileExtensions`](../../packages/Parser.mdx#extrafileextensions) between files in the same project with +the [`projectService`](../../packages/Parser.mdx#projectservice) option may cause performance degradations. +For every file linted, we update the `projectService` whenever `extraFileExtensions` changes. +This causes the underlying TypeScript server to perform a full project reload. + + + + +```js title="eslint.config.js" +// @ts-check + +import tseslint from 'typescript-eslint'; +import vueParser from 'vue-eslint-parser'; + +// Add this line +const extraFileExtensions = ['.vue']; +export default [ + { + files: ['*.ts'], + languageOptions: { + parser: tseslint.parser, + parserOptions: { + projectService: true, + // Add this line + extraFileExtensions, + }, + }, + }, + { + files: ['*.vue'], + languageOptions: { + parser: vueParser, + parserOptions: { + projectService: true, + parser: tseslint.parser, + // Remove this line + extraFileExtensions: ['.vue'], + // Add this line + extraFileExtensions, + }, + }, + }, +]; +``` + + + + +```js title=".eslintrc.js" +// Add this line +const extraFileExtensions = ['.vue']; +module.exports = { + files: ['*.ts'], + parser: '@typescript-eslint/parser', + parserOptions: { + projectService: true, + // Add this line + extraFileExtensions, + }, + overrides: [ + { + files: ['*.vue'], + parser: 'vue-eslint-parser', + parserOptions: { + parser: '@typescript-eslint/parser', + projectService: true, + // Remove this line + extraFileExtensions: ['.vue'], + // Add this line + extraFileExtensions, + }, + }, + ], +}; +``` + + + + +Project reloads can be observed using the [debug environment variable](../../packages/typescript-estree/#debugging): `DEBUG='typescript-eslint:typescript-estree:*'`. + +``` +typescript-estree:useProgramFromProjectService Updating extra file extensions: before=[]: after=[ '.vue' ] +typescript-estree:tsserver:info reload projects. +typescript-estree:useProgramFromProjectService Extra file extensions updated: [ '.vue' ] +... +typescript-estree:useProgramFromProjectService Updating extra file extensions: before=[ '.vue' ]: after=[] +typescript-estree:tsserver:info reload projects. +typescript-estree:useProgramFromProjectService Extra file extensions updated: [] +... +typescript-estree:tsserver:info Scheduled: /path/to/tsconfig.src.json, Cancelled earlier one +0ms +typescript-estree:tsserver:info Scheduled: *ensureProjectForOpenFiles*, Cancelled earlier one +0ms +... +typescript-estree:useProgramFromProjectService Updating extra file extensions: before=[]: after=[ '.vue' ] +typescript-estree:tsserver:info reload projects. +typescript-estree:useProgramFromProjectService Extra file extensions updated: [ '.vue' ] +``` + +## Traditional Project issues + +### Wide includes in your ESLint options + +:::tip +The [new "project service" in v8](/blog/announcing-typescript-eslint-v8-beta#project-service) requires no additional configuration for wide TSConfig includes. +If you're using `parserOptions.projectService`, this problem is solved for you. +::: + +Specifying `tsconfig.json` paths in an ESLint `parserOptions.project` configuration is also likely to cause much more disk IO than expected. Instead of globs that use `**` to recursively check all folders, prefer paths that use a single `*` at a time. @@ -93,9 +202,11 @@ module.exports = { See [Glob pattern in parser's option "project" slows down linting](https://github.com/typescript-eslint/typescript-eslint/issues/2611) for more details. -## The `indent` / `@typescript-eslint/indent` rules +## Third-Party Plugins + +### `@stylistic/ts/indent` and other stylistic rules rules -This rule helps ensure your codebase follows a consistent indentation pattern. +The [`@stylisic/ts/indent` rule](https://eslint.style/rules/ts/indent#ts-indent) helps ensure your codebase follows a consistent indentation pattern. However this involves a _lot_ of computations across every single token in a file. Across a large codebase, these can add up, and severely impact performance. @@ -103,7 +214,7 @@ We recommend not using this rule, and instead using a tool like [`prettier`](htt See our [documentation on formatting](../../users/What_About_Formatting.mdx) for more information. -## `eslint-plugin-prettier` +### `eslint-plugin-prettier` This plugin surfaces Prettier formatting problems at lint time, helping to ensure your code is always formatted. However this comes at a quite a large cost - in order to figure out if there is a difference, it has to do a Prettier format on every file being linted. @@ -119,7 +230,7 @@ npm run prettier --check . See [Prettier's `--check` docs](https://prettier.io/docs/en/cli#--check) for more details. -## `eslint-plugin-import` +### `eslint-plugin-import` This is another great plugin that we use ourselves in this project. However there are a few rules which can cause your lints to be really slow, because they cause the plugin to do its own parsing, and file tracking. @@ -142,15 +253,13 @@ The following rules do not have equivalent checks in TypeScript, so we recommend - `import/no-unused-modules` - `import/no-deprecated` -### `import/extensions` - -#### Enforcing extensions are used +#### `import/extensions` enforcing extensions are used If you want to enforce file extensions are always used and you're **NOT** using `moduleResolution` `node16` or `nodenext`, then there's not really a good alternative for you, and you should continue using the `import/extensions` lint rule. If you want to enforce file extensions are always used and you **ARE** using `moduleResolution` `node16` or `nodenext`, then you don't need to use the lint rule at all because TypeScript will automatically enforce that you include extensions! -#### Enforcing extensions are not used +#### `import/extensions` enforcing extensions are not used On the surface `import/extensions` seems like it should be fast for this use case, however the rule isn't just a pure AST-check - it has to resolve modules on disk so that it doesn't false positive on cases where you are importing modules with an extension as part of their name (eg `foo.js` resolves to `node_modules/foo.js/index.js`, so the `.js` is required). This disk lookup is costly and thus makes the rule slow. diff --git a/docs/troubleshooting/typed-linting/index.mdx b/docs/troubleshooting/typed-linting/index.mdx index 8d7aa142a9f2..05e0280d94da 100644 --- a/docs/troubleshooting/typed-linting/index.mdx +++ b/docs/troubleshooting/typed-linting/index.mdx @@ -20,86 +20,118 @@ For now, the workaround is to run the _**Restart ESLint Server**_ command in VS See [ESLint does not re-compute cross-file information on file changes (microsoft/vscode-eslint#1774)](https://github.com/microsoft/vscode-eslint/issues/1774) for more information. - +## How do I disable type-checked linting for a file? -## I get errors telling me "Having many files run with the default project is known to cause performance issues and slow down linting." +Use [ESLint's configuration objects](https://eslint.org/docs/latest/use/configure/configuration-files#specifying-files-with-arbitrary-extensions) with our [`disable-type-checked`](../../users/Shared_Configurations.mdx#disable-type-checked) config to disable type checking for a `files` match that includes that file. -These errors are caused by using the [`EXPERIMENTAL_useProjectService`](../../packages/Parser.mdx#experimental_useprojectservice) `allowDefaultProjectForFiles` with an excessively wide glob. -`allowDefaultProjectForFiles` causes a new TypeScript "program" to be built for each "out of project" file it includes, which incurs a performance overhead for each file. +For example, to disable type-checked linting on all `.js` files: -To resolve this error, narrow the glob(s) used for `allowDefaultProjectForFiles` to include fewer files. -For example: + + -```diff title="eslint.config.mjs" -parserOptions: { - EXPERIMENTAL_useProjectService: { - allowDefaultProjectForFiles: [ -- "**/*.js", -+ "./*.js" - ] - } -} +```js title="eslint.config.mjs" +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + // ... the rest of your config ... + { + files: ['**/*.js'], + extends: [tseslint.configs.disableTypeChecked], + }, +); ``` -You may also need to include more files in your `tsconfig.json`. -For example: + + + +```js title=".eslintrc.cjs" +module.exports = { + // ... the rest of your config ... + overrides: [ + { + extends: ['plugin:@typescript-eslint/disable-type-checked'], + files: ['./**/*.js'], + }, + ], +}; +``` + + + + +Alternatively to disable type checking for files manually, you can set [`parserOptions: { project: false }`](../../packages/Parser.mdx#project) to an override for the files you wish to exclude. + +## typescript-eslint thinks my variable is never nullish / is `any` / etc., but that is clearly not the case to me + +Our type-aware rules almost always trust the type information provided by the TypeScript compiler. Therefore, an easy way to check if our rule is behaving correctly is to inspect the type of the variable in question, such as by hovering over it in your IDE. + +If the IDE also shows that the type is never nullish / is `any`, you need to fix the type. A very common case is with the [`no-unnecessary-condition`](/rules/no-unnecessary-condition) rule. Take this code for example: + +```ts +let condition = false; -```diff title="tsconfig.json" -"include": [ - "src", -+ "*.js" -] +const f = () => (condition = true); +f(); + +if (condition) { + //^^^^^^^^^ Unnecessary conditional, value is always falsy. +} ``` +You can see that the type of `condition` is actually the literal type `false` by hovering over it in your IDE. In this case, typescript-eslint cannot possible know better than TypeScript itself, so you need to fix the report by fixing the type, such as through an assertion (`let condition = false as boolean`). + +If the IDE provides different type information from typescript-eslint's report, then make sure that the TypeScript setup used for your IDE, typescript-eslint, and `tsc` are the same: the same TypeScript version, the same type-checking compiler options, and the same files being included in the project. For example, if a type is declared in another file but that file is not included, the type will become `any`, and cause our `no-unsafe-*` rules to report. + +## Are TypeScript project references supported? + +Yes, but only with [`EXPERIMENTAL_useProjectService`](../../packages/Parser.mdx#experimental_useprojectservice). + +See [issue #2094 discussing project references](https://github.com/typescript-eslint/typescript-eslint/issues/2094) for more details. + +## Project Service Issues + + + + +### I get errors telling me "Having many files run with the default project is known to cause performance issues and slow down linting." + +These errors are caused by attempting to use the [`projectService`](../../packages/Parser.mdx#projectservice) to lint a file not explicitly included in a `tsconfig.json`. + +For each file being reported: + +- If you **do not** want to lint the file: + - Use [one of the options ESLint offers to ignore files](https://eslint.org/docs/latest/user-guide/configuring/ignoring-code), such an `ignores` config key. +- If you **do** want to lint the file: + - If you **do not** want to lint the file with [type-aware linting](../../getting-started/Typed_Linting.mdx): [disable type-checked linting for that file](#how-do-i-disable-type-checked-linting-for-a-file). + - If you **do** want to lint the file with [type-aware linting](../../getting-started/Typed_Linting.mdx): + 1. If possible, add the file to the closest `tsconfig.json`'s `include`. For example, allowing `.js` files: + ```diff title="tsconfig.json" + "include": [ + "src", + + "*.js" + ] + ``` + 2. Otherwise, consider setting [`parserOptions.createProjectService.allowDefaultProject`](../../packages/parser#allowdefaultproject). + +typescript-eslint allows up to 8 "out of project" files by default. +Each file causes a new TypeScript "program" to be built for each file it includes, which incurs a performance overhead _for each file_. + If you cannot do this, please [file an issue on typescript-eslint's typescript-estree package](https://github.com/typescript-eslint/typescript-eslint/issues/new?assignees=&labels=enhancement%2Ctriage&projects=&template=07-enhancement-other.yaml&title=Enhancement%3A+%3Ca+short+description+of+my+proposal%3E) telling us your use case and why you need more out-of-project files linted. Be sure to include a minimal reproduction we can work with to understand your use case! -## I get errors telling me "ESLint was configured to run ... However, that TSConfig does not / none of those TSConfigs include this file" +## Traditional Project Issues -These errors are caused by an ESLint config requesting type information be generated for a file that isn't included in the TypeScript configuration. +### I get errors telling me "ESLint was configured to run ... However, that TSConfig does not / none of those TSConfigs include this file" -### Fixing the Error +These errors are caused by an ESLint config requesting type information be generated for a file that isn't included in the TypeScript configuration. - +#### Fixing the Error - If you **do not** want to lint the file: - - Use [one of the options ESLint offers](https://eslint.org/docs/latest/user-guide/configuring/ignoring-code) to ignore files, namely a `.eslintignore` file, or `ignorePatterns` config. + - Use [one of the options ESLint offers to ignore files](https://eslint.org/docs/latest/user-guide/configuring/ignoring-code), namely a `.eslintignore` file, or `ignorePatterns` config. - If you **do** want to lint the file: - If you **do not** want to lint the file with [type-aware linting](../../getting-started/Typed_Linting.mdx): - - Use [ESLint's `overrides` configuration](https://eslint.org/docs/latest/user-guide/configuring/configuration-files#configuration-based-on-glob-patterns) with our [`disable-type-checked`](../../users/Shared_Configurations.mdx#disable-type-checked) config to disable type checking for just that type of file. - - - - ```js title="eslint.config.mjs" - import tseslint from 'typescript-eslint'; - - export default tseslint.config( - // ... the rest of your config ... - { - files: ['**/*.js'], - extends: [tseslint.configs.disableTypeChecked], - }, - ); - ``` - - - - - ```js title=".eslintrc.cjs" - module.exports = { - // ... the rest of your config ... - overrides: [ - { - extends: ['plugin:@typescript-eslint/disable-type-checked'], - files: ['./**/*.js'], - }, - ], - }; - ``` - - - - - Alternatively to disable type checking for files manually, you can set `parserOptions: { project: false }` to an override for the files you wish to exclude. + - Use [ESLint's configuration objects](https://eslint.org/docs/latest/use/configure/configuration-files#specifying-files-with-arbitrary-extensions) with our [`disable-type-checked`](../../users/Shared_Configurations.mdx#disable-type-checked) config to disable type checking for just that type of file. - If you **do** want to lint the file with [type-aware linting](../../getting-started/Typed_Linting.mdx): - Check the `include` option of each of the TSConfigs that you provide to `parserOptions.project` - you must ensure that all files match an `include` glob, or else our tooling will not be able to find it. - If the file is a `.cjs`, `.js`, or `.mjs` file, make sure [`allowJs`](https://www.typescriptlang.org/tsconfig#allowJs) is enabled. @@ -107,9 +139,7 @@ These errors are caused by an ESLint config requesting type information be gener - [`tsconfig.eslint.json`](https://github.com/typescript-eslint/typescript-eslint/blob/main/tsconfig.eslint.json) - [`eslint.config.mjs`](https://github.com/typescript-eslint/typescript-eslint/blob/main/eslint.config.mjs) - - -### More Details +#### More Details This error may appear from the combination of two things: @@ -123,75 +153,14 @@ However, if no specified TSConfig includes the source file, the parser won't be This error most commonly happens on config files or similar that are not included in their project TSConfig(s). For example, many projects have files like: -- An `.eslintrc.cjs` / `eslint.config.mjs` with `parserOptions.project: ["./tsconfig.json"]` +- An `.eslintrc.cjs` / `eslint.config.mjs` with `parserOptions.project: true` - A `tsconfig.json` with `include: ["src"]` In that case, viewing the file in an IDE with the ESLint extension will show the error notice that the file couldn't be linted because it isn't included in `tsconfig.json`. See our docs on [type aware linting](../../getting-started/Typed_Linting.mdx) for more information. -## I get errors telling me "The file must be included in at least one of the projects provided" +### I get errors telling me "The file must be included in at least one of the projects provided" You're using an outdated version of `@typescript-eslint/parser`. Update to the latest version to see a more informative version of this error message, explained [above](#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file 'backlink to I get errors telling me ESLint was configured to run ...'). - - - -## I get errors telling me "Having many files run with the default project is known to cause performance issues and slow down linting." - -These errors are caused by using the [`EXPERIMENTAL_useProjectService`](../../packages/Parser.mdx#experimental_useprojectservice) `allowDefaultProjectForFiles` with an excessively wide glob. -`allowDefaultProjectForFiles` causes a new TypeScript "program" to be built for each "out of project" file it includes, which incurs a performance overhead for each file. - -To resolve this error, narrow the glob(s) used for `allowDefaultProjectForFiles` to include fewer files. -For example: - -```diff title="eslint.config.mjs" -parserOptions: { - EXPERIMENTAL_useProjectService: { - allowDefaultProjectForFiles: [ -- "**/*.js", -+ "./*.js" - ] - } -} -``` - -You may also need to include more files in your `tsconfig.json`. -For example: - -```diff title="tsconfig.json" -"include": [ - "src", -+ "*.js" -] -``` - -If you cannot do this, please [file an issue on typescript-eslint's typescript-estree package](https://github.com/typescript-eslint/typescript-eslint/issues/new?assignees=&labels=enhancement%2Ctriage&projects=&template=07-enhancement-other.yaml&title=Enhancement%3A+%3Ca+short+description+of+my+proposal%3E) telling us your use case and why you need more out-of-project files linted. -Be sure to include a minimal reproduction we can work with to understand your use case! - -## typescript-eslint thinks my variable is never nullish / is `any` / etc., but that is clearly not the case to me - -Our type-aware rules almost always trust the type information provided by the TypeScript compiler. Therefore, an easy way to check if our rule is behaving correctly is to inspect the type of the variable in question, such as by hovering over it in your IDE. - -If the IDE also shows that the type is never nullish / is `any`, you need to fix the type. A very common case is with the [`no-unnecessary-condition`](/rules/no-unnecessary-condition) rule. Take this code for example: - -```ts -let condition = false; - -const f = () => (condition = true); -f(); - -if (condition) { - //^^^^^^^^^ Unnecessary conditional, value is always falsy. -} -``` - -You can see that the type of `condition` is actually the literal type `false` by hovering over it in your IDE. In this case, typescript-eslint cannot possible know better than TypeScript itself, so you need to fix the report by fixing the type, such as through an assertion (`let condition = false as boolean`). - -If the IDE provides different type information from typescript-eslint's report, then make sure that the TypeScript setup used for your IDE, typescript-eslint, and `tsc` are the same: the same TypeScript version, the same type-checking compiler options, and the same files being included in the project. For example, if a type is declared in another file but that file is not included, the type will become `any`, and cause our `no-unsafe-*` rules to report. - -## Are TypeScript project references supported? - -Yes, but only with [`EXPERIMENTAL_useProjectService`](../../packages/Parser.mdx#experimental_useprojectservice). - -See [issue #2094 discussing project references](https://github.com/typescript-eslint/typescript-eslint/issues/2094) for more details. diff --git a/eslint.config.mjs b/eslint.config.mjs index 7715e4ddbdd9..6022344465a5 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -2,6 +2,7 @@ import url from 'node:url'; +import { fixupConfigRules, fixupPluginRules } from '@eslint/compat'; import { FlatCompat } from '@eslint/eslintrc'; import eslint from '@eslint/js'; import tseslintInternalPlugin from '@typescript-eslint/eslint-plugin-internal'; @@ -30,15 +31,20 @@ export default tseslint.config( plugins: { ['@typescript-eslint']: tseslint.plugin, ['@typescript-eslint/internal']: tseslintInternalPlugin, - ['deprecation']: deprecationPlugin, + // https://github.com/gund/eslint-plugin-deprecation/issues/78 + // https://github.com/typescript-eslint/typescript-eslint/issues/8988 + ['deprecation']: fixupPluginRules(deprecationPlugin), ['eslint-comments']: eslintCommentsPlugin, ['eslint-plugin']: eslintPluginPlugin, - ['import']: importPlugin, + // https://github.com/import-js/eslint-plugin-import/issues/2948 + ['import']: fixupPluginRules(importPlugin), ['jest']: jestPlugin, ['jsdoc']: jsdocPlugin, ['jsx-a11y']: jsxA11yPlugin, - ['react-hooks']: reactHooksPlugin, - ['react']: reactPlugin, + // https://github.com/facebook/react/issues/28313 + ['react-hooks']: fixupPluginRules(reactHooksPlugin), + // https://github.com/jsx-eslint/eslint-plugin-react/issues/3699 + ['react']: fixupPluginRules(reactPlugin), ['simple-import-sort']: simpleImportSortPlugin, ['unicorn']: unicornPlugin, }, @@ -47,6 +53,8 @@ export default tseslint.config( { // config with just ignores is the replacement for `.eslintignore` ignores: [ + '.nx/', + '.yarn/', '**/jest.config.js', '**/node_modules/**', '**/dist/**', @@ -60,9 +68,9 @@ export default tseslint.config( // Files copied as part of the build 'packages/types/src/generated/**/*.ts', // Playground types downloaded from the web - 'packages/website/src/vendor', + 'packages/website/src/vendor/', // see the file header in eslint-base.test.js for more info - 'packages/rule-tester/tests/eslint-base', + 'packages/rule-tester/tests/eslint-base/', ], }, @@ -80,25 +88,7 @@ export default tseslint.config( ...globals.node, }, 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.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', - ], + projectService: true, tsconfigRootDir: __dirname, warnOnUnsupportedTypeScriptVersion: false, }, @@ -179,6 +169,12 @@ export default tseslint.config( ignorePrimitives: true, }, ], + '@typescript-eslint/no-require-imports': [ + 'error', + { + allow: ['/package\\.json$'], + }, + ], // // Internal repo rules @@ -223,6 +219,7 @@ export default tseslint.config( 'operator-assignment': 'error', 'prefer-arrow-callback': 'error', 'prefer-const': 'error', + 'prefer-object-has-own': 'error', 'prefer-object-spread': 'error', 'prefer-rest-params': 'error', radix: 'error', @@ -323,6 +320,7 @@ export default tseslint.config( 'jsdoc/informative-docs': 'error', 'unicorn/no-typeof-undefined': 'error', + 'unicorn/no-useless-spread': 'error', }, }, { @@ -449,7 +447,14 @@ export default tseslint.config( 'packages/eslint-plugin/src/rules/**/*.{ts,tsx,cts,mts}', ], rules: { - 'eslint-plugin/no-property-in-node': 'error', + 'eslint-plugin/no-property-in-node': [ + 'error', + { + additionalNodeTypeFiles: [ + 'packages[\\/]types[\\/]src[\\/]generated[\\/]ast-spec.ts', + ], + }, + ], 'eslint-plugin/require-meta-docs-description': [ 'error', { pattern: '^(Enforce|Require|Disallow) .+[^. ]$' }, @@ -529,8 +534,8 @@ export default tseslint.config( files: ['packages/website/**/*.{ts,tsx,mts,cts,js,jsx}'], extends: [ ...compat.config(jsxA11yPlugin.configs.recommended), - ...compat.config(reactPlugin.configs.recommended), - ...compat.config(reactHooksPlugin.configs.recommended), + ...fixupConfigRules(compat.config(reactPlugin.configs.recommended)), + ...fixupConfigRules(compat.config(reactHooksPlugin.configs.recommended)), ], rules: { '@typescript-eslint/internal/prefer-ast-types-enum': 'off', @@ -538,6 +543,7 @@ export default tseslint.config( 'react/jsx-no-target-blank': 'off', 'react/no-unescaped-entities': 'off', 'react-hooks/exhaustive-deps': 'warn', // TODO: enable it later + 'react/prop-types': 'off', }, settings: { react: { diff --git a/package.json b/package.json index 2fa12ac2dd15..226c4f7fa5c0 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "typecheck": "nx run-many --target=typecheck" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "devDependencies": { "@actions/core": "^1.10.1", @@ -60,6 +60,7 @@ "@babel/eslint-parser": "^7.24.1", "@babel/parser": "^7.24.4", "@babel/types": "^7.24.0", + "@eslint/compat": "^1.0.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "^8.57.0", "@jest/types": "29.6.3", @@ -91,10 +92,10 @@ "cross-fetch": "^4.0.0", "cspell": "^8.6.1", "downlevel-dts": ">=0.11.0", - "eslint": "8.57.0", + "eslint": "^9.3.0", "eslint-plugin-deprecation": "^2.0.0", "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-eslint-plugin": "^5.5.0", + "eslint-plugin-eslint-plugin": "^6.2.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-jest": "^27.9.0", "eslint-plugin-jsdoc": "^47.0.2", @@ -121,7 +122,7 @@ "semver": "7.6.2", "tmp": "^0.2.1", "tsx": "*", - "typescript": ">=4.7.4 <5.6.0", + "typescript": ">=4.8.4 <5.6.0", "typescript-eslint": "workspace:^", "yargs": "17.7.2" }, @@ -135,7 +136,7 @@ "@types/estree": "link:./tools/dummypkg", "@types/node": "^20.0.0", "@types/react": "^18.2.14", - "eslint": "8.57.0", + "eslint": "^9", "eslint-visitor-keys": "^3.4.1", "jest-config": "^29", "jest-resolve": "^29", diff --git a/packages/ast-spec/CHANGELOG.md b/packages/ast-spec/CHANGELOG.md index 7f9c282c5ec0..5acf15cc100b 100644 --- a/packages/ast-spec/CHANGELOG.md +++ b/packages/ast-spec/CHANGELOG.md @@ -1,3 +1,46 @@ +## 8.0.1 (2024-08-05) + +This was a version bump only for ast-spec 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. + +# 8.0.0 (2024-07-31) + + +### 🚀 Features + +- stricter parent types for the AST + +- **typescript-estree:** split TSMappedType typeParameter into constraint and key + +- **ast-spec:** remove deprecated type params + +- **typescript-estree:** stabilize EXPERIMENTAL_useProjectService as projectService + + +### 🩹 Fixes + +- **typescript-estree:** add TSEnumBody node for TSEnumDeclaration body + + +### ❤️ Thank You + +- Abraham Guo +- Alfred Ringstad +- auvred +- Brad Zacher +- Christopher Aubut +- Collin Bachman +- James Henry +- Josh Goldberg +- Josh Goldberg ✨ +- Kirk Waiblinger +- StyleShit +- Victor Lin +- Yukihiro Hasegawa + +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.18.0 (2024-07-29) This was a version bump only for ast-spec to align it with other projects, there were no code changes. diff --git a/packages/ast-spec/package.json b/packages/ast-spec/package.json index 95726cbe3a7a..52a3b3343818 100644 --- a/packages/ast-spec/package.json +++ b/packages/ast-spec/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/ast-spec", - "version": "7.18.0", + "version": "8.0.1", "description": "Complete specification for the TypeScript-ESTree AST", "private": true, "keywords": [ @@ -9,7 +9,7 @@ "estree" ], "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "files": [ "dist", diff --git a/packages/ast-spec/src/ast-node-types.ts b/packages/ast-spec/src/ast-node-types.ts index 050bc6f61f34..ea4b3207f116 100644 --- a/packages/ast-spec/src/ast-node-types.ts +++ b/packages/ast-spec/src/ast-node-types.ts @@ -110,6 +110,7 @@ export enum AST_NODE_TYPES { TSDeclareKeyword = 'TSDeclareKeyword', TSEmptyBodyFunctionExpression = 'TSEmptyBodyFunctionExpression', TSEnumDeclaration = 'TSEnumDeclaration', + TSEnumBody = 'TSEnumBody', TSEnumMember = 'TSEnumMember', TSExportAssignment = 'TSExportAssignment', TSExportKeyword = 'TSExportKeyword', diff --git a/packages/ast-spec/src/base/ClassBase.ts b/packages/ast-spec/src/base/ClassBase.ts index 34f6d2a49e41..d58d4aff9942 100644 --- a/packages/ast-spec/src/base/ClassBase.ts +++ b/packages/ast-spec/src/base/ClassBase.ts @@ -56,10 +56,6 @@ export interface ClassBase extends BaseNode { * The generic type parameters passed to the superClass. */ superTypeArguments: TSTypeParameterInstantiation | undefined; - - /** @deprecated Use {@link `superTypeArguments`} instead. */ - superTypeParameters: TSTypeParameterInstantiation | undefined; - /** * The generic type parameters declared for the class. */ diff --git a/packages/ast-spec/src/base/TSHeritageBase.ts b/packages/ast-spec/src/base/TSHeritageBase.ts index dc7f701f0f1a..8ff7e3a361f8 100644 --- a/packages/ast-spec/src/base/TSHeritageBase.ts +++ b/packages/ast-spec/src/base/TSHeritageBase.ts @@ -6,7 +6,4 @@ export interface TSHeritageBase extends BaseNode { // TODO(#1852) - this should be restricted to MemberExpression | Identifier expression: Expression; typeArguments: TSTypeParameterInstantiation | undefined; - - /** @deprecated Use {@link `typeArguments`} instead. */ - typeParameters: TSTypeParameterInstantiation | undefined; } diff --git a/packages/ast-spec/src/declaration/ExportNamedDeclaration/fixtures/enum/snapshots/1-TSESTree-AST.shot b/packages/ast-spec/src/declaration/ExportNamedDeclaration/fixtures/enum/snapshots/1-TSESTree-AST.shot index f3dff0f70ee9..f8860c0534d2 100644 --- a/packages/ast-spec/src/declaration/ExportNamedDeclaration/fixtures/enum/snapshots/1-TSESTree-AST.shot +++ b/packages/ast-spec/src/declaration/ExportNamedDeclaration/fixtures/enum/snapshots/1-TSESTree-AST.shot @@ -9,6 +9,16 @@ Program { attributes: [], declaration: TSEnumDeclaration { type: "TSEnumDeclaration", + body: TSEnumBody { + type: "TSEnumBody", + members: [], + + range: [16, 18], + loc: { + start: { column: 16, line: 1 }, + end: { column: 18, line: 1 }, + }, + }, const: false, declare: false, id: Identifier { @@ -23,7 +33,6 @@ Program { end: { column: 15, line: 1 }, }, }, - members: [], range: [7, 18], loc: { diff --git a/packages/ast-spec/src/declaration/ExportNamedDeclaration/fixtures/enum/snapshots/5-AST-Alignment-AST.shot b/packages/ast-spec/src/declaration/ExportNamedDeclaration/fixtures/enum/snapshots/5-AST-Alignment-AST.shot index 0f83a6c9f506..9aa4daf97447 100644 --- a/packages/ast-spec/src/declaration/ExportNamedDeclaration/fixtures/enum/snapshots/5-AST-Alignment-AST.shot +++ b/packages/ast-spec/src/declaration/ExportNamedDeclaration/fixtures/enum/snapshots/5-AST-Alignment-AST.shot @@ -14,6 +14,16 @@ exports[`AST Fixtures declaration ExportNamedDeclaration enum AST Alignment - AS + assertions: Array [], declaration: TSEnumDeclaration { type: 'TSEnumDeclaration', +- body: TSEnumBody { +- type: 'TSEnumBody', +- members: Array [], +- +- range: [16, 18], +- loc: { +- start: { column: 16, line: 1 }, +- end: { column: 18, line: 1 }, +- }, +- }, - const: false, - declare: false, id: Identifier { @@ -28,7 +38,7 @@ exports[`AST Fixtures declaration ExportNamedDeclaration enum AST Alignment - AS end: { column: 15, line: 1 }, }, }, - members: Array [], ++ members: Array [], range: [7, 18], loc: { diff --git a/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/const/snapshots/1-TSESTree-AST.shot b/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/const/snapshots/1-TSESTree-AST.shot index 45c1e395be04..30fdcfbc6815 100644 --- a/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/const/snapshots/1-TSESTree-AST.shot +++ b/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/const/snapshots/1-TSESTree-AST.shot @@ -6,6 +6,16 @@ Program { body: [ TSEnumDeclaration { type: "TSEnumDeclaration", + body: TSEnumBody { + type: "TSEnumBody", + members: [], + + range: [15, 17], + loc: { + start: { column: 15, line: 1 }, + end: { column: 17, line: 1 }, + }, + }, const: true, declare: false, id: Identifier { @@ -20,7 +30,6 @@ Program { end: { column: 14, line: 1 }, }, }, - members: [], range: [0, 17], loc: { diff --git a/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/const/snapshots/5-AST-Alignment-AST.shot b/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/const/snapshots/5-AST-Alignment-AST.shot index c9480e42c3a4..a0fe24cb4d5c 100644 --- a/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/const/snapshots/5-AST-Alignment-AST.shot +++ b/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/const/snapshots/5-AST-Alignment-AST.shot @@ -10,6 +10,16 @@ exports[`AST Fixtures declaration TSEnumDeclaration const AST Alignment - AST 1` body: Array [ TSEnumDeclaration { type: 'TSEnumDeclaration', +- body: TSEnumBody { +- type: 'TSEnumBody', +- members: Array [], +- +- range: [15, 17], +- loc: { +- start: { column: 15, line: 1 }, +- end: { column: 17, line: 1 }, +- }, +- }, const: true, - declare: false, id: Identifier { @@ -24,7 +34,7 @@ exports[`AST Fixtures declaration TSEnumDeclaration const AST Alignment - AST 1` end: { column: 14, line: 1 }, }, }, - members: Array [], ++ members: Array [], range: [0, 17], loc: { diff --git a/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/declare/snapshots/1-TSESTree-AST.shot b/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/declare/snapshots/1-TSESTree-AST.shot index b4e7d92295e5..e1f611ebd1ba 100644 --- a/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/declare/snapshots/1-TSESTree-AST.shot +++ b/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/declare/snapshots/1-TSESTree-AST.shot @@ -6,6 +6,16 @@ Program { body: [ TSEnumDeclaration { type: "TSEnumDeclaration", + body: TSEnumBody { + type: "TSEnumBody", + members: [], + + range: [17, 19], + loc: { + start: { column: 17, line: 1 }, + end: { column: 19, line: 1 }, + }, + }, const: false, declare: true, id: Identifier { @@ -20,7 +30,6 @@ Program { end: { column: 16, line: 1 }, }, }, - members: [], range: [0, 19], loc: { diff --git a/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/declare/snapshots/5-AST-Alignment-AST.shot b/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/declare/snapshots/5-AST-Alignment-AST.shot index 3926e3f88cc8..c942393ad72d 100644 --- a/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/declare/snapshots/5-AST-Alignment-AST.shot +++ b/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/declare/snapshots/5-AST-Alignment-AST.shot @@ -10,6 +10,16 @@ exports[`AST Fixtures declaration TSEnumDeclaration declare AST Alignment - AST body: Array [ TSEnumDeclaration { type: 'TSEnumDeclaration', +- body: TSEnumBody { +- type: 'TSEnumBody', +- members: Array [], +- +- range: [17, 19], +- loc: { +- start: { column: 17, line: 1 }, +- end: { column: 19, line: 1 }, +- }, +- }, - const: false, declare: true, id: Identifier { @@ -24,7 +34,7 @@ exports[`AST Fixtures declaration TSEnumDeclaration declare AST Alignment - AST end: { column: 16, line: 1 }, }, }, - members: Array [], ++ members: Array [], range: [0, 19], loc: { diff --git a/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/empty/snapshots/1-TSESTree-AST.shot b/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/empty/snapshots/1-TSESTree-AST.shot index 2c454bcfcd82..dd8b9928297b 100644 --- a/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/empty/snapshots/1-TSESTree-AST.shot +++ b/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/empty/snapshots/1-TSESTree-AST.shot @@ -6,6 +6,16 @@ Program { body: [ TSEnumDeclaration { type: "TSEnumDeclaration", + body: TSEnumBody { + type: "TSEnumBody", + members: [], + + range: [9, 11], + loc: { + start: { column: 9, line: 1 }, + end: { column: 11, line: 1 }, + }, + }, const: false, declare: false, id: Identifier { @@ -20,7 +30,6 @@ Program { end: { column: 8, line: 1 }, }, }, - members: [], range: [0, 11], loc: { diff --git a/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/empty/snapshots/5-AST-Alignment-AST.shot b/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/empty/snapshots/5-AST-Alignment-AST.shot index 86d2f4844f4a..f6fd91b6e44c 100644 --- a/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/empty/snapshots/5-AST-Alignment-AST.shot +++ b/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/empty/snapshots/5-AST-Alignment-AST.shot @@ -10,6 +10,16 @@ exports[`AST Fixtures declaration TSEnumDeclaration empty AST Alignment - AST 1` body: Array [ TSEnumDeclaration { type: 'TSEnumDeclaration', +- body: TSEnumBody { +- type: 'TSEnumBody', +- members: Array [], +- +- range: [9, 11], +- loc: { +- start: { column: 9, line: 1 }, +- end: { column: 11, line: 1 }, +- }, +- }, - const: false, - declare: false, id: Identifier { @@ -24,7 +34,7 @@ exports[`AST Fixtures declaration TSEnumDeclaration empty AST Alignment - AST 1` end: { column: 8, line: 1 }, }, }, - members: Array [], ++ members: Array [], range: [0, 11], loc: { diff --git a/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/with-member-one/snapshots/1-TSESTree-AST.shot b/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/with-member-one/snapshots/1-TSESTree-AST.shot index 29f98e4e129e..4d9bc63c978a 100644 --- a/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/with-member-one/snapshots/1-TSESTree-AST.shot +++ b/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/with-member-one/snapshots/1-TSESTree-AST.shot @@ -6,6 +6,39 @@ Program { body: [ TSEnumDeclaration { type: "TSEnumDeclaration", + body: TSEnumBody { + type: "TSEnumBody", + members: [ + TSEnumMember { + type: "TSEnumMember", + computed: false, + id: Identifier { + type: "Identifier", + decorators: [], + name: "A", + optional: false, + + range: [13, 14], + loc: { + start: { column: 2, line: 2 }, + end: { column: 3, line: 2 }, + }, + }, + + range: [13, 14], + loc: { + start: { column: 2, line: 2 }, + end: { column: 3, line: 2 }, + }, + }, + ], + + range: [9, 17], + loc: { + start: { column: 9, line: 1 }, + end: { column: 1, line: 3 }, + }, + }, const: false, declare: false, id: Identifier { @@ -20,30 +53,6 @@ Program { end: { column: 8, line: 1 }, }, }, - members: [ - TSEnumMember { - type: "TSEnumMember", - computed: false, - id: Identifier { - type: "Identifier", - decorators: [], - name: "A", - optional: false, - - range: [13, 14], - loc: { - start: { column: 2, line: 2 }, - end: { column: 3, line: 2 }, - }, - }, - - range: [13, 14], - loc: { - start: { column: 2, line: 2 }, - end: { column: 3, line: 2 }, - }, - }, - ], range: [0, 17], loc: { diff --git a/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/with-member-one/snapshots/5-AST-Alignment-AST.shot b/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/with-member-one/snapshots/5-AST-Alignment-AST.shot index 7137394a11bb..85d1319d1313 100644 --- a/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/with-member-one/snapshots/5-AST-Alignment-AST.shot +++ b/packages/ast-spec/src/declaration/TSEnumDeclaration/fixtures/with-member-one/snapshots/5-AST-Alignment-AST.shot @@ -10,29 +10,39 @@ exports[`AST Fixtures declaration TSEnumDeclaration with-member-one AST Alignmen body: Array [ TSEnumDeclaration { type: 'TSEnumDeclaration', -- const: false, -- declare: false, - id: Identifier { - type: 'Identifier', -- decorators: Array [], - name: 'Foo', -- optional: false, +- body: TSEnumBody { +- type: 'TSEnumBody', +- members: Array [ +- TSEnumMember { +- type: 'TSEnumMember', +- computed: false, +- id: Identifier { +- type: 'Identifier', +- decorators: Array [], +- name: 'A', +- optional: false, ++ id: Identifier { ++ type: 'Identifier', ++ name: 'Foo', - range: [5, 8], - loc: { - start: { column: 5, line: 1 }, - end: { column: 8, line: 1 }, - }, - }, - members: Array [ - TSEnumMember { - type: 'TSEnumMember', -- computed: false, - id: Identifier { - type: 'Identifier', -- decorators: Array [], - name: 'A', -- optional: false, +- range: [13, 14], +- loc: { +- start: { column: 2, line: 2 }, +- end: { column: 3, line: 2 }, +- }, +- }, ++ range: [5, 8], ++ loc: { ++ start: { column: 5, line: 1 }, ++ end: { column: 8, line: 1 }, ++ }, ++ }, ++ members: Array [ ++ TSEnumMember { ++ type: 'TSEnumMember', ++ id: Identifier { ++ type: 'Identifier', ++ name: 'A', range: [13, 14], loc: { @@ -40,14 +50,34 @@ exports[`AST Fixtures declaration TSEnumDeclaration with-member-one AST Alignmen end: { column: 3, line: 2 }, }, }, +- ], - range: [13, 14], - loc: { - start: { column: 2, line: 2 }, - end: { column: 3, line: 2 }, - }, +- range: [9, 17], +- loc: { +- start: { column: 9, line: 1 }, +- end: { column: 1, line: 3 }, ++ range: [13, 14], ++ loc: { ++ start: { column: 2, line: 2 }, ++ end: { column: 3, line: 2 }, ++ }, }, - ], +- }, +- const: false, +- declare: false, +- id: Identifier { +- type: 'Identifier', +- decorators: Array [], +- name: 'Foo', +- optional: false, +- +- range: [5, 8], +- loc: { +- start: { column: 5, line: 1 }, +- end: { column: 8, line: 1 }, +- }, +- }, ++ ], range: [0, 17], loc: { diff --git a/packages/ast-spec/src/declaration/TSEnumDeclaration/spec.ts b/packages/ast-spec/src/declaration/TSEnumDeclaration/spec.ts index fe1ae6d40b76..e10a95f7e902 100644 --- a/packages/ast-spec/src/declaration/TSEnumDeclaration/spec.ts +++ b/packages/ast-spec/src/declaration/TSEnumDeclaration/spec.ts @@ -2,9 +2,14 @@ import type { AST_NODE_TYPES } from '../../ast-node-types'; import type { BaseNode } from '../../base/BaseNode'; import type { TSEnumMember } from '../../element/TSEnumMember/spec'; import type { Identifier } from '../../expression/Identifier/spec'; +import type { TSEnumBody } from '../../special/TSEnumBody/spec'; export interface TSEnumDeclaration extends BaseNode { type: AST_NODE_TYPES.TSEnumDeclaration; + /** + * The body of the enum. + */ + body: TSEnumBody; /** * Whether this is a `const` enum. * @example @@ -27,6 +32,7 @@ export interface TSEnumDeclaration extends BaseNode { id: Identifier; /** * The enum members. + * @deprecated Use {@link body} instead. */ members: TSEnumMember[]; } diff --git a/packages/ast-spec/src/expression/CallExpression/spec.ts b/packages/ast-spec/src/expression/CallExpression/spec.ts index 2706ca6b4a16..f380de25bc9d 100644 --- a/packages/ast-spec/src/expression/CallExpression/spec.ts +++ b/packages/ast-spec/src/expression/CallExpression/spec.ts @@ -9,9 +9,5 @@ export interface CallExpression extends BaseNode { callee: Expression; arguments: CallExpressionArgument[]; typeArguments: TSTypeParameterInstantiation | undefined; - - /** @deprecated Use {@link `typeArguments`} instead. */ - typeParameters: TSTypeParameterInstantiation | undefined; - optional: boolean; } diff --git a/packages/ast-spec/src/expression/NewExpression/spec.ts b/packages/ast-spec/src/expression/NewExpression/spec.ts index de45a835764a..c4478bd476d8 100644 --- a/packages/ast-spec/src/expression/NewExpression/spec.ts +++ b/packages/ast-spec/src/expression/NewExpression/spec.ts @@ -9,7 +9,4 @@ export interface NewExpression extends BaseNode { callee: Expression; arguments: CallExpressionArgument[]; typeArguments: TSTypeParameterInstantiation | undefined; - - /** @deprecated Use {@link `typeArguments`} instead. */ - typeParameters: TSTypeParameterInstantiation | undefined; } diff --git a/packages/ast-spec/src/expression/TSInstantiationExpression/spec.ts b/packages/ast-spec/src/expression/TSInstantiationExpression/spec.ts index 064dd30612f7..2219a440bdf7 100644 --- a/packages/ast-spec/src/expression/TSInstantiationExpression/spec.ts +++ b/packages/ast-spec/src/expression/TSInstantiationExpression/spec.ts @@ -7,7 +7,4 @@ export interface TSInstantiationExpression extends BaseNode { type: AST_NODE_TYPES.TSInstantiationExpression; expression: Expression; typeArguments: TSTypeParameterInstantiation; - - /** @deprecated Use {@link `typeArguments`} instead. */ - typeParameters?: TSTypeParameterInstantiation; } diff --git a/packages/ast-spec/src/expression/TaggedTemplateExpression/spec.ts b/packages/ast-spec/src/expression/TaggedTemplateExpression/spec.ts index 2e2d77e68db4..2f9c3fbb2882 100644 --- a/packages/ast-spec/src/expression/TaggedTemplateExpression/spec.ts +++ b/packages/ast-spec/src/expression/TaggedTemplateExpression/spec.ts @@ -5,12 +5,8 @@ import type { Expression } from '../../unions/Expression'; import type { TemplateLiteral } from '../TemplateLiteral/spec'; export interface TaggedTemplateExpression extends BaseNode { + quasi: TemplateLiteral; + tag: Expression; type: AST_NODE_TYPES.TaggedTemplateExpression; typeArguments: TSTypeParameterInstantiation | undefined; - - /** @deprecated Use {@link `typeArguments`} instead. */ - typeParameters: TSTypeParameterInstantiation | undefined; - - tag: Expression; - quasi: TemplateLiteral; } diff --git a/packages/ast-spec/src/jsx/JSXOpeningElement/spec.ts b/packages/ast-spec/src/jsx/JSXOpeningElement/spec.ts index 1c4ca8af452d..157512bc7908 100644 --- a/packages/ast-spec/src/jsx/JSXOpeningElement/spec.ts +++ b/packages/ast-spec/src/jsx/JSXOpeningElement/spec.ts @@ -8,10 +8,6 @@ import type { JSXSpreadAttribute } from '../JSXSpreadAttribute/spec'; export interface JSXOpeningElement extends BaseNode { type: AST_NODE_TYPES.JSXOpeningElement; typeArguments: TSTypeParameterInstantiation | undefined; - - /** @deprecated Use {@link `typeArguments`} instead. */ - typeParameters: TSTypeParameterInstantiation | undefined; - selfClosing: boolean; name: JSXTagNameExpression; attributes: (JSXAttribute | JSXSpreadAttribute)[]; diff --git a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/const-enum/snapshots/1-TSESTree-AST.shot b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/const-enum/snapshots/1-TSESTree-AST.shot index 6afdeed4fd39..e6eeb71a2399 100644 --- a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/const-enum/snapshots/1-TSESTree-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/const-enum/snapshots/1-TSESTree-AST.shot @@ -6,62 +6,57 @@ Program { body: [ TSEnumDeclaration { type: "TSEnumDeclaration", - const: true, - declare: false, - id: Identifier { - type: "Identifier", - decorators: [], - name: "Foo", - optional: false, + body: TSEnumBody { + type: "TSEnumBody", + members: [ + TSEnumMember { + type: "TSEnumMember", + computed: false, + id: Identifier { + type: "Identifier", + decorators: [], + name: "foo", + optional: false, - range: [84, 87], - loc: { - start: { column: 11, line: 3 }, - end: { column: 14, line: 3 }, - }, - }, - members: [ - TSEnumMember { - type: "TSEnumMember", - computed: false, - id: Identifier { - type: "Identifier", - decorators: [], - name: "foo", - optional: false, + range: [92, 95], + loc: { + start: { column: 2, line: 4 }, + end: { column: 5, line: 4 }, + }, + }, + initializer: Literal { + type: "Literal", + raw: "1", + value: 1, - range: [92, 95], - loc: { - start: { column: 2, line: 4 }, - end: { column: 5, line: 4 }, + range: [98, 99], + loc: { + start: { column: 8, line: 4 }, + end: { column: 9, line: 4 }, + }, }, - }, - initializer: Literal { - type: "Literal", - raw: "1", - value: 1, - range: [98, 99], + range: [92, 99], loc: { - start: { column: 8, line: 4 }, + start: { column: 2, line: 4 }, end: { column: 9, line: 4 }, }, }, + TSEnumMember { + type: "TSEnumMember", + computed: false, + id: Identifier { + type: "Identifier", + decorators: [], + name: "bar", + optional: false, - range: [92, 99], - loc: { - start: { column: 2, line: 4 }, - end: { column: 9, line: 4 }, - }, - }, - TSEnumMember { - type: "TSEnumMember", - computed: false, - id: Identifier { - type: "Identifier", - decorators: [], - name: "bar", - optional: false, + range: [103, 106], + loc: { + start: { column: 2, line: 5 }, + end: { column: 5, line: 5 }, + }, + }, range: [103, 106], loc: { @@ -69,14 +64,28 @@ Program { end: { column: 5, line: 5 }, }, }, + ], - range: [103, 106], - loc: { - start: { column: 2, line: 5 }, - end: { column: 5, line: 5 }, - }, + range: [88, 109], + loc: { + start: { column: 15, line: 3 }, + end: { column: 1, line: 6 }, }, - ], + }, + const: true, + declare: false, + id: Identifier { + type: "Identifier", + decorators: [], + name: "Foo", + optional: false, + + range: [84, 87], + loc: { + start: { column: 11, line: 3 }, + end: { column: 14, line: 3 }, + }, + }, range: [73, 109], loc: { diff --git a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/const-enum/snapshots/5-AST-Alignment-AST.shot b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/const-enum/snapshots/5-AST-Alignment-AST.shot index 3ca65220e227..edeb8e60fafa 100644 --- a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/const-enum/snapshots/5-AST-Alignment-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/const-enum/snapshots/5-AST-Alignment-AST.shot @@ -10,62 +10,95 @@ exports[`AST Fixtures legacy-fixtures basics const-enum AST Alignment - AST 1`] body: Array [ TSEnumDeclaration { type: 'TSEnumDeclaration', - const: true, -- declare: false, - id: Identifier { - type: 'Identifier', -- decorators: Array [], - name: 'Foo', -- optional: false, +- body: TSEnumBody { +- type: 'TSEnumBody', +- members: Array [ +- TSEnumMember { +- type: 'TSEnumMember', +- computed: false, +- id: Identifier { +- type: 'Identifier', +- decorators: Array [], +- name: 'foo', +- optional: false, ++ const: true, ++ id: Identifier { ++ type: 'Identifier', ++ name: 'Foo', - range: [84, 87], - loc: { - start: { column: 11, line: 3 }, - end: { column: 14, line: 3 }, - }, - }, - members: Array [ - TSEnumMember { - type: 'TSEnumMember', -- computed: false, - id: Identifier { - type: 'Identifier', -- decorators: Array [], - name: 'foo', -- optional: false, +- range: [92, 95], +- loc: { +- start: { column: 2, line: 4 }, +- end: { column: 5, line: 4 }, +- }, +- }, +- initializer: Literal { +- type: 'Literal', +- raw: '1', +- value: 1, ++ range: [84, 87], ++ loc: { ++ start: { column: 11, line: 3 }, ++ end: { column: 14, line: 3 }, ++ }, ++ }, ++ members: Array [ ++ TSEnumMember { ++ type: 'TSEnumMember', ++ id: Identifier { ++ type: 'Identifier', ++ name: 'foo', - range: [92, 95], - loc: { - start: { column: 2, line: 4 }, - end: { column: 5, line: 4 }, +- range: [98, 99], +- loc: { +- start: { column: 8, line: 4 }, +- end: { column: 9, line: 4 }, +- }, ++ range: [92, 95], ++ loc: { ++ start: { column: 2, line: 4 }, ++ end: { column: 5, line: 4 }, }, - }, - initializer: Literal { - type: 'Literal', - raw: '1', - value: 1, ++ }, ++ initializer: Literal { ++ type: 'Literal', ++ raw: '1', ++ value: 1, - range: [98, 99], +- range: [92, 99], ++ range: [98, 99], loc: { - start: { column: 8, line: 4 }, +- start: { column: 2, line: 4 }, ++ start: { column: 8, line: 4 }, end: { column: 9, line: 4 }, }, }, +- TSEnumMember { +- type: 'TSEnumMember', +- computed: false, +- id: Identifier { +- type: 'Identifier', +- decorators: Array [], +- name: 'bar', +- optional: false, - range: [92, 99], - loc: { - start: { column: 2, line: 4 }, - end: { column: 9, line: 4 }, - }, - }, - TSEnumMember { - type: 'TSEnumMember', -- computed: false, - id: Identifier { - type: 'Identifier', -- decorators: Array [], - name: 'bar', -- optional: false, +- range: [103, 106], +- loc: { +- start: { column: 2, line: 5 }, +- end: { column: 5, line: 5 }, +- }, +- }, ++ range: [92, 99], ++ loc: { ++ start: { column: 2, line: 4 }, ++ end: { column: 9, line: 4 }, ++ }, ++ }, ++ TSEnumMember { ++ type: 'TSEnumMember', ++ id: Identifier { ++ type: 'Identifier', ++ name: 'bar', range: [103, 106], loc: { @@ -73,14 +106,34 @@ exports[`AST Fixtures legacy-fixtures basics const-enum AST Alignment - AST 1`] end: { column: 5, line: 5 }, }, }, +- ], - range: [103, 106], - loc: { - start: { column: 2, line: 5 }, - end: { column: 5, line: 5 }, - }, +- range: [88, 109], +- loc: { +- start: { column: 15, line: 3 }, +- end: { column: 1, line: 6 }, +- }, +- }, +- const: true, +- declare: false, +- id: Identifier { +- type: 'Identifier', +- decorators: Array [], +- name: 'Foo', +- optional: false, +- +- range: [84, 87], +- loc: { +- start: { column: 11, line: 3 }, +- end: { column: 14, line: 3 }, ++ range: [103, 106], ++ loc: { ++ start: { column: 2, line: 5 }, ++ end: { column: 5, line: 5 }, ++ }, }, - ], +- }, ++ ], range: [73, 109], loc: { diff --git a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-declare-const-named-enum/snapshots/1-TSESTree-AST.shot b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-declare-const-named-enum/snapshots/1-TSESTree-AST.shot index edf76ad24aee..39a9d91b204a 100644 --- a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-declare-const-named-enum/snapshots/1-TSESTree-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-declare-const-named-enum/snapshots/1-TSESTree-AST.shot @@ -9,62 +9,57 @@ Program { attributes: [], declaration: TSEnumDeclaration { type: "TSEnumDeclaration", - const: true, - declare: true, - id: Identifier { - type: "Identifier", - decorators: [], - name: "Foo", - optional: false, + body: TSEnumBody { + type: "TSEnumBody", + members: [ + TSEnumMember { + type: "TSEnumMember", + computed: false, + id: Identifier { + type: "Identifier", + decorators: [], + name: "foo", + optional: false, - range: [99, 102], - loc: { - start: { column: 26, line: 3 }, - end: { column: 29, line: 3 }, - }, - }, - members: [ - TSEnumMember { - type: "TSEnumMember", - computed: false, - id: Identifier { - type: "Identifier", - decorators: [], - name: "foo", - optional: false, + range: [107, 110], + loc: { + start: { column: 2, line: 4 }, + end: { column: 5, line: 4 }, + }, + }, + initializer: Literal { + type: "Literal", + raw: "1", + value: 1, - range: [107, 110], - loc: { - start: { column: 2, line: 4 }, - end: { column: 5, line: 4 }, + range: [113, 114], + loc: { + start: { column: 8, line: 4 }, + end: { column: 9, line: 4 }, + }, }, - }, - initializer: Literal { - type: "Literal", - raw: "1", - value: 1, - range: [113, 114], + range: [107, 114], loc: { - start: { column: 8, line: 4 }, + start: { column: 2, line: 4 }, end: { column: 9, line: 4 }, }, }, + TSEnumMember { + type: "TSEnumMember", + computed: false, + id: Identifier { + type: "Identifier", + decorators: [], + name: "bar", + optional: false, - range: [107, 114], - loc: { - start: { column: 2, line: 4 }, - end: { column: 9, line: 4 }, - }, - }, - TSEnumMember { - type: "TSEnumMember", - computed: false, - id: Identifier { - type: "Identifier", - decorators: [], - name: "bar", - optional: false, + range: [118, 121], + loc: { + start: { column: 2, line: 5 }, + end: { column: 5, line: 5 }, + }, + }, range: [118, 121], loc: { @@ -72,14 +67,28 @@ Program { end: { column: 5, line: 5 }, }, }, + ], - range: [118, 121], - loc: { - start: { column: 2, line: 5 }, - end: { column: 5, line: 5 }, - }, + range: [103, 124], + loc: { + start: { column: 30, line: 3 }, + end: { column: 1, line: 6 }, }, - ], + }, + const: true, + declare: true, + id: Identifier { + type: "Identifier", + decorators: [], + name: "Foo", + optional: false, + + range: [99, 102], + loc: { + start: { column: 26, line: 3 }, + end: { column: 29, line: 3 }, + }, + }, range: [80, 124], loc: { diff --git a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-declare-const-named-enum/snapshots/5-AST-Alignment-AST.shot b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-declare-const-named-enum/snapshots/5-AST-Alignment-AST.shot index 1795c3300259..7d3830711a9c 100644 --- a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-declare-const-named-enum/snapshots/5-AST-Alignment-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-declare-const-named-enum/snapshots/5-AST-Alignment-AST.shot @@ -14,62 +14,96 @@ exports[`AST Fixtures legacy-fixtures basics export-declare-const-named-enum AST + assertions: Array [], declaration: TSEnumDeclaration { type: 'TSEnumDeclaration', - const: true, - declare: true, - id: Identifier { - type: 'Identifier', -- decorators: Array [], - name: 'Foo', -- optional: false, +- body: TSEnumBody { +- type: 'TSEnumBody', +- members: Array [ +- TSEnumMember { +- type: 'TSEnumMember', +- computed: false, +- id: Identifier { +- type: 'Identifier', +- decorators: Array [], +- name: 'foo', +- optional: false, ++ const: true, ++ declare: true, ++ id: Identifier { ++ type: 'Identifier', ++ name: 'Foo', - range: [99, 102], - loc: { - start: { column: 26, line: 3 }, - end: { column: 29, line: 3 }, - }, - }, - members: Array [ - TSEnumMember { - type: 'TSEnumMember', -- computed: false, - id: Identifier { - type: 'Identifier', -- decorators: Array [], - name: 'foo', -- optional: false, +- range: [107, 110], +- loc: { +- start: { column: 2, line: 4 }, +- end: { column: 5, line: 4 }, +- }, +- }, +- initializer: Literal { +- type: 'Literal', +- raw: '1', +- value: 1, ++ range: [99, 102], ++ loc: { ++ start: { column: 26, line: 3 }, ++ end: { column: 29, line: 3 }, ++ }, ++ }, ++ members: Array [ ++ TSEnumMember { ++ type: 'TSEnumMember', ++ id: Identifier { ++ type: 'Identifier', ++ name: 'foo', - range: [107, 110], - loc: { - start: { column: 2, line: 4 }, - end: { column: 5, line: 4 }, +- range: [113, 114], +- loc: { +- start: { column: 8, line: 4 }, +- end: { column: 9, line: 4 }, +- }, ++ range: [107, 110], ++ loc: { ++ start: { column: 2, line: 4 }, ++ end: { column: 5, line: 4 }, }, - }, - initializer: Literal { - type: 'Literal', - raw: '1', - value: 1, ++ }, ++ initializer: Literal { ++ type: 'Literal', ++ raw: '1', ++ value: 1, - range: [113, 114], +- range: [107, 114], ++ range: [113, 114], loc: { - start: { column: 8, line: 4 }, +- start: { column: 2, line: 4 }, ++ start: { column: 8, line: 4 }, end: { column: 9, line: 4 }, }, }, +- TSEnumMember { +- type: 'TSEnumMember', +- computed: false, +- id: Identifier { +- type: 'Identifier', +- decorators: Array [], +- name: 'bar', +- optional: false, - range: [107, 114], - loc: { - start: { column: 2, line: 4 }, - end: { column: 9, line: 4 }, - }, - }, - TSEnumMember { - type: 'TSEnumMember', -- computed: false, - id: Identifier { - type: 'Identifier', -- decorators: Array [], - name: 'bar', -- optional: false, +- range: [118, 121], +- loc: { +- start: { column: 2, line: 5 }, +- end: { column: 5, line: 5 }, +- }, +- }, ++ range: [107, 114], ++ loc: { ++ start: { column: 2, line: 4 }, ++ end: { column: 9, line: 4 }, ++ }, ++ }, ++ TSEnumMember { ++ type: 'TSEnumMember', ++ id: Identifier { ++ type: 'Identifier', ++ name: 'bar', range: [118, 121], loc: { @@ -77,14 +111,34 @@ exports[`AST Fixtures legacy-fixtures basics export-declare-const-named-enum AST end: { column: 5, line: 5 }, }, }, +- ], - range: [118, 121], - loc: { - start: { column: 2, line: 5 }, - end: { column: 5, line: 5 }, - }, +- range: [103, 124], +- loc: { +- start: { column: 30, line: 3 }, +- end: { column: 1, line: 6 }, +- }, +- }, +- const: true, +- declare: true, +- id: Identifier { +- type: 'Identifier', +- decorators: Array [], +- name: 'Foo', +- optional: false, +- +- range: [99, 102], +- loc: { +- start: { column: 26, line: 3 }, +- end: { column: 29, line: 3 }, ++ range: [118, 121], ++ loc: { ++ start: { column: 2, line: 5 }, ++ end: { column: 5, line: 5 }, ++ }, }, - ], +- }, ++ ], range: [80, 124], loc: { diff --git a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-declare-named-enum/snapshots/1-TSESTree-AST.shot b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-declare-named-enum/snapshots/1-TSESTree-AST.shot index 14b710bbca7d..80bdeff4c86d 100644 --- a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-declare-named-enum/snapshots/1-TSESTree-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-declare-named-enum/snapshots/1-TSESTree-AST.shot @@ -9,62 +9,57 @@ Program { attributes: [], declaration: TSEnumDeclaration { type: "TSEnumDeclaration", - const: false, - declare: true, - id: Identifier { - type: "Identifier", - decorators: [], - name: "Foo", - optional: false, + body: TSEnumBody { + type: "TSEnumBody", + members: [ + TSEnumMember { + type: "TSEnumMember", + computed: false, + id: Identifier { + type: "Identifier", + decorators: [], + name: "foo", + optional: false, - range: [93, 96], - loc: { - start: { column: 20, line: 3 }, - end: { column: 23, line: 3 }, - }, - }, - members: [ - TSEnumMember { - type: "TSEnumMember", - computed: false, - id: Identifier { - type: "Identifier", - decorators: [], - name: "foo", - optional: false, + range: [101, 104], + loc: { + start: { column: 2, line: 4 }, + end: { column: 5, line: 4 }, + }, + }, + initializer: Literal { + type: "Literal", + raw: "1", + value: 1, - range: [101, 104], - loc: { - start: { column: 2, line: 4 }, - end: { column: 5, line: 4 }, + range: [107, 108], + loc: { + start: { column: 8, line: 4 }, + end: { column: 9, line: 4 }, + }, }, - }, - initializer: Literal { - type: "Literal", - raw: "1", - value: 1, - range: [107, 108], + range: [101, 108], loc: { - start: { column: 8, line: 4 }, + start: { column: 2, line: 4 }, end: { column: 9, line: 4 }, }, }, + TSEnumMember { + type: "TSEnumMember", + computed: false, + id: Identifier { + type: "Identifier", + decorators: [], + name: "bar", + optional: false, - range: [101, 108], - loc: { - start: { column: 2, line: 4 }, - end: { column: 9, line: 4 }, - }, - }, - TSEnumMember { - type: "TSEnumMember", - computed: false, - id: Identifier { - type: "Identifier", - decorators: [], - name: "bar", - optional: false, + range: [112, 115], + loc: { + start: { column: 2, line: 5 }, + end: { column: 5, line: 5 }, + }, + }, range: [112, 115], loc: { @@ -72,14 +67,28 @@ Program { end: { column: 5, line: 5 }, }, }, + ], - range: [112, 115], - loc: { - start: { column: 2, line: 5 }, - end: { column: 5, line: 5 }, - }, + range: [97, 118], + loc: { + start: { column: 24, line: 3 }, + end: { column: 1, line: 6 }, }, - ], + }, + const: false, + declare: true, + id: Identifier { + type: "Identifier", + decorators: [], + name: "Foo", + optional: false, + + range: [93, 96], + loc: { + start: { column: 20, line: 3 }, + end: { column: 23, line: 3 }, + }, + }, range: [80, 118], loc: { diff --git a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-declare-named-enum/snapshots/5-AST-Alignment-AST.shot b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-declare-named-enum/snapshots/5-AST-Alignment-AST.shot index 90b208e7ad14..913dcb738db1 100644 --- a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-declare-named-enum/snapshots/5-AST-Alignment-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-declare-named-enum/snapshots/5-AST-Alignment-AST.shot @@ -14,62 +14,95 @@ exports[`AST Fixtures legacy-fixtures basics export-declare-named-enum AST Align + assertions: Array [], declaration: TSEnumDeclaration { type: 'TSEnumDeclaration', -- const: false, - declare: true, - id: Identifier { - type: 'Identifier', -- decorators: Array [], - name: 'Foo', -- optional: false, +- body: TSEnumBody { +- type: 'TSEnumBody', +- members: Array [ +- TSEnumMember { +- type: 'TSEnumMember', +- computed: false, +- id: Identifier { +- type: 'Identifier', +- decorators: Array [], +- name: 'foo', +- optional: false, ++ declare: true, ++ id: Identifier { ++ type: 'Identifier', ++ name: 'Foo', - range: [93, 96], - loc: { - start: { column: 20, line: 3 }, - end: { column: 23, line: 3 }, - }, - }, - members: Array [ - TSEnumMember { - type: 'TSEnumMember', -- computed: false, - id: Identifier { - type: 'Identifier', -- decorators: Array [], - name: 'foo', -- optional: false, +- range: [101, 104], +- loc: { +- start: { column: 2, line: 4 }, +- end: { column: 5, line: 4 }, +- }, +- }, +- initializer: Literal { +- type: 'Literal', +- raw: '1', +- value: 1, ++ range: [93, 96], ++ loc: { ++ start: { column: 20, line: 3 }, ++ end: { column: 23, line: 3 }, ++ }, ++ }, ++ members: Array [ ++ TSEnumMember { ++ type: 'TSEnumMember', ++ id: Identifier { ++ type: 'Identifier', ++ name: 'foo', - range: [101, 104], - loc: { - start: { column: 2, line: 4 }, - end: { column: 5, line: 4 }, +- range: [107, 108], +- loc: { +- start: { column: 8, line: 4 }, +- end: { column: 9, line: 4 }, +- }, ++ range: [101, 104], ++ loc: { ++ start: { column: 2, line: 4 }, ++ end: { column: 5, line: 4 }, }, - }, - initializer: Literal { - type: 'Literal', - raw: '1', - value: 1, ++ }, ++ initializer: Literal { ++ type: 'Literal', ++ raw: '1', ++ value: 1, - range: [107, 108], +- range: [101, 108], ++ range: [107, 108], loc: { - start: { column: 8, line: 4 }, +- start: { column: 2, line: 4 }, ++ start: { column: 8, line: 4 }, end: { column: 9, line: 4 }, }, }, +- TSEnumMember { +- type: 'TSEnumMember', +- computed: false, +- id: Identifier { +- type: 'Identifier', +- decorators: Array [], +- name: 'bar', +- optional: false, - range: [101, 108], - loc: { - start: { column: 2, line: 4 }, - end: { column: 9, line: 4 }, - }, - }, - TSEnumMember { - type: 'TSEnumMember', -- computed: false, - id: Identifier { - type: 'Identifier', -- decorators: Array [], - name: 'bar', -- optional: false, +- range: [112, 115], +- loc: { +- start: { column: 2, line: 5 }, +- end: { column: 5, line: 5 }, +- }, +- }, ++ range: [101, 108], ++ loc: { ++ start: { column: 2, line: 4 }, ++ end: { column: 9, line: 4 }, ++ }, ++ }, ++ TSEnumMember { ++ type: 'TSEnumMember', ++ id: Identifier { ++ type: 'Identifier', ++ name: 'bar', range: [112, 115], loc: { @@ -77,14 +110,34 @@ exports[`AST Fixtures legacy-fixtures basics export-declare-named-enum AST Align end: { column: 5, line: 5 }, }, }, +- ], +- +- range: [97, 118], +- loc: { +- start: { column: 24, line: 3 }, +- end: { column: 1, line: 6 }, +- }, +- }, +- const: false, +- declare: true, +- id: Identifier { +- type: 'Identifier', +- decorators: Array [], +- name: 'Foo', +- optional: false, - range: [112, 115], - loc: { - start: { column: 2, line: 5 }, - end: { column: 5, line: 5 }, - }, +- range: [93, 96], +- loc: { +- start: { column: 20, line: 3 }, +- end: { column: 23, line: 3 }, ++ range: [112, 115], ++ loc: { ++ start: { column: 2, line: 5 }, ++ end: { column: 5, line: 5 }, ++ }, }, - ], +- }, ++ ], range: [80, 118], loc: { diff --git a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-named-enum/snapshots/1-TSESTree-AST.shot b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-named-enum/snapshots/1-TSESTree-AST.shot index 7126562753f5..d0fa7b0eb7ba 100644 --- a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-named-enum/snapshots/1-TSESTree-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-named-enum/snapshots/1-TSESTree-AST.shot @@ -9,62 +9,57 @@ Program { attributes: [], declaration: TSEnumDeclaration { type: "TSEnumDeclaration", - const: false, - declare: false, - id: Identifier { - type: "Identifier", - decorators: [], - name: "Foo", - optional: false, + body: TSEnumBody { + type: "TSEnumBody", + members: [ + TSEnumMember { + type: "TSEnumMember", + computed: false, + id: Identifier { + type: "Identifier", + decorators: [], + name: "foo", + optional: false, - range: [85, 88], - loc: { - start: { column: 12, line: 3 }, - end: { column: 15, line: 3 }, - }, - }, - members: [ - TSEnumMember { - type: "TSEnumMember", - computed: false, - id: Identifier { - type: "Identifier", - decorators: [], - name: "foo", - optional: false, + range: [93, 96], + loc: { + start: { column: 2, line: 4 }, + end: { column: 5, line: 4 }, + }, + }, + initializer: Literal { + type: "Literal", + raw: "1", + value: 1, - range: [93, 96], - loc: { - start: { column: 2, line: 4 }, - end: { column: 5, line: 4 }, + range: [99, 100], + loc: { + start: { column: 8, line: 4 }, + end: { column: 9, line: 4 }, + }, }, - }, - initializer: Literal { - type: "Literal", - raw: "1", - value: 1, - range: [99, 100], + range: [93, 100], loc: { - start: { column: 8, line: 4 }, + start: { column: 2, line: 4 }, end: { column: 9, line: 4 }, }, }, + TSEnumMember { + type: "TSEnumMember", + computed: false, + id: Identifier { + type: "Identifier", + decorators: [], + name: "bar", + optional: false, - range: [93, 100], - loc: { - start: { column: 2, line: 4 }, - end: { column: 9, line: 4 }, - }, - }, - TSEnumMember { - type: "TSEnumMember", - computed: false, - id: Identifier { - type: "Identifier", - decorators: [], - name: "bar", - optional: false, + range: [104, 107], + loc: { + start: { column: 2, line: 5 }, + end: { column: 5, line: 5 }, + }, + }, range: [104, 107], loc: { @@ -72,14 +67,28 @@ Program { end: { column: 5, line: 5 }, }, }, + ], - range: [104, 107], - loc: { - start: { column: 2, line: 5 }, - end: { column: 5, line: 5 }, - }, + range: [89, 110], + loc: { + start: { column: 16, line: 3 }, + end: { column: 1, line: 6 }, }, - ], + }, + const: false, + declare: false, + id: Identifier { + type: "Identifier", + decorators: [], + name: "Foo", + optional: false, + + range: [85, 88], + loc: { + start: { column: 12, line: 3 }, + end: { column: 15, line: 3 }, + }, + }, range: [80, 110], loc: { diff --git a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-named-enum/snapshots/5-AST-Alignment-AST.shot b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-named-enum/snapshots/5-AST-Alignment-AST.shot index 020aebc7f3d0..3b1b5eeb9c1a 100644 --- a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-named-enum/snapshots/5-AST-Alignment-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/export-named-enum/snapshots/5-AST-Alignment-AST.shot @@ -14,62 +14,94 @@ exports[`AST Fixtures legacy-fixtures basics export-named-enum AST Alignment - A + assertions: Array [], declaration: TSEnumDeclaration { type: 'TSEnumDeclaration', -- const: false, -- declare: false, - id: Identifier { - type: 'Identifier', -- decorators: Array [], - name: 'Foo', -- optional: false, +- body: TSEnumBody { +- type: 'TSEnumBody', +- members: Array [ +- TSEnumMember { +- type: 'TSEnumMember', +- computed: false, +- id: Identifier { +- type: 'Identifier', +- decorators: Array [], +- name: 'foo', +- optional: false, ++ id: Identifier { ++ type: 'Identifier', ++ name: 'Foo', - range: [85, 88], - loc: { - start: { column: 12, line: 3 }, - end: { column: 15, line: 3 }, - }, - }, - members: Array [ - TSEnumMember { - type: 'TSEnumMember', -- computed: false, - id: Identifier { - type: 'Identifier', -- decorators: Array [], - name: 'foo', -- optional: false, +- range: [93, 96], +- loc: { +- start: { column: 2, line: 4 }, +- end: { column: 5, line: 4 }, +- }, +- }, +- initializer: Literal { +- type: 'Literal', +- raw: '1', +- value: 1, ++ range: [85, 88], ++ loc: { ++ start: { column: 12, line: 3 }, ++ end: { column: 15, line: 3 }, ++ }, ++ }, ++ members: Array [ ++ TSEnumMember { ++ type: 'TSEnumMember', ++ id: Identifier { ++ type: 'Identifier', ++ name: 'foo', - range: [93, 96], - loc: { - start: { column: 2, line: 4 }, - end: { column: 5, line: 4 }, +- range: [99, 100], +- loc: { +- start: { column: 8, line: 4 }, +- end: { column: 9, line: 4 }, +- }, ++ range: [93, 96], ++ loc: { ++ start: { column: 2, line: 4 }, ++ end: { column: 5, line: 4 }, }, - }, - initializer: Literal { - type: 'Literal', - raw: '1', - value: 1, ++ }, ++ initializer: Literal { ++ type: 'Literal', ++ raw: '1', ++ value: 1, - range: [99, 100], +- range: [93, 100], ++ range: [99, 100], loc: { - start: { column: 8, line: 4 }, +- start: { column: 2, line: 4 }, ++ start: { column: 8, line: 4 }, end: { column: 9, line: 4 }, }, }, +- TSEnumMember { +- type: 'TSEnumMember', +- computed: false, +- id: Identifier { +- type: 'Identifier', +- decorators: Array [], +- name: 'bar', +- optional: false, - range: [93, 100], - loc: { - start: { column: 2, line: 4 }, - end: { column: 9, line: 4 }, - }, - }, - TSEnumMember { - type: 'TSEnumMember', -- computed: false, - id: Identifier { - type: 'Identifier', -- decorators: Array [], - name: 'bar', -- optional: false, +- range: [104, 107], +- loc: { +- start: { column: 2, line: 5 }, +- end: { column: 5, line: 5 }, +- }, +- }, ++ range: [93, 100], ++ loc: { ++ start: { column: 2, line: 4 }, ++ end: { column: 9, line: 4 }, ++ }, ++ }, ++ TSEnumMember { ++ type: 'TSEnumMember', ++ id: Identifier { ++ type: 'Identifier', ++ name: 'bar', range: [104, 107], loc: { @@ -77,14 +109,34 @@ exports[`AST Fixtures legacy-fixtures basics export-named-enum AST Alignment - A end: { column: 5, line: 5 }, }, }, +- ], - range: [104, 107], - loc: { - start: { column: 2, line: 5 }, - end: { column: 5, line: 5 }, - }, +- range: [89, 110], +- loc: { +- start: { column: 16, line: 3 }, +- end: { column: 1, line: 6 }, ++ range: [104, 107], ++ loc: { ++ start: { column: 2, line: 5 }, ++ end: { column: 5, line: 5 }, ++ }, }, - ], +- }, +- const: false, +- declare: false, +- id: Identifier { +- type: 'Identifier', +- decorators: Array [], +- name: 'Foo', +- optional: false, +- +- range: [85, 88], +- loc: { +- start: { column: 12, line: 3 }, +- end: { column: 15, line: 3 }, +- }, +- }, ++ ], range: [80, 110], loc: { diff --git a/packages/ast-spec/src/legacy-fixtures/declare/fixtures/enum/snapshots/1-TSESTree-AST.shot b/packages/ast-spec/src/legacy-fixtures/declare/fixtures/enum/snapshots/1-TSESTree-AST.shot index 956fec5e84a7..e6515601902c 100644 --- a/packages/ast-spec/src/legacy-fixtures/declare/fixtures/enum/snapshots/1-TSESTree-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/declare/fixtures/enum/snapshots/1-TSESTree-AST.shot @@ -6,29 +6,24 @@ Program { body: [ TSEnumDeclaration { type: "TSEnumDeclaration", - const: false, - declare: true, - id: Identifier { - type: "Identifier", - decorators: [], - name: "Foo", - optional: false, + body: TSEnumBody { + type: "TSEnumBody", + members: [ + TSEnumMember { + type: "TSEnumMember", + computed: false, + id: Identifier { + type: "Identifier", + decorators: [], + name: "Bar", + optional: false, - range: [86, 89], - loc: { - start: { column: 13, line: 3 }, - end: { column: 16, line: 3 }, - }, - }, - members: [ - TSEnumMember { - type: "TSEnumMember", - computed: false, - id: Identifier { - type: "Identifier", - decorators: [], - name: "Bar", - optional: false, + range: [94, 97], + loc: { + start: { column: 2, line: 4 }, + end: { column: 5, line: 4 }, + }, + }, range: [94, 97], loc: { @@ -36,21 +31,21 @@ Program { end: { column: 5, line: 4 }, }, }, + TSEnumMember { + type: "TSEnumMember", + computed: false, + id: Identifier { + type: "Identifier", + decorators: [], + name: "Baz", + optional: false, - range: [94, 97], - loc: { - start: { column: 2, line: 4 }, - end: { column: 5, line: 4 }, - }, - }, - TSEnumMember { - type: "TSEnumMember", - computed: false, - id: Identifier { - type: "Identifier", - decorators: [], - name: "Baz", - optional: false, + range: [101, 104], + loc: { + start: { column: 2, line: 5 }, + end: { column: 5, line: 5 }, + }, + }, range: [101, 104], loc: { @@ -58,14 +53,28 @@ Program { end: { column: 5, line: 5 }, }, }, + ], - range: [101, 104], - loc: { - start: { column: 2, line: 5 }, - end: { column: 5, line: 5 }, - }, + range: [90, 107], + loc: { + start: { column: 17, line: 3 }, + end: { column: 1, line: 6 }, }, - ], + }, + const: false, + declare: true, + id: Identifier { + type: "Identifier", + decorators: [], + name: "Foo", + optional: false, + + range: [86, 89], + loc: { + start: { column: 13, line: 3 }, + end: { column: 16, line: 3 }, + }, + }, range: [73, 107], loc: { diff --git a/packages/ast-spec/src/legacy-fixtures/declare/fixtures/enum/snapshots/5-AST-Alignment-AST.shot b/packages/ast-spec/src/legacy-fixtures/declare/fixtures/enum/snapshots/5-AST-Alignment-AST.shot index 1ef1a9d0f0b7..baf9d0365b3a 100644 --- a/packages/ast-spec/src/legacy-fixtures/declare/fixtures/enum/snapshots/5-AST-Alignment-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/declare/fixtures/enum/snapshots/5-AST-Alignment-AST.shot @@ -10,29 +10,40 @@ exports[`AST Fixtures legacy-fixtures declare enum AST Alignment - AST 1`] = ` body: Array [ TSEnumDeclaration { type: 'TSEnumDeclaration', -- const: false, - declare: true, - id: Identifier { - type: 'Identifier', -- decorators: Array [], - name: 'Foo', -- optional: false, +- body: TSEnumBody { +- type: 'TSEnumBody', +- members: Array [ +- TSEnumMember { +- type: 'TSEnumMember', +- computed: false, +- id: Identifier { +- type: 'Identifier', +- decorators: Array [], +- name: 'Bar', +- optional: false, ++ declare: true, ++ id: Identifier { ++ type: 'Identifier', ++ name: 'Foo', - range: [86, 89], - loc: { - start: { column: 13, line: 3 }, - end: { column: 16, line: 3 }, - }, - }, - members: Array [ - TSEnumMember { - type: 'TSEnumMember', -- computed: false, - id: Identifier { - type: 'Identifier', -- decorators: Array [], - name: 'Bar', -- optional: false, +- range: [94, 97], +- loc: { +- start: { column: 2, line: 4 }, +- end: { column: 5, line: 4 }, +- }, +- }, ++ range: [86, 89], ++ loc: { ++ start: { column: 13, line: 3 }, ++ end: { column: 16, line: 3 }, ++ }, ++ }, ++ members: Array [ ++ TSEnumMember { ++ type: 'TSEnumMember', ++ id: Identifier { ++ type: 'Identifier', ++ name: 'Bar', range: [94, 97], loc: { @@ -40,21 +51,32 @@ exports[`AST Fixtures legacy-fixtures declare enum AST Alignment - AST 1`] = ` end: { column: 5, line: 4 }, }, }, +- TSEnumMember { +- type: 'TSEnumMember', +- computed: false, +- id: Identifier { +- type: 'Identifier', +- decorators: Array [], +- name: 'Baz', +- optional: false, - range: [94, 97], - loc: { - start: { column: 2, line: 4 }, - end: { column: 5, line: 4 }, - }, - }, - TSEnumMember { - type: 'TSEnumMember', -- computed: false, - id: Identifier { - type: 'Identifier', -- decorators: Array [], - name: 'Baz', -- optional: false, +- range: [101, 104], +- loc: { +- start: { column: 2, line: 5 }, +- end: { column: 5, line: 5 }, +- }, +- }, ++ range: [94, 97], ++ loc: { ++ start: { column: 2, line: 4 }, ++ end: { column: 5, line: 4 }, ++ }, ++ }, ++ TSEnumMember { ++ type: 'TSEnumMember', ++ id: Identifier { ++ type: 'Identifier', ++ name: 'Baz', range: [101, 104], loc: { @@ -62,14 +84,34 @@ exports[`AST Fixtures legacy-fixtures declare enum AST Alignment - AST 1`] = ` end: { column: 5, line: 5 }, }, }, +- ], - range: [101, 104], - loc: { - start: { column: 2, line: 5 }, - end: { column: 5, line: 5 }, - }, +- range: [90, 107], +- loc: { +- start: { column: 17, line: 3 }, +- end: { column: 1, line: 6 }, +- }, +- }, +- const: false, +- declare: true, +- id: Identifier { +- type: 'Identifier', +- decorators: Array [], +- name: 'Foo', +- optional: false, +- +- range: [86, 89], +- loc: { +- start: { column: 13, line: 3 }, +- end: { column: 16, line: 3 }, ++ range: [101, 104], ++ loc: { ++ start: { column: 2, line: 5 }, ++ end: { column: 5, line: 5 }, ++ }, }, - ], +- }, ++ ], range: [73, 107], loc: { diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-named-type/snapshots/1-TSESTree-AST.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-named-type/snapshots/1-TSESTree-AST.shot index ef00d6dd163a..57cd9d3e565c 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-named-type/snapshots/1-TSESTree-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-named-type/snapshots/1-TSESTree-AST.shot @@ -21,6 +21,49 @@ Program { }, typeAnnotation: TSMappedType { type: "TSMappedType", + constraint: TSTypeOperator { + type: "TSTypeOperator", + operator: "keyof", + typeAnnotation: TSTypeReference { + type: "TSTypeReference", + typeName: Identifier { + type: "Identifier", + decorators: [], + name: "T", + optional: false, + + range: [104, 105], + loc: { + start: { column: 14, line: 4 }, + end: { column: 15, line: 4 }, + }, + }, + + range: [104, 105], + loc: { + start: { column: 14, line: 4 }, + end: { column: 15, line: 4 }, + }, + }, + + range: [98, 105], + loc: { + start: { column: 8, line: 4 }, + end: { column: 15, line: 4 }, + }, + }, + key: Identifier { + type: "Identifier", + decorators: [], + name: "P", + optional: false, + + range: [93, 94], + loc: { + start: { column: 3, line: 4 }, + end: { column: 4, line: 4 }, + }, + }, nameType: TSLiteralType { type: "TSLiteralType", literal: Literal { @@ -92,61 +135,6 @@ Program { end: { column: 29, line: 4 }, }, }, - typeParameter: TSTypeParameter { - type: "TSTypeParameter", - const: false, - constraint: TSTypeOperator { - type: "TSTypeOperator", - operator: "keyof", - typeAnnotation: TSTypeReference { - type: "TSTypeReference", - typeName: Identifier { - type: "Identifier", - decorators: [], - name: "T", - optional: false, - - range: [104, 105], - loc: { - start: { column: 14, line: 4 }, - end: { column: 15, line: 4 }, - }, - }, - - range: [104, 105], - loc: { - start: { column: 14, line: 4 }, - end: { column: 15, line: 4 }, - }, - }, - - range: [98, 105], - loc: { - start: { column: 8, line: 4 }, - end: { column: 15, line: 4 }, - }, - }, - in: false, - name: Identifier { - type: "Identifier", - decorators: [], - name: "P", - optional: false, - - range: [93, 94], - loc: { - start: { column: 3, line: 4 }, - end: { column: 4, line: 4 }, - }, - }, - out: false, - - range: [93, 105], - loc: { - start: { column: 3, line: 4 }, - end: { column: 15, line: 4 }, - }, - }, range: [88, 122], loc: { diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-named-type/snapshots/5-AST-Alignment-AST.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-named-type/snapshots/5-AST-Alignment-AST.shot index 368c485e82da..3c7484cb3764 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-named-type/snapshots/5-AST-Alignment-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-named-type/snapshots/5-AST-Alignment-AST.shot @@ -25,6 +25,49 @@ exports[`AST Fixtures legacy-fixtures types mapped-named-type AST Alignment - AS }, typeAnnotation: TSMappedType { type: 'TSMappedType', +- constraint: TSTypeOperator { +- type: 'TSTypeOperator', +- operator: 'keyof', +- typeAnnotation: TSTypeReference { +- type: 'TSTypeReference', +- typeName: Identifier { +- type: 'Identifier', +- decorators: Array [], +- name: 'T', +- optional: false, +- +- range: [104, 105], +- loc: { +- start: { column: 14, line: 4 }, +- end: { column: 15, line: 4 }, +- }, +- }, +- +- range: [104, 105], +- loc: { +- start: { column: 14, line: 4 }, +- end: { column: 15, line: 4 }, +- }, +- }, +- +- range: [98, 105], +- loc: { +- start: { column: 8, line: 4 }, +- end: { column: 15, line: 4 }, +- }, +- }, +- key: Identifier { +- type: 'Identifier', +- decorators: Array [], +- name: 'P', +- optional: false, +- +- range: [93, 94], +- loc: { +- start: { column: 3, line: 4 }, +- end: { column: 4, line: 4 }, +- }, +- }, nameType: TSLiteralType { type: 'TSLiteralType', literal: Literal { @@ -96,62 +139,45 @@ exports[`AST Fixtures legacy-fixtures types mapped-named-type AST Alignment - AS end: { column: 29, line: 4 }, }, }, - typeParameter: TSTypeParameter { - type: 'TSTypeParameter', -- const: false, - constraint: TSTypeOperator { - type: 'TSTypeOperator', - operator: 'keyof', - typeAnnotation: TSTypeReference { - type: 'TSTypeReference', - typeName: Identifier { - type: 'Identifier', -- decorators: Array [], - name: 'T', -- optional: false, - - range: [104, 105], - loc: { - start: { column: 14, line: 4 }, - end: { column: 15, line: 4 }, - }, - }, - - range: [104, 105], - loc: { - start: { column: 14, line: 4 }, - end: { column: 15, line: 4 }, - }, - }, - - range: [98, 105], - loc: { - start: { column: 8, line: 4 }, - end: { column: 15, line: 4 }, - }, - }, -- in: false, -- name: Identifier { -- type: 'Identifier', -- decorators: Array [], -- name: 'P', -- optional: false, -- -- range: [93, 94], -- loc: { -- start: { column: 3, line: 4 }, -- end: { column: 4, line: 4 }, -- }, -- }, -- out: false, ++ typeParameter: TSTypeParameter { ++ type: 'TSTypeParameter', ++ constraint: TSTypeOperator { ++ type: 'TSTypeOperator', ++ operator: 'keyof', ++ typeAnnotation: TSTypeReference { ++ type: 'TSTypeReference', ++ typeName: Identifier { ++ type: 'Identifier', ++ name: 'T', ++ ++ range: [104, 105], ++ loc: { ++ start: { column: 14, line: 4 }, ++ end: { column: 15, line: 4 }, ++ }, ++ }, ++ ++ range: [104, 105], ++ loc: { ++ start: { column: 14, line: 4 }, ++ end: { column: 15, line: 4 }, ++ }, ++ }, ++ ++ range: [98, 105], ++ loc: { ++ start: { column: 8, line: 4 }, ++ end: { column: 15, line: 4 }, ++ }, ++ }, + name: 'P', - - range: [93, 105], - loc: { - start: { column: 3, line: 4 }, - end: { column: 15, line: 4 }, - }, - }, ++ ++ range: [93, 105], ++ loc: { ++ start: { column: 3, line: 4 }, ++ end: { column: 15, line: 4 }, ++ }, ++ }, range: [88, 122], loc: { diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly-minus/snapshots/1-TSESTree-AST.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly-minus/snapshots/1-TSESTree-AST.shot index 5e7781c3ef65..3ddeb8b5a60c 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly-minus/snapshots/1-TSESTree-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly-minus/snapshots/1-TSESTree-AST.shot @@ -19,6 +19,27 @@ Program { type: "TSTypeAnnotation", typeAnnotation: TSMappedType { type: "TSMappedType", + constraint: TSStringKeyword { + type: "TSStringKeyword", + + range: [100, 106], + loc: { + start: { column: 27, line: 3 }, + end: { column: 33, line: 3 }, + }, + }, + key: Identifier { + type: "Identifier", + decorators: [], + name: "P", + optional: false, + + range: [95, 96], + loc: { + start: { column: 22, line: 3 }, + end: { column: 23, line: 3 }, + }, + }, nameType: null, optional: "-", readonly: "-", @@ -31,39 +52,6 @@ Program { end: { column: 44, line: 3 }, }, }, - typeParameter: TSTypeParameter { - type: "TSTypeParameter", - const: false, - constraint: TSStringKeyword { - type: "TSStringKeyword", - - range: [100, 106], - loc: { - start: { column: 27, line: 3 }, - end: { column: 33, line: 3 }, - }, - }, - in: false, - name: Identifier { - type: "Identifier", - decorators: [], - name: "P", - optional: false, - - range: [95, 96], - loc: { - start: { column: 22, line: 3 }, - end: { column: 23, line: 3 }, - }, - }, - out: false, - - range: [95, 106], - loc: { - start: { column: 22, line: 3 }, - end: { column: 33, line: 3 }, - }, - }, range: [82, 119], loc: { diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly-minus/snapshots/5-AST-Alignment-AST.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly-minus/snapshots/5-AST-Alignment-AST.shot index 5a1e7b20c516..be92a34ed653 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly-minus/snapshots/5-AST-Alignment-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly-minus/snapshots/5-AST-Alignment-AST.shot @@ -23,6 +23,27 @@ exports[`AST Fixtures legacy-fixtures types mapped-readonly-minus AST Alignment type: 'TSTypeAnnotation', typeAnnotation: TSMappedType { type: 'TSMappedType', +- constraint: TSStringKeyword { +- type: 'TSStringKeyword', +- +- range: [100, 106], +- loc: { +- start: { column: 27, line: 3 }, +- end: { column: 33, line: 3 }, +- }, +- }, +- key: Identifier { +- type: 'Identifier', +- decorators: Array [], +- name: 'P', +- optional: false, +- +- range: [95, 96], +- loc: { +- start: { column: 22, line: 3 }, +- end: { column: 23, line: 3 }, +- }, +- }, nameType: null, optional: '-', readonly: '-', @@ -35,41 +56,26 @@ exports[`AST Fixtures legacy-fixtures types mapped-readonly-minus AST Alignment end: { column: 44, line: 3 }, }, }, - typeParameter: TSTypeParameter { - type: 'TSTypeParameter', -- const: false, - constraint: TSStringKeyword { - type: 'TSStringKeyword', ++ typeParameter: TSTypeParameter { ++ type: 'TSTypeParameter', ++ constraint: TSStringKeyword { ++ type: 'TSStringKeyword', - range: [100, 106], - loc: { - start: { column: 27, line: 3 }, - end: { column: 33, line: 3 }, - }, - }, -- in: false, -- name: Identifier { -- type: 'Identifier', -- decorators: Array [], -- name: 'P', -- optional: false, ++ range: [100, 106], ++ loc: { ++ start: { column: 27, line: 3 }, ++ end: { column: 33, line: 3 }, ++ }, ++ }, + name: 'P', - -- range: [95, 96], -- loc: { -- start: { column: 22, line: 3 }, -- end: { column: 23, line: 3 }, -- }, -- }, -- out: false, -- - range: [95, 106], - loc: { - start: { column: 22, line: 3 }, - end: { column: 33, line: 3 }, - }, - }, - ++ ++ range: [95, 106], ++ loc: { ++ start: { column: 22, line: 3 }, ++ end: { column: 33, line: 3 }, ++ }, ++ }, ++ range: [82, 119], loc: { start: { column: 9, line: 3 }, diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly-plus/snapshots/1-TSESTree-AST.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly-plus/snapshots/1-TSESTree-AST.shot index fcdef4d6c306..0f3690a03bbb 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly-plus/snapshots/1-TSESTree-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly-plus/snapshots/1-TSESTree-AST.shot @@ -19,6 +19,27 @@ Program { type: "TSTypeAnnotation", typeAnnotation: TSMappedType { type: "TSMappedType", + constraint: TSStringKeyword { + type: "TSStringKeyword", + + range: [100, 106], + loc: { + start: { column: 27, line: 3 }, + end: { column: 33, line: 3 }, + }, + }, + key: Identifier { + type: "Identifier", + decorators: [], + name: "P", + optional: false, + + range: [95, 96], + loc: { + start: { column: 22, line: 3 }, + end: { column: 23, line: 3 }, + }, + }, nameType: null, optional: "+", readonly: "+", @@ -31,39 +52,6 @@ Program { end: { column: 44, line: 3 }, }, }, - typeParameter: TSTypeParameter { - type: "TSTypeParameter", - const: false, - constraint: TSStringKeyword { - type: "TSStringKeyword", - - range: [100, 106], - loc: { - start: { column: 27, line: 3 }, - end: { column: 33, line: 3 }, - }, - }, - in: false, - name: Identifier { - type: "Identifier", - decorators: [], - name: "P", - optional: false, - - range: [95, 96], - loc: { - start: { column: 22, line: 3 }, - end: { column: 23, line: 3 }, - }, - }, - out: false, - - range: [95, 106], - loc: { - start: { column: 22, line: 3 }, - end: { column: 33, line: 3 }, - }, - }, range: [82, 119], loc: { diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly-plus/snapshots/5-AST-Alignment-AST.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly-plus/snapshots/5-AST-Alignment-AST.shot index 6beae1721224..9dd0ad409179 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly-plus/snapshots/5-AST-Alignment-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly-plus/snapshots/5-AST-Alignment-AST.shot @@ -23,6 +23,27 @@ exports[`AST Fixtures legacy-fixtures types mapped-readonly-plus AST Alignment - type: 'TSTypeAnnotation', typeAnnotation: TSMappedType { type: 'TSMappedType', +- constraint: TSStringKeyword { +- type: 'TSStringKeyword', +- +- range: [100, 106], +- loc: { +- start: { column: 27, line: 3 }, +- end: { column: 33, line: 3 }, +- }, +- }, +- key: Identifier { +- type: 'Identifier', +- decorators: Array [], +- name: 'P', +- optional: false, +- +- range: [95, 96], +- loc: { +- start: { column: 22, line: 3 }, +- end: { column: 23, line: 3 }, +- }, +- }, nameType: null, optional: '+', readonly: '+', @@ -35,41 +56,26 @@ exports[`AST Fixtures legacy-fixtures types mapped-readonly-plus AST Alignment - end: { column: 44, line: 3 }, }, }, - typeParameter: TSTypeParameter { - type: 'TSTypeParameter', -- const: false, - constraint: TSStringKeyword { - type: 'TSStringKeyword', ++ typeParameter: TSTypeParameter { ++ type: 'TSTypeParameter', ++ constraint: TSStringKeyword { ++ type: 'TSStringKeyword', - range: [100, 106], - loc: { - start: { column: 27, line: 3 }, - end: { column: 33, line: 3 }, - }, - }, -- in: false, -- name: Identifier { -- type: 'Identifier', -- decorators: Array [], -- name: 'P', -- optional: false, ++ range: [100, 106], ++ loc: { ++ start: { column: 27, line: 3 }, ++ end: { column: 33, line: 3 }, ++ }, ++ }, + name: 'P', - -- range: [95, 96], -- loc: { -- start: { column: 22, line: 3 }, -- end: { column: 23, line: 3 }, -- }, -- }, -- out: false, -- - range: [95, 106], - loc: { - start: { column: 22, line: 3 }, - end: { column: 33, line: 3 }, - }, - }, - ++ ++ range: [95, 106], ++ loc: { ++ start: { column: 22, line: 3 }, ++ end: { column: 33, line: 3 }, ++ }, ++ }, ++ range: [82, 119], loc: { start: { column: 9, line: 3 }, diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly/snapshots/1-TSESTree-AST.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly/snapshots/1-TSESTree-AST.shot index f70b1ed6956b..c28ad65a923f 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly/snapshots/1-TSESTree-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly/snapshots/1-TSESTree-AST.shot @@ -19,6 +19,27 @@ Program { type: "TSTypeAnnotation", typeAnnotation: TSMappedType { type: "TSMappedType", + constraint: TSStringKeyword { + type: "TSStringKeyword", + + range: [99, 105], + loc: { + start: { column: 26, line: 3 }, + end: { column: 32, line: 3 }, + }, + }, + key: Identifier { + type: "Identifier", + decorators: [], + name: "P", + optional: false, + + range: [94, 95], + loc: { + start: { column: 21, line: 3 }, + end: { column: 22, line: 3 }, + }, + }, nameType: null, optional: true, readonly: true, @@ -31,39 +52,6 @@ Program { end: { column: 42, line: 3 }, }, }, - typeParameter: TSTypeParameter { - type: "TSTypeParameter", - const: false, - constraint: TSStringKeyword { - type: "TSStringKeyword", - - range: [99, 105], - loc: { - start: { column: 26, line: 3 }, - end: { column: 32, line: 3 }, - }, - }, - in: false, - name: Identifier { - type: "Identifier", - decorators: [], - name: "P", - optional: false, - - range: [94, 95], - loc: { - start: { column: 21, line: 3 }, - end: { column: 22, line: 3 }, - }, - }, - out: false, - - range: [94, 105], - loc: { - start: { column: 21, line: 3 }, - end: { column: 32, line: 3 }, - }, - }, range: [82, 117], loc: { diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly/snapshots/5-AST-Alignment-AST.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly/snapshots/5-AST-Alignment-AST.shot index 8c8500647fd6..af26bcceb36b 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly/snapshots/5-AST-Alignment-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-readonly/snapshots/5-AST-Alignment-AST.shot @@ -23,6 +23,27 @@ exports[`AST Fixtures legacy-fixtures types mapped-readonly AST Alignment - AST type: 'TSTypeAnnotation', typeAnnotation: TSMappedType { type: 'TSMappedType', +- constraint: TSStringKeyword { +- type: 'TSStringKeyword', +- +- range: [99, 105], +- loc: { +- start: { column: 26, line: 3 }, +- end: { column: 32, line: 3 }, +- }, +- }, +- key: Identifier { +- type: 'Identifier', +- decorators: Array [], +- name: 'P', +- optional: false, +- +- range: [94, 95], +- loc: { +- start: { column: 21, line: 3 }, +- end: { column: 22, line: 3 }, +- }, +- }, nameType: null, optional: true, readonly: true, @@ -35,41 +56,26 @@ exports[`AST Fixtures legacy-fixtures types mapped-readonly AST Alignment - AST end: { column: 42, line: 3 }, }, }, - typeParameter: TSTypeParameter { - type: 'TSTypeParameter', -- const: false, - constraint: TSStringKeyword { - type: 'TSStringKeyword', ++ typeParameter: TSTypeParameter { ++ type: 'TSTypeParameter', ++ constraint: TSStringKeyword { ++ type: 'TSStringKeyword', - range: [99, 105], - loc: { - start: { column: 26, line: 3 }, - end: { column: 32, line: 3 }, - }, - }, -- in: false, -- name: Identifier { -- type: 'Identifier', -- decorators: Array [], -- name: 'P', -- optional: false, ++ range: [99, 105], ++ loc: { ++ start: { column: 26, line: 3 }, ++ end: { column: 32, line: 3 }, ++ }, ++ }, + name: 'P', - -- range: [94, 95], -- loc: { -- start: { column: 21, line: 3 }, -- end: { column: 22, line: 3 }, -- }, -- }, -- out: false, -- - range: [94, 105], - loc: { - start: { column: 21, line: 3 }, - end: { column: 32, line: 3 }, - }, - }, - ++ ++ range: [94, 105], ++ loc: { ++ start: { column: 21, line: 3 }, ++ end: { column: 32, line: 3 }, ++ }, ++ }, ++ range: [82, 117], loc: { start: { column: 9, line: 3 }, diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-untypped/snapshots/1-TSESTree-AST.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-untypped/snapshots/1-TSESTree-AST.shot index d57cfcd0f424..09ca20f18bf2 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-untypped/snapshots/1-TSESTree-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-untypped/snapshots/1-TSESTree-AST.shot @@ -19,40 +19,28 @@ Program { type: "TSTypeAnnotation", typeAnnotation: TSMappedType { type: "TSMappedType", - nameType: null, - typeParameter: TSTypeParameter { - type: "TSTypeParameter", - const: false, - constraint: TSStringKeyword { - type: "TSStringKeyword", - - range: [90, 96], - loc: { - start: { column: 17, line: 3 }, - end: { column: 23, line: 3 }, - }, - }, - in: false, - name: Identifier { - type: "Identifier", - decorators: [], - name: "P", - optional: false, + constraint: TSStringKeyword { + type: "TSStringKeyword", - range: [85, 86], - loc: { - start: { column: 12, line: 3 }, - end: { column: 13, line: 3 }, - }, + range: [90, 96], + loc: { + start: { column: 17, line: 3 }, + end: { column: 23, line: 3 }, }, - out: false, + }, + key: Identifier { + type: "Identifier", + decorators: [], + name: "P", + optional: false, - range: [85, 96], + range: [85, 86], loc: { start: { column: 12, line: 3 }, - end: { column: 23, line: 3 }, + end: { column: 13, line: 3 }, }, }, + nameType: null, range: [82, 99], loc: { diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-untypped/snapshots/5-AST-Alignment-AST.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-untypped/snapshots/5-AST-Alignment-AST.shot index 5af936438b5b..7dd7abd04846 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-untypped/snapshots/5-AST-Alignment-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped-untypped/snapshots/5-AST-Alignment-AST.shot @@ -23,41 +23,40 @@ exports[`AST Fixtures legacy-fixtures types mapped-untypped AST Alignment - AST type: 'TSTypeAnnotation', typeAnnotation: TSMappedType { type: 'TSMappedType', - nameType: null, - typeParameter: TSTypeParameter { - type: 'TSTypeParameter', -- const: false, - constraint: TSStringKeyword { - type: 'TSStringKeyword', +- constraint: TSStringKeyword { +- type: 'TSStringKeyword', ++ nameType: null, ++ typeParameter: TSTypeParameter { ++ type: 'TSTypeParameter', ++ constraint: TSStringKeyword { ++ type: 'TSStringKeyword', - range: [90, 96], - loc: { - start: { column: 17, line: 3 }, - end: { column: 23, line: 3 }, - }, +- range: [90, 96], +- loc: { +- start: { column: 17, line: 3 }, +- end: { column: 23, line: 3 }, ++ range: [90, 96], ++ loc: { ++ start: { column: 17, line: 3 }, ++ end: { column: 23, line: 3 }, ++ }, }, -- in: false, -- name: Identifier { -- type: 'Identifier', -- decorators: Array [], -- name: 'P', -- optional: false, -+ name: 'P', +- }, +- key: Identifier { +- type: 'Identifier', +- decorators: Array [], + name: 'P', +- optional: false, -- range: [85, 86], -- loc: { -- start: { column: 12, line: 3 }, -- end: { column: 13, line: 3 }, -- }, -- }, -- out: false, -- - range: [85, 96], +- range: [85, 86], ++ range: [85, 96], loc: { start: { column: 12, line: 3 }, - end: { column: 23, line: 3 }, +- end: { column: 13, line: 3 }, ++ end: { column: 23, line: 3 }, }, }, +- nameType: null, range: [82, 99], loc: { diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped/snapshots/1-TSESTree-AST.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped/snapshots/1-TSESTree-AST.shot index 82119bfda8e2..98ccc34fe16a 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped/snapshots/1-TSESTree-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped/snapshots/1-TSESTree-AST.shot @@ -19,47 +19,35 @@ Program { type: "TSTypeAnnotation", typeAnnotation: TSMappedType { type: "TSMappedType", - nameType: null, - typeAnnotation: TSNumberKeyword { - type: "TSNumberKeyword", + constraint: TSStringKeyword { + type: "TSStringKeyword", - range: [99, 105], + range: [90, 96], loc: { - start: { column: 26, line: 3 }, - end: { column: 32, line: 3 }, + start: { column: 17, line: 3 }, + end: { column: 23, line: 3 }, }, }, - typeParameter: TSTypeParameter { - type: "TSTypeParameter", - const: false, - constraint: TSStringKeyword { - type: "TSStringKeyword", + key: Identifier { + type: "Identifier", + decorators: [], + name: "P", + optional: false, - range: [90, 96], - loc: { - start: { column: 17, line: 3 }, - end: { column: 23, line: 3 }, - }, - }, - in: false, - name: Identifier { - type: "Identifier", - decorators: [], - name: "P", - optional: false, - - range: [85, 86], - loc: { - start: { column: 12, line: 3 }, - end: { column: 13, line: 3 }, - }, + range: [85, 86], + loc: { + start: { column: 12, line: 3 }, + end: { column: 13, line: 3 }, }, - out: false, + }, + nameType: null, + typeAnnotation: TSNumberKeyword { + type: "TSNumberKeyword", - range: [85, 96], + range: [99, 105], loc: { - start: { column: 12, line: 3 }, - end: { column: 23, line: 3 }, + start: { column: 26, line: 3 }, + end: { column: 32, line: 3 }, }, }, diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped/snapshots/5-AST-Alignment-AST.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped/snapshots/5-AST-Alignment-AST.shot index 2fcdfabe0986..62e39e79d426 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped/snapshots/5-AST-Alignment-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/mapped/snapshots/5-AST-Alignment-AST.shot @@ -23,48 +23,54 @@ exports[`AST Fixtures legacy-fixtures types mapped AST Alignment - AST 1`] = ` type: 'TSTypeAnnotation', typeAnnotation: TSMappedType { type: 'TSMappedType', - nameType: null, - typeAnnotation: TSNumberKeyword { - type: 'TSNumberKeyword', +- constraint: TSStringKeyword { +- type: 'TSStringKeyword', ++ nameType: null, ++ typeAnnotation: TSNumberKeyword { ++ type: 'TSNumberKeyword', - range: [99, 105], +- range: [90, 96], ++ range: [99, 105], loc: { - start: { column: 26, line: 3 }, - end: { column: 32, line: 3 }, +- start: { column: 17, line: 3 }, +- end: { column: 23, line: 3 }, ++ start: { column: 26, line: 3 }, ++ end: { column: 32, line: 3 }, }, }, - typeParameter: TSTypeParameter { - type: 'TSTypeParameter', -- const: false, - constraint: TSStringKeyword { - type: 'TSStringKeyword', +- key: Identifier { +- type: 'Identifier', +- decorators: Array [], ++ typeParameter: TSTypeParameter { ++ type: 'TSTypeParameter', ++ constraint: TSStringKeyword { ++ type: 'TSStringKeyword', ++ ++ range: [90, 96], ++ loc: { ++ start: { column: 17, line: 3 }, ++ end: { column: 23, line: 3 }, ++ }, ++ }, + name: 'P', +- optional: false, - range: [90, 96], - loc: { - start: { column: 17, line: 3 }, - end: { column: 23, line: 3 }, - }, - }, -- in: false, -- name: Identifier { -- type: 'Identifier', -- decorators: Array [], -- name: 'P', -- optional: false, -+ name: 'P', - -- range: [85, 86], -- loc: { -- start: { column: 12, line: 3 }, -- end: { column: 13, line: 3 }, -- }, -- }, -- out: false, -- - range: [85, 96], +- range: [85, 86], ++ range: [85, 96], loc: { start: { column: 12, line: 3 }, - end: { column: 23, line: 3 }, +- end: { column: 13, line: 3 }, +- }, +- }, +- nameType: null, +- typeAnnotation: TSNumberKeyword { +- type: 'TSNumberKeyword', +- +- range: [99, 105], +- loc: { +- start: { column: 26, line: 3 }, +- end: { column: 32, line: 3 }, ++ end: { column: 23, line: 3 }, }, }, diff --git a/packages/ast-spec/src/special/TSEnumBody/spec.ts b/packages/ast-spec/src/special/TSEnumBody/spec.ts new file mode 100644 index 000000000000..3cdb9bbccdf0 --- /dev/null +++ b/packages/ast-spec/src/special/TSEnumBody/spec.ts @@ -0,0 +1,8 @@ +import type { AST_NODE_TYPES } from '../../ast-node-types'; +import type { BaseNode } from '../../base/BaseNode'; +import type { TSEnumMember } from '../../element/TSEnumMember/spec'; + +export interface TSEnumBody extends BaseNode { + type: AST_NODE_TYPES.TSEnumBody; + members: TSEnumMember[]; +} diff --git a/packages/ast-spec/src/special/spec.ts b/packages/ast-spec/src/special/spec.ts index c906deb52957..24ef5463f150 100644 --- a/packages/ast-spec/src/special/spec.ts +++ b/packages/ast-spec/src/special/spec.ts @@ -11,6 +11,7 @@ export * from './PrivateIdentifier/spec'; export * from './Program/spec'; export * from './SwitchCase/spec'; export * from './TSClassImplements/spec'; +export * from './TSEnumBody/spec'; export * from './TSExternalModuleReference/spec'; export * from './TSInterfaceBody/spec'; export * from './TSInterfaceHeritage/spec'; diff --git a/packages/ast-spec/src/type/TSImportType/spec.ts b/packages/ast-spec/src/type/TSImportType/spec.ts index 3eb30235d9b0..f85074de7154 100644 --- a/packages/ast-spec/src/type/TSImportType/spec.ts +++ b/packages/ast-spec/src/type/TSImportType/spec.ts @@ -9,7 +9,4 @@ export interface TSImportType extends BaseNode { argument: TypeNode; qualifier: EntityName | null; typeArguments: TSTypeParameterInstantiation | null; - - /** @deprecated Use {@link `typeArguments`} instead. */ - typeParameters: TSTypeParameterInstantiation | null; } diff --git a/packages/ast-spec/src/type/TSMappedType/spec.ts b/packages/ast-spec/src/type/TSMappedType/spec.ts index 8b81c4f77bb6..44f64ac8621a 100644 --- a/packages/ast-spec/src/type/TSMappedType/spec.ts +++ b/packages/ast-spec/src/type/TSMappedType/spec.ts @@ -1,11 +1,17 @@ import type { AST_NODE_TYPES } from '../../ast-node-types'; import type { BaseNode } from '../../base/BaseNode'; +import type { Identifier } from '../../expression/Identifier/spec'; import type { TSTypeParameter } from '../../special/TSTypeParameter/spec'; import type { TypeNode } from '../../unions/TypeNode'; export interface TSMappedType extends BaseNode { type: AST_NODE_TYPES.TSMappedType; + + /** @deprecated Use {@link `constraint`} and {@link `key`} instead. */ typeParameter: TSTypeParameter; + + constraint: TypeNode; + key: Identifier; readonly: boolean | '-' | '+' | undefined; optional: boolean | '-' | '+' | undefined; typeAnnotation: TypeNode | undefined; diff --git a/packages/ast-spec/src/type/TSTypeQuery/spec.ts b/packages/ast-spec/src/type/TSTypeQuery/spec.ts index b6ec9fdf6ec2..e5cad59b9c71 100644 --- a/packages/ast-spec/src/type/TSTypeQuery/spec.ts +++ b/packages/ast-spec/src/type/TSTypeQuery/spec.ts @@ -8,7 +8,4 @@ export interface TSTypeQuery extends BaseNode { type: AST_NODE_TYPES.TSTypeQuery; exprName: EntityName | TSImportType; typeArguments: TSTypeParameterInstantiation | undefined; - - /** @deprecated Use {@link `typeArguments`} instead. */ - typeParameters: TSTypeParameterInstantiation | undefined; } diff --git a/packages/ast-spec/src/type/TSTypeReference/spec.ts b/packages/ast-spec/src/type/TSTypeReference/spec.ts index c7b5b340b2f4..5ffa64e64062 100644 --- a/packages/ast-spec/src/type/TSTypeReference/spec.ts +++ b/packages/ast-spec/src/type/TSTypeReference/spec.ts @@ -6,9 +6,5 @@ import type { EntityName } from '../../unions/EntityName'; export interface TSTypeReference extends BaseNode { type: AST_NODE_TYPES.TSTypeReference; typeArguments: TSTypeParameterInstantiation | undefined; - - /** @deprecated Use {@link `typeArguments`} instead. */ - typeParameters: TSTypeParameterInstantiation | undefined; - typeName: EntityName; } diff --git a/packages/ast-spec/src/unions/Node.ts b/packages/ast-spec/src/unions/Node.ts index b0952e0e1863..e224e9711b22 100644 --- a/packages/ast-spec/src/unions/Node.ts +++ b/packages/ast-spec/src/unions/Node.ts @@ -92,6 +92,7 @@ import type { Program } from '../special/Program/spec'; import type { SwitchCase } from '../special/SwitchCase/spec'; import type { TemplateElement } from '../special/TemplateElement/spec'; import type { TSClassImplements } from '../special/TSClassImplements/spec'; +import type { TSEnumBody } from '../special/TSEnumBody/spec'; import type { TSExternalModuleReference } from '../special/TSExternalModuleReference/spec'; import type { TSInterfaceBody } from '../special/TSInterfaceBody/spec'; import type { TSInterfaceHeritage } from '../special/TSInterfaceHeritage/spec'; @@ -272,6 +273,7 @@ export type Node = | TSDeclareFunction | TSDeclareKeyword | TSEmptyBodyFunctionExpression + | TSEnumBody | TSEnumDeclaration | TSEnumMember | TSExportAssignment diff --git a/packages/ast-spec/tests/fixtures.test.ts b/packages/ast-spec/tests/fixtures.test.ts index d107cc320255..3273b2f65731 100644 --- a/packages/ast-spec/tests/fixtures.test.ts +++ b/packages/ast-spec/tests/fixtures.test.ts @@ -1,5 +1,5 @@ import fs from 'fs'; -import glob = require('glob'); +import * as glob from 'glob'; import makeDir from 'make-dir'; import path from 'path'; @@ -66,6 +66,7 @@ const FIXTURES: readonly Fixture[] = [...VALID_FIXTURES, ...ERROR_FIXTURES].map( absolute, config: ((): ASTFixtureConfig => { try { + // eslint-disable-next-line @typescript-eslint/no-require-imports return require(configPath).default; } catch { return {}; diff --git a/packages/eslint-plugin-internal/CHANGELOG.md b/packages/eslint-plugin-internal/CHANGELOG.md index db37061a2e3e..7bf3302333ba 100644 --- a/packages/eslint-plugin-internal/CHANGELOG.md +++ b/packages/eslint-plugin-internal/CHANGELOG.md @@ -1,3 +1,52 @@ +## 8.0.1 (2024-08-05) + +This was a version bump only for eslint-plugin-internal 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. + +# 8.0.0 (2024-07-31) + + +### 🚀 Features + +- stricter parent types for the AST + +- **utils:** allow specifying additional rule meta.docs in RuleCreator + +- **rule-tester:** support multipass fixes + +- **typescript-estree:** stabilize EXPERIMENTAL_useProjectService as projectService + +- **rule-tester:** switched to flat config + + +### 🩹 Fixes + +- **typescript-estree:** add TSEnumBody node for TSEnumDeclaration body + +- **rule-tester:** re-apply updates from main + +- **rule-tester:** provide Linter a cwd in its constructor + + +### ❤️ Thank You + +- Abraham Guo +- Alfred Ringstad +- auvred +- Brad Zacher +- Christopher Aubut +- Collin Bachman +- James Henry +- Josh Goldberg +- Josh Goldberg ✨ +- Kirk Waiblinger +- StyleShit +- Victor Lin +- Yukihiro Hasegawa + +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.18.0 (2024-07-29) This was a version bump only for eslint-plugin-internal to align it with other projects, there were no code changes. diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json index 8de5d8cf04b5..2328da96a934 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.18.0", + "version": "8.0.1", "private": true, "main": "dist/index.js", "types": "index.d.ts", @@ -15,10 +15,10 @@ }, "dependencies": { "@prettier/sync": "^0.5.1", - "@typescript-eslint/rule-tester": "7.18.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/type-utils": "7.18.0", - "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/rule-tester": "8.0.1", + "@typescript-eslint/scope-manager": "8.0.1", + "@typescript-eslint/type-utils": "8.0.1", + "@typescript-eslint/utils": "8.0.1", "prettier": "^3.2.5" }, "devDependencies": { diff --git a/packages/eslint-plugin-internal/src/rules/no-poorly-typed-ts-props.ts b/packages/eslint-plugin-internal/src/rules/no-poorly-typed-ts-props.ts index 1d07e54351b4..2ce87e978cf4 100644 --- a/packages/eslint-plugin-internal/src/rules/no-poorly-typed-ts-props.ts +++ b/packages/eslint-plugin-internal/src/rules/no-poorly-typed-ts-props.ts @@ -31,7 +31,6 @@ export default createRule({ docs: { description: "Enforce that rules don't use TS API properties with known bad type definitions", - recommended: 'recommended', requiresTypeChecking: true, }, fixable: 'code', 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 index e27ed2125f8e..5913830b0baf 100644 --- 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 @@ -10,7 +10,6 @@ export default createRule({ meta: { type: 'problem', docs: { - recommended: 'recommended', description: 'Disallow relative paths to internal packages', }, messages: { diff --git a/packages/eslint-plugin-internal/src/rules/no-typescript-default-import.ts b/packages/eslint-plugin-internal/src/rules/no-typescript-default-import.ts index e9cb7e31d9d6..dd252175f9a3 100644 --- a/packages/eslint-plugin-internal/src/rules/no-typescript-default-import.ts +++ b/packages/eslint-plugin-internal/src/rules/no-typescript-default-import.ts @@ -20,7 +20,6 @@ export default createRule({ docs: { description: "Enforce that packages rules don't do `import ts from 'typescript';`", - recommended: 'recommended', }, fixable: 'code', schema: [], @@ -42,7 +41,7 @@ export default createRule({ 'ImportDeclaration > ImportDefaultSpecifier'( node: TSESTree.ImportDefaultSpecifier, ): void { - const importStatement = node.parent as TSESTree.ImportDeclaration; + const importStatement = node.parent; if (importStatement.source.value === 'typescript') { context.report({ node, diff --git a/packages/eslint-plugin-internal/src/rules/no-typescript-estree-import.ts b/packages/eslint-plugin-internal/src/rules/no-typescript-estree-import.ts index 758328efe21f..865540033cd8 100644 --- a/packages/eslint-plugin-internal/src/rules/no-typescript-estree-import.ts +++ b/packages/eslint-plugin-internal/src/rules/no-typescript-estree-import.ts @@ -16,7 +16,6 @@ export default createRule({ type: 'problem', docs: { description: `Enforce that eslint-plugin rules don't require anything from ${TSESTREE_NAME} or ${TYPES_NAME}`, - recommended: 'recommended', }, fixable: 'code', schema: [], diff --git a/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts b/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts index 20a401465285..57a372367fca 100644 --- a/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts +++ b/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts @@ -109,7 +109,6 @@ export default createRule({ type: 'problem', docs: { description: `Enforce that eslint-plugin test snippets are correctly formatted`, - recommended: 'recommended', requiresTypeChecking: true, }, fixable: 'code', diff --git a/packages/eslint-plugin-internal/src/rules/prefer-ast-types-enum.ts b/packages/eslint-plugin-internal/src/rules/prefer-ast-types-enum.ts index 9e97e44d2536..07665a9bf68b 100755 --- a/packages/eslint-plugin-internal/src/rules/prefer-ast-types-enum.ts +++ b/packages/eslint-plugin-internal/src/rules/prefer-ast-types-enum.ts @@ -13,7 +13,6 @@ export default createRule({ meta: { type: 'problem', docs: { - recommended: 'recommended', description: 'Enforce consistent usage of `AST_NODE_TYPES`, `AST_TOKEN_TYPES` and `DefinitionType` enums', }, @@ -41,9 +40,8 @@ export default createRule({ Literal(node: TSESTree.Literal): void { if ( node.parent.type === AST_NODE_TYPES.TSEnumMember && - node.parent.parent.type === AST_NODE_TYPES.TSEnumDeclaration && ['AST_NODE_TYPES', 'AST_TOKEN_TYPES', 'DefinitionType'].includes( - node.parent.parent.id.name, + node.parent.parent.parent.id.name, ) ) { return; @@ -55,15 +53,15 @@ export default createRule({ const value = node.value; - if (Object.prototype.hasOwnProperty.call(AST_NODE_TYPES, value)) { + if (Object.hasOwn(AST_NODE_TYPES, value)) { report('AST_NODE_TYPES', node); } - if (Object.prototype.hasOwnProperty.call(AST_TOKEN_TYPES, value)) { + if (Object.hasOwn(AST_TOKEN_TYPES, value)) { report('AST_TOKEN_TYPES', node); } - if (Object.prototype.hasOwnProperty.call(DefinitionType, value)) { + if (Object.hasOwn(DefinitionType, value)) { report('DefinitionType', node); } }, diff --git a/packages/eslint-plugin-internal/src/util/createRule.ts b/packages/eslint-plugin-internal/src/util/createRule.ts index 068fde1f5860..aaa9ae45a014 100644 --- a/packages/eslint-plugin-internal/src/util/createRule.ts +++ b/packages/eslint-plugin-internal/src/util/createRule.ts @@ -4,7 +4,11 @@ import { ESLintUtils } from '@typescript-eslint/utils'; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { version }: { version: string } = require('../../package.json'); -const createRule = ESLintUtils.RuleCreator( +export interface ESLintPluginInternalDocs { + requiresTypeChecking?: true; +} + +const createRule = ESLintUtils.RuleCreator( name => `https://github.com/typescript-eslint/typescript-eslint/blob/v${version}/packages/eslint-plugin-internal/src/rules/${name}.ts`, ); diff --git a/packages/eslint-plugin-internal/tests/rules/no-poorly-typed-ts-props.test.ts b/packages/eslint-plugin-internal/tests/rules/no-poorly-typed-ts-props.test.ts index 5e75c5e2b7e5..4e9b2a60ce25 100644 --- a/packages/eslint-plugin-internal/tests/rules/no-poorly-typed-ts-props.test.ts +++ b/packages/eslint-plugin-internal/tests/rules/no-poorly-typed-ts-props.test.ts @@ -5,11 +5,11 @@ import rule from '../../src/rules/no-poorly-typed-ts-props'; import { getFixturesRootDir } from '../RuleTester'; const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', - parserOptions: { - project: './tsconfig.json', - tsconfigRootDir: getFixturesRootDir(), - sourceType: 'module', + languageOptions: { + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: getFixturesRootDir(), + }, }, }); 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 index 2e1cbf99e216..3f29b69e50e0 100644 --- 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 @@ -6,7 +6,11 @@ import rule, { } from '../../src/rules/no-relative-paths-to-internal-packages'; const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', + languageOptions: { + parserOptions: { + tsconfigRootDir: PACKAGES_DIR, + }, + }, }); ruleTester.run('no-relative-paths-to-internal-packages', rule, { diff --git a/packages/eslint-plugin-internal/tests/rules/no-typescript-default-import.test.ts b/packages/eslint-plugin-internal/tests/rules/no-typescript-default-import.test.ts index 45590e5012de..94ab1f566266 100644 --- a/packages/eslint-plugin-internal/tests/rules/no-typescript-default-import.test.ts +++ b/packages/eslint-plugin-internal/tests/rules/no-typescript-default-import.test.ts @@ -2,12 +2,7 @@ import { RuleTester } from '@typescript-eslint/rule-tester'; import rule from '../../src/rules/no-typescript-default-import'; -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', - parserOptions: { - sourceType: 'module', - }, -}); +const ruleTester = new RuleTester(); ruleTester.run('no-typescript-default-import', rule, { valid: [ diff --git a/packages/eslint-plugin-internal/tests/rules/no-typescript-estree.test.ts b/packages/eslint-plugin-internal/tests/rules/no-typescript-estree.test.ts index 46eb2edabbe0..8c4bcda6fa82 100644 --- a/packages/eslint-plugin-internal/tests/rules/no-typescript-estree.test.ts +++ b/packages/eslint-plugin-internal/tests/rules/no-typescript-estree.test.ts @@ -2,12 +2,7 @@ import { RuleTester } from '@typescript-eslint/rule-tester'; import rule from '../../src/rules/no-typescript-estree-import'; -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', - parserOptions: { - sourceType: 'module', - }, -}); +const ruleTester = new RuleTester(); ruleTester.run('no-typescript-estree-import', rule, { valid: [ diff --git a/packages/eslint-plugin-internal/tests/rules/plugin-test-formatting.test.ts b/packages/eslint-plugin-internal/tests/rules/plugin-test-formatting.test.ts index 28e567d052b8..290673eb56e0 100644 --- a/packages/eslint-plugin-internal/tests/rules/plugin-test-formatting.test.ts +++ b/packages/eslint-plugin-internal/tests/rules/plugin-test-formatting.test.ts @@ -4,11 +4,11 @@ import rule from '../../src/rules/plugin-test-formatting'; import { getFixturesRootDir } from '../RuleTester'; const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', - parserOptions: { - project: './tsconfig.json', - tsconfigRootDir: getFixturesRootDir(), - sourceType: 'module', + languageOptions: { + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: getFixturesRootDir(), + }, }, }); @@ -160,7 +160,7 @@ const test2 = { // TODO - figure out how to handle this pattern ` -import { TSESLint } from '@typescript-eslint/utils'; +import { InvalidTestCase } from '@typescript-eslint/rule-tester'; const test = [ { @@ -169,7 +169,7 @@ const test = [ { code: 'const badlyFormatted = "code2"', }, -].map>(test => ({ +].map>(test => ({ code: test.code, errors: [], })); @@ -206,8 +206,18 @@ const test = [ }, { code: wrap`'for (const x of y) {}'`, - output: wrap`\`for (const x of y) { + output: [ + wrap`\`for (const x of y) { }\``, + wrap`\` +for (const x of y) { +} +\``, + wrap`\` +for (const x of y) { +} +${PARENT_INDENT}\``, + ], errors: [ { messageId: 'invalidFormatting', @@ -217,8 +227,18 @@ const test = [ { code: wrap`'for (const x of \`asdf\`) {}'`, // make sure it escapes the backticks - output: wrap`\`for (const x of \\\`asdf\\\`) { + output: [ + wrap`\`for (const x of \\\`asdf\\\`) { }\``, + wrap`\` +for (const x of \\\`asdf\\\`) { +} +\``, + wrap`\` +for (const x of \\\`asdf\\\`) { +} +${PARENT_INDENT}\``, + ], errors: [ { messageId: 'invalidFormatting', @@ -238,7 +258,7 @@ const test = [ }, { code: wrap`\`const a = '1'\``, - output: wrap`"const a = '1'"`, + output: [wrap`"const a = '1'"`, wrap`"const a = '1';"`], errors: [ { messageId: 'singleLineQuotes', @@ -247,7 +267,7 @@ const test = [ }, { code: wrap`\`const a = "1";\``, - output: wrap`'const a = "1";'`, + output: [wrap`'const a = "1";'`, wrap`"const a = '1';"`], errors: [ { messageId: 'singleLineQuotes', @@ -258,9 +278,14 @@ const test = [ { code: wrap`\`const a = "1"; ${PARENT_INDENT}\``, - output: wrap`\` + output: [ + wrap`\` const a = "1"; ${PARENT_INDENT}\``, + wrap`\` +const a = '1'; +${PARENT_INDENT}\``, + ], errors: [ { messageId: 'templateLiteralEmptyEnds', @@ -270,9 +295,17 @@ ${PARENT_INDENT}\``, { code: wrap`\` ${CODE_INDENT}const a = "1";\``, - output: wrap`\` + output: [ + wrap`\` ${CODE_INDENT}const a = "1"; \``, + wrap`\` +${CODE_INDENT}const a = "1"; +${PARENT_INDENT}\``, + wrap`\` +${CODE_INDENT}const a = '1'; +${PARENT_INDENT}\``, + ], errors: [ { messageId: 'templateLiteralEmptyEnds', @@ -282,10 +315,20 @@ ${CODE_INDENT}const a = "1"; { code: wrap`\`const a = "1"; ${CODE_INDENT}const b = "2";\``, - output: wrap`\` + output: [ + wrap`\` const a = "1"; ${CODE_INDENT}const b = "2"; \``, + wrap`\` +const a = "1"; +${CODE_INDENT}const b = "2"; +${PARENT_INDENT}\``, + wrap`\` +const a = '1'; +const b = '2'; +${PARENT_INDENT}\``, + ], errors: [ { messageId: 'templateLiteralEmptyEnds', @@ -297,9 +340,14 @@ ${CODE_INDENT}const b = "2"; code: wrap`\` ${CODE_INDENT}const a = "1"; \``, - output: wrap`\` + output: [ + wrap`\` ${CODE_INDENT}const a = "1"; ${PARENT_INDENT}\``, + wrap`\` +${CODE_INDENT}const a = '1'; +${PARENT_INDENT}\``, + ], errors: [ { messageId: 'templateLiteralLastLineIndent', @@ -310,9 +358,14 @@ ${PARENT_INDENT}\``, code: wrap`\` ${CODE_INDENT}const a = "1"; \``, - output: wrap`\` + output: [ + wrap`\` ${CODE_INDENT}const a = "1"; ${PARENT_INDENT}\``, + wrap`\` +${CODE_INDENT}const a = '1'; +${PARENT_INDENT}\``, + ], errors: [ { messageId: 'templateLiteralLastLineIndent', @@ -483,7 +536,8 @@ ruleTester.run({ ], }); `, - output: ` + output: [ + ` ruleTester.run({ valid: [ { @@ -517,6 +571,75 @@ foo ], }); `, + ` +ruleTester.run({ + valid: [ + { + code: 'foo;', + }, + { + code: \` +foo + \`, + }, + { + code: \` + foo + \`, + }, + ], + invalid: [ + { + code: 'foo;', + }, + { + code: \` +foo + \`, + }, + { + code: \` + foo + \`, + }, + ], +}); + `, + ` +ruleTester.run({ + valid: [ + { + code: 'foo;', + }, + { + code: \` +foo; + \`, + }, + { + code: \` + foo + \`, + }, + ], + invalid: [ + { + code: 'foo;', + }, + { + code: \` +foo; + \`, + }, + { + code: \` + foo + \`, + }, + ], +}); + `, + ], errors: [ { messageId: 'singleLineQuotes', @@ -601,9 +724,9 @@ const test: RunTests = { }, { code: ` -import { TSESLint } from '@typescript-eslint/utils'; +import { RunTests } from '@typescript-eslint/rule-tester'; -const test: TSESLint.RunTests<'', []> = { +const test: RunTests<'', []> = { valid: [ 'const badlyFormatted = "code"', { @@ -619,9 +742,9 @@ const test: TSESLint.RunTests<'', []> = { }; `, output: ` -import { TSESLint } from '@typescript-eslint/utils'; +import { RunTests } from '@typescript-eslint/rule-tester'; -const test: TSESLint.RunTests<'', []> = { +const test: RunTests<'', []> = { valid: [ "const badlyFormatted = 'code';", { @@ -650,16 +773,16 @@ const test: TSESLint.RunTests<'', []> = { }, { code: ` -import { TSESLint } from '@typescript-eslint/utils'; +import { ValidTestCase } from '@typescript-eslint/rule-tester'; -const test: TSESLint.ValidTestCase<[]> = { +const test: ValidTestCase<[]> = { code: 'const badlyFormatted = "code"', }; `, output: ` -import { TSESLint } from '@typescript-eslint/utils'; +import { ValidTestCase } from '@typescript-eslint/rule-tester'; -const test: TSESLint.ValidTestCase<[]> = { +const test: ValidTestCase<[]> = { code: "const badlyFormatted = 'code';", }; `, @@ -671,9 +794,9 @@ const test: TSESLint.ValidTestCase<[]> = { }, { code: ` -import { TSESLint } from '@typescript-eslint/utils'; +import { InvalidTestCase } from '@typescript-eslint/rule-tester'; -const test: TSESLint.InvalidTestCase<'', []> = { +const test: InvalidTestCase<'', []> = { code: 'const badlyFormatted = "code1"', errors: [ { @@ -692,9 +815,9 @@ const test: TSESLint.InvalidTestCase<'', []> = { }; `, output: ` -import { TSESLint } from '@typescript-eslint/utils'; +import { InvalidTestCase } from '@typescript-eslint/rule-tester'; -const test: TSESLint.InvalidTestCase<'', []> = { +const test: InvalidTestCase<'', []> = { code: "const badlyFormatted = 'code1';", errors: [ { diff --git a/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-enum.test.ts b/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-enum.test.ts index 44f9f6118bac..78052be73451 100644 --- a/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-enum.test.ts +++ b/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-enum.test.ts @@ -4,12 +4,7 @@ import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; import rule from '../../src/rules/prefer-ast-types-enum'; -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', - parserOptions: { - sourceType: 'module', - }, -}); +const ruleTester = new RuleTester(); ruleTester.run('prefer-ast-types-enum', rule, { valid: [ diff --git a/packages/eslint-plugin-internal/tsconfig.json b/packages/eslint-plugin-internal/tsconfig.json index 83713e5c8bf0..0c5f13cc5103 100644 --- a/packages/eslint-plugin-internal/tsconfig.json +++ b/packages/eslint-plugin-internal/tsconfig.json @@ -2,7 +2,6 @@ "extends": "./tsconfig.build.json", "compilerOptions": { "composite": false, - "target": "ES2022", "rootDir": "." }, "include": ["src", "typings", "tests", "index.d.ts"], diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index a57d34393303..99559386858b 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,3 +1,112 @@ +## 8.0.1 (2024-08-05) + + +### 🩹 Fixes + +- **eslint-plugin:** [no-unused-vars] ignore imports used only as types + + +### ❤️ Thank You + +- Jake Bailey + +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. + +# 8.0.0 (2024-07-31) + + +### 🚀 Features + +- stricter parent types for the AST + +- **typescript-estree:** split TSMappedType typeParameter into constraint and key + +- **eslint-plugin:** remove formatting/layout rules + +- **eslint-plugin:** [prefer-nullish-coalescing] change ignoreConditionalTests default to true + +- **eslint-plugin:** deprecate no-loss-of-precision extension rule + +- **eslint-plugin:** [no-unused-vars] align catch behavior to ESLint 9 + +- **typescript-estree:** rename automaticSingleRunInference to disallowAutomaticSingleRunInference + +- **utils:** allow specifying additional rule meta.docs in RuleCreator + +- **eslint-plugin:** split no-empty-object-type out from ban-types and no-empty-interfaces + +- **rule-tester:** support multipass fixes + +- **typescript-estree:** stabilize EXPERIMENTAL_useProjectService as projectService + +- **eslint-plugin:** remove deprecated no-throw-literal rule + +- **eslint-plugin:** apply initial config changes for v8 + +- **eslint-plugin:** remove no-useless-template-literals + +- **eslint-plugin:** [no-floating-promises] add 'allowForKnownSafeCalls' option + +- **eslint-plugin:** replace ban-types with no-restricted-types, no-unsafe-function-type, no-wrapper-object-types + +- **eslint-plugin:** [no-unused-vars] add `reportUnusedIgnorePattern` option + +- **eslint-plugin:** [no-unused-vars] support `ignoreClassWithStaticInitBlock` + +- **eslint-plugin:** [no-unused-vars] handle comma operator for assignments, treat for-of the same as for-in + +- **eslint-plugin:** [no-unused-vars] report if var used only in typeof + +- **eslint-plugin:** [no-floating-promises] disable checkThenables by default for v8 + +- **rule-tester:** switched to flat config + +- **eslint-plugin:** [no-unnecessary-type-parameters] promote to strict + + +### 🩹 Fixes + +- correct eslint-plugin's peerDependency on parser@8 + +- disable `projectService` in `disabled-type-checked` shared config + +- **typescript-estree:** add TSEnumBody node for TSEnumDeclaration body + +- **eslint-plugin:** include alpha pre-releases in parser peer dependency + +- **eslint-plugin:** correct rules.d.ts types to not rely on non-existent imports + +- **eslint-plugin:** remove duplicate import `RuleModuleWithMetaDocs` + +- **type-utils:** also check declared modules for package names in TypeOrValueSpecifier + +- **eslint-plugin:** [no-unnecessary-template-expression] do not render escaped strings in autofixes + +- **eslint-plugin:** [no-unused-vars] incorporate upstream changes around caught errors report messages + +- **eslint-plugin:** [no-misused-promises] perf: avoid getting types of variables/functions if the annotated type is obviously not a function + +- **rule-tester:** provide Linter a cwd in its constructor + + +### ❤️ Thank You + +- Abraham Guo +- Alfred Ringstad +- auvred +- Brad Zacher +- Christopher Aubut +- Collin Bachman +- James Henry +- Josh Goldberg +- Josh Goldberg ✨ +- Kirk Waiblinger +- StyleShit +- Victor Lin +- Yukihiro Hasegawa + +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.18.0 (2024-07-29) diff --git a/packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md b/packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md index 8276fabd9226..b0fc34b3fc5f 100644 --- a/packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md +++ b/packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md @@ -15,32 +15,32 @@ It lists all TSLint rules along side rules from the ESLint ecosystem that are th ### TypeScript-specific -| TSLint rule | | ESLint rule | -| --------------------------------- | :-: | ---------------------------------------------------- | -| [`adjacent-overload-signatures`] | ✅ | [`@typescript-eslint/adjacent-overload-signatures`] | -| [`ban-ts-ignore`] | ✅ | [`@typescript-eslint/ban-ts-comment`] | -| [`ban-types`] | 🌓 | [`@typescript-eslint/ban-types`][1] | -| [`invalid-void`] | ✅ | [`@typescript-eslint/no-invalid-void-type`] | -| [`member-access`] | ✅ | [`@typescript-eslint/explicit-member-accessibility`] | -| [`member-ordering`] | ✅ | [`@typescript-eslint/member-ordering`] | -| [`no-any`] | ✅ | [`@typescript-eslint/no-explicit-any`] | -| [`no-empty-interface`] | ✅ | [`@typescript-eslint/no-empty-interface`] | -| [`no-import-side-effect`] | 🔌 | [`import/no-unassigned-import`] | -| [`no-inferrable-types`] | ✅ | [`@typescript-eslint/no-inferrable-types`] | -| [`no-internal-module`] | ✅ | [`@typescript-eslint/prefer-namespace-keyword`] | -| [`no-magic-numbers`] | ✅ | [`@typescript-eslint/no-magic-numbers`] | -| [`no-namespace`] | ✅ | [`@typescript-eslint/no-namespace`] | -| [`no-non-null-assertion`] | ✅ | [`@typescript-eslint/no-non-null-assertion`] | -| [`no-parameter-reassignment`] | ✅ | [`no-param-reassign`][no-param-reassign] | -| [`no-reference`] | ✅ | [`@typescript-eslint/triple-slash-reference`] | -| [`no-unnecessary-type-assertion`] | ✅ | [`@typescript-eslint/no-unnecessary-type-assertion`] | -| [`no-var-requires`] | ✅ | [`@typescript-eslint/no-var-requires`] | -| [`only-arrow-functions`] | 🔌 | [`prefer-arrow/prefer-arrow-functions`] | -| [`prefer-for-of`] | ✅ | [`@typescript-eslint/prefer-for-of`] | -| [`promise-function-async`] | ✅ | [`@typescript-eslint/promise-function-async`] | -| [`typedef-whitespace`] | ✅ | [`@typescript-eslint/type-annotation-spacing`] | -| [`typedef`] | ✅ | [`@typescript-eslint/typedef`] | -| [`unified-signatures`] | ✅ | [`@typescript-eslint/unified-signatures`] | +| TSLint rule | | ESLint rule | +| --------------------------------- | :-: | -------------------------------------------------------- | +| [`adjacent-overload-signatures`] | ✅ | [`@typescript-eslint/adjacent-overload-signatures`] | +| [`ban-ts-ignore`] | ✅ | [`@typescript-eslint/ban-ts-comment`] | +| [`ban-types`] | 🌓 | [`@typescript-eslint/no-restricted-types`][1] | +| [`invalid-void`] | ✅ | [`@typescript-eslint/no-invalid-void-type`] | +| [`member-access`] | ✅ | [`@typescript-eslint/explicit-member-accessibility`] | +| [`member-ordering`] | ✅ | [`@typescript-eslint/member-ordering`] | +| [`no-any`] | ✅ | [`@typescript-eslint/no-explicit-any`] | +| [`no-empty-interface`] | ✅ | [`@typescript-eslint/no-empty-object-type`] | +| [`no-import-side-effect`] | 🔌 | [`import/no-unassigned-import`] | +| [`no-inferrable-types`] | ✅ | [`@typescript-eslint/no-inferrable-types`] | +| [`no-internal-module`] | ✅ | [`@typescript-eslint/prefer-namespace-keyword`] | +| [`no-magic-numbers`] | ✅ | [`@typescript-eslint/no-magic-numbers`] | +| [`no-namespace`] | ✅ | [`@typescript-eslint/no-namespace`] | +| [`no-non-null-assertion`] | ✅ | [`@typescript-eslint/no-non-null-assertion`] | +| [`no-parameter-reassignment`] | ✅ | [`no-param-reassign`][no-param-reassign] | +| [`no-reference`] | ✅ | [`@typescript-eslint/triple-slash-reference`] | +| [`no-unnecessary-type-assertion`] | ✅ | [`@typescript-eslint/no-unnecessary-type-assertion`] | +| [`no-var-requires`] | ✅ | [`@typescript-eslint/no-var-requires`] | +| [`only-arrow-functions`] | 🔌 | [`prefer-arrow/prefer-arrow-functions`] | +| [`prefer-for-of`] | ✅ | [`@typescript-eslint/prefer-for-of`] | +| [`promise-function-async`] | ✅ | [`@typescript-eslint/promise-function-async`] | +| [`typedef-whitespace`] | ✅ | [`@typescript-eslint/type-annotation-spacing`] | +| [`typedef`] | ✅ | [`@typescript-eslint/typedef`] | +| [`unified-signatures`] | ✅ | [`@typescript-eslint/unified-signatures`] | [1] The ESLint rule only supports exact string matching, rather than regular expressions
@@ -85,7 +85,7 @@ It lists all TSLint rules along side rules from the ESLint ecosystem that are th | [`no-shadowed-variable`] | 🌟 | [`no-shadow`][no-shadow] | | [`no-sparse-arrays`] | 🌟 | [`no-sparse-arrays`][no-sparse-arrays] | | [`no-string-literal`] | 🌟 | [`dot-notation`][dot-notation] | -| [`no-string-throw`] | ✅ | [`@typescript-eslint/no-throw-literal`] | +| [`no-string-throw`] | ✅ | [`@typescript-eslint/only-throw-literal`] | | [`no-submodule-imports`] | 🌓 | [`import/no-internal-modules`] (slightly different) | | [`no-switch-case-fall-through`] | 🌟 | [`no-fallthrough`][no-fallthrough] | | [`no-tautology-expression`] | 🛑 | N/A | @@ -595,7 +595,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- [`@typescript-eslint/adjacent-overload-signatures`]: https://typescript-eslint.io/rules/adjacent-overload-signatures [`@typescript-eslint/await-thenable`]: https://typescript-eslint.io/rules/await-thenable -[`@typescript-eslint/ban-types`]: https://typescript-eslint.io/rules/ban-types +[`@typescript-eslint/no-restricted-types`]: https://typescript-eslint.io/rules/no-restricted-types [`@typescript-eslint/ban-ts-comment`]: https://typescript-eslint.io/rules/ban-ts-comment [`@typescript-eslint/class-methods-use-this`]: https://typescript-eslint.io/rules/class-methods-use-this [`@typescript-eslint/consistent-type-assertions`]: https://typescript-eslint.io/rules/consistent-type-assertions @@ -604,7 +604,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- [`@typescript-eslint/member-ordering`]: https://typescript-eslint.io/rules/member-ordering [`@typescript-eslint/method-signature-style`]: https://typescript-eslint.io/rules/method-signature-style [`@typescript-eslint/no-explicit-any`]: https://typescript-eslint.io/rules/no-explicit-any -[`@typescript-eslint/no-empty-interface`]: https://typescript-eslint.io/rules/no-empty-interface +[`@typescript-eslint/no-empty-object-type`]: https://typescript-eslint.io/rules/no-empty-object-type [`@typescript-eslint/no-implied-eval`]: https://typescript-eslint.io/rules/no-implied-eval [`@typescript-eslint/no-inferrable-types`]: https://typescript-eslint.io/rules/no-inferrable-types [`@typescript-eslint/prefer-namespace-keyword`]: https://typescript-eslint.io/rules/prefer-namespace-keyword @@ -622,7 +622,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- [`@typescript-eslint/no-unnecessary-boolean-literal-compare`]: https://typescript-eslint.io/rules/no-unnecessary-boolean-literal-compare [`@typescript-eslint/no-misused-new`]: https://typescript-eslint.io/rules/no-misused-new [`@typescript-eslint/no-this-alias`]: https://typescript-eslint.io/rules/no-this-alias -[`@typescript-eslint/no-throw-literal`]: https://typescript-eslint.io/rules/no-throw-literal +[`@typescript-eslint/only-throw-literal`]: https://typescript-eslint.io/rules/only-throw-literal [`@typescript-eslint/no-extraneous-class`]: https://typescript-eslint.io/rules/no-extraneous-class [`@typescript-eslint/no-unused-vars`]: https://typescript-eslint.io/rules/no-unused-vars [`@typescript-eslint/no-use-before-define`]: https://typescript-eslint.io/rules/no-use-before-define diff --git a/packages/eslint-plugin/docs/rules/README.md b/packages/eslint-plugin/docs/rules/README.md index 3cdf72e4c923..75c5723d748a 100644 --- a/packages/eslint-plugin/docs/rules/README.md +++ b/packages/eslint-plugin/docs/rules/README.md @@ -33,9 +33,6 @@ import RulesTable from "@site/src/components/RulesTable"; - Sometimes, it is not safe to automatically fix the code with an auto-fixer. But in these cases, we often have a good guess of what the correct fix should be, and we can provide it as a suggestion to the developer. - `💭 requires type information` refers to whether the rule requires [typed linting](/getting-started/typed-linting). - `🧱 extension rule` means that the rule is an extension of an [core ESLint rule](https://eslint.org/docs/latest/rules) (see [Extension Rules](#extension-rules)). -- `📐 formatting rule` means that the rule has to do with formatting. - - We [strongly recommend against using ESLint for formatting](/users/what-about-formatting). - - Soon, formatting rules will be moved to the [ESLint stylistic plugin](https://eslint.style). - `💀 deprecated rule` means that the rule should no longer be used and will be removed from the plugin in a future version. ## Extension Rules diff --git a/packages/eslint-plugin/docs/rules/ban-types.md b/packages/eslint-plugin/docs/rules/ban-types.md new file mode 100644 index 000000000000..f2aa717a07b2 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/ban-types.md @@ -0,0 +1,22 @@ +:::danger Deprecated + +The old `ban-types` rule encompassed multiple areas of functionality, and so has been split into several rules. + +**[`no-restricted-types`](./no-restricted-types.mdx)** is the new rule for banning a configurable list of type names. +It has no options enabled by default and is akin to rules like [`no-restricted-globals`](https://eslint.org/docs/latest/rules/no-restricted-globals), [`no-restricted-properties`](https://eslint.org/docs/latest/rules/no-restricted-properties), and [`no-restricted-syntax`](https://eslint.org/docs/latest/rules/no-restricted-syntax). + +The default options from `ban-types` are now covered by: + +- **[`no-empty-object-type`](./no-empty-object-type.mdx)**: banning the built-in `{}` type in confusing locations +- **[`no-unsafe-function-type`](./no-unsafe-function-type.mdx)**: banning the built-in `Function` +- **[`no-wrapper-object-types`](./no-wrapper-object-types.mdx)**: banning `Object` and built-in class wrappers such as `Number` + +`ban-types` itself is removed in typescript-eslint v8. +See [Announcing typescript-eslint v8 Beta](/blog/announcing-typescript-eslint-v8-beta) for more details. +::: + + diff --git a/packages/eslint-plugin/docs/rules/ban-types.mdx b/packages/eslint-plugin/docs/rules/ban-types.mdx deleted file mode 100644 index 9ce4d3427c06..000000000000 --- a/packages/eslint-plugin/docs/rules/ban-types.mdx +++ /dev/null @@ -1,143 +0,0 @@ ---- -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. - -:::danger Deprecated -**This rule is deprecated** and will be removed in typescript-eslint@v8. -See _**[Replacement of `ban-types`](/blog/announcing-typescript-eslint-v8-beta#replacement-of-ban-types)**_ for more details. -::: - -Some built-in types have aliases, while some types are considered dangerous or harmful. -It's often a good idea to ban certain types to help with consistency and safety. - -This rule bans specific types and can suggest alternatives. -Note that it does not ban the corresponding runtime objects from being used. - -## Examples - -Examples of code with the default options: - - - - -```ts -// use lower-case primitives for consistency -const str: String = 'foo'; -const bool: Boolean = true; -const num: Number = 1; -const symb: Symbol = Symbol('foo'); -const bigInt: BigInt = 1n; - -// use a proper function type -const func: Function = () => 1; - -// use safer object types -const lowerObj: Object = {}; -const capitalObj: Object = { a: 'string' }; - -const curly1: {} = 1; -const curly2: {} = { a: 'string' }; -``` - - - - -```ts -// use lower-case primitives for consistency -const str: string = 'foo'; -const bool: boolean = true; -const num: number = 1; -const symb: symbol = Symbol('foo'); -const bigInt: bigint = 1n; - -// use a proper function type -const func: () => number = () => 1; - -// use safer object types -const lowerObj: object = {}; -const capitalObj: { a: string } = { a: 'string' }; - -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: - -- Don't use the upper-case primitive types, you should use the lower-case types for consistency. -- Avoid the `Function` type, as it provides little safety for the following reasons: - - It provides no type safety when calling the value, which means it's easy to provide the wrong arguments. - - It accepts class declarations, which will fail when called, as they are called without the `new` keyword. -- Avoid the `Object` and `{}` types, as they mean "any non-nullish value". - - This is a point of confusion for many developers, who think it means "any object type". - - See [this comment for more information](https://github.com/typescript-eslint/typescript-eslint/issues/2063#issuecomment-675156492). - -
-Default Options - -{/* Inject default options */} - -
- -### `types` - -An object whose keys are the types you want to ban, and the values are error messages. - -The type can either be a type name literal (`Foo`), a type name with generic parameter instantiation(s) (`Foo`), the empty object literal (`{}`), or the empty tuple type (`[]`). - -The values can be: - -- A string, which is the error message to be reported; or -- `false` to specifically un-ban this type (useful when you are using `extendDefaults`); or -- An object with the following properties: - - `message: string` - the message to display when the type is matched. - - `fixWith?: string` - a string to replace the banned type with when the fixer is run. If this is omitted, no fix will be done. - - `suggest?: string[]` - a list of suggested replacements for the banned type. - -### `extendDefaults` - -If you're specifying custom `types`, you can set this to `true` to extend the default `types` configuration. This is a convenience option to save you copying across the defaults when adding another type. - -If this is `false`, the rule will _only_ use the types defined in your configuration. - -Example configuration: - -```jsonc -{ - "@typescript-eslint/ban-types": [ - "error", - { - "types": { - // add a custom message to help explain why not to use it - "Foo": "Don't use Foo because it is unsafe", - - // add a custom message, AND tell the plugin how to fix it - "OldAPI": { - "message": "Use NewAPI instead", - "fixWith": "NewAPI", - }, - - // un-ban a type that's banned by default - "{}": false, - }, - "extendDefaults": true, - }, - ], -} -``` - -## When Not To Use It - -If your project is a rare one that intentionally deals with the class equivalents of primitives, it might not be worthwhile to enable the default `ban-types` options. -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. diff --git a/packages/eslint-plugin/docs/rules/block-spacing.md b/packages/eslint-plugin/docs/rules/block-spacing.md new file mode 100644 index 000000000000..45d53728c3f8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/block-spacing.md @@ -0,0 +1,11 @@ +:::danger Deprecated + +This rule has been moved to the [ESLint stylistic plugin](https://eslint.style). +See [#8072](https://github.com/typescript-eslint/typescript-eslint/issues/8072) and [#8074](https://github.com/typescript-eslint/typescript-eslint/issues/8074) for more information. + +::: + + diff --git a/packages/eslint-plugin/docs/rules/block-spacing.mdx b/packages/eslint-plugin/docs/rules/block-spacing.mdx deleted file mode 100644 index de933a031de5..000000000000 --- a/packages/eslint-plugin/docs/rules/block-spacing.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -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. - -This rule extends the base [`eslint/block-spacing`](https://eslint.org/docs/rules/block-spacing) rule. -This version adds support for TypeScript related blocks (interfaces, object type literals and enums). diff --git a/packages/eslint-plugin/docs/rules/brace-style.md b/packages/eslint-plugin/docs/rules/brace-style.md new file mode 100644 index 000000000000..45d53728c3f8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/brace-style.md @@ -0,0 +1,11 @@ +:::danger Deprecated + +This rule has been moved to the [ESLint stylistic plugin](https://eslint.style). +See [#8072](https://github.com/typescript-eslint/typescript-eslint/issues/8072) and [#8074](https://github.com/typescript-eslint/typescript-eslint/issues/8074) for more information. + +::: + + diff --git a/packages/eslint-plugin/docs/rules/brace-style.mdx b/packages/eslint-plugin/docs/rules/brace-style.mdx deleted file mode 100644 index a1e4cb18fc7c..000000000000 --- a/packages/eslint-plugin/docs/rules/brace-style.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -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. - -This rule extends the base [`eslint/brace-style`](https://eslint.org/docs/rules/brace-style) rule. -It adds support for `enum`, `interface`, `namespace` and `module` declarations. diff --git a/packages/eslint-plugin/docs/rules/comma-dangle.md b/packages/eslint-plugin/docs/rules/comma-dangle.md new file mode 100644 index 000000000000..45d53728c3f8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/comma-dangle.md @@ -0,0 +1,11 @@ +:::danger Deprecated + +This rule has been moved to the [ESLint stylistic plugin](https://eslint.style). +See [#8072](https://github.com/typescript-eslint/typescript-eslint/issues/8072) and [#8074](https://github.com/typescript-eslint/typescript-eslint/issues/8074) for more information. + +::: + + diff --git a/packages/eslint-plugin/docs/rules/comma-dangle.mdx b/packages/eslint-plugin/docs/rules/comma-dangle.mdx deleted file mode 100644 index fa934f5ee4bf..000000000000 --- a/packages/eslint-plugin/docs/rules/comma-dangle.mdx +++ /dev/null @@ -1,23 +0,0 @@ ---- -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. - -This rule extends the base [`eslint/comma-dangle`](https://eslint.org/docs/rules/comma-dangle) rule. -It adds support for TypeScript syntax. - -See the [ESLint documentation](https://eslint.org/docs/rules/comma-dangle) for more details on the `comma-dangle` rule. - -## Options - -In addition to the options supported by the `comma-dangle` rule in ESLint core, the rule adds the following options: - -- `"enums"` is for trailing comma in enum. (e.g. `enum Foo = {Bar,}`) -- `"generics"` is for trailing comma in generic. (e.g. `function foo() {}`) -- `"tuples"` is for trailing comma in tuple. (e.g. `type Foo = [string,]`) diff --git a/packages/eslint-plugin/docs/rules/comma-spacing.md b/packages/eslint-plugin/docs/rules/comma-spacing.md new file mode 100644 index 000000000000..45d53728c3f8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/comma-spacing.md @@ -0,0 +1,11 @@ +:::danger Deprecated + +This rule has been moved to the [ESLint stylistic plugin](https://eslint.style). +See [#8072](https://github.com/typescript-eslint/typescript-eslint/issues/8072) and [#8074](https://github.com/typescript-eslint/typescript-eslint/issues/8074) for more information. + +::: + + diff --git a/packages/eslint-plugin/docs/rules/comma-spacing.mdx b/packages/eslint-plugin/docs/rules/comma-spacing.mdx deleted file mode 100644 index 249f8933e656..000000000000 --- a/packages/eslint-plugin/docs/rules/comma-spacing.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -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. - -This rule extends the base [`eslint/comma-spacing`](https://eslint.org/docs/rules/comma-spacing) rule. -It adds support for trailing comma in a types parameters list. diff --git a/packages/eslint-plugin/docs/rules/func-call-spacing.md b/packages/eslint-plugin/docs/rules/func-call-spacing.md new file mode 100644 index 000000000000..45d53728c3f8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/func-call-spacing.md @@ -0,0 +1,11 @@ +:::danger Deprecated + +This rule has been moved to the [ESLint stylistic plugin](https://eslint.style). +See [#8072](https://github.com/typescript-eslint/typescript-eslint/issues/8072) and [#8074](https://github.com/typescript-eslint/typescript-eslint/issues/8074) for more information. + +::: + + diff --git a/packages/eslint-plugin/docs/rules/func-call-spacing.mdx b/packages/eslint-plugin/docs/rules/func-call-spacing.mdx deleted file mode 100644 index defc8d976d7d..000000000000 --- a/packages/eslint-plugin/docs/rules/func-call-spacing.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -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. - -This rule extends the base [`eslint/func-call-spacing`](https://eslint.org/docs/rules/func-call-spacing) rule. -It adds support for generic type parameters on function calls. diff --git a/packages/eslint-plugin/docs/rules/indent.md b/packages/eslint-plugin/docs/rules/indent.md new file mode 100644 index 000000000000..45d53728c3f8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/indent.md @@ -0,0 +1,11 @@ +:::danger Deprecated + +This rule has been moved to the [ESLint stylistic plugin](https://eslint.style). +See [#8072](https://github.com/typescript-eslint/typescript-eslint/issues/8072) and [#8074](https://github.com/typescript-eslint/typescript-eslint/issues/8074) for more information. + +::: + + diff --git a/packages/eslint-plugin/docs/rules/indent.mdx b/packages/eslint-plugin/docs/rules/indent.mdx deleted file mode 100644 index cf74735986a9..000000000000 --- a/packages/eslint-plugin/docs/rules/indent.mdx +++ /dev/null @@ -1,21 +0,0 @@ ---- -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. - -## Warning - -:::warning - -Please read [Issue #1824: Problems with the indent rule](https://github.com/typescript-eslint/typescript-eslint/issues/1824) before using this rule! - -::: - -This rule extends the base [`eslint/indent`](https://eslint.org/docs/rules/indent) rule. -It adds support for TypeScript nodes. diff --git a/packages/eslint-plugin/docs/rules/key-spacing.md b/packages/eslint-plugin/docs/rules/key-spacing.md new file mode 100644 index 000000000000..45d53728c3f8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/key-spacing.md @@ -0,0 +1,11 @@ +:::danger Deprecated + +This rule has been moved to the [ESLint stylistic plugin](https://eslint.style). +See [#8072](https://github.com/typescript-eslint/typescript-eslint/issues/8072) and [#8074](https://github.com/typescript-eslint/typescript-eslint/issues/8074) for more information. + +::: + + diff --git a/packages/eslint-plugin/docs/rules/key-spacing.mdx b/packages/eslint-plugin/docs/rules/key-spacing.mdx deleted file mode 100644 index da0ebae30511..000000000000 --- a/packages/eslint-plugin/docs/rules/key-spacing.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -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. - -This rule extends the base [`eslint/key-spacing`](https://eslint.org/docs/rules/key-spacing) rule. -It adds support for type annotations on interfaces, classes and type literals properties. diff --git a/packages/eslint-plugin/docs/rules/keyword-spacing.md b/packages/eslint-plugin/docs/rules/keyword-spacing.md new file mode 100644 index 000000000000..45d53728c3f8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/keyword-spacing.md @@ -0,0 +1,11 @@ +:::danger Deprecated + +This rule has been moved to the [ESLint stylistic plugin](https://eslint.style). +See [#8072](https://github.com/typescript-eslint/typescript-eslint/issues/8072) and [#8074](https://github.com/typescript-eslint/typescript-eslint/issues/8074) for more information. + +::: + + diff --git a/packages/eslint-plugin/docs/rules/keyword-spacing.mdx b/packages/eslint-plugin/docs/rules/keyword-spacing.mdx deleted file mode 100644 index c092b46a84f6..000000000000 --- a/packages/eslint-plugin/docs/rules/keyword-spacing.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -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. - -This rule extends the base [`eslint/keyword-spacing`](https://eslint.org/docs/rules/keyword-spacing) rule. -It adds support for generic type parameters on function calls. diff --git a/packages/eslint-plugin/docs/rules/lines-around-comment.md b/packages/eslint-plugin/docs/rules/lines-around-comment.md new file mode 100644 index 000000000000..45d53728c3f8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/lines-around-comment.md @@ -0,0 +1,11 @@ +:::danger Deprecated + +This rule has been moved to the [ESLint stylistic plugin](https://eslint.style). +See [#8072](https://github.com/typescript-eslint/typescript-eslint/issues/8072) and [#8074](https://github.com/typescript-eslint/typescript-eslint/issues/8074) for more information. + +::: + + diff --git a/packages/eslint-plugin/docs/rules/lines-around-comment.mdx b/packages/eslint-plugin/docs/rules/lines-around-comment.mdx deleted file mode 100644 index bf612ceaaea8..000000000000 --- a/packages/eslint-plugin/docs/rules/lines-around-comment.mdx +++ /dev/null @@ -1,28 +0,0 @@ ---- -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. - -This rule extends the base [`eslint/lines-around-comment`](https://eslint.org/docs/rules/lines-around-comment) rule. -It adds support for TypeScript syntax. - -See the [ESLint documentation](https://eslint.org/docs/rules/lines-around-comment) for more details on the `lines-around-comment` rule. - -## Options - -In addition to the options supported by the `lines-around-comment` rule in ESLint core, the rule adds the following options: - -- `allowEnumEnd: true` doesn't require a blank line after an enum body block end -- `allowEnumStart: true` doesn't require a blank line before an enum body block start -- `allowInterfaceEnd: true` doesn't require a blank line before an interface body block end -- `allowInterfaceStart: true` doesn't require a blank line after an interface body block start -- `allowModuleEnd: true` doesn't require a blank line before a module body block end -- `allowModuleStart: true` doesn't require a blank line after a module body block start -- `allowTypeEnd: true` doesn't require a blank line before a type literal block end -- `allowTypeStart: true` doesn't require a blank line after a type literal block start diff --git a/packages/eslint-plugin/docs/rules/lines-between-class-members.md b/packages/eslint-plugin/docs/rules/lines-between-class-members.md new file mode 100644 index 000000000000..45d53728c3f8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/lines-between-class-members.md @@ -0,0 +1,11 @@ +:::danger Deprecated + +This rule has been moved to the [ESLint stylistic plugin](https://eslint.style). +See [#8072](https://github.com/typescript-eslint/typescript-eslint/issues/8072) and [#8074](https://github.com/typescript-eslint/typescript-eslint/issues/8074) for more information. + +::: + + diff --git a/packages/eslint-plugin/docs/rules/lines-between-class-members.mdx b/packages/eslint-plugin/docs/rules/lines-between-class-members.mdx deleted file mode 100644 index 0112c47cbfd6..000000000000 --- a/packages/eslint-plugin/docs/rules/lines-between-class-members.mdx +++ /dev/null @@ -1,56 +0,0 @@ ---- -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. - -This rule extends the base [`eslint/lines-between-class-members`](https://eslint.org/docs/rules/lines-between-class-members) rule. -It adds support for ignoring overload methods in a class. - -## Options - -In addition to the options supported by the `lines-between-class-members` rule in ESLint core, the rule adds the following options: - -- Object option: - - - `"exceptAfterOverload": true` (default) - Skip checking empty lines after overload class members - - `"exceptAfterOverload": false` - **do not** skip checking empty lines after overload class members - -### `exceptAfterOverload: true` - -Examples of **correct** code for the `{ "exceptAfterOverload": true }` option: - -```ts option='"always", { "exceptAfterOverload": true }' showPlaygroundButton -class foo { - bar(a: string): void; - bar(a: string, b: string): void; - bar(a: string, b: string) {} - - baz() {} - - qux() {} -} -``` - -### `exceptAfterOverload: false` - -Examples of **correct** code for the `{ "exceptAfterOverload": false }` option: - -```ts option='"always", { "exceptAfterOverload": false }' showPlaygroundButton -class foo { - bar(a: string): void; - - bar(a: string, b: string): void; - - bar(a: string, b: string) {} - - baz() {} - - qux() {} -} -``` diff --git a/packages/eslint-plugin/docs/rules/member-delimiter-style.md b/packages/eslint-plugin/docs/rules/member-delimiter-style.md new file mode 100644 index 000000000000..45d53728c3f8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/member-delimiter-style.md @@ -0,0 +1,11 @@ +:::danger Deprecated + +This rule has been moved to the [ESLint stylistic plugin](https://eslint.style). +See [#8072](https://github.com/typescript-eslint/typescript-eslint/issues/8072) and [#8074](https://github.com/typescript-eslint/typescript-eslint/issues/8074) for more information. + +::: + + diff --git a/packages/eslint-plugin/docs/rules/member-delimiter-style.mdx b/packages/eslint-plugin/docs/rules/member-delimiter-style.mdx deleted file mode 100644 index 7e3c5547cbc1..000000000000 --- a/packages/eslint-plugin/docs/rules/member-delimiter-style.mdx +++ /dev/null @@ -1,170 +0,0 @@ ---- -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): - name: string; - - // Commas (JSON-like): - name: string, - - // Line breaks (none): - name: string -} -``` - -For code readability, it's generally best to use the same style consistently in your codebase. - -This rule enforces keeping to one configurable code style. -It can also standardize the presence (or absence) of a delimiter in the last member of a construct, as well as a separate delimiter syntax for single line declarations. - -## Options - -Default config: - -```json -{ - "multiline": { - "delimiter": "semi", - "requireLast": true - }, - "singleline": { - "delimiter": "semi", - "requireLast": false - }, - "multilineDetection": "brackets" -} -``` - -`multiline` config only applies to multiline `interface`/`type` definitions. -`singleline` config only applies to single line `interface`/`type` definitions. -The two configs are entirely separate, and do not effect one another. - -`multilineDetection` determines what counts as multiline - -- `"brackets"` (default) any newlines in the type or interface make it multiline. -- `"last-member"` if the last member of the interface is on the same line as the last bracket, it is counted as a single line. - -### `delimiter` - -Accepts three values (or two for `singleline`): - -- `comma` - each member should be delimited with a comma (`,`). -- `semi` - each member should be delimited with a semicolon (`;`). -- `none` - each member should be delimited with nothing. - -:::note -`none` is not an option for `singleline` because having no delimiter between members on a single line is a syntax error in TS. -::: - -### `requireLast` - -Determines whether or not the last member in the `interface`/`type` should have a delimiter: - -- `true` - the last member **_must_** have a delimiter. -- `false` - the last member **_must not_** have a delimiter. - -### `overrides` - -Allows you to specify options specifically for either `interface`s or `type` definitions / inline `type`s. - -For example, to require commas for `type`s, and semicolons for multiline `interface`s: - -```json -{ - "multiline": { - "delimiter": "comma", - "requireLast": true - }, - "singleline": { - "delimiter": "comma", - "requireLast": true - }, - "overrides": { - "interface": { - "multiline": { - "delimiter": "semi", - "requireLast": true - } - } - } -} -``` - -## Examples - -Examples of code for this rule with the default config: - - - - -{/* prettier-ignore */} -```ts -// missing semicolon delimiter -interface Foo { - name: string - greet(): string -} - -// using incorrect delimiter -interface Bar { - name: string, - greet(): string, -} - -// missing last member delimiter -interface Baz { - name: string; - greet(): string -} - -// incorrect delimiter -type FooBar = { name: string, greet(): string } - -// last member should not have delimiter -type FooBar = { name: string; greet(): string; } -``` - - - - -{/* prettier-ignore */} -```ts -interface Foo { - name: string; - greet(): string; -} - -interface Foo { name: string } - -type Bar = { - name: string; - greet(): string; -} - -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. - -However, keep in mind that inconsistent style can harm readability in a project. -We recommend picking a single option for this rule that works best for your project. diff --git a/packages/eslint-plugin/docs/rules/no-empty-interface.mdx b/packages/eslint-plugin/docs/rules/no-empty-interface.mdx index ad240237ddb8..733d8b39ebe6 100644 --- a/packages/eslint-plugin/docs/rules/no-empty-interface.mdx +++ b/packages/eslint-plugin/docs/rules/no-empty-interface.mdx @@ -9,6 +9,12 @@ import TabItem from '@theme/TabItem'; > > See **https://typescript-eslint.io/rules/no-empty-interface** for documentation. +:::danger Deprecated + +This rule has been deprecated in favour of the more comprehensive [`@typescript-eslint/no-empty-object-type`](./no-empty-object-type.mdx) rule. + +::: + An empty interface in TypeScript does very little: any non-nullable value is assignable to `{}`. Using an empty interface is often a sign of programmer error, such as misunderstanding the concept of `{}` or forgetting to fill in fields. @@ -61,3 +67,7 @@ interface Baz extends Foo, Bar {} ## When Not To Use It If you don't care about having empty/meaningless interfaces, then you will not need this rule. + +## Related To + +- [`no-empty-object-type`](./no-empty-object-type.mdx) diff --git a/packages/eslint-plugin/docs/rules/no-empty-object-type.mdx b/packages/eslint-plugin/docs/rules/no-empty-object-type.mdx index 6a0e27bd8834..8bf941a66615 100644 --- a/packages/eslint-plugin/docs/rules/no-empty-object-type.mdx +++ b/packages/eslint-plugin/docs/rules/no-empty-object-type.mdx @@ -89,19 +89,9 @@ Whether to allow empty interfaces, as one of: - `'never'` _(default)_: to never allow interfaces with no fields - `'with-single-extends'`: to allow empty interfaces that `extend` from a single base interface -Examples of code for this rule with `{ allowInterfaces: 'with-single-extends' }`: +Examples of **correct** code for this rule with `{ allowInterfaces: 'with-single-extends' }`: - - - -```ts option='{ "allowInterfaces": "with-single-extends" }' -interface Foo {} -``` - - - - -```ts option='{ "allowInterfaces": "with-single-extends" }' +```ts option='{ "allowInterfaces": "with-single-extends" }' showPlaygroundButton interface Base { value: boolean; } @@ -109,9 +99,6 @@ interface Base { interface Derived extends Base {} ``` - - - ### `allowObjectTypes` Whether to allow empty object type literals, as one of: @@ -119,25 +106,6 @@ Whether to allow empty object type literals, as one of: - `'always'`: to always allow object type literals with no fields - `'never'` _(default)_: to never allow object type literals with no fields -Examples of code for this rule with `{ allowObjectTypes: 'always' }`: - - - - -```ts option='{ "allowObjectTypes": "always" }' -interface Base {} -``` - - - - -```ts option='{ "allowObjectTypes": "always" }' -type Base = {}; -``` - - - - ### `allowWithName` A stringified regular expression to allow interfaces and object type aliases with the configured name. @@ -148,7 +116,7 @@ Examples of code for this rule with `{ allowWithName: 'Props$' }`: -```ts option='{ "allowWithName": "Props$" }' +```ts option='{ "allowWithName": "Props$" }' showPlaygroundButton interface InterfaceValue {} type TypeValue = {}; @@ -157,7 +125,7 @@ type TypeValue = {}; -```ts option='{ "allowWithName": "Props$" }' +```ts option='{ "allowWithName": "Props$" }' showPlaygroundButton interface InterfaceProps {} type TypeProps = {}; diff --git a/packages/eslint-plugin/docs/rules/no-extra-parens.md b/packages/eslint-plugin/docs/rules/no-extra-parens.md new file mode 100644 index 000000000000..45d53728c3f8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-extra-parens.md @@ -0,0 +1,11 @@ +:::danger Deprecated + +This rule has been moved to the [ESLint stylistic plugin](https://eslint.style). +See [#8072](https://github.com/typescript-eslint/typescript-eslint/issues/8072) and [#8074](https://github.com/typescript-eslint/typescript-eslint/issues/8074) for more information. + +::: + + diff --git a/packages/eslint-plugin/docs/rules/no-extra-parens.mdx b/packages/eslint-plugin/docs/rules/no-extra-parens.mdx deleted file mode 100644 index 4c56e6ecd279..000000000000 --- a/packages/eslint-plugin/docs/rules/no-extra-parens.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -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. - -This rule extends the base [`eslint/no-extra-parens`](https://eslint.org/docs/rules/no-extra-parens) rule. -It adds support for TypeScript type assertions. diff --git a/packages/eslint-plugin/docs/rules/no-extra-semi.md b/packages/eslint-plugin/docs/rules/no-extra-semi.md new file mode 100644 index 000000000000..45d53728c3f8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-extra-semi.md @@ -0,0 +1,11 @@ +:::danger Deprecated + +This rule has been moved to the [ESLint stylistic plugin](https://eslint.style). +See [#8072](https://github.com/typescript-eslint/typescript-eslint/issues/8072) and [#8074](https://github.com/typescript-eslint/typescript-eslint/issues/8074) for more information. + +::: + + diff --git a/packages/eslint-plugin/docs/rules/no-extra-semi.mdx b/packages/eslint-plugin/docs/rules/no-extra-semi.mdx deleted file mode 100644 index d59534dcfc60..000000000000 --- a/packages/eslint-plugin/docs/rules/no-extra-semi.mdx +++ /dev/null @@ -1,15 +0,0 @@ ---- -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. - -This rule extends the base [`eslint/no-extra-semi`](https://eslint.org/docs/rules/no-extra-semi) rule. -It adds support for class properties. - -Note that this rule is classified as a "Suggestion" rule instead of a "Layout & Formatting" rule because [adding extra semi-colons actually changes the AST of the program](https://typescript-eslint.io/play/#ts=5.1.6&showAST=es&fileType=.ts&code=MYewdgzgLgBAHjAvDAjAbg0A&eslintrc=N4KABGBEBOCuA2BTAzpAXGUEKQHYHsBaRADwBdoBDQ5RAWwEt0p8AzVyAGnG0gAEyATwAOKAMbQGwssWTwGuMgHoCxclRr0mGSImjR80SDwC%2BIE0A&tsconfig=&tokens=false). With that said, modern TypeScript formatters will remove extra semi-colons automatically during the formatting process. Thus, if you [use a formatter](/users/what-about-formatting), then enabling this rule is probably unnecessary. diff --git a/packages/eslint-plugin/docs/rules/no-floating-promises.mdx b/packages/eslint-plugin/docs/rules/no-floating-promises.mdx index f2cb066ceb2d..bd814ac63dcd 100644 --- a/packages/eslint-plugin/docs/rules/no-floating-promises.mdx +++ b/packages/eslint-plugin/docs/rules/no-floating-promises.mdx @@ -129,10 +129,6 @@ await createMyThenable(); -:::info -This option is enabled by default in v7 but will be turned off by default in v8. -::: - ### `ignoreVoid` This option, which is `true` by default, allows you to stop the rule reporting promises consumed with void operator. @@ -225,6 +221,44 @@ returnsSafePromise();
+### `allowForKnownSafeCalls` + +This option allows marking specific functions as "safe" to be called to create floating Promises. +For example, you may need to do this in the case of libraries whose APIs may be called without handling the resultant Promises. + +This option takes the same array format as [`allowForKnownSafePromises`](#allowForKnownSafePromises). + +Examples of code for this rule with: + +```json +{ + "allowForKnownSafeCalls": [ + { "from": "file", "name": "safe", "path": "input.ts" } + ] +} +``` + + + + +```ts option='{"allowForKnownSafeCalls":[{"from":"file","name":"safe","path":"input.ts"}]}' +declare function unsafe(...args: unknown[]): Promise; + +unsafe('...', () => {}); +``` + + + + +```ts option='{"allowForKnownSafeCalls":[{"from":"file","name":"safe","path":"input.ts"}]}' skipValidation +declare function safe(...args: unknown[]): Promise; + +safe('...', () => {}); +``` + + + + ## When Not To Use It This rule can be difficult to enable on large existing projects that set up many floating Promises. diff --git a/packages/eslint-plugin/docs/rules/no-loss-of-precision.mdx b/packages/eslint-plugin/docs/rules/no-loss-of-precision.mdx index 07fb1f0deb56..77f48255ae4a 100644 --- a/packages/eslint-plugin/docs/rules/no-loss-of-precision.mdx +++ b/packages/eslint-plugin/docs/rules/no-loss-of-precision.mdx @@ -9,5 +9,9 @@ import TabItem from '@theme/TabItem'; > > See **https://typescript-eslint.io/rules/no-loss-of-precision** for documentation. -This rule extends the base [`eslint/no-loss-of-precision`](https://eslint.org/docs/rules/no-loss-of-precision) rule. -It adds support for [numeric separators](https://github.com/tc39/proposal-numeric-separator). +:::danger Deprecated + +This rule has been deprecated because the base [`eslint/no-loss-of-precision`](https://eslint.org/docs/rules/no-loss-of-precision) rule added support for [numeric separators](https://github.com/tc39/proposal-numeric-separator). +There is no longer any need to use this extension rule. + +::: diff --git a/packages/eslint-plugin/docs/rules/no-require-imports.mdx b/packages/eslint-plugin/docs/rules/no-require-imports.mdx index 5af39f233cf5..4512c2693d95 100644 --- a/packages/eslint-plugin/docs/rules/no-require-imports.mdx +++ b/packages/eslint-plugin/docs/rules/no-require-imports.mdx @@ -38,7 +38,7 @@ import * as lib3 from 'lib3'; ### `allow` -A array of strings. These strings will be compiled into regular expressions with the `u` flag and be used to test against the imported path. A common use case is to allow importing `package.json`. This is because `package.json` commonly lives outside of the TS root directory, so statically importing it would lead to root directory conflicts, especially with `resolveJsonModule` enabled. You can also use it to allow importing any JSON if your environment doesn't support JSON modules, or use it for other cases where `import` statements cannot work. +An array of strings. These strings will be compiled into regular expressions with the `u` flag and be used to test against the imported path. A common use case is to allow importing `package.json`. This is because `package.json` commonly lives outside of the TS root directory, so statically importing it would lead to root directory conflicts, especially with `resolveJsonModule` enabled. You can also use it to allow importing any JSON if your environment doesn't support JSON modules, or use it for other cases where `import` statements cannot work. With `{allow: ['/package\\.json$']}`: @@ -59,6 +59,33 @@ console.log(require('../package.json').version); +### `allowAsImport` + +When set to `true`, the `import x = require(...)` declaration won't be reported. +This is useful if you use certain module options that require strict CommonJS interop semantics. + +With `{allowAsImport: true}`: + + + + +```ts option='{ "allowAsImport": true }' +var foo = require('foo'); +const foo = require('foo'); +let foo = require('foo'); +``` + + + + +```ts option='{ "allowAsImport": true }' +import foo = require('foo'); +import foo from 'foo'; +``` + + + + ## When Not To Use It If your project frequently uses older CommonJS `require`s, then this rule might not be applicable to you. diff --git a/packages/eslint-plugin/docs/rules/no-restricted-types.mdx b/packages/eslint-plugin/docs/rules/no-restricted-types.mdx new file mode 100644 index 000000000000..20732174929e --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-restricted-types.mdx @@ -0,0 +1,71 @@ +--- +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/no-restricted-types** for documentation. + +It can sometimes be useful to ban specific types from being used in type annotations. +For example, a project might be migrating from using one type to another, and want to ban references to the old type. + +This rule can be configured to ban a list of specific types and can suggest alternatives. +Note that it does not ban the corresponding runtime objects from being used. + +## Options + +### `types` + +An object whose keys are the types you want to ban, and the values are error messages. + +The type can either be a type name literal (`OldType`) or a a type name with generic parameter instantiation(s) (`OldType`). + +The values can be: + +- A string, which is the error message to be reported; or +- `false` to specifically un-ban this type (useful when you are using `extendDefaults`); or +- An object with the following properties: + - `message: string`: the message to display when the type is matched. + - `fixWith?: string`: a string to replace the banned type with when the fixer is run. If this is omitted, no fix will be done. + - `suggest?: string[]`: a list of suggested replacements for the banned type. + +Example configuration: + +```jsonc +{ + "@typescript-eslint/no-restricted-types": [ + "error", + { + "types": { + // add a custom message to help explain why not to use it + "OldType": "Don't use OldType because it is unsafe", + + // add a custom message, and tell the plugin how to fix it + "OldAPI": { + "message": "Use NewAPI instead", + "fixWith": "NewAPI", + }, + + // add a custom message, and tell the plugin how to suggest a fix + "SoonToBeOldAPI": { + "message": "Use NewAPI instead", + "suggest": ["NewAPIOne", "NewAPITwo"], + }, + }, + }, + ], +} +``` + +## When Not To Use It + +If you have no need to ban specific types from being used in type annotations, you don't need this rule. + +## Related To + +- [`no-empty-object-type`](./no-empty-object-type.mdx) +- [`no-unsafe-function-type`](./no-unsafe-function-type.mdx) +- [`no-wrapper-object-types`](./no-wrapper-object-types.mdx) diff --git a/packages/eslint-plugin/docs/rules/no-throw-literal.mdx b/packages/eslint-plugin/docs/rules/no-throw-literal.mdx deleted file mode 100644 index 9bd9481ffa15..000000000000 --- a/packages/eslint-plugin/docs/rules/no-throw-literal.mdx +++ /dev/null @@ -1,25 +0,0 @@ ---- -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. - -It is considered good practice to only `throw` the `Error` object itself or an object using the `Error` object as base objects for user-defined exceptions. -The fundamental benefit of `Error` objects is that they automatically keep track of where they were built and originated. - -This rule restricts what can be thrown as an exception. - -:::warning -This rule is being renamed to [`only-throw-error`](./only-throw-error.mdx). -The current name, `no-throw-literal`, will be removed in a future major version of typescript-eslint. - -When it was first created, this rule only prevented literals from being thrown (hence the name), but it has now been expanded to only allow expressions which have a possibility of being an `Error` object. -With the `allowThrowingAny` and `allowThrowingUnknown` options, it can be configured to only allow throwing values which are guaranteed to be an instance of `Error`. -::: - -{/* Intentionally Omitted: When Not To Use It */} diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-template-expression.mdx b/packages/eslint-plugin/docs/rules/no-unnecessary-template-expression.mdx index 9db56432d931..1fe39d412dd1 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-template-expression.mdx +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-template-expression.mdx @@ -14,8 +14,6 @@ This rule reports template literals that contain substitution expressions (also :::info[Migration from `no-useless-template-literals`] This rule was formerly known as [`no-useless-template-literals`](./no-useless-template-literals.mdx). -We encourage users to migrate to the new name, `no-unnecessary-template-expression`, as the old name will be removed in a future major version of typescript-eslint. - The new name is a drop-in replacement with identical functionality. ::: diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-type-parameters.mdx b/packages/eslint-plugin/docs/rules/no-unnecessary-type-parameters.mdx index 130c40b1ce04..79eb4b5d4936 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-type-parameters.mdx +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-type-parameters.mdx @@ -18,10 +18,9 @@ It can usually be replaced with explicit types such as `unknown`. At best unnecessary type parameters make code harder to read. At worst they can be used to disguise unsafe type assertions. -:::warning Early Stage -This rule was recently added to typescript-eslint and still considered experimental. -It might change significantly between minor versions. -Please try it out and give us feedback! +:::warning +This rule was recently added, and has a surprising amount of hidden complexity compared to most of our rules. If you encounter unexpected behavior with it, please check closely the [Limitations](#limitations) section below and our [issue tracker](https://github.com/typescript-eslint/typescript-eslint/issues?q=is%3Aissue+no-unnecessary-type-parameters). +If you don't see your case covered, please [reach out to us](https://typescript-eslint.io/contributing/issues)! ::: ## Examples diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-function-type.mdx b/packages/eslint-plugin/docs/rules/no-unsafe-function-type.mdx index c9acd1dc3862..ea7b60794e4e 100644 --- a/packages/eslint-plugin/docs/rules/no-unsafe-function-type.mdx +++ b/packages/eslint-plugin/docs/rules/no-unsafe-function-type.mdx @@ -59,5 +59,5 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates ## Related To - [`no-empty-object-type`](./no-empty-object-type.mdx) -- [`ban-types`](./ban-types.mdx) +- [`no-restricted-types`](./no-restricted-types.mdx) - [`no-wrapper-object-types`](./no-wrapper-object-types.mdx) diff --git a/packages/eslint-plugin/docs/rules/no-useless-template-literals.mdx b/packages/eslint-plugin/docs/rules/no-useless-template-literals.mdx index b245bc3a7b9e..812db678b319 100644 --- a/packages/eslint-plugin/docs/rules/no-useless-template-literals.mdx +++ b/packages/eslint-plugin/docs/rules/no-useless-template-literals.mdx @@ -1,23 +1,5 @@ ---- -description: 'Disallow unnecessary template expressions.' ---- +:::danger Deprecated -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; +This rule has been renamed to [`no-unnecessary-template-expression`](./no-unnecessary-template-expression.mdx). See [#8544](https://github.com/typescript-eslint/typescript-eslint/issues/8544) for more information. -> 🛑 This file is source code, not the primary documentation location! 🛑 -> -> See **https://typescript-eslint.io/rules/no-useless-template-literals** for documentation. - -This rule reports template literals that contain substitution expressions (also variously referred to as embedded expressions or string interpolations) that are unnecessary and can be simplified. - -:::warning -This rule is being renamed to [`no-unnecessary-template-expression`](./no-unnecessary-template-expression.mdx). -The current name, `no-useless-template-literals`, will be removed in a future major version of typescript-eslint. - -After the creation of this rule, it was realized that the name `no-useless-template-literals` could be misleading, seeing as this rule only targets template literals with substitution expressions. -In particular, it does _not_ aim to flag useless template literals that look like `` `this` `` and could be simplified to `"this"`. -If you are looking for such a rule, you can configure the [`@stylistic/ts/quotes`](https://eslint.style/rules/ts/quotes) rule to do this. ::: - -{/* Intentionally Omitted: When Not To Use It */} diff --git a/packages/eslint-plugin/docs/rules/no-var-requires.mdx b/packages/eslint-plugin/docs/rules/no-var-requires.mdx index e71c2e17c712..c9945304a8a2 100644 --- a/packages/eslint-plugin/docs/rules/no-var-requires.mdx +++ b/packages/eslint-plugin/docs/rules/no-var-requires.mdx @@ -9,6 +9,12 @@ import TabItem from '@theme/TabItem'; > > See **https://typescript-eslint.io/rules/no-var-requires** for documentation. +:::danger Deprecated + +This rule has been deprecated in favour of the [`@typescript-eslint/no-require-imports`](./no-require-imports.mdx) rule. + +::: + In other words, the use of forms such as `var foo = require("foo")` are banned. Instead use ES6 style imports or `import foo = require("foo")` imports. ## Examples diff --git a/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx b/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx index 5c3c9b5780e5..2d6a93371943 100644 --- a/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx +++ b/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx @@ -71,5 +71,5 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates ## Related To - [`no-empty-object-type`](./no-empty-object-type.mdx) -- [`ban-types`](./ban-types.mdx) +- [`no-restricted-types`](./no-restricted-types.mdx) - [`no-unsafe-function-type`](./no-unsafe-function-type.mdx) diff --git a/packages/eslint-plugin/docs/rules/object-curly-spacing.md b/packages/eslint-plugin/docs/rules/object-curly-spacing.md new file mode 100644 index 000000000000..45d53728c3f8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/object-curly-spacing.md @@ -0,0 +1,11 @@ +:::danger Deprecated + +This rule has been moved to the [ESLint stylistic plugin](https://eslint.style). +See [#8072](https://github.com/typescript-eslint/typescript-eslint/issues/8072) and [#8074](https://github.com/typescript-eslint/typescript-eslint/issues/8074) for more information. + +::: + + diff --git a/packages/eslint-plugin/docs/rules/object-curly-spacing.mdx b/packages/eslint-plugin/docs/rules/object-curly-spacing.mdx deleted file mode 100644 index af82a39f986b..000000000000 --- a/packages/eslint-plugin/docs/rules/object-curly-spacing.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -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. - -This rule extends the base [`eslint/object-curly-spacing`](https://eslint.org/docs/rules/object-curly-spacing) rule. -It adds support for TypeScript's object types. diff --git a/packages/eslint-plugin/docs/rules/only-throw-error.mdx b/packages/eslint-plugin/docs/rules/only-throw-error.mdx index 0070d648a888..8fffde428fee 100644 --- a/packages/eslint-plugin/docs/rules/only-throw-error.mdx +++ b/packages/eslint-plugin/docs/rules/only-throw-error.mdx @@ -16,9 +16,7 @@ This rule restricts what can be thrown as an exception. :::info[Migration from `no-throw-literal`] -This rule was formerly known as [`no-throw-literal`](./no-throw-literal.mdx). -We encourage users to migrate to the new name, `only-throw-error`, as the old name will be removed in a future major version of typescript-eslint. - +This rule was formerly known as `no-throw-literal`. The new name is a drop-in replacement with identical functionality. ::: diff --git a/packages/eslint-plugin/docs/rules/padding-line-between-statements.md b/packages/eslint-plugin/docs/rules/padding-line-between-statements.md new file mode 100644 index 000000000000..45d53728c3f8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/padding-line-between-statements.md @@ -0,0 +1,11 @@ +:::danger Deprecated + +This rule has been moved to the [ESLint stylistic plugin](https://eslint.style). +See [#8072](https://github.com/typescript-eslint/typescript-eslint/issues/8072) and [#8074](https://github.com/typescript-eslint/typescript-eslint/issues/8074) for more information. + +::: + + diff --git a/packages/eslint-plugin/docs/rules/padding-line-between-statements.mdx b/packages/eslint-plugin/docs/rules/padding-line-between-statements.mdx deleted file mode 100644 index e1bf6365703d..000000000000 --- a/packages/eslint-plugin/docs/rules/padding-line-between-statements.mdx +++ /dev/null @@ -1,36 +0,0 @@ ---- -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. - -This rule extends the base [`eslint/padding-line-between-statements`](https://eslint.org/docs/rules/padding-line-between-statements) rule. -It adds support for TypeScript constructs such as `interface` and `type`. - -## Options - -In addition to options provided by ESLint, `interface` and `type` can be used as statement types. - -For example, to add blank lines before interfaces and type definitions: - -```jsonc -{ - // Example - Add blank lines before interface and type definitions. - "padding-line-between-statements": "off", - "@typescript-eslint/padding-line-between-statements": [ - "error", - { - "blankLine": "always", - "prev": "*", - "next": ["interface", "type"], - }, - ], -} -``` - -**Note:** ESLint `cjs-export` and `cjs-import` statement types are renamed to `exports` and `require` respectively. diff --git a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.mdx b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.mdx index 3215b83dc832..6748b58873ba 100644 --- a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.mdx +++ b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.mdx @@ -65,7 +65,7 @@ foo ?? 'a string'; ### `ignoreConditionalTests` -Setting this option to `true` will cause the rule to ignore any cases that are located within a conditional test. This is set to `false` by default. +Setting this option to `false` will cause the rule to also check cases that are located within a conditional test. This is set to `true` by default. Generally expressions within conditional tests intentionally use the falsy fallthrough behavior of the logical or operator, meaning that fixing the operator to the nullish coalesce operator could cause bugs. diff --git a/packages/eslint-plugin/docs/rules/quotes.md b/packages/eslint-plugin/docs/rules/quotes.md new file mode 100644 index 000000000000..45d53728c3f8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/quotes.md @@ -0,0 +1,11 @@ +:::danger Deprecated + +This rule has been moved to the [ESLint stylistic plugin](https://eslint.style). +See [#8072](https://github.com/typescript-eslint/typescript-eslint/issues/8072) and [#8074](https://github.com/typescript-eslint/typescript-eslint/issues/8074) for more information. + +::: + + diff --git a/packages/eslint-plugin/docs/rules/quotes.mdx b/packages/eslint-plugin/docs/rules/quotes.mdx deleted file mode 100644 index 800bb2b24580..000000000000 --- a/packages/eslint-plugin/docs/rules/quotes.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -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. - -This rule extends the base [`eslint/quotes`](https://eslint.org/docs/rules/quotes) rule. -It adds support for TypeScript features which allow quoted names, but not backtick quoted names. diff --git a/packages/eslint-plugin/docs/rules/semi.md b/packages/eslint-plugin/docs/rules/semi.md new file mode 100644 index 000000000000..45d53728c3f8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/semi.md @@ -0,0 +1,11 @@ +:::danger Deprecated + +This rule has been moved to the [ESLint stylistic plugin](https://eslint.style). +See [#8072](https://github.com/typescript-eslint/typescript-eslint/issues/8072) and [#8074](https://github.com/typescript-eslint/typescript-eslint/issues/8074) for more information. + +::: + + diff --git a/packages/eslint-plugin/docs/rules/semi.mdx b/packages/eslint-plugin/docs/rules/semi.mdx deleted file mode 100644 index a0cd3e3653b4..000000000000 --- a/packages/eslint-plugin/docs/rules/semi.mdx +++ /dev/null @@ -1,15 +0,0 @@ ---- -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. - -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.mdx) rule, which allows you to specify the delimiter for `type` and `interface` members. diff --git a/packages/eslint-plugin/docs/rules/space-before-blocks.md b/packages/eslint-plugin/docs/rules/space-before-blocks.md new file mode 100644 index 000000000000..45d53728c3f8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/space-before-blocks.md @@ -0,0 +1,11 @@ +:::danger Deprecated + +This rule has been moved to the [ESLint stylistic plugin](https://eslint.style). +See [#8072](https://github.com/typescript-eslint/typescript-eslint/issues/8072) and [#8074](https://github.com/typescript-eslint/typescript-eslint/issues/8074) for more information. + +::: + + diff --git a/packages/eslint-plugin/docs/rules/space-before-blocks.mdx b/packages/eslint-plugin/docs/rules/space-before-blocks.mdx deleted file mode 100644 index 84f6cf93e336..000000000000 --- a/packages/eslint-plugin/docs/rules/space-before-blocks.mdx +++ /dev/null @@ -1,49 +0,0 @@ ---- -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. - -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. - - - - -{/* prettier-ignore */} -```ts -enum Breakpoint{ - Large, - Medium, -} - -interface State{ - currentBreakpoint: Breakpoint; -} -``` - - - - -```ts -enum Breakpoint { - Large, - Medium, -} - -interface State { - currentBreakpoint: Breakpoint; -} -``` - - - - -## 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.md new file mode 100644 index 000000000000..45d53728c3f8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/space-before-function-paren.md @@ -0,0 +1,11 @@ +:::danger Deprecated + +This rule has been moved to the [ESLint stylistic plugin](https://eslint.style). +See [#8072](https://github.com/typescript-eslint/typescript-eslint/issues/8072) and [#8074](https://github.com/typescript-eslint/typescript-eslint/issues/8074) for more information. + +::: + + diff --git a/packages/eslint-plugin/docs/rules/space-before-function-paren.mdx b/packages/eslint-plugin/docs/rules/space-before-function-paren.mdx deleted file mode 100644 index 8fd3e915bc42..000000000000 --- a/packages/eslint-plugin/docs/rules/space-before-function-paren.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -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. - -This rule extends the base [`eslint/space-before-function-paren`](https://eslint.org/docs/rules/space-before-function-paren) rule. -It adds support for generic type parameters on function calls. diff --git a/packages/eslint-plugin/docs/rules/space-infix-ops.md b/packages/eslint-plugin/docs/rules/space-infix-ops.md new file mode 100644 index 000000000000..45d53728c3f8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/space-infix-ops.md @@ -0,0 +1,11 @@ +:::danger Deprecated + +This rule has been moved to the [ESLint stylistic plugin](https://eslint.style). +See [#8072](https://github.com/typescript-eslint/typescript-eslint/issues/8072) and [#8074](https://github.com/typescript-eslint/typescript-eslint/issues/8074) for more information. + +::: + + diff --git a/packages/eslint-plugin/docs/rules/space-infix-ops.mdx b/packages/eslint-plugin/docs/rules/space-infix-ops.mdx deleted file mode 100644 index 6293ca22e2f5..000000000000 --- a/packages/eslint-plugin/docs/rules/space-infix-ops.mdx +++ /dev/null @@ -1,19 +0,0 @@ ---- -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. - -This rule extends the base [`eslint/space-infix-ops`](https://eslint.org/docs/rules/space-infix-ops) rule. -It adds support for enum members. - -```ts -enum MyEnum { - KEY = 'value', -} -``` diff --git a/packages/eslint-plugin/docs/rules/type-annotation-spacing.md b/packages/eslint-plugin/docs/rules/type-annotation-spacing.md new file mode 100644 index 000000000000..45d53728c3f8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/type-annotation-spacing.md @@ -0,0 +1,11 @@ +:::danger Deprecated + +This rule has been moved to the [ESLint stylistic plugin](https://eslint.style). +See [#8072](https://github.com/typescript-eslint/typescript-eslint/issues/8072) and [#8074](https://github.com/typescript-eslint/typescript-eslint/issues/8074) for more information. + +::: + + diff --git a/packages/eslint-plugin/docs/rules/type-annotation-spacing.mdx b/packages/eslint-plugin/docs/rules/type-annotation-spacing.mdx deleted file mode 100644 index 423dc90da83d..000000000000 --- a/packages/eslint-plugin/docs/rules/type-annotation-spacing.mdx +++ /dev/null @@ -1,335 +0,0 @@ ---- -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"; - -// with no spaces -let foo:string = "bar"; - -// with space before and after -let foo : string = "bar"; - -// with space before, but not after -let foo :string = "bar"; - -// with spaces before and after the fat arrow (default if no option is specified) -type Foo = (string: name) => string; - -// with no spaces between the fat arrow -type Foo = (string: name)=>string; - -// with space after, but not before the fat arrow -type Foo = (string: name)=> string; - -// with space before, but not after the fat arrow -type Foo = (string: name) =>string; -``` - -## Examples - -This rule aims to enforce specific spacing patterns around type annotations and function types in type literals. - - - - -{/* prettier-ignore */} -```ts -let foo:string = "bar"; -let foo :string = "bar"; -let foo : string = "bar"; - -function foo():string {} -function foo() :string {} -function foo() : string {} - -class Foo { - name:string; -} - -class Foo { - name :string; -} - -class Foo { - name : string; -} - -type Foo = ()=>{}; -type Foo = () =>{}; -type Foo = ()=> {}; -``` - - - - -{/* prettier-ignore */} -```ts -let foo: string = "bar"; - -function foo(): string {} - -class Foo { - name: string; -} - -type Foo = () => {}; -``` - - - - -## Options - -### after - -```json -{ "before": false, "after": true } -``` - - - - -{/* prettier-ignore */} -```ts option='{ "before": false, "after": true }' -let foo:string = "bar"; -let foo :string = "bar"; -let foo : string = "bar"; - -function foo():string {} -function foo() :string {} -function foo() : string {} - -class Foo { - name:string; -} - -class Foo { - name :string; -} - -class Foo { - name : string; -} - -type Foo = ()=>{}; -type Foo = () =>{}; -type Foo = () => {}; -``` - - - - -{/* prettier-ignore */} -```ts option='{ "before": false, "after": true }' -let foo: string = "bar"; - -function foo(): string {} - -class Foo { - name: string; -} - -type Foo = ()=> {}; -``` - - - - -### before - -```json -{ "before": true, "after": true } -``` - - - - -{/* prettier-ignore */} -```ts option='{ "before": true, "after": true }' -let foo: string = "bar"; -let foo:string = "bar"; -let foo :string = "bar"; - -function foo(): string {} -function foo():string {} -function foo() :string {} - -class Foo { - name: string; -} - -class Foo { - name:string; -} - -class Foo { - name :string; -} - -type Foo = ()=>{}; -type Foo = () =>{}; -type Foo = ()=> {}; -``` - - - - -{/* prettier-ignore */} -```ts option='{ "before": true, "after": true }' -let foo : string = "bar"; - -function foo() : string {} - -class Foo { - name : string; -} - -type Foo = () => {}; -``` - - - - -### overrides - colon - -```json -{ - "before": false, - "after": false, - "overrides": { "colon": { "before": true, "after": true } } -} -``` - - - - -{/* prettier-ignore */} -```ts option='{"before":false,"after":false,"overrides":{"colon":{"before":true,"after":true}}}' -let foo: string = "bar"; -let foo:string = "bar"; -let foo :string = "bar"; - -function foo(): string {} -function foo():string {} -function foo() :string {} - -class Foo { - name: string; -} - -class Foo { - name:string; -} - -class Foo { - name :string; -} - -type Foo = () =>{}; -type Foo = ()=> {}; -type Foo = () => {}; -``` - - - - -{/* prettier-ignore */} -```ts option='{"before":false,"after":false,"overrides":{"colon":{"before":true,"after":true}}}' -let foo : string = "bar"; - -function foo() : string {} - -class Foo { - name : string; -} - -type Foo = { - name : (name : string)=>string; -} - -type Foo = ()=>{}; -``` - - - - -### overrides - arrow - -```json -{ - "before": false, - "after": false, - "overrides": { "arrow": { "before": true, "after": true } } -} -``` - - - - -{/* prettier-ignore */} -```ts option='{"before":false,"after":false,"overrides":{"arrow":{"before":true,"after":true}}}' -let foo: string = "bar"; -let foo : string = "bar"; -let foo :string = "bar"; - -function foo(): string {} -function foo():string {} -function foo() :string {} - -class Foo { - name: string; -} - -class Foo { - name : string; -} - -class Foo { - name :string; -} - -type Foo = ()=>{}; -type Foo = () =>{}; -type Foo = ()=> {}; -``` - - - - -{/* prettier-ignore */} -```ts option='{"before":false,"after":false,"overrides":{"arrow":{"before":true,"after":true}}}' -let foo:string = "bar"; - -function foo():string {} - -class Foo { - name:string; -} - -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. - -## Further Reading - -- [TypeScript Type System](https://basarat.gitbooks.io/typescript/docs/types/type-system.html) -- [Type Inference](https://www.typescriptlang.org/docs/handbook/type-inference.html) diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 15cf13c4e3b1..f995f707b655 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "7.18.0", + "version": "8.0.1", "description": "TypeScript plugin for ESLint", "files": [ "dist", @@ -28,7 +28,7 @@ } }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "repository": { "type": "git", @@ -60,10 +60,10 @@ }, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/type-utils": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", + "@typescript-eslint/scope-manager": "8.0.1", + "@typescript-eslint/type-utils": "8.0.1", + "@typescript-eslint/utils": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -74,13 +74,12 @@ "@types/marked": "^5.0.2", "@types/mdast": "^4.0.3", "@types/natural-compare": "*", - "@typescript-eslint/rule-schema-to-typescript-types": "7.18.0", - "@typescript-eslint/rule-tester": "7.18.0", + "@typescript-eslint/rule-schema-to-typescript-types": "8.0.1", + "@typescript-eslint/rule-tester": "8.0.1", "ajv": "^6.12.6", "cross-env": "^7.0.3", "cross-fetch": "*", "eslint": "*", - "espree": "^10.0.1", "jest": "29.7.0", "jest-specific-snapshot": "^8.0.0", "json-schema": "*", @@ -97,8 +96,8 @@ "unist-util-visit": "^5.0.0" }, "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" }, "peerDependenciesMeta": { "typescript": { diff --git a/packages/eslint-plugin/rules.d.ts b/packages/eslint-plugin/rules.d.ts index 71745fe4ef77..d6625d09950e 100644 --- a/packages/eslint-plugin/rules.d.ts +++ b/packages/eslint-plugin/rules.d.ts @@ -35,12 +35,43 @@ This is likely not portable. A type annotation is necessary. ts(2742) ``` */ -import type { RuleModule } from '@typescript-eslint/utils/ts-eslint'; +import type { + RuleModuleWithMetaDocs, + RuleRecommendation, + RuleRecommendationAcrossConfigs, +} from '@typescript-eslint/utils/ts-eslint'; + +export interface ESLintPluginDocs { + /** + * Does the rule extend (or is it based off of) an ESLint code rule? + * Alternately accepts the name of the base rule, in case the rule has been renamed. + * This is only used for documentation purposes. + */ + extendsBaseRule?: boolean | string; + + /** + * If a string config name, which starting config this rule is enabled in. + * If an object, which settings it has enabled in each of those configs. + */ + recommended?: RuleRecommendation | RuleRecommendationAcrossConfigs; + + /** + * Does the rule require us to create a full TypeScript Program in order for it + * to type-check code. This is only used for documentation purposes. + */ + requiresTypeChecking?: boolean; +} + +export type ESLintPluginRuleModule = RuleModuleWithMetaDocs< + string, + readonly unknown[], + ESLintPluginDocs +>; export type TypeScriptESLintRules = Record< string, - RuleModule + RuleModuleWithMetaDocs >; + declare const rules: TypeScriptESLintRules; -// eslint-disable-next-line import/no-default-export export default rules; diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 51b511f32074..dd0fb8befa80 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -15,7 +15,6 @@ export = { '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/ban-ts-comment': 'error', '@typescript-eslint/ban-tslint-comment': 'error', - '@typescript-eslint/ban-types': 'error', '@typescript-eslint/class-literal-property-style': 'error', 'class-methods-use-this': 'off', '@typescript-eslint/class-methods-use-this': 'error', @@ -54,7 +53,6 @@ export = { '@typescript-eslint/no-dynamic-delete': 'error', 'no-empty-function': 'off', '@typescript-eslint/no-empty-function': 'error', - '@typescript-eslint/no-empty-interface': 'error', '@typescript-eslint/no-empty-object-type': 'error', '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', @@ -70,8 +68,6 @@ export = { '@typescript-eslint/no-invalid-void-type': 'error', 'no-loop-func': 'off', '@typescript-eslint/no-loop-func': 'error', - 'no-loss-of-precision': 'off', - '@typescript-eslint/no-loss-of-precision': 'error', 'no-magic-numbers': 'off', '@typescript-eslint/no-magic-numbers': 'error', '@typescript-eslint/no-meaningless-void-operator': 'error', @@ -88,6 +84,7 @@ export = { '@typescript-eslint/no-require-imports': 'error', 'no-restricted-imports': 'off', '@typescript-eslint/no-restricted-imports': 'error', + '@typescript-eslint/no-restricted-types': 'error', 'no-shadow': 'off', '@typescript-eslint/no-shadow': 'error', '@typescript-eslint/no-this-alias': 'error', @@ -118,7 +115,6 @@ export = { 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/no-useless-empty-export': 'error', - '@typescript-eslint/no-var-requires': 'error', '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/non-nullable-type-assertion-style': 'error', 'no-throw-literal': 'off', diff --git a/packages/eslint-plugin/src/configs/disable-type-checked.ts b/packages/eslint-plugin/src/configs/disable-type-checked.ts index f0ba1bc0225e..b85ca09d6f70 100644 --- a/packages/eslint-plugin/src/configs/disable-type-checked.ts +++ b/packages/eslint-plugin/src/configs/disable-type-checked.ts @@ -8,11 +8,7 @@ import type { ClassicConfig } from '@typescript-eslint/utils/ts-eslint'; export = { - parserOptions: { - project: false, - program: null, - EXPERIMENTAL_useProjectService: false, - }, + parserOptions: { project: false, program: null, projectService: false }, rules: { '@typescript-eslint/await-thenable': 'off', '@typescript-eslint/consistent-return': 'off', @@ -30,7 +26,6 @@ export = { '@typescript-eslint/no-misused-promises': 'off', '@typescript-eslint/no-mixed-enums': 'off', '@typescript-eslint/no-redundant-type-constituents': 'off', - '@typescript-eslint/no-throw-literal': 'off', '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'off', '@typescript-eslint/no-unnecessary-condition': 'off', '@typescript-eslint/no-unnecessary-qualifier': 'off', @@ -45,7 +40,6 @@ export = { '@typescript-eslint/no-unsafe-member-access': 'off', '@typescript-eslint/no-unsafe-return': 'off', '@typescript-eslint/no-unsafe-unary-minus': 'off', - '@typescript-eslint/no-useless-template-literals': 'off', '@typescript-eslint/non-nullable-type-assertion-style': 'off', '@typescript-eslint/only-throw-error': 'off', '@typescript-eslint/prefer-destructuring': 'off', diff --git a/packages/eslint-plugin/src/configs/recommended-type-checked-only.ts b/packages/eslint-plugin/src/configs/recommended-type-checked-only.ts index 13a1f5fb4271..c9c0f5cb4368 100644 --- a/packages/eslint-plugin/src/configs/recommended-type-checked-only.ts +++ b/packages/eslint-plugin/src/configs/recommended-type-checked-only.ts @@ -11,6 +11,7 @@ export = { extends: ['./configs/base', './configs/eslint-recommended'], rules: { '@typescript-eslint/await-thenable': 'error', + '@typescript-eslint/no-array-delete': 'error', '@typescript-eslint/no-base-to-string': 'error', '@typescript-eslint/no-duplicate-type-constituents': 'error', '@typescript-eslint/no-floating-promises': 'error', @@ -26,6 +27,11 @@ export = { '@typescript-eslint/no-unsafe-enum-comparison': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', + '@typescript-eslint/no-unsafe-unary-minus': 'error', + 'no-throw-literal': 'off', + '@typescript-eslint/only-throw-error': 'error', + 'prefer-promise-reject-errors': 'off', + '@typescript-eslint/prefer-promise-reject-errors': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', '@typescript-eslint/restrict-plus-operands': 'error', diff --git a/packages/eslint-plugin/src/configs/recommended-type-checked.ts b/packages/eslint-plugin/src/configs/recommended-type-checked.ts index 38d36c132851..e8e10a7071a5 100644 --- a/packages/eslint-plugin/src/configs/recommended-type-checked.ts +++ b/packages/eslint-plugin/src/configs/recommended-type-checked.ts @@ -12,25 +12,25 @@ export = { rules: { '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/ban-ts-comment': 'error', - '@typescript-eslint/ban-types': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', + '@typescript-eslint/no-array-delete': 'error', '@typescript-eslint/no-base-to-string': 'error', '@typescript-eslint/no-duplicate-enum-values': 'error', '@typescript-eslint/no-duplicate-type-constituents': 'error', + '@typescript-eslint/no-empty-object-type': 'error', '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-floating-promises': 'error', '@typescript-eslint/no-for-in-array': 'error', 'no-implied-eval': 'off', '@typescript-eslint/no-implied-eval': 'error', - 'no-loss-of-precision': 'off', - '@typescript-eslint/no-loss-of-precision': 'error', '@typescript-eslint/no-misused-new': 'error', '@typescript-eslint/no-misused-promises': 'error', '@typescript-eslint/no-namespace': 'error', '@typescript-eslint/no-non-null-asserted-optional-chain': 'error', '@typescript-eslint/no-redundant-type-constituents': 'error', + '@typescript-eslint/no-require-imports': 'error', '@typescript-eslint/no-this-alias': 'error', '@typescript-eslint/no-unnecessary-type-assertion': 'error', '@typescript-eslint/no-unnecessary-type-constraint': 'error', @@ -39,12 +39,21 @@ export = { '@typescript-eslint/no-unsafe-call': 'error', '@typescript-eslint/no-unsafe-declaration-merging': 'error', '@typescript-eslint/no-unsafe-enum-comparison': 'error', + '@typescript-eslint/no-unsafe-function-type': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', + '@typescript-eslint/no-unsafe-unary-minus': 'error', + 'no-unused-expressions': 'off', + '@typescript-eslint/no-unused-expressions': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', - '@typescript-eslint/no-var-requires': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', + 'no-throw-literal': 'off', + '@typescript-eslint/only-throw-error': 'error', '@typescript-eslint/prefer-as-const': 'error', + '@typescript-eslint/prefer-namespace-keyword': 'error', + 'prefer-promise-reject-errors': 'off', + '@typescript-eslint/prefer-promise-reject-errors': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', '@typescript-eslint/restrict-plus-operands': 'error', diff --git a/packages/eslint-plugin/src/configs/recommended.ts b/packages/eslint-plugin/src/configs/recommended.ts index 80a607a3288f..487935ee1f23 100644 --- a/packages/eslint-plugin/src/configs/recommended.ts +++ b/packages/eslint-plugin/src/configs/recommended.ts @@ -11,24 +11,27 @@ export = { extends: ['./configs/base', './configs/eslint-recommended'], rules: { '@typescript-eslint/ban-ts-comment': 'error', - '@typescript-eslint/ban-types': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', '@typescript-eslint/no-duplicate-enum-values': 'error', + '@typescript-eslint/no-empty-object-type': 'error', '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', - 'no-loss-of-precision': 'off', - '@typescript-eslint/no-loss-of-precision': 'error', '@typescript-eslint/no-misused-new': 'error', '@typescript-eslint/no-namespace': 'error', '@typescript-eslint/no-non-null-asserted-optional-chain': 'error', + '@typescript-eslint/no-require-imports': 'error', '@typescript-eslint/no-this-alias': 'error', '@typescript-eslint/no-unnecessary-type-constraint': 'error', '@typescript-eslint/no-unsafe-declaration-merging': 'error', + '@typescript-eslint/no-unsafe-function-type': 'error', + 'no-unused-expressions': 'off', + '@typescript-eslint/no-unused-expressions': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', - '@typescript-eslint/no-var-requires': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/prefer-as-const': 'error', + '@typescript-eslint/prefer-namespace-keyword': 'error', '@typescript-eslint/triple-slash-reference': 'error', }, } 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 a17bc16c257e..d2a4a2fbd891 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked-only.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked-only.ts @@ -28,15 +28,16 @@ export = { '@typescript-eslint/no-unnecessary-template-expression': 'error', '@typescript-eslint/no-unnecessary-type-arguments': 'error', '@typescript-eslint/no-unnecessary-type-assertion': 'error', + '@typescript-eslint/no-unnecessary-type-parameters': 'error', '@typescript-eslint/no-unsafe-argument': 'error', '@typescript-eslint/no-unsafe-assignment': 'error', '@typescript-eslint/no-unsafe-call': 'error', '@typescript-eslint/no-unsafe-enum-comparison': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', + '@typescript-eslint/no-unsafe-unary-minus': 'error', 'no-throw-literal': 'off', '@typescript-eslint/only-throw-error': 'error', - '@typescript-eslint/prefer-includes': 'error', 'prefer-promise-reject-errors': 'off', '@typescript-eslint/prefer-promise-reject-errors': 'error', '@typescript-eslint/prefer-reduce-type-parameter': 'error', @@ -64,6 +65,11 @@ export = { allowNever: false, }, ], + 'no-return-await': 'off', + '@typescript-eslint/return-await': [ + 'error', + 'error-handling-correctness-only', + ], '@typescript-eslint/unbound-method': 'error', '@typescript-eslint/use-unknown-in-catch-callback-variable': 'error', }, diff --git a/packages/eslint-plugin/src/configs/strict-type-checked.ts b/packages/eslint-plugin/src/configs/strict-type-checked.ts index cdce2e4962b6..73e809e5bf5d 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked.ts @@ -15,7 +15,6 @@ export = { 'error', { minimumDescriptionLength: 10 }, ], - '@typescript-eslint/ban-types': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', '@typescript-eslint/no-array-delete': 'error', @@ -24,6 +23,7 @@ export = { '@typescript-eslint/no-duplicate-enum-values': 'error', '@typescript-eslint/no-duplicate-type-constituents': 'error', '@typescript-eslint/no-dynamic-delete': 'error', + '@typescript-eslint/no-empty-object-type': 'error', '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-extraneous-class': 'error', @@ -32,8 +32,6 @@ export = { 'no-implied-eval': 'off', '@typescript-eslint/no-implied-eval': 'error', '@typescript-eslint/no-invalid-void-type': 'error', - 'no-loss-of-precision': 'off', - '@typescript-eslint/no-loss-of-precision': 'error', '@typescript-eslint/no-meaningless-void-operator': 'error', '@typescript-eslint/no-misused-new': 'error', '@typescript-eslint/no-misused-promises': 'error', @@ -43,6 +41,7 @@ export = { '@typescript-eslint/no-non-null-asserted-optional-chain': 'error', '@typescript-eslint/no-non-null-assertion': 'error', '@typescript-eslint/no-redundant-type-constituents': 'error', + '@typescript-eslint/no-require-imports': 'error', '@typescript-eslint/no-this-alias': 'error', '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'error', '@typescript-eslint/no-unnecessary-condition': 'error', @@ -50,23 +49,28 @@ export = { '@typescript-eslint/no-unnecessary-type-arguments': 'error', '@typescript-eslint/no-unnecessary-type-assertion': 'error', '@typescript-eslint/no-unnecessary-type-constraint': 'error', + '@typescript-eslint/no-unnecessary-type-parameters': 'error', '@typescript-eslint/no-unsafe-argument': 'error', '@typescript-eslint/no-unsafe-assignment': 'error', '@typescript-eslint/no-unsafe-call': 'error', '@typescript-eslint/no-unsafe-declaration-merging': 'error', '@typescript-eslint/no-unsafe-enum-comparison': 'error', + '@typescript-eslint/no-unsafe-function-type': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', + '@typescript-eslint/no-unsafe-unary-minus': 'error', + 'no-unused-expressions': 'off', + '@typescript-eslint/no-unused-expressions': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', - '@typescript-eslint/no-var-requires': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', 'no-throw-literal': 'off', '@typescript-eslint/only-throw-error': 'error', '@typescript-eslint/prefer-as-const': 'error', - '@typescript-eslint/prefer-includes': 'error', '@typescript-eslint/prefer-literal-enum-member': 'error', + '@typescript-eslint/prefer-namespace-keyword': 'error', 'prefer-promise-reject-errors': 'off', '@typescript-eslint/prefer-promise-reject-errors': 'error', '@typescript-eslint/prefer-reduce-type-parameter': 'error', @@ -94,6 +98,11 @@ export = { allowNever: false, }, ], + 'no-return-await': 'off', + '@typescript-eslint/return-await': [ + 'error', + 'error-handling-correctness-only', + ], '@typescript-eslint/triple-slash-reference': 'error', '@typescript-eslint/unbound-method': 'error', '@typescript-eslint/unified-signatures': 'error', diff --git a/packages/eslint-plugin/src/configs/strict.ts b/packages/eslint-plugin/src/configs/strict.ts index 9c51d5c47348..0e655d1464ca 100644 --- a/packages/eslint-plugin/src/configs/strict.ts +++ b/packages/eslint-plugin/src/configs/strict.ts @@ -14,32 +14,35 @@ export = { 'error', { minimumDescriptionLength: 10 }, ], - '@typescript-eslint/ban-types': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', '@typescript-eslint/no-duplicate-enum-values': 'error', '@typescript-eslint/no-dynamic-delete': 'error', + '@typescript-eslint/no-empty-object-type': 'error', '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-extraneous-class': 'error', '@typescript-eslint/no-invalid-void-type': 'error', - 'no-loss-of-precision': 'off', - '@typescript-eslint/no-loss-of-precision': 'error', '@typescript-eslint/no-misused-new': 'error', '@typescript-eslint/no-namespace': 'error', '@typescript-eslint/no-non-null-asserted-nullish-coalescing': 'error', '@typescript-eslint/no-non-null-asserted-optional-chain': 'error', '@typescript-eslint/no-non-null-assertion': 'error', + '@typescript-eslint/no-require-imports': 'error', '@typescript-eslint/no-this-alias': 'error', '@typescript-eslint/no-unnecessary-type-constraint': 'error', '@typescript-eslint/no-unsafe-declaration-merging': 'error', + '@typescript-eslint/no-unsafe-function-type': 'error', + 'no-unused-expressions': 'off', + '@typescript-eslint/no-unused-expressions': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', - '@typescript-eslint/no-var-requires': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/prefer-as-const': 'error', '@typescript-eslint/prefer-literal-enum-member': 'error', + '@typescript-eslint/prefer-namespace-keyword': 'error', '@typescript-eslint/triple-slash-reference': 'error', '@typescript-eslint/unified-signatures': 'error', }, diff --git a/packages/eslint-plugin/src/configs/stylistic-type-checked-only.ts b/packages/eslint-plugin/src/configs/stylistic-type-checked-only.ts index d9026c1db57e..03428e419be5 100644 --- a/packages/eslint-plugin/src/configs/stylistic-type-checked-only.ts +++ b/packages/eslint-plugin/src/configs/stylistic-type-checked-only.ts @@ -13,8 +13,11 @@ export = { 'dot-notation': 'off', '@typescript-eslint/dot-notation': 'error', '@typescript-eslint/non-nullable-type-assertion-style': 'error', + '@typescript-eslint/prefer-find': 'error', + '@typescript-eslint/prefer-includes': 'error', '@typescript-eslint/prefer-nullish-coalescing': 'error', '@typescript-eslint/prefer-optional-chain': 'error', + '@typescript-eslint/prefer-regexp-exec': 'error', '@typescript-eslint/prefer-string-starts-ends-with': 'error', }, } satisfies ClassicConfig.Config; diff --git a/packages/eslint-plugin/src/configs/stylistic-type-checked.ts b/packages/eslint-plugin/src/configs/stylistic-type-checked.ts index 0bb075e5c8f2..f2512877aebe 100644 --- a/packages/eslint-plugin/src/configs/stylistic-type-checked.ts +++ b/packages/eslint-plugin/src/configs/stylistic-type-checked.ts @@ -23,14 +23,15 @@ export = { '@typescript-eslint/no-confusing-non-null-assertion': 'error', 'no-empty-function': 'off', '@typescript-eslint/no-empty-function': 'error', - '@typescript-eslint/no-empty-interface': 'error', '@typescript-eslint/no-inferrable-types': 'error', '@typescript-eslint/non-nullable-type-assertion-style': 'error', + '@typescript-eslint/prefer-find': 'error', '@typescript-eslint/prefer-for-of': 'error', '@typescript-eslint/prefer-function-type': 'error', - '@typescript-eslint/prefer-namespace-keyword': 'error', + '@typescript-eslint/prefer-includes': 'error', '@typescript-eslint/prefer-nullish-coalescing': 'error', '@typescript-eslint/prefer-optional-chain': 'error', + '@typescript-eslint/prefer-regexp-exec': 'error', '@typescript-eslint/prefer-string-starts-ends-with': 'error', }, } satisfies ClassicConfig.Config; diff --git a/packages/eslint-plugin/src/configs/stylistic.ts b/packages/eslint-plugin/src/configs/stylistic.ts index 74f2586dd78b..02705c56c034 100644 --- a/packages/eslint-plugin/src/configs/stylistic.ts +++ b/packages/eslint-plugin/src/configs/stylistic.ts @@ -21,10 +21,8 @@ export = { '@typescript-eslint/no-confusing-non-null-assertion': 'error', 'no-empty-function': 'off', '@typescript-eslint/no-empty-function': 'error', - '@typescript-eslint/no-empty-interface': 'error', '@typescript-eslint/no-inferrable-types': 'error', '@typescript-eslint/prefer-for-of': 'error', '@typescript-eslint/prefer-function-type': 'error', - '@typescript-eslint/prefer-namespace-keyword': 'error', }, } satisfies ClassicConfig.Config; diff --git a/packages/eslint-plugin/src/rules/block-spacing.ts b/packages/eslint-plugin/src/rules/block-spacing.ts deleted file mode 100644 index 05db7c76199c..000000000000 --- a/packages/eslint-plugin/src/rules/block-spacing.ts +++ /dev/null @@ -1,167 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_TOKEN_TYPES } from '@typescript-eslint/utils'; - -import type { - InferMessageIdsTypeFromRule, - InferOptionsTypeFromRule, -} from '../util'; -import { createRule, isTokenOnSameLine } from '../util'; -import { getESLintCoreRule } from '../util/getESLintCoreRule'; - -const baseRule = getESLintCoreRule('block-spacing'); - -export type Options = InferOptionsTypeFromRule; -export type MessageIds = InferMessageIdsTypeFromRule; - -export default createRule({ - name: 'block-spacing', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/block-spacing'], - type: 'layout', - docs: { - description: - 'Disallow or enforce spaces inside of blocks after opening block and before closing block', - extendsBaseRule: true, - }, - fixable: 'whitespace', - hasSuggestions: baseRule.meta.hasSuggestions, - schema: baseRule.meta.schema, - messages: baseRule.meta.messages, - }, - defaultOptions: ['always'], - - create(context, [whenToApplyOption]) { - const baseRules = baseRule.create(context); - const always = whenToApplyOption !== 'never'; - const messageId = always ? 'missing' : 'extra'; - /** - * Gets the open brace token from a given node. - * @returns The token of the open brace. - */ - function getOpenBrace( - node: TSESTree.TSEnumDeclaration, - ): TSESTree.PunctuatorToken { - // guaranteed for enums - // This is the only change made here from the base rule - return context.sourceCode.getFirstToken(node, { - filter: token => - token.type === AST_TOKEN_TYPES.Punctuator && token.value === '{', - }) as TSESTree.PunctuatorToken; - } - - /** - * Checks whether or not: - * - given tokens are on same line. - * - there is/isn't a space between given tokens. - * @param left A token to check. - * @param right The token which is next to `left`. - * @returns - * When the option is `"always"`, `true` if there are one or more spaces between given tokens. - * When the option is `"never"`, `true` if there are not any spaces between given tokens. - * If given tokens are not on same line, it's always `true`. - */ - function isValid(left: TSESTree.Token, right: TSESTree.Token): boolean { - return ( - !isTokenOnSameLine(left, right) || - context.sourceCode.isSpaceBetween(left, right) === always - ); - } - - /** - * Checks and reports invalid spacing style inside braces. - */ - function checkSpacingInsideBraces(node: TSESTree.TSEnumDeclaration): void { - // Gets braces and the first/last token of content. - const openBrace = getOpenBrace(node); - const closeBrace = context.sourceCode.getLastToken(node)!; - const firstToken = context.sourceCode.getTokenAfter(openBrace, { - includeComments: true, - })!; - const lastToken = context.sourceCode.getTokenBefore(closeBrace, { - includeComments: true, - })!; - - // Skip if the node is invalid or empty. - if ( - openBrace.value !== '{' || - closeBrace.type !== AST_TOKEN_TYPES.Punctuator || - closeBrace.value !== '}' || - firstToken === closeBrace - ) { - return; - } - - // Skip line comments for option never - if (!always && firstToken.type === AST_TOKEN_TYPES.Line) { - return; - } - - if (!isValid(openBrace, firstToken)) { - let loc = openBrace.loc; - - if (messageId === 'extra') { - loc = { - start: openBrace.loc.end, - end: firstToken.loc.start, - }; - } - - context.report({ - node, - loc, - messageId, - data: { - location: 'after', - token: openBrace.value, - }, - fix(fixer) { - if (always) { - return fixer.insertTextBefore(firstToken, ' '); - } - - return fixer.removeRange([openBrace.range[1], firstToken.range[0]]); - }, - }); - } - if (!isValid(lastToken, closeBrace)) { - let loc = closeBrace.loc; - - if (messageId === 'extra') { - loc = { - start: lastToken.loc.end, - end: closeBrace.loc.start, - }; - } - context.report({ - node, - loc, - messageId, - data: { - location: 'before', - token: closeBrace.value, - }, - fix(fixer) { - if (always) { - return fixer.insertTextAfter(lastToken, ' '); - } - - return fixer.removeRange([lastToken.range[1], closeBrace.range[0]]); - }, - }); - } - } - return { - ...baseRules, - - // This code worked "out of the box" for interface and type literal - // Enums were very close to match as well, the only reason they are not is that was that enums don't have a body node in the parser - // So the opening brace punctuator starts in the middle of the node - `getFirstToken` in - // the base rule did not filter for the first opening brace punctuator - TSInterfaceBody: baseRules.BlockStatement as never, - TSTypeLiteral: baseRules.BlockStatement as never, - TSEnumDeclaration: checkSpacingInsideBraces, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/brace-style.ts b/packages/eslint-plugin/src/rules/brace-style.ts deleted file mode 100644 index 1021c9c9a48c..000000000000 --- a/packages/eslint-plugin/src/rules/brace-style.ts +++ /dev/null @@ -1,142 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { TSESTree } from '@typescript-eslint/utils'; - -import type { - InferMessageIdsTypeFromRule, - InferOptionsTypeFromRule, -} from '../util'; -import { createRule, isTokenOnSameLine } from '../util'; -import { getESLintCoreRule } from '../util/getESLintCoreRule'; - -const baseRule = getESLintCoreRule('brace-style'); - -export type Options = InferOptionsTypeFromRule; -export type MessageIds = InferMessageIdsTypeFromRule; - -export default createRule({ - name: 'brace-style', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/brace-style'], - type: 'layout', - docs: { - description: 'Enforce consistent brace style for blocks', - extendsBaseRule: true, - }, - messages: baseRule.meta.messages, - fixable: baseRule.meta.fixable, - hasSuggestions: baseRule.meta.hasSuggestions, - schema: baseRule.meta.schema, - }, - defaultOptions: ['1tbs'], - create(context) { - const [style, { allowSingleLine } = { allowSingleLine: false }] = - // eslint-disable-next-line no-restricted-syntax -- Use raw options for extended rules. - context.options; - - const isAllmanStyle = style === 'allman'; - - const rules = baseRule.create(context); - - /** - * Checks a pair of curly brackets based on the user's config - */ - function validateCurlyPair( - openingCurlyToken: TSESTree.Token, - closingCurlyToken: TSESTree.Token, - ): void { - if ( - allowSingleLine && - isTokenOnSameLine(openingCurlyToken, closingCurlyToken) - ) { - return; - } - - const tokenBeforeOpeningCurly = - context.sourceCode.getTokenBefore(openingCurlyToken)!; - const tokenBeforeClosingCurly = - context.sourceCode.getTokenBefore(closingCurlyToken)!; - const tokenAfterOpeningCurly = - context.sourceCode.getTokenAfter(openingCurlyToken)!; - - if ( - !isAllmanStyle && - !isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurlyToken) - ) { - context.report({ - node: openingCurlyToken, - messageId: 'nextLineOpen', - fix: fixer => { - const textRange: TSESTree.Range = [ - tokenBeforeOpeningCurly.range[1], - openingCurlyToken.range[0], - ]; - const textBetween = context.sourceCode.text.slice( - textRange[0], - textRange[1], - ); - - if (textBetween.trim()) { - return null; - } - - return fixer.replaceTextRange(textRange, ' '); - }, - }); - } - - if ( - isAllmanStyle && - isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurlyToken) - ) { - context.report({ - node: openingCurlyToken, - messageId: 'sameLineOpen', - fix: fixer => fixer.insertTextBefore(openingCurlyToken, '\n'), - }); - } - - if ( - isTokenOnSameLine(openingCurlyToken, tokenAfterOpeningCurly) && - tokenAfterOpeningCurly !== closingCurlyToken - ) { - context.report({ - node: openingCurlyToken, - messageId: 'blockSameLine', - fix: fixer => fixer.insertTextAfter(openingCurlyToken, '\n'), - }); - } - - if ( - isTokenOnSameLine(tokenBeforeClosingCurly, closingCurlyToken) && - tokenBeforeClosingCurly !== openingCurlyToken - ) { - context.report({ - node: closingCurlyToken, - messageId: 'singleLineClose', - fix: fixer => fixer.insertTextBefore(closingCurlyToken, '\n'), - }); - } - } - - return { - ...rules, - 'TSInterfaceBody, TSModuleBlock'( - node: TSESTree.TSInterfaceBody | TSESTree.TSModuleBlock, - ): void { - const openingCurly = context.sourceCode.getFirstToken(node)!; - const closingCurly = context.sourceCode.getLastToken(node)!; - - validateCurlyPair(openingCurly, closingCurly); - }, - TSEnumDeclaration(node): void { - const closingCurly = context.sourceCode.getLastToken(node)!; - const openingCurly = context.sourceCode.getTokenBefore( - node.members.length ? node.members[0] : closingCurly, - )!; - - validateCurlyPair(openingCurly, closingCurly); - }, - }; - }, -}); 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 6813e80f60d8..816c7803604a 100644 --- a/packages/eslint-plugin/src/rules/class-literal-property-style.ts +++ b/packages/eslint-plugin/src/rules/class-literal-property-style.ts @@ -169,17 +169,15 @@ export default createRule({ 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' && - getStringValue(element.key) === name - ); - }); - if (hasDuplicateKeySetter) { - return; - } + const hasDuplicateKeySetter = node.parent.body.some(element => { + return ( + element.type === AST_NODE_TYPES.MethodDefinition && + element.kind === 'set' && + getStringValue(element.key) === name + ); + }); + if (hasDuplicateKeySetter) { + return; } context.report({ diff --git a/packages/eslint-plugin/src/rules/class-methods-use-this.ts b/packages/eslint-plugin/src/rules/class-methods-use-this.ts index 6236a46ddb7b..4f574471e03b 100644 --- a/packages/eslint-plugin/src/rules/class-methods-use-this.ts +++ b/packages/eslint-plugin/src/rules/class-methods-use-this.ts @@ -114,9 +114,7 @@ export default createRule({ if (member?.parent.type === AST_NODE_TYPES.ClassBody) { stack = { member, - class: member.parent.parent as - | TSESTree.ClassDeclaration - | TSESTree.ClassExpression, + class: member.parent.parent, usesThis: false, parent: stack, }; diff --git a/packages/eslint-plugin/src/rules/comma-dangle.ts b/packages/eslint-plugin/src/rules/comma-dangle.ts deleted file mode 100644 index 60b48ecd0b2c..000000000000 --- a/packages/eslint-plugin/src/rules/comma-dangle.ts +++ /dev/null @@ -1,190 +0,0 @@ -import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; - -import type { - InferMessageIdsTypeFromRule, - InferOptionsTypeFromRule, -} from '../util'; -import { createRule, isCommaToken } from '../util'; -import { getESLintCoreRule } from '../util/getESLintCoreRule'; - -const baseRule = getESLintCoreRule('comma-dangle'); - -export type Options = InferOptionsTypeFromRule; -export type MessageIds = InferMessageIdsTypeFromRule; - -type Option = Options[0]; -type NormalizedOptions = Required< - Pick, 'enums' | 'generics' | 'tuples'> ->; - -const OPTION_VALUE_SCHEME = [ - 'always-multiline', - 'always', - 'never', - 'only-multiline', -]; - -const DEFAULT_OPTION_VALUE = 'never'; - -function normalizeOptions(options: Option): NormalizedOptions { - if (typeof options === 'string') { - return { - enums: options, - generics: options, - tuples: options, - }; - } - return { - enums: options.enums ?? DEFAULT_OPTION_VALUE, - generics: options.generics ?? DEFAULT_OPTION_VALUE, - tuples: options.tuples ?? DEFAULT_OPTION_VALUE, - }; -} - -export default createRule({ - name: 'comma-dangle', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/comma-dangle'], - type: 'layout', - docs: { - description: 'Require or disallow trailing commas', - extendsBaseRule: true, - }, - schema: { - $defs: { - value: { - type: 'string', - enum: OPTION_VALUE_SCHEME, - }, - valueWithIgnore: { - type: 'string', - enum: [...OPTION_VALUE_SCHEME, 'ignore'], - }, - }, - type: 'array', - items: [ - { - oneOf: [ - { - $ref: '#/$defs/value', - }, - { - type: 'object', - properties: { - arrays: { $ref: '#/$defs/valueWithIgnore' }, - objects: { $ref: '#/$defs/valueWithIgnore' }, - imports: { $ref: '#/$defs/valueWithIgnore' }, - exports: { $ref: '#/$defs/valueWithIgnore' }, - functions: { $ref: '#/$defs/valueWithIgnore' }, - enums: { $ref: '#/$defs/valueWithIgnore' }, - generics: { $ref: '#/$defs/valueWithIgnore' }, - tuples: { $ref: '#/$defs/valueWithIgnore' }, - }, - additionalProperties: false, - }, - ], - }, - ], - additionalItems: false, - }, - fixable: 'code', - hasSuggestions: baseRule.meta.hasSuggestions, - messages: baseRule.meta.messages, - }, - defaultOptions: ['never'], - create(context, [options]) { - const rules = baseRule.create(context); - - const normalizedOptions = normalizeOptions(options); - - const predicate = { - always: forceComma, - 'always-multiline': forceCommaIfMultiline, - 'only-multiline': allowCommaIfMultiline, - never: forbidComma, - // https://github.com/typescript-eslint/typescript-eslint/issues/7220 - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type, @typescript-eslint/no-empty-function - ignore: () => {}, - }; - - function last(nodes: TSESTree.Node[]): TSESTree.Node | null { - return nodes[nodes.length - 1] ?? null; - } - - function getLastItem(node: TSESTree.Node): TSESTree.Node | null { - switch (node.type) { - case AST_NODE_TYPES.TSEnumDeclaration: - return last(node.members); - case AST_NODE_TYPES.TSTypeParameterDeclaration: - return last(node.params); - case AST_NODE_TYPES.TSTupleType: - return last(node.elementTypes); - default: - return null; - } - } - - function getTrailingToken(node: TSESTree.Node): TSESTree.Token | null { - const last = getLastItem(node); - const trailing = last && context.sourceCode.getTokenAfter(last); - return trailing; - } - - function isMultiline(node: TSESTree.Node): boolean { - const last = getLastItem(node); - const lastToken = context.sourceCode.getLastToken(node); - return last?.loc.end.line !== lastToken?.loc.end.line; - } - - function forbidComma(node: TSESTree.Node): void { - const last = getLastItem(node); - const trailing = getTrailingToken(node); - if (last && trailing && isCommaToken(trailing)) { - context.report({ - node, - messageId: 'unexpected', - fix(fixer) { - return fixer.remove(trailing); - }, - }); - } - } - - function forceComma(node: TSESTree.Node): void { - const last = getLastItem(node); - const trailing = getTrailingToken(node); - if (last && trailing && !isCommaToken(trailing)) { - context.report({ - node, - messageId: 'missing', - fix(fixer) { - return fixer.insertTextAfter(last, ','); - }, - }); - } - } - - function allowCommaIfMultiline(node: TSESTree.Node): void { - if (!isMultiline(node)) { - forbidComma(node); - } - } - - function forceCommaIfMultiline(node: TSESTree.Node): void { - if (isMultiline(node)) { - forceComma(node); - } else { - forbidComma(node); - } - } - - return { - ...rules, - TSEnumDeclaration: predicate[normalizedOptions.enums], - TSTypeParameterDeclaration: predicate[normalizedOptions.generics], - TSTupleType: predicate[normalizedOptions.tuples], - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/comma-spacing.ts b/packages/eslint-plugin/src/rules/comma-spacing.ts deleted file mode 100644 index cd283e4c97ac..000000000000 --- a/packages/eslint-plugin/src/rules/comma-spacing.ts +++ /dev/null @@ -1,201 +0,0 @@ -import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_TOKEN_TYPES } from '@typescript-eslint/utils'; - -import { - createRule, - isClosingBraceToken, - isClosingBracketToken, - isClosingParenToken, - isCommaToken, - isTokenOnSameLine, -} from '../util'; - -type Options = [ - { - before: boolean; - after: boolean; - }, -]; -type MessageIds = 'missing' | 'unexpected'; - -export default createRule({ - name: 'comma-spacing', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/comma-spacing'], - type: 'layout', - docs: { - description: 'Enforce consistent spacing before and after commas', - extendsBaseRule: true, - }, - fixable: 'whitespace', - schema: [ - { - type: 'object', - properties: { - before: { - type: 'boolean', - default: false, - }, - after: { - type: 'boolean', - default: true, - }, - }, - additionalProperties: false, - }, - ], - messages: { - unexpected: `There should be no space {{loc}} ','.`, - missing: `A space is required {{loc}} ','.`, - }, - }, - defaultOptions: [ - { - before: false, - after: true, - }, - ], - create(context, [{ before: spaceBefore, after: spaceAfter }]) { - const tokensAndComments = context.sourceCode.tokensAndComments; - const ignoredTokens = new Set(); - - /** - * Adds null elements of the ArrayExpression or ArrayPattern node to the ignore list - * @param node node to evaluate - */ - function addNullElementsToIgnoreList( - node: TSESTree.ArrayExpression | TSESTree.ArrayPattern, - ): void { - let previousToken = context.sourceCode.getFirstToken(node); - for (const element of node.elements) { - let token: TSESTree.Token | null; - if (element == null) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - token = context.sourceCode.getTokenAfter(previousToken!); - if (token && isCommaToken(token)) { - ignoredTokens.add(token); - } - } else { - token = context.sourceCode.getTokenAfter(element); - } - - previousToken = token; - } - } - - /** - * Adds type parameters trailing comma token to the ignore list - * @param node node to evaluate - */ - function addTypeParametersTrailingCommaToIgnoreList( - node: TSESTree.TSTypeParameterDeclaration, - ): void { - const paramLength = node.params.length; - if (paramLength) { - const param = node.params[paramLength - 1]; - const afterToken = context.sourceCode.getTokenAfter(param); - if (afterToken && isCommaToken(afterToken)) { - ignoredTokens.add(afterToken); - } - } - } - - /** - * Validates the spacing around a comma token. - * @param commaToken The token representing the comma - * @param prevToken The last token before the comma - * @param nextToken The first token after the comma - */ - function validateCommaSpacing( - commaToken: TSESTree.PunctuatorToken, - prevToken: TSESTree.Token | null, - nextToken: TSESTree.Token | null, - ): void { - if ( - prevToken && - isTokenOnSameLine(prevToken, commaToken) && - spaceBefore !== context.sourceCode.isSpaceBetween(prevToken, commaToken) - ) { - context.report({ - node: commaToken, - data: { - loc: 'before', - }, - messageId: spaceBefore ? 'missing' : 'unexpected', - fix: fixer => - spaceBefore - ? fixer.insertTextBefore(commaToken, ' ') - : fixer.replaceTextRange( - [prevToken.range[1], commaToken.range[0]], - '', - ), - }); - } - - if (nextToken && isClosingParenToken(nextToken)) { - return; - } - - if ( - spaceAfter && - nextToken && - (isClosingBraceToken(nextToken) || isClosingBracketToken(nextToken)) - ) { - return; - } - - if (!spaceAfter && nextToken && nextToken.type === AST_TOKEN_TYPES.Line) { - return; - } - - if ( - nextToken && - isTokenOnSameLine(commaToken, nextToken) && - spaceAfter !== context.sourceCode.isSpaceBetween(commaToken, nextToken) - ) { - context.report({ - node: commaToken, - data: { - loc: 'after', - }, - messageId: spaceAfter ? 'missing' : 'unexpected', - fix: fixer => - spaceAfter - ? fixer.insertTextAfter(commaToken, ' ') - : fixer.replaceTextRange( - [commaToken.range[1], nextToken.range[0]], - '', - ), - }); - } - } - - return { - TSTypeParameterDeclaration: addTypeParametersTrailingCommaToIgnoreList, - ArrayExpression: addNullElementsToIgnoreList, - ArrayPattern: addNullElementsToIgnoreList, - - 'Program:exit'(): void { - tokensAndComments.forEach((token, i) => { - if (!isCommaToken(token)) { - return; - } - - const prevToken = tokensAndComments[i - 1]; - const nextToken = tokensAndComments.at(i + 1); - - validateCommaSpacing( - token, - isCommaToken(prevToken) || ignoredTokens.has(token) - ? null - : prevToken, - (nextToken && isCommaToken(nextToken)) || ignoredTokens.has(token) - ? null - : nextToken ?? null, - ); - }); - }, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/func-call-spacing.ts b/packages/eslint-plugin/src/rules/func-call-spacing.ts deleted file mode 100644 index bdd60ac2f90e..000000000000 --- a/packages/eslint-plugin/src/rules/func-call-spacing.ts +++ /dev/null @@ -1,183 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { TSESTree } from '@typescript-eslint/utils'; - -import { - createRule, - isNotOptionalChainPunctuator, - isOpeningParenToken, - isOptionalCallExpression, - LINEBREAK_MATCHER, -} from '../util'; - -export type Options = [ - 'always' | 'never', - { - allowNewlines?: boolean; - }?, -]; -export type MessageIds = - | 'missing' - | 'unexpectedNewline' - | 'unexpectedWhitespace'; - -export default createRule({ - name: 'func-call-spacing', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/func-call-spacing'], - type: 'layout', - docs: { - description: - 'Require or disallow spacing between function identifiers and their invocations', - extendsBaseRule: true, - }, - fixable: 'whitespace', - schema: { - anyOf: [ - { - type: 'array', - items: [ - { - type: 'string', - enum: ['never'], - }, - ], - minItems: 0, - maxItems: 1, - }, - { - type: 'array', - items: [ - { - type: 'string', - enum: ['always'], - }, - { - type: 'object', - properties: { - allowNewlines: { - type: 'boolean', - }, - }, - additionalProperties: false, - }, - ], - minItems: 0, - maxItems: 2, - }, - ], - }, - - messages: { - unexpectedWhitespace: - 'Unexpected whitespace between function name and paren.', - unexpectedNewline: 'Unexpected newline between function name and paren.', - missing: 'Missing space between function name and paren.', - }, - }, - defaultOptions: ['never', {}], - create(context, [option, config]) { - const text = context.sourceCode.getText(); - - /** - * Check if open space is present in a function name - * @param node node to evaluate - * @private - */ - function checkSpacing( - node: TSESTree.CallExpression | TSESTree.NewExpression, - ): void { - const isOptionalCall = isOptionalCallExpression(node); - - const closingParenToken = context.sourceCode.getLastToken(node)!; - const lastCalleeTokenWithoutPossibleParens = - context.sourceCode.getLastToken(node.typeArguments ?? node.callee)!; - const openingParenToken = context.sourceCode.getFirstTokenBetween( - lastCalleeTokenWithoutPossibleParens, - closingParenToken, - isOpeningParenToken, - ); - if (!openingParenToken || openingParenToken.range[1] >= node.range[1]) { - // new expression with no parens... - return; - } - const lastCalleeToken = context.sourceCode.getTokenBefore( - openingParenToken, - isNotOptionalChainPunctuator, - )!; - - const textBetweenTokens = text - .slice(lastCalleeToken.range[1], openingParenToken.range[0]) - .replace(/\/\*.*?\*\//gu, ''); - const hasWhitespace = /\s/u.test(textBetweenTokens); - const hasNewline = - hasWhitespace && LINEBREAK_MATCHER.test(textBetweenTokens); - - if (option === 'never') { - if (hasWhitespace) { - return context.report({ - node, - loc: lastCalleeToken.loc.start, - messageId: 'unexpectedWhitespace', - fix(fixer) { - /* - * Only autofix if there is no newline - * https://github.com/eslint/eslint/issues/7787 - */ - if ( - !hasNewline && - // don't fix optional calls - !isOptionalCall - ) { - return fixer.removeRange([ - lastCalleeToken.range[1], - openingParenToken.range[0], - ]); - } - - return null; - }, - }); - } - } else if (isOptionalCall) { - // disallow: - // foo?. (); - // foo ?.(); - // foo ?. (); - if (hasWhitespace || hasNewline) { - context.report({ - node, - loc: lastCalleeToken.loc.start, - messageId: 'unexpectedWhitespace', - }); - } - } else if (!hasWhitespace) { - context.report({ - node, - loc: lastCalleeToken.loc.start, - messageId: 'missing', - fix(fixer) { - return fixer.insertTextBefore(openingParenToken, ' '); - }, - }); - } else if (!config!.allowNewlines && hasNewline) { - context.report({ - node, - loc: lastCalleeToken.loc.start, - messageId: 'unexpectedNewline', - fix(fixer) { - return fixer.replaceTextRange( - [lastCalleeToken.range[1], openingParenToken.range[0]], - ' ', - ); - }, - }); - } - } - - return { - CallExpression: checkSpacing, - NewExpression: checkSpacing, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/indent.ts b/packages/eslint-plugin/src/rules/indent.ts deleted file mode 100644 index c52b761fb745..000000000000 --- a/packages/eslint-plugin/src/rules/indent.ts +++ /dev/null @@ -1,497 +0,0 @@ -/** - * Note this file is rather type-unsafe in its current state. - * This is due to some really funky type conversions between different node types. - * This is done intentionally based on the internal implementation of the base indent rule. - */ -/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, eslint-plugin/no-property-in-node */ - -import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; - -import type { - InferMessageIdsTypeFromRule, - InferOptionsTypeFromRule, -} from '../util'; -import { createRule } from '../util'; -import { getESLintCoreRule } from '../util/getESLintCoreRule'; - -const baseRule = getESLintCoreRule('indent'); - -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; - -const KNOWN_NODES = new Set([ - // Class properties aren't yet supported by eslint... - AST_NODE_TYPES.PropertyDefinition, - - // ts keywords - AST_NODE_TYPES.TSAbstractKeyword, - AST_NODE_TYPES.TSAnyKeyword, - AST_NODE_TYPES.TSBooleanKeyword, - AST_NODE_TYPES.TSNeverKeyword, - AST_NODE_TYPES.TSNumberKeyword, - AST_NODE_TYPES.TSStringKeyword, - AST_NODE_TYPES.TSSymbolKeyword, - AST_NODE_TYPES.TSUndefinedKeyword, - AST_NODE_TYPES.TSUnknownKeyword, - AST_NODE_TYPES.TSVoidKeyword, - AST_NODE_TYPES.TSNullKeyword, - - // ts specific nodes we want to support - AST_NODE_TYPES.TSAbstractPropertyDefinition, - AST_NODE_TYPES.TSAbstractMethodDefinition, - AST_NODE_TYPES.TSArrayType, - AST_NODE_TYPES.TSAsExpression, - AST_NODE_TYPES.TSCallSignatureDeclaration, - AST_NODE_TYPES.TSConditionalType, - AST_NODE_TYPES.TSConstructorType, - AST_NODE_TYPES.TSConstructSignatureDeclaration, - AST_NODE_TYPES.TSDeclareFunction, - AST_NODE_TYPES.TSEmptyBodyFunctionExpression, - AST_NODE_TYPES.TSEnumDeclaration, - AST_NODE_TYPES.TSEnumMember, - AST_NODE_TYPES.TSExportAssignment, - AST_NODE_TYPES.TSExternalModuleReference, - AST_NODE_TYPES.TSFunctionType, - AST_NODE_TYPES.TSImportType, - AST_NODE_TYPES.TSIndexedAccessType, - AST_NODE_TYPES.TSIndexSignature, - AST_NODE_TYPES.TSInferType, - AST_NODE_TYPES.TSInterfaceBody, - AST_NODE_TYPES.TSInterfaceDeclaration, - AST_NODE_TYPES.TSInterfaceHeritage, - AST_NODE_TYPES.TSIntersectionType, - AST_NODE_TYPES.TSImportEqualsDeclaration, - AST_NODE_TYPES.TSLiteralType, - AST_NODE_TYPES.TSMappedType, - AST_NODE_TYPES.TSMethodSignature, - 'TSMinusToken', - AST_NODE_TYPES.TSModuleBlock, - AST_NODE_TYPES.TSModuleDeclaration, - AST_NODE_TYPES.TSNonNullExpression, - AST_NODE_TYPES.TSParameterProperty, - 'TSPlusToken', - AST_NODE_TYPES.TSPropertySignature, - AST_NODE_TYPES.TSQualifiedName, - 'TSQuestionToken', - AST_NODE_TYPES.TSRestType, - AST_NODE_TYPES.TSThisType, - AST_NODE_TYPES.TSTupleType, - AST_NODE_TYPES.TSTypeAnnotation, - AST_NODE_TYPES.TSTypeLiteral, - AST_NODE_TYPES.TSTypeOperator, - AST_NODE_TYPES.TSTypeParameter, - AST_NODE_TYPES.TSTypeParameterDeclaration, - AST_NODE_TYPES.TSTypeParameterInstantiation, - AST_NODE_TYPES.TSTypeReference, - AST_NODE_TYPES.TSUnionType, - AST_NODE_TYPES.Decorator, -]); - -export default createRule({ - name: 'indent', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/indent'], - type: 'layout', - docs: { - description: 'Enforce consistent indentation', - // too opinionated to be recommended - extendsBaseRule: true, - }, - fixable: 'whitespace', - hasSuggestions: baseRule.meta.hasSuggestions, - schema: baseRule.meta.schema, - messages: baseRule.meta.messages, - }, - defaultOptions: [ - // typescript docs and playground use 4 space indent - 4, - { - // typescript docs indent the case from the switch - // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-1-8.html#example-4 - SwitchCase: 1, - flatTernaryExpressions: false, - ignoredNodes: [], - }, - ], - create(context, optionsWithDefaults) { - // because we extend the base rule, have to update opts on the context - // the context defines options as readonly though... - const contextWithDefaults: typeof context = Object.create(context, { - options: { - writable: false, - configurable: false, - value: optionsWithDefaults, - }, - }); - - const rules = baseRule.create(contextWithDefaults); - - /** - * Converts from a TSPropertySignature to a Property - * @param node a TSPropertySignature node - * @param [type] the type to give the new node - * @returns a Property node - */ - function TSPropertySignatureToProperty( - node: - | TSESTree.TSEnumMember - | TSESTree.TSPropertySignature - | TSESTree.TypeElement, - type: - | AST_NODE_TYPES.Property - | AST_NODE_TYPES.PropertyDefinition = AST_NODE_TYPES.Property, - ): TSESTree.Node | null { - const base = { - // indent doesn't actually use these - key: null as any, - value: null as any, - - // Property flags - computed: false, - method: false, - kind: 'init', - // this will stop eslint from interrogating the type literal - shorthand: true, - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - }; - if (type === AST_NODE_TYPES.Property) { - return { - type, - ...base, - } as TSESTree.Property; - } - return { - type, - accessibility: undefined, - declare: false, - decorators: [], - definite: false, - optional: false, - override: false, - readonly: false, - static: false, - typeAnnotation: undefined, - ...base, - } as TSESTree.PropertyDefinition; - } - - return { - ...rules, - // overwrite the base rule here so we can use our KNOWN_NODES list instead - '*:exit'(node: TSESTree.Node): void { - // For nodes we care about, skip the default handling, because it just marks the node as ignored... - if (!KNOWN_NODES.has(node.type)) { - rules['*:exit'](node); - } - }, - - VariableDeclaration(node: TSESTree.VariableDeclaration): void { - // https://github.com/typescript-eslint/typescript-eslint/issues/441 - if (node.declarations.length === 0) { - return; - } - - return rules.VariableDeclaration(node); - }, - - TSAsExpression(node: TSESTree.TSAsExpression): void { - // transform it to a BinaryExpression - return rules['BinaryExpression, LogicalExpression']({ - type: AST_NODE_TYPES.BinaryExpression, - operator: 'as' as any, - left: node.expression, - // the first typeAnnotation includes the as token - right: node.typeAnnotation as any, - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - }); - }, - - TSConditionalType(node: TSESTree.TSConditionalType): void { - // transform it to a ConditionalExpression - return rules.ConditionalExpression({ - type: AST_NODE_TYPES.ConditionalExpression, - test: { - parent: node, - type: AST_NODE_TYPES.BinaryExpression, - operator: 'extends' as any, - left: node.checkType as any, - right: node.extendsType as any, - - // location data - range: [node.checkType.range[0], node.extendsType.range[1]], - loc: { - start: node.checkType.loc.start, - end: node.extendsType.loc.end, - }, - }, - consequent: node.trueType as any, - alternate: node.falseType as any, - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - }); - }, - - 'TSEnumDeclaration, TSTypeLiteral'( - node: TSESTree.TSEnumDeclaration | TSESTree.TSTypeLiteral, - ): void { - // transform it to an ObjectExpression - return rules['ObjectExpression, ObjectPattern']({ - type: AST_NODE_TYPES.ObjectExpression, - properties: ( - node.members as (TSESTree.TSEnumMember | TSESTree.TypeElement)[] - ).map( - member => - TSPropertySignatureToProperty(member) as TSESTree.Property, - ), - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - }); - }, - - TSImportEqualsDeclaration( - node: TSESTree.TSImportEqualsDeclaration, - ): void { - // transform it to an VariableDeclaration - // use VariableDeclaration instead of ImportDeclaration because it's essentially the same thing - const { id, moduleReference } = node; - - return rules.VariableDeclaration({ - type: AST_NODE_TYPES.VariableDeclaration, - kind: 'const' as const, - declarations: [ - { - type: AST_NODE_TYPES.VariableDeclarator, - range: [id.range[0], moduleReference.range[1]], - loc: { - start: id.loc.start, - end: moduleReference.loc.end, - }, - id: id, - init: { - type: AST_NODE_TYPES.CallExpression, - callee: { - type: AST_NODE_TYPES.Identifier, - name: 'require', - range: [ - moduleReference.range[0], - moduleReference.range[0] + 'require'.length, - ], - loc: { - start: moduleReference.loc.start, - end: { - line: moduleReference.loc.end.line, - column: moduleReference.loc.start.line + 'require'.length, - }, - }, - }, - arguments: - 'expression' in moduleReference - ? [moduleReference.expression] - : [], - - // location data - range: moduleReference.range, - loc: moduleReference.loc, - }, - } as TSESTree.VariableDeclarator, - ], - declare: false, - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - }); - }, - - TSIndexedAccessType(node: TSESTree.TSIndexedAccessType): void { - // convert to a MemberExpression - return rules['MemberExpression, JSXMemberExpression, MetaProperty']({ - type: AST_NODE_TYPES.MemberExpression, - object: node.objectType as any, - property: node.indexType as any, - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - optional: false, - computed: true, - }); - }, - - TSInterfaceBody(node: TSESTree.TSInterfaceBody): void { - // transform it to an ClassBody - return rules['BlockStatement, ClassBody']({ - type: AST_NODE_TYPES.ClassBody, - body: node.body.map( - p => - TSPropertySignatureToProperty( - p, - AST_NODE_TYPES.PropertyDefinition, - ) as TSESTree.PropertyDefinition, - ), - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - }); - }, - - 'TSInterfaceDeclaration[extends.length > 0]'( - node: TSESTree.TSInterfaceDeclaration, - ): void { - // transform it to a ClassDeclaration - return rules[ - 'ClassDeclaration[superClass], ClassExpression[superClass]' - ]({ - type: AST_NODE_TYPES.ClassDeclaration, - body: node.body as any, - id: null, - // TODO: This is invalid, there can be more than one extends in interface - superClass: node.extends[0].expression as any, - abstract: false, - declare: false, - decorators: [], - implements: [], - superTypeArguments: undefined, - superTypeParameters: undefined, - typeParameters: undefined, - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - }); - }, - - TSMappedType(node: TSESTree.TSMappedType): void { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const squareBracketStart = context.sourceCode.getTokenBefore( - node.typeParameter, - )!; - - // transform it to an ObjectExpression - return rules['ObjectExpression, ObjectPattern']({ - type: AST_NODE_TYPES.ObjectExpression, - properties: [ - { - parent: node, - type: AST_NODE_TYPES.Property, - key: node.typeParameter as any, - value: node.typeAnnotation as any, - - // location data - range: [ - squareBracketStart.range[0], - node.typeAnnotation - ? node.typeAnnotation.range[1] - : squareBracketStart.range[0], - ], - loc: { - start: squareBracketStart.loc.start, - end: node.typeAnnotation - ? node.typeAnnotation.loc.end - : squareBracketStart.loc.end, - }, - kind: 'init' as const, - computed: false, - method: false, - optional: false, - shorthand: false, - }, - ], - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - }); - }, - - TSModuleBlock(node: TSESTree.TSModuleBlock): void { - // transform it to a BlockStatement - return rules['BlockStatement, ClassBody']({ - type: AST_NODE_TYPES.BlockStatement, - body: node.body as any, - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - }); - }, - - TSQualifiedName(node: TSESTree.TSQualifiedName): void { - return rules['MemberExpression, JSXMemberExpression, MetaProperty']({ - type: AST_NODE_TYPES.MemberExpression, - object: node.left as any, - property: node.right as any, - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - optional: false, - computed: false, - }); - }, - - TSTupleType(node: TSESTree.TSTupleType): void { - // transform it to an ArrayExpression - return rules['ArrayExpression, ArrayPattern']({ - type: AST_NODE_TYPES.ArrayExpression, - elements: node.elementTypes as any, - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - }); - }, - - TSTypeParameterDeclaration( - node: TSESTree.TSTypeParameterDeclaration, - ): void { - if (!node.params.length) { - return; - } - - const [name, ...attributes] = node.params; - - // JSX is about the closest we can get because the angle brackets - // it's not perfect but it works! - return rules.JSXOpeningElement({ - type: AST_NODE_TYPES.JSXOpeningElement, - selfClosing: false, - name: name as any, - attributes: attributes as any, - typeArguments: undefined, - typeParameters: undefined, - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - }); - }, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index f7f064b0b34c..75e5d46a1292 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -5,13 +5,8 @@ import arrayType from './array-type'; import awaitThenable from './await-thenable'; import banTsComment from './ban-ts-comment'; import banTslintComment from './ban-tslint-comment'; -import banTypes from './ban-types'; -import blockSpacing from './block-spacing'; -import braceStyle from './brace-style'; import classLiteralPropertyStyle from './class-literal-property-style'; import classMethodsUseThis from './class-methods-use-this'; -import commaDangle from './comma-dangle'; -import commaSpacing from './comma-spacing'; import consistentGenericConstructors from './consistent-generic-constructors'; import consistentIndexedObjectStyle from './consistent-indexed-object-style'; import consistentReturn from './consistent-return'; @@ -24,15 +19,8 @@ import dotNotation from './dot-notation'; import explicitFunctionReturnType from './explicit-function-return-type'; import explicitMemberAccessibility from './explicit-member-accessibility'; import explicitModuleBoundaryTypes from './explicit-module-boundary-types'; -import funcCallSpacing from './func-call-spacing'; -import indent from './indent'; import initDeclarations from './init-declarations'; -import keySpacing from './key-spacing'; -import keywordSpacing from './keyword-spacing'; -import linesAroundComment from './lines-around-comment'; -import linesBetweenClassMembers from './lines-between-class-members'; import maxParams from './max-params'; -import memberDelimiterStyle from './member-delimiter-style'; import memberOrdering from './member-ordering'; import methodSignatureStyle from './method-signature-style'; import namingConvention from './naming-convention'; @@ -50,8 +38,6 @@ import noEmptyInterface from './no-empty-interface'; import noEmptyObjectType from './no-empty-object-type'; import noExplicitAny from './no-explicit-any'; import noExtraNonNullAssertion from './no-extra-non-null-assertion'; -import noExtraParens from './no-extra-parens'; -import noExtraSemi from './no-extra-semi'; import noExtraneousClass from './no-extraneous-class'; import noFloatingPromises from './no-floating-promises'; import noForInArray from './no-for-in-array'; @@ -75,9 +61,9 @@ import noRedeclare from './no-redeclare'; import noRedundantTypeConstituents from './no-redundant-type-constituents'; import noRequireImports from './no-require-imports'; import noRestrictedImports from './no-restricted-imports'; +import noRestrictedTypes from './no-restricted-types'; import noShadow from './no-shadow'; import noThisAlias from './no-this-alias'; -import noThrowLiteral from './no-throw-literal'; import noTypeAlias from './no-type-alias'; import noUnnecessaryBooleanLiteralCompare from './no-unnecessary-boolean-literal-compare'; import noUnnecessaryCondition from './no-unnecessary-condition'; @@ -102,13 +88,10 @@ import noUnusedVars from './no-unused-vars'; import noUseBeforeDefine from './no-use-before-define'; import noUselessConstructor from './no-useless-constructor'; import noUselessEmptyExport from './no-useless-empty-export'; -import noUselessTemplateLiterals from './no-useless-template-literals'; import noVarRequires from './no-var-requires'; import noWrapperObjectTypes from './no-wrapper-object-types'; import nonNullableTypeAssertionStyle from './non-nullable-type-assertion-style'; -import objectCurlySpacing from './object-curly-spacing'; import onlyThrowError from './only-throw-error'; -import paddingLineBetweenStatements from './padding-line-between-statements'; import parameterProperties from './parameter-properties'; import preferAsConst from './prefer-as-const'; import preferDestructuring from './prefer-destructuring'; @@ -130,21 +113,15 @@ import preferReturnThisType from './prefer-return-this-type'; import preferStringStartsEndsWith from './prefer-string-starts-ends-with'; import preferTsExpectError from './prefer-ts-expect-error'; import promiseFunctionAsync from './promise-function-async'; -import quotes from './quotes'; import requireArraySortCompare from './require-array-sort-compare'; import requireAwait from './require-await'; import restrictPlusOperands from './restrict-plus-operands'; import restrictTemplateExpressions from './restrict-template-expressions'; import returnAwait from './return-await'; -import semi from './semi'; import sortTypeConstituents from './sort-type-constituents'; -import spaceBeforeBlocks from './space-before-blocks'; -import spaceBeforeFunctionParen from './space-before-function-paren'; -import spaceInfixOps from './space-infix-ops'; import strictBooleanExpressions from './strict-boolean-expressions'; import switchExhaustivenessCheck from './switch-exhaustiveness-check'; import tripleSlashReference from './triple-slash-reference'; -import typeAnnotationSpacing from './type-annotation-spacing'; import typedef from './typedef'; import unboundMethod from './unbound-method'; import unifiedSignatures from './unified-signatures'; @@ -156,13 +133,8 @@ export default { 'await-thenable': awaitThenable, 'ban-ts-comment': banTsComment, 'ban-tslint-comment': banTslintComment, - 'ban-types': banTypes, - 'block-spacing': blockSpacing, - 'brace-style': braceStyle, 'class-literal-property-style': classLiteralPropertyStyle, 'class-methods-use-this': classMethodsUseThis, - 'comma-dangle': commaDangle, - 'comma-spacing': commaSpacing, 'consistent-generic-constructors': consistentGenericConstructors, 'consistent-indexed-object-style': consistentIndexedObjectStyle, 'consistent-return': consistentReturn, @@ -175,15 +147,8 @@ export default { 'explicit-function-return-type': explicitFunctionReturnType, 'explicit-member-accessibility': explicitMemberAccessibility, 'explicit-module-boundary-types': explicitModuleBoundaryTypes, - 'func-call-spacing': funcCallSpacing, - indent: indent, 'init-declarations': initDeclarations, - 'key-spacing': keySpacing, - 'keyword-spacing': keywordSpacing, - 'lines-around-comment': linesAroundComment, - 'lines-between-class-members': linesBetweenClassMembers, 'max-params': maxParams, - 'member-delimiter-style': memberDelimiterStyle, 'member-ordering': memberOrdering, 'method-signature-style': methodSignatureStyle, 'naming-convention': namingConvention, @@ -201,8 +166,6 @@ export default { 'no-empty-object-type': noEmptyObjectType, 'no-explicit-any': noExplicitAny, 'no-extra-non-null-assertion': noExtraNonNullAssertion, - 'no-extra-parens': noExtraParens, - 'no-extra-semi': noExtraSemi, 'no-extraneous-class': noExtraneousClass, 'no-floating-promises': noFloatingPromises, 'no-for-in-array': noForInArray, @@ -226,9 +189,9 @@ export default { 'no-redundant-type-constituents': noRedundantTypeConstituents, 'no-require-imports': noRequireImports, 'no-restricted-imports': noRestrictedImports, + 'no-restricted-types': noRestrictedTypes, 'no-shadow': noShadow, 'no-this-alias': noThisAlias, - 'no-throw-literal': noThrowLiteral, 'no-type-alias': noTypeAlias, 'no-unnecessary-boolean-literal-compare': noUnnecessaryBooleanLiteralCompare, 'no-unnecessary-condition': noUnnecessaryCondition, @@ -254,13 +217,10 @@ export default { 'no-use-before-define': noUseBeforeDefine, 'no-useless-constructor': noUselessConstructor, 'no-useless-empty-export': noUselessEmptyExport, - 'no-useless-template-literals': noUselessTemplateLiterals, 'no-var-requires': noVarRequires, 'no-wrapper-object-types': noWrapperObjectTypes, 'non-nullable-type-assertion-style': nonNullableTypeAssertionStyle, - 'object-curly-spacing': objectCurlySpacing, 'only-throw-error': onlyThrowError, - 'padding-line-between-statements': paddingLineBetweenStatements, 'parameter-properties': parameterProperties, 'prefer-as-const': preferAsConst, 'prefer-destructuring': preferDestructuring, @@ -282,21 +242,15 @@ export default { 'prefer-string-starts-ends-with': preferStringStartsEndsWith, 'prefer-ts-expect-error': preferTsExpectError, 'promise-function-async': promiseFunctionAsync, - quotes: quotes, 'require-array-sort-compare': requireArraySortCompare, 'require-await': requireAwait, 'restrict-plus-operands': restrictPlusOperands, 'restrict-template-expressions': restrictTemplateExpressions, 'return-await': returnAwait, - semi: semi, 'sort-type-constituents': sortTypeConstituents, - 'space-before-blocks': spaceBeforeBlocks, - 'space-before-function-paren': spaceBeforeFunctionParen, - 'space-infix-ops': spaceInfixOps, 'strict-boolean-expressions': strictBooleanExpressions, 'switch-exhaustiveness-check': switchExhaustivenessCheck, 'triple-slash-reference': tripleSlashReference, - 'type-annotation-spacing': typeAnnotationSpacing, typedef: typedef, 'unbound-method': unboundMethod, 'unified-signatures': unifiedSignatures, diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts deleted file mode 100644 index 7ca41df72cc0..000000000000 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ /dev/null @@ -1,440 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; - -import type { - InferMessageIdsTypeFromRule, - InferOptionsTypeFromRule, -} from '../util'; -import { - createRule, - getStringLength, - isClosingBracketToken, - isColonToken, -} from '../util'; -import { getESLintCoreRule } from '../util/getESLintCoreRule'; - -const baseRule = getESLintCoreRule('key-spacing'); - -export type Options = InferOptionsTypeFromRule; -export type MessageIds = InferMessageIdsTypeFromRule; - -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -const baseSchema = Array.isArray(baseRule.meta.schema) - ? baseRule.meta.schema[0] - : baseRule.meta.schema; - -export default createRule({ - name: 'key-spacing', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/key-spacing'], - type: 'layout', - docs: { - description: - 'Enforce consistent spacing between property names and type annotations in types and interfaces', - extendsBaseRule: true, - }, - fixable: 'whitespace', - hasSuggestions: baseRule.meta.hasSuggestions, - schema: [baseSchema], - messages: baseRule.meta.messages, - }, - defaultOptions: [{}], - - create(context, [options]) { - const baseRules = baseRule.create(context); - - /** - * @returns the column of the position after converting all unicode characters in the line to 1 char length - */ - function adjustedColumn(position: TSESTree.Position): number { - const line = position.line - 1; // position.line is 1-indexed - return getStringLength( - context.sourceCode.lines.at(line)!.slice(0, position.column), - ); - } - - /** - * Starting from the given a node (a property.key node here) looks forward - * until it finds the last token before a colon punctuator and returns it. - */ - function getLastTokenBeforeColon(node: TSESTree.Node): TSESTree.Token { - const colonToken = context.sourceCode.getTokenAfter(node, isColonToken)!; - - return context.sourceCode.getTokenBefore(colonToken)!; - } - - type KeyTypeNode = - | TSESTree.PropertyDefinition - | TSESTree.TSIndexSignature - | TSESTree.TSPropertySignature; - - type KeyTypeNodeWithTypeAnnotation = KeyTypeNode & { - typeAnnotation: TSESTree.TSTypeAnnotation; - }; - - function isKeyTypeNode( - node: TSESTree.Node, - ): node is KeyTypeNodeWithTypeAnnotation { - return ( - (node.type === AST_NODE_TYPES.TSPropertySignature || - node.type === AST_NODE_TYPES.TSIndexSignature || - node.type === AST_NODE_TYPES.PropertyDefinition) && - !!node.typeAnnotation - ); - } - - function isApplicable( - node: TSESTree.Node, - ): node is KeyTypeNodeWithTypeAnnotation { - return ( - isKeyTypeNode(node) && - node.typeAnnotation.loc.start.line === node.loc.end.line - ); - } - - /** - * To handle index signatures, to get the whole text for the parameters - */ - function getKeyText(node: KeyTypeNodeWithTypeAnnotation): string { - if (node.type !== AST_NODE_TYPES.TSIndexSignature) { - return context.sourceCode.getText(node.key); - } - - const code = context.sourceCode.getText(node); - return code.slice( - 0, - context.sourceCode.getTokenAfter( - node.parameters.at(-1)!, - isClosingBracketToken, - )!.range[1] - node.range[0], - ); - } - - /** - * To handle index signatures, be able to get the end position of the parameters - */ - function getKeyLocEnd( - node: KeyTypeNodeWithTypeAnnotation, - ): TSESTree.Position { - return getLastTokenBeforeColon( - node.type !== AST_NODE_TYPES.TSIndexSignature - ? node.key - : node.parameters.at(-1)!, - ).loc.end; - } - - function checkBeforeColon( - node: KeyTypeNodeWithTypeAnnotation, - expectedWhitespaceBeforeColon: number, - mode: 'minimum' | 'strict', - ): void { - const { typeAnnotation } = node; - const colon = typeAnnotation.loc.start.column; - const keyEnd = getKeyLocEnd(node); - const difference = colon - keyEnd.column - expectedWhitespaceBeforeColon; - if (mode === 'strict' ? difference : difference < 0) { - context.report({ - node, - messageId: difference > 0 ? 'extraKey' : 'missingKey', - fix: fixer => { - if (difference > 0) { - return fixer.removeRange([ - typeAnnotation.range[0] - difference, - typeAnnotation.range[0], - ]); - } - return fixer.insertTextBefore( - typeAnnotation, - ' '.repeat(-difference), - ); - }, - data: { - computed: '', - key: getKeyText(node), - }, - }); - } - } - - function checkAfterColon( - node: KeyTypeNodeWithTypeAnnotation, - expectedWhitespaceAfterColon: number, - mode: 'minimum' | 'strict', - ): void { - const { typeAnnotation } = node; - const colonToken = context.sourceCode.getFirstToken(typeAnnotation)!; - const typeStart = context.sourceCode.getTokenAfter(colonToken, { - includeComments: true, - })!.loc.start.column; - const difference = - typeStart - - colonToken.loc.start.column - - 1 - - expectedWhitespaceAfterColon; - if (mode === 'strict' ? difference : difference < 0) { - context.report({ - node, - messageId: difference > 0 ? 'extraValue' : 'missingValue', - fix: fixer => { - if (difference > 0) { - return fixer.removeRange([ - colonToken.range[1], - colonToken.range[1] + difference, - ]); - } - return fixer.insertTextAfter(colonToken, ' '.repeat(-difference)); - }, - data: { - computed: '', - key: getKeyText(node), - }, - }); - } - } - - // adapted from https://github.com/eslint/eslint/blob/ba74253e8bd63e9e163bbee0540031be77e39253/lib/rules/key-spacing.js#L356 - function continuesAlignGroup( - lastMember: TSESTree.Node, - candidate: TSESTree.Node, - ): boolean { - const groupEndLine = lastMember.loc.start.line; - const candidateValueStartLine = ( - isKeyTypeNode(candidate) ? candidate.typeAnnotation : candidate - ).loc.start.line; - - if (candidateValueStartLine === groupEndLine) { - return false; - } - - if (candidateValueStartLine - groupEndLine === 1) { - return true; - } - - /* - * Check that the first comment is adjacent to the end of the group, the - * last comment is adjacent to the candidate property, and that successive - * comments are adjacent to each other. - */ - const leadingComments = context.sourceCode.getCommentsBefore(candidate); - - if ( - leadingComments.length && - leadingComments[0].loc.start.line - groupEndLine <= 1 && - candidateValueStartLine - leadingComments.at(-1)!.loc.end.line <= 1 - ) { - for (let i = 1; i < leadingComments.length; i++) { - if ( - leadingComments[i].loc.start.line - - leadingComments[i - 1].loc.end.line > - 1 - ) { - return false; - } - } - return true; - } - - return false; - } - - function checkAlignGroup(group: TSESTree.Node[]): void { - let alignColumn = 0; - const align: 'colon' | 'value' = - (typeof options.align === 'object' - ? options.align.on - : typeof options.multiLine?.align === 'object' - ? options.multiLine.align.on - : options.multiLine?.align ?? options.align) ?? 'colon'; - const beforeColon = - (typeof options.align === 'object' - ? options.align.beforeColon - : options.multiLine - ? typeof options.multiLine.align === 'object' - ? options.multiLine.align.beforeColon - : options.multiLine.beforeColon - : options.beforeColon) ?? false; - const expectedWhitespaceBeforeColon = beforeColon ? 1 : 0; - const afterColon = - (typeof options.align === 'object' - ? options.align.afterColon - : options.multiLine - ? typeof options.multiLine.align === 'object' - ? options.multiLine.align.afterColon - : options.multiLine.afterColon - : options.afterColon) ?? true; - const expectedWhitespaceAfterColon = afterColon ? 1 : 0; - const mode = - (typeof options.align === 'object' - ? options.align.mode - : options.multiLine - ? typeof options.multiLine.align === 'object' - ? // same behavior as in original rule - options.multiLine.align.mode ?? options.multiLine.mode - : options.multiLine.mode - : options.mode) ?? 'strict'; - - for (const node of group) { - if (isKeyTypeNode(node)) { - const keyEnd = adjustedColumn(getKeyLocEnd(node)); - alignColumn = Math.max( - alignColumn, - align === 'colon' - ? keyEnd + expectedWhitespaceBeforeColon - : keyEnd + - ':'.length + - expectedWhitespaceAfterColon + - expectedWhitespaceBeforeColon, - ); - } - } - - for (const node of group) { - if (!isApplicable(node)) { - continue; - } - const { typeAnnotation } = node; - const toCheck = - align === 'colon' ? typeAnnotation : typeAnnotation.typeAnnotation; - const difference = adjustedColumn(toCheck.loc.start) - alignColumn; - - if (difference) { - context.report({ - node, - messageId: - difference > 0 - ? align === 'colon' - ? 'extraKey' - : 'extraValue' - : align === 'colon' - ? 'missingKey' - : 'missingValue', - fix: fixer => { - if (difference > 0) { - return fixer.removeRange([ - toCheck.range[0] - difference, - toCheck.range[0], - ]); - } - return fixer.insertTextBefore(toCheck, ' '.repeat(-difference)); - }, - data: { - computed: '', - key: getKeyText(node), - }, - }); - } - - if (align === 'colon') { - checkAfterColon(node, expectedWhitespaceAfterColon, mode); - } else { - checkBeforeColon(node, expectedWhitespaceBeforeColon, mode); - } - } - } - - function checkIndividualNode( - node: TSESTree.Node, - { singleLine }: { singleLine: boolean }, - ): void { - const beforeColon = - (singleLine - ? options.singleLine - ? options.singleLine.beforeColon - : options.beforeColon - : options.multiLine - ? options.multiLine.beforeColon - : options.beforeColon) ?? false; - const expectedWhitespaceBeforeColon = beforeColon ? 1 : 0; - const afterColon = - (singleLine - ? options.singleLine - ? options.singleLine.afterColon - : options.afterColon - : options.multiLine - ? options.multiLine.afterColon - : options.afterColon) ?? true; - const expectedWhitespaceAfterColon = afterColon ? 1 : 0; - const mode = - (singleLine - ? options.singleLine - ? options.singleLine.mode - : options.mode - : options.multiLine - ? options.multiLine.mode - : options.mode) ?? 'strict'; - - if (isApplicable(node)) { - checkBeforeColon(node, expectedWhitespaceBeforeColon, mode); - checkAfterColon(node, expectedWhitespaceAfterColon, mode); - } - } - - function validateBody( - body: - | TSESTree.ClassBody - | TSESTree.TSInterfaceBody - | TSESTree.TSTypeLiteral, - ): void { - const isSingleLine = body.loc.start.line === body.loc.end.line; - - const members = - body.type === AST_NODE_TYPES.TSTypeLiteral ? body.members : body.body; - - let alignGroups: TSESTree.Node[][] = []; - let unalignedElements: TSESTree.Node[] = []; - - if (options.align || options.multiLine?.align) { - let currentAlignGroup: TSESTree.Node[] = []; - alignGroups.push(currentAlignGroup); - - let prevNode: TSESTree.Node | undefined = undefined; - - for (const node of members) { - let prevAlignedNode = currentAlignGroup.at(-1); - if (prevAlignedNode !== prevNode) { - prevAlignedNode = undefined; - } - - if (prevAlignedNode && continuesAlignGroup(prevAlignedNode, node)) { - currentAlignGroup.push(node); - } else if (prevNode?.loc.start.line === node.loc.start.line) { - if (prevAlignedNode) { - // Here, prevNode === prevAlignedNode === currentAlignGroup.at(-1) - unalignedElements.push(prevAlignedNode); - currentAlignGroup.pop(); - } - unalignedElements.push(node); - } else { - currentAlignGroup = [node]; - alignGroups.push(currentAlignGroup); - } - - prevNode = node; - } - - unalignedElements = unalignedElements.concat( - ...alignGroups.filter(group => group.length === 1), - ); - alignGroups = alignGroups.filter(group => group.length >= 2); - } else { - unalignedElements = members; - } - - for (const group of alignGroups) { - checkAlignGroup(group); - } - - for (const node of unalignedElements) { - checkIndividualNode(node, { singleLine: isSingleLine }); - } - } - return { - ...baseRules, - TSTypeLiteral: validateBody, - TSInterfaceBody: validateBody, - ClassBody: validateBody, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/keyword-spacing.ts b/packages/eslint-plugin/src/rules/keyword-spacing.ts deleted file mode 100644 index 7b47c8294260..000000000000 --- a/packages/eslint-plugin/src/rules/keyword-spacing.ts +++ /dev/null @@ -1,124 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; -import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema'; - -import type { - InferMessageIdsTypeFromRule, - InferOptionsTypeFromRule, -} from '../util'; -import { createRule, deepMerge, nullThrows, NullThrowsReasons } from '../util'; -import { getESLintCoreRule } from '../util/getESLintCoreRule'; - -const baseRule = getESLintCoreRule('keyword-spacing'); - -export type Options = InferOptionsTypeFromRule; -export type MessageIds = InferMessageIdsTypeFromRule; - -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -const baseSchema = Array.isArray(baseRule.meta.schema) - ? baseRule.meta.schema[0] - : baseRule.meta.schema; -const schema = deepMerge( - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- https://github.com/microsoft/TypeScript/issues/17002 - baseSchema, - { - properties: { - overrides: { - properties: { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access - type: baseSchema.properties.overrides.properties.import, - }, - }, - }, - }, -) as unknown as JSONSchema4; - -export default createRule({ - name: 'keyword-spacing', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/keyword-spacing'], - type: 'layout', - docs: { - description: 'Enforce consistent spacing before and after keywords', - extendsBaseRule: true, - }, - fixable: 'whitespace', - hasSuggestions: baseRule.meta.hasSuggestions, - schema: [schema], - messages: baseRule.meta.messages, - }, - defaultOptions: [{}], - - create(context, [{ after, overrides }]) { - const baseRules = baseRule.create(context); - return { - ...baseRules, - TSAsExpression(node): void { - const asToken = nullThrows( - context.sourceCode.getTokenAfter( - node.expression, - token => token.value === 'as', - ), - NullThrowsReasons.MissingToken('as', node.type), - ); - const oldTokenType = asToken.type; - // as is a contextual keyword, so it's always reported as an Identifier - // the rule looks for keyword tokens, so we temporarily override it - // we mutate it at the token level because the rule calls sourceCode.getFirstToken, - // so mutating a copy would not change the underlying copy returned by that method - asToken.type = AST_TOKEN_TYPES.Keyword; - - // use this selector just because it is just a call to `checkSpacingAroundFirstToken` - baseRules.DebuggerStatement(asToken as never); - - // make sure to reset the type afterward so we don't permanently mutate the AST - asToken.type = oldTokenType; - }, - 'ImportDeclaration[importKind=type]'( - node: TSESTree.ImportDeclaration, - ): void { - const { type: typeOptionOverride = {} } = overrides ?? {}; - const typeToken = context.sourceCode.getFirstToken(node, { skip: 1 })!; - const punctuatorToken = context.sourceCode.getTokenAfter(typeToken)!; - if ( - node.specifiers[0]?.type === AST_NODE_TYPES.ImportDefaultSpecifier - ) { - return; - } - const spacesBetweenTypeAndPunctuator = - punctuatorToken.range[0] - typeToken.range[1]; - if ( - (typeOptionOverride.after ?? after) === true && - spacesBetweenTypeAndPunctuator === 0 - ) { - context.report({ - loc: typeToken.loc, - messageId: 'expectedAfter', - data: { value: 'type' }, - fix(fixer) { - return fixer.insertTextAfter(typeToken, ' '); - }, - }); - } - if ( - (typeOptionOverride.after ?? after) === false && - spacesBetweenTypeAndPunctuator > 0 - ) { - context.report({ - loc: typeToken.loc, - messageId: 'unexpectedAfter', - data: { value: 'type' }, - fix(fixer) { - return fixer.removeRange([ - typeToken.range[1], - typeToken.range[1] + spacesBetweenTypeAndPunctuator, - ]); - }, - }); - } - }, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/lines-around-comment.ts b/packages/eslint-plugin/src/rules/lines-around-comment.ts deleted file mode 100644 index 0c3fe87c6f17..000000000000 --- a/packages/eslint-plugin/src/rules/lines-around-comment.ts +++ /dev/null @@ -1,456 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; - -import type { - InferMessageIdsTypeFromRule, - InferOptionsTypeFromRule, -} from '../util'; -import { createRule, isCommentToken, isTokenOnSameLine } from '../util'; -import { getESLintCoreRule } from '../util/getESLintCoreRule'; - -const baseRule = getESLintCoreRule('lines-around-comment'); - -export type Options = InferOptionsTypeFromRule; -export type MessageIds = InferMessageIdsTypeFromRule; - -const COMMENTS_IGNORE_PATTERN = - /^\s*(?:eslint|jshint\s+|jslint\s+|istanbul\s+|globals?\s+|exported\s+|jscs)/u; - -/** - * @returns an array with with any line numbers that are empty. - */ -function getEmptyLineNums(lines: string[]): number[] { - const emptyLines = lines - .map((line, i) => ({ - code: line.trim(), - num: i + 1, - })) - .filter(line => !line.code) - .map(line => line.num); - - return emptyLines; -} - -/** - * @returns an array with with any line numbers that contain comments. - */ -function getCommentLineNums(comments: TSESTree.Comment[]): number[] { - const lines: number[] = []; - - comments.forEach(token => { - const start = token.loc.start.line; - const end = token.loc.end.line; - - lines.push(start, end); - }); - return lines; -} - -export default createRule({ - name: 'lines-around-comment', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/lines-around-comment'], - type: 'layout', - docs: { - description: 'Require empty lines around comments', - extendsBaseRule: true, - }, - schema: [ - { - type: 'object', - properties: { - beforeBlockComment: { - type: 'boolean', - default: true, - }, - afterBlockComment: { - type: 'boolean', - default: false, - }, - beforeLineComment: { - type: 'boolean', - default: false, - }, - afterLineComment: { - type: 'boolean', - default: false, - }, - allowBlockStart: { - type: 'boolean', - default: false, - }, - allowBlockEnd: { - type: 'boolean', - default: false, - }, - allowClassStart: { - type: 'boolean', - }, - allowClassEnd: { - type: 'boolean', - }, - allowObjectStart: { - type: 'boolean', - }, - allowObjectEnd: { - type: 'boolean', - }, - allowArrayStart: { - type: 'boolean', - }, - allowArrayEnd: { - type: 'boolean', - }, - allowInterfaceStart: { - type: 'boolean', - }, - allowInterfaceEnd: { - type: 'boolean', - }, - allowTypeStart: { - type: 'boolean', - }, - allowTypeEnd: { - type: 'boolean', - }, - allowEnumStart: { - type: 'boolean', - }, - allowEnumEnd: { - type: 'boolean', - }, - allowModuleStart: { - type: 'boolean', - }, - allowModuleEnd: { - type: 'boolean', - }, - ignorePattern: { - type: 'string', - }, - applyDefaultIgnorePatterns: { - type: 'boolean', - }, - }, - additionalProperties: false, - }, - ], - fixable: baseRule.meta.fixable, - hasSuggestions: baseRule.meta.hasSuggestions, - messages: baseRule.meta.messages, - }, - defaultOptions: [ - { - beforeBlockComment: true, - }, - ], - create(context, [_options]) { - const options = _options!; - const defaultIgnoreRegExp = COMMENTS_IGNORE_PATTERN; - const customIgnoreRegExp = new RegExp(options.ignorePattern ?? '', 'u'); - - const comments = context.sourceCode.getAllComments(); - - const lines = context.sourceCode.lines; - const commentLines = getCommentLineNums(comments); - const emptyLines = getEmptyLineNums(lines); - const commentAndEmptyLines = new Set(commentLines.concat(emptyLines)); - - /** - * @returns whether comments are on lines starting with or ending with code. - */ - function codeAroundComment(token: TSESTree.Token): boolean { - let currentToken: TSESTree.Token | null = token; - - do { - currentToken = context.sourceCode.getTokenBefore(currentToken, { - includeComments: true, - }); - } while (currentToken && isCommentToken(currentToken)); - - if (currentToken && isTokenOnSameLine(currentToken, token)) { - return true; - } - - currentToken = token; - do { - currentToken = context.sourceCode.getTokenAfter(currentToken, { - includeComments: true, - }); - } while (currentToken && isCommentToken(currentToken)); - - if (currentToken && isTokenOnSameLine(token, currentToken)) { - return true; - } - - return false; - } - - /** - * @returns whether comments are inside a node type. - */ - function isParentNodeType( - parent: TSESTree.Node, - nodeType: T, - ): parent is Extract { - return parent.type === nodeType; - } - - /** - * @returns the parent node that contains the given token. - */ - function getParentNodeOfToken(token: TSESTree.Token): TSESTree.Node | null { - const node = context.sourceCode.getNodeByRangeIndex(token.range[0]); - - return node; - } - - /** - * @returns whether comments are at the parent start. - */ - function isCommentAtParentStart( - token: TSESTree.Token, - nodeType: TSESTree.AST_NODE_TYPES, - ): boolean { - const parent = getParentNodeOfToken(token); - - if (parent && isParentNodeType(parent, nodeType)) { - const parentStartNodeOrToken = parent; - - return ( - token.loc.start.line - parentStartNodeOrToken.loc.start.line === 1 - ); - } - - return false; - } - - /** - * @returns whether comments are at the parent end. - */ - function isCommentAtParentEnd( - token: TSESTree.Token, - nodeType: TSESTree.AST_NODE_TYPES, - ): boolean { - const parent = getParentNodeOfToken(token); - - return ( - !!parent && - isParentNodeType(parent, nodeType) && - parent.loc.end.line - token.loc.end.line === 1 - ); - } - - function isCommentAtInterfaceStart(token: TSESTree.Comment): boolean { - return isCommentAtParentStart(token, AST_NODE_TYPES.TSInterfaceBody); - } - - function isCommentAtInterfaceEnd(token: TSESTree.Comment): boolean { - return isCommentAtParentEnd(token, AST_NODE_TYPES.TSInterfaceBody); - } - - function isCommentAtTypeStart(token: TSESTree.Comment): boolean { - return isCommentAtParentStart(token, AST_NODE_TYPES.TSTypeLiteral); - } - - function isCommentAtTypeEnd(token: TSESTree.Comment): boolean { - return isCommentAtParentEnd(token, AST_NODE_TYPES.TSTypeLiteral); - } - - function isCommentAtEnumStart(token: TSESTree.Comment): boolean { - return isCommentAtParentStart(token, AST_NODE_TYPES.TSEnumDeclaration); - } - - function isCommentAtEnumEnd(token: TSESTree.Comment): boolean { - return isCommentAtParentEnd(token, AST_NODE_TYPES.TSEnumDeclaration); - } - - function isCommentAtModuleStart(token: TSESTree.Comment): boolean { - return isCommentAtParentStart(token, AST_NODE_TYPES.TSModuleBlock); - } - - function isCommentAtModuleEnd(token: TSESTree.Comment): boolean { - return isCommentAtParentEnd(token, AST_NODE_TYPES.TSModuleBlock); - } - - function isCommentNearTSConstruct(token: TSESTree.Comment): boolean { - return ( - isCommentAtInterfaceStart(token) || - isCommentAtInterfaceEnd(token) || - isCommentAtTypeStart(token) || - isCommentAtTypeEnd(token) || - isCommentAtEnumStart(token) || - isCommentAtEnumEnd(token) || - isCommentAtModuleStart(token) || - isCommentAtModuleEnd(token) - ); - } - - function checkForEmptyLine( - token: TSESTree.Comment, - { before, after }: { before?: boolean; after?: boolean }, - ): void { - // the base rule handles comments away from TS constructs blocks correctly, we skip those - if (!isCommentNearTSConstruct(token)) { - return; - } - - if ( - options.applyDefaultIgnorePatterns !== false && - defaultIgnoreRegExp.test(token.value) - ) { - return; - } - - if (options.ignorePattern && customIgnoreRegExp.test(token.value)) { - return; - } - - const prevLineNum = token.loc.start.line - 1; - const nextLineNum = token.loc.end.line + 1; - - // we ignore all inline comments - if (codeAroundComment(token)) { - return; - } - - const interfaceStartAllowed = - Boolean(options.allowInterfaceStart) && - isCommentAtInterfaceStart(token); - const interfaceEndAllowed = - Boolean(options.allowInterfaceEnd) && isCommentAtInterfaceEnd(token); - const typeStartAllowed = - Boolean(options.allowTypeStart) && isCommentAtTypeStart(token); - const typeEndAllowed = - Boolean(options.allowTypeEnd) && isCommentAtTypeEnd(token); - const enumStartAllowed = - Boolean(options.allowEnumStart) && isCommentAtEnumStart(token); - const enumEndAllowed = - Boolean(options.allowEnumEnd) && isCommentAtEnumEnd(token); - const moduleStartAllowed = - Boolean(options.allowModuleStart) && isCommentAtModuleStart(token); - const moduleEndAllowed = - Boolean(options.allowModuleEnd) && isCommentAtModuleEnd(token); - - const exceptionStartAllowed = - interfaceStartAllowed || - typeStartAllowed || - enumStartAllowed || - moduleStartAllowed; - const exceptionEndAllowed = - interfaceEndAllowed || - typeEndAllowed || - enumEndAllowed || - moduleEndAllowed; - - const previousTokenOrComment = context.sourceCode.getTokenBefore(token, { - includeComments: true, - }); - const nextTokenOrComment = context.sourceCode.getTokenAfter(token, { - includeComments: true, - }); - - // check for newline before - if ( - !exceptionStartAllowed && - before && - !commentAndEmptyLines.has(prevLineNum) && - !( - isCommentToken(previousTokenOrComment!) && - isTokenOnSameLine(previousTokenOrComment, token) - ) - ) { - const lineStart = token.range[0] - token.loc.start.column; - const range = [lineStart, lineStart] as const; - - context.report({ - node: token, - messageId: 'before', - fix(fixer) { - return fixer.insertTextBeforeRange(range, '\n'); - }, - }); - } - - // check for newline after - if ( - !exceptionEndAllowed && - after && - !commentAndEmptyLines.has(nextLineNum) && - !( - isCommentToken(nextTokenOrComment!) && - isTokenOnSameLine(token, nextTokenOrComment) - ) - ) { - context.report({ - node: token, - messageId: 'after', - fix(fixer) { - return fixer.insertTextAfter(token, '\n'); - }, - }); - } - } - - /** - * A custom report function for the baseRule to ignore false positive errors - * caused by TS-specific codes - */ - const customReport: typeof context.report = descriptor => { - if ('node' in descriptor) { - if ( - descriptor.node.type === AST_TOKEN_TYPES.Line || - descriptor.node.type === AST_TOKEN_TYPES.Block - ) { - if (isCommentNearTSConstruct(descriptor.node)) { - return; - } - } - } - return context.report(descriptor); - }; - - const customContext = { report: customReport }; - - // we can't directly proxy `context` because its `report` property is non-configurable - // and non-writable. So we proxy `customContext` and redirect all - // property access to the original context except for `report` - const proxiedContext = new Proxy( - customContext as typeof context, - { - get(target, path, receiver): unknown { - if (path !== 'report') { - return Reflect.get(context, path, receiver); - } - return Reflect.get(target, path, receiver); - }, - }, - ); - - const rules = baseRule.create(proxiedContext); - - return { - Program(): void { - rules.Program(); - - comments.forEach(token => { - if (token.type === AST_TOKEN_TYPES.Line) { - if (options.beforeLineComment || options.afterLineComment) { - checkForEmptyLine(token, { - after: options.afterLineComment, - before: options.beforeLineComment, - }); - } - } else if (options.beforeBlockComment || options.afterBlockComment) { - checkForEmptyLine(token, { - after: options.afterBlockComment, - before: options.beforeBlockComment, - }); - } - }); - }, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/lines-between-class-members.ts b/packages/eslint-plugin/src/rules/lines-between-class-members.ts deleted file mode 100644 index 60da9308757a..000000000000 --- a/packages/eslint-plugin/src/rules/lines-between-class-members.ts +++ /dev/null @@ -1,78 +0,0 @@ -import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema'; - -import type { - InferMessageIdsTypeFromRule, - InferOptionsTypeFromRule, -} from '../util'; -import { createRule, deepMerge } from '../util'; -import { getESLintCoreRule } from '../util/getESLintCoreRule'; - -const baseRule = getESLintCoreRule('lines-between-class-members'); - -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; - -const schema = Object.values( - deepMerge( - { ...baseRule.meta.schema }, - { - 1: { - properties: { - exceptAfterOverload: { - type: 'boolean', - default: true, - }, - }, - }, - }, - ), -) as JSONSchema4[]; - -export default createRule({ - name: 'lines-between-class-members', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/lines-between-class-members'], - type: 'layout', - docs: { - description: 'Require or disallow an empty line between class members', - extendsBaseRule: true, - }, - fixable: 'whitespace', - hasSuggestions: baseRule.meta.hasSuggestions, - schema, - messages: baseRule.meta.messages, - }, - defaultOptions: [ - 'always', - { - exceptAfterOverload: true, - exceptAfterSingleLine: false, - }, - ], - create(context, [firstOption, secondOption]) { - const rules = baseRule.create(context); - const exceptAfterOverload = - secondOption?.exceptAfterOverload && firstOption === 'always'; - - function isOverload(node: TSESTree.Node): boolean { - return ( - (node.type === AST_NODE_TYPES.TSAbstractMethodDefinition || - node.type === AST_NODE_TYPES.MethodDefinition) && - node.value.type === AST_NODE_TYPES.TSEmptyBodyFunctionExpression - ); - } - - return { - ClassBody(node): void { - const body = exceptAfterOverload - ? node.body.filter(node => !isOverload(node)) - : node.body; - - rules.ClassBody({ ...node, body }); - }, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/member-delimiter-style.ts b/packages/eslint-plugin/src/rules/member-delimiter-style.ts deleted file mode 100644 index 2202fc58f034..000000000000 --- a/packages/eslint-plugin/src/rules/member-delimiter-style.ts +++ /dev/null @@ -1,350 +0,0 @@ -import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema'; - -import { createRule, deepMerge } from '../util'; - -type Delimiter = 'comma' | 'none' | 'semi'; -// need type's implicit index sig for deepMerge -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -type TypeOptions = { - delimiter?: Delimiter; - requireLast?: boolean; -}; -type TypeOptionsWithType = TypeOptions & { - type: string; -}; -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -type BaseOptions = { - multiline?: TypeOptions; - singleline?: TypeOptions; -}; -type Config = BaseOptions & { - overrides?: { - typeLiteral?: BaseOptions; - interface?: BaseOptions; - }; - multilineDetection?: 'brackets' | 'last-member'; -}; -type Options = [Config]; -type MessageIds = - | 'expectedComma' - | 'expectedSemi' - | 'unexpectedComma' - | 'unexpectedSemi'; -type LastTokenType = TSESTree.Token; - -interface MakeFixFunctionParams { - optsNone: boolean; - optsSemi: boolean; - lastToken: LastTokenType; - commentsAfterLastToken: LastTokenType | undefined; - missingDelimiter: boolean; - lastTokenLine: string; - isSingleLine: boolean; -} - -type MakeFixFunctionReturnType = - | ((fixer: TSESLint.RuleFixer) => TSESLint.RuleFix) - | null; - -const isLastTokenEndOfLine = (token: LastTokenType, line: string): boolean => { - const positionInLine = token.loc.start.column; - - return positionInLine === line.length - 1; -}; - -const isCommentsEndOfLine = ( - token: LastTokenType, - comments: LastTokenType | undefined, - line: string, -): boolean => { - if (!comments) { - return false; - } - - if (comments.loc.end.line > token.loc.end.line) { - return true; - } - - const positionInLine = comments.loc.end.column; - - return positionInLine === line.length; -}; - -const makeFixFunction = ({ - optsNone, - optsSemi, - lastToken, - commentsAfterLastToken, - missingDelimiter, - lastTokenLine, - isSingleLine, -}: MakeFixFunctionParams): MakeFixFunctionReturnType => { - // if removing is the action but last token is not the end of the line - if ( - optsNone && - !isLastTokenEndOfLine(lastToken, lastTokenLine) && - !isCommentsEndOfLine(lastToken, commentsAfterLastToken, lastTokenLine) && - !isSingleLine - ) { - return null; - } - - return (fixer: TSESLint.RuleFixer): TSESLint.RuleFix => { - if (optsNone) { - // remove the unneeded token - return fixer.remove(lastToken); - } - - const token = optsSemi ? ';' : ','; - - if (missingDelimiter) { - // add the missing delimiter - return fixer.insertTextAfter(lastToken, token); - } - - // correct the current delimiter - return fixer.replaceText(lastToken, token); - }; -}; - -const BASE_SCHEMA: JSONSchema4 = { - type: 'object', - properties: { - multiline: { - type: 'object', - properties: { - delimiter: { $ref: '#/items/0/$defs/multiLineOption' }, - requireLast: { type: 'boolean' }, - }, - additionalProperties: false, - }, - singleline: { - type: 'object', - properties: { - delimiter: { $ref: '#/items/0/$defs/singleLineOption' }, - requireLast: { type: 'boolean' }, - }, - additionalProperties: false, - }, - }, - additionalProperties: false, -}; - -export default createRule({ - name: 'member-delimiter-style', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/member-delimiter-style'], - type: 'layout', - docs: { - description: - 'Require a specific member delimiter style for interfaces and type literals', - }, - fixable: 'whitespace', - messages: { - unexpectedComma: 'Unexpected separator (,).', - unexpectedSemi: 'Unexpected separator (;).', - expectedComma: 'Expected a comma.', - expectedSemi: 'Expected a semicolon.', - }, - schema: [ - { - $defs: { - multiLineOption: { - type: 'string', - enum: ['none', 'semi', 'comma'], - }, - // note can't have "none" for single line delimiter as it's invalid syntax - singleLineOption: { - type: 'string', - enum: ['semi', 'comma'], - }, - // note - need to define this last as it references the enums - delimiterConfig: BASE_SCHEMA, - }, - type: 'object', - properties: { - ...BASE_SCHEMA.properties, - overrides: { - type: 'object', - properties: { - interface: { - $ref: '#/items/0/$defs/delimiterConfig', - }, - typeLiteral: { - $ref: '#/items/0/$defs/delimiterConfig', - }, - }, - additionalProperties: false, - }, - multilineDetection: { - type: 'string', - enum: ['brackets', 'last-member'], - }, - }, - additionalProperties: false, - }, - ], - }, - defaultOptions: [ - { - multiline: { - delimiter: 'semi', - requireLast: true, - }, - singleline: { - delimiter: 'semi', - requireLast: false, - }, - multilineDetection: 'brackets', - }, - ], - create(context, [options]) { - // use the base options as the defaults for the cases - const baseOptions = options; - const overrides = baseOptions.overrides ?? {}; - const interfaceOptions: BaseOptions = deepMerge( - baseOptions, - overrides.interface, - ); - const typeLiteralOptions: BaseOptions = deepMerge( - baseOptions, - overrides.typeLiteral, - ); - - /** - * Check the last token in the given member. - * @param member the member to be evaluated. - * @param opts the options to be validated. - * @param isLast a flag indicating `member` is the last in the interface or type literal. - */ - function checkLastToken( - member: TSESTree.TypeElement, - opts: TypeOptionsWithType, - isLast: boolean, - ): void { - /** - * Resolves the boolean value for the given setting enum value - * @param type the option name - */ - function getOption(type: Delimiter): boolean { - if (isLast && !opts.requireLast) { - // only turn the option on if its expecting no delimiter for the last member - return type === 'none'; - } - return opts.delimiter === type; - } - - let messageId: MessageIds | null = null; - let missingDelimiter = false; - const lastToken = context.sourceCode.getLastToken(member, { - includeComments: false, - }); - - if (!lastToken) { - return; - } - - const commentsAfterLastToken = context.sourceCode - .getCommentsAfter(lastToken) - .pop(); - - const sourceCodeLines = context.sourceCode.getLines(); - const lastTokenLine = sourceCodeLines[lastToken.loc.start.line - 1]; - - const optsSemi = getOption('semi'); - const optsComma = getOption('comma'); - const optsNone = getOption('none'); - - if (lastToken.value === ';') { - if (optsComma) { - messageId = 'expectedComma'; - } else if (optsNone) { - missingDelimiter = true; - messageId = 'unexpectedSemi'; - } - } else if (lastToken.value === ',') { - if (optsSemi) { - messageId = 'expectedSemi'; - } else if (optsNone) { - missingDelimiter = true; - messageId = 'unexpectedComma'; - } - } else if (optsSemi) { - missingDelimiter = true; - messageId = 'expectedSemi'; - } else if (optsComma) { - missingDelimiter = true; - messageId = 'expectedComma'; - } - - if (messageId) { - context.report({ - node: lastToken, - loc: { - start: { - line: lastToken.loc.end.line, - column: lastToken.loc.end.column, - }, - end: { - line: lastToken.loc.end.line, - column: lastToken.loc.end.column, - }, - }, - messageId, - fix: makeFixFunction({ - optsNone, - optsSemi, - lastToken, - commentsAfterLastToken, - missingDelimiter, - lastTokenLine, - isSingleLine: opts.type === 'single-line', - }), - }); - } - } - - /** - * Check the member separator being used matches the delimiter. - * @param node the node to be evaluated. - */ - function checkMemberSeparatorStyle( - node: TSESTree.TSInterfaceBody | TSESTree.TSTypeLiteral, - ): void { - const members = - node.type === AST_NODE_TYPES.TSInterfaceBody ? node.body : node.members; - - let isSingleLine = node.loc.start.line === node.loc.end.line; - if ( - options.multilineDetection === 'last-member' && - !isSingleLine && - members.length > 0 - ) { - const lastMember = members[members.length - 1]; - if (lastMember.loc.end.line === node.loc.end.line) { - isSingleLine = true; - } - } - - const typeOpts = - node.type === AST_NODE_TYPES.TSInterfaceBody - ? interfaceOptions - : typeLiteralOptions; - const opts = isSingleLine - ? { ...typeOpts.singleline, type: 'single-line' } - : { ...typeOpts.multiline, type: 'multi-line' }; - - members.forEach((member, index) => { - checkLastToken(member, opts, index === members.length - 1); - }); - } - - return { - TSInterfaceBody: checkMemberSeparatorStyle, - TSTypeLiteral: checkMemberSeparatorStyle, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/method-signature-style.ts b/packages/eslint-plugin/src/rules/method-signature-style.ts index 0a17da9c0913..48207e5cc91a 100644 --- a/packages/eslint-plugin/src/rules/method-signature-style.ts +++ b/packages/eslint-plugin/src/rules/method-signature-style.ts @@ -133,9 +133,7 @@ export default createRule({ const members = parent.type === AST_NODE_TYPES.TSInterfaceBody ? parent.body - : parent.type === AST_NODE_TYPES.TSTypeLiteral - ? parent.members - : []; + : parent.members; const duplicatedKeyMethodNodes: TSESTree.TSMethodSignature[] = members.filter( diff --git a/packages/eslint-plugin/src/rules/naming-convention-utils/schema.ts b/packages/eslint-plugin/src/rules/naming-convention-utils/schema.ts index 91e5bfec36f3..d7016e217f2f 100644 --- a/packages/eslint-plugin/src/rules/naming-convention-utils/schema.ts +++ b/packages/eslint-plugin/src/rules/naming-convention-utils/schema.ts @@ -146,39 +146,37 @@ function selectorsSchema(): JSONSchema.JSONSchema4 { description: 'Multiple selectors in one config', properties: { ...FORMAT_OPTIONS_PROPERTIES, - ...{ - filter: { - oneOf: [ - { - type: 'string', - minLength: 1, - }, - MATCH_REGEX_SCHEMA, - ], - }, - selector: { - type: 'array', - items: { + filter: { + oneOf: [ + { type: 'string', - enum: [...getEnumNames(MetaSelectors), ...getEnumNames(Selectors)], + minLength: 1, }, - additionalItems: false, + MATCH_REGEX_SCHEMA, + ], + }, + selector: { + type: 'array', + items: { + type: 'string', + enum: [...getEnumNames(MetaSelectors), ...getEnumNames(Selectors)], }, - modifiers: { - type: 'array', - items: { - type: 'string', - enum: getEnumNames(Modifiers), - }, - additionalItems: false, + additionalItems: false, + }, + modifiers: { + type: 'array', + items: { + type: 'string', + enum: getEnumNames(Modifiers), }, - types: { - type: 'array', - items: { - $ref: '#/$defs/typeModifiers', - }, - additionalItems: false, + additionalItems: false, + }, + types: { + type: 'array', + items: { + $ref: '#/$defs/typeModifiers', }, + additionalItems: false, }, }, required: ['selector', 'format'], diff --git a/packages/eslint-plugin/src/rules/naming-convention.ts b/packages/eslint-plugin/src/rules/naming-convention.ts index c5c36436bc2e..4cd4277557c7 100644 --- a/packages/eslint-plugin/src/rules/naming-convention.ts +++ b/packages/eslint-plugin/src/rules/naming-convention.ts @@ -7,7 +7,7 @@ import { AST_NODE_TYPES, TSESLint } from '@typescript-eslint/utils'; import type { ScriptTarget } from 'typescript'; import { - collectUnusedVariables, + collectVariables, createRule, getParserServices, requiresQuoting as _requiresQuoting, @@ -162,7 +162,7 @@ export default createRule({ return modifiers; } - const unusedVariables = collectUnusedVariables(context); + const { unusedVariables } = collectVariables(context); function isUnused( name: string, initialScope: TSESLint.Scope.Scope | null, diff --git a/packages/eslint-plugin/src/rules/no-array-delete.ts b/packages/eslint-plugin/src/rules/no-array-delete.ts index d9900a1df10f..9b6230ebf821 100644 --- a/packages/eslint-plugin/src/rules/no-array-delete.ts +++ b/packages/eslint-plugin/src/rules/no-array-delete.ts @@ -17,7 +17,7 @@ export default createRule<[], MessageId>({ type: 'problem', docs: { description: 'Disallow using the `delete` operator on array values', - recommended: 'strict', + recommended: 'recommended', requiresTypeChecking: true, }, messages: { diff --git a/packages/eslint-plugin/src/rules/no-duplicate-enum-values.ts b/packages/eslint-plugin/src/rules/no-duplicate-enum-values.ts index f5a58ce14f12..d8ac6586666d 100644 --- a/packages/eslint-plugin/src/rules/no-duplicate-enum-values.ts +++ b/packages/eslint-plugin/src/rules/no-duplicate-enum-values.ts @@ -37,7 +37,7 @@ export default createRule({ return { TSEnumDeclaration(node: TSESTree.TSEnumDeclaration): void { - const enumMembers = node.members; + const enumMembers = node.body.members; const seenValues = new Set(); enumMembers.forEach(member => { diff --git a/packages/eslint-plugin/src/rules/no-duplicate-type-constituents.ts b/packages/eslint-plugin/src/rules/no-duplicate-type-constituents.ts index 19f605eaf9d4..d92de657aa47 100644 --- a/packages/eslint-plugin/src/rules/no-duplicate-type-constituents.ts +++ b/packages/eslint-plugin/src/rules/no-duplicate-type-constituents.ts @@ -45,8 +45,7 @@ const isSameAstNode = (actualNode: unknown, expectedNode: unknown): boolean => { } if ( actualNodeKeys.some( - actualNodeKey => - !Object.prototype.hasOwnProperty.call(expectedNode, actualNodeKey), + actualNodeKey => !Object.hasOwn(expectedNode, actualNodeKey), ) ) { return false; diff --git a/packages/eslint-plugin/src/rules/no-empty-interface.ts b/packages/eslint-plugin/src/rules/no-empty-interface.ts index 674b86d44bf9..03d00c1a9535 100644 --- a/packages/eslint-plugin/src/rules/no-empty-interface.ts +++ b/packages/eslint-plugin/src/rules/no-empty-interface.ts @@ -17,8 +17,9 @@ export default createRule({ type: 'suggestion', docs: { description: 'Disallow the declaration of empty interfaces', - recommended: 'stylistic', }, + deprecated: true, + replacedBy: ['@typescript-eslint/no-empty-object-type'], fixable: 'code', hasSuggestions: true, messages: { diff --git a/packages/eslint-plugin/src/rules/no-empty-object-type.ts b/packages/eslint-plugin/src/rules/no-empty-object-type.ts index 21db86ba2033..2471726240b8 100644 --- a/packages/eslint-plugin/src/rules/no-empty-object-type.ts +++ b/packages/eslint-plugin/src/rules/no-empty-object-type.ts @@ -37,6 +37,7 @@ export default createRule({ type: 'suggestion', docs: { description: 'Disallow accidentally using the "empty object" type', + recommended: 'recommended', }, hasSuggestions: true, messages: { @@ -59,7 +60,7 @@ export default createRule({ type: 'string', }, allowObjectTypes: { - enum: ['always', 'in-type-alias-with-name', 'never'], + enum: ['always', 'never'], type: 'string', }, allowWithName: { diff --git a/packages/eslint-plugin/src/rules/no-extra-parens.ts b/packages/eslint-plugin/src/rules/no-extra-parens.ts deleted file mode 100644 index 0b5c82d08438..000000000000 --- a/packages/eslint-plugin/src/rules/no-extra-parens.ts +++ /dev/null @@ -1,308 +0,0 @@ -// any is required to work around manipulating the AST in weird ways -/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment */ - -import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; - -import type { - InferMessageIdsTypeFromRule, - InferOptionsTypeFromRule, -} from '../util'; -import { createRule, isOpeningParenToken, isTypeAssertion } from '../util'; -import { getESLintCoreRule } from '../util/getESLintCoreRule'; - -const baseRule = getESLintCoreRule('no-extra-parens'); - -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; - -export default createRule({ - name: 'no-extra-parens', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/no-extra-parens'], - type: 'layout', - docs: { - description: 'Disallow unnecessary parentheses', - extendsBaseRule: true, - }, - fixable: 'code', - hasSuggestions: baseRule.meta.hasSuggestions, - schema: baseRule.meta.schema, - messages: baseRule.meta.messages, - }, - defaultOptions: ['all'], - create(context) { - const rules = baseRule.create(context); - - function binaryExp( - node: TSESTree.BinaryExpression | TSESTree.LogicalExpression, - ): void { - const rule = rules.BinaryExpression as (n: typeof node) => void; - - // makes the rule think it should skip the left or right - const isLeftTypeAssertion = isTypeAssertion(node.left); - const isRightTypeAssertion = isTypeAssertion(node.right); - if (isLeftTypeAssertion && isRightTypeAssertion) { - return; // ignore - } - if (isLeftTypeAssertion) { - return rule({ - ...node, - left: { - ...node.left, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - if (isRightTypeAssertion) { - return rule({ - ...node, - right: { - ...node.right, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - - return rule(node); - } - function callExp( - node: TSESTree.CallExpression | TSESTree.NewExpression, - ): void { - const rule = rules.CallExpression as (n: typeof node) => void; - - if (isTypeAssertion(node.callee)) { - // reduces the precedence of the node so the rule thinks it needs to be wrapped - return rule({ - ...node, - callee: { - ...node.callee, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - - if ( - node.arguments.length === 1 && - // is there any opening parenthesis in type arguments - context.sourceCode.getTokenAfter(node.callee, isOpeningParenToken) !== - context.sourceCode.getTokenBefore( - node.arguments[0], - isOpeningParenToken, - ) - ) { - return rule({ - ...node, - arguments: [ - { - ...node.arguments[0], - type: AST_NODE_TYPES.SequenceExpression as any, - }, - ], - }); - } - - return rule(node); - } - function unaryUpdateExpression( - node: TSESTree.UnaryExpression | TSESTree.UpdateExpression, - ): void { - const rule = rules.UnaryExpression as (n: typeof node) => void; - - if (isTypeAssertion(node.argument)) { - // reduces the precedence of the node so the rule thinks it needs to be wrapped - return rule({ - ...node, - argument: { - ...node.argument, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - - return rule(node); - } - - const overrides: TSESLint.RuleListener = { - // ArrayExpression - ArrowFunctionExpression(node) { - if (!isTypeAssertion(node.body)) { - return rules.ArrowFunctionExpression(node); - } - }, - // AssignmentExpression - AwaitExpression(node) { - if (isTypeAssertion(node.argument)) { - // reduces the precedence of the node so the rule thinks it needs to be wrapped - return rules.AwaitExpression({ - ...node, - argument: { - ...node.argument, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - return rules.AwaitExpression(node); - }, - BinaryExpression: binaryExp, - CallExpression: callExp, - ClassDeclaration(node) { - if (node.superClass?.type === AST_NODE_TYPES.TSAsExpression) { - return rules.ClassDeclaration({ - ...node, - superClass: { - ...node.superClass, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - return rules.ClassDeclaration(node); - }, - ClassExpression(node) { - if (node.superClass?.type === AST_NODE_TYPES.TSAsExpression) { - return rules.ClassExpression({ - ...node, - superClass: { - ...node.superClass, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - return rules.ClassExpression(node); - }, - ConditionalExpression(node) { - // reduces the precedence of the node so the rule thinks it needs to be wrapped - if (isTypeAssertion(node.test)) { - return rules.ConditionalExpression({ - ...node, - test: { - ...node.test, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - if (isTypeAssertion(node.consequent)) { - return rules.ConditionalExpression({ - ...node, - consequent: { - ...node.consequent, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - if (isTypeAssertion(node.alternate)) { - // reduces the precedence of the node so the rule thinks it needs to be wrapped - return rules.ConditionalExpression({ - ...node, - alternate: { - ...node.alternate, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - return rules.ConditionalExpression(node); - }, - ForInStatement(node): void { - if (isTypeAssertion(node.right)) { - // as of 7.20.0 there's no way to skip checking the right of the ForIn - // so just don't validate it at all - return; - } - - return rules.ForInStatement(node); - }, - ForOfStatement(node): void { - if (isTypeAssertion(node.right)) { - // makes the rule skip checking of the right - return rules.ForOfStatement({ - ...node, - type: AST_NODE_TYPES.ForOfStatement, - right: { - ...node.right, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - - return rules.ForOfStatement(node); - }, - // DoWhileStatement - ForStatement(node) { - // make the rule skip the piece by removing it entirely - if (node.init && isTypeAssertion(node.init)) { - return rules.ForStatement({ - ...node, - init: null, - }); - } - if (node.test && isTypeAssertion(node.test)) { - return rules.ForStatement({ - ...node, - test: null, - }); - } - if (node.update && isTypeAssertion(node.update)) { - return rules.ForStatement({ - ...node, - update: null, - }); - } - - return rules.ForStatement(node); - }, - 'ForStatement > *.init:exit'(node: TSESTree.Node) { - if (!isTypeAssertion(node)) { - return rules['ForStatement > *.init:exit'](node); - } - }, - // IfStatement - LogicalExpression: binaryExp, - MemberExpression(node) { - if (isTypeAssertion(node.object)) { - // reduces the precedence of the node so the rule thinks it needs to be wrapped - return rules.MemberExpression({ - ...node, - object: { - ...node.object, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - - return rules.MemberExpression(node); - }, - NewExpression: callExp, - // ObjectExpression - // ReturnStatement - // SequenceExpression - SpreadElement(node) { - if (!isTypeAssertion(node.argument)) { - return rules.SpreadElement(node); - } - }, - SwitchCase(node) { - if (node.test && !isTypeAssertion(node.test)) { - return rules.SwitchCase(node); - } - }, - // SwitchStatement - ThrowStatement(node) { - if (node.argument && !isTypeAssertion(node.argument)) { - return rules.ThrowStatement(node); - } - }, - UnaryExpression: unaryUpdateExpression, - UpdateExpression: unaryUpdateExpression, - // VariableDeclarator - // WhileStatement - // WithStatement - i'm not going to even bother implementing this terrible and never used feature - YieldExpression(node) { - if (node.argument && !isTypeAssertion(node.argument)) { - return rules.YieldExpression(node); - } - }, - }; - return { ...rules, ...overrides }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/no-extra-semi.ts b/packages/eslint-plugin/src/rules/no-extra-semi.ts deleted file mode 100644 index 4d68f2d6db46..000000000000 --- a/packages/eslint-plugin/src/rules/no-extra-semi.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { - InferMessageIdsTypeFromRule, - InferOptionsTypeFromRule, -} from '../util'; -import { createRule } from '../util'; -import { getESLintCoreRule } from '../util/getESLintCoreRule'; - -const baseRule = getESLintCoreRule('no-extra-semi'); - -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; - -export default createRule({ - name: 'no-extra-semi', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/no-extra-semi'], - type: 'suggestion', - docs: { - description: 'Disallow unnecessary semicolons', - extendsBaseRule: true, - }, - fixable: 'code', - hasSuggestions: baseRule.meta.hasSuggestions, - schema: baseRule.meta.schema, - messages: baseRule.meta.messages, - }, - defaultOptions: [], - create(context) { - const rules = baseRule.create(context); - - return { - ...rules, - 'TSAbstractMethodDefinition, TSAbstractPropertyDefinition'( - node: never, - ): void { - rules['MethodDefinition, PropertyDefinition, StaticBlock'](node); - }, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/no-extraneous-class.ts b/packages/eslint-plugin/src/rules/no-extraneous-class.ts index a7c05f8cc5a8..854cec6b40eb 100644 --- a/packages/eslint-plugin/src/rules/no-extraneous-class.ts +++ b/packages/eslint-plugin/src/rules/no-extraneous-class.ts @@ -79,9 +79,7 @@ export default createRule({ return { ClassBody(node): void { - const parent = node.parent as - | TSESTree.ClassDeclaration - | TSESTree.ClassExpression; + const parent = node.parent; if (parent.superClass || isAllowWithDecorator(parent)) { return; diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index 37bcda743bb9..14c3fe671ae0 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -18,6 +18,7 @@ import { type Options = [ { allowForKnownSafePromises?: TypeOrValueSpecifier[]; + allowForKnownSafeCalls?: TypeOrValueSpecifier[]; checkThenables?: boolean; ignoreIIFE?: boolean; ignoreVoid?: boolean; @@ -78,6 +79,7 @@ export default createRule({ type: 'object', properties: { allowForKnownSafePromises: readonlynessOptionsSchema.properties.allow, + allowForKnownSafeCalls: readonlynessOptionsSchema.properties.allow, checkThenables: { description: 'Whether to check all "Thenable"s, not just the built-in Promise type.', @@ -100,10 +102,11 @@ export default createRule({ }, defaultOptions: [ { + allowForKnownSafeCalls: readonlynessOptionsDefaults.allow, allowForKnownSafePromises: readonlynessOptionsDefaults.allow, - checkThenables: true, - ignoreVoid: true, + checkThenables: false, ignoreIIFE: false, + ignoreVoid: true, }, ], @@ -113,8 +116,10 @@ export default createRule({ const { checkThenables } = options; // TODO: #5439 - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + /* eslint-disable @typescript-eslint/no-non-null-assertion */ const allowForKnownSafePromises = options.allowForKnownSafePromises!; + const allowForKnownSafeCalls = options.allowForKnownSafeCalls!; + /* eslint-enable @typescript-eslint/no-non-null-assertion */ return { ExpressionStatement(node): void { @@ -128,6 +133,10 @@ export default createRule({ expression = expression.expression; } + if (isKnownSafePromiseReturn(expression)) { + return; + } + const { isUnhandled, nonFunctionHandler, promiseArray } = isUnhandledPromise(checker, expression); @@ -207,6 +216,18 @@ export default createRule({ }, }; + function isKnownSafePromiseReturn(node: TSESTree.Node): boolean { + if (node.type !== AST_NODE_TYPES.CallExpression) { + return false; + } + + const type = services.getTypeAtLocation(node.callee); + + return allowForKnownSafeCalls.some(allowedType => + typeMatchesSpecifier(type, allowedType, services.program), + ); + } + function isHigherPrecedenceThanUnary(node: ts.Node): boolean { const operator = ts.isBinaryExpression(node) ? node.operatorToken.kind diff --git a/packages/eslint-plugin/src/rules/no-implied-eval.ts b/packages/eslint-plugin/src/rules/no-implied-eval.ts index f4cafd3e3735..62a8d7cd102e 100644 --- a/packages/eslint-plugin/src/rules/no-implied-eval.ts +++ b/packages/eslint-plugin/src/rules/no-implied-eval.ts @@ -3,7 +3,12 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; -import { createRule, getParserServices, isBuiltinSymbolLike } from '../util'; +import { + createRule, + getParserServices, + isBuiltinSymbolLike, + isReferenceToGlobalFunction, +} from '../util'; const FUNCTION_CONSTRUCTOR = 'Function'; const GLOBAL_CANDIDATES = new Set(['global', 'window', 'globalThis']); @@ -112,18 +117,6 @@ export default createRule({ } } - function isReferenceToGlobalFunction( - calleeName: string, - node: TSESTree.Node, - ): boolean { - const ref = context.sourceCode - .getScope(node) - .references.find(ref => ref.identifier.name === calleeName); - - // ensure it's the "global" version - return !ref?.resolved || ref.resolved.defs.length === 0; - } - function checkImpliedEval( node: TSESTree.CallExpression | TSESTree.NewExpression, ): void { @@ -156,7 +149,7 @@ export default createRule({ if ( EVAL_LIKE_METHODS.has(calleeName) && !isFunction(handler) && - isReferenceToGlobalFunction(calleeName, node) + isReferenceToGlobalFunction(calleeName, node, context.sourceCode) ) { context.report({ node: handler, messageId: 'noImpliedEvalError' }); } diff --git a/packages/eslint-plugin/src/rules/no-loss-of-precision.ts b/packages/eslint-plugin/src/rules/no-loss-of-precision.ts index 06938f9e4dcf..000e5d9aa0bc 100644 --- a/packages/eslint-plugin/src/rules/no-loss-of-precision.ts +++ b/packages/eslint-plugin/src/rules/no-loss-of-precision.ts @@ -1,5 +1,3 @@ -import type { TSESTree } from '@typescript-eslint/utils'; - import type { InferMessageIdsTypeFromRule, InferOptionsTypeFromRule, @@ -16,9 +14,9 @@ export default createRule({ name: 'no-loss-of-precision', meta: { type: 'problem', + deprecated: true, docs: { description: 'Disallow literal numbers that lose precision', - recommended: 'recommended', extendsBaseRule: true, }, hasSuggestions: baseRule.meta.hasSuggestions, @@ -27,18 +25,6 @@ export default createRule({ }, defaultOptions: [], create(context) { - const rules = baseRule.create(context); - - function isSeparatedNumeric(node: TSESTree.Literal): boolean { - return typeof node.value === 'number' && node.raw.includes('_'); - } - return { - Literal(node: TSESTree.Literal): void { - rules.Literal({ - ...node, - raw: isSeparatedNumeric(node) ? node.raw.replace(/_/g, '') : node.raw, - } as never); - }, - }; + return baseRule.create(context); }, }); diff --git a/packages/eslint-plugin/src/rules/no-misused-new.ts b/packages/eslint-plugin/src/rules/no-misused-new.ts index 932a85cc6080..f877f9d7d9cb 100644 --- a/packages/eslint-plugin/src/rules/no-misused-new.ts +++ b/packages/eslint-plugin/src/rules/no-misused-new.ts @@ -99,14 +99,7 @@ export default createRule({ node: TSESTree.MethodDefinition, ): void { if (node.value.type === AST_NODE_TYPES.TSEmptyBodyFunctionExpression) { - if ( - isMatchingParentType( - node.parent.parent as - | TSESTree.ClassDeclaration - | TSESTree.ClassExpression, - node.value.returnType, - ) - ) { + if (isMatchingParentType(node.parent.parent, node.value.returnType)) { context.report({ node, messageId: 'errorMessageClass', diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index 1a58d884dc7c..46d3d9759b1c 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -6,7 +6,10 @@ import * as ts from 'typescript'; import { createRule, getParserServices, + isFunction, isRestParameterDeclaration, + nullThrows, + NullThrowsReasons, } from '../util'; type Options = [ @@ -170,9 +173,77 @@ export default createRule({ SpreadElement: checkSpread, }; - function checkTestConditional(node: { - test: TSESTree.Expression | null; - }): void { + /** + * A syntactic check to see if an annotated type is maybe a function type. + * This is a perf optimization to help avoid requesting types where possible + */ + function isPossiblyFunctionType(node: TSESTree.TSTypeAnnotation): boolean { + switch (node.typeAnnotation.type) { + case AST_NODE_TYPES.TSConditionalType: + case AST_NODE_TYPES.TSConstructorType: + case AST_NODE_TYPES.TSFunctionType: + case AST_NODE_TYPES.TSImportType: + case AST_NODE_TYPES.TSIndexedAccessType: + case AST_NODE_TYPES.TSInferType: + case AST_NODE_TYPES.TSIntersectionType: + case AST_NODE_TYPES.TSQualifiedName: + case AST_NODE_TYPES.TSThisType: + case AST_NODE_TYPES.TSTypeOperator: + case AST_NODE_TYPES.TSTypeQuery: + case AST_NODE_TYPES.TSTypeReference: + case AST_NODE_TYPES.TSUnionType: + return true; + + case AST_NODE_TYPES.TSTypeLiteral: + return node.typeAnnotation.members.some( + member => + member.type === AST_NODE_TYPES.TSCallSignatureDeclaration || + member.type === AST_NODE_TYPES.TSConstructSignatureDeclaration, + ); + + case AST_NODE_TYPES.TSAbstractKeyword: + case AST_NODE_TYPES.TSAnyKeyword: + case AST_NODE_TYPES.TSArrayType: + case AST_NODE_TYPES.TSAsyncKeyword: + case AST_NODE_TYPES.TSBigIntKeyword: + case AST_NODE_TYPES.TSBooleanKeyword: + case AST_NODE_TYPES.TSDeclareKeyword: + case AST_NODE_TYPES.TSExportKeyword: + case AST_NODE_TYPES.TSIntrinsicKeyword: + case AST_NODE_TYPES.TSLiteralType: + case AST_NODE_TYPES.TSMappedType: + case AST_NODE_TYPES.TSNamedTupleMember: + case AST_NODE_TYPES.TSNeverKeyword: + case AST_NODE_TYPES.TSNullKeyword: + case AST_NODE_TYPES.TSNumberKeyword: + case AST_NODE_TYPES.TSObjectKeyword: + case AST_NODE_TYPES.TSOptionalType: + case AST_NODE_TYPES.TSPrivateKeyword: + case AST_NODE_TYPES.TSProtectedKeyword: + case AST_NODE_TYPES.TSPublicKeyword: + case AST_NODE_TYPES.TSReadonlyKeyword: + case AST_NODE_TYPES.TSRestType: + case AST_NODE_TYPES.TSStaticKeyword: + case AST_NODE_TYPES.TSStringKeyword: + case AST_NODE_TYPES.TSSymbolKeyword: + case AST_NODE_TYPES.TSTemplateLiteralType: + case AST_NODE_TYPES.TSTupleType: + case AST_NODE_TYPES.TSTypePredicate: + case AST_NODE_TYPES.TSUndefinedKeyword: + case AST_NODE_TYPES.TSUnknownKeyword: + case AST_NODE_TYPES.TSVoidKeyword: + return false; + } + } + + function checkTestConditional( + node: + | TSESTree.ConditionalExpression + | TSESTree.DoWhileStatement + | TSESTree.ForStatement + | TSESTree.IfStatement + | TSESTree.WhileStatement, + ): void { if (node.test) { checkConditional(node.test, true); } @@ -255,9 +326,19 @@ export default createRule({ function checkVariableDeclaration(node: TSESTree.VariableDeclarator): void { const tsNode = services.esTreeNodeToTSNodeMap.get(node); - if (tsNode.initializer === undefined || node.init == null) { + if ( + tsNode.initializer === undefined || + node.init == null || + node.id.typeAnnotation == null + ) { return; } + + // syntactically ignore some known-good cases to avoid touching type info + if (!isPossiblyFunctionType(node.id.typeAnnotation)) { + return; + } + const varType = services.getTypeAtLocation(node.id); if (!isVoidReturningFunctionType(checker, tsNode.initializer, varType)) { return; @@ -352,6 +433,22 @@ export default createRule({ if (tsNode.expression === undefined || node.argument == null) { return; } + + // syntactically ignore some known-good cases to avoid touching type info + const functionNode = (() => { + let current: TSESTree.Node | undefined = node.parent; + while (current && !isFunction(current)) { + current = current.parent; + } + return nullThrows(current, NullThrowsReasons.MissingParent); + })(); + if ( + functionNode.returnType && + !isPossiblyFunctionType(functionNode.returnType) + ) { + return; + } + const contextualType = checker.getContextualType(tsNode.expression); if ( contextualType !== undefined && diff --git a/packages/eslint-plugin/src/rules/no-mixed-enums.ts b/packages/eslint-plugin/src/rules/no-mixed-enums.ts index a3eddf6f2a0f..ce6a706a10d0 100644 --- a/packages/eslint-plugin/src/rules/no-mixed-enums.ts +++ b/packages/eslint-plugin/src/rules/no-mixed-enums.ts @@ -51,7 +51,7 @@ export default createRule({ if ( definition.node.type === AST_NODE_TYPES.TSEnumDeclaration && definition.node.range[0] < node.range[0] && - definition.node.members.length > 0 + definition.node.body.members.length > 0 ) { found.previousSibling = definition.node; break; @@ -146,7 +146,7 @@ export default createRule({ // enum MyEnum { A } // enum MyEnum { B } if (previousSibling) { - return getMemberType(previousSibling.members[0]); + return getMemberType(previousSibling.body.members[0]); } // Case: Namespace declaration merging @@ -182,12 +182,12 @@ export default createRule({ } // Finally, we default to the type of the first enum member - return getMemberType(node.members[0]); + return getMemberType(node.body.members[0]); } return { TSEnumDeclaration(node): void { - if (!node.members.length) { + if (!node.body.members.length) { return; } @@ -196,7 +196,7 @@ export default createRule({ return; } - for (const member of node.members) { + for (const member of node.body.members) { const currentType = getMemberType(member); if (currentType === AllowedType.Unknown) { return; diff --git a/packages/eslint-plugin/src/rules/no-require-imports.ts b/packages/eslint-plugin/src/rules/no-require-imports.ts index 1956694484f4..64f626076c32 100644 --- a/packages/eslint-plugin/src/rules/no-require-imports.ts +++ b/packages/eslint-plugin/src/rules/no-require-imports.ts @@ -5,7 +5,8 @@ import * as util from '../util'; type Options = [ { - allow: string[]; + allow?: string[]; + allowAsImport?: boolean; }, ]; type MessageIds = 'noRequireImports'; @@ -16,6 +17,7 @@ export default util.createRule({ type: 'problem', docs: { description: 'Disallow invocation of `require()`', + recommended: 'recommended', }, schema: [ { @@ -26,6 +28,10 @@ export default util.createRule({ items: { type: 'string' }, description: 'Patterns of import paths to allow requiring from.', }, + allowAsImport: { + type: 'boolean', + description: 'Allows `require` statements in import declarations.', + }, }, additionalProperties: false, }, @@ -34,13 +40,14 @@ export default util.createRule({ noRequireImports: 'A `require()` style import is forbidden.', }, }, - defaultOptions: [{ allow: [] }], + defaultOptions: [{ allow: [], allowAsImport: false }], create(context, options) { - const allowPatterns = options[0].allow.map( + const allowAsImport = options[0].allowAsImport; + const allowPatterns = options[0].allow?.map( pattern => new RegExp(pattern, 'u'), ); - function isImportPathAllowed(importPath: string): boolean { - return allowPatterns.some(pattern => importPath.match(pattern)); + function isImportPathAllowed(importPath: string): boolean | undefined { + return allowPatterns?.some(pattern => importPath.match(pattern)); } function isStringOrTemplateLiteral(node: TSESTree.Node): boolean { return ( @@ -64,7 +71,6 @@ export default util.createRule({ context.sourceCode.getScope(node), 'require', ); - // ignore non-global require usage as it's something user-land custom instead // of the commonjs standard if (!variable?.identifiers.length) { @@ -81,6 +87,12 @@ export default util.createRule({ return; } } + if ( + allowAsImport && + node.parent.type === AST_NODE_TYPES.TSImportEqualsDeclaration + ) { + return; + } context.report({ node, messageId: 'noRequireImports', diff --git a/packages/eslint-plugin/src/rules/no-restricted-imports.ts b/packages/eslint-plugin/src/rules/no-restricted-imports.ts index 1bd052ab3330..f61baa7ed396 100644 --- a/packages/eslint-plugin/src/rules/no-restricted-imports.ts +++ b/packages/eslint-plugin/src/rules/no-restricted-imports.ts @@ -172,13 +172,13 @@ const schema: JSONSchema4AnyOfSchema = { function isObjectOfPaths( obj: unknown, ): obj is { paths: ArrayOfStringOrObject } { - return Object.prototype.hasOwnProperty.call(obj, 'paths'); + return !!obj && Object.hasOwn(obj, 'paths'); } function isObjectOfPatterns( obj: unknown, ): obj is { patterns: ArrayOfStringOrObjectPatterns } { - return Object.prototype.hasOwnProperty.call(obj, 'patterns'); + return !!obj && Object.hasOwn(obj, 'patterns'); } function isOptionsArrayOfStringOrObject( @@ -314,7 +314,7 @@ export default createRule({ if ( node.moduleReference.type === AST_NODE_TYPES.TSExternalModuleReference ) { - const synthesizedImport = { + const synthesizedImport: TSESTree.ImportDeclaration = { ...node, type: AST_NODE_TYPES.ImportDeclaration, source: node.moduleReference.expression, @@ -325,9 +325,11 @@ export default createRule({ ...node.id, type: AST_NODE_TYPES.ImportDefaultSpecifier, local: node.id, + // @ts-expect-error -- parent types are incompatible but it's fine for the purposes of this extension + parent: node.id.parent, }, ], - } satisfies TSESTree.ImportDeclaration; + }; return checkImportNode(synthesizedImport); } }, diff --git a/packages/eslint-plugin/src/rules/ban-types.ts b/packages/eslint-plugin/src/rules/no-restricted-types.ts similarity index 64% rename from packages/eslint-plugin/src/rules/ban-types.ts rename to packages/eslint-plugin/src/rules/no-restricted-types.ts index adbb8311621a..f3d2e3b3f197 100644 --- a/packages/eslint-plugin/src/rules/ban-types.ts +++ b/packages/eslint-plugin/src/rules/no-restricted-types.ts @@ -21,6 +21,7 @@ export type Options = [ extendDefaults?: boolean; }, ]; + export type MessageIds = 'bannedTypeMessage' | 'bannedTypeReplacement'; function removeSpaces(str: string): string { @@ -37,7 +38,7 @@ function stringifyNode( function getCustomMessage( bannedType: string | true | { message?: string; fixWith?: string } | null, ): string { - if (bannedType == null || bannedType === true) { + if (!bannedType || bannedType === true) { return ''; } @@ -52,65 +53,7 @@ function getCustomMessage( return ''; } -const defaultTypes: Types = { - String: { - message: 'Use string instead', - fixWith: 'string', - }, - Boolean: { - message: 'Use boolean instead', - fixWith: 'boolean', - }, - Number: { - message: 'Use number instead', - fixWith: 'number', - }, - Symbol: { - message: 'Use symbol instead', - fixWith: 'symbol', - }, - BigInt: { - message: 'Use bigint instead', - fixWith: 'bigint', - }, - - Function: { - message: [ - 'The `Function` type accepts any function-like value.', - 'It provides no type safety when calling the function, which can be a common source of bugs.', - 'It also accepts things like class declarations, which will throw at runtime as they will not be called with `new`.', - 'If you are expecting the function to accept certain arguments, you should explicitly define the function shape.', - ].join('\n'), - }, - - // object typing - Object: { - message: [ - 'The `Object` type actually means "any non-nullish value", so it is marginally better than `unknown`.', - '- If you want a type meaning "any object", you probably want `object` instead.', - '- If you want a type meaning "any value", you probably want `unknown` instead.', - '- If you really want a type meaning "any non-nullish value", you probably want `NonNullable` instead.', - ].join('\n'), - suggest: ['object', 'unknown', 'NonNullable'], - }, - '{}': { - message: [ - '`{}` actually means "any non-nullish value".', - '- If you want a type meaning "any object", you probably want `object` instead.', - '- If you want a type meaning "any value", you probably want `unknown` instead.', - '- If you want a type meaning "empty object", you probably want `Record` instead.', - '- If you really want a type meaning "any non-nullish value", you probably want `NonNullable` instead.', - ].join('\n'), - suggest: [ - 'object', - 'unknown', - 'Record', - 'NonNullable', - ], - }, -}; - -export const TYPE_KEYWORDS = { +const TYPE_KEYWORDS = { bigint: AST_NODE_TYPES.TSBigIntKeyword, boolean: AST_NODE_TYPES.TSBooleanKeyword, never: AST_NODE_TYPES.TSNeverKeyword, @@ -125,12 +68,11 @@ export const TYPE_KEYWORDS = { }; export default createRule({ - name: 'ban-types', + name: 'no-restricted-types', meta: { type: 'suggestion', docs: { description: 'Disallow certain types', - recommended: 'recommended', }, fixable: 'code', hasSuggestions: true, @@ -143,16 +85,6 @@ export default createRule({ $defs: { banConfig: { oneOf: [ - { - type: 'null', - description: 'Bans the type with the default message', - }, - { - type: 'boolean', - enum: [false], - description: - 'Un-bans the type (useful when paired with `extendDefaults`)', - }, { type: 'boolean', enum: [true], @@ -179,7 +111,6 @@ export default createRule({ type: 'array', items: { type: 'string' }, description: 'Types to suggest replacing with.', - additionalItems: false, }, }, additionalProperties: false, @@ -195,22 +126,13 @@ export default createRule({ $ref: '#/items/0/$defs/banConfig', }, }, - extendDefaults: { - type: 'boolean', - }, }, additionalProperties: false, }, ], }, defaultOptions: [{}], - create(context, [options]) { - const extendDefaults = options.extendDefaults ?? true; - const customTypes = options.types ?? {}; - const types = { - ...(extendDefaults && defaultTypes), - ...customTypes, - }; + create(context, [{ types = {} }]) { const bannedTypes = new Map( Object.entries(types).map(([type, data]) => [removeSpaces(type), data]), ); @@ -271,15 +193,19 @@ export default createRule({ return { ...keywordSelectors, - TSTypeLiteral(node): void { - if (node.members.length) { - return; - } - + TSClassImplements(node): void { + checkBannedTypes(node); + }, + TSInterfaceHeritage(node): void { checkBannedTypes(node); }, TSTupleType(node): void { - if (node.elementTypes.length === 0) { + if (!node.elementTypes.length) { + checkBannedTypes(node); + } + }, + TSTypeLiteral(node): void { + if (!node.members.length) { checkBannedTypes(node); } }, @@ -290,12 +216,6 @@ export default createRule({ checkBannedTypes(node); } }, - TSInterfaceHeritage(node): void { - checkBannedTypes(node); - }, - TSClassImplements(node): void { - checkBannedTypes(node); - }, }; }, }); diff --git a/packages/eslint-plugin/src/rules/no-shadow.ts b/packages/eslint-plugin/src/rules/no-shadow.ts index f66c21f6cdb1..165b052a230e 100644 --- a/packages/eslint-plugin/src/rules/no-shadow.ts +++ b/packages/eslint-plugin/src/rules/no-shadow.ts @@ -1,12 +1,9 @@ -import type { - Definition, - ImportBindingDefinition, -} from '@typescript-eslint/scope-manager'; import { DefinitionType, ScopeType } from '@typescript-eslint/scope-manager'; import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils'; import { createRule } from '../util'; +import { isTypeImport } from '../util/isTypeImport'; type MessageIds = 'noShadow' | 'noShadowGlobal'; type Options = [ @@ -102,17 +99,6 @@ export default createRule({ ); } - function isTypeImport( - definition?: Definition, - ): definition is ImportBindingDefinition { - return ( - definition?.type === DefinitionType.ImportBinding && - (definition.parent.importKind === 'type' || - (definition.node.type === AST_NODE_TYPES.ImportSpecifier && - definition.node.importKind === 'type')) - ); - } - function isTypeValueShadow( variable: TSESLint.Scope.Variable, shadowed: TSESLint.Scope.Variable, diff --git a/packages/eslint-plugin/src/rules/no-throw-literal.ts b/packages/eslint-plugin/src/rules/no-throw-literal.ts deleted file mode 100644 index d893dbc164a5..000000000000 --- a/packages/eslint-plugin/src/rules/no-throw-literal.ts +++ /dev/null @@ -1,99 +0,0 @@ -import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as ts from 'typescript'; - -import { - createRule, - getParserServices, - isErrorLike, - isTypeAnyType, - isTypeUnknownType, -} from '../util'; - -type MessageIds = 'object' | 'undef'; - -type Options = [ - { - allowThrowingAny?: boolean; - allowThrowingUnknown?: boolean; - }, -]; - -export default createRule({ - name: 'no-throw-literal', - meta: { - type: 'problem', - deprecated: true, - replacedBy: ['@typescript-eslint/only-throw-error'], - docs: { - description: 'Disallow throwing literals as exceptions', - extendsBaseRule: true, - requiresTypeChecking: true, - }, - schema: [ - { - type: 'object', - properties: { - allowThrowingAny: { - type: 'boolean', - }, - allowThrowingUnknown: { - type: 'boolean', - }, - }, - additionalProperties: false, - }, - ], - messages: { - object: 'Expected an error object to be thrown.', - undef: 'Do not throw undefined.', - }, - }, - defaultOptions: [ - { - allowThrowingAny: true, - allowThrowingUnknown: true, - }, - ], - create(context, [options]) { - const services = getParserServices(context); - - function checkThrowArgument(node: TSESTree.Node): void { - if ( - node.type === AST_NODE_TYPES.AwaitExpression || - node.type === AST_NODE_TYPES.YieldExpression - ) { - return; - } - - const type = services.getTypeAtLocation(node); - - if (type.flags & ts.TypeFlags.Undefined) { - context.report({ node, messageId: 'undef' }); - return; - } - - if (options.allowThrowingAny && isTypeAnyType(type)) { - return; - } - - if (options.allowThrowingUnknown && isTypeUnknownType(type)) { - return; - } - - if (isErrorLike(services.program, type)) { - return; - } - - context.report({ node, messageId: 'object' }); - } - - return { - ThrowStatement(node): void { - if (node.argument) { - checkThrowArgument(node.argument); - } - }, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index c1f78e172b47..e3bf94a30c78 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -627,13 +627,9 @@ export default createRule({ if (prevType.isUnion()) { const isOwnNullable = prevType.types.some(type => { const signatures = type.getCallSignatures(); - return signatures.some(sig => - isNullableType(sig.getReturnType(), { allowUndefined: true }), - ); + return signatures.some(sig => isNullableType(sig.getReturnType())); }); - return ( - !isOwnNullable && isNullableType(prevType, { allowUndefined: true }) - ); + return !isOwnNullable && isNullableType(prevType); } return false; diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts b/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts index 329b26d87aee..5b90c5674367 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts @@ -160,7 +160,7 @@ export default createRule({ 'TSModuleDeclaration > TSModuleBlock'( node: TSESTree.TSModuleBlock, ): void { - enterDeclaration(node.parent as TSESTree.TSModuleDeclaration); + enterDeclaration(node.parent); }, TSEnumDeclaration: enterDeclaration, 'ExportNamedDeclaration[declaration.type="TSModuleDeclaration"]': diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts b/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts index 44e10c5e33c8..3f6e7f69ea8b 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts @@ -6,13 +6,23 @@ import { createRule, getConstrainedTypeAtLocation, getParserServices, - getStaticStringValue, isTypeFlagSet, isUndefinedIdentifier, } from '../util'; type MessageId = 'noUnnecessaryTemplateExpression'; +const evenNumOfBackslashesRegExp = /(?({ name: 'no-unnecessary-template-expression', meta: { @@ -53,11 +63,15 @@ export default createRule<[], MessageId>({ return isString(type); } - function isLiteral(expression: TSESTree.Expression): boolean { + function isLiteral( + expression: TSESTree.Expression, + ): expression is TSESTree.Literal { return expression.type === AST_NODE_TYPES.Literal; } - function isTemplateLiteral(expression: TSESTree.Expression): boolean { + function isTemplateLiteral( + expression: TSESTree.Expression, + ): expression is TSESTree.TemplateLiteral { return expression.type === AST_NODE_TYPES.TemplateLiteral; } @@ -113,62 +127,150 @@ export default createRule<[], MessageId>({ return; } - const fixableExpressions = node.expressions.filter( - expression => - isLiteral(expression) || - isTemplateLiteral(expression) || - isUndefinedIdentifier(expression) || - isInfinityIdentifier(expression) || - isNaNIdentifier(expression), - ); + const fixableExpressions = node.expressions + .filter( + expression => + isLiteral(expression) || + isTemplateLiteral(expression) || + isUndefinedIdentifier(expression) || + isInfinityIdentifier(expression) || + isNaNIdentifier(expression), + ) + .reverse(); + + let nextCharacterIsOpeningCurlyBrace = false; + + for (const expression of fixableExpressions) { + const fixers: ((fixer: TSESLint.RuleFixer) => TSESLint.RuleFix[])[] = + []; + const index = node.expressions.indexOf(expression); + const prevQuasi = node.quasis[index]; + const nextQuasi = node.quasis[index + 1]; + + if (nextQuasi.value.raw.length !== 0) { + nextCharacterIsOpeningCurlyBrace = + nextQuasi.value.raw.startsWith('{'); + } + + if (isLiteral(expression)) { + let escapedValue = ( + typeof expression.value === 'string' + ? // The value is already a string, so we're removing quotes: + // "'va`lue'" -> "va`lue" + expression.raw.slice(1, -1) + : // The value may be one of number | bigint | boolean | RegExp | null. + // In regular expressions, we escape every backslash + String(expression.value).replace(/\\/g, '\\\\') + ) + // The string or RegExp may contain ` or ${. + // We want both of these to be escaped in the final template expression. + // + // A pair of backslashes means "escaped backslash", so backslashes + // from this pair won't escape ` or ${. Therefore, to escape these + // sequences in the resulting template expression, we need to escape + // all sequences that are preceded by an even number of backslashes. + // + // This RegExp does the following transformations: + // \` -> \` + // \\` -> \\\` + // \${ -> \${ + // \\${ -> \\\${ + .replace( + new RegExp( + String(evenNumOfBackslashesRegExp.source) + '(`|\\${)', + 'g', + ), + '\\$1', + ); + + // `...${'...$'}{...` + // ^^^^ + if ( + nextCharacterIsOpeningCurlyBrace && + endsWithUnescapedDollarSign(escapedValue) + ) { + escapedValue = escapedValue.replaceAll(/\$$/g, '\\$'); + } + + if (escapedValue.length !== 0) { + nextCharacterIsOpeningCurlyBrace = escapedValue.startsWith('{'); + } + + fixers.push(fixer => [fixer.replaceText(expression, escapedValue)]); + } else if (isTemplateLiteral(expression)) { + // Since we iterate from the last expression to the first, + // a subsequent expression can tell the current expression + // that it starts with {. + // + // `... ${`... $`}${'{...'} ...` + // ^ ^ subsequent expression starts with { + // current expression ends with a dollar sign, + // so '$' + '{' === '${' (bad news for us). + // Let's escape the dollar sign at the end. + if ( + nextCharacterIsOpeningCurlyBrace && + endsWithUnescapedDollarSign( + expression.quasis[expression.quasis.length - 1].value.raw, + ) + ) { + fixers.push(fixer => [ + fixer.replaceTextRange( + [expression.range[1] - 2, expression.range[1] - 2], + '\\', + ), + ]); + } + if ( + expression.quasis.length === 1 && + expression.quasis[0].value.raw.length !== 0 + ) { + nextCharacterIsOpeningCurlyBrace = + expression.quasis[0].value.raw.startsWith('{'); + } + + // Remove the beginning and trailing backtick characters. + fixers.push(fixer => [ + fixer.removeRange([expression.range[0], expression.range[0] + 1]), + fixer.removeRange([expression.range[1] - 1, expression.range[1]]), + ]); + } else { + nextCharacterIsOpeningCurlyBrace = false; + } + + // `... $${'{...'} ...` + // ^^^^^ + if ( + nextCharacterIsOpeningCurlyBrace && + endsWithUnescapedDollarSign(prevQuasi.value.raw) + ) { + fixers.push(fixer => [ + fixer.replaceTextRange( + [prevQuasi.range[1] - 3, prevQuasi.range[1] - 2], + '\\$', + ), + ]); + } - fixableExpressions.forEach(expression => { context.report({ node: expression, messageId: 'noUnnecessaryTemplateExpression', fix(fixer): TSESLint.RuleFix[] { - const index = node.expressions.indexOf(expression); - const prevQuasi = node.quasis[index]; - const nextQuasi = node.quasis[index + 1]; - - // Remove the quasis' parts that are related to the current expression. - const fixes = [ + return [ + // Remove the quasis' parts that are related to the current expression. fixer.removeRange([ prevQuasi.range[1] - 2, expression.range[0], ]), - fixer.removeRange([ expression.range[1], nextQuasi.range[0] + 1, ]), - ]; - const stringValue = getStaticStringValue(expression); - - if (stringValue != null) { - const escapedValue = stringValue.replace(/([`$\\])/g, '\\$1'); - - fixes.push(fixer.replaceText(expression, escapedValue)); - } else if (isTemplateLiteral(expression)) { - // Note that some template literals get handled in the previous branch too. - // Remove the beginning and trailing backtick characters. - fixes.push( - fixer.removeRange([ - expression.range[0], - expression.range[0] + 1, - ]), - fixer.removeRange([ - expression.range[1] - 1, - expression.range[1], - ]), - ); - } - - return fixes; + ...fixers.flatMap(cb => cb(fixer)), + ]; }, }); - }); + } }, }; }, diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-parameters.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-parameters.ts index 1fcaad928213..f191efa2f4e7 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-parameters.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-parameters.ts @@ -18,6 +18,7 @@ export default createRule({ docs: { description: 'Disallow type parameters that only appear once', requiresTypeChecking: true, + recommended: 'strict', }, messages: { sole: 'Type parameter {{name}} is used only once.', diff --git a/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts b/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts index 0d165ae7eb5c..c9625dd5f62a 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts @@ -171,14 +171,7 @@ export default createRule({ const { parent } = node; - /** - * @see https://github.com/typescript-eslint/typescript-eslint/issues/6225 - */ - const switchStatement = parent as TSESTree.SwitchStatement; - - const leftType = parserServices.getTypeAtLocation( - switchStatement.discriminant, - ); + const leftType = parserServices.getTypeAtLocation(parent.discriminant); const rightType = parserServices.getTypeAtLocation(node.test); if (isMismatchedComparison(leftType, rightType)) { diff --git a/packages/eslint-plugin/src/rules/no-unsafe-function-type.ts b/packages/eslint-plugin/src/rules/no-unsafe-function-type.ts index d96cba33bee3..624c038f8ff7 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-function-type.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-function-type.ts @@ -9,6 +9,7 @@ export default createRule({ type: 'problem', docs: { description: 'Disallow using the unsafe built-in Function type', + recommended: 'recommended', }, fixable: 'code', messages: { diff --git a/packages/eslint-plugin/src/rules/no-unsafe-unary-minus.ts b/packages/eslint-plugin/src/rules/no-unsafe-unary-minus.ts index 66488e37124a..a61572515437 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-unary-minus.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-unary-minus.ts @@ -12,6 +12,7 @@ export default util.createRule({ type: 'problem', docs: { description: 'Require unary negation to take a number', + recommended: 'recommended', requiresTypeChecking: true, }, messages: { diff --git a/packages/eslint-plugin/src/rules/no-unused-expressions.ts b/packages/eslint-plugin/src/rules/no-unused-expressions.ts index 83c2ddd6c527..3c386aa69a87 100644 --- a/packages/eslint-plugin/src/rules/no-unused-expressions.ts +++ b/packages/eslint-plugin/src/rules/no-unused-expressions.ts @@ -19,6 +19,7 @@ export default createRule({ docs: { description: 'Disallow unused expressions', extendsBaseRule: true, + recommended: 'recommended', }, hasSuggestions: baseRule.meta.hasSuggestions, schema: baseRule.meta.schema, diff --git a/packages/eslint-plugin/src/rules/no-unused-vars.ts b/packages/eslint-plugin/src/rules/no-unused-vars.ts index 4ee0455a0e71..d98c2f5bf5a5 100644 --- a/packages/eslint-plugin/src/rules/no-unused-vars.ts +++ b/packages/eslint-plugin/src/rules/no-unused-vars.ts @@ -1,9 +1,16 @@ -import { PatternVisitor } from '@typescript-eslint/scope-manager'; +import type { + Definition, + ScopeVariable, +} from '@typescript-eslint/scope-manager'; +import { + DefinitionType, + PatternVisitor, +} from '@typescript-eslint/scope-manager'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES, TSESLint } from '@typescript-eslint/utils'; import { - collectUnusedVariables as _collectUnusedVariables, + collectVariables, createRule, getNameLocationInGlobalDirectiveComment, isDefinitionFile, @@ -11,8 +18,9 @@ import { nullThrows, NullThrowsReasons, } from '../util'; +import { referenceContainsTypeQuery } from '../util/referenceContainsTypeQuery'; -export type MessageIds = 'unusedVar'; +export type MessageIds = 'unusedVar' | 'usedIgnoredVar' | 'usedOnlyAsType'; export type Options = [ | 'all' | 'local' @@ -25,6 +33,8 @@ export type Options = [ caughtErrors?: 'all' | 'none'; caughtErrorsIgnorePattern?: string; destructuredArrayIgnorePattern?: string; + ignoreClassWithStaticInitBlock?: boolean; + reportUsedIgnorePattern?: boolean; }, ]; @@ -37,8 +47,16 @@ interface TranslatedOptions { caughtErrors: 'all' | 'none'; caughtErrorsIgnorePattern?: RegExp; destructuredArrayIgnorePattern?: RegExp; + ignoreClassWithStaticInitBlock: boolean; + reportUsedIgnorePattern: boolean; } +type VariableType = + | 'array-destructure' + | 'catch-clause' + | 'parameter' + | 'variable'; + export default createRule({ name: 'no-unused-vars', meta: { @@ -85,6 +103,12 @@ export default createRule({ destructuredArrayIgnorePattern: { type: 'string', }, + ignoreClassWithStaticInitBlock: { + type: 'boolean', + }, + reportUsedIgnorePattern: { + type: 'boolean', + }, }, additionalProperties: false, }, @@ -93,6 +117,10 @@ export default createRule({ ], messages: { unusedVar: "'{{varName}}' is {{action}} but never used{{additional}}.", + usedIgnoredVar: + "'{{varName}}' is marked as ignored but is used{{additional}}.", + usedOnlyAsType: + "'{{varName}}' is {{action}} but only used as a type{{additional}}.", }, }, defaultOptions: [{}], @@ -104,7 +132,9 @@ export default createRule({ vars: 'all', args: 'after-used', ignoreRestSiblings: false, - caughtErrors: 'none', + caughtErrors: 'all', + ignoreClassWithStaticInitBlock: false, + reportUsedIgnorePattern: false, }; if (typeof firstOption === 'string') { @@ -115,6 +145,12 @@ export default createRule({ options.ignoreRestSiblings = firstOption.ignoreRestSiblings ?? options.ignoreRestSiblings; options.caughtErrors = firstOption.caughtErrors ?? options.caughtErrors; + options.ignoreClassWithStaticInitBlock = + firstOption.ignoreClassWithStaticInitBlock ?? + options.ignoreClassWithStaticInitBlock; + options.reportUsedIgnorePattern = + firstOption.reportUsedIgnorePattern ?? + options.reportUsedIgnorePattern; if (firstOption.varsIgnorePattern) { options.varsIgnorePattern = new RegExp( @@ -148,7 +184,161 @@ export default createRule({ return options; })(); - function collectUnusedVariables(): TSESLint.Scope.Variable[] { + /** + * Determines what variable type a def is. + * @param def the declaration to check + * @returns a simple name for the types of variables that this rule supports + */ + function defToVariableType(def: Definition): VariableType { + /* + * This `destructuredArrayIgnorePattern` error report works differently from the catch + * clause and parameter error reports. _Both_ the `varsIgnorePattern` and the + * `destructuredArrayIgnorePattern` will be checked for array destructuring. However, + * for the purposes of the report, the currently defined behavior is to only inform the + * user of the `destructuredArrayIgnorePattern` if it's present (regardless of the fact + * that the `varsIgnorePattern` would also apply). If it's not present, the user will be + * informed of the `varsIgnorePattern`, assuming that's present. + */ + if ( + options.destructuredArrayIgnorePattern && + def.name.parent.type === AST_NODE_TYPES.ArrayPattern + ) { + return 'array-destructure'; + } + + switch (def.type) { + case DefinitionType.CatchClause: + return 'catch-clause'; + case DefinitionType.Parameter: + return 'parameter'; + default: + return 'variable'; + } + } + + /** + * Gets a given variable's description and configured ignore pattern + * based on the provided variableType + * @param variableType a simple name for the types of variables that this rule supports + * @returns the given variable's description and + * ignore pattern + */ + function getVariableDescription(variableType: VariableType): { + pattern: string | undefined; + variableDescription: string; + } { + switch (variableType) { + case 'array-destructure': + return { + pattern: options.destructuredArrayIgnorePattern?.toString(), + variableDescription: 'elements of array destructuring', + }; + + case 'catch-clause': + return { + pattern: options.caughtErrorsIgnorePattern?.toString(), + variableDescription: 'caught errors', + }; + + case 'parameter': + return { + pattern: options.argsIgnorePattern?.toString(), + variableDescription: 'args', + }; + + case 'variable': + return { + pattern: options.varsIgnorePattern?.toString(), + variableDescription: 'vars', + }; + } + } + + /** + * Generates the message data about the variable being defined and unused, + * including the ignore pattern if configured. + * @param unusedVar eslint-scope variable object. + * @returns The message data to be used with this unused variable. + */ + function getDefinedMessageData( + unusedVar: ScopeVariable, + ): Record { + const def = unusedVar.defs.at(0); + let additionalMessageData = ''; + + if (def) { + const { variableDescription, pattern } = getVariableDescription( + defToVariableType(def), + ); + + if (pattern && variableDescription) { + additionalMessageData = `. Allowed unused ${variableDescription} must match ${pattern}`; + } + } + + return { + varName: unusedVar.name, + action: 'defined', + additional: additionalMessageData, + }; + } + + /** + * Generate the warning message about the variable being + * assigned and unused, including the ignore pattern if configured. + * @param unusedVar eslint-scope variable object. + * @returns The message data to be used with this unused variable. + */ + function getAssignedMessageData( + unusedVar: ScopeVariable, + ): Record { + const def = unusedVar.defs.at(0); + let additionalMessageData = ''; + + if (def) { + const { variableDescription, pattern } = getVariableDescription( + defToVariableType(def), + ); + + if (pattern && variableDescription) { + additionalMessageData = `. Allowed unused ${variableDescription} must match ${pattern}`; + } + } + + return { + varName: unusedVar.name, + action: 'assigned a value', + additional: additionalMessageData, + }; + } + + /** + * Generate the warning message about a variable being used even though + * it is marked as being ignored. + * @param variable eslint-scope variable object + * @param variableType a simple name for the types of variables that this rule supports + * @returns The message data to be used with this used ignored variable. + */ + function getUsedIgnoredMessageData( + variable: ScopeVariable, + variableType: VariableType, + ): Record { + const { variableDescription, pattern } = + getVariableDescription(variableType); + + let additionalMessageData = ''; + + if (pattern && variableDescription) { + additionalMessageData = `. Used ${variableDescription} must not match ${pattern}`; + } + + return { + varName: variable.name, + additional: additionalMessageData, + }; + } + + function collectUnusedVariables(): ScopeVariable[] { /** * Checks whether a node is a sibling of the rest property or not. * @param node a node to check @@ -168,9 +358,7 @@ export default createRule({ * @param variable eslint-scope variable object. * @returns True if the variable is exported, false if not. */ - function hasRestSpreadSibling( - variable: TSESLint.Scope.Variable, - ): boolean { + function hasRestSpreadSibling(variable: ScopeVariable): boolean { if (options.ignoreRestSiblings) { const hasRestSiblingDefinition = variable.defs.some(def => hasRestSibling(def.name.parent), @@ -190,7 +378,7 @@ export default createRule({ * @param variable The variable to check. * @returns `true` if the variable is defined after the last used parameter. */ - function isAfterLastUsedArg(variable: TSESLint.Scope.Variable): boolean { + function isAfterLastUsedArg(variable: ScopeVariable): boolean { const def = variable.defs[0]; const params = context.sourceCode.getDeclaredVariables(def.node); const posteriorParams = params.slice(params.indexOf(variable) + 1); @@ -201,12 +389,25 @@ export default createRule({ ); } - const unusedVariablesOriginal = _collectUnusedVariables(context); - const unusedVariablesReturn: TSESLint.Scope.Variable[] = []; - for (const variable of unusedVariablesOriginal) { + const analysisResults = collectVariables(context); + const variables = [ + ...Array.from(analysisResults.unusedVariables, variable => ({ + used: false, + variable, + })), + ...Array.from(analysisResults.usedVariables, variable => ({ + used: true, + variable, + })), + ]; + const unusedVariablesReturn: ScopeVariable[] = []; + for (const { used, variable } of variables) { // explicit global variables don't have definitions. if (variable.defs.length === 0) { - unusedVariablesReturn.push(variable); + if (!used) { + unusedVariablesReturn.push(variable); + } + continue; } const def = variable.defs[0]; @@ -230,9 +431,26 @@ export default createRule({ def.name.type === AST_NODE_TYPES.Identifier && options.destructuredArrayIgnorePattern?.test(def.name.name) ) { + if (options.reportUsedIgnorePattern && used) { + context.report({ + node: def.name, + messageId: 'usedIgnoredVar', + data: getUsedIgnoredMessageData(variable, 'array-destructure'), + }); + } continue; } + if (def.type === TSESLint.Scope.DefinitionType.ClassName) { + const hasStaticBlock = def.node.body.body.some( + node => node.type === AST_NODE_TYPES.StaticBlock, + ); + + if (options.ignoreClassWithStaticInitBlock && hasStaticBlock) { + continue; + } + } + // skip catch variables if (def.type === TSESLint.Scope.DefinitionType.CatchClause) { if (options.caughtErrors === 'none') { @@ -243,11 +461,16 @@ export default createRule({ def.name.type === AST_NODE_TYPES.Identifier && options.caughtErrorsIgnorePattern?.test(def.name.name) ) { + if (options.reportUsedIgnorePattern && used) { + context.report({ + node: def.name, + messageId: 'usedIgnoredVar', + data: getUsedIgnoredMessageData(variable, 'catch-clause'), + }); + } continue; } - } - - if (def.type === TSESLint.Scope.DefinitionType.Parameter) { + } else if (def.type === TSESLint.Scope.DefinitionType.Parameter) { // if "args" option is "none", skip any parameter if (options.args === 'none') { continue; @@ -257,6 +480,13 @@ export default createRule({ def.name.type === AST_NODE_TYPES.Identifier && options.argsIgnorePattern?.test(def.name.name) ) { + if (options.reportUsedIgnorePattern && used) { + context.report({ + node: def.name, + messageId: 'usedIgnoredVar', + data: getUsedIgnoredMessageData(variable, 'parameter'), + }); + } continue; } // if "args" option is "after-used", skip used variables @@ -273,6 +503,13 @@ export default createRule({ def.name.type === AST_NODE_TYPES.Identifier && options.varsIgnorePattern?.test(def.name.name) ) { + if (options.reportUsedIgnorePattern && used) { + context.report({ + node: def.name, + messageId: 'usedIgnoredVar', + data: getUsedIgnoredMessageData(variable, 'variable'), + }); + } continue; } @@ -286,7 +523,9 @@ export default createRule({ continue; } - unusedVariablesReturn.push(variable); + if (!used) { + unusedVariablesReturn.push(variable); + } } return unusedVariablesReturn; @@ -335,83 +574,24 @@ export default createRule({ // collect 'Program:exit'(programNode): void { - /** - * Generates the message data about the variable being defined and unused, - * including the ignore pattern if configured. - * @param unusedVar eslint-scope variable object. - * @returns The message data to be used with this unused variable. - */ - function getDefinedMessageData( - unusedVar: TSESLint.Scope.Variable, - ): Record { - const defType = unusedVar.defs[0]?.type; - let type; - let pattern; - - if ( - defType === TSESLint.Scope.DefinitionType.CatchClause && - options.caughtErrorsIgnorePattern - ) { - type = 'args'; - pattern = options.caughtErrorsIgnorePattern.toString(); - } else if ( - defType === TSESLint.Scope.DefinitionType.Parameter && - options.argsIgnorePattern - ) { - type = 'args'; - pattern = options.argsIgnorePattern.toString(); - } else if ( - defType !== TSESLint.Scope.DefinitionType.Parameter && - options.varsIgnorePattern - ) { - type = 'vars'; - pattern = options.varsIgnorePattern.toString(); - } - - const additional = type - ? `. Allowed unused ${type} must match ${pattern}` - : ''; - - return { - varName: unusedVar.name, - action: 'defined', - additional, - }; - } - - /** - * Generate the warning message about the variable being - * assigned and unused, including the ignore pattern if configured. - * @param unusedVar eslint-scope variable object. - * @returns The message data to be used with this unused variable. - */ - function getAssignedMessageData( - unusedVar: TSESLint.Scope.Variable, - ): Record { - const def = unusedVar.defs.at(0); - let additional = ''; - - if ( - options.destructuredArrayIgnorePattern && - def?.name.parent.type === AST_NODE_TYPES.ArrayPattern - ) { - additional = `. Allowed unused elements of array destructuring patterns must match ${options.destructuredArrayIgnorePattern.toString()}`; - } else if (options.varsIgnorePattern) { - additional = `. Allowed unused vars must match ${options.varsIgnorePattern.toString()}`; - } - - return { - varName: unusedVar.name, - action: 'assigned a value', - additional, - }; - } - const unusedVars = collectUnusedVariables(); for (const unusedVar of unusedVars) { // Report the first declaration. if (unusedVar.defs.length > 0) { + const usedOnlyAsType = unusedVar.references.some(ref => + referenceContainsTypeQuery(ref.identifier), + ); + + const isImportUsedOnlyAsType = + usedOnlyAsType && + unusedVar.defs.some( + def => def.type === DefinitionType.ImportBinding, + ); + if (isImportUsedOnlyAsType) { + continue; + } + const writeReferences = unusedVar.references.filter( ref => ref.isWrite() && @@ -422,6 +602,8 @@ export default createRule({ ? writeReferences[writeReferences.length - 1].identifier : unusedVar.identifiers[0]; + const messageId = usedOnlyAsType ? 'usedOnlyAsType' : 'unusedVar'; + const { start } = id.loc; const idLength = id.name.length; @@ -435,7 +617,7 @@ export default createRule({ context.report({ loc, - messageId: 'unusedVar', + messageId, data: unusedVar.references.some(ref => ref.isWrite()) ? getAssignedMessageData(unusedVar) : getDefinedMessageData(unusedVar), @@ -626,17 +808,7 @@ namespace Test { } type T = Test.Foo.T; // Error: Namespace 'Test' has no exported member 'Foo'. -*/ - -/* - -###### TODO ###### - -We currently extend base `no-unused-vars` implementation because it's easier and lighter-weight. - -Because of this, there are a few false-negatives which won't get caught. -We could fix these if we fork the base rule; but that's a lot of code (~650 lines) to add in. -I didn't want to do that just yet without some real-world issues, considering these are pretty rare edge-cases. +--- These cases are mishandled because the base rule assumes that each variable has one def, but type-value shadowing creates a variable with two defs diff --git a/packages/eslint-plugin/src/rules/no-use-before-define.ts b/packages/eslint-plugin/src/rules/no-use-before-define.ts index d577773de9ef..f3ef882e1700 100644 --- a/packages/eslint-plugin/src/rules/no-use-before-define.ts +++ b/packages/eslint-plugin/src/rules/no-use-before-define.ts @@ -3,6 +3,7 @@ import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES, TSESLint } from '@typescript-eslint/utils'; import { createRule } from '../util'; +import { referenceContainsTypeQuery } from '../util/referenceContainsTypeQuery'; const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/; @@ -106,24 +107,6 @@ function isNamedExports(reference: TSESLint.Scope.Reference): boolean { ); } -/** - * Recursively checks whether or not a given reference has a type query declaration among it's parents - */ -function referenceContainsTypeQuery(node: TSESTree.Node): boolean { - switch (node.type) { - case AST_NODE_TYPES.TSTypeQuery: - return true; - - case AST_NODE_TYPES.TSQualifiedName: - case AST_NODE_TYPES.Identifier: - return referenceContainsTypeQuery(node.parent); - - default: - // if we find a different node, there's no chance that we're in a TSTypeQuery - return false; - } -} - /** * Checks whether or not a given reference is a type reference. */ diff --git a/packages/eslint-plugin/src/rules/no-useless-constructor.ts b/packages/eslint-plugin/src/rules/no-useless-constructor.ts index dcfc7dd976de..cd91a71c2002 100644 --- a/packages/eslint-plugin/src/rules/no-useless-constructor.ts +++ b/packages/eslint-plugin/src/rules/no-useless-constructor.ts @@ -22,14 +22,7 @@ function checkAccessibility(node: TSESTree.MethodDefinition): boolean { case 'private': return false; case 'public': - if ( - node.parent.type === AST_NODE_TYPES.ClassBody && - ( - node.parent.parent as - | TSESTree.ClassDeclaration - | TSESTree.ClassExpression - ).superClass - ) { + if (node.parent.parent.superClass) { return false; } break; diff --git a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts deleted file mode 100644 index 7b13cd8e2e9a..000000000000 --- a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts +++ /dev/null @@ -1,176 +0,0 @@ -import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as ts from 'typescript'; - -import { - createRule, - getConstrainedTypeAtLocation, - getParserServices, - getStaticStringValue, - isTypeFlagSet, - isUndefinedIdentifier, -} from '../util'; - -type MessageId = 'noUnnecessaryTemplateExpression'; - -export default createRule<[], MessageId>({ - name: 'no-useless-template-literals', - meta: { - fixable: 'code', - type: 'suggestion', - docs: { - description: 'Disallow unnecessary template expressions', - requiresTypeChecking: true, - }, - messages: { - noUnnecessaryTemplateExpression: - 'Template literal expression is unnecessary and can be simplified.', - }, - schema: [], - deprecated: true, - replacedBy: ['@typescript-eslint/no-unnecessary-template-expression'], - }, - defaultOptions: [], - create(context) { - const services = getParserServices(context); - - function isUnderlyingTypeString( - expression: TSESTree.Expression, - ): expression is TSESTree.StringLiteral | TSESTree.Identifier { - const type = getConstrainedTypeAtLocation(services, expression); - - const isString = (t: ts.Type): boolean => { - return isTypeFlagSet(t, ts.TypeFlags.StringLike); - }; - - if (type.isUnion()) { - return type.types.every(isString); - } - - if (type.isIntersection()) { - return type.types.some(isString); - } - - return isString(type); - } - - function isLiteral(expression: TSESTree.Expression): boolean { - return expression.type === AST_NODE_TYPES.Literal; - } - - function isTemplateLiteral(expression: TSESTree.Expression): boolean { - return expression.type === AST_NODE_TYPES.TemplateLiteral; - } - - function isInfinityIdentifier(expression: TSESTree.Expression): boolean { - return ( - expression.type === AST_NODE_TYPES.Identifier && - expression.name === 'Infinity' - ); - } - - function isNaNIdentifier(expression: TSESTree.Expression): boolean { - return ( - expression.type === AST_NODE_TYPES.Identifier && - expression.name === 'NaN' - ); - } - - return { - TemplateLiteral(node: TSESTree.TemplateLiteral): void { - if (node.parent.type === AST_NODE_TYPES.TaggedTemplateExpression) { - return; - } - - const hasSingleStringVariable = - node.quasis.length === 2 && - node.quasis[0].value.raw === '' && - node.quasis[1].value.raw === '' && - node.expressions.length === 1 && - isUnderlyingTypeString(node.expressions[0]); - - if (hasSingleStringVariable) { - context.report({ - node: node.expressions[0], - messageId: 'noUnnecessaryTemplateExpression', - fix(fixer): TSESLint.RuleFix[] { - const [prevQuasi, nextQuasi] = node.quasis; - - // Remove the quasis and backticks. - return [ - fixer.removeRange([ - prevQuasi.range[1] - 3, - node.expressions[0].range[0], - ]), - - fixer.removeRange([ - node.expressions[0].range[1], - nextQuasi.range[0] + 2, - ]), - ]; - }, - }); - - return; - } - - const fixableExpressions = node.expressions.filter( - expression => - isLiteral(expression) || - isTemplateLiteral(expression) || - isUndefinedIdentifier(expression) || - isInfinityIdentifier(expression) || - isNaNIdentifier(expression), - ); - - fixableExpressions.forEach(expression => { - context.report({ - node: expression, - messageId: 'noUnnecessaryTemplateExpression', - fix(fixer): TSESLint.RuleFix[] { - const index = node.expressions.indexOf(expression); - const prevQuasi = node.quasis[index]; - const nextQuasi = node.quasis[index + 1]; - - // Remove the quasis' parts that are related to the current expression. - const fixes = [ - fixer.removeRange([ - prevQuasi.range[1] - 2, - expression.range[0], - ]), - - fixer.removeRange([ - expression.range[1], - nextQuasi.range[0] + 1, - ]), - ]; - - const stringValue = getStaticStringValue(expression); - - if (stringValue != null) { - const escapedValue = stringValue.replace(/([`$\\])/g, '\\$1'); - - fixes.push(fixer.replaceText(expression, escapedValue)); - } else if (isTemplateLiteral(expression)) { - // Note that some template literals get handled in the previous branch too. - // Remove the beginning and trailing backtick characters. - fixes.push( - fixer.removeRange([ - expression.range[0], - expression.range[0] + 1, - ]), - fixer.removeRange([ - expression.range[1] - 1, - expression.range[1], - ]), - ); - } - - return fixes; - }, - }); - }); - }, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/no-var-requires.ts b/packages/eslint-plugin/src/rules/no-var-requires.ts index 9bb9fb7c1921..b7c38620168a 100644 --- a/packages/eslint-plugin/src/rules/no-var-requires.ts +++ b/packages/eslint-plugin/src/rules/no-var-requires.ts @@ -13,10 +13,11 @@ type MessageIds = 'noVarReqs'; export default createRule({ name: 'no-var-requires', meta: { + deprecated: true, + replacedBy: ['@typescript-eslint/no-require-imports'], type: 'problem', docs: { description: 'Disallow `require` statements except in import statements', - recommended: 'recommended', }, messages: { noVarReqs: 'Require statement not part of import statement.', diff --git a/packages/eslint-plugin/src/rules/no-wrapper-object-types.ts b/packages/eslint-plugin/src/rules/no-wrapper-object-types.ts index 699a950b59cc..f51b6c8564b5 100644 --- a/packages/eslint-plugin/src/rules/no-wrapper-object-types.ts +++ b/packages/eslint-plugin/src/rules/no-wrapper-object-types.ts @@ -20,6 +20,7 @@ export default createRule({ type: 'problem', docs: { description: 'Disallow using confusing built-in primitive class wrappers', + recommended: 'recommended', }, fixable: 'code', messages: { diff --git a/packages/eslint-plugin/src/rules/object-curly-spacing.ts b/packages/eslint-plugin/src/rules/object-curly-spacing.ts deleted file mode 100644 index 4edd5a688f99..000000000000 --- a/packages/eslint-plugin/src/rules/object-curly-spacing.ts +++ /dev/null @@ -1,293 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; - -import type { - InferMessageIdsTypeFromRule, - InferOptionsTypeFromRule, -} from '../util'; -import { - createRule, - isClosingBraceToken, - isClosingBracketToken, - isTokenOnSameLine, -} from '../util'; -import { getESLintCoreRule } from '../util/getESLintCoreRule'; - -const baseRule = getESLintCoreRule('object-curly-spacing'); - -export type Options = InferOptionsTypeFromRule; -export type MessageIds = InferMessageIdsTypeFromRule; - -export default createRule({ - name: 'object-curly-spacing', - // eslint-disable-next-line eslint-plugin/prefer-message-ids,eslint-plugin/require-meta-type,eslint-plugin/require-meta-schema,eslint-plugin/require-meta-fixable -- all in base rule - https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/issues/274 - meta: { - ...baseRule.meta, - deprecated: true, - docs: { - description: 'Enforce consistent spacing inside braces', - extendsBaseRule: true, - }, - replacedBy: ['@stylistic/ts/object-curly-spacing'], - }, - defaultOptions: ['never'], - create(context) { - // eslint-disable-next-line no-restricted-syntax -- Use raw options for extended rules. - const [firstOption, secondOption] = context.options; - const spaced = firstOption === 'always'; - - /** - * Determines whether an option is set, relative to the spacing option. - * If spaced is "always", then check whether option is set to false. - * If spaced is "never", then check whether option is set to true. - * @param option The option to exclude. - * @returns Whether or not the property is excluded. - */ - function isOptionSet( - option: 'arraysInObjects' | 'objectsInObjects', - ): boolean { - return secondOption ? secondOption[option] === !spaced : false; - } - - const options = { - spaced, - arraysInObjectsException: isOptionSet('arraysInObjects'), - objectsInObjectsException: isOptionSet('objectsInObjects'), - }; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Reports that there shouldn't be a space after the first token - * @param node The node to report in the event of an error. - * @param token The token to use for the report. - */ - function reportNoBeginningSpace( - node: TSESTree.TSMappedType | TSESTree.TSTypeLiteral, - token: TSESTree.Token, - ): void { - const nextToken = context.sourceCode.getTokenAfter(token, { - includeComments: true, - })!; - - context.report({ - node, - loc: { start: token.loc.end, end: nextToken.loc.start }, - messageId: 'unexpectedSpaceAfter', - data: { - token: token.value, - }, - fix(fixer) { - return fixer.removeRange([token.range[1], nextToken.range[0]]); - }, - }); - } - - /** - * Reports that there shouldn't be a space before the last token - * @param node The node to report in the event of an error. - * @param token The token to use for the report. - */ - function reportNoEndingSpace( - node: TSESTree.TSMappedType | TSESTree.TSTypeLiteral, - token: TSESTree.Token, - ): void { - const previousToken = context.sourceCode.getTokenBefore(token, { - includeComments: true, - })!; - - context.report({ - node, - loc: { start: previousToken.loc.end, end: token.loc.start }, - messageId: 'unexpectedSpaceBefore', - data: { - token: token.value, - }, - fix(fixer) { - return fixer.removeRange([previousToken.range[1], token.range[0]]); - }, - }); - } - - /** - * Reports that there should be a space after the first token - * @param node The node to report in the event of an error. - * @param token The token to use for the report. - */ - function reportRequiredBeginningSpace( - node: TSESTree.TSMappedType | TSESTree.TSTypeLiteral, - token: TSESTree.Token, - ): void { - context.report({ - node, - loc: token.loc, - messageId: 'requireSpaceAfter', - data: { - token: token.value, - }, - fix(fixer) { - return fixer.insertTextAfter(token, ' '); - }, - }); - } - - /** - * Reports that there should be a space before the last token - * @param node The node to report in the event of an error. - * @param token The token to use for the report. - */ - function reportRequiredEndingSpace( - node: TSESTree.TSMappedType | TSESTree.TSTypeLiteral, - token: TSESTree.Token, - ): void { - context.report({ - node, - loc: token.loc, - messageId: 'requireSpaceBefore', - data: { - token: token.value, - }, - fix(fixer) { - return fixer.insertTextBefore(token, ' '); - }, - }); - } - - /** - * Determines if spacing in curly braces is valid. - * @param node The AST node to check. - * @param first The first token to check (should be the opening brace) - * @param second The second token to check (should be first after the opening brace) - * @param penultimate The penultimate token to check (should be last before closing brace) - * @param last The last token to check (should be closing brace) - */ - function validateBraceSpacing( - node: TSESTree.TSMappedType | TSESTree.TSTypeLiteral, - first: TSESTree.Token, - second: TSESTree.Token, - penultimate: TSESTree.Token, - last: TSESTree.Token, - ): void { - if (isTokenOnSameLine(first, second)) { - const firstSpaced = context.sourceCode.isSpaceBetween(first, second); - const secondType = context.sourceCode.getNodeByRangeIndex( - second.range[0], - )!.type; - - const openingCurlyBraceMustBeSpaced = - options.arraysInObjectsException && - [ - AST_NODE_TYPES.TSMappedType, - AST_NODE_TYPES.TSIndexSignature, - ].includes(secondType) - ? !options.spaced - : options.spaced; - - if (openingCurlyBraceMustBeSpaced && !firstSpaced) { - reportRequiredBeginningSpace(node, first); - } - if ( - !openingCurlyBraceMustBeSpaced && - firstSpaced && - second.type !== AST_TOKEN_TYPES.Line - ) { - reportNoBeginningSpace(node, first); - } - } - - if (isTokenOnSameLine(penultimate, last)) { - const shouldCheckPenultimate = - (options.arraysInObjectsException && - isClosingBracketToken(penultimate)) || - (options.objectsInObjectsException && - isClosingBraceToken(penultimate)); - const penultimateType = shouldCheckPenultimate - ? context.sourceCode.getNodeByRangeIndex(penultimate.range[0])!.type - : undefined; - - const closingCurlyBraceMustBeSpaced = - (options.arraysInObjectsException && - penultimateType === AST_NODE_TYPES.TSTupleType) || - (options.objectsInObjectsException && - penultimateType !== undefined && - [ - AST_NODE_TYPES.TSMappedType, - AST_NODE_TYPES.TSTypeLiteral, - ].includes(penultimateType)) - ? !options.spaced - : options.spaced; - - const lastSpaced = context.sourceCode.isSpaceBetween(penultimate, last); - - if (closingCurlyBraceMustBeSpaced && !lastSpaced) { - reportRequiredEndingSpace(node, last); - } - if (!closingCurlyBraceMustBeSpaced && lastSpaced) { - reportNoEndingSpace(node, last); - } - } - } - - /** - * Gets '}' token of an object node. - * - * Because the last token of object patterns might be a type annotation, - * this traverses tokens preceded by the last property, then returns the - * first '}' token. - * @param node The node to get. This node is an - * ObjectExpression or an ObjectPattern. And this node has one or - * more properties. - * @returns '}' token. - */ - function getClosingBraceOfObject( - node: TSESTree.TSTypeLiteral, - ): TSESTree.Token | null { - const lastProperty = node.members[node.members.length - 1]; - - return context.sourceCode.getTokenAfter( - lastProperty, - isClosingBraceToken, - ); - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - const rules = baseRule.create(context); - return { - ...rules, - TSMappedType(node: TSESTree.TSMappedType): void { - const first = context.sourceCode.getFirstToken(node)!; - const last = context.sourceCode.getLastToken(node)!; - const second = context.sourceCode.getTokenAfter(first, { - includeComments: true, - })!; - const penultimate = context.sourceCode.getTokenBefore(last, { - includeComments: true, - })!; - - validateBraceSpacing(node, first, second, penultimate, last); - }, - TSTypeLiteral(node: TSESTree.TSTypeLiteral): void { - if (node.members.length === 0) { - return; - } - - const first = context.sourceCode.getFirstToken(node)!; - const last = getClosingBraceOfObject(node)!; - const second = context.sourceCode.getTokenAfter(first, { - includeComments: true, - })!; - const penultimate = context.sourceCode.getTokenBefore(last, { - includeComments: true, - })!; - - validateBraceSpacing(node, first, second, penultimate, last); - }, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/only-throw-error.ts b/packages/eslint-plugin/src/rules/only-throw-error.ts index 62ce268fc700..45a6dac0942d 100644 --- a/packages/eslint-plugin/src/rules/only-throw-error.ts +++ b/packages/eslint-plugin/src/rules/only-throw-error.ts @@ -25,7 +25,7 @@ export default createRule({ type: 'problem', docs: { description: 'Disallow throwing non-`Error` values as exceptions', - recommended: 'strict', + recommended: 'recommended', extendsBaseRule: 'no-throw-literal', requiresTypeChecking: true, }, diff --git a/packages/eslint-plugin/src/rules/padding-line-between-statements.ts b/packages/eslint-plugin/src/rules/padding-line-between-statements.ts deleted file mode 100644 index ecd87a06a643..000000000000 --- a/packages/eslint-plugin/src/rules/padding-line-between-statements.ts +++ /dev/null @@ -1,825 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion, eslint-plugin/no-property-in-node */ -import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; - -import { - createRule, - isClosingBraceToken, - isFunction, - isNotSemicolonToken, - isParenthesized, - isSemicolonToken, - isTokenOnSameLine, -} from '../util'; - -/** - * This rule is a replica of padding-line-between-statements. - * - * Ideally we would want to extend the rule support typescript specific support. - * But since not all the state is exposed by the eslint and eslint has frozen stylistic rules, - * (see - https://eslint.org/blog/2020/05/changes-to-rules-policies for details.) - * we are forced to re-implement the rule here. - * - * We have tried to keep the implementation as close as possible to the eslint implementation, to make - * patching easier for future contributors. - * - * Reference rule - https://github.com/eslint/eslint/blob/main/lib/rules/padding-line-between-statements.js - */ - -type NodeTest = ( - node: TSESTree.Node, - sourceCode: TSESLint.SourceCode, -) => boolean; - -interface NodeTestObject { - test: NodeTest; -} - -interface PaddingOption { - blankLine: keyof typeof PaddingTypes; - prev: string[] | string; - next: string[] | string; -} - -type MessageIds = 'expectedBlankLine' | 'unexpectedBlankLine'; -type Options = PaddingOption[]; - -const LT = `[${Array.from( - new Set(['\r\n', '\r', '\n', '\u2028', '\u2029']), -).join('')}]`; -const PADDING_LINE_SEQUENCE = new RegExp( - String.raw`^(\s*?${LT})\s*${LT}(\s*;?)$`, - 'u', -); - -/** - * Creates tester which check if a node starts with specific keyword with the - * appropriate AST_NODE_TYPES. - * @param keyword The keyword to test. - * @returns the created tester. - * @private - */ -function newKeywordTester( - type: AST_NODE_TYPES | AST_NODE_TYPES[], - keyword: string, -): NodeTestObject { - return { - test(node, sourceCode): boolean { - const isSameKeyword = sourceCode.getFirstToken(node)?.value === keyword; - const isSameType = Array.isArray(type) - ? type.some(val => val === node.type) - : type === node.type; - - return isSameKeyword && isSameType; - }, - }; -} - -/** - * Creates tester which check if a node starts with specific keyword and spans a single line. - * @param keyword The keyword to test. - * @returns the created tester. - * @private - */ -function newSinglelineKeywordTester(keyword: string): NodeTestObject { - return { - test(node, sourceCode): boolean { - return ( - node.loc.start.line === node.loc.end.line && - sourceCode.getFirstToken(node)!.value === keyword - ); - }, - }; -} - -/** - * Creates tester which check if a node starts with specific keyword and spans multiple lines. - * @param keyword The keyword to test. - * @returns the created tester. - * @private - */ -function newMultilineKeywordTester(keyword: string): NodeTestObject { - return { - test(node, sourceCode): boolean { - return ( - node.loc.start.line !== node.loc.end.line && - sourceCode.getFirstToken(node)!.value === keyword - ); - }, - }; -} - -/** - * Creates tester which check if a node is specific type. - * @param type The node type to test. - * @returns the created tester. - * @private - */ -function newNodeTypeTester(type: AST_NODE_TYPES): NodeTestObject { - return { - test: (node): boolean => node.type === type, - }; -} - -/** - * Skips a chain expression node - * @param node The node to test - * @returns A non-chain expression - * @private - */ -function skipChainExpression(node: TSESTree.Node): TSESTree.Node { - return node.type === AST_NODE_TYPES.ChainExpression ? node.expression : node; -} - -/** - * Checks the given node is an expression statement of IIFE. - * @param node The node to check. - * @returns `true` if the node is an expression statement of IIFE. - * @private - */ -function isIIFEStatement(node: TSESTree.Node): boolean { - if (node.type === AST_NODE_TYPES.ExpressionStatement) { - let expression = skipChainExpression(node.expression); - if (expression.type === AST_NODE_TYPES.UnaryExpression) { - expression = skipChainExpression(expression.argument); - } - if (expression.type === AST_NODE_TYPES.CallExpression) { - let node: TSESTree.Node = expression.callee; - while (node.type === AST_NODE_TYPES.SequenceExpression) { - node = node.expressions[node.expressions.length - 1]; - } - return isFunction(node); - } - } - return false; -} - -/** - * Checks the given node is a CommonJS require statement - * @param node The node to check. - * @returns `true` if the node is a CommonJS require statement. - * @private - */ -function isCJSRequire(node: TSESTree.Node): boolean { - if (node.type === AST_NODE_TYPES.VariableDeclaration) { - const declaration = node.declarations.at(0); - if (declaration?.init) { - let call = declaration.init; - while (call.type === AST_NODE_TYPES.MemberExpression) { - call = call.object; - } - if ( - call.type === AST_NODE_TYPES.CallExpression && - call.callee.type === AST_NODE_TYPES.Identifier - ) { - return call.callee.name === 'require'; - } - } - } - return false; -} - -/** - * Checks whether the given node is a block-like statement. - * This checks the last token of the node is the closing brace of a block. - * @param node The node to check. - * @param sourceCode The source code to get tokens. - * @returns `true` if the node is a block-like statement. - * @private - */ -function isBlockLikeStatement( - node: TSESTree.Node, - sourceCode: TSESLint.SourceCode, -): boolean { - // do-while with a block is a block-like statement. - if ( - node.type === AST_NODE_TYPES.DoWhileStatement && - node.body.type === AST_NODE_TYPES.BlockStatement - ) { - return true; - } - - /** - * IIFE is a block-like statement specially from - * JSCS#disallowPaddingNewLinesAfterBlocks. - */ - if (isIIFEStatement(node)) { - return true; - } - - // Checks the last token is a closing brace of blocks. - const lastToken = sourceCode.getLastToken(node, isNotSemicolonToken); - const belongingNode = - lastToken && isClosingBraceToken(lastToken) - ? sourceCode.getNodeByRangeIndex(lastToken.range[0]) - : null; - - return ( - !!belongingNode && - (belongingNode.type === AST_NODE_TYPES.BlockStatement || - belongingNode.type === AST_NODE_TYPES.SwitchStatement) - ); -} - -/** - * Check whether the given node is a directive or not. - * @param node The node to check. - * @param sourceCode The source code object to get tokens. - * @returns `true` if the node is a directive. - */ -function isDirective( - node: TSESTree.Node, - sourceCode: TSESLint.SourceCode, -): boolean { - return ( - node.type === AST_NODE_TYPES.ExpressionStatement && - (node.parent.type === AST_NODE_TYPES.Program || - (node.parent.type === AST_NODE_TYPES.BlockStatement && - isFunction(node.parent.parent))) && - node.expression.type === AST_NODE_TYPES.Literal && - typeof node.expression.value === 'string' && - !isParenthesized(node.expression, sourceCode) - ); -} - -/** - * Check whether the given node is a part of directive prologue or not. - * @param node The node to check. - * @param sourceCode The source code object to get tokens. - * @returns `true` if the node is a part of directive prologue. - */ -function isDirectivePrologue( - node: TSESTree.Node, - sourceCode: TSESLint.SourceCode, -): boolean { - if ( - isDirective(node, sourceCode) && - node.parent && - 'body' in node.parent && - Array.isArray(node.parent.body) - ) { - for (const sibling of node.parent.body) { - if (sibling === node) { - break; - } - if (!isDirective(sibling, sourceCode)) { - return false; - } - } - return true; - } - return false; -} - -/** - * Checks the given node is a CommonJS export statement - * @param node The node to check. - * @returns `true` if the node is a CommonJS export statement. - * @private - */ -function isCJSExport(node: TSESTree.Node): boolean { - if (node.type === AST_NODE_TYPES.ExpressionStatement) { - const expression = node.expression; - if (expression.type === AST_NODE_TYPES.AssignmentExpression) { - let left = expression.left; - if (left.type === AST_NODE_TYPES.MemberExpression) { - while (left.object.type === AST_NODE_TYPES.MemberExpression) { - left = left.object; - } - return ( - left.object.type === AST_NODE_TYPES.Identifier && - (left.object.name === 'exports' || - (left.object.name === 'module' && - left.property.type === AST_NODE_TYPES.Identifier && - left.property.name === 'exports')) - ); - } - } - } - return false; -} - -/** - * Check whether the given node is an expression - * @param node The node to check. - * @param sourceCode The source code object to get tokens. - * @returns `true` if the node is an expression - */ -function isExpression( - node: TSESTree.Node, - sourceCode: TSESLint.SourceCode, -): boolean { - return ( - node.type === AST_NODE_TYPES.ExpressionStatement && - !isDirectivePrologue(node, sourceCode) - ); -} - -/** - * Gets the actual last token. - * - * If a semicolon is semicolon-less style's semicolon, this ignores it. - * For example: - * - * foo() - * ;[1, 2, 3].forEach(bar) - * @param node The node to get. - * @param sourceCode The source code to get tokens. - * @private - */ -function getActualLastToken( - node: TSESTree.Node, - sourceCode: TSESLint.SourceCode, -): TSESTree.Token | null { - const semiToken = sourceCode.getLastToken(node)!; - const prevToken = sourceCode.getTokenBefore(semiToken); - const nextToken = sourceCode.getTokenAfter(semiToken); - const isSemicolonLessStyle = - prevToken && - nextToken && - prevToken.range[0] >= node.range[0] && - isSemicolonToken(semiToken) && - semiToken.loc.start.line !== prevToken.loc.end.line && - semiToken.loc.end.line === nextToken.loc.start.line; - - return isSemicolonLessStyle ? prevToken : semiToken; -} - -/** - * This returns the concatenation of the first 2 captured strings. - * @param _ Unused. Whole matched string. - * @param trailingSpaces The trailing spaces of the first line. - * @param indentSpaces The indentation spaces of the last line. - * @returns The concatenation of trailingSpaces and indentSpaces. - * @private - */ -function replacerToRemovePaddingLines( - _: string, - trailingSpaces: string, - indentSpaces: string, -): string { - return trailingSpaces + indentSpaces; -} - -/** - * Check and report statements for `any` configuration. - * It does nothing. - * - * @private - */ -function verifyForAny(): void { - // Empty -} - -/** - * Check and report statements for `never` configuration. - * This autofix removes blank lines between the given 2 statements. - * However, if comments exist between 2 blank lines, it does not remove those - * blank lines automatically. - * @param context The rule context to report. - * @param _ Unused. The previous node to check. - * @param nextNode The next node to check. - * @param paddingLines The array of token pairs that blank - * lines exist between the pair. - * - * @private - */ -function verifyForNever( - context: TSESLint.RuleContext, - _: TSESTree.Node, - nextNode: TSESTree.Node, - paddingLines: [TSESTree.Token, TSESTree.Token][], -): void { - if (paddingLines.length === 0) { - return; - } - - context.report({ - node: nextNode, - messageId: 'unexpectedBlankLine', - fix(fixer) { - if (paddingLines.length >= 2) { - return null; - } - - const prevToken = paddingLines[0][0]; - const nextToken = paddingLines[0][1]; - const start = prevToken.range[1]; - const end = nextToken.range[0]; - const text = context.sourceCode.text - .slice(start, end) - .replace(PADDING_LINE_SEQUENCE, replacerToRemovePaddingLines); - - return fixer.replaceTextRange([start, end], text); - }, - }); -} - -/** - * Check and report statements for `always` configuration. - * This autofix inserts a blank line between the given 2 statements. - * If the `prevNode` has trailing comments, it inserts a blank line after the - * trailing comments. - * @param context The rule context to report. - * @param prevNode The previous node to check. - * @param nextNode The next node to check. - * @param paddingLines The array of token pairs that blank - * lines exist between the pair. - * - * @private - */ -function verifyForAlways( - context: TSESLint.RuleContext, - prevNode: TSESTree.Node, - nextNode: TSESTree.Node, - paddingLines: [TSESTree.Token, TSESTree.Token][], -): void { - if (paddingLines.length > 0) { - return; - } - - context.report({ - node: nextNode, - messageId: 'expectedBlankLine', - fix(fixer) { - let prevToken = getActualLastToken(prevNode, context.sourceCode)!; - const nextToken = - context.sourceCode.getFirstTokenBetween(prevToken, nextNode, { - includeComments: true, - - /** - * Skip the trailing comments of the previous node. - * This inserts a blank line after the last trailing comment. - * - * For example: - * - * foo(); // trailing comment. - * // comment. - * bar(); - * - * Get fixed to: - * - * foo(); // trailing comment. - * - * // comment. - * bar(); - * @param token The token to check. - * @returns `true` if the token is not a trailing comment. - * @private - */ - filter(token) { - if (isTokenOnSameLine(prevToken, token)) { - prevToken = token; - return false; - } - return true; - }, - }) ?? nextNode; - const insertText = isTokenOnSameLine(prevToken, nextToken) - ? '\n\n' - : '\n'; - - return fixer.insertTextAfter(prevToken, insertText); - }, - }); -} - -/** - * Types of blank lines. - * `any`, `never`, and `always` are defined. - * Those have `verify` method to check and report statements. - * @private - */ -const PaddingTypes = { - any: { verify: verifyForAny }, - never: { verify: verifyForNever }, - always: { verify: verifyForAlways }, -}; - -/** - * Types of statements. - * Those have `test` method to check it matches to the given statement. - * @private - */ -const StatementTypes: Record = { - '*': { test: (): boolean => true }, - 'block-like': { test: isBlockLikeStatement }, - exports: { test: isCJSExport }, - require: { test: isCJSRequire }, - directive: { test: isDirectivePrologue }, - expression: { test: isExpression }, - iife: { test: isIIFEStatement }, - - 'multiline-block-like': { - test: (node, sourceCode) => - node.loc.start.line !== node.loc.end.line && - isBlockLikeStatement(node, sourceCode), - }, - 'multiline-expression': { - test: (node, sourceCode) => - node.loc.start.line !== node.loc.end.line && - node.type === AST_NODE_TYPES.ExpressionStatement && - !isDirectivePrologue(node, sourceCode), - }, - - 'multiline-const': newMultilineKeywordTester('const'), - 'multiline-let': newMultilineKeywordTester('let'), - 'multiline-var': newMultilineKeywordTester('var'), - 'singleline-const': newSinglelineKeywordTester('const'), - 'singleline-let': newSinglelineKeywordTester('let'), - 'singleline-var': newSinglelineKeywordTester('var'), - - block: newNodeTypeTester(AST_NODE_TYPES.BlockStatement), - empty: newNodeTypeTester(AST_NODE_TYPES.EmptyStatement), - function: newNodeTypeTester(AST_NODE_TYPES.FunctionDeclaration), - - break: newKeywordTester(AST_NODE_TYPES.BreakStatement, 'break'), - case: newKeywordTester(AST_NODE_TYPES.SwitchCase, 'case'), - class: newKeywordTester(AST_NODE_TYPES.ClassDeclaration, 'class'), - const: newKeywordTester(AST_NODE_TYPES.VariableDeclaration, 'const'), - continue: newKeywordTester(AST_NODE_TYPES.ContinueStatement, 'continue'), - debugger: newKeywordTester(AST_NODE_TYPES.DebuggerStatement, 'debugger'), - default: newKeywordTester( - [AST_NODE_TYPES.SwitchCase, AST_NODE_TYPES.ExportDefaultDeclaration], - 'default', - ), - do: newKeywordTester(AST_NODE_TYPES.DoWhileStatement, 'do'), - export: newKeywordTester( - [ - AST_NODE_TYPES.ExportDefaultDeclaration, - AST_NODE_TYPES.ExportNamedDeclaration, - ], - 'export', - ), - for: newKeywordTester( - [ - AST_NODE_TYPES.ForStatement, - AST_NODE_TYPES.ForInStatement, - AST_NODE_TYPES.ForOfStatement, - ], - 'for', - ), - if: newKeywordTester(AST_NODE_TYPES.IfStatement, 'if'), - import: newKeywordTester(AST_NODE_TYPES.ImportDeclaration, 'import'), - let: newKeywordTester(AST_NODE_TYPES.VariableDeclaration, 'let'), - return: newKeywordTester(AST_NODE_TYPES.ReturnStatement, 'return'), - switch: newKeywordTester(AST_NODE_TYPES.SwitchStatement, 'switch'), - throw: newKeywordTester(AST_NODE_TYPES.ThrowStatement, 'throw'), - try: newKeywordTester(AST_NODE_TYPES.TryStatement, 'try'), - var: newKeywordTester(AST_NODE_TYPES.VariableDeclaration, 'var'), - while: newKeywordTester( - [AST_NODE_TYPES.WhileStatement, AST_NODE_TYPES.DoWhileStatement], - 'while', - ), - with: newKeywordTester(AST_NODE_TYPES.WithStatement, 'with'), - - // Additional Typescript constructs - interface: newKeywordTester( - AST_NODE_TYPES.TSInterfaceDeclaration, - 'interface', - ), - type: newKeywordTester(AST_NODE_TYPES.TSTypeAliasDeclaration, 'type'), -}; - -//------------------------------------------------------------------------------ -// Rule Definition -//------------------------------------------------------------------------------ - -export default createRule({ - name: 'padding-line-between-statements', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/padding-line-between-statements'], - type: 'layout', - docs: { - description: 'Require or disallow padding lines between statements', - extendsBaseRule: true, - }, - fixable: 'whitespace', - hasSuggestions: false, - // This is intentionally an array schema as you can pass 0..n config objects - schema: { - $defs: { - paddingType: { - type: 'string', - enum: Object.keys(PaddingTypes), - }, - statementType: { - anyOf: [ - { - type: 'string', - enum: Object.keys(StatementTypes), - }, - { - type: 'array', - items: { - type: 'string', - enum: Object.keys(StatementTypes), - }, - minItems: 1, - uniqueItems: true, - additionalItems: false, - }, - ], - }, - }, - type: 'array', - additionalItems: false, - items: { - type: 'object', - properties: { - blankLine: { $ref: '#/$defs/paddingType' }, - prev: { $ref: '#/$defs/statementType' }, - next: { $ref: '#/$defs/statementType' }, - }, - additionalProperties: false, - required: ['blankLine', 'prev', 'next'], - }, - }, - messages: { - unexpectedBlankLine: 'Unexpected blank line before this statement.', - expectedBlankLine: 'Expected blank line before this statement.', - }, - }, - defaultOptions: [], - create(context) { - // eslint-disable-next-line no-restricted-syntax -- We need all raw options. - const configureList = context.options; - - type Scope = { - upper: Scope; - prevNode: TSESTree.Node | null; - } | null; - - let scopeInfo: Scope = null; - - /** - * Processes to enter to new scope. - * This manages the current previous statement. - * - * @private - */ - function enterScope(): void { - scopeInfo = { - upper: scopeInfo, - prevNode: null, - }; - } - - /** - * Processes to exit from the current scope. - * - * @private - */ - function exitScope(): void { - if (scopeInfo) { - scopeInfo = scopeInfo.upper; - } - } - - /** - * Checks whether the given node matches the given type. - * @param node The statement node to check. - * @param type The statement type to check. - * @returns `true` if the statement node matched the type. - * @private - */ - function match(node: TSESTree.Node, type: string[] | string): boolean { - let innerStatementNode = node; - - while (innerStatementNode.type === AST_NODE_TYPES.LabeledStatement) { - innerStatementNode = innerStatementNode.body; - } - - if (Array.isArray(type)) { - return type.some(match.bind(null, innerStatementNode)); - } - - return StatementTypes[type].test(innerStatementNode, context.sourceCode); - } - - /** - * Finds the last matched configure from configureList. - * @paramprevNode The previous statement to match. - * @paramnextNode The current statement to match. - * @returns The tester of the last matched configure. - * @private - */ - function getPaddingType( - prevNode: TSESTree.Node, - nextNode: TSESTree.Node, - ): (typeof PaddingTypes)[keyof typeof PaddingTypes] { - for (let i = configureList.length - 1; i >= 0; --i) { - const configure = configureList[i]; - if ( - match(prevNode, configure.prev) && - match(nextNode, configure.next) - ) { - return PaddingTypes[configure.blankLine]; - } - } - return PaddingTypes.any; - } - - /** - * Gets padding line sequences between the given 2 statements. - * Comments are separators of the padding line sequences. - * @paramprevNode The previous statement to count. - * @paramnextNode The current statement to count. - * @returns The array of token pairs. - * @private - */ - function getPaddingLineSequences( - prevNode: TSESTree.Node, - nextNode: TSESTree.Node, - ): [TSESTree.Token, TSESTree.Token][] { - const pairs: [TSESTree.Token, TSESTree.Token][] = []; - let prevToken: TSESTree.Token = getActualLastToken( - prevNode, - context.sourceCode, - )!; - - if (nextNode.loc.start.line - prevToken.loc.end.line >= 2) { - do { - const token: TSESTree.Token = context.sourceCode.getTokenAfter( - prevToken, - { - includeComments: true, - }, - )!; - - if (token.loc.start.line - prevToken.loc.end.line >= 2) { - pairs.push([prevToken, token]); - } - prevToken = token; - } while (prevToken.range[0] < nextNode.range[0]); - } - - return pairs; - } - - /** - * Verify padding lines between the given node and the previous node. - * @param node The node to verify. - * - * @private - */ - function verify(node: TSESTree.Node): void { - if ( - !node.parent || - ![ - AST_NODE_TYPES.BlockStatement, - AST_NODE_TYPES.Program, - AST_NODE_TYPES.SwitchCase, - AST_NODE_TYPES.SwitchStatement, - AST_NODE_TYPES.TSModuleBlock, - ].includes(node.parent.type) - ) { - return; - } - - // Save this node as the current previous statement. - const prevNode = scopeInfo!.prevNode; - - // Verify. - if (prevNode) { - const type = getPaddingType(prevNode, node); - const paddingLines = getPaddingLineSequences(prevNode, node); - - type.verify(context, prevNode, node, paddingLines); - } - - scopeInfo!.prevNode = node; - } - - /** - * Verify padding lines between the given node and the previous node. - * Then process to enter to new scope. - * @param node The node to verify. - * - * @private - */ - function verifyThenEnterScope(node: TSESTree.Node): void { - verify(node); - enterScope(); - } - - return { - Program: enterScope, - BlockStatement: enterScope, - SwitchStatement: enterScope, - TSModuleBlock: enterScope, - 'Program:exit': exitScope, - 'BlockStatement:exit': exitScope, - 'SwitchStatement:exit': exitScope, - 'TSModuleBlock:exit': exitScope, - - ':statement': verify, - - SwitchCase: verifyThenEnterScope, - TSDeclareFunction: verifyThenEnterScope, - 'SwitchCase:exit': exitScope, - 'TSDeclareFunction:exit': exitScope, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/prefer-enum-initializers.ts b/packages/eslint-plugin/src/rules/prefer-enum-initializers.ts index 27572b4f8f7f..00dae8cccb96 100644 --- a/packages/eslint-plugin/src/rules/prefer-enum-initializers.ts +++ b/packages/eslint-plugin/src/rules/prefer-enum-initializers.ts @@ -24,7 +24,7 @@ export default createRule<[], MessageIds>({ defaultOptions: [], create(context) { function TSEnumDeclaration(node: TSESTree.TSEnumDeclaration): void { - const { members } = node; + const { members } = node.body; members.forEach((member, index) => { if (member.initializer == null) { diff --git a/packages/eslint-plugin/src/rules/prefer-find.ts b/packages/eslint-plugin/src/rules/prefer-find.ts index c9ab83014286..587cd5d0f03d 100644 --- a/packages/eslint-plugin/src/rules/prefer-find.ts +++ b/packages/eslint-plugin/src/rules/prefer-find.ts @@ -18,6 +18,7 @@ export default createRule({ docs: { description: 'Enforce the use of Array.prototype.find() over Array.prototype.filter() followed by [0] when looking for a single result', + recommended: 'stylistic', requiresTypeChecking: true, }, messages: { diff --git a/packages/eslint-plugin/src/rules/prefer-includes.ts b/packages/eslint-plugin/src/rules/prefer-includes.ts index 92e421bf1d39..b5e57fa8d89d 100644 --- a/packages/eslint-plugin/src/rules/prefer-includes.ts +++ b/packages/eslint-plugin/src/rules/prefer-includes.ts @@ -18,7 +18,7 @@ export default createRule({ type: 'suggestion', docs: { description: 'Enforce `includes` method over `indexOf` method', - recommended: 'strict', + recommended: 'stylistic', requiresTypeChecking: true, }, fixable: 'code', diff --git a/packages/eslint-plugin/src/rules/prefer-literal-enum-member.ts b/packages/eslint-plugin/src/rules/prefer-literal-enum-member.ts index 4567e896d501..efc3eacdd518 100644 --- a/packages/eslint-plugin/src/rules/prefer-literal-enum-member.ts +++ b/packages/eslint-plugin/src/rules/prefer-literal-enum-member.ts @@ -41,7 +41,7 @@ export default createRule({ decl: TSESTree.TSEnumDeclaration, name: string, ): boolean { - return decl.members.some( + return decl.body.members.some( member => isIdentifierWithName(member.id, name) || (member.id.type === AST_NODE_TYPES.Literal && @@ -101,7 +101,7 @@ export default createRule({ ) { return; } - const declaration = node.parent as TSESTree.TSEnumDeclaration; + const declaration = node.parent.parent; // -1 and +1 if (node.initializer.type === AST_NODE_TYPES.UnaryExpression) { diff --git a/packages/eslint-plugin/src/rules/prefer-namespace-keyword.ts b/packages/eslint-plugin/src/rules/prefer-namespace-keyword.ts index 576d896c972d..774c65e53771 100644 --- a/packages/eslint-plugin/src/rules/prefer-namespace-keyword.ts +++ b/packages/eslint-plugin/src/rules/prefer-namespace-keyword.ts @@ -9,7 +9,7 @@ export default createRule({ docs: { description: 'Require using `namespace` keyword over `module` keyword to declare custom TypeScript modules', - recommended: 'stylistic', + recommended: 'recommended', }, fixable: 'code', messages: { diff --git a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts index 885314787456..87a23ab98808 100644 --- a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts +++ b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts @@ -101,7 +101,7 @@ export default createRule({ defaultOptions: [ { allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: false, - ignoreConditionalTests: false, + ignoreConditionalTests: true, ignoreTernaryTests: false, ignoreMixedLogicalExpressions: false, ignorePrimitives: { diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts index 54c44f4edda9..53beee5d2826 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts @@ -13,7 +13,7 @@ import { } from 'ts-api-utils'; import * as ts from 'typescript'; -import { isTypeFlagSet } from '../../util'; +import { isReferenceToGlobalFunction, isTypeFlagSet } from '../../util'; import type { PreferOptionalChainOptions } from './PreferOptionalChainOptions'; const enum ComparisonValueType { @@ -153,16 +153,13 @@ export function gatherLogicalOperands( comparedExpression.operator === 'typeof' ) { const argument = comparedExpression.argument; - if (argument.type === AST_NODE_TYPES.Identifier) { - const reference = sourceCode - .getScope(argument) - .references.find(ref => ref.identifier.name === argument.name); - - if (!reference?.resolved?.defs.length) { - // typeof window === 'undefined' - result.push({ type: OperandValidity.Invalid }); - continue; - } + if ( + argument.type === AST_NODE_TYPES.Identifier && + // typeof window === 'undefined' + isReferenceToGlobalFunction(argument.name, argument, sourceCode) + ) { + result.push({ type: OperandValidity.Invalid }); + continue; } // typeof x.y === 'undefined' diff --git a/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts b/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts index 3803b1298b4a..ef15fe918c17 100644 --- a/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts +++ b/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts @@ -26,7 +26,7 @@ export default createRule({ type: 'suggestion', docs: { description: 'Require using Error objects as Promise rejection reasons', - recommended: 'strict', + recommended: 'recommended', extendsBaseRule: true, requiresTypeChecking: true, }, diff --git a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts index 9f9e0a1dfb26..b19e0305c7bb 100644 --- a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts +++ b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/prefer-literal-enum-member */ import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; @@ -29,6 +28,7 @@ export default createRule({ docs: { description: 'Enforce `RegExp#exec` over `String#match` if no global flag is provided', + recommended: 'stylistic', requiresTypeChecking: true, }, messages: { diff --git a/packages/eslint-plugin/src/rules/quotes.ts b/packages/eslint-plugin/src/rules/quotes.ts deleted file mode 100644 index 2e1f3d6cd901..000000000000 --- a/packages/eslint-plugin/src/rules/quotes.ts +++ /dev/null @@ -1,80 +0,0 @@ -import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; - -import type { - InferMessageIdsTypeFromRule, - InferOptionsTypeFromRule, -} from '../util'; -import { createRule } from '../util'; -import { getESLintCoreRule } from '../util/getESLintCoreRule'; - -const baseRule = getESLintCoreRule('quotes'); - -export type Options = InferOptionsTypeFromRule; -export type MessageIds = InferMessageIdsTypeFromRule; - -export default createRule({ - name: 'quotes', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/quotes'], - type: 'layout', - docs: { - description: - 'Enforce the consistent use of either backticks, double, or single quotes', - extendsBaseRule: true, - }, - fixable: 'code', - hasSuggestions: baseRule.meta.hasSuggestions, - messages: baseRule.meta.messages, - schema: baseRule.meta.schema, - }, - defaultOptions: [ - 'double', - { - allowTemplateLiterals: false, - avoidEscape: false, - }, - ], - create(context, [option]) { - const rules = baseRule.create(context); - - function isAllowedAsNonBacktick(node: TSESTree.Literal): boolean { - const parent = node.parent; - - switch (parent.type) { - case AST_NODE_TYPES.TSAbstractMethodDefinition: - case AST_NODE_TYPES.TSMethodSignature: - case AST_NODE_TYPES.TSPropertySignature: - case AST_NODE_TYPES.TSModuleDeclaration: - case AST_NODE_TYPES.TSLiteralType: - case AST_NODE_TYPES.TSExternalModuleReference: - return true; - - case AST_NODE_TYPES.TSEnumMember: - return node === parent.id; - - case AST_NODE_TYPES.TSAbstractPropertyDefinition: - case AST_NODE_TYPES.PropertyDefinition: - return node === parent.key; - - default: - return false; - } - } - - return { - Literal(node): void { - if (option === 'backtick' && isAllowedAsNonBacktick(node)) { - return; - } - - rules.Literal(node); - }, - - TemplateLiteral(node): void { - rules.TemplateLiteral(node); - }, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/return-await.ts b/packages/eslint-plugin/src/rules/return-await.ts index 493bc1ec184c..716962a8a11b 100644 --- a/packages/eslint-plugin/src/rules/return-await.ts +++ b/packages/eslint-plugin/src/rules/return-await.ts @@ -37,6 +37,9 @@ export default createRule({ description: 'Enforce consistent awaiting of returned promises', requiresTypeChecking: true, extendsBaseRule: 'no-return-await', + recommended: { + strict: ['error-handling-correctness-only'], + }, }, fixable: 'code', hasSuggestions: true, diff --git a/packages/eslint-plugin/src/rules/semi.ts b/packages/eslint-plugin/src/rules/semi.ts deleted file mode 100644 index 674ea99e3a96..000000000000 --- a/packages/eslint-plugin/src/rules/semi.ts +++ /dev/null @@ -1,75 +0,0 @@ -import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; - -import type { - InferMessageIdsTypeFromRule, - InferOptionsTypeFromRule, -} from '../util'; -import { createRule } from '../util'; -import { getESLintCoreRule } from '../util/getESLintCoreRule'; - -const baseRule = getESLintCoreRule('semi'); - -export type Options = InferOptionsTypeFromRule; -export type MessageIds = InferMessageIdsTypeFromRule; - -export default createRule({ - name: 'semi', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/semi'], - type: 'layout', - docs: { - description: 'Require or disallow semicolons instead of ASI', - // too opinionated to be recommended - extendsBaseRule: true, - }, - fixable: 'code', - hasSuggestions: baseRule.meta.hasSuggestions, - schema: baseRule.meta.schema, - messages: baseRule.meta.messages, - }, - defaultOptions: [ - 'always', - { - omitLastInOneLineBlock: false, - beforeStatementContinuationChars: 'any', - }, - ], - create(context) { - const rules = baseRule.create(context); - const checkForSemicolon = - rules.ExpressionStatement as TSESLint.RuleFunction; - - /* - The following nodes are handled by the member-delimiter-style rule - AST_NODE_TYPES.TSCallSignatureDeclaration, - AST_NODE_TYPES.TSConstructSignatureDeclaration, - AST_NODE_TYPES.TSIndexSignature, - AST_NODE_TYPES.TSMethodSignature, - AST_NODE_TYPES.TSPropertySignature, - */ - const nodesToCheck = [ - AST_NODE_TYPES.PropertyDefinition, - AST_NODE_TYPES.TSAbstractPropertyDefinition, - AST_NODE_TYPES.TSDeclareFunction, - AST_NODE_TYPES.TSExportAssignment, - AST_NODE_TYPES.TSImportEqualsDeclaration, - AST_NODE_TYPES.TSTypeAliasDeclaration, - AST_NODE_TYPES.TSEmptyBodyFunctionExpression, - ].reduce((acc, node) => { - acc[node as string] = checkForSemicolon; - return acc; - }, {}); - - return { - ...rules, - ...nodesToCheck, - ExportDefaultDeclaration(node): void { - if (node.declaration.type !== AST_NODE_TYPES.TSInterfaceDeclaration) { - rules.ExportDefaultDeclaration(node); - } - }, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/space-before-blocks.ts b/packages/eslint-plugin/src/rules/space-before-blocks.ts deleted file mode 100644 index cb50e5b57eb0..000000000000 --- a/packages/eslint-plugin/src/rules/space-before-blocks.ts +++ /dev/null @@ -1,94 +0,0 @@ -import type { TSESTree } from '@typescript-eslint/utils'; - -import type { - InferMessageIdsTypeFromRule, - InferOptionsTypeFromRule, -} from '../util'; -import { createRule, isTokenOnSameLine } from '../util'; -import { getESLintCoreRule } from '../util/getESLintCoreRule'; - -const baseRule = getESLintCoreRule('space-before-blocks'); - -export type Options = InferOptionsTypeFromRule; -export type MessageIds = InferMessageIdsTypeFromRule; - -export default createRule({ - name: 'space-before-blocks', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/space-before-blocks'], - type: 'layout', - docs: { - description: 'Enforce consistent spacing before blocks', - extendsBaseRule: true, - }, - fixable: baseRule.meta.fixable, - hasSuggestions: baseRule.meta.hasSuggestions, - schema: baseRule.meta.schema, - messages: { - // @ts-expect-error -- we report on this messageId so we need to ensure it's there in case ESLint changes in future - unexpectedSpace: 'Unexpected space before opening brace.', - // @ts-expect-error -- we report on this messageId so we need to ensure it's there in case ESLint changes in future - missingSpace: 'Missing space before opening brace.', - ...baseRule.meta.messages, - }, - }, - defaultOptions: ['always'], - create(context, [config]) { - const rules = baseRule.create(context); - - let requireSpace = true; - - if (typeof config === 'object') { - requireSpace = config.classes === 'always'; - } else if (config === 'never') { - requireSpace = false; - } - - function checkPrecedingSpace( - node: TSESTree.Token | TSESTree.TSInterfaceBody, - ): void { - const precedingToken = context.sourceCode.getTokenBefore(node); - if (precedingToken && isTokenOnSameLine(precedingToken, node)) { - const hasSpace = context.sourceCode.isSpaceBetween( - precedingToken, - node as TSESTree.Token, - ); - - if (requireSpace && !hasSpace) { - context.report({ - node, - messageId: 'missingSpace', - fix(fixer) { - return fixer.insertTextBefore(node, ' '); - }, - }); - } else if (!requireSpace && hasSpace) { - context.report({ - node, - messageId: 'unexpectedSpace', - fix(fixer) { - return fixer.removeRange([ - precedingToken.range[1], - node.range[0], - ]); - }, - }); - } - } - } - - function checkSpaceAfterEnum(node: TSESTree.TSEnumDeclaration): void { - const punctuator = context.sourceCode.getTokenAfter(node.id); - if (punctuator) { - checkPrecedingSpace(punctuator); - } - } - - return { - ...rules, - TSEnumDeclaration: checkSpaceAfterEnum, - TSInterfaceBody: checkPrecedingSpace, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/space-before-function-paren.ts b/packages/eslint-plugin/src/rules/space-before-function-paren.ts deleted file mode 100644 index 78ea98239db7..000000000000 --- a/packages/eslint-plugin/src/rules/space-before-function-paren.ts +++ /dev/null @@ -1,196 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; - -import { createRule, isOpeningParenToken } from '../util'; - -type Option = 'always' | 'never'; -type FuncOption = Option | 'ignore'; - -export type Options = [ - | Option - | { - anonymous?: FuncOption; - named?: FuncOption; - asyncArrow?: FuncOption; - }, -]; -export type MessageIds = 'missing' | 'unexpected'; - -export default createRule({ - name: 'space-before-function-paren', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/space-before-function-paren'], - type: 'layout', - docs: { - description: 'Enforce consistent spacing before function parenthesis', - extendsBaseRule: true, - }, - fixable: 'whitespace', - schema: [ - { - oneOf: [ - { - type: 'string', - enum: ['always', 'never'], - }, - { - type: 'object', - properties: { - anonymous: { - type: 'string', - enum: ['always', 'never', 'ignore'], - }, - named: { - type: 'string', - enum: ['always', 'never', 'ignore'], - }, - asyncArrow: { - type: 'string', - enum: ['always', 'never', 'ignore'], - }, - }, - additionalProperties: false, - }, - ], - }, - ], - messages: { - unexpected: 'Unexpected space before function parentheses.', - missing: 'Missing space before function parentheses.', - }, - }, - defaultOptions: ['always'], - - create(context, [firstOption]) { - const baseConfig = typeof firstOption === 'string' ? firstOption : 'always'; - const overrideConfig = typeof firstOption === 'object' ? firstOption : {}; - - /** - * Determines whether a function has a name. - */ - function isNamedFunction( - node: - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression - | TSESTree.TSDeclareFunction - | TSESTree.TSEmptyBodyFunctionExpression, - ): boolean { - if (node.id != null) { - return true; - } - - const parent = node.parent; - - return ( - parent.type === AST_NODE_TYPES.MethodDefinition || - parent.type === AST_NODE_TYPES.TSAbstractMethodDefinition || - (parent.type === AST_NODE_TYPES.Property && - (parent.kind === 'get' || parent.kind === 'set' || parent.method)) - ); - } - - /** - * Gets the config for a given function - */ - function getConfigForFunction( - node: - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression - | TSESTree.TSDeclareFunction - | TSESTree.TSEmptyBodyFunctionExpression, - ): FuncOption { - if (node.type === AST_NODE_TYPES.ArrowFunctionExpression) { - // Always ignore non-async functions and arrow functions without parens, e.g. async foo => bar - if ( - node.async && - isOpeningParenToken( - context.sourceCode.getFirstToken(node, { skip: 1 })!, - ) - ) { - return overrideConfig.asyncArrow ?? baseConfig; - } - } else if (isNamedFunction(node)) { - return overrideConfig.named ?? baseConfig; - - // `generator-star-spacing` should warn anonymous generators. E.g. `function* () {}` - } else if (!node.generator) { - return overrideConfig.anonymous ?? baseConfig; - } - - return 'ignore'; - } - - /** - * Checks the parens of a function node - * @param node A function node - */ - function checkFunction( - node: - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression - | TSESTree.TSDeclareFunction - | TSESTree.TSEmptyBodyFunctionExpression, - ): void { - const functionConfig = getConfigForFunction(node); - - if (functionConfig === 'ignore') { - return; - } - - let leftToken: TSESTree.Token; - let rightToken: TSESTree.Token; - if (node.typeParameters) { - leftToken = context.sourceCode.getLastToken(node.typeParameters)!; - rightToken = context.sourceCode.getTokenAfter(leftToken)!; - } else { - rightToken = context.sourceCode.getFirstToken( - node, - isOpeningParenToken, - )!; - leftToken = context.sourceCode.getTokenBefore(rightToken)!; - } - - const hasSpacing = context.sourceCode.isSpaceBetween( - leftToken, - rightToken, - ); - - if (hasSpacing && functionConfig === 'never') { - context.report({ - node, - loc: { - start: leftToken.loc.end, - end: rightToken.loc.start, - }, - messageId: 'unexpected', - fix: fixer => - fixer.removeRange([leftToken.range[1], rightToken.range[0]]), - }); - } else if ( - !hasSpacing && - functionConfig === 'always' && - (!node.typeParameters || node.id) - ) { - context.report({ - node, - loc: rightToken.loc, - messageId: 'missing', - fix: fixer => fixer.insertTextAfter(leftToken, ' '), - }); - } - } - - return { - ArrowFunctionExpression: checkFunction, - FunctionDeclaration: checkFunction, - FunctionExpression: checkFunction, - TSEmptyBodyFunctionExpression: checkFunction, - TSDeclareFunction: checkFunction, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/space-infix-ops.ts b/packages/eslint-plugin/src/rules/space-infix-ops.ts deleted file mode 100644 index 432f7907b270..000000000000 --- a/packages/eslint-plugin/src/rules/space-infix-ops.ts +++ /dev/null @@ -1,187 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -import { AST_TOKEN_TYPES, TSESTree } from '@typescript-eslint/utils'; - -import type { - InferMessageIdsTypeFromRule, - InferOptionsTypeFromRule, -} from '../util'; -import { createRule, isNotOpeningParenToken } from '../util'; -import { getESLintCoreRule } from '../util/getESLintCoreRule'; - -const baseRule = getESLintCoreRule('space-infix-ops'); - -export type Options = InferOptionsTypeFromRule; -export type MessageIds = InferMessageIdsTypeFromRule; - -const UNIONS = ['|', '&']; - -export default createRule({ - name: 'space-infix-ops', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/space-infix-ops'], - type: 'layout', - docs: { - description: 'Require spacing around infix operators', - extendsBaseRule: true, - }, - fixable: baseRule.meta.fixable, - hasSuggestions: baseRule.meta.hasSuggestions, - schema: baseRule.meta.schema, - messages: { - // @ts-expect-error -- we report on this messageId so we need to ensure it's there in case ESLint changes in future - missingSpace: "Operator '{{operator}}' must be spaced.", - ...baseRule.meta.messages, - }, - }, - defaultOptions: [ - { - int32Hint: false, - }, - ], - create(context) { - const rules = baseRule.create(context); - - function report(operator: TSESTree.Token): void { - context.report({ - node: operator, - messageId: 'missingSpace', - data: { - operator: operator.value, - }, - fix(fixer) { - const previousToken = context.sourceCode.getTokenBefore(operator); - const afterToken = context.sourceCode.getTokenAfter(operator); - let fixString = ''; - - if (operator.range[0] - previousToken!.range[1] === 0) { - fixString = ' '; - } - - fixString += operator.value; - - if (afterToken!.range[0] - operator.range[1] === 0) { - fixString += ' '; - } - - return fixer.replaceText(operator, fixString); - }, - }); - } - - function isSpaceChar(token: TSESTree.Token): boolean { - return ( - token.type === AST_TOKEN_TYPES.Punctuator && /^[=?:]$/.test(token.value) - ); - } - - function checkAndReportAssignmentSpace( - leftNode: TSESTree.Node | TSESTree.Token | null, - rightNode?: TSESTree.Node | TSESTree.Token | null, - ): void { - if (!rightNode || !leftNode) { - return; - } - - const operator = context.sourceCode.getFirstTokenBetween( - leftNode, - rightNode, - isSpaceChar, - )!; - - const prev = context.sourceCode.getTokenBefore(operator)!; - const next = context.sourceCode.getTokenAfter(operator)!; - - if ( - !context.sourceCode.isSpaceBetween(prev, operator) || - !context.sourceCode.isSpaceBetween(operator, next) - ) { - report(operator); - } - } - - /** - * Check if it has an assignment char and report if it's faulty - * @param node The node to report - */ - function checkForEnumAssignmentSpace(node: TSESTree.TSEnumMember): void { - checkAndReportAssignmentSpace(node.id, node.initializer); - } - - /** - * Check if it has an assignment char and report if it's faulty - * @param node The node to report - */ - function checkForPropertyDefinitionAssignmentSpace( - node: TSESTree.PropertyDefinition, - ): void { - const leftNode = - node.optional && !node.typeAnnotation - ? context.sourceCode.getTokenAfter(node.key) - : node.typeAnnotation ?? node.key; - - checkAndReportAssignmentSpace(leftNode, node.value); - } - - /** - * Check if it is missing spaces between type annotations chaining - * @param typeAnnotation TypeAnnotations list - */ - function checkForTypeAnnotationSpace( - typeAnnotation: TSESTree.TSIntersectionType | TSESTree.TSUnionType, - ): void { - const types = typeAnnotation.types; - - types.forEach(type => { - const skipFunctionParenthesis = - type.type === TSESTree.AST_NODE_TYPES.TSFunctionType - ? isNotOpeningParenToken - : 0; - const operator = context.sourceCode.getTokenBefore( - type, - skipFunctionParenthesis, - ); - - if (operator != null && UNIONS.includes(operator.value)) { - const prev = context.sourceCode.getTokenBefore(operator); - const next = context.sourceCode.getTokenAfter(operator); - - if ( - !context.sourceCode.isSpaceBetween(prev!, operator) || - !context.sourceCode.isSpaceBetween(operator, next!) - ) { - report(operator); - } - } - }); - } - - /** - * Check if it has an assignment char and report if it's faulty - * @param node The node to report - */ - function checkForTypeAliasAssignment( - node: TSESTree.TSTypeAliasDeclaration, - ): void { - checkAndReportAssignmentSpace( - node.typeParameters ?? node.id, - node.typeAnnotation, - ); - } - - function checkForTypeConditional(node: TSESTree.TSConditionalType): void { - checkAndReportAssignmentSpace(node.extendsType, node.trueType); - checkAndReportAssignmentSpace(node.trueType, node.falseType); - } - - return { - ...rules, - TSEnumMember: checkForEnumAssignmentSpace, - PropertyDefinition: checkForPropertyDefinitionAssignmentSpace, - TSTypeAliasDeclaration: checkForTypeAliasAssignment, - TSUnionType: checkForTypeAnnotationSpace, - TSIntersectionType: checkForTypeAnnotationSpace, - TSConditionalType: checkForTypeConditional, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/type-annotation-spacing.ts b/packages/eslint-plugin/src/rules/type-annotation-spacing.ts deleted file mode 100644 index 47fbf80d8ac0..000000000000 --- a/packages/eslint-plugin/src/rules/type-annotation-spacing.ts +++ /dev/null @@ -1,289 +0,0 @@ -import type { TSESTree } from '@typescript-eslint/utils'; - -import { - createRule, - isClassOrTypeElement, - isFunction, - isFunctionOrFunctionType, - isIdentifier, - isTSConstructorType, - isTSFunctionType, - isVariableDeclarator, -} from '../util'; - -interface WhitespaceRule { - readonly before?: boolean; - readonly after?: boolean; -} - -interface WhitespaceOverride { - readonly colon?: WhitespaceRule; - readonly arrow?: WhitespaceRule; - readonly variable?: WhitespaceRule; - readonly property?: WhitespaceRule; - readonly parameter?: WhitespaceRule; - readonly returnType?: WhitespaceRule; -} - -interface Config extends WhitespaceRule { - readonly overrides?: WhitespaceOverride; -} - -type WhitespaceRules = Required; - -type Options = [Config?]; -type MessageIds = - | 'expectedSpaceAfter' - | 'expectedSpaceBefore' - | 'unexpectedSpaceAfter' - | 'unexpectedSpaceBefore' - | 'unexpectedSpaceBetween'; - -function createRules(options?: Config): WhitespaceRules { - const globals = { - ...(options?.before !== undefined ? { before: options.before } : {}), - ...(options?.after !== undefined ? { after: options.after } : {}), - }; - const override = options?.overrides ?? {}; - const colon = { - ...{ before: false, after: true }, - ...globals, - ...override.colon, - }; - const arrow = { - ...{ before: true, after: true }, - ...globals, - ...override.arrow, - }; - - return { - colon: colon, - arrow: arrow, - variable: { ...colon, ...override.variable }, - property: { ...colon, ...override.property }, - parameter: { ...colon, ...override.parameter }, - returnType: { ...colon, ...override.returnType }, - }; -} - -function getIdentifierRules( - rules: WhitespaceRules, - node: TSESTree.Node | undefined, -): WhitespaceRule { - const scope = node?.parent; - - if (isVariableDeclarator(scope)) { - return rules.variable; - } else if (isFunctionOrFunctionType(scope)) { - return rules.parameter; - } - return rules.colon; -} - -function getRules( - rules: WhitespaceRules, - node: TSESTree.TypeNode, -): WhitespaceRule { - const scope = node.parent.parent; - - if (isTSFunctionType(scope) || isTSConstructorType(scope)) { - return rules.arrow; - } else if (isIdentifier(scope)) { - return getIdentifierRules(rules, scope); - } else if (isClassOrTypeElement(scope)) { - return rules.property; - } else if (isFunction(scope)) { - return rules.returnType; - } - return rules.colon; -} - -export default createRule({ - name: 'type-annotation-spacing', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/type-annotation-spacing'], - type: 'layout', - docs: { - description: 'Require consistent spacing around type annotations', - }, - fixable: 'whitespace', - messages: { - expectedSpaceAfter: "Expected a space after the '{{type}}'.", - expectedSpaceBefore: "Expected a space before the '{{type}}'.", - unexpectedSpaceAfter: "Unexpected space after the '{{type}}'.", - unexpectedSpaceBefore: "Unexpected space before the '{{type}}'.", - unexpectedSpaceBetween: - "Unexpected space between the '{{previousToken}}' and the '{{type}}'.", - }, - schema: [ - { - $defs: { - spacingConfig: { - type: 'object', - properties: { - before: { type: 'boolean' }, - after: { type: 'boolean' }, - }, - additionalProperties: false, - }, - }, - type: 'object', - properties: { - before: { type: 'boolean' }, - after: { type: 'boolean' }, - overrides: { - type: 'object', - properties: { - colon: { $ref: '#/items/0/$defs/spacingConfig' }, - arrow: { $ref: '#/items/0/$defs/spacingConfig' }, - variable: { $ref: '#/items/0/$defs/spacingConfig' }, - parameter: { $ref: '#/items/0/$defs/spacingConfig' }, - property: { $ref: '#/items/0/$defs/spacingConfig' }, - returnType: { $ref: '#/items/0/$defs/spacingConfig' }, - }, - additionalProperties: false, - }, - }, - additionalProperties: false, - }, - ], - }, - defaultOptions: [ - // technically there is a default, but the overrides mean - // that if we apply them here, it will break the no override case. - {}, - ], - create(context, [options]) { - const punctuators = [':', '=>']; - - const ruleSet = createRules(options); - - /** - * Checks if there's proper spacing around type annotations (no space - * before colon, one space after). - */ - function checkTypeAnnotationSpacing( - typeAnnotation: TSESTree.TypeNode, - ): void { - const nextToken = typeAnnotation; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const punctuatorTokenEnd = context.sourceCode.getTokenBefore(nextToken)!; - let punctuatorTokenStart = punctuatorTokenEnd; - let previousToken = - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - context.sourceCode.getTokenBefore(punctuatorTokenEnd)!; - let type = punctuatorTokenEnd.value; - - if (!punctuators.includes(type)) { - return; - } - - const { before, after } = getRules(ruleSet, typeAnnotation); - - if (type === ':' && previousToken.value === '?') { - if ( - context.sourceCode.isSpaceBetween(previousToken, punctuatorTokenStart) - ) { - context.report({ - node: punctuatorTokenStart, - messageId: 'unexpectedSpaceBetween', - data: { - type, - previousToken: previousToken.value, - }, - fix(fixer) { - return fixer.removeRange([ - previousToken.range[1], - punctuatorTokenStart.range[0], - ]); - }, - }); - } - - // shift the start to the ? - type = '?:'; - punctuatorTokenStart = previousToken; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - previousToken = context.sourceCode.getTokenBefore(previousToken)!; - - // handle the +/- modifiers for optional modification operators - if (previousToken.value === '+' || previousToken.value === '-') { - type = `${previousToken.value}?:`; - punctuatorTokenStart = previousToken; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - previousToken = context.sourceCode.getTokenBefore(previousToken)!; - } - } - - const previousDelta = - punctuatorTokenStart.range[0] - previousToken.range[1]; - const nextDelta = nextToken.range[0] - punctuatorTokenEnd.range[1]; - - if (after && nextDelta === 0) { - context.report({ - node: punctuatorTokenEnd, - messageId: 'expectedSpaceAfter', - data: { - type, - }, - fix(fixer) { - return fixer.insertTextAfter(punctuatorTokenEnd, ' '); - }, - }); - } else if (!after && nextDelta > 0) { - context.report({ - node: punctuatorTokenEnd, - messageId: 'unexpectedSpaceAfter', - data: { - type, - }, - fix(fixer) { - return fixer.removeRange([ - punctuatorTokenEnd.range[1], - nextToken.range[0], - ]); - }, - }); - } - - if (before && previousDelta === 0) { - context.report({ - node: punctuatorTokenStart, - messageId: 'expectedSpaceBefore', - data: { - type, - }, - fix(fixer) { - return fixer.insertTextAfter(previousToken, ' '); - }, - }); - } else if (!before && previousDelta > 0) { - context.report({ - node: punctuatorTokenStart, - messageId: 'unexpectedSpaceBefore', - data: { - type, - }, - fix(fixer) { - return fixer.removeRange([ - previousToken.range[1], - punctuatorTokenStart.range[0], - ]); - }, - }); - } - } - - return { - TSMappedType(node): void { - if (node.typeAnnotation) { - checkTypeAnnotationSpacing(node.typeAnnotation); - } - }, - TSTypeAnnotation(node): void { - checkTypeAnnotationSpacing(node.typeAnnotation); - }, - }; - }, -}); diff --git a/packages/eslint-plugin/src/util/collectUnusedVariables.ts b/packages/eslint-plugin/src/util/collectUnusedVariables.ts index 83d2348895b5..f5602f52e2e7 100644 --- a/packages/eslint-plugin/src/util/collectUnusedVariables.ts +++ b/packages/eslint-plugin/src/util/collectUnusedVariables.ts @@ -1,3 +1,7 @@ +import type { + ScopeManager, + ScopeVariable, +} from '@typescript-eslint/scope-manager'; import { ImplicitLibVariable, ScopeType, @@ -11,42 +15,51 @@ import { TSESLint, } from '@typescript-eslint/utils'; -class UnusedVarsVisitor< - MessageIds extends string, - Options extends readonly unknown[], -> extends Visitor { +import { isTypeImport } from './isTypeImport'; +import { referenceContainsTypeQuery } from './referenceContainsTypeQuery'; + +interface VariableAnalysis { + readonly unusedVariables: ReadonlySet; + readonly usedVariables: ReadonlySet; +} +interface MutableVariableAnalysis { + readonly unusedVariables: Set; + readonly usedVariables: Set; +} + +/** + * This class leverages an AST visitor to mark variables as used via the + * `eslintUsed` property. + */ +class UnusedVarsVisitor extends Visitor { + /** + * We keep a weak cache so that multiple rules can share the calculation + */ private static readonly RESULTS_CACHE = new WeakMap< TSESTree.Program, - ReadonlySet + VariableAnalysis >(); readonly #scopeManager: TSESLint.Scope.ScopeManager; - // readonly #unusedVariables = new Set(); - private constructor(context: TSESLint.RuleContext) { + private constructor(scopeManager: ScopeManager) { super({ visitChildrenEvenIfSelectorExists: true, }); - this.#scopeManager = ESLintUtils.nullThrows( - context.sourceCode.scopeManager, - 'Missing required scope manager', - ); + this.#scopeManager = scopeManager; } - public static collectUnusedVariables< - MessageIds extends string, - Options extends readonly unknown[], - >( - context: TSESLint.RuleContext, - ): ReadonlySet { - const program = context.sourceCode.ast; + public static collectUnusedVariables( + program: TSESTree.Program, + scopeManager: ScopeManager, + ): VariableAnalysis { const cached = this.RESULTS_CACHE.get(program); if (cached) { return cached; } - const visitor = new this(context); + const visitor = new this(scopeManager); visitor.visit(program); const unusedVars = visitor.collectUnusedVariables( @@ -58,34 +71,49 @@ class UnusedVarsVisitor< private collectUnusedVariables( scope: TSESLint.Scope.Scope, - unusedVariables = new Set(), - ): ReadonlySet { - for (const variable of scope.variables) { - if ( - // skip function expression names, - scope.functionExpressionScope || - // variables marked with markVariableAsUsed(), - variable.eslintUsed || - // implicit lib variables (from @typescript-eslint/scope-manager), - variable instanceof ImplicitLibVariable || - // basic exported variables - isExported(variable) || - // variables implicitly exported via a merged declaration - isMergableExported(variable) || - // used variables - isUsedVariable(variable) - ) { - continue; - } + variables: MutableVariableAnalysis = { + unusedVariables: new Set(), + usedVariables: new Set(), + }, + ): VariableAnalysis { + if ( + // skip function expression names + // this scope is created just to house the variable that allows a function + // expression to self-reference if it has a name defined + !scope.functionExpressionScope + ) { + for (const variable of scope.variables) { + // cases that we don't want to treat as used or unused + if ( + // implicit lib variables (from @typescript-eslint/scope-manager) + // these aren't variables that should be checked ever + variable instanceof ImplicitLibVariable + ) { + continue; + } - unusedVariables.add(variable); + if ( + // variables marked with markVariableAsUsed() + variable.eslintUsed || + // basic exported variables + isExported(variable) || + // variables implicitly exported via a merged declaration + isMergableExported(variable) || + // used variables + isUsedVariable(variable) + ) { + variables.usedVariables.add(variable); + } else { + variables.unusedVariables.add(variable); + } + } } for (const childScope of scope.childScopes) { - this.collectUnusedVariables(childScope, unusedVariables); + this.collectUnusedVariables(childScope, variables); } - return unusedVariables; + return variables; } //#region HELPERS @@ -112,14 +140,11 @@ class UnusedVarsVisitor< } private markVariableAsUsed( - variableOrIdentifier: TSESLint.Scope.Variable | TSESTree.Identifier, + variableOrIdentifier: ScopeVariable | TSESTree.Identifier, ): void; private markVariableAsUsed(name: string, parent: TSESTree.Node): void; private markVariableAsUsed( - variableOrIdentifierOrName: - | TSESLint.Scope.Variable - | TSESTree.Identifier - | string, + variableOrIdentifierOrName: ScopeVariable | TSESTree.Identifier | string, parent?: TSESTree.Node, ): void { if ( @@ -212,20 +237,9 @@ class UnusedVarsVisitor< } } - //#endregion HELPERS - - //#region VISITORS - // NOTE - This is a simple visitor - meaning it does not support selectors - - protected ClassDeclaration = this.visitClass; - - protected ClassExpression = this.visitClass; - - protected FunctionDeclaration = this.visitFunction; - - protected FunctionExpression = this.visitFunction; - - protected ForInStatement(node: TSESTree.ForInStatement): void { + private visitForInForOf( + node: TSESTree.ForInStatement | TSESTree.ForOfStatement, + ): void { /** * (Brad Zacher): I hate that this has to exist. * But it is required for compat with the base ESLint rule. @@ -274,6 +288,23 @@ class UnusedVarsVisitor< this.markVariableAsUsed(idOrVariable); } + //#endregion HELPERS + + //#region VISITORS + // NOTE - This is a simple visitor - meaning it does not support selectors + + protected ClassDeclaration = this.visitClass; + + protected ClassExpression = this.visitClass; + + protected FunctionDeclaration = this.visitFunction; + + protected FunctionExpression = this.visitFunction; + + protected ForInStatement = this.visitForInForOf; + + protected ForOfStatement = this.visitForInForOf; + protected Identifier(node: TSESTree.Identifier): void { const scope = this.getScope(node); if ( @@ -315,7 +346,7 @@ class UnusedVarsVisitor< protected TSMappedType(node: TSESTree.TSMappedType): void { // mapped types create a variable for their type name, but it's not necessary to reference it, // so we shouldn't consider it as unused for the purpose of this rule. - this.markVariableAsUsed(node.typeParameter.name); + this.markVariableAsUsed(node.key); } protected TSMethodSignature = this.visitFunctionTypeSignature; @@ -396,7 +427,7 @@ const MERGABLE_TYPES = new Set([ * Determine if the variable is directly exported * @param variable the variable to check */ -function isMergableExported(variable: TSESLint.Scope.Variable): boolean { +function isMergableExported(variable: ScopeVariable): boolean { // If all of the merged things are of the same type, TS will error if not all of them are exported - so we only need to find one for (const def of variable.defs) { // parameters can never be exported. @@ -423,7 +454,7 @@ function isMergableExported(variable: TSESLint.Scope.Variable): boolean { * @param variable eslint-scope variable object. * @returns True if the variable is exported, false if not. */ -function isExported(variable: TSESLint.Scope.Variable): boolean { +function isExported(variable: ScopeVariable): boolean { return variable.defs.some(definition => { let node = definition.node; @@ -446,15 +477,13 @@ const LOGICAL_ASSIGNMENT_OPERATORS = new Set(['&&=', '||=', '??=']); * @param variable The variable to check. * @returns True if the variable is used */ -function isUsedVariable(variable: TSESLint.Scope.Variable): boolean { +function isUsedVariable(variable: ScopeVariable): boolean { /** * Gets a list of function definitions for a specified variable. * @param variable eslint-scope variable object. * @returns Function nodes. */ - function getFunctionDefinitions( - variable: TSESLint.Scope.Variable, - ): Set { + function getFunctionDefinitions(variable: ScopeVariable): Set { const functionDefinitions = new Set(); variable.defs.forEach(def => { @@ -475,9 +504,7 @@ function isUsedVariable(variable: TSESLint.Scope.Variable): boolean { return functionDefinitions; } - function getTypeDeclarations( - variable: TSESLint.Scope.Variable, - ): Set { + function getTypeDeclarations(variable: ScopeVariable): Set { const nodes = new Set(); variable.defs.forEach(def => { @@ -492,9 +519,7 @@ function isUsedVariable(variable: TSESLint.Scope.Variable): boolean { return nodes; } - function getModuleDeclarations( - variable: TSESLint.Scope.Variable, - ): Set { + function getModuleDeclarations(variable: ScopeVariable): Set { const nodes = new Set(); variable.defs.forEach(def => { @@ -506,6 +531,18 @@ function isUsedVariable(variable: TSESLint.Scope.Variable): boolean { return nodes; } + function getEnumDeclarations(variable: ScopeVariable): Set { + const nodes = new Set(); + + variable.defs.forEach(def => { + if (def.node.type === AST_NODE_TYPES.TSEnumDeclaration) { + nodes.add(def.node); + } + }); + + return nodes; + } + /** * Checks if the ref is contained within one of the given nodes */ @@ -522,6 +559,31 @@ function isUsedVariable(variable: TSESLint.Scope.Variable): boolean { return false; } + /** + * Checks whether a given node is unused expression or not. + * @param node The node itself + * @returns The node is an unused expression. + */ + function isUnusedExpression(node: TSESTree.Expression): boolean { + const parent = node.parent; + + if (parent.type === AST_NODE_TYPES.ExpressionStatement) { + return true; + } + + if (parent.type === AST_NODE_TYPES.SequenceExpression) { + const isLastExpression = + parent.expressions[parent.expressions.length - 1] === node; + + if (!isLastExpression) { + return true; + } + return isUnusedExpression(parent); + } + + return false; + } + /** * If a given reference is left-hand side of an assignment, this gets * the right-hand side node of the assignment. @@ -564,8 +626,6 @@ function isUsedVariable(variable: TSESLint.Scope.Variable): boolean { const id = ref.identifier; const parent = id.parent; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const grandparent = parent.parent!; const refScope = ref.from.variableScope; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const varScope = ref.resolved!.scope.variableScope; @@ -581,7 +641,7 @@ function isUsedVariable(variable: TSESLint.Scope.Variable): boolean { if ( parent.type === AST_NODE_TYPES.AssignmentExpression && - grandparent.type === AST_NODE_TYPES.ExpressionStatement && + isUnusedExpression(parent) && id === parent.left && !canBeUsedLater ) { @@ -699,19 +759,16 @@ function isUsedVariable(variable: TSESLint.Scope.Variable): boolean { const id = ref.identifier; const parent = id.parent; - // https://github.com/typescript-eslint/typescript-eslint/issues/6225 - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const grandparent = parent.parent!; return ( ref.isRead() && // in RHS of an assignment for itself. e.g. `a = a + 1` // self update. e.g. `a += 1`, `a++` ((parent.type === AST_NODE_TYPES.AssignmentExpression && !LOGICAL_ASSIGNMENT_OPERATORS.has(parent.operator) && - grandparent.type === AST_NODE_TYPES.ExpressionStatement && + isUnusedExpression(parent) && parent.left === id) || (parent.type === AST_NODE_TYPES.UpdateExpression && - grandparent.type === AST_NODE_TYPES.ExpressionStatement) || + isUnusedExpression(parent)) || (!!rhsNode && isInside(id, rhsNode) && !isInsideOfStorableFunction(id, rhsNode))) @@ -727,6 +784,11 @@ function isUsedVariable(variable: TSESLint.Scope.Variable): boolean { const moduleDeclNodes = getModuleDeclarations(variable); const isModuleDecl = moduleDeclNodes.size > 0; + const enumDeclNodes = getEnumDeclarations(variable); + const isEnumDecl = enumDeclNodes.size > 0; + + const isImportedAsType = variable.defs.every(isTypeImport); + let rhsNode: TSESTree.Node | null = null; return variable.references.some(ref => { @@ -737,9 +799,11 @@ function isUsedVariable(variable: TSESLint.Scope.Variable): boolean { return ( ref.isRead() && !forItself && + !(!isImportedAsType && referenceContainsTypeQuery(ref.identifier)) && !(isFunctionDefinition && isSelfReference(ref, functionNodes)) && !(isTypeDecl && isInsideOneOf(ref, typeDeclNodes)) && - !(isModuleDecl && isSelfReference(ref, moduleDeclNodes)) + !(isModuleDecl && isSelfReference(ref, moduleDeclNodes)) && + !(isEnumDecl && isSelfReference(ref, enumDeclNodes)) ); }); } @@ -753,13 +817,19 @@ function isUsedVariable(variable: TSESLint.Scope.Variable): boolean { * - variables within declaration files * - variables within ambient module declarations */ -function collectUnusedVariables< +function collectVariables< MessageIds extends string, Options extends readonly unknown[], >( context: Readonly>, -): ReadonlySet { - return UnusedVarsVisitor.collectUnusedVariables(context); +): VariableAnalysis { + return UnusedVarsVisitor.collectUnusedVariables( + context.sourceCode.ast, + ESLintUtils.nullThrows( + context.sourceCode.scopeManager, + 'Missing required scope manager', + ), + ); } -export { collectUnusedVariables }; +export { collectVariables }; diff --git a/packages/eslint-plugin/src/util/createRule.ts b/packages/eslint-plugin/src/util/createRule.ts index 1008ffcc11bd..80611a8fdb89 100644 --- a/packages/eslint-plugin/src/util/createRule.ts +++ b/packages/eslint-plugin/src/util/createRule.ts @@ -1,5 +1,7 @@ import { ESLintUtils } from '@typescript-eslint/utils'; -export const createRule = ESLintUtils.RuleCreator( +import type { ESLintPluginDocs } from '../../rules'; + +export const createRule = ESLintUtils.RuleCreator( name => `https://typescript-eslint.io/rules/${name}`, ); diff --git a/packages/eslint-plugin/src/util/getESLintCoreRule.ts b/packages/eslint-plugin/src/util/getESLintCoreRule.ts index be28069d2877..59b22c7292fd 100644 --- a/packages/eslint-plugin/src/util/getESLintCoreRule.ts +++ b/packages/eslint-plugin/src/util/getESLintCoreRule.ts @@ -4,23 +4,13 @@ import { builtinRules } from 'eslint/use-at-your-own-risk'; interface RuleMap { /* eslint-disable @typescript-eslint/consistent-type-imports -- more concise to use inline imports */ 'arrow-parens': typeof import('eslint/lib/rules/arrow-parens'); - 'block-spacing': typeof import('eslint/lib/rules/block-spacing'); - 'brace-style': typeof import('eslint/lib/rules/brace-style'); - 'comma-dangle': typeof import('eslint/lib/rules/comma-dangle'); 'consistent-return': typeof import('eslint/lib/rules/consistent-return'); 'dot-notation': typeof import('eslint/lib/rules/dot-notation'); - indent: typeof import('eslint/lib/rules/indent'); 'init-declarations': typeof import('eslint/lib/rules/init-declarations'); - 'key-spacing': typeof import('eslint/lib/rules/key-spacing'); - 'keyword-spacing': typeof import('eslint/lib/rules/keyword-spacing'); - 'lines-around-comment': typeof import('eslint/lib/rules/lines-around-comment'); - 'lines-between-class-members': typeof import('eslint/lib/rules/lines-between-class-members'); 'max-params': typeof import('eslint/lib/rules/max-params'); 'no-dupe-args': typeof import('eslint/lib/rules/no-dupe-args'); 'no-dupe-class-members': typeof import('eslint/lib/rules/no-dupe-class-members'); 'no-empty-function': typeof import('eslint/lib/rules/no-empty-function'); - 'no-extra-parens': typeof import('eslint/lib/rules/no-extra-parens'); - 'no-extra-semi': typeof import('eslint/lib/rules/no-extra-semi'); 'no-implicit-globals': typeof import('eslint/lib/rules/no-implicit-globals'); 'no-invalid-this': typeof import('eslint/lib/rules/no-invalid-this'); 'no-loop-func': typeof import('eslint/lib/rules/no-loop-func'); @@ -31,13 +21,8 @@ interface RuleMap { 'no-unused-expressions': typeof import('eslint/lib/rules/no-unused-expressions'); 'no-useless-constructor': typeof import('eslint/lib/rules/no-useless-constructor'); 'no-restricted-globals': typeof import('eslint/lib/rules/no-restricted-globals'); - 'object-curly-spacing': typeof import('eslint/lib/rules/object-curly-spacing'); 'prefer-const': typeof import('eslint/lib/rules/prefer-const'); 'prefer-destructuring': typeof import('eslint/lib/rules/prefer-destructuring'); - quotes: typeof import('eslint/lib/rules/quotes'); - semi: typeof import('eslint/lib/rules/semi'); - 'space-before-blocks': typeof import('eslint/lib/rules/space-before-blocks'); - 'space-infix-ops': typeof import('eslint/lib/rules/space-infix-ops'); strict: typeof import('eslint/lib/rules/strict'); /* eslint-enable @typescript-eslint/consistent-type-imports */ } diff --git a/packages/eslint-plugin/src/util/getFunctionHeadLoc.ts b/packages/eslint-plugin/src/util/getFunctionHeadLoc.ts index cc7b5c5bd7a1..56227d7ed11d 100644 --- a/packages/eslint-plugin/src/util/getFunctionHeadLoc.ts +++ b/packages/eslint-plugin/src/util/getFunctionHeadLoc.ts @@ -153,8 +153,8 @@ export function getFunctionHeadLoc( sourceCode: TSESLint.SourceCode, ): TSESTree.SourceLocation { const parent = node.parent; - let start = null; - let end = null; + let start: TSESTree.Position | null = null; + let end: TSESTree.Position | null = null; if ( parent.type === AST_NODE_TYPES.MethodDefinition || diff --git a/packages/eslint-plugin/src/util/isTypeImport.ts b/packages/eslint-plugin/src/util/isTypeImport.ts new file mode 100644 index 000000000000..6e8af22d751c --- /dev/null +++ b/packages/eslint-plugin/src/util/isTypeImport.ts @@ -0,0 +1,27 @@ +import type { + Definition, + ImportBindingDefinition, +} from '@typescript-eslint/scope-manager'; +import { DefinitionType } from '@typescript-eslint/scope-manager'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; + +/** + * Determine whether a variable definition is a type import. e.g.: + * + * ```ts + * import type { Foo } from 'foo'; + * import { type Bar } from 'bar'; + * ``` + * + * @param definition - The variable definition to check. + */ +export function isTypeImport( + definition?: Definition, +): definition is ImportBindingDefinition { + return ( + definition?.type === DefinitionType.ImportBinding && + (definition.parent.importKind === 'type' || + (definition.node.type === AST_NODE_TYPES.ImportSpecifier && + definition.node.importKind === 'type')) + ); +} diff --git a/packages/eslint-plugin/src/util/referenceContainsTypeQuery.ts b/packages/eslint-plugin/src/util/referenceContainsTypeQuery.ts new file mode 100644 index 000000000000..60872beeddb5 --- /dev/null +++ b/packages/eslint-plugin/src/util/referenceContainsTypeQuery.ts @@ -0,0 +1,20 @@ +import type { TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; + +/** + * Recursively checks whether a given reference has a type query declaration among its parents + */ +export function referenceContainsTypeQuery(node: TSESTree.Node): boolean { + switch (node.type) { + case AST_NODE_TYPES.TSTypeQuery: + return true; + + case AST_NODE_TYPES.TSQualifiedName: + case AST_NODE_TYPES.Identifier: + return referenceContainsTypeQuery(node.parent); + + default: + // if we find a different node, there's no chance that we're in a TSTypeQuery + return false; + } +} diff --git a/packages/eslint-plugin/tests/RuleTester.ts b/packages/eslint-plugin/tests/RuleTester.ts index 1ad34648a8b0..0fce4ae04394 100644 --- a/packages/eslint-plugin/tests/RuleTester.ts +++ b/packages/eslint-plugin/tests/RuleTester.ts @@ -44,7 +44,11 @@ export function batchedSingleLineTests< MessageIds extends string, Options extends readonly unknown[], >( - options: InvalidTestCase | ValidTestCase, + options: + | (Omit, 'output'> & { + output?: string | null; + }) + | ValidTestCase, ): (InvalidTestCase | ValidTestCase)[] { // -- eslint counts lines from 1 const lineOffset = options.code.startsWith('\n') ? 2 : 1; diff --git a/packages/eslint-plugin/tests/configs.test.ts b/packages/eslint-plugin/tests/configs.test.ts index db9130e18011..22da970a907d 100644 --- a/packages/eslint-plugin/tests/configs.test.ts +++ b/packages/eslint-plugin/tests/configs.test.ts @@ -59,14 +59,14 @@ function filterAndMapRuleConfigs({ if (recommendations) { 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); + default: + return false; } }); } diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/ban-types.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/ban-types.shot deleted file mode 100644 index f96ac650ffa5..000000000000 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/ban-types.shot +++ /dev/null @@ -1,72 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Validating rule docs ban-types.mdx code examples ESLint output 1`] = ` -"Incorrect - -// use lower-case primitives for consistency -const str: String = 'foo'; - ~~~~~~ Don't use \`String\` as a type. Use string instead -const bool: Boolean = true; - ~~~~~~~ Don't use \`Boolean\` as a type. Use boolean instead -const num: Number = 1; - ~~~~~~ Don't use \`Number\` as a type. Use number instead -const symb: Symbol = Symbol('foo'); - ~~~~~~ Don't use \`Symbol\` as a type. Use symbol instead -const bigInt: BigInt = 1n; - ~~~~~~ Don't use \`BigInt\` as a type. Use bigint instead - -// use a proper function type -const func: Function = () => 1; - ~~~~~~~~ Don't use \`Function\` as a type. The \`Function\` type accepts any function-like value. - It provides no type safety when calling the function, which can be a common source of bugs. - It also accepts things like class declarations, which will throw at runtime as they will not be called with \`new\`. - If you are expecting the function to accept certain arguments, you should explicitly define the function shape. - -// use safer object types -const lowerObj: Object = {}; - ~~~~~~ Don't use \`Object\` as a type. The \`Object\` type actually means "any non-nullish value", so it is marginally better than \`unknown\`. - - If you want a type meaning "any object", you probably want \`object\` instead. - - If you want a type meaning "any value", you probably want \`unknown\` instead. - - If you really want a type meaning "any non-nullish value", you probably want \`NonNullable\` instead. -const capitalObj: Object = { a: 'string' }; - ~~~~~~ Don't use \`Object\` as a type. The \`Object\` type actually means "any non-nullish value", so it is marginally better than \`unknown\`. - - If you want a type meaning "any object", you probably want \`object\` instead. - - If you want a type meaning "any value", you probably want \`unknown\` instead. - - If you really want a type meaning "any non-nullish value", you probably want \`NonNullable\` instead. - -const curly1: {} = 1; - ~~ Don't use \`{}\` as a type. \`{}\` actually means "any non-nullish value". - - If you want a type meaning "any object", you probably want \`object\` instead. - - If you want a type meaning "any value", you probably want \`unknown\` instead. - - If you want a type meaning "empty object", you probably want \`Record\` instead. - - If you really want a type meaning "any non-nullish value", you probably want \`NonNullable\` instead. -const curly2: {} = { a: 'string' }; - ~~ Don't use \`{}\` as a type. \`{}\` actually means "any non-nullish value". - - If you want a type meaning "any object", you probably want \`object\` instead. - - If you want a type meaning "any value", you probably want \`unknown\` instead. - - If you want a type meaning "empty object", you probably want \`Record\` instead. - - If you really want a type meaning "any non-nullish value", you probably want \`NonNullable\` instead. -" -`; - -exports[`Validating rule docs ban-types.mdx code examples ESLint output 2`] = ` -"Correct - -// use lower-case primitives for consistency -const str: string = 'foo'; -const bool: boolean = true; -const num: number = 1; -const symb: symbol = Symbol('foo'); -const bigInt: bigint = 1n; - -// use a proper function type -const func: () => number = () => 1; - -// use safer object types -const lowerObj: object = {}; -const capitalObj: { a: string } = { a: 'string' }; - -const curly1: number = 1; -const curly2: Record<'a', string> = { a: 'string' }; -" -`; diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/lines-between-class-members.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/lines-between-class-members.shot deleted file mode 100644 index 551df7110d76..000000000000 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/lines-between-class-members.shot +++ /dev/null @@ -1,33 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Validating rule docs lines-between-class-members.mdx code examples ESLint output 1`] = ` -"Options: "always", { "exceptAfterOverload": true } - -class foo { - bar(a: string): void; - bar(a: string, b: string): void; - bar(a: string, b: string) {} - - baz() {} - - qux() {} -} -" -`; - -exports[`Validating rule docs lines-between-class-members.mdx code examples ESLint output 2`] = ` -"Options: "always", { "exceptAfterOverload": false } - -class foo { - bar(a: string): void; - - bar(a: string, b: string): void; - - bar(a: string, b: string) {} - - baz() {} - - qux() {} -} -" -`; diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/member-delimiter-style.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/member-delimiter-style.shot deleted file mode 100644 index b1b5eb48f627..000000000000 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/member-delimiter-style.shot +++ /dev/null @@ -1,58 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Validating rule docs member-delimiter-style.mdx code examples ESLint output 1`] = ` -"Incorrect - -// missing semicolon delimiter -interface Foo { - name: string - ~ Expected a semicolon. - greet(): string - ~ Expected a semicolon. -} - -// using incorrect delimiter -interface Bar { - name: string, - ~ Expected a semicolon. - greet(): string, - ~ Expected a semicolon. -} - -// missing last member delimiter -interface Baz { - name: string; - greet(): string - ~ Expected a semicolon. -} - -// incorrect delimiter -type FooBar = { name: string, greet(): string } - ~ Expected a semicolon. - -// last member should not have delimiter -type FooBar = { name: string; greet(): string; } - ~ Unexpected separator (;). -" -`; - -exports[`Validating rule docs member-delimiter-style.mdx code examples ESLint output 2`] = ` -"Correct - -interface Foo { - name: string; - greet(): string; -} - -interface Foo { name: string } - -type Bar = { - name: string; - greet(): string; -} - -type Bar = { name: string } - -type FooBar = { name: string; greet(): string } -" -`; diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-empty-object-type.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-empty-object-type.shot index 1dacb3b537d8..15ee654ff40a 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-empty-object-type.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-empty-object-type.shot @@ -61,20 +61,7 @@ type TypeWith = { property: boolean }; `; exports[`Validating rule docs no-empty-object-type.mdx code examples ESLint output 3`] = ` -"Incorrect -Options: { "allowInterfaces": "with-single-extends" } - -interface Foo {} - ~~~ An empty interface declaration allows any non-nullish value, including literals like \`0\` and \`""\`. - - If that's what you want, disable this lint rule with an inline comment or configure the 'allowInterfaces' rule option. - - If you want a type meaning "any object", you probably want \`object\` instead. - - If you want a type meaning "any value", you probably want \`unknown\` instead. -" -`; - -exports[`Validating rule docs no-empty-object-type.mdx code examples ESLint output 4`] = ` -"Correct -Options: { "allowInterfaces": "with-single-extends" } +"Options: { "allowInterfaces": "with-single-extends" } interface Base { value: boolean; @@ -84,27 +71,7 @@ interface Derived extends Base {} " `; -exports[`Validating rule docs no-empty-object-type.mdx code examples ESLint output 5`] = ` -"Incorrect -Options: { "allowObjectTypes": "always" } - -interface Base {} - ~~~~ An empty interface declaration allows any non-nullish value, including literals like \`0\` and \`""\`. - - If that's what you want, disable this lint rule with an inline comment or configure the 'allowInterfaces' rule option. - - If you want a type meaning "any object", you probably want \`object\` instead. - - If you want a type meaning "any value", you probably want \`unknown\` instead. -" -`; - -exports[`Validating rule docs no-empty-object-type.mdx code examples ESLint output 6`] = ` -"Correct -Options: { "allowObjectTypes": "always" } - -type Base = {}; -" -`; - -exports[`Validating rule docs no-empty-object-type.mdx code examples ESLint output 7`] = ` +exports[`Validating rule docs no-empty-object-type.mdx code examples ESLint output 4`] = ` "Incorrect Options: { "allowWithName": "Props$" } @@ -122,7 +89,7 @@ type TypeValue = {}; " `; -exports[`Validating rule docs no-empty-object-type.mdx code examples ESLint output 8`] = ` +exports[`Validating rule docs no-empty-object-type.mdx code examples ESLint output 5`] = ` "Correct Options: { "allowWithName": "Props$" } diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-floating-promises.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-floating-promises.shot index 5dafc5366a02..14872659bec0 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-floating-promises.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-floating-promises.shot @@ -146,3 +146,25 @@ function returnsSafePromise(): SafePromise { returnsSafePromise(); " `; + +exports[`Validating rule docs no-floating-promises.mdx code examples ESLint output 9`] = ` +"Incorrect +Options: {"allowForKnownSafeCalls":[{"from":"file","name":"safe","path":"input.ts"}]} + +declare function unsafe(...args: unknown[]): Promise; + +unsafe('...', () => {}); +~~~~~~~~~~~~~~~~~~~~~~~~ Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the \`void\` operator. +" +`; + +exports[`Validating rule docs no-floating-promises.mdx code examples ESLint output 10`] = ` +"Correct +Options: {"allowForKnownSafeCalls":[{"from":"file","name":"safe","path":"input.ts"}]} + +declare function safe(...args: unknown[]): Promise; + +safe('...', () => {}); +~~~~~~~~~~~~~~~~~~~~~~ Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the \`void\` operator. +" +`; diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-require-imports.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-require-imports.shot index 027cb637f45c..54cb8f1ddd2e 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-require-imports.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-require-imports.shot @@ -37,3 +37,25 @@ Options: { "allow": ["/package.json$"] } console.log(require('../package.json').version); " `; + +exports[`Validating rule docs no-require-imports.mdx code examples ESLint output 5`] = ` +"Incorrect +Options: { "allowAsImport": true } + +var foo = require('foo'); + ~~~~~~~~~~~~~~ A \`require()\` style import is forbidden. +const foo = require('foo'); + ~~~~~~~~~~~~~~ A \`require()\` style import is forbidden. +let foo = require('foo'); + ~~~~~~~~~~~~~~ A \`require()\` style import is forbidden. +" +`; + +exports[`Validating rule docs no-require-imports.mdx code examples ESLint output 6`] = ` +"Correct +Options: { "allowAsImport": true } + +import foo = require('foo'); +import foo from 'foo'; +" +`; diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-throw-literal.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-throw-literal.shot deleted file mode 100644 index 71c60ea2ec68..000000000000 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-throw-literal.shot +++ /dev/null @@ -1,78 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Validating rule docs no-throw-literal.md code examples ESLint output snapshot 1`] = ` -"Incorrect - -throw 'error'; - ~~~~~~~ Expected an error object to be thrown. - -throw 0; - ~ Expected an error object to be thrown. - -throw undefined; - ~~~~~~~~~ Do not throw undefined. - -throw null; - ~~~~ Expected an error object to be thrown. - -const err1 = new Error(); -throw 'an ' + err1; - ~~~~~~~~~~~~ Expected an error object to be thrown. - -const err2 = new Error(); -throw \`\${err2}\`; - ~~~~~~~~~ Expected an error object to be thrown. - -const err3 = ''; -throw err3; - ~~~~ Expected an error object to be thrown. - -function getErr() { - return ''; -} -throw getErr(); - ~~~~~~~~ Expected an error object to be thrown. - -const foo = { - bar: '', -}; -throw foo.bar; - ~~~~~~~ Expected an error object to be thrown. -" -`; - -exports[`Validating rule docs no-throw-literal.md code examples ESLint output snapshot 2`] = ` -"Correct - -throw new Error(); - -throw new Error('error'); - -const e = new Error('error'); -throw e; - -try { - throw new Error('error'); -} catch (e) { - throw e; -} - -const err = new Error(); -throw err; - -function getErr() { - return new Error(); -} -throw getErr(); - -const foo = { - bar: new Error(), -}; -throw foo.bar; - -class CustomError extends Error { - // ... -} -throw new CustomError(); -" -`; diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-useless-template-literals.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-useless-template-literals.shot deleted file mode 100644 index 3cc841703a98..000000000000 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-useless-template-literals.shot +++ /dev/null @@ -1,46 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Validating rule docs no-useless-template-literals.mdx code examples ESLint output 1`] = ` -"Incorrect - -const ab1 = \`\${'a'}\${'b'}\`; - ~~~ Template literal expression is unnecessary and can be simplified. - ~~~ Template literal expression is unnecessary and can be simplified. -const ab2 = \`a\${'b'}\`; - ~~~ Template literal expression is unnecessary and can be simplified. - -const stringWithNumber = \`\${'1 + 1 = '}\${2}\`; - ~~~~~~~~~~ Template literal expression is unnecessary and can be simplified. - ~ Template literal expression is unnecessary and can be simplified. - -const stringWithBoolean = \`\${'true is '}\${true}\`; - ~~~~~~~~~~ Template literal expression is unnecessary and can be simplified. - ~~~~ Template literal expression is unnecessary and can be simplified. - -const text = 'a'; -const wrappedText = \`\${text}\`; - ~~~~ Template literal expression is unnecessary and can be simplified. - -declare const intersectionWithString: string & { _brand: 'test-brand' }; -const wrappedIntersection = \`\${intersectionWithString}\`; - ~~~~~~~~~~~~~~~~~~~~~~ Template literal expression is unnecessary and can be simplified. -" -`; - -exports[`Validating rule docs no-useless-template-literals.mdx code examples ESLint output 2`] = ` -"Correct - -const ab1 = 'ab'; -const ab2 = 'ab'; - -const stringWithNumber = \`1 + 1 = 2\`; - -const stringWithBoolean = \`true is true\`; - -const text = 'a'; -const wrappedText = text; - -declare const intersectionWithString: string & { _brand: 'test-brand' }; -const wrappedIntersection = intersectionWithString; -" -`; diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/space-before-blocks.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/space-before-blocks.shot deleted file mode 100644 index 74158461fac1..000000000000 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/space-before-blocks.shot +++ /dev/null @@ -1,33 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Validating rule docs space-before-blocks.mdx code examples ESLint output 1`] = ` -"Incorrect - -enum Breakpoint{ - ~ Missing space before opening brace. - Large, - Medium, -} - -interface State{ - ~ Missing space before opening brace. - currentBreakpoint: Breakpoint; -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -} -~ -" -`; - -exports[`Validating rule docs space-before-blocks.mdx code examples ESLint output 2`] = ` -"Correct - -enum Breakpoint { - Large, - Medium, -} - -interface State { - currentBreakpoint: Breakpoint; -} -" -`; diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/type-annotation-spacing.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/type-annotation-spacing.shot deleted file mode 100644 index 765805e9bbab..000000000000 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/type-annotation-spacing.shot +++ /dev/null @@ -1,311 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Validating rule docs type-annotation-spacing.mdx code examples ESLint output 1`] = ` -"Incorrect - -let foo:string = "bar"; - ~ Expected a space after the ':'. -let foo :string = "bar"; - ~ Expected a space after the ':'. - ~ Unexpected space before the ':'. -let foo : string = "bar"; - ~ Unexpected space before the ':'. - -function foo():string {} - ~ Expected a space after the ':'. -function foo() :string {} - ~ Expected a space after the ':'. - ~ Unexpected space before the ':'. -function foo() : string {} - ~ Unexpected space before the ':'. - -class Foo { - name:string; - ~ Expected a space after the ':'. -} - -class Foo { - name :string; - ~ Expected a space after the ':'. - ~ Unexpected space before the ':'. -} - -class Foo { - name : string; - ~ Unexpected space before the ':'. -} - -type Foo = ()=>{}; - ~~ Expected a space after the '=>'. - ~~ Expected a space before the '=>'. -type Foo = () =>{}; - ~~ Expected a space after the '=>'. -type Foo = ()=> {}; - ~~ Expected a space before the '=>'. -" -`; - -exports[`Validating rule docs type-annotation-spacing.mdx code examples ESLint output 2`] = ` -"Correct - -let foo: string = "bar"; - -function foo(): string {} - -class Foo { - name: string; -} - -type Foo = () => {}; -" -`; - -exports[`Validating rule docs type-annotation-spacing.mdx code examples ESLint output 3`] = ` -"Incorrect -Options: { "before": false, "after": true } - -let foo:string = "bar"; - ~ Expected a space after the ':'. -let foo :string = "bar"; - ~ Expected a space after the ':'. - ~ Unexpected space before the ':'. -let foo : string = "bar"; - ~ Unexpected space before the ':'. - -function foo():string {} - ~ Expected a space after the ':'. -function foo() :string {} - ~ Expected a space after the ':'. - ~ Unexpected space before the ':'. -function foo() : string {} - ~ Unexpected space before the ':'. - -class Foo { - name:string; - ~ Expected a space after the ':'. -} - -class Foo { - name :string; - ~ Expected a space after the ':'. - ~ Unexpected space before the ':'. -} - -class Foo { - name : string; - ~ Unexpected space before the ':'. -} - -type Foo = ()=>{}; - ~~ Expected a space after the '=>'. -type Foo = () =>{}; - ~~ Expected a space after the '=>'. - ~~ Unexpected space before the '=>'. -type Foo = () => {}; - ~~ Unexpected space before the '=>'. -" -`; - -exports[`Validating rule docs type-annotation-spacing.mdx code examples ESLint output 4`] = ` -"Correct -Options: { "before": false, "after": true } - -let foo: string = "bar"; - -function foo(): string {} - -class Foo { - name: string; -} - -type Foo = ()=> {}; -" -`; - -exports[`Validating rule docs type-annotation-spacing.mdx code examples ESLint output 5`] = ` -"Incorrect -Options: { "before": true, "after": true } - -let foo: string = "bar"; - ~ Expected a space before the ':'. -let foo:string = "bar"; - ~ Expected a space after the ':'. - ~ Expected a space before the ':'. -let foo :string = "bar"; - ~ Expected a space after the ':'. - -function foo(): string {} - ~ Expected a space before the ':'. -function foo():string {} - ~ Expected a space after the ':'. - ~ Expected a space before the ':'. -function foo() :string {} - ~ Expected a space after the ':'. - -class Foo { - name: string; - ~ Expected a space before the ':'. -} - -class Foo { - name:string; - ~ Expected a space after the ':'. - ~ Expected a space before the ':'. -} - -class Foo { - name :string; - ~ Expected a space after the ':'. -} - -type Foo = ()=>{}; - ~~ Expected a space after the '=>'. - ~~ Expected a space before the '=>'. -type Foo = () =>{}; - ~~ Expected a space after the '=>'. -type Foo = ()=> {}; - ~~ Expected a space before the '=>'. -" -`; - -exports[`Validating rule docs type-annotation-spacing.mdx code examples ESLint output 6`] = ` -"Correct -Options: { "before": true, "after": true } - -let foo : string = "bar"; - -function foo() : string {} - -class Foo { - name : string; -} - -type Foo = () => {}; -" -`; - -exports[`Validating rule docs type-annotation-spacing.mdx code examples ESLint output 7`] = ` -"Incorrect -Options: {"before":false,"after":false,"overrides":{"colon":{"before":true,"after":true}}} - -let foo: string = "bar"; - ~ Expected a space before the ':'. -let foo:string = "bar"; - ~ Expected a space after the ':'. - ~ Expected a space before the ':'. -let foo :string = "bar"; - ~ Expected a space after the ':'. - -function foo(): string {} - ~ Expected a space before the ':'. -function foo():string {} - ~ Expected a space after the ':'. - ~ Expected a space before the ':'. -function foo() :string {} - ~ Expected a space after the ':'. - -class Foo { - name: string; - ~ Expected a space before the ':'. -} - -class Foo { - name:string; - ~ Expected a space after the ':'. - ~ Expected a space before the ':'. -} - -class Foo { - name :string; - ~ Expected a space after the ':'. -} - -type Foo = () =>{}; - ~~ Unexpected space before the '=>'. -type Foo = ()=> {}; - ~~ Unexpected space after the '=>'. -type Foo = () => {}; - ~~ Unexpected space after the '=>'. - ~~ Unexpected space before the '=>'. -" -`; - -exports[`Validating rule docs type-annotation-spacing.mdx code examples ESLint output 8`] = ` -"Correct -Options: {"before":false,"after":false,"overrides":{"colon":{"before":true,"after":true}}} - -let foo : string = "bar"; - -function foo() : string {} - -class Foo { - name : string; -} - -type Foo = { - name : (name : string)=>string; -} - -type Foo = ()=>{}; -" -`; - -exports[`Validating rule docs type-annotation-spacing.mdx code examples ESLint output 9`] = ` -"Incorrect -Options: {"before":false,"after":false,"overrides":{"arrow":{"before":true,"after":true}}} - -let foo: string = "bar"; - ~ Unexpected space after the ':'. -let foo : string = "bar"; - ~ Unexpected space after the ':'. - ~ Unexpected space before the ':'. -let foo :string = "bar"; - ~ Unexpected space before the ':'. - -function foo(): string {} - ~ Unexpected space after the ':'. -function foo():string {} -function foo() :string {} - ~ Unexpected space before the ':'. - -class Foo { - name: string; - ~ Unexpected space after the ':'. -} - -class Foo { - name : string; - ~ Unexpected space after the ':'. - ~ Unexpected space before the ':'. -} - -class Foo { - name :string; - ~ Unexpected space before the ':'. -} - -type Foo = ()=>{}; - ~~ Expected a space after the '=>'. - ~~ Expected a space before the '=>'. -type Foo = () =>{}; - ~~ Expected a space after the '=>'. -type Foo = ()=> {}; - ~~ Expected a space before the '=>'. -" -`; - -exports[`Validating rule docs type-annotation-spacing.mdx code examples ESLint output 10`] = ` -"Correct -Options: {"before":false,"after":false,"overrides":{"arrow":{"before":true,"after":true}}} - -let foo:string = "bar"; - -function foo():string {} - -class Foo { - name:string; -} - -type Foo = () => {}; -" -`; diff --git a/packages/eslint-plugin/tests/docs.test.ts b/packages/eslint-plugin/tests/docs.test.ts index daae32f5e17a..a54f57a7bee4 100644 --- a/packages/eslint-plugin/tests/docs.test.ts +++ b/packages/eslint-plugin/tests/docs.test.ts @@ -100,7 +100,7 @@ function renderLintResults(code: string, errors: Linter.LintMessage[]): string { return output.join('\n').trim() + '\n'; } -const linter = new Linter(); +const linter = new Linter({ configType: 'eslintrc' }); linter.defineParser('@typescript-eslint/parser', tseslintParser); const eslintOutputSnapshotFolder = path.resolve( @@ -125,18 +125,49 @@ describe('Validating rule docs', () => { unistUtilVisit = await dynamicImport('unist-util-visit'); }); + const oldStylisticRules = [ + 'block-spacing.md', + 'brace-style.md', + 'camelcase.md', + 'comma-dangle.md', + 'comma-spacing.md', + 'func-call-spacing.md', + 'indent.md', + 'key-spacing.md', + 'keyword-spacing.md', + 'lines-around-comment.md', + 'lines-between-class-members.md', + 'member-delimiter-style.md', + 'no-extra-parens.md', + 'no-extra-semi.md', + 'object-curly-spacing.md', + 'padding-line-between-statements.md', + 'quotes.md', + 'semi.md', + 'space-before-blocks.md', + 'space-before-function-paren.md', + 'space-infix-ops.md', + 'type-annotation-spacing.md', + ]; + const ignoredFiles = new Set([ 'README.md', 'TEMPLATE.md', // These rule docs were left behind on purpose for legacy reasons. See the // comments in the files for more information. - 'camelcase.md', + 'ban-types.md', 'no-duplicate-imports.mdx', 'no-parameter-properties.mdx', + 'no-useless-template-literals.mdx', 'sort-type-union-intersection-members.mdx', + ...oldStylisticRules, ]); - const rulesWithComplexOptions = new Set(['array-type', 'member-ordering']); + const rulesWithComplexOptions = new Set([ + 'array-type', + 'member-ordering', + 'no-restricted-types', + ]); // TODO: whittle this list down to as few as possible const rulesWithComplexOptionHeadings = new Set([ @@ -151,6 +182,7 @@ describe('Validating rule docs', () => { 'no-confusing-void-expression', 'no-duplicate-type-constituents', 'no-empty-interface', + 'no-empty-object-type', 'no-explicit-any', 'no-floating-promises', 'no-inferrable-types', @@ -270,8 +302,7 @@ describe('Validating rule docs', () => { if ( !rulesWithComplexOptions.has(ruleName) && Array.isArray(schema) && - !rule.meta.docs?.extendsBaseRule && - rule.meta.type !== 'layout' + !rule.meta.docs?.extendsBaseRule ) { describe('rule options', () => { const headingsAfterOptions = headings.slice( @@ -435,6 +466,7 @@ describe('Validating rule docs', () => { { parser: '@typescript-eslint/parser', parserOptions: { + disallowAutomaticSingleRunInference: true, tsconfigRootDir: rootPath, project: './tsconfig.json', }, diff --git a/packages/eslint-plugin/tests/eslint-rules/arrow-parens.test.ts b/packages/eslint-plugin/tests/eslint-rules/arrow-parens.test.ts index 825eceb29d97..9f27b18b35ec 100644 --- a/packages/eslint-plugin/tests/eslint-rules/arrow-parens.test.ts +++ b/packages/eslint-plugin/tests/eslint-rules/arrow-parens.test.ts @@ -4,9 +4,7 @@ import { getESLintCoreRule } from '../../src/util/getESLintCoreRule'; const rule = getESLintCoreRule('arrow-parens'); -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', -}); +const ruleTester = new RuleTester(); ruleTester.run('arrow-parens', rule, { valid: [ diff --git a/packages/eslint-plugin/tests/eslint-rules/no-dupe-args.test.ts b/packages/eslint-plugin/tests/eslint-rules/no-dupe-args.test.ts index 054d19140bf7..0050a131761b 100644 --- a/packages/eslint-plugin/tests/eslint-rules/no-dupe-args.test.ts +++ b/packages/eslint-plugin/tests/eslint-rules/no-dupe-args.test.ts @@ -4,14 +4,7 @@ import { getESLintCoreRule } from '../../src/util/getESLintCoreRule'; const rule = getESLintCoreRule('no-dupe-args'); -const ruleTester = new RuleTester({ - parserOptions: { - ecmaVersion: 6, - sourceType: 'module', - ecmaFeatures: {}, - }, - parser: '@typescript-eslint/parser', -}); +const ruleTester = new RuleTester(); ruleTester.run('no-dupe-args', rule, { valid: [ diff --git a/packages/eslint-plugin/tests/eslint-rules/no-implicit-globals.test.ts b/packages/eslint-plugin/tests/eslint-rules/no-implicit-globals.test.ts index ba4d5cc1a430..ebac70b7d2f7 100644 --- a/packages/eslint-plugin/tests/eslint-rules/no-implicit-globals.test.ts +++ b/packages/eslint-plugin/tests/eslint-rules/no-implicit-globals.test.ts @@ -3,13 +3,7 @@ import { RuleTester } from '@typescript-eslint/rule-tester'; import { getESLintCoreRule } from '../../src/util/getESLintCoreRule'; const rule = getESLintCoreRule('no-implicit-globals'); -const ruleTester = new RuleTester({ - parserOptions: { - ecmaVersion: 6, - sourceType: 'module', - }, - parser: '@typescript-eslint/parser', -}); +const ruleTester = new RuleTester(); ruleTester.run('no-implicit-globals', rule, { valid: [ diff --git a/packages/eslint-plugin/tests/eslint-rules/no-restricted-globals.test.ts b/packages/eslint-plugin/tests/eslint-rules/no-restricted-globals.test.ts index b015020fa5e1..2958d9d13cfd 100644 --- a/packages/eslint-plugin/tests/eslint-rules/no-restricted-globals.test.ts +++ b/packages/eslint-plugin/tests/eslint-rules/no-restricted-globals.test.ts @@ -4,14 +4,7 @@ import { getESLintCoreRule } from '../../src/util/getESLintCoreRule'; const rule = getESLintCoreRule('no-restricted-globals'); -const ruleTester = new RuleTester({ - parserOptions: { - ecmaVersion: 6, - sourceType: 'module', - ecmaFeatures: {}, - }, - parser: '@typescript-eslint/parser', -}); +const ruleTester = new RuleTester(); ruleTester.run('no-restricted-globals', rule, { valid: [ diff --git a/packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts b/packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts index a6ce89cc8d24..2180223d1eab 100644 --- a/packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts +++ b/packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts @@ -4,14 +4,7 @@ import { getESLintCoreRule } from '../../src/util/getESLintCoreRule'; const rule = getESLintCoreRule('no-undef'); -const ruleTester = new RuleTester({ - parserOptions: { - ecmaVersion: 6, - sourceType: 'module', - ecmaFeatures: {}, - }, - parser: '@typescript-eslint/parser', -}); +const ruleTester = new RuleTester(); ruleTester.run('no-undef', rule, { valid: [ @@ -47,8 +40,10 @@ const links = document.querySelectorAll(selector) as NodeListOf; /*globals document, selector */ const links = document.querySelectorAll(selector) as NodeListOf; `, - parserOptions: { - lib: ['dom'], + languageOptions: { + parserOptions: { + lib: ['dom'], + }, }, }, // https://github.com/eslint/typescript-eslint-parser/issues/437 @@ -155,9 +150,11 @@ let test: unknown; function Foo() {} ; `, - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, }, @@ -167,9 +164,11 @@ type T = 1; function Foo() {} />; `, - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, }, @@ -179,9 +178,11 @@ const x = 1; function Foo() {} ; `, - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, }, @@ -191,9 +192,11 @@ const x = {}; function Foo() {} ; `, - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, }, @@ -203,9 +206,11 @@ const x = {}; function Foo() {} {x}; `, - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, }, @@ -214,9 +219,11 @@ function Foo() {} code: `
; `, - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, }, @@ -224,9 +231,11 @@ function Foo() {} code: ` ; `, - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, }, @@ -301,9 +310,11 @@ class Foo { }, { code: ';', - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, errors: [ @@ -322,9 +333,11 @@ class Foo { function Foo() {} ; `, - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, errors: [ @@ -343,9 +356,11 @@ function Foo() {} function Foo() {} ; `, - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, errors: [ @@ -364,9 +379,11 @@ function Foo() {} function Foo() {} />; `, - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, errors: [ @@ -385,9 +402,11 @@ function Foo() {} function Foo() {} {x}; `, - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, errors: [ diff --git a/packages/eslint-plugin/tests/eslint-rules/prefer-const.test.ts b/packages/eslint-plugin/tests/eslint-rules/prefer-const.test.ts index e9d8350ff097..5223be374c4a 100644 --- a/packages/eslint-plugin/tests/eslint-rules/prefer-const.test.ts +++ b/packages/eslint-plugin/tests/eslint-rules/prefer-const.test.ts @@ -4,9 +4,7 @@ import { getESLintCoreRule } from '../../src/util/getESLintCoreRule'; const rule = getESLintCoreRule('prefer-const'); -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', -}); +const ruleTester = new RuleTester(); ruleTester.run('prefer-const', rule, { valid: [ diff --git a/packages/eslint-plugin/tests/eslint-rules/strict.test.ts b/packages/eslint-plugin/tests/eslint-rules/strict.test.ts index a3146c36d7bd..e25d6f7a44da 100644 --- a/packages/eslint-plugin/tests/eslint-rules/strict.test.ts +++ b/packages/eslint-plugin/tests/eslint-rules/strict.test.ts @@ -4,13 +4,7 @@ import { getESLintCoreRule } from '../../src/util/getESLintCoreRule'; const rule = getESLintCoreRule('strict'); -const ruleTester = new RuleTester({ - parserOptions: { - ecmaVersion: 6, - sourceType: 'module', - }, - parser: '@typescript-eslint/parser', -}); +const ruleTester = new RuleTester(); ruleTester.run('strict', rule, { valid: [ @@ -33,8 +27,10 @@ window.whatevs = { }, }; `, - parserOptions: { - sourceType: 'script', + languageOptions: { + parserOptions: { + sourceType: 'script', + }, }, errors: [ { diff --git a/packages/eslint-plugin/tests/fixtures/indent/indent-invalid-fixture-1.js b/packages/eslint-plugin/tests/fixtures/indent/indent-invalid-fixture-1.js deleted file mode 100644 index f03507ff61ea..000000000000 --- a/packages/eslint-plugin/tests/fixtures/indent/indent-invalid-fixture-1.js +++ /dev/null @@ -1,530 +0,0 @@ -if (a) { - var b = c; - var d = e - * f; - var e = f; // <- -// -> - function g() { - if (h) { - var i = j; - } // <- - } // <- - - while (k) l++; - while (m) { - n--; // -> - } // <- - - do { - o = p + - q; // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS - o = p + - q; - } while(r); // <- - - for (var s in t) { - u++; - } - - for (;;) { - v++; // <- - } - - if ( w ) { - x++; - } else if (y) { - z++; // <- - aa++; - } else { // <- - bb++; // -> -} // -> -} - -/**/var b; // NO ERROR: single line multi-line comments followed by code is OK -/* - * - */ var b; // NO ERROR: multi-line comments followed by code is OK - -var arr = [ - a, - b, - c, - function (){ - d - }, // <- - {}, - { - a: b, - c: d, - d: e - }, - [ - f, - g, - h, - i - ], - [j] -]; - -var obj = { - a: { - b: { - c: d, - e: f, - g: h + - i // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS - } - }, - g: [ - h, - i, - j, - k - ] -}; - -var arrObject = {a:[ - a, - b, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE - c -]}; - -var objArray = [{ - a: b, - b: c, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE - c: d -}]; - -var arrArray = [[ - a, - b, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE - c -]]; - -var objObject = {a:{ - a: b, - b: c, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE - c: d -}}; - - -switch (a) { - case 'a': - var a = 'b'; // -> - break; - case 'b': - var a = 'b'; - break; - case 'c': - var a = 'b'; // <- - break; - case 'd': - var a = 'b'; - break; // -> - case 'f': - var a = 'b'; - break; - case 'g': { - var a = 'b'; - break; - } - case 'z': - default: - break; // <- -} - -a.b('hi') - .c(a.b()) // <- - .d(); // <- - -if ( a ) { - if ( b ) { -d.e(f) // -> - .g() // -> - .h(); // -> - - i.j(m) - .k() // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS - .l(); // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS - - n.o(p) // <- - .q() // <- - .r(); // <- - } -} - -var a = b, - c = function () { - h = i; // -> - j = k; - l = m; // <- - }, - e = { - f: g, - n: o, - p: q - }, - r = [ - s, - t, - u - ]; - -var a = function () { -b = c; // -> - d = e; - f = g; // <- -}; - -function c(a, b) { - if (a || (a && - b)) { // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS - return d; - } -} - -if ( a - || b ) { -var x; // -> - var c, - d = function(a, - b) { // <- - a; // -> - b; - c; // <- - } -} - - -a({ - d: 1 -}); - -a( -1 -); - -a( - b({ - d: 1 - }) -); - -a( - b( - c({ - d: 1, - e: 1, - f: 1 - }) - ) -); - -a({ d: 1 }); - -aa( - b({ // NO ERROR: CallExpression args not linted by default - c: d, // -> - e: f, - f: g - }) // -> -); - -aaaaaa( - b, - c, - { - d: a - } -); - -a(b, c, - d, e, - f, g // NO ERROR: alignment of arguments of callExpression not checked - ); // <- - -a( - ); // <- - -aaaaaa( - b, - c, { - d: a - }, { - e: f - } -); - -a.b() - .c(function(){ - var a; - }).d.e; - -if (a == 'b') { - if (c && d) e = f - else g('h').i('j') -} - -a = function (b, c) { - return a(function () { - var d = e - var f = g - var h = i - - if (!j) k('l', (m = n)) - if (o) p - else if (q) r - }) -} - -var a = function() { - "b" - .replace(/a/, "a") - .replace(/bc?/, function(e) { - return "b" + (e.f === 2 ? "c" : "f"); - }) - .replace(/d/, "d"); -}; - -$(b) - .on('a', 'b', function() { $(c).e('f'); }) - .on('g', 'h', function() { $(i).j('k'); }); - -a - .b('c', - 'd'); // NO ERROR: CallExpression args not linted by default - -a - .b('c', [ 'd', function(e) { - e++; - }]); - -var a = function() { - a++; - b++; // <- - c++; // <- - }, - b; - -var b = [ - a, - b, - c - ], - c; - -var c = { - a: 1, - b: 2, - c: 3 - }, - d; - -// holes in arrays indentation -x = [ - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1 -]; - -try { - a++; - b++; // <- -c++; // -> -} catch (d) { - e++; - f++; // <- -g++; // -> -} finally { - h++; - i++; // <- -j++; // -> -} - -if (array.some(function(){ - return true; -})) { -a++; // -> - b++; - c++; // <- -} - -var a = b.c(function() { - d++; - }), - e; - -switch (true) { - case (a - && b): -case (c // -> -&& d): - case (e // <- - && f): - case (g -&& h): - var i = j; // <- - var k = l; - var m = n; // -> -} - -if (a) { - b(); -} -else { -c(); // -> - d(); - e(); // <- -} - -if (a) b(); -else { -c(); // -> - d(); - e(); // <- -} - -if (a) { - b(); -} else c(); - -if (a) { - b(); -} -else c(); - -a(); - -if( "very very long multi line" + - "with weird indentation" ) { - b(); -a(); // -> - c(); // <- -} - -a( "very very long multi line" + - "with weird indentation", function() { - b(); -a(); // -> - c(); // <- - }); // <- - -a = function(content, dom) { - b(); - c(); // <- -d(); // -> -}; - -a = function(content, dom) { - b(); - c(); // <- - d(); // -> - }; - -a = function(content, dom) { - b(); // -> - }; - -a = function(content, dom) { -b(); // -> - }; - -a('This is a terribly long description youll ' + - 'have to read', function () { - b(); // <- - c(); // <- - }); // <- - -if ( - array.some(function(){ - return true; - }) -) { -a++; // -> - b++; - c++; // <- -} - -function c(d) { - return { - e: function(f, g) { - } - }; -} - -function a(b) { - switch(x) { - case 1: - if (foo) { - return 5; - } - } -} - -function a(b) { - switch(x) { - case 1: - c; - } -} - -function a(b) { - switch(x) { - case 1: c; - } -} - -function test() { - var a = 1; - { - a(); - } -} - -{ - a(); -} - -function a(b) { - switch(x) { - case 1: - { // <- - a(); // -> - } - break; - default: - { - b(); - } - } -} - -switch (a) { - default: - if (b) - c(); -} - -function test(x) { - switch (x) { - case 1: - return function() { - var a = 5; - return a; - }; - } -} - -switch (a) { - default: - if (b) - c(); -} diff --git a/packages/eslint-plugin/tests/fixtures/indent/indent-valid-fixture-1.js b/packages/eslint-plugin/tests/fixtures/indent/indent-valid-fixture-1.js deleted file mode 100644 index 5c298429f69d..000000000000 --- a/packages/eslint-plugin/tests/fixtures/indent/indent-valid-fixture-1.js +++ /dev/null @@ -1,530 +0,0 @@ -if (a) { - var b = c; - var d = e - * f; - var e = f; // <- - // -> - function g() { - if (h) { - var i = j; - } // <- - } // <- - - while (k) l++; - while (m) { - n--; // -> - } // <- - - do { - o = p + - q; // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS - o = p + - q; - } while(r); // <- - - for (var s in t) { - u++; - } - - for (;;) { - v++; // <- - } - - if ( w ) { - x++; - } else if (y) { - z++; // <- - aa++; - } else { // <- - bb++; // -> - } // -> -} - -/**/var b; // NO ERROR: single line multi-line comments followed by code is OK -/* - * - */ var b; // NO ERROR: multi-line comments followed by code is OK - -var arr = [ - a, - b, - c, - function (){ - d - }, // <- - {}, - { - a: b, - c: d, - d: e - }, - [ - f, - g, - h, - i - ], - [j] -]; - -var obj = { - a: { - b: { - c: d, - e: f, - g: h + - i // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS - } - }, - g: [ - h, - i, - j, - k - ] -}; - -var arrObject = {a:[ - a, - b, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE - c -]}; - -var objArray = [{ - a: b, - b: c, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE - c: d -}]; - -var arrArray = [[ - a, - b, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE - c -]]; - -var objObject = {a:{ - a: b, - b: c, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE - c: d -}}; - - -switch (a) { - case 'a': - var a = 'b'; // -> - break; - case 'b': - var a = 'b'; - break; - case 'c': - var a = 'b'; // <- - break; - case 'd': - var a = 'b'; - break; // -> - case 'f': - var a = 'b'; - break; - case 'g': { - var a = 'b'; - break; - } - case 'z': - default: - break; // <- -} - -a.b('hi') - .c(a.b()) // <- - .d(); // <- - -if ( a ) { - if ( b ) { - d.e(f) // -> - .g() // -> - .h(); // -> - - i.j(m) - .k() // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS - .l(); // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS - - n.o(p) // <- - .q() // <- - .r(); // <- - } -} - -var a = b, - c = function () { - h = i; // -> - j = k; - l = m; // <- - }, - e = { - f: g, - n: o, - p: q - }, - r = [ - s, - t, - u - ]; - -var a = function () { - b = c; // -> - d = e; - f = g; // <- -}; - -function c(a, b) { - if (a || (a && - b)) { // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS - return d; - } -} - -if ( a - || b ) { - var x; // -> - var c, - d = function(a, - b) { // <- - a; // -> - b; - c; // <- - } -} - - -a({ - d: 1 -}); - -a( -1 -); - -a( - b({ - d: 1 - }) -); - -a( - b( - c({ - d: 1, - e: 1, - f: 1 - }) - ) -); - -a({ d: 1 }); - -aa( - b({ // NO ERROR: CallExpression args not linted by default - c: d, // -> - e: f, - f: g - }) // -> -); - -aaaaaa( - b, - c, - { - d: a - } -); - -a(b, c, - d, e, - f, g // NO ERROR: alignment of arguments of callExpression not checked -); // <- - -a( -); // <- - -aaaaaa( - b, - c, { - d: a - }, { - e: f - } -); - -a.b() - .c(function(){ - var a; - }).d.e; - -if (a == 'b') { - if (c && d) e = f - else g('h').i('j') -} - -a = function (b, c) { - return a(function () { - var d = e - var f = g - var h = i - - if (!j) k('l', (m = n)) - if (o) p - else if (q) r - }) -} - -var a = function() { - "b" - .replace(/a/, "a") - .replace(/bc?/, function(e) { - return "b" + (e.f === 2 ? "c" : "f"); - }) - .replace(/d/, "d"); -}; - -$(b) - .on('a', 'b', function() { $(c).e('f'); }) - .on('g', 'h', function() { $(i).j('k'); }); - -a - .b('c', - 'd'); // NO ERROR: CallExpression args not linted by default - -a - .b('c', [ 'd', function(e) { - e++; - }]); - -var a = function() { - a++; - b++; // <- - c++; // <- - }, - b; - -var b = [ - a, - b, - c - ], - c; - -var c = { - a: 1, - b: 2, - c: 3 - }, - d; - -// holes in arrays indentation -x = [ - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1 -]; - -try { - a++; - b++; // <- - c++; // -> -} catch (d) { - e++; - f++; // <- - g++; // -> -} finally { - h++; - i++; // <- - j++; // -> -} - -if (array.some(function(){ - return true; -})) { - a++; // -> - b++; - c++; // <- -} - -var a = b.c(function() { - d++; - }), - e; - -switch (true) { - case (a - && b): - case (c // -> -&& d): - case (e // <- - && f): - case (g -&& h): - var i = j; // <- - var k = l; - var m = n; // -> -} - -if (a) { - b(); -} -else { - c(); // -> - d(); - e(); // <- -} - -if (a) b(); -else { - c(); // -> - d(); - e(); // <- -} - -if (a) { - b(); -} else c(); - -if (a) { - b(); -} -else c(); - -a(); - -if( "very very long multi line" + - "with weird indentation" ) { - b(); - a(); // -> - c(); // <- -} - -a( "very very long multi line" + - "with weird indentation", function() { - b(); - a(); // -> - c(); // <- -}); // <- - -a = function(content, dom) { - b(); - c(); // <- - d(); // -> -}; - -a = function(content, dom) { - b(); - c(); // <- - d(); // -> -}; - -a = function(content, dom) { - b(); // -> -}; - -a = function(content, dom) { - b(); // -> -}; - -a('This is a terribly long description youll ' + - 'have to read', function () { - b(); // <- - c(); // <- -}); // <- - -if ( - array.some(function(){ - return true; - }) -) { - a++; // -> - b++; - c++; // <- -} - -function c(d) { - return { - e: function(f, g) { - } - }; -} - -function a(b) { - switch(x) { - case 1: - if (foo) { - return 5; - } - } -} - -function a(b) { - switch(x) { - case 1: - c; - } -} - -function a(b) { - switch(x) { - case 1: c; - } -} - -function test() { - var a = 1; - { - a(); - } -} - -{ - a(); -} - -function a(b) { - switch(x) { - case 1: - { // <- - a(); // -> - } - break; - default: - { - b(); - } - } -} - -switch (a) { - default: - if (b) - c(); -} - -function test(x) { - switch (x) { - case 1: - return function() { - var a = 5; - return a; - }; - } -} - -switch (a) { - default: - if (b) - c(); -} diff --git a/packages/eslint-plugin/tests/rules/adjacent-overload-signatures.test.ts b/packages/eslint-plugin/tests/rules/adjacent-overload-signatures.test.ts index e834112f6ecc..3839469351ce 100644 --- a/packages/eslint-plugin/tests/rules/adjacent-overload-signatures.test.ts +++ b/packages/eslint-plugin/tests/rules/adjacent-overload-signatures.test.ts @@ -2,9 +2,7 @@ import { RuleTester } from '@typescript-eslint/rule-tester'; import rule from '../../src/rules/adjacent-overload-signatures'; -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', -}); +const ruleTester = new RuleTester(); ruleTester.run('adjacent-overload-signatures', rule, { valid: [ @@ -15,7 +13,7 @@ function error(b: number); function error(ab: string | number) {} export { error }; `, - parserOptions: { sourceType: 'module' }, + languageOptions: { parserOptions: { sourceType: 'module' } }, }, { code: ` @@ -27,7 +25,7 @@ function mapStateToProps() {} function mapDispatchToProps() {} export default connect(mapStateToProps, mapDispatchToProps)(ErrorMessage); `, - parserOptions: { sourceType: 'module' }, + languageOptions: { parserOptions: { sourceType: 'module' } }, }, ` export const foo = 'a', diff --git a/packages/eslint-plugin/tests/rules/array-type.test.ts b/packages/eslint-plugin/tests/rules/array-type.test.ts index 3ee5720d32c9..ca71cbdaf15c 100644 --- a/packages/eslint-plugin/tests/rules/array-type.test.ts +++ b/packages/eslint-plugin/tests/rules/array-type.test.ts @@ -6,9 +6,7 @@ import type { OptionString } from '../../src/rules/array-type'; import rule from '../../src/rules/array-type'; import { areOptionsValid } from '../areOptionsValid'; -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', -}); +const ruleTester = new RuleTester(); ruleTester.run('array-type', rule, { valid: [ @@ -1947,7 +1945,7 @@ interface FooInterface { // -- eslint rule tester is not working with multi-pass // https://github.com/eslint/eslint/issues/11187 describe('array-type (nested)', () => { - const linter = new TSESLint.Linter(); + const linter = new TSESLint.Linter({ configType: 'eslintrc' }); linter.defineRule('array-type', rule); linter.defineParser('@typescript-eslint/parser', parser); diff --git a/packages/eslint-plugin/tests/rules/await-thenable.test.ts b/packages/eslint-plugin/tests/rules/await-thenable.test.ts index 27e4309092e8..ebb8c6ef9537 100644 --- a/packages/eslint-plugin/tests/rules/await-thenable.test.ts +++ b/packages/eslint-plugin/tests/rules/await-thenable.test.ts @@ -7,12 +7,12 @@ const rootDir = getFixturesRootDir(); const messageId = 'await'; const ruleTester = new RuleTester({ - parserOptions: { - ecmaVersion: 2018, - tsconfigRootDir: rootDir, - project: './tsconfig.json', + languageOptions: { + parserOptions: { + tsconfigRootDir: rootDir, + project: './tsconfig.json', + }, }, - parser: '@typescript-eslint/parser', }); ruleTester.run('await-thenable', rule, { 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 51447bfe6130..8ac62dbb40cb 100644 --- a/packages/eslint-plugin/tests/rules/ban-ts-comment.test.ts +++ b/packages/eslint-plugin/tests/rules/ban-ts-comment.test.ts @@ -2,9 +2,7 @@ import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; import rule from '../../src/rules/ban-ts-comment'; -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', -}); +const ruleTester = new RuleTester(); ruleTester.run('ts-expect-error', rule, { valid: [ diff --git a/packages/eslint-plugin/tests/rules/ban-tslint-comment.test.ts b/packages/eslint-plugin/tests/rules/ban-tslint-comment.test.ts index 01f6ec078a84..062fcd531988 100644 --- a/packages/eslint-plugin/tests/rules/ban-tslint-comment.test.ts +++ b/packages/eslint-plugin/tests/rules/ban-tslint-comment.test.ts @@ -46,9 +46,7 @@ console.log(woah); }, ] -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', -}); +const ruleTester = new RuleTester(); ruleTester.run('ban-tslint-comment', rule, { valid: [ diff --git a/packages/eslint-plugin/tests/rules/ban-types.test.ts b/packages/eslint-plugin/tests/rules/ban-types.test.ts deleted file mode 100644 index 5f8d72ab0f76..000000000000 --- a/packages/eslint-plugin/tests/rules/ban-types.test.ts +++ /dev/null @@ -1,779 +0,0 @@ -/* eslint-disable @typescript-eslint/internal/prefer-ast-types-enum */ -import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; -import type { TSESLint } from '@typescript-eslint/utils'; - -import type { MessageIds, Options } from '../../src/rules/ban-types'; -import rule, { TYPE_KEYWORDS } from '../../src/rules/ban-types'; -import { objectReduceKey } from '../../src/util'; - -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', -}); - -const options: Options = [ - { - types: { - String: { - message: 'Use string instead.', - fixWith: 'string', - }, - Object: "Use '{}' instead.", - Array: null, - F: null, - 'NS.Bad': { - message: 'Use NS.Good instead.', - fixWith: 'NS.Good', - }, - }, - extendDefaults: false, - }, -]; - -ruleTester.run('ban-types', rule, { - valid: [ - 'let f = Object();', // Should not fail if there is no options set - 'let f: { x: number; y: number } = { x: 1, y: 1 };', - { - code: 'let f = Object();', - options, - }, - { - code: 'let g = Object.create(null);', - options, - }, - { - code: 'let h = String(false);', - options, - }, - { - code: 'let e: foo.String;', - options, - }, - { - code: 'let a: _.NS.Bad;', - options, - }, - { - code: 'let a: NS.Bad._;', - options, - }, - // Replace default options instead of merging with extendDefaults: false - { - code: 'let a: String;', - options: [ - { - types: { - Number: { - message: 'Use number instead.', - fixWith: 'number', - }, - }, - extendDefaults: false, - }, - ], - }, - { - code: 'let a: undefined;', - options: [ - { - types: { - null: { - message: 'Use undefined instead.', - fixWith: 'undefined', - }, - }, - }, - ], - }, - { - code: 'let a: null;', - options: [ - { - types: { - undefined: null, - }, - extendDefaults: false, - }, - ], - }, - { - code: 'type Props = {};', - options: [ - { - types: { - '{}': false, - }, - extendDefaults: true, - }, - ], - }, - 'let a: [];', - ], - invalid: [ - { - code: 'let a: String;', - output: 'let a: string;', - errors: [ - { - messageId: 'bannedTypeMessage', - line: 1, - column: 8, - }, - ], - }, - { - code: 'let a: Object;', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'Object', - customMessage: " Use '{}' instead.", - }, - line: 1, - column: 8, - }, - ], - options, - }, - { - code: 'let a: Object;', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'Object', - customMessage: [ - ' The `Object` type actually means "any non-nullish value", so it is marginally better than `unknown`.', - '- If you want a type meaning "any object", you probably want `object` instead.', - '- If you want a type meaning "any value", you probably want `unknown` instead.', - '- If you really want a type meaning "any non-nullish value", you probably want `NonNullable` instead.', - ].join('\n'), - }, - line: 1, - column: 8, - suggestions: [ - { - messageId: 'bannedTypeReplacement', - data: { name: 'Object', replacement: 'object' }, - output: 'let a: object;', - }, - { - messageId: 'bannedTypeReplacement', - data: { name: 'Object', replacement: 'unknown' }, - output: 'let a: unknown;', - }, - { - messageId: 'bannedTypeReplacement', - data: { name: 'Object', replacement: 'NonNullable' }, - output: 'let a: NonNullable;', - }, - ], - }, - ], - options: [{}], - }, - { - code: 'let aa: Foo;', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'Foo', - customMessage: '', - }, - }, - ], - options: [ - { - types: { - Foo: { message: '' }, - }, - }, - ], - }, - { - code: 'let b: { c: String };', - output: 'let b: { c: string };', - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'String', - customMessage: ' Use string instead.', - }, - line: 1, - column: 13, - }, - ], - options, - }, - { - code: 'function foo(a: String) {}', - output: 'function foo(a: string) {}', - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'String', - customMessage: ' Use string instead.', - }, - line: 1, - column: 17, - }, - ], - options, - }, - { - code: "'a' as String;", - output: "'a' as string;", - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'String', - customMessage: ' Use string instead.', - }, - line: 1, - column: 8, - }, - ], - options, - }, - { - code: 'let c: F;', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { name: 'F', customMessage: '' }, - line: 1, - column: 8, - }, - ], - options, - }, - { - code: ` -class Foo extends Bar implements Baz { - constructor(foo: String | Object) {} - - exit(): Array { - const foo: String = 1 as String; - } -} - `, - output: ` -class Foo extends Bar implements Baz { - constructor(foo: string | Object) {} - - exit(): Array { - const foo: string = 1 as string; - } -} - `, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'String', - customMessage: ' Use string instead.', - }, - line: 2, - column: 15, - }, - { - messageId: 'bannedTypeMessage', - data: { - name: 'String', - customMessage: ' Use string instead.', - }, - line: 2, - column: 35, - }, - { - messageId: 'bannedTypeMessage', - data: { - name: 'Object', - customMessage: " Use '{}' instead.", - }, - line: 2, - column: 58, - }, - { - messageId: 'bannedTypeMessage', - data: { - name: 'String', - customMessage: ' Use string instead.', - }, - line: 3, - column: 20, - }, - { - messageId: 'bannedTypeMessage', - data: { - name: 'Object', - customMessage: " Use '{}' instead.", - }, - line: 3, - column: 29, - }, - { - messageId: 'bannedTypeMessage', - data: { name: 'Array', customMessage: '' }, - line: 5, - column: 11, - }, - { - messageId: 'bannedTypeMessage', - data: { - name: 'String', - customMessage: ' Use string instead.', - }, - line: 5, - column: 17, - }, - { - messageId: 'bannedTypeMessage', - data: { - name: 'String', - customMessage: ' Use string instead.', - }, - line: 6, - column: 16, - }, - { - messageId: 'bannedTypeMessage', - data: { - name: 'String', - customMessage: ' Use string instead.', - }, - line: 6, - column: 30, - }, - ], - options, - }, - { - code: 'let a: NS.Bad;', - output: 'let a: NS.Good;', - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'NS.Bad', - customMessage: ' Use NS.Good instead.', - }, - line: 1, - column: 8, - }, - ], - options, - }, - { - code: ` -let a: NS.Bad; -let b: Foo; - `, - output: ` -let a: NS.Good; -let b: Foo; - `, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'NS.Bad', - customMessage: ' Use NS.Good instead.', - }, - line: 2, - column: 8, - }, - { - messageId: 'bannedTypeMessage', - data: { - name: 'NS.Bad', - customMessage: ' Use NS.Good instead.', - }, - line: 3, - column: 12, - }, - ], - options, - }, - { - code: 'let foo: {} = {};', - output: 'let foo: object = {};', - options: [ - { - types: { - '{}': { - message: 'Use object instead.', - fixWith: 'object', - }, - }, - }, - ], - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: '{}', - customMessage: ' Use object instead.', - }, - line: 1, - column: 10, - }, - ], - }, - { - code: noFormat` -let foo: {} = {}; -let bar: { } = {}; -let baz: { -} = {}; - `, - output: ` -let foo: object = {}; -let bar: object = {}; -let baz: object = {}; - `, - options: [ - { - types: { - '{ }': { - message: 'Use object instead.', - fixWith: 'object', - }, - }, - }, - ], - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: '{}', - customMessage: ' Use object instead.', - }, - line: 2, - column: 10, - }, - { - messageId: 'bannedTypeMessage', - data: { - name: '{}', - customMessage: ' Use object instead.', - }, - line: 3, - column: 10, - }, - { - messageId: 'bannedTypeMessage', - data: { - name: '{}', - customMessage: ' Use object instead.', - }, - line: 4, - column: 10, - }, - ], - }, - { - code: 'let a: NS.Bad;', - output: 'let a: NS.Good;', - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'NS.Bad', - customMessage: ' Use NS.Good instead.', - }, - line: 1, - column: 8, - }, - ], - options: [ - { - types: { - ' NS.Bad ': { - message: 'Use NS.Good instead.', - fixWith: 'NS.Good', - }, - }, - }, - ], - }, - { - code: noFormat`let a: Foo< F >;`, - output: `let a: Foo< T >;`, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'F', - customMessage: ' Use T instead.', - }, - line: 1, - column: 15, - }, - ], - options: [ - { - types: { - ' F ': { - message: 'Use T instead.', - fixWith: 'T', - }, - }, - }, - ], - }, - { - code: 'type Foo = Bar;', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'Bar', - customMessage: " Don't use `any` as a type parameter to `Bar`", - }, - line: 1, - column: 12, - }, - ], - options: [ - { - types: { - 'Bar': "Don't use `any` as a type parameter to `Bar`", - }, - }, - ], - }, - { - code: noFormat`type Foo = Bar;`, - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'Bar', - customMessage: " Don't pass `A, B` as parameters to `Bar`", - }, - line: 1, - column: 12, - }, - ], - options: [ - { - types: { - 'Bar': "Don't pass `A, B` as parameters to `Bar`", - }, - }, - ], - }, - { - code: 'let a: [];', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: '[]', - customMessage: ' `[]` does only allow empty arrays.', - }, - line: 1, - column: 8, - }, - ], - options: [ - { - types: { - '[]': '`[]` does only allow empty arrays.', - }, - }, - ], - }, - { - code: noFormat`let a: [ ] ;`, - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: '[]', - customMessage: ' `[]` does only allow empty arrays.', - }, - line: 1, - column: 9, - }, - ], - options: [ - { - types: { - '[]': '`[]` does only allow empty arrays.', - }, - }, - ], - }, - { - code: 'let a: [];', - output: 'let a: any[];', - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: '[]', - customMessage: ' `[]` does only allow empty arrays.', - }, - line: 1, - column: 8, - }, - ], - options: [ - { - types: { - '[]': { - message: '`[]` does only allow empty arrays.', - fixWith: 'any[]', - }, - }, - }, - ], - }, - { - code: 'let a: [[]];', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: '[]', - customMessage: ' `[]` does only allow empty arrays.', - }, - line: 1, - column: 9, - }, - ], - options: [ - { - types: { - '[]': '`[]` does only allow empty arrays.', - }, - }, - ], - }, - { - code: 'type Baz = 1 & Foo;', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - }, - ], - options: [ - { - types: { - Foo: { message: '' }, - }, - }, - ], - }, - { - code: 'interface Foo extends Bar {}', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - }, - ], - options: [ - { - types: { - Bar: { message: '' }, - }, - }, - ], - }, - { - code: 'interface Foo extends Bar, Baz {}', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - }, - ], - options: [ - { - types: { - Bar: { message: '' }, - }, - }, - ], - }, - { - code: 'class Foo implements Bar {}', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - }, - ], - options: [ - { - types: { - Bar: { message: '' }, - }, - }, - ], - }, - { - code: 'class Foo implements Bar, Baz {}', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - }, - ], - options: [ - { - types: { - Bar: { message: 'Bla' }, - }, - }, - ], - }, - ...objectReduceKey( - TYPE_KEYWORDS, - (acc: TSESLint.InvalidTestCase[], key) => { - acc.push({ - code: `function foo(x: ${key}) {}`, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: key, - customMessage: '', - }, - line: 1, - column: 17, - }, - ], - options: [ - { - extendDefaults: false, - types: { - [key]: null, - }, - }, - ], - }); - return acc; - }, - [], - ), - ], -}); diff --git a/packages/eslint-plugin/tests/rules/block-spacing.test.ts b/packages/eslint-plugin/tests/rules/block-spacing.test.ts deleted file mode 100644 index dabb15e692f3..000000000000 --- a/packages/eslint-plugin/tests/rules/block-spacing.test.ts +++ /dev/null @@ -1,148 +0,0 @@ -import type { - InvalidTestCase, - ValidTestCase, -} from '@typescript-eslint/rule-tester'; -import { RuleTester } from '@typescript-eslint/rule-tester'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; - -import rule from '../../src/rules/block-spacing'; - -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', -}); - -type InvalidBlockSpacingTestCase = InvalidTestCase< - 'extra' | 'missing', - ['always' | 'never'] ->; - -const options = ['always', 'never'] as const; -const typeDeclarations = [ - { - nodeType: AST_NODE_TYPES.TSInterfaceBody, - stringPrefix: 'interface Foo ', - }, - { - nodeType: AST_NODE_TYPES.TSTypeLiteral, - stringPrefix: 'type Foo = ', - }, - { - nodeType: AST_NODE_TYPES.TSEnumDeclaration, - stringPrefix: 'enum Foo ', - }, - { - nodeType: AST_NODE_TYPES.TSEnumDeclaration, - stringPrefix: 'const enum Foo ', - }, -]; -const emptyBlocks = ['{}', '{ }']; -const singlePropertyBlocks = ['{bar: true}', '{ bar: true }']; -const blockComment = '/* comment */'; - -ruleTester.run('block-spacing', rule, { - valid: [ - // Empty blocks don't apply - ...options.flatMap(option => - typeDeclarations.flatMap(typeDec => - emptyBlocks.map>(blockType => ({ - code: typeDec.stringPrefix + blockType, - options: [option], - })), - ), - ), - ...typeDeclarations.flatMap>( - typeDec => { - const property = - typeDec.nodeType === AST_NODE_TYPES.TSEnumDeclaration - ? 'bar = 1' - : 'bar: true;'; - return [ - { - code: `${typeDec.stringPrefix}{ /* comment */ ${property} /* comment */ } // always`, - options: ['always'], - }, - { - code: `${typeDec.stringPrefix}{/* comment */ ${property} /* comment */} // never`, - options: ['never'], - }, - { - code: `${typeDec.stringPrefix}{ //comment\n ${property}}`, - options: ['never'], - }, - ]; - }, - ), - ], - invalid: [ - ...options.flatMap(option => - typeDeclarations.flatMap(typeDec => { - return singlePropertyBlocks.flatMap( - (blockType, blockIndex) => { - // These are actually valid, so filter them out - if ( - (option === 'always' && blockType.startsWith('{ ')) || - (option === 'never' && blockType.startsWith('{bar')) - ) { - return []; - } - const reverseBlockType = singlePropertyBlocks[1 - blockIndex]; - let code = `${typeDec.stringPrefix}${blockType}; /* ${option} */`; - let output = `${typeDec.stringPrefix}${reverseBlockType}; /* ${option} */`; - if (typeDec.nodeType === AST_NODE_TYPES.TSEnumDeclaration) { - output = output.replace(':', '='); - code = code.replace(':', '='); - } - - return { - code, - options: [option], - output, - errors: [ - { - type: typeDec.nodeType, - messageId: option === 'always' ? 'missing' : 'extra', - data: { location: 'after', token: '{' }, - }, - { - type: typeDec.nodeType, - messageId: option === 'always' ? 'missing' : 'extra', - data: { location: 'before', token: '}' }, - }, - ], - }; - }, - ); - }), - ), - // With block comments - ...options.flatMap(option => - typeDeclarations.flatMap(typeDec => { - const property = - typeDec.nodeType === AST_NODE_TYPES.TSEnumDeclaration - ? 'bar = 1' - : 'bar: true;'; - const alwaysSpace = option === 'always' ? '' : ' '; - const neverSpace = option === 'always' ? ' ' : ''; - return [ - { - code: `${typeDec.stringPrefix}{${alwaysSpace}${blockComment}${property}${blockComment}${alwaysSpace}} /* ${option} */`, - output: `${typeDec.stringPrefix}{${neverSpace}${blockComment}${property}${blockComment}${neverSpace}} /* ${option} */`, - options: [option], - errors: [ - { - type: typeDec.nodeType, - messageId: option === 'always' ? 'missing' : 'extra', - data: { location: 'after', token: '{' }, - }, - { - type: typeDec.nodeType, - messageId: option === 'always' ? 'missing' : 'extra', - data: { location: 'before', token: '}' }, - }, - ], - }, - ]; - }), - ), - ], -}); diff --git a/packages/eslint-plugin/tests/rules/brace-style.test.ts b/packages/eslint-plugin/tests/rules/brace-style.test.ts deleted file mode 100644 index ec1ac6b3bc5f..000000000000 --- a/packages/eslint-plugin/tests/rules/brace-style.test.ts +++ /dev/null @@ -1,1188 +0,0 @@ -/* eslint-disable eslint-comments/no-use */ -// this rule tests the position of braces, which prettier will want to fix and break the tests -/* eslint "@typescript-eslint/internal/plugin-test-formatting": ["error", { formatWithPrettier: false }] */ -/* eslint-enable eslint-comments/no-use */ - -import { RuleTester } from '@typescript-eslint/rule-tester'; - -import rule from '../../src/rules/brace-style'; - -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaVersion: 6, - sourceType: 'module', - ecmaFeatures: {}, - }, -}); - -ruleTester.run('brace-style', rule, { - valid: [ - { - code: ` -function f() { - if (true) - return { x: 1 }; - else { - var y = 2; - return y; - } -} - `, - }, - { - code: ` -if (tag === 1) glyph.id = pbf.readVarint(); -else if (tag === 2) glyph.bitmap = pbf.readBytes(); - `, - }, - { - code: ` -function foo () { - return; -} - `, - }, - { - code: ` -function a(b, -c, -d) { } - `, - }, - { - code: ` -!function foo () { - return; -} - `, - }, - { - code: ` -!function a(b, -c, -d) { } - `, - }, - { - code: ` -if (foo) { - bar(); -} - `, - }, - { - code: ` -if (a) { - b(); -} else { - c(); -} - `, - }, - { - code: ` -while (foo) { - bar(); -} - `, - }, - { - code: ` -for (;;) { - bar(); -} - `, - }, - { - code: ` -with (foo) { - bar(); -} - `, - }, - { - code: ` -switch (foo) { - case 'bar': break; -} - `, - }, - { - code: ` -try { - bar(); -} catch (e) { - baz(); -} - `, - }, - { - code: ` -do { - bar(); -} while (true) - `, - }, - { - code: ` -for (foo in bar) { - baz(); -} - `, - }, - { - code: ` -if (a && - b && - c) { - } - `, - }, - { - code: ` -switch(0) { -} - `, - }, - { - code: ` -class Foo { -} - `, - }, - { - code: ` -(class { -}) - `, - }, - { - code: ` -class -Foo { -} - `, - }, - { - code: ` -class Foo { - bar() { - } -} - `, - }, - { - code: ` -if (foo) { -} -else { -} - `, - options: ['stroustrup'], - }, - { - code: ` -if (foo) -{ -} -else -{ -} - `, - options: ['allman'], - }, - { - code: ` -try { - bar(); -} -catch (e) { - baz(); -} - `, - options: ['stroustrup'], - }, - { - code: ` -try -{ - bar(); -} -catch (e) -{ - baz(); -} - `, - options: ['allman'], - }, - { - code: 'function foo () { return; }', - options: ['1tbs', { allowSingleLine: true }], - }, - { - code: 'function foo () { a(); b(); return; }', - options: ['1tbs', { allowSingleLine: true }], - }, - { - code: 'function a(b,c,d) { }', - options: ['1tbs', { allowSingleLine: true }], - }, - { - code: '!function foo () { return; }', - options: ['1tbs', { allowSingleLine: true }], - }, - { - code: '!function a(b,c,d) { }', - options: ['1tbs', { allowSingleLine: true }], - }, - { - code: 'if (foo) { bar(); }', - options: ['1tbs', { allowSingleLine: true }], - }, - { - code: 'if (a) { b(); } else { c(); }', - options: ['1tbs', { allowSingleLine: true }], - }, - { - code: 'while (foo) { bar(); }', - options: ['1tbs', { allowSingleLine: true }], - }, - { - code: 'for (;;) { bar(); }', - options: ['1tbs', { allowSingleLine: true }], - }, - { - code: 'with (foo) { bar(); }', - options: ['1tbs', { allowSingleLine: true }], - }, - { - code: "switch (foo) { case 'bar': break; }", - options: ['1tbs', { allowSingleLine: true }], - }, - { - code: 'try { bar(); } catch (e) { baz(); }', - options: ['1tbs', { allowSingleLine: true }], - }, - { - code: 'do { bar(); } while (true)', - options: ['1tbs', { allowSingleLine: true }], - }, - { - code: 'for (foo in bar) { baz(); }', - options: ['1tbs', { allowSingleLine: true }], - }, - { - code: 'if (a && b && c) { }', - options: ['1tbs', { allowSingleLine: true }], - }, - { - code: 'switch(0) {}', - options: ['1tbs', { allowSingleLine: true }], - }, - { - code: ` -if (foo) {} -else {} - `, - options: ['stroustrup', { allowSingleLine: true }], - }, - { - code: ` -try { bar(); } -catch (e) { baz(); } - `, - options: ['stroustrup', { allowSingleLine: true }], - }, - { - code: 'var foo = () => { return; }', - options: ['stroustrup', { allowSingleLine: true }], - parserOptions: { ecmaVersion: 6 }, - }, - { - code: ` -if (foo) {} -else {} - `, - options: ['allman', { allowSingleLine: true }], - }, - { - code: ` -try { bar(); } -catch (e) { baz(); } - `, - options: ['allman', { allowSingleLine: true }], - }, - { - code: 'var foo = () => { return; }', - options: ['allman', { allowSingleLine: true }], - parserOptions: { ecmaVersion: 6 }, - }, - { - code: ` -if (tag === 1) fontstack.name = pbf.readString(); -else if (tag === 2) fontstack.range = pbf.readString(); -else if (tag === 3) { - var glyph = pbf.readMessage(readGlyph, {}); - fontstack.glyphs[glyph.id] = glyph; -} - `, - options: ['1tbs'], - }, - { - code: ` -if (tag === 1) fontstack.name = pbf.readString(); -else if (tag === 2) fontstack.range = pbf.readString(); -else if (tag === 3) { - var glyph = pbf.readMessage(readGlyph, {}); - fontstack.glyphs[glyph.id] = glyph; -} - `, - options: ['stroustrup'], - }, - { - code: ` -switch(x) -{ - case 1: - bar(); -} - `, - options: ['allman'], - }, - { - code: 'switch(x) {}', - options: ['allman', { allowSingleLine: true }], - }, - { - code: ` -class Foo { -} - `, - options: ['stroustrup'], - }, - { - code: ` -(class { -}) - `, - options: ['stroustrup'], - }, - { - code: ` -class Foo -{ -} - `, - options: ['allman'], - }, - { - code: ` -(class -{ -}) - `, - options: ['allman'], - }, - { - code: ` -class -Foo -{ -} - `, - options: ['allman'], - }, - { - code: 'class Foo {}', - options: ['1tbs', { allowSingleLine: true }], - }, - { - code: 'class Foo {}', - options: ['allman', { allowSingleLine: true }], - }, - { - code: '(class {})', - options: ['1tbs', { allowSingleLine: true }], - }, - { - code: '(class {})', - options: ['allman', { allowSingleLine: true }], - }, - - // https://github.com/eslint/eslint/issues/7908 - { - code: '{}', - }, - { - code: ` -if (foo) { -} -{ -} - `, - }, - { - code: ` -switch (foo) { - case bar: - baz(); - { - qux(); - } -} - `, - }, - { - code: ` -{ -} - `, - }, - { - code: ` -{ - { - } -} - `, - }, - - // https://github.com/eslint/eslint/issues/7974 - { - code: ` -class Ball { - throw() {} - catch() {} -} - `, - }, - { - code: ` -({ - and() {}, - finally() {} -}) - `, - }, - { - code: ` -(class { - or() {} - else() {} -}) - `, - }, - { - code: ` -if (foo) bar = function() {} -else baz() - `, - }, - { - code: ` -interface Foo { -} - `, - options: ['1tbs'], - }, - { - code: ` -interface Foo { -} - `, - options: ['stroustrup'], - }, - { - code: ` -interface Foo -{ -} - `, - options: ['allman'], - }, - { - code: ` -module "Foo" { -} - `, - options: ['1tbs'], - }, - { - code: ` -module "Foo" { -} - `, - options: ['stroustrup'], - }, - { - code: ` -module "Foo" -{ -} - `, - options: ['allman'], - }, - { - code: ` -namespace Foo { -} - `, - options: ['1tbs'], - }, - { - code: ` -namespace Foo { -} - `, - options: ['stroustrup'], - }, - { - code: ` -namespace Foo -{ -} - `, - options: ['allman'], - }, - { - code: ` -enum Foo -{ - A, - B -} - `, - options: ['allman'], - }, - { - code: ` -enum Foo { - A, - B -} - `, - options: ['1tbs'], - }, - { - code: ` -enum Foo { - A, - B -} - `, - options: ['stroustrup'], - }, - { - code: 'enum Foo { A, B }', - options: ['1tbs', { allowSingleLine: true }], - }, - ], - - invalid: [ - { - code: ` -if (f) { - bar; -} -else - baz; - `, - output: ` -if (f) { - bar; -} else - baz; - `, - errors: [{ messageId: 'nextLineClose' }], - }, - { - code: 'var foo = () => { return; }', - output: 'var foo = () => {\n return; \n}', - parserOptions: { ecmaVersion: 6 }, - errors: [ - { messageId: 'blockSameLine' }, - { messageId: 'singleLineClose' }, - ], - }, - { - code: 'function foo() { return; }', - output: 'function foo() {\n return; \n}', - errors: [ - { messageId: 'blockSameLine' }, - { messageId: 'singleLineClose' }, - ], - }, - { - code: 'function foo() \n { \n return; }', - output: 'function foo() { \n return; \n}', - errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], - }, - { - code: '!function foo() \n { \n return; }', - output: '!function foo() { \n return; \n}', - errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], - }, - { - code: 'if (foo) \n { \n bar(); }', - output: 'if (foo) { \n bar(); \n}', - errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], - }, - { - code: 'if (a) { \nb();\n } else \n { c(); }', - output: 'if (a) { \nb();\n } else {\n c(); \n}', - errors: [ - { messageId: 'nextLineOpen' }, - { messageId: 'blockSameLine' }, - { messageId: 'singleLineClose' }, - ], - }, - { - code: 'while (foo) \n { \n bar(); }', - output: 'while (foo) { \n bar(); \n}', - errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], - }, - { - code: 'for (;;) \n { \n bar(); }', - output: 'for (;;) { \n bar(); \n}', - errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], - }, - { - code: 'with (foo) \n { \n bar(); }', - output: 'with (foo) { \n bar(); \n}', - errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], - }, - { - code: "switch (foo) \n { \n case 'bar': break; }", - output: "switch (foo) { \n case 'bar': break; \n}", - errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], - }, - { - code: 'switch (foo) \n { }', - output: 'switch (foo) { }', - errors: [{ messageId: 'nextLineOpen' }], - }, - { - code: 'try \n { \n bar(); \n } catch (e) {}', - output: 'try { \n bar(); \n } catch (e) {}', - errors: [{ messageId: 'nextLineOpen' }], - }, - { - code: 'try { \n bar(); \n } catch (e) \n {}', - output: 'try { \n bar(); \n } catch (e) {}', - errors: [{ messageId: 'nextLineOpen' }], - }, - { - code: 'do \n { \n bar(); \n} while (true)', - output: 'do { \n bar(); \n} while (true)', - errors: [{ messageId: 'nextLineOpen' }], - }, - { - code: 'for (foo in bar) \n { \n baz(); \n }', - output: 'for (foo in bar) { \n baz(); \n }', - errors: [{ messageId: 'nextLineOpen' }], - }, - { - code: 'for (foo of bar) \n { \n baz(); \n }', - output: 'for (foo of bar) { \n baz(); \n }', - parserOptions: { ecmaVersion: 6 }, - errors: [{ messageId: 'nextLineOpen' }], - }, - { - code: 'try { \n bar(); \n }\ncatch (e) {\n}', - output: 'try { \n bar(); \n } catch (e) {\n}', - errors: [{ messageId: 'nextLineClose' }], - }, - { - code: 'try { \n bar(); \n } catch (e) {\n}\n finally {\n}', - output: 'try { \n bar(); \n } catch (e) {\n} finally {\n}', - errors: [{ messageId: 'nextLineClose' }], - }, - { - code: 'if (a) { \nb();\n } \n else { \nc();\n }', - output: 'if (a) { \nb();\n } else { \nc();\n }', - errors: [{ messageId: 'nextLineClose' }], - }, - { - code: 'try { \n bar(); \n }\ncatch (e) {\n} finally {\n}', - output: 'try { \n bar(); \n }\ncatch (e) {\n}\n finally {\n}', - options: ['stroustrup'], - errors: [{ messageId: 'sameLineClose' }], - }, - { - code: 'try { \n bar(); \n } catch (e) {\n}\n finally {\n}', - output: 'try { \n bar(); \n }\n catch (e) {\n}\n finally {\n}', - options: ['stroustrup'], - errors: [{ messageId: 'sameLineClose' }], - }, - { - code: 'if (a) { \nb();\n } else { \nc();\n }', - output: 'if (a) { \nb();\n }\n else { \nc();\n }', - options: ['stroustrup'], - errors: [{ messageId: 'sameLineClose' }], - }, - { - code: 'if (foo) {\nbaz();\n} else if (bar) {\nbaz();\n}\nelse {\nqux();\n}', - output: - 'if (foo) {\nbaz();\n}\n else if (bar) {\nbaz();\n}\nelse {\nqux();\n}', - options: ['stroustrup'], - errors: [{ messageId: 'sameLineClose' }], - }, - { - code: 'if (foo) {\npoop();\n} \nelse if (bar) {\nbaz();\n} else if (thing) {\nboom();\n}\nelse {\nqux();\n}', - output: - 'if (foo) {\npoop();\n} \nelse if (bar) {\nbaz();\n}\n else if (thing) {\nboom();\n}\nelse {\nqux();\n}', - options: ['stroustrup'], - errors: [{ messageId: 'sameLineClose' }], - }, - { - code: 'try { \n bar(); \n }\n catch (e) {\n}\n finally {\n}', - output: 'try \n{ \n bar(); \n }\n catch (e) \n{\n}\n finally \n{\n}', - options: ['allman'], - errors: [ - { messageId: 'sameLineOpen', line: 1 }, - { messageId: 'sameLineOpen', line: 4 }, - { messageId: 'sameLineOpen', line: 6 }, - ], - }, - { - code: 'switch(x) { case 1: \nbar(); }\n ', - output: 'switch(x) \n{\n case 1: \nbar(); \n}\n ', - options: ['allman'], - errors: [ - { messageId: 'sameLineOpen', line: 1 }, - { messageId: 'blockSameLine', line: 1 }, - { messageId: 'singleLineClose', line: 2 }, - ], - }, - { - code: 'if (a) { \nb();\n } else { \nc();\n }', - output: 'if (a) \n{ \nb();\n }\n else \n{ \nc();\n }', - options: ['allman'], - errors: [ - { messageId: 'sameLineOpen' }, - { messageId: 'sameLineClose' }, - { messageId: 'sameLineOpen' }, - ], - }, - { - code: 'if (foo) {\nbaz();\n} else if (bar) {\nbaz();\n}\nelse {\nqux();\n}', - output: - 'if (foo) \n{\nbaz();\n}\n else if (bar) \n{\nbaz();\n}\nelse \n{\nqux();\n}', - options: ['allman'], - errors: [ - { messageId: 'sameLineOpen' }, - { messageId: 'sameLineClose' }, - { messageId: 'sameLineOpen' }, - { messageId: 'sameLineOpen' }, - ], - }, - { - code: 'if (foo)\n{ poop();\n} \nelse if (bar) {\nbaz();\n} else if (thing) {\nboom();\n}\nelse {\nqux();\n}', - output: - 'if (foo)\n{\n poop();\n} \nelse if (bar) \n{\nbaz();\n}\n else if (thing) \n{\nboom();\n}\nelse \n{\nqux();\n}', - options: ['allman'], - errors: [ - { messageId: 'blockSameLine' }, - { messageId: 'sameLineOpen' }, - { messageId: 'sameLineClose' }, - { messageId: 'sameLineOpen' }, - { messageId: 'sameLineOpen' }, - ], - }, - { - code: 'if (foo)\n{\n bar(); }', - output: 'if (foo)\n{\n bar(); \n}', - options: ['allman'], - errors: [{ messageId: 'singleLineClose' }], - }, - { - code: 'try\n{\n somethingRisky();\n} catch (e)\n{\n handleError()\n}', - output: - 'try\n{\n somethingRisky();\n}\n catch (e)\n{\n handleError()\n}', - options: ['allman'], - errors: [{ messageId: 'sameLineClose' }], - }, - // allowSingleLine: true - { - code: 'function foo() { return; \n}', - output: 'function foo() {\n return; \n}', - options: ['1tbs', { allowSingleLine: true }], - errors: [{ messageId: 'blockSameLine' }], - }, - { - code: 'function foo() { a(); b(); return; \n}', - output: 'function foo() {\n a(); b(); return; \n}', - options: ['1tbs', { allowSingleLine: true }], - errors: [{ messageId: 'blockSameLine' }], - }, - { - code: 'function foo() { \n return; }', - output: 'function foo() { \n return; \n}', - options: ['1tbs', { allowSingleLine: true }], - errors: [{ messageId: 'singleLineClose' }], - }, - { - code: 'function foo() {\na();\nb();\nreturn; }', - output: 'function foo() {\na();\nb();\nreturn; \n}', - options: ['1tbs', { allowSingleLine: true }], - errors: [{ messageId: 'singleLineClose' }], - }, - { - code: '!function foo() { \n return; }', - output: '!function foo() { \n return; \n}', - options: ['1tbs', { allowSingleLine: true }], - errors: [{ messageId: 'singleLineClose' }], - }, - { - code: 'if (a) { b();\n } else { c(); }', - output: 'if (a) {\n b();\n } else { c(); }', - options: ['1tbs', { allowSingleLine: true }], - errors: [{ messageId: 'blockSameLine' }], - }, - { - code: 'if (a) { b(); }\nelse { c(); }', - output: 'if (a) { b(); } else { c(); }', - options: ['1tbs', { allowSingleLine: true }], - errors: [{ messageId: 'nextLineClose' }], - }, - { - code: 'while (foo) { \n bar(); }', - output: 'while (foo) { \n bar(); \n}', - options: ['1tbs', { allowSingleLine: true }], - errors: [{ messageId: 'singleLineClose' }], - }, - { - code: 'for (;;) { bar(); \n }', - output: 'for (;;) {\n bar(); \n }', - options: ['1tbs', { allowSingleLine: true }], - errors: [{ messageId: 'blockSameLine' }], - }, - { - code: 'with (foo) { bar(); \n }', - output: 'with (foo) {\n bar(); \n }', - options: ['1tbs', { allowSingleLine: true }], - errors: [{ messageId: 'blockSameLine' }], - }, - { - code: 'switch (foo) \n { \n case `bar`: break; }', - output: 'switch (foo) { \n case `bar`: break; \n}', - options: ['1tbs', { allowSingleLine: true }], - errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], - }, - { - code: 'switch (foo) \n { }', - output: 'switch (foo) { }', - options: ['1tbs', { allowSingleLine: true }], - errors: [{ messageId: 'nextLineOpen' }], - }, - { - code: 'try { bar(); }\ncatch (e) { baz(); }', - output: 'try { bar(); } catch (e) { baz(); }', - options: ['1tbs', { allowSingleLine: true }], - errors: [{ messageId: 'nextLineClose' }], - }, - { - code: 'try \n { \n bar(); \n } catch (e) {}', - output: 'try { \n bar(); \n } catch (e) {}', - options: ['1tbs', { allowSingleLine: true }], - errors: [{ messageId: 'nextLineOpen' }], - }, - { - code: 'try { \n bar(); \n } catch (e) \n {}', - output: 'try { \n bar(); \n } catch (e) {}', - options: ['1tbs', { allowSingleLine: true }], - errors: [{ messageId: 'nextLineOpen' }], - }, - { - code: 'do \n { \n bar(); \n} while (true)', - output: 'do { \n bar(); \n} while (true)', - options: ['1tbs', { allowSingleLine: true }], - errors: [{ messageId: 'nextLineOpen' }], - }, - { - code: 'for (foo in bar) \n { \n baz(); \n }', - output: 'for (foo in bar) { \n baz(); \n }', - options: ['1tbs', { allowSingleLine: true }], - errors: [{ messageId: 'nextLineOpen' }], - }, - { - code: 'try { \n bar(); \n }\ncatch (e) {\n}', - output: 'try { \n bar(); \n } catch (e) {\n}', - options: ['1tbs', { allowSingleLine: true }], - errors: [{ messageId: 'nextLineClose' }], - }, - { - code: 'try { \n bar(); \n } catch (e) {\n}\n finally {\n}', - output: 'try { \n bar(); \n } catch (e) {\n} finally {\n}', - options: ['1tbs', { allowSingleLine: true }], - errors: [{ messageId: 'nextLineClose' }], - }, - { - code: 'if (a) { \nb();\n } \n else { \nc();\n }', - output: 'if (a) { \nb();\n } else { \nc();\n }', - options: ['1tbs', { allowSingleLine: true }], - errors: [{ messageId: 'nextLineClose' }], - }, - { - code: 'try { \n bar(); \n }\ncatch (e) {\n} finally {\n}', - output: 'try { \n bar(); \n }\ncatch (e) {\n}\n finally {\n}', - options: ['stroustrup', { allowSingleLine: true }], - errors: [{ messageId: 'sameLineClose' }], - }, - { - code: 'try { \n bar(); \n } catch (e) {\n}\n finally {\n}', - output: 'try { \n bar(); \n }\n catch (e) {\n}\n finally {\n}', - options: ['stroustrup', { allowSingleLine: true }], - errors: [{ messageId: 'sameLineClose' }], - }, - { - code: 'if (a) { \nb();\n } else { \nc();\n }', - output: 'if (a) { \nb();\n }\n else { \nc();\n }', - options: ['stroustrup', { allowSingleLine: true }], - errors: [{ messageId: 'sameLineClose' }], - }, - { - code: 'if (foo)\n{ poop();\n} \nelse if (bar) {\nbaz();\n} else if (thing) {\nboom();\n}\nelse {\nqux();\n}', - output: - 'if (foo)\n{\n poop();\n} \nelse if (bar) \n{\nbaz();\n}\n else if (thing) \n{\nboom();\n}\nelse \n{\nqux();\n}', - options: ['allman', { allowSingleLine: true }], - errors: [ - { messageId: 'blockSameLine' }, - { messageId: 'sameLineOpen' }, - { messageId: 'sameLineClose' }, - { messageId: 'sameLineOpen' }, - { messageId: 'sameLineOpen' }, - ], - }, - // Comment interferes with fix - { - code: 'if (foo) // comment \n{\nbar();\n}', - output: null, - errors: [{ messageId: 'nextLineOpen' }], - }, - // https://github.com/eslint/eslint/issues/7493 - { - code: 'if (foo) {\n bar\n.baz }', - output: 'if (foo) {\n bar\n.baz \n}', - errors: [{ messageId: 'singleLineClose' }], - }, - { - code: 'if (foo)\n{\n bar\n.baz }', - output: 'if (foo)\n{\n bar\n.baz \n}', - options: ['allman'], - errors: [{ messageId: 'singleLineClose' }], - }, - { - code: 'if (foo) { bar\n.baz }', - output: 'if (foo) {\n bar\n.baz \n}', - options: ['1tbs', { allowSingleLine: true }], - errors: [ - { messageId: 'blockSameLine' }, - { messageId: 'singleLineClose' }, - ], - }, - { - code: 'if (foo) { bar\n.baz }', - output: 'if (foo) \n{\n bar\n.baz \n}', - options: ['allman', { allowSingleLine: true }], - errors: [ - { messageId: 'sameLineOpen' }, - { messageId: 'blockSameLine' }, - { messageId: 'singleLineClose' }, - ], - }, - { - code: 'switch (x) {\n case 1: foo() }', - output: 'switch (x) {\n case 1: foo() \n}', - options: ['1tbs', { allowSingleLine: true }], - errors: [{ messageId: 'singleLineClose' }], - }, - { - code: 'class Foo\n{\n}', - output: 'class Foo {\n}', - errors: [{ messageId: 'nextLineOpen' }], - }, - { - code: '(class\n{\n})', - output: '(class {\n})', - errors: [{ messageId: 'nextLineOpen' }], - }, - { - code: 'class Foo{\n}', - output: 'class Foo\n{\n}', - options: ['allman'], - errors: [{ messageId: 'sameLineOpen' }], - }, - { - code: '(class {\n})', - output: '(class \n{\n})', - options: ['allman'], - errors: [{ messageId: 'sameLineOpen' }], - }, - { - code: 'class Foo {\nbar() {\n}}', - output: 'class Foo {\nbar() {\n}\n}', - errors: [{ messageId: 'singleLineClose' }], - }, - { - code: '(class Foo {\nbar() {\n}})', - output: '(class Foo {\nbar() {\n}\n})', - errors: [{ messageId: 'singleLineClose' }], - }, - { - code: 'class\nFoo{}', - output: 'class\nFoo\n{}', - options: ['allman'], - errors: [{ messageId: 'sameLineOpen' }], - }, - // https://github.com/eslint/eslint/issues/7621 - { - code: ` -if (foo) -{ - bar -} -else { - baz -} - `, - output: ` -if (foo) { - bar -} else { - baz -} - `, - errors: [{ messageId: 'nextLineOpen' }, { messageId: 'nextLineClose' }], - }, - { - code: ` -interface Foo -{ -} - `, - output: ` -interface Foo { -} - `, - errors: [{ messageId: 'nextLineOpen' }], - }, - { - code: ` -interface Foo -{ -} - `, - output: ` -interface Foo { -} - `, - options: ['stroustrup'], - errors: [{ messageId: 'nextLineOpen' }], - }, - { - code: 'interface Foo { \n }', - output: 'interface Foo \n{ \n }', - options: ['allman'], - errors: [{ messageId: 'sameLineOpen' }], - }, - { - code: ` -module "Foo" -{ -} - `, - output: ` -module "Foo" { -} - `, - errors: [{ messageId: 'nextLineOpen' }], - }, - { - code: ` -module "Foo" -{ -} - `, - output: ` -module "Foo" { -} - `, - options: ['stroustrup'], - errors: [{ messageId: 'nextLineOpen' }], - }, - { - code: 'module "Foo" { \n }', - output: 'module "Foo" \n{ \n }', - options: ['allman'], - errors: [{ messageId: 'sameLineOpen' }], - }, - { - code: ` -namespace Foo -{ -} - `, - output: ` -namespace Foo { -} - `, - errors: [{ messageId: 'nextLineOpen' }], - }, - { - code: ` -namespace Foo -{ -} - `, - output: ` -namespace Foo { -} - `, - options: ['stroustrup'], - errors: [{ messageId: 'nextLineOpen' }], - }, - { - code: 'namespace Foo { \n }', - output: 'namespace Foo \n{ \n }', - options: ['allman'], - errors: [{ messageId: 'sameLineOpen' }], - }, - { - code: ` -enum Foo -{ -} - `, - output: ` -enum Foo { -} - `, - errors: [{ messageId: 'nextLineOpen' }], - }, - { - code: ` -enum Foo -{ -} - `, - output: ` -enum Foo { -} - `, - options: ['stroustrup'], - errors: [{ messageId: 'nextLineOpen' }], - }, - { - code: 'enum Foo { A }', - output: 'enum Foo \n{\n A \n}', - options: ['allman'], - errors: [ - { messageId: 'sameLineOpen' }, - { messageId: 'blockSameLine' }, - { messageId: 'singleLineClose' }, - ], - }, - ], -}); 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 9e6519c10db0..902274292572 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 @@ -2,9 +2,7 @@ import { RuleTester } from '@typescript-eslint/rule-tester'; import rule from '../../src/rules/class-literal-property-style'; -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', -}); +const ruleTester = new RuleTester(); ruleTester.run('class-literal-property-style', rule, { valid: [ diff --git a/packages/eslint-plugin/tests/rules/class-methods-use-this/class-methods-use-this-core.test.ts b/packages/eslint-plugin/tests/rules/class-methods-use-this/class-methods-use-this-core.test.ts index 4fba4d557f15..50f527082553 100644 --- a/packages/eslint-plugin/tests/rules/class-methods-use-this/class-methods-use-this-core.test.ts +++ b/packages/eslint-plugin/tests/rules/class-methods-use-this/class-methods-use-this-core.test.ts @@ -5,97 +5,113 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import rule from '../../../src/rules/class-methods-use-this'; -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', -}); +const ruleTester = new RuleTester(); ruleTester.run('class-methods-use-this', rule, { valid: [ - { code: 'class A { constructor() {} }', parserOptions: { ecmaVersion: 6 } }, - { code: 'class A { foo() {this} }', parserOptions: { ecmaVersion: 6 } }, + { + code: 'class A { constructor() {} }', + languageOptions: { parserOptions: { ecmaVersion: 6 } }, + }, + { + code: 'class A { foo() {this} }', + languageOptions: { parserOptions: { ecmaVersion: 6 } }, + }, { code: "class A { foo() {this.bar = 'bar';} }", - parserOptions: { ecmaVersion: 6 }, + languageOptions: { parserOptions: { ecmaVersion: 6 } }, }, { code: 'class A { foo() {bar(this);} }', - parserOptions: { ecmaVersion: 6 }, + languageOptions: { parserOptions: { ecmaVersion: 6 } }, }, { code: 'class A extends B { foo() {super.foo();} }', - parserOptions: { ecmaVersion: 6 }, + languageOptions: { parserOptions: { ecmaVersion: 6 } }, }, { code: 'class A { foo() { if(true) { return this; } } }', - parserOptions: { ecmaVersion: 6 }, + languageOptions: { parserOptions: { ecmaVersion: 6 } }, + }, + { + code: 'class A { static foo() {} }', + languageOptions: { parserOptions: { ecmaVersion: 6 } }, + }, + { + code: '({ a(){} });', + languageOptions: { parserOptions: { ecmaVersion: 6 } }, }, - { code: 'class A { static foo() {} }', parserOptions: { ecmaVersion: 6 } }, - { code: '({ a(){} });', parserOptions: { ecmaVersion: 6 } }, { code: 'class A { foo() { () => this; } }', - parserOptions: { ecmaVersion: 6 }, + languageOptions: { parserOptions: { ecmaVersion: 6 } }, + }, + { + code: '({ a: function () {} });', + languageOptions: { parserOptions: { ecmaVersion: 6 } }, }, - { code: '({ a: function () {} });', parserOptions: { ecmaVersion: 6 } }, { code: 'class A { foo() {this} bar() {} }', options: [{ exceptMethods: ['bar'] }], - parserOptions: { ecmaVersion: 6 }, + languageOptions: { parserOptions: { ecmaVersion: 6 } }, }, { code: 'class A { "foo"() { } }', options: [{ exceptMethods: ['foo'] }], - parserOptions: { ecmaVersion: 6 }, + languageOptions: { parserOptions: { ecmaVersion: 6 } }, }, { code: 'class A { 42() { } }', options: [{ exceptMethods: ['42'] }], - parserOptions: { ecmaVersion: 6 }, + languageOptions: { parserOptions: { ecmaVersion: 6 } }, }, { code: 'class A { foo = function() {this} }', - parserOptions: { ecmaVersion: 2022 }, + languageOptions: { parserOptions: { ecmaVersion: 2022 } }, }, { code: 'class A { foo = () => {this} }', - parserOptions: { ecmaVersion: 2022 }, + languageOptions: { parserOptions: { ecmaVersion: 2022 } }, }, { code: 'class A { foo = () => {super.toString} }', - parserOptions: { ecmaVersion: 2022 }, + languageOptions: { parserOptions: { ecmaVersion: 2022 } }, }, { code: 'class A { static foo = function() {} }', - parserOptions: { ecmaVersion: 2022 }, + languageOptions: { parserOptions: { ecmaVersion: 2022 } }, }, { code: 'class A { static foo = () => {} }', - parserOptions: { ecmaVersion: 2022 }, + languageOptions: { parserOptions: { ecmaVersion: 2022 } }, }, { code: 'class A { #bar() {} }', options: [{ exceptMethods: ['#bar'] }], - parserOptions: { ecmaVersion: 2022 }, + languageOptions: { parserOptions: { ecmaVersion: 2022 } }, }, { code: 'class A { foo = function () {} }', options: [{ enforceForClassFields: false }], - parserOptions: { ecmaVersion: 2022 }, + languageOptions: { parserOptions: { ecmaVersion: 2022 } }, }, { code: 'class A { foo = () => {} }', options: [{ enforceForClassFields: false }], - parserOptions: { ecmaVersion: 2022 }, + languageOptions: { parserOptions: { ecmaVersion: 2022 } }, }, { code: 'class A { foo() { return class { [this.foo] = 1 }; } }', - parserOptions: { ecmaVersion: 2022 }, + languageOptions: { parserOptions: { ecmaVersion: 2022 } }, + }, + { + code: 'class A { static {} }', + languageOptions: { parserOptions: { ecmaVersion: 2022 } }, }, - { code: 'class A { static {} }', parserOptions: { ecmaVersion: 2022 } }, ], invalid: [ { code: 'class A { foo() {} }', - parserOptions: { ecmaVersion: 6 }, + languageOptions: { parserOptions: { ecmaVersion: 6 } }, errors: [ { type: AST_NODE_TYPES.FunctionExpression, @@ -108,7 +124,7 @@ ruleTester.run('class-methods-use-this', rule, { }, { code: 'class A { foo() {/**this**/} }', - parserOptions: { ecmaVersion: 6 }, + languageOptions: { parserOptions: { ecmaVersion: 6 } }, errors: [ { type: AST_NODE_TYPES.FunctionExpression, @@ -121,7 +137,7 @@ ruleTester.run('class-methods-use-this', rule, { }, { code: 'class A { foo() {var a = function () {this};} }', - parserOptions: { ecmaVersion: 6 }, + languageOptions: { parserOptions: { ecmaVersion: 6 } }, errors: [ { type: AST_NODE_TYPES.FunctionExpression, @@ -134,7 +150,7 @@ ruleTester.run('class-methods-use-this', rule, { }, { code: 'class A { foo() {var a = function () {var b = function(){this}};} }', - parserOptions: { ecmaVersion: 6 }, + languageOptions: { parserOptions: { ecmaVersion: 6 } }, errors: [ { type: AST_NODE_TYPES.FunctionExpression, @@ -147,7 +163,7 @@ ruleTester.run('class-methods-use-this', rule, { }, { code: 'class A { foo() {window.this} }', - parserOptions: { ecmaVersion: 6 }, + languageOptions: { parserOptions: { ecmaVersion: 6 } }, errors: [ { type: AST_NODE_TYPES.FunctionExpression, @@ -160,7 +176,7 @@ ruleTester.run('class-methods-use-this', rule, { }, { code: "class A { foo() {that.this = 'this';} }", - parserOptions: { ecmaVersion: 6 }, + languageOptions: { parserOptions: { ecmaVersion: 6 } }, errors: [ { type: AST_NODE_TYPES.FunctionExpression, @@ -173,7 +189,7 @@ ruleTester.run('class-methods-use-this', rule, { }, { code: 'class A { foo() { () => undefined; } }', - parserOptions: { ecmaVersion: 6 }, + languageOptions: { parserOptions: { ecmaVersion: 6 } }, errors: [ { type: AST_NODE_TYPES.FunctionExpression, @@ -187,7 +203,7 @@ ruleTester.run('class-methods-use-this', rule, { { code: 'class A { foo() {} bar() {} }', options: [{ exceptMethods: ['bar'] }], - parserOptions: { ecmaVersion: 6 }, + languageOptions: { parserOptions: { ecmaVersion: 6 } }, errors: [ { type: AST_NODE_TYPES.FunctionExpression, @@ -201,7 +217,7 @@ ruleTester.run('class-methods-use-this', rule, { { code: 'class A { foo() {} hasOwnProperty() {} }', options: [{ exceptMethods: ['foo'] }], - parserOptions: { ecmaVersion: 6 }, + languageOptions: { parserOptions: { ecmaVersion: 6 } }, errors: [ { type: AST_NODE_TYPES.FunctionExpression, @@ -215,7 +231,7 @@ ruleTester.run('class-methods-use-this', rule, { { code: 'class A { [foo]() {} }', options: [{ exceptMethods: ['foo'] }], - parserOptions: { ecmaVersion: 6 }, + languageOptions: { parserOptions: { ecmaVersion: 6 } }, errors: [ { type: AST_NODE_TYPES.FunctionExpression, @@ -229,7 +245,7 @@ ruleTester.run('class-methods-use-this', rule, { { code: 'class A { #foo() { } foo() {} #bar() {} }', options: [{ exceptMethods: ['#foo'] }], - parserOptions: { ecmaVersion: 2022 }, + languageOptions: { parserOptions: { ecmaVersion: 2022 } }, errors: [ { type: AST_NODE_TYPES.FunctionExpression, @@ -249,7 +265,7 @@ ruleTester.run('class-methods-use-this', rule, { }, { code: "class A { foo(){} 'bar'(){} 123(){} [`baz`](){} [a](){} [f(a)](){} get quux(){} set[a](b){} *quuux(){} }", - parserOptions: { ecmaVersion: 6 }, + languageOptions: { parserOptions: { ecmaVersion: 6 } }, errors: [ { messageId: 'missingThis', @@ -309,7 +325,7 @@ ruleTester.run('class-methods-use-this', rule, { }, { code: 'class A { foo = function() {} }', - parserOptions: { ecmaVersion: 2022 }, + languageOptions: { parserOptions: { ecmaVersion: 2022 } }, errors: [ { messageId: 'missingThis', @@ -321,7 +337,7 @@ ruleTester.run('class-methods-use-this', rule, { }, { code: 'class A { foo = () => {} }', - parserOptions: { ecmaVersion: 2022 }, + languageOptions: { parserOptions: { ecmaVersion: 2022 } }, errors: [ { messageId: 'missingThis', @@ -333,7 +349,7 @@ ruleTester.run('class-methods-use-this', rule, { }, { code: 'class A { #foo = function() {} }', - parserOptions: { ecmaVersion: 2022 }, + languageOptions: { parserOptions: { ecmaVersion: 2022 } }, errors: [ { messageId: 'missingThis', @@ -345,7 +361,7 @@ ruleTester.run('class-methods-use-this', rule, { }, { code: 'class A { #foo = () => {} }', - parserOptions: { ecmaVersion: 2022 }, + languageOptions: { parserOptions: { ecmaVersion: 2022 } }, errors: [ { messageId: 'missingThis', @@ -357,7 +373,7 @@ ruleTester.run('class-methods-use-this', rule, { }, { code: 'class A { #foo() {} }', - parserOptions: { ecmaVersion: 2022 }, + languageOptions: { parserOptions: { ecmaVersion: 2022 } }, errors: [ { messageId: 'missingThis', @@ -369,7 +385,7 @@ ruleTester.run('class-methods-use-this', rule, { }, { code: 'class A { get #foo() {} }', - parserOptions: { ecmaVersion: 2022 }, + languageOptions: { parserOptions: { ecmaVersion: 2022 } }, errors: [ { messageId: 'missingThis', @@ -381,7 +397,7 @@ ruleTester.run('class-methods-use-this', rule, { }, { code: 'class A { set #foo(x) {} }', - parserOptions: { ecmaVersion: 2022 }, + languageOptions: { parserOptions: { ecmaVersion: 2022 } }, errors: [ { messageId: 'missingThis', @@ -393,7 +409,7 @@ ruleTester.run('class-methods-use-this', rule, { }, { code: 'class A { foo () { return class { foo = this }; } }', - parserOptions: { ecmaVersion: 2022 }, + languageOptions: { parserOptions: { ecmaVersion: 2022 } }, errors: [ { messageId: 'missingThis', @@ -405,7 +421,7 @@ ruleTester.run('class-methods-use-this', rule, { }, { code: 'class A { foo () { return function () { foo = this }; } }', - parserOptions: { ecmaVersion: 2022 }, + languageOptions: { parserOptions: { ecmaVersion: 2022 } }, errors: [ { messageId: 'missingThis', @@ -417,7 +433,7 @@ ruleTester.run('class-methods-use-this', rule, { }, { code: 'class A { foo () { return class { static { this; } } } }', - parserOptions: { ecmaVersion: 2022 }, + languageOptions: { parserOptions: { ecmaVersion: 2022 } }, errors: [ { messageId: 'missingThis', diff --git a/packages/eslint-plugin/tests/rules/class-methods-use-this/class-methods-use-this.test.ts b/packages/eslint-plugin/tests/rules/class-methods-use-this/class-methods-use-this.test.ts index 31303569beab..575daef11a8b 100644 --- a/packages/eslint-plugin/tests/rules/class-methods-use-this/class-methods-use-this.test.ts +++ b/packages/eslint-plugin/tests/rules/class-methods-use-this/class-methods-use-this.test.ts @@ -2,9 +2,7 @@ import { RuleTester } from '@typescript-eslint/rule-tester'; import rule from '../../../src/rules/class-methods-use-this'; -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', -}); +const ruleTester = new RuleTester(); ruleTester.run('class-methods-use-this', rule, { valid: [ diff --git a/packages/eslint-plugin/tests/rules/comma-dangle.test.ts b/packages/eslint-plugin/tests/rules/comma-dangle.test.ts deleted file mode 100644 index 148f05c0a774..000000000000 --- a/packages/eslint-plugin/tests/rules/comma-dangle.test.ts +++ /dev/null @@ -1,259 +0,0 @@ -/* eslint-disable eslint-comments/no-use */ -// this rule tests the new lines, which prettier will want to fix and break the tests -/* eslint "@typescript-eslint/internal/plugin-test-formatting": ["error", { formatWithPrettier: false }] */ -/* eslint-enable eslint-comments/no-use */ -import { RuleTester } from '@typescript-eslint/rule-tester'; - -import rule from '../../src/rules/comma-dangle'; - -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', -}); - -ruleTester.run('comma-dangle', rule, { - valid: [ - // default - { code: 'enum Foo {}' }, - { code: 'enum Foo {\n}' }, - { code: 'enum Foo {Bar}' }, - { code: 'function Foo() {}' }, - { code: 'type Foo = []' }, - { code: 'type Foo = [\n]' }, - - // never - { code: 'enum Foo {Bar}', options: ['never'] }, - { code: 'enum Foo {Bar\n}', options: ['never'] }, - { code: 'enum Foo {Bar\n}', options: [{ enums: 'never' }] }, - { code: 'function Foo() {}', options: ['never'] }, - { code: 'function Foo() {}', options: ['never'] }, - { code: 'function Foo() {}', options: [{ generics: 'never' }] }, - { code: 'type Foo = [string]', options: ['never'] }, - { code: 'type Foo = [string]', options: [{ tuples: 'never' }] }, - - // always - { code: 'enum Foo {Bar,}', options: ['always'] }, - { code: 'enum Foo {Bar,\n}', options: ['always'] }, - { code: 'enum Foo {Bar,\n}', options: [{ enums: 'always' }] }, - { code: 'function Foo() {}', options: ['always'] }, - { code: 'function Foo() {}', options: ['always'] }, - { code: 'function Foo() {}', options: [{ generics: 'always' }] }, - { code: 'type Foo = [string,]', options: ['always'] }, - { code: 'type Foo = [string,\n]', options: [{ tuples: 'always' }] }, - - // always-multiline - { code: 'enum Foo {Bar}', options: ['always-multiline'] }, - { code: 'enum Foo {Bar,\n}', options: ['always-multiline'] }, - { code: 'enum Foo {Bar,\n}', options: [{ enums: 'always-multiline' }] }, - { code: 'function Foo() {}', options: ['always-multiline'] }, - { code: 'function Foo() {}', options: ['always-multiline'] }, - { - code: 'function Foo() {}', - options: [{ generics: 'always-multiline' }], - }, - { code: 'type Foo = [string]', options: ['always-multiline'] }, - { code: 'type Foo = [string,\n]', options: ['always-multiline'] }, - { - code: 'type Foo = [string,\n]', - options: [{ tuples: 'always-multiline' }], - }, - - // only-multiline - { code: 'enum Foo {Bar}', options: ['only-multiline'] }, - { code: 'enum Foo {Bar\n}', options: ['only-multiline'] }, - { code: 'enum Foo {Bar,\n}', options: ['only-multiline'] }, - { code: 'enum Foo {Bar,\n}', options: [{ enums: 'only-multiline' }] }, - { code: 'function Foo() {}', options: ['only-multiline'] }, - { code: 'function Foo() {}', options: ['only-multiline'] }, - { code: 'function Foo() {}', options: ['only-multiline'] }, - { - code: 'function Foo() {}', - options: [{ generics: 'only-multiline' }], - }, - { - code: 'function Foo() {}', - options: [{ generics: 'only-multiline' }], - }, - { code: 'type Foo = [string\n]', options: [{ tuples: 'only-multiline' }] }, - { code: 'type Foo = [string,\n]', options: [{ tuples: 'only-multiline' }] }, - - // ignore - { code: 'const a = () => {}', options: [{ generics: 'ignore' }] }, - - // each options - { - code: ` -const Obj = { a: 1 }; -enum Foo {Bar} -function Baz() {} -type Qux = [string, -] - `, - options: [ - { - enums: 'never', - generics: 'always', - tuples: 'always-multiline', - }, - ], - }, - ], - invalid: [ - // base rule - { - code: 'const Foo = {bar: 1,}', - output: 'const Foo = {bar: 1}', - errors: [{ messageId: 'unexpected' }], - }, - - // default - { - code: 'enum Foo {Bar,}', - output: 'enum Foo {Bar}', - errors: [{ messageId: 'unexpected' }], - }, - { - code: 'function Foo() {}', - output: 'function Foo() {}', - errors: [{ messageId: 'unexpected' }], - }, - { - code: 'type Foo = [string,]', - output: 'type Foo = [string]', - errors: [{ messageId: 'unexpected' }], - }, - - // never - { - code: 'enum Foo {Bar,}', - output: 'enum Foo {Bar}', - options: ['never'], - errors: [{ messageId: 'unexpected' }], - }, - { - code: 'enum Foo {Bar,\n}', - output: 'enum Foo {Bar\n}', - options: ['never'], - errors: [{ messageId: 'unexpected' }], - }, - { - code: 'function Foo() {}', - output: 'function Foo() {}', - options: ['never'], - errors: [{ messageId: 'unexpected' }], - }, - { - code: 'function Foo() {}', - output: 'function Foo() {}', - options: ['never'], - errors: [{ messageId: 'unexpected' }], - }, - { - code: 'type Foo = [string,]', - output: 'type Foo = [string]', - options: ['never'], - errors: [{ messageId: 'unexpected' }], - }, - { - code: 'type Foo = [string,\n]', - output: 'type Foo = [string\n]', - options: ['never'], - errors: [{ messageId: 'unexpected' }], - }, - - // always - { - code: 'enum Foo {Bar}', - output: 'enum Foo {Bar,}', - options: ['always'], - errors: [{ messageId: 'missing' }], - }, - { - code: 'enum Foo {Bar\n}', - output: 'enum Foo {Bar,\n}', - options: ['always'], - errors: [{ messageId: 'missing' }], - }, - { - code: 'function Foo() {}', - output: 'function Foo() {}', - options: ['always'], - errors: [{ messageId: 'missing' }], - }, - { - code: 'function Foo() {}', - output: 'function Foo() {}', - options: ['always'], - errors: [{ messageId: 'missing' }], - }, - { - code: 'type Foo = [string]', - output: 'type Foo = [string,]', - options: ['always'], - errors: [{ messageId: 'missing' }], - }, - { - code: 'type Foo = [string\n]', - output: 'type Foo = [string,\n]', - options: ['always'], - errors: [{ messageId: 'missing' }], - }, - - // always-multiline - { - code: 'enum Foo {Bar,}', - output: 'enum Foo {Bar}', - options: ['always-multiline'], - errors: [{ messageId: 'unexpected' }], - }, - { - code: 'enum Foo {Bar\n}', - output: 'enum Foo {Bar,\n}', - options: ['always-multiline'], - errors: [{ messageId: 'missing' }], - }, - { - code: 'function Foo() {}', - output: 'function Foo() {}', - options: ['always-multiline'], - errors: [{ messageId: 'unexpected' }], - }, - { - code: 'function Foo() {}', - output: 'function Foo() {}', - options: ['always-multiline'], - errors: [{ messageId: 'missing' }], - }, - { - code: 'type Foo = [string,]', - output: 'type Foo = [string]', - options: ['always-multiline'], - errors: [{ messageId: 'unexpected' }], - }, - { - code: 'type Foo = [string\n]', - output: 'type Foo = [string,\n]', - options: ['always-multiline'], - errors: [{ messageId: 'missing' }], - }, - - // only-multiline - { - code: 'enum Foo {Bar,}', - output: 'enum Foo {Bar}', - options: ['only-multiline'], - errors: [{ messageId: 'unexpected' }], - }, - { - code: 'function Foo() {}', - output: 'function Foo() {}', - options: ['only-multiline'], - errors: [{ messageId: 'unexpected' }], - }, - { - code: 'type Foo = [string,]', - output: 'type Foo = [string]', - options: ['only-multiline'], - errors: [{ messageId: 'unexpected' }], - }, - ], -}); diff --git a/packages/eslint-plugin/tests/rules/comma-spacing.test.ts b/packages/eslint-plugin/tests/rules/comma-spacing.test.ts deleted file mode 100644 index d86edef2ae34..000000000000 --- a/packages/eslint-plugin/tests/rules/comma-spacing.test.ts +++ /dev/null @@ -1,854 +0,0 @@ -/* eslint-disable eslint-comments/no-use */ -// this rule tests the spacing, which prettier will want to fix and break the tests -/* eslint "@typescript-eslint/internal/plugin-test-formatting": ["error", { formatWithPrettier: false }] */ -/* eslint-enable eslint-comments/no-use */ - -import { RuleTester } from '@typescript-eslint/rule-tester'; - -import rule from '../../src/rules/comma-spacing'; - -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', -}); - -ruleTester.run('comma-spacing', rule, { - valid: [ - "foo(1, true/* comment */, 'text');", - "foo(1, true /* comment */, 'text');", - "foo(1, true/* comment *//* comment */, 'text');", - "foo(1, true/* comment */ /* comment */, 'text');", - "foo(1, true, /* comment */ 'text');", - "foo(1, // comment\n true, /* comment */ 'text');", - { - code: "foo(1, // comment\n true,/* comment */ 'text');", - options: [{ before: false, after: false }], - }, - 'const a = 1, b = 2;', - 'const foo = [, ];', - 'const foo = [1, ];', - 'const foo = [, 2];', - 'const foo = [1, 2];', - 'const foo = [, , ];', - 'const foo = [1, , ];', - 'const foo = [, 2, ];', - 'const foo = [, , 3];', - 'const foo = [1, 2, ];', - 'const foo = [, 2, 3];', - 'const foo = [1, , 3];', - 'const foo = [1, 2, 3];', - "const foo = {'foo':'foo', 'baz':'baz'};", - "const foo = {'foo':'foo', 'baz':\n'baz'};", - "const foo = {'foo':\n'foo', 'baz':\n'baz'};", - 'function foo(a, b){}', - 'function foo(a, b = 1){}', - 'function foo(a = 1, b, c){}', - 'const foo = (a, b) => {}', - 'const foo = (a=1, b) => {}', - 'const foo = a => a + 2', - 'a, b', - 'const a = (1 + 2, 2)', - 'a(b, c)', - 'new A(b, c)', - 'foo((a), b)', - 'const b = ((1 + 2), 2)', - 'parseInt((a + b), 10)', - 'go.boom((a + b), 10)', - 'go.boom((a + b), 10, (4))', - 'const x = [ (a + c), (b + b) ]', - "[' , ']", - '[` , `]', - '`${[1, 2]}`', - 'fn(a, b,)', - 'const fn = (a, b,) => {}', - 'const fn = function (a, b,) {}', - "foo(/,/, 'a')", - "const x = ',,,,,';", - "const code = 'var foo = 1, bar = 3;'", - "['apples', \n 'oranges'];", - "{x: 'var x,y,z'}", - { - code: "const foo = {'foo':\n'bar' ,'baz':\n'qur'};", - options: [{ before: true, after: false }], - }, - { - code: 'const a = 1 ,b = 2;', - options: [{ before: true, after: false }], - }, - { - code: 'function foo(a ,b){}', - options: [{ before: true, after: false }], - }, - { - code: 'const arr = [,];', - options: [{ before: true, after: false }], - }, - { - code: 'const arr = [1 ,];', - options: [{ before: true, after: false }], - }, - { - code: 'const arr = [ ,2];', - options: [{ before: true, after: false }], - }, - { - code: 'const arr = [1 ,2];', - options: [{ before: true, after: false }], - }, - { - code: 'const arr = [,,];', - options: [{ before: true, after: false }], - }, - { - code: 'const arr = [1 , ,];', - options: [{ before: true, after: false }], - }, - { - code: 'const arr = [ ,2 ,];', - options: [{ before: true, after: false }], - }, - { - code: 'const arr = [ , ,3];', - options: [{ before: true, after: false }], - }, - { - code: 'const arr = [1 ,2 ,];', - options: [{ before: true, after: false }], - }, - { - code: 'const arr = [ ,2 ,3];', - options: [{ before: true, after: false }], - }, - { - code: 'const arr = [1 , ,3];', - options: [{ before: true, after: false }], - }, - { - code: 'const arr = [1 ,2 ,3];', - options: [{ before: true, after: false }], - }, - { - code: "const obj = {'foo':'bar' , 'baz':'qur'};", - options: [{ before: true, after: true }], - }, - { - code: 'const a = 1 , b = 2;', - options: [{ before: true, after: true }], - }, - { - code: 'const arr = [, ];', - options: [{ before: true, after: true }], - }, - { - code: 'const arr = [1 , ];', - options: [{ before: true, after: true }], - }, - { - code: 'const arr = [ , 2];', - options: [{ before: true, after: true }], - }, - { - code: 'const arr = [1 , 2];', - options: [{ before: true, after: true }], - }, - { - code: 'const arr = [, , ];', - options: [{ before: true, after: true }], - }, - { - code: 'const arr = [1 , , ];', - options: [{ before: true, after: true }], - }, - { - code: 'const arr = [ , 2 , ];', - options: [{ before: true, after: true }], - }, - { - code: 'const arr = [ , , 3];', - options: [{ before: true, after: true }], - }, - { - code: 'const arr = [1 , 2 , ];', - options: [{ before: true, after: true }], - }, - { - code: 'const arr = [, 2 , 3];', - options: [{ before: true, after: true }], - }, - { - code: 'const arr = [1 , , 3];', - options: [{ before: true, after: true }], - }, - { - code: 'const arr = [1 , 2 , 3];', - options: [{ before: true, after: true }], - }, - { - code: 'a , b', - options: [{ before: true, after: true }], - }, - { - code: 'const arr = [,];', - options: [{ before: false, after: false }], - }, - { - code: 'const arr = [ ,];', - options: [{ before: false, after: false }], - }, - { - code: 'const arr = [1,];', - options: [{ before: false, after: false }], - }, - { - code: 'const arr = [,2];', - options: [{ before: false, after: false }], - }, - { - code: 'const arr = [ ,2];', - options: [{ before: false, after: false }], - }, - { - code: 'const arr = [1,2];', - options: [{ before: false, after: false }], - }, - { - code: 'const arr = [,,];', - options: [{ before: false, after: false }], - }, - { - code: 'const arr = [ ,,];', - options: [{ before: false, after: false }], - }, - { - code: 'const arr = [1,,];', - options: [{ before: false, after: false }], - }, - { - code: 'const arr = [,2,];', - options: [{ before: false, after: false }], - }, - { - code: 'const arr = [ ,2,];', - options: [{ before: false, after: false }], - }, - { - code: 'const arr = [,,3];', - options: [{ before: false, after: false }], - }, - { - code: 'const arr = [1,2,];', - options: [{ before: false, after: false }], - }, - { - code: 'const arr = [,2,3];', - options: [{ before: false, after: false }], - }, - { - code: 'const arr = [1,,3];', - options: [{ before: false, after: false }], - }, - { - code: 'const arr = [1,2,3];', - options: [{ before: false, after: false }], - }, - { - code: 'const a = (1 + 2,2)', - options: [{ before: false, after: false }], - }, - 'const a; console.log(`${a}`, "a");', - 'const [a, b] = [1, 2];', - 'const [a, b, ] = [1, 2];', - 'const [a, , b] = [1, 2, 3];', - 'const [ , b] = a;', - 'const [, b] = a;', - { - code: ',', - parserOptions: { - ecmaFeatures: { jsx: true }, - }, - }, - { - code: ' , ', - parserOptions: { - ecmaFeatures: { jsx: true }, - }, - }, - { - code: 'Hello, world', - options: [{ before: true, after: false }], - parserOptions: { ecmaFeatures: { jsx: true } }, - }, - 'const Foo = (foo: T) => {}', - 'function foo() {}', - 'class Foo {}', - 'interface Foo{}', - 'interface A<> {}', - 'let foo,', - 'const arr = [,];', - 'const arr = [ ,];', - 'const arr = [ , ];', - 'const arr = [1,];', - 'const arr = [ , 2];', - 'const arr = [,,];', - 'const arr = [ ,,];', - 'const arr = [, ,];', - 'const arr = [,, ];', - 'const arr = [ , ,];', - 'const arr = [ ,, ];', - 'const arr = [ , , ];', - 'const arr = [,, 3];', - 'const arr = [1, 2, 3,];', - 'const arr = [1, 2, 3, ];', - "const obj = {'foo':'bar', 'baz':'qur', };", - "const obj = {'foo':'bar', 'baz':'qur',};", - { code: 'const arr = [ ,];', options: [{ before: true, after: false }] }, - { code: 'const arr = [, ];', options: [{ before: true, after: false }] }, - { code: 'const arr = [ , ];', options: [{ before: true, after: false }] }, - { code: 'const arr = [ ,,];', options: [{ before: true, after: false }] }, - { code: 'const arr = [, ,];', options: [{ before: true, after: false }] }, - { code: 'const arr = [,, ];', options: [{ before: true, after: false }] }, - { code: 'const arr = [ , ,];', options: [{ before: true, after: false }] }, - { code: 'const arr = [ ,, ];', options: [{ before: true, after: false }] }, - { code: 'const arr = [, , ];', options: [{ before: true, after: false }] }, - { code: 'const arr = [ , , ];', options: [{ before: true, after: false }] }, - { - code: 'const arr = [ , , ];', - options: [{ before: false, after: false }], - }, - { code: 'const [a, b,] = [1, 2];', parserOptions: { ecmaVersion: 6 } }, - { - code: 'Hello, world', - options: [{ before: true, after: false }], - parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } }, - }, - { code: '[a, /**/ , ]', options: [{ before: false, after: true }] }, - { code: '[a , /**/, ]', options: [{ before: true, after: true }] }, - { - code: '[a, /**/ , ] = foo', - options: [{ before: false, after: true }], - parserOptions: { ecmaVersion: 6 }, - }, - { - code: '[a , /**/, ] = foo', - options: [{ before: true, after: true }], - parserOptions: { ecmaVersion: 6 }, - }, - ], - - invalid: [ - { - code: 'a(b,c)', - output: 'a(b , c)', - options: [{ before: true, after: true }], - errors: [ - { - messageId: 'missing', - column: 4, - line: 1, - data: { loc: 'before' }, - }, - { - messageId: 'missing', - column: 4, - line: 1, - data: { loc: 'after' }, - }, - ], - }, - { - code: 'new A(b,c)', - output: 'new A(b , c)', - options: [{ before: true, after: true }], - errors: [ - { - messageId: 'missing', - column: 8, - line: 1, - data: { loc: 'before' }, - }, - { - messageId: 'missing', - column: 8, - line: 1, - data: { loc: 'after' }, - }, - ], - }, - { - code: 'const a = 1 ,b = 2;', - output: 'const a = 1, b = 2;', - errors: [ - { - messageId: 'unexpected', - column: 13, - line: 1, - data: { loc: 'before' }, - }, - { - messageId: 'missing', - column: 13, - line: 1, - data: { loc: 'after' }, - }, - ], - }, - { - code: 'const arr = [1 , 2];', - output: 'const arr = [1, 2];', - errors: [ - { - messageId: 'unexpected', - column: 16, - line: 1, - data: { loc: 'before' }, - }, - ], - }, - { - code: 'const arr = [1 , ];', - output: 'const arr = [1, ];', - errors: [ - { - messageId: 'unexpected', - column: 16, - line: 1, - data: { loc: 'before' }, - }, - ], - }, - { - code: 'const arr = [1 , ];', - output: 'const arr = [1 ,];', - options: [{ before: true, after: false }], - errors: [ - { - messageId: 'unexpected', - column: 16, - line: 1, - data: { loc: 'after' }, - }, - ], - }, - { - code: 'const arr = [1 ,2];', - output: 'const arr = [1, 2];', - errors: [ - { - messageId: 'unexpected', - column: 16, - line: 1, - data: { loc: 'before' }, - }, - { - messageId: `missing`, - column: 16, - line: 1, - data: { loc: 'after' }, - }, - ], - }, - { - code: 'const arr = [(1) , 2];', - output: 'const arr = [(1), 2];', - errors: [ - { - messageId: 'unexpected', - column: 18, - line: 1, - data: { loc: 'before' }, - }, - ], - }, - { - code: 'const arr = [1, 2];', - output: 'const arr = [1 ,2];', - options: [{ before: true, after: false }], - errors: [ - { - messageId: 'missing', - column: 15, - line: 1, - data: { loc: 'before' }, - }, - { - messageId: 'unexpected', - column: 15, - line: 1, - data: { loc: 'after' }, - }, - ], - }, - { - code: 'const arr = [1\n , 2];', - output: 'const arr = [1\n ,2];', - options: [{ before: false, after: false }], - errors: [ - { - messageId: 'unexpected', - column: 3, - line: 2, - data: { loc: 'after' }, - }, - ], - }, - { - code: 'const arr = [1,\n 2];', - output: 'const arr = [1 ,\n 2];', - options: [{ before: true, after: false }], - errors: [ - { - messageId: 'missing', - column: 15, - line: 1, - data: { loc: 'before' }, - }, - ], - }, - { - code: "const obj = {'foo':\n'bar', 'baz':\n'qur'};", - output: "const obj = {'foo':\n'bar' ,'baz':\n'qur'};", - options: [{ before: true, after: false }], - errors: [ - { - messageId: 'missing', - column: 6, - line: 2, - data: { loc: 'before' }, - }, - { - messageId: 'unexpected', - column: 6, - line: 2, - data: { loc: 'after' }, - }, - ], - }, - { - code: 'const obj = {a: 1\n ,b: 2};', - output: 'const obj = {a: 1\n , b: 2};', - options: [{ before: false, after: true }], - errors: [ - { - messageId: 'missing', - column: 3, - line: 2, - data: { loc: 'after' }, - }, - ], - }, - { - code: 'const obj = {a: 1 ,\n b: 2};', - output: 'const obj = {a: 1,\n b: 2};', - options: [{ before: false, after: false }], - errors: [ - { - messageId: 'unexpected', - column: 19, - line: 1, - data: { loc: 'before' }, - }, - ], - }, - { - code: 'const arr = [1 ,2];', - output: 'const arr = [1 , 2];', - options: [{ before: true, after: true }], - errors: [ - { - messageId: 'missing', - column: 16, - line: 1, - data: { loc: 'after' }, - }, - ], - }, - { - code: 'const arr = [1,2];', - output: 'const arr = [1 , 2];', - options: [{ before: true, after: true }], - errors: [ - { - messageId: 'missing', - column: 15, - line: 1, - data: { loc: 'before' }, - }, - { - messageId: 'missing', - column: 15, - line: 1, - data: { loc: 'after' }, - }, - ], - }, - { - code: "const obj = {'foo':\n'bar','baz':\n'qur'};", - output: "const obj = {'foo':\n'bar' , 'baz':\n'qur'};", - options: [{ before: true, after: true }], - errors: [ - { - messageId: 'missing', - column: 6, - line: 2, - data: { loc: 'before' }, - }, - { - messageId: 'missing', - column: 6, - line: 2, - data: { loc: 'after' }, - }, - ], - }, - { - code: 'const arr = [1 , 2];', - output: 'const arr = [1,2];', - options: [{ before: false, after: false }], - errors: [ - { - messageId: 'unexpected', - column: 16, - line: 1, - data: { loc: 'before' }, - }, - { - messageId: 'unexpected', - column: 16, - line: 1, - data: { loc: 'after' }, - }, - ], - }, - { - code: 'a ,b', - output: 'a, b', - options: [{ before: false, after: true }], - errors: [ - { - messageId: 'unexpected', - column: 3, - line: 1, - data: { loc: 'before' }, - }, - { - messageId: 'missing', - column: 3, - line: 1, - data: { loc: 'after' }, - }, - ], - }, - { - code: 'function foo(a,b){}', - output: 'function foo(a , b){}', - options: [{ before: true, after: true }], - errors: [ - { - messageId: 'missing', - column: 15, - line: 1, - data: { loc: 'before' }, - }, - { - messageId: 'missing', - column: 15, - line: 1, - data: { loc: 'after' }, - }, - ], - }, - { - code: 'const foo = (a,b) => {}', - output: 'const foo = (a , b) => {}', - options: [{ before: true, after: true }], - errors: [ - { - messageId: 'missing', - column: 15, - line: 1, - data: { loc: 'before' }, - }, - { - messageId: 'missing', - column: 15, - line: 1, - data: { loc: 'after' }, - }, - ], - }, - { - code: 'const foo = (a = 1,b) => {}', - output: 'const foo = (a = 1 , b) => {}', - options: [{ before: true, after: true }], - errors: [ - { - messageId: 'missing', - column: 19, - line: 1, - data: { loc: 'before' }, - }, - { - messageId: 'missing', - column: 19, - line: 1, - data: { loc: 'after' }, - }, - ], - }, - { - code: 'function foo(a = 1 ,b = 2) {}', - output: 'function foo(a = 1, b = 2) {}', - options: [{ before: false, after: true }], - errors: [ - { - messageId: 'unexpected', - column: 20, - line: 1, - data: { loc: 'before' }, - }, - { - messageId: 'missing', - column: 20, - line: 1, - data: { loc: 'after' }, - }, - ], - }, - { - code: '{foo(1 ,2)}', - output: '{foo(1, 2)}', - parserOptions: { - ecmaFeatures: { jsx: true }, - }, - errors: [ - { - messageId: 'unexpected', - column: 11, - line: 1, - data: { loc: 'before' }, - }, - { - messageId: 'missing', - column: 11, - line: 1, - data: { loc: 'after' }, - }, - ], - }, - { - code: "foo(1, true/* comment */ , 'foo');", - output: "foo(1, true/* comment */, 'foo');", - errors: [ - { - messageId: 'unexpected', - column: 26, - line: 1, - data: { loc: 'before' }, - }, - ], - }, - { - code: "foo(1, true,/* comment */ 'foo');", - output: "foo(1, true, /* comment */ 'foo');", - errors: [ - { - messageId: 'missing', - column: 12, - line: 1, - data: { loc: 'after' }, - }, - ], - }, - { - code: "foo(404,// comment\n true, 'hello');", - output: "foo(404, // comment\n true, 'hello');", - errors: [ - { - messageId: 'missing', - column: 8, - line: 1, - data: { loc: 'after' }, - }, - ], - }, - { - code: 'function Foo() {}', - output: 'function Foo() {}', - errors: [ - { - messageId: 'missing', - column: 15, - line: 1, - data: { loc: 'after' }, - }, - ], - }, - { - code: 'function Foo() {}', - output: 'function Foo() {}', - errors: [ - { - messageId: 'unexpected', - column: 16, - line: 1, - data: { loc: 'before' }, - }, - ], - }, - { - code: 'function Foo() {}', - output: 'function Foo() {}', - errors: [ - { - messageId: 'unexpected', - column: 16, - line: 1, - data: { loc: 'before' }, - }, - { - messageId: 'missing', - column: 16, - line: 1, - data: { loc: 'after' }, - }, - ], - }, - { - code: 'function Foo() {}', - output: 'function Foo() {}', - options: [{ before: false, after: false }], - errors: [ - { - messageId: 'unexpected', - column: 15, - line: 1, - data: { loc: 'after' }, - }, - ], - }, - { - code: 'function Foo() {}', - output: 'function Foo() {}', - options: [{ before: true, after: false }], - errors: [ - { - messageId: 'missing', - column: 15, - line: 1, - data: { loc: 'before' }, - }, - ], - }, - { - code: 'let foo ,', - output: 'let foo,', - errors: [ - { - messageId: 'unexpected', - column: 9, - line: 1, - data: { loc: 'before' }, - }, - ], - }, - ], -}); diff --git a/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts b/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts index f21dac8f6399..f7f50ea44524 100644 --- a/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts @@ -2,9 +2,7 @@ import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; import rule from '../../src/rules/consistent-generic-constructors'; -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', -}); +const ruleTester = new RuleTester(); ruleTester.run('consistent-generic-constructors', rule, { valid: [ diff --git a/packages/eslint-plugin/tests/rules/consistent-indexed-object-style.test.ts b/packages/eslint-plugin/tests/rules/consistent-indexed-object-style.test.ts index 2b1aa4661a99..30f98f976e5c 100644 --- a/packages/eslint-plugin/tests/rules/consistent-indexed-object-style.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-indexed-object-style.test.ts @@ -2,9 +2,7 @@ import { RuleTester } from '@typescript-eslint/rule-tester'; import rule from '../../src/rules/consistent-indexed-object-style'; -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', -}); +const ruleTester = new RuleTester(); ruleTester.run('consistent-indexed-object-style', rule, { valid: [ diff --git a/packages/eslint-plugin/tests/rules/consistent-return.test.ts b/packages/eslint-plugin/tests/rules/consistent-return.test.ts index cb74ca29b8c3..b26b70654dee 100644 --- a/packages/eslint-plugin/tests/rules/consistent-return.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-return.test.ts @@ -6,11 +6,11 @@ import { getFixturesRootDir } from '../RuleTester'; const rootDir = getFixturesRootDir(); const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaVersion: 2021, - tsconfigRootDir: rootDir, - project: './tsconfig.json', + languageOptions: { + parserOptions: { + tsconfigRootDir: rootDir, + project: './tsconfig.json', + }, }, }); diff --git a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts index fde1ed0d0dd5..29eb3d5297a0 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts @@ -10,7 +10,7 @@ import rule from '../../src/rules/consistent-type-assertions'; import { dedupeTestCases } from '../dedupeTestCases'; import { batchedSingleLineTests } from '../RuleTester'; -const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser' }); +const ruleTester = new RuleTester(); const ANGLE_BRACKET_TESTS_EXCEPT_CONST_CASE = ` const x = new Generic(); @@ -136,7 +136,7 @@ ruleTester.run('consistent-type-assertions', rule, { { code: 'const x = [1] as const;', options: [{ assertionStyle: 'never' }] }, { code: 'const bar = ;', - parserOptions: { ecmaFeatures: { jsx: true } }, + languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } }, options: [ { assertionStyle: 'as', @@ -533,7 +533,7 @@ ruleTester.run('consistent-type-assertions', rule, { { code: 'const foo = ;', output: null, - parserOptions: { ecmaFeatures: { jsx: true } }, + languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } }, options: [{ assertionStyle: 'never' }], errors: [{ messageId: 'never', line: 1 }], }, diff --git a/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts index b0a2092994d3..4fa17b04e988 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts @@ -2,9 +2,7 @@ import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; import rule from '../../src/rules/consistent-type-definitions'; -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', -}); +const ruleTester = new RuleTester(); ruleTester.run('consistent-type-definitions', rule, { valid: [ diff --git a/packages/eslint-plugin/tests/rules/consistent-type-exports.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-exports.test.ts index 4d4bb3b5ae1d..209a285c7ee1 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-exports.test.ts @@ -6,12 +6,11 @@ import { getFixturesRootDir } from '../RuleTester'; const rootDir = getFixturesRootDir(); const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaVersion: 2020, - sourceType: 'module', - tsconfigRootDir: rootDir, - project: './tsconfig.json', + languageOptions: { + parserOptions: { + tsconfigRootDir: rootDir, + project: './tsconfig.json', + }, }, }); 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 a751730cfd34..48d918a6436e 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts @@ -19,8 +19,7 @@ const PARSER_OPTION_COMBOS = [ for (const parserOptions of PARSER_OPTION_COMBOS) { describe(`experimentalDecorators: ${parserOptions.experimentalDecorators} + emitDecoratorMetadata: ${parserOptions.emitDecoratorMetadata}`, () => { const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', - parserOptions, + languageOptions: { parserOptions }, }); ruleTester.run('consistent-type-imports', rule, { @@ -290,9 +289,11 @@ export const ComponentFoo: React.FC = () => { return
Foo Foo
; }; `, - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, }, @@ -304,11 +305,13 @@ export const ComponentFoo: h.FC = () => { return
Foo Foo
; }; `, - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + jsxPragma: 'h', }, - jsxPragma: 'h', }, }, { @@ -319,11 +322,13 @@ export const ComponentFoo: Fragment = () => { return <>Foo Foo; }; `, - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + jsxFragmentName: 'Fragment', }, - jsxFragmentName: 'Fragment', }, }, ` @@ -1946,10 +1951,11 @@ function test(foo: Foo) {} // the special ignored config case describe('experimentalDecorators: true + emitDecoratorMetadata: true', () => { const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', - parserOptions: { - experimentalDecorators: true, - emitDecoratorMetadata: true, + languageOptions: { + parserOptions: { + experimentalDecorators: true, + emitDecoratorMetadata: true, + }, }, }); diff --git a/packages/eslint-plugin/tests/rules/default-param-last.test.ts b/packages/eslint-plugin/tests/rules/default-param-last.test.ts index 33be682be7c5..bcad5e8a6d62 100644 --- a/packages/eslint-plugin/tests/rules/default-param-last.test.ts +++ b/packages/eslint-plugin/tests/rules/default-param-last.test.ts @@ -2,9 +2,7 @@ import { RuleTester } from '@typescript-eslint/rule-tester'; import rule from '../../src/rules/default-param-last'; -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', -}); +const ruleTester = new RuleTester(); ruleTester.run('default-param-last', rule, { valid: [ diff --git a/packages/eslint-plugin/tests/rules/dot-notation.test.ts b/packages/eslint-plugin/tests/rules/dot-notation.test.ts index b799ef3243c3..580ddef5715c 100644 --- a/packages/eslint-plugin/tests/rules/dot-notation.test.ts +++ b/packages/eslint-plugin/tests/rules/dot-notation.test.ts @@ -6,11 +6,11 @@ import { getFixturesRootDir } from '../RuleTester'; const rootPath = getFixturesRootDir(); const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', - parserOptions: { - sourceType: 'module', - tsconfigRootDir: rootPath, - project: './tsconfig.json', + languageOptions: { + parserOptions: { + tsconfigRootDir: rootPath, + project: './tsconfig.json', + }, }, }); @@ -51,19 +51,28 @@ ruleTester.run('dot-notation', rule, { code: "a['lots_of_snake_case'];", options: [{ allowPattern: '^[a-z]+(_[a-z]+)+$' }], }, - { code: 'a[`time${range}`];', parserOptions: { ecmaVersion: 6 } }, + { + code: 'a[`time${range}`];', + languageOptions: { parserOptions: { ecmaVersion: 6 } }, + }, { code: 'a[`while`];', options: [{ allowKeywords: false }], - parserOptions: { ecmaVersion: 6 }, + languageOptions: { parserOptions: { ecmaVersion: 6 } }, + }, + { + code: 'a[`time range`];', + languageOptions: { parserOptions: { ecmaVersion: 6 } }, }, - { code: 'a[`time range`];', parserOptions: { ecmaVersion: 6 } }, 'a.true;', 'a.null;', 'a[undefined];', 'a[void 0];', 'a[b()];', - { code: 'a[/(?0)/];', parserOptions: { ecmaVersion: 2018 } }, + { + code: 'a[/(?0)/];', + languageOptions: { parserOptions: { ecmaVersion: 2018 } }, + }, { code: ` @@ -117,7 +126,7 @@ dingus?.nested.property; dingus?.nested['hello']; `, options: [{ allowIndexSignaturePropertyAccess: true }], - parserOptions: { ecmaVersion: 2020 }, + languageOptions: { parserOptions: { ecmaVersion: 2020 } }, }, { code: ` @@ -198,7 +207,7 @@ x.pub_prop = 123; { code: "a['time'];", output: 'a.time;', - parserOptions: { ecmaVersion: 6 }, + languageOptions: { parserOptions: { ecmaVersion: 6 } }, errors: [{ messageId: 'useDot', data: { key: '"time"' } }], }, { 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 0e8c5182b7d3..ae80f009727b 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 @@ -2,9 +2,7 @@ import { RuleTester } from '@typescript-eslint/rule-tester'; import rule from '../../src/rules/explicit-function-return-type'; -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', -}); +const ruleTester = new RuleTester(); ruleTester.run('explicit-function-return-type', rule, { valid: [ @@ -185,45 +183,55 @@ class App { { code: 'const foo =