diff --git a/.cspell.json b/.cspell.json index 73006b14fd39..1a754cf9b96d 100644 --- a/.cspell.json +++ b/.cspell.json @@ -49,7 +49,8 @@ "words": [ "Airbnb", "Airbnb's", - "allowdefaultprojectforfiles", + "ambiently", + "allowdefaultproject", "ambiently", "Armano", "astexplorer", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aaa3b40510f5..3d09db3f80db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -134,6 +134,25 @@ jobs: run: yarn stylelint working-directory: packages/website + eslint_v9_tests: + name: Run tests on ESLint v9 + needs: [build] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install + uses: ./.github/actions/prepare-install + with: + node-version: ${{ env.PRIMARY_NODE_VERSION }} + - name: Build + uses: ./.github/actions/prepare-build + - run: yarn add eslint@9 + - name: Run tests + run: yarn test + env: + CI: true + integration_tests: name: Run integration tests on primary Node.js version needs: [build] @@ -148,7 +167,7 @@ jobs: - name: Build uses: ./.github/actions/prepare-build - - name: Run integrations tests + - name: Run integration tests run: yarn test-integration env: CI: true @@ -221,8 +240,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: @@ -246,7 +265,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/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 d0494bdd0c33..713a79840cb1 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/maintenance/Issues.mdx b/docs/maintenance/Issues.mdx index 525147f88c54..8b39f5e5506d 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 d9f46936de12..c337e5655967 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` @@ -209,9 +210,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,54 +270,15 @@ 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. - -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 +For an option that allows linting files outside of your TSConfig file(s), see [`projectService`](#projectService). -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`). @@ -323,7 +289,7 @@ export default [ { languageOptions: { parserOptions: { - EXPERIMENTAL_useProjectService: true, + projectService: true, }, }, }, @@ -337,7 +303,7 @@ export default [ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { - EXPERIMENTAL_useProjectService: true, + projectService: true, }, }; ``` @@ -345,17 +311,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](docs: blog post on parserOptions.projectService ) -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. +#### `ProjectServiceOptions` + +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..41b4c2cc3acd 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 { diff --git a/docs/packages/TypeScript_ESTree.mdx b/docs/packages/TypeScript_ESTree.mdx index 516148c4fde7..e8a27eb0f012 100644 --- a/docs/packages/TypeScript_ESTree.mdx +++ b/docs/packages/TypeScript_ESTree.mdx @@ -158,30 +158,47 @@ 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. * - * @see https://github.com/typescript-eslint/typescript-eslint/issues/6575 + * In other words, typescript-eslint is faster by default, and this option + * disables an automatic performance optimization. + * + * 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. @@ -213,6 +230,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 +244,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 +260,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 +269,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 +277,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! diff --git a/docs/troubleshooting/FAQ.mdx b/docs/troubleshooting/FAQ.mdx index 066b618c5ed7..164ee17c32bc 100644 --- a/docs/troubleshooting/FAQ.mdx +++ b/docs/troubleshooting/FAQ.mdx @@ -32,20 +32,20 @@ If you don't find an existing extension rule, or the extension rule doesn't work > We release a new version our tooling every week. > _Please_ ensure that you [check our the latest list of "extension" rules](/rules/#extension-rules) **_before_** filing an issue. - + ## 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. +These errors are caused by using the [`projectService`](../packages/Parser.mdx#projectService) `allowDefaultProject` with an excessively wide glob. +`allowDefaultProject` 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. +To resolve this error, narrow the glob(s) used for `allowDefaultProject` to include fewer files. For example: ```diff title="eslint.config.js" parserOptions: { - EXPERIMENTAL_useProjectService: { - allowDefaultProjectForFiles: [ + projectService: { + allowDefaultProject: [ - "**/*.js", + "./*.js" ] @@ -533,6 +533,6 @@ If you think you're having issues with performance, see our [Performance Trouble ## Are TypeScript project references supported? -Yes, but only with [`EXPERIMENTAL_useProjectService`](../packages/Parser.mdx#experimental_useprojectservice). +Yes, but only with [`projectService`](../packages/Parser.mdx#projectService). 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 3e71ec470c9d..b4d50a9435af 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -76,12 +76,6 @@ 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', @@ -173,6 +167,12 @@ export default tseslint.config( ignorePrimitives: true, }, ], + '@typescript-eslint/no-require-imports': [ + 'error', + { + allow: ['/package\\.json$'], + }, + ], // // Internal repo rules @@ -206,6 +206,7 @@ export default tseslint.config( { commentPattern: '.*intentional fallthrough.*' }, ], 'one-var': ['error', 'never'], + 'prefer-object-has-own': 'error', // // eslint-plugin-eslint-comment @@ -519,6 +520,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 4bb9e04aa8d6..0fa671b3ef5f 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "typecheck": "npx nx run-many --target=typecheck --parallel" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "devDependencies": { "@actions/core": "^1.10.1", @@ -115,7 +115,7 @@ "rimraf": "^5.0.5", "tmp": "^0.2.1", "tsx": "*", - "typescript": ">=4.7.4 <5.5.0", + "typescript": ">=4.8.4 <5.5.0", "typescript-eslint": "workspace:^", "yargs": "17.7.2" }, diff --git a/packages/ast-spec/package.json b/packages/ast-spec/package.json index 549ca869eb23..21716692d52c 100644 --- a/packages/ast-spec/package.json +++ b/packages/ast-spec/package.json @@ -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 7a637489eb0d..c1f8d7b4cc36 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 595d1393ac48..d96c37deb9fc 100644 --- a/packages/ast-spec/src/base/ClassBase.ts +++ b/packages/ast-spec/src/base/ClassBase.ts @@ -53,10 +53,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 1625063426c8..9d51d654c47b 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. * ``` @@ -25,6 +30,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/element/TSEnumMember/spec.ts b/packages/ast-spec/src/element/TSEnumMember/spec.ts index 9dd1ddeee696..3e506809dab9 100644 --- a/packages/ast-spec/src/element/TSEnumMember/spec.ts +++ b/packages/ast-spec/src/element/TSEnumMember/spec.ts @@ -1,5 +1,6 @@ import type { AST_NODE_TYPES } from '../../ast-node-types'; import type { BaseNode } from '../../base/BaseNode'; +import type { TSEnumBody } from '../../special/spec'; import type { Expression } from '../../unions/Expression'; import type { PropertyNameComputed, @@ -13,6 +14,7 @@ interface TSEnumMemberBase extends BaseNode { | PropertyNameNonComputed; initializer: Expression | undefined; computed: boolean; + parent: TSEnumBody; } /** 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..f6eef26a1afe --- /dev/null +++ b/packages/ast-spec/src/special/TSEnumBody/spec.ts @@ -0,0 +1,10 @@ +import type { AST_NODE_TYPES } from '../../ast-node-types'; +import type { BaseNode } from '../../base/BaseNode'; +import type { TSEnumDeclaration } from '../../declaration/TSEnumDeclaration/spec'; +import type { TSEnumMember } from '../../element/TSEnumMember/spec'; + +export interface TSEnumBody extends BaseNode { + type: AST_NODE_TYPES.TSEnumBody; + members: TSEnumMember[]; + parent: TSEnumDeclaration; +} 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/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..594ac9f51c2f 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: [], 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/plugin-test-formatting.test.ts b/packages/eslint-plugin-internal/tests/rules/plugin-test-formatting.test.ts index 28e567d052b8..280d3fd67f8e 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 @@ -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', 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/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 32c1cefc16e0..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](/troubleshooting/formatting). - - Soon, formatting rules will be moved to the [ESLint stylistic plugin](https://eslint.style). - `💀 deprecated rule` means that the rule should no longer be used and will be removed from the plugin in a future version. ## Extension Rules 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 da30f4959116..000000000000 --- a/packages/eslint-plugin/docs/rules/ban-types.mdx +++ /dev/null @@ -1,138 +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. - -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 new file mode 100644 index 000000000000..8bf941a66615 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-empty-object-type.mdx @@ -0,0 +1,145 @@ +--- +description: 'Disallow accidentally using the "empty object" type.' +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +> 🛑 This file is source code, not the primary documentation location! 🛑 +> +> See **https://typescript-eslint.io/rules/no-empty-object-type** for documentation. + +The `{}`, or "empty object" type in TypeScript is a common source of confusion for developers unfamiliar with TypeScript's structural typing. +`{}` represents any _non-nullish value_, including literals like `0` and `""`: + +```ts +let anyNonNullishValue: {} = 'Intentionally allowed by TypeScript.'; +``` + +Often, developers writing `{}` actually mean either: + +- `object`: representing any _object_ value +- `unknown`: representing any value at all, including `null` and `undefined` + +In other words, the "empty object" type `{}` really means _"any value that is defined"_. +That includes arrays, class instances, functions, and primitives such as `string` and `symbol`. + +To avoid confusion around the `{}` type allowing any _non-nullish value_, this rule bans usage of the `{}` type. +That includes interfaces and object type aliases with no fields. + +:::tip +If you do have a use case for an API allowing `{}`, you can always configure the [rule's options](#options), use an [ESLint disable comment](https://eslint.org/docs/latest/use/configure/rules#using-configuration-comments-1), or [disable the rule in your ESLint config](https://eslint.org/docs/latest/use/configure/rules#using-configuration-files-1). +::: + +Note that this rule does not report on: + +- `{}` as a type constituent in an intersection type (e.g. types like TypeScript's built-in `type NonNullable = T & {}`), as this can be useful in type system operations. +- Interfaces that extend from multiple other interfaces. + +## Examples + + + + +```ts +let anyObject: {}; +let anyValue: {}; + +interface AnyObjectA {} +interface AnyValueA {} + +type AnyObjectB = {}; +type AnyValueB = {}; +``` + + + + +```ts +let anyObject: object; +let anyValue: unknown; + +type AnyObjectA = object; +type AnyValueA = unknown; + +type AnyObjectB = object; +type AnyValueB = unknown; + +let objectWith: { property: boolean }; + +interface InterfaceWith { + property: boolean; +} + +type TypeWith = { property: boolean }; +``` + + + + +## Options + +By default, this rule flags both interfaces and object types. + +### `allowInterfaces` + +Whether to allow empty interfaces, as one of: + +- `'always'`: to always allow interfaces with no fields +- `'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 **correct** code for this rule with `{ allowInterfaces: 'with-single-extends' }`: + +```ts option='{ "allowInterfaces": "with-single-extends" }' showPlaygroundButton +interface Base { + value: boolean; +} + +interface Derived extends Base {} +``` + +### `allowObjectTypes` + +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 + +### `allowWithName` + +A stringified regular expression to allow interfaces and object type aliases with the configured name. +This can be useful if your existing code style includes a pattern of declaring empty types with `{}` instead of `object`. + +Examples of code for this rule with `{ allowWithName: 'Props$' }`: + + + + +```ts option='{ "allowWithName": "Props$" }' showPlaygroundButton +interface InterfaceValue {} + +type TypeValue = {}; +``` + + + + +```ts option='{ "allowWithName": "Props$" }' showPlaygroundButton +interface InterfaceProps {} + +type TypeProps = {}; +``` + + + + +## When Not To Use It + +If your code commonly needs to represent the _"any non-nullish value"_ type, this rule may not be for you. +Projects that extensively use type operations such as conditional types and mapped types oftentimes benefit from disabling this rule. + +## Further Reading + +- [Enhancement: [ban-types] Split the {} ban into a separate, better-phrased rule](https://github.com/typescript-eslint/typescript-eslint/issues/8700) +- [The Empty Object Type in TypeScript](https://www.totaltypescript.com/the-empty-object-type-in-typescript) 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 ec23c28eca8d..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](/troubleshooting/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 e3f047e6e4f0..c3d051151d6f 100644 --- a/packages/eslint-plugin/docs/rules/no-floating-promises.mdx +++ b/packages/eslint-plugin/docs/rules/no-floating-promises.mdx @@ -176,6 +176,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-unsafe-function-type.mdx b/packages/eslint-plugin/docs/rules/no-unsafe-function-type.mdx new file mode 100644 index 000000000000..ea7b60794e4e --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-unsafe-function-type.mdx @@ -0,0 +1,63 @@ +--- +description: 'Disallow using the unsafe built-in Function type.' +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +> 🛑 This file is source code, not the primary documentation location! 🛑 +> +> See **https://typescript-eslint.io/rules/no-unsafe-function-type** for documentation. + +TypeScript's built-in `Function` type allows being called with any number of arguments and returns type `any`. +`Function` also allows classes or plain objects that happen to possess all properties of the `Function` class. +It's generally better to specify function parameters and return types with the function type syntax. + +"Catch-all" function types include: + +- `() => void`: a function that has no parameters and whose return is ignored +- `(...args: never) => unknown`: a "top type" for functions that can be assigned any function type, but can't be called + +Examples of code for this rule: + + + + +```ts +let noParametersOrReturn: Function; +noParametersOrReturn = () => {}; + +let stringToNumber: Function; +stringToNumber = (text: string) => text.length; + +let identity: Function; +identity = value => value; +``` + + + + +```ts +let noParametersOrReturn: () => void; +noParametersOrReturn = () => {}; + +let stringToNumber: (text: string) => number; +stringToNumber = text => text.length; + +let identity: (value: T) => T; +identity = value => value; +``` + + + + +## When Not To Use It + +If your project is still onboarding to TypeScript, it might be difficult to fully replace all unsafe `Function` types with more precise function types. +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. + +## Related To + +- [`no-empty-object-type`](./no-empty-object-type.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 new file mode 100644 index 000000000000..858802470cbb --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx @@ -0,0 +1,67 @@ +--- +description: 'Disallow using confusing built-in primitive class wrappers.' +--- + +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-wrapper-object-types** for documentation. + +The JavaScript language has a set of language types, but some of them correspond to two TypeScript types, which look similar: `boolean`/`Boolean`, `number`/`Number`, `string`/`String`, `bigint`/`BigInt`, `symbol`/`Symbol`, `object`/`Object`. +The difference is that the lowercase variants are compiler intrinsics and specify the actual _runtime types_ (that is, the return value when you use the `typeof` operator), while the uppercase variants are _structural types_ defined in the library that can be satisfied by any user-defined object with the right properties, not just the real primitives. +JavaScript also has a "wrapper" class object for each of those primitives, such as [`Boolean`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean) and [`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number). +These wrapper objects are assignable to the uppercase types, but not to the lowercase types. + +Using the primitives like `0` instead of object wrappers like `new Number(0)` is generally considered a JavaScript best practice. +JavaScript programs typically work with the real number primitives, rather than objects that "look like" numbers. +Primitives are simpler to conceptualize and work with `==` and `===` equality checks -- which their object equivalents do notDeepEqual. +As a result, using the lowercase type names like `number` instead of the uppercase names like `Number` helps make your code behave more reliably. + +Examples of code for this rule: + + + + +```ts +let myBigInt: BigInt; +let myBoolean: Boolean; +let myNumber: Number; +let myString: String; +let mySymbol: Symbol; + +let myObject: Object = 'allowed by TypeScript'; +``` + + + + +```ts +let myBigint: bigint; +let myBoolean: boolean; +let myNumber: number; +let myString: string; +let mySymbol: symbol; + +let myObject: object = "Type 'string' is not assignable to type 'object'."; +``` + + + + +## 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 use this rule. +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. + +## Further Reading + +- [MDN documentation on primitives](https://developer.mozilla.org/en-US/docs/Glossary/Primitive) +- [MDN documentation on `string` primitives and `String` objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#string_primitives_and_string_objects) + +## Related To + +- [`no-empty-object-type`](./no-empty-object-type.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 922f653ba17c..d07588dac0cc 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -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", @@ -97,8 +97,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..8afcb38dca8b 100644 --- a/packages/eslint-plugin/rules.d.ts +++ b/packages/eslint-plugin/rules.d.ts @@ -35,11 +35,15 @@ This is likely not portable. A type annotation is necessary. ts(2742) ``` */ -import type { RuleModule } from '@typescript-eslint/utils/ts-eslint'; +import type { RuleModuleWithMetaDocs } from '@typescript-eslint/utils/ts-eslint'; + +import type { ESLintPluginDocs, ESLintPluginRuleModule } from './src/util'; + +export { ESLintPluginDocs, ESLintPluginRuleModule }; export type TypeScriptESLintRules = Record< string, - RuleModule + RuleModuleWithMetaDocs >; declare const rules: TypeScriptESLintRules; // eslint-disable-next-line import/no-default-export diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index ee778e7e48cd..ab24d6943de0 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,7 @@ 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', '@typescript-eslint/no-extraneous-class': 'error', @@ -69,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', @@ -87,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', @@ -102,6 +100,7 @@ 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', @@ -114,7 +113,7 @@ 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', '@typescript-eslint/only-throw-error': 'error', diff --git a/packages/eslint-plugin/src/configs/disable-type-checked.ts b/packages/eslint-plugin/src/configs/disable-type-checked.ts index 8e47f9688d73..9a45d83452cf 100644 --- a/packages/eslint-plugin/src/configs/disable-type-checked.ts +++ b/packages/eslint-plugin/src/configs/disable-type-checked.ts @@ -26,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', @@ -40,7 +39,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..53f13d96748f 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked-only.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked-only.ts @@ -34,9 +34,9 @@ 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', - '@typescript-eslint/prefer-includes': 'error', 'prefer-promise-reject-errors': 'off', '@typescript-eslint/prefer-promise-reject-errors': 'error', '@typescript-eslint/prefer-reduce-type-parameter': '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..11d65130de62 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', @@ -55,18 +54,22 @@ 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', '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', 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/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 80df92aaa701..000000000000 --- a/packages/eslint-plugin/src/rules/func-call-spacing.ts +++ /dev/null @@ -1,185 +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 248ecd0d7e8a..000000000000 --- a/packages/eslint-plugin/src/rules/indent.ts +++ /dev/null @@ -1,492 +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 Object.assign({}, rules, { - // overwrite the base rule here so we can use our KNOWN_NODES list instead - '*:exit'(node: TSESTree.Node) { - // 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) { - // https://github.com/typescript-eslint/typescript-eslint/issues/441 - if (node.declarations.length === 0) { - return; - } - - return rules.VariableDeclaration(node); - }, - - TSAsExpression(node: TSESTree.TSAsExpression) { - // 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) { - // 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, - ) { - // 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) { - // 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) { - // 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) { - // 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, - ) { - // 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) { - // 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) { - // 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) { - 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) { - // 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) { - 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 fe0d36026bd1..d11366a54341 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'; @@ -47,10 +35,9 @@ import noDuplicateTypeConstituents from './no-duplicate-type-constituents'; import noDynamicDelete from './no-dynamic-delete'; import noEmptyFunction from './no-empty-function'; 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'; @@ -74,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'; @@ -90,6 +77,7 @@ import noUnsafeAssignment from './no-unsafe-assignment'; import noUnsafeCall from './no-unsafe-call'; import noUnsafeDeclarationMerging from './no-unsafe-declaration-merging'; import noUnsafeEnumComparison from './no-unsafe-enum-comparison'; +import noUnsafeFunctionType from './no-unsafe-function-type'; import noUnsafeMemberAccess from './no-unsafe-member-access'; import noUnsafeReturn from './no-unsafe-return'; import noUnsafeUnaryMinus from './no-unsafe-unary-minus'; @@ -98,12 +86,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'; @@ -125,21 +111,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'; @@ -151,13 +131,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, @@ -170,15 +145,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, @@ -193,10 +161,9 @@ export default { 'no-dynamic-delete': noDynamicDelete, 'no-empty-function': noEmptyFunction, 'no-empty-interface': noEmptyInterface, + '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, @@ -220,9 +187,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, @@ -236,6 +203,7 @@ export default { 'no-unsafe-call': noUnsafeCall, 'no-unsafe-declaration-merging': noUnsafeDeclarationMerging, 'no-unsafe-enum-comparison': noUnsafeEnumComparison, + 'no-unsafe-function-type': noUnsafeFunctionType, 'no-unsafe-member-access': noUnsafeMemberAccess, 'no-unsafe-return': noUnsafeReturn, 'no-unsafe-unary-minus': noUnsafeUnaryMinus, @@ -244,12 +212,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, @@ -271,21 +237,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 428f31f3667e..000000000000 --- a/packages/eslint-plugin/src/rules/member-delimiter-style.ts +++ /dev/null @@ -1,352 +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/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 58bd5ad6db24..b6b1a832e534 100644 --- a/packages/eslint-plugin/src/rules/no-duplicate-type-constituents.ts +++ b/packages/eslint-plugin/src/rules/no-duplicate-type-constituents.ts @@ -44,8 +44,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 new file mode 100644 index 000000000000..ad20103061c6 --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-empty-object-type.ts @@ -0,0 +1,187 @@ +import type { TSESLint } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; + +import { createRule } from '../util'; + +export type AllowInterfaces = 'always' | 'never' | 'with-single-extends'; + +export type AllowObjectTypes = 'always' | 'never'; + +export type Options = [ + { + allowInterfaces?: AllowInterfaces; + allowObjectTypes?: AllowObjectTypes; + allowWithName?: string; + }, +]; + +export type MessageIds = + | 'noEmptyInterface' + | 'noEmptyObject' + | 'noEmptyInterfaceWithSuper' + | 'replaceEmptyInterface' + | 'replaceEmptyInterfaceWithSuper' + | 'replaceEmptyObjectType'; + +const noEmptyMessage = (emptyType: string): string => + [ + `${emptyType} 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 '{{ option }}' 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.', + ].join('\n'); + +export default createRule({ + name: 'no-empty-object-type', + meta: { + type: 'suggestion', + docs: { + description: 'Disallow accidentally using the "empty object" type', + recommended: 'recommended', + }, + hasSuggestions: true, + messages: { + noEmptyInterface: noEmptyMessage('An empty interface declaration'), + noEmptyObject: noEmptyMessage('The `{}` ("empty object") type'), + noEmptyInterfaceWithSuper: + 'An interface declaring no members is equivalent to its supertype.', + replaceEmptyInterface: 'Replace empty interface with `{{replacement}}`.', + replaceEmptyInterfaceWithSuper: + 'Replace empty interface with a type alias.', + replaceEmptyObjectType: 'Replace `{}` with `{{replacement}}`.', + }, + schema: [ + { + type: 'object', + additionalProperties: false, + properties: { + allowInterfaces: { + enum: ['always', 'never', 'with-single-extends'], + type: 'string', + }, + allowObjectTypes: { + enum: ['always', 'in-type-alias-with-name', 'never'], + type: 'string', + }, + allowWithName: { + type: 'string', + }, + }, + }, + ], + }, + defaultOptions: [ + { + allowInterfaces: 'never', + allowObjectTypes: 'never', + }, + ], + create(context, [{ allowInterfaces, allowWithName, allowObjectTypes }]) { + const allowWithNameTester = allowWithName + ? new RegExp(allowWithName, 'u') + : undefined; + + return { + ...(allowInterfaces !== 'always' && { + TSInterfaceDeclaration(node): void { + if (allowWithNameTester?.test(node.id.name)) { + return; + } + + const extend = node.extends; + if ( + node.body.body.length !== 0 || + (extend.length === 1 && + allowInterfaces === 'with-single-extends') || + extend.length > 1 + ) { + return; + } + + const scope = context.sourceCode.getScope(node); + + const mergedWithClassDeclaration = scope.set + .get(node.id.name) + ?.defs.some( + def => def.node.type === AST_NODE_TYPES.ClassDeclaration, + ); + + if (extend.length === 0) { + context.report({ + data: { option: 'allowInterfaces' }, + node: node.id, + messageId: 'noEmptyInterface', + ...(!mergedWithClassDeclaration && { + suggest: ['object', 'unknown'].map(replacement => ({ + data: { replacement }, + fix(fixer): TSESLint.RuleFix { + const id = context.sourceCode.getText(node.id); + const typeParam = node.typeParameters + ? context.sourceCode.getText(node.typeParameters) + : ''; + + return fixer.replaceText( + node, + `type ${id}${typeParam} = ${replacement}`, + ); + }, + messageId: 'replaceEmptyInterface', + })), + }), + }); + return; + } + + context.report({ + node: node.id, + messageId: 'noEmptyInterfaceWithSuper', + ...(!mergedWithClassDeclaration && { + suggest: [ + { + fix(fixer): TSESLint.RuleFix { + const extended = context.sourceCode.getText(extend[0]); + const id = context.sourceCode.getText(node.id); + const typeParam = node.typeParameters + ? context.sourceCode.getText(node.typeParameters) + : ''; + + return fixer.replaceText( + node, + `type ${id}${typeParam} = ${extended}`, + ); + }, + messageId: 'replaceEmptyInterfaceWithSuper', + }, + ], + }), + }); + }, + }), + ...(allowObjectTypes !== 'always' && { + TSTypeLiteral(node): void { + if ( + node.members.length || + node.parent.type === AST_NODE_TYPES.TSIntersectionType || + (allowWithNameTester && + node.parent.type === AST_NODE_TYPES.TSTypeAliasDeclaration && + allowWithNameTester.test(node.parent.id.name)) + ) { + return; + } + + context.report({ + data: { option: 'allowObjectTypes' }, + messageId: 'noEmptyObject', + node, + suggest: ['object', 'unknown'].map(replacement => ({ + data: { replacement }, + messageId: 'replaceEmptyObjectType', + fix: (fixer): TSESLint.RuleFix => + fixer.replaceText(node, replacement), + })), + }); + }, + }), + }; + }, +}); 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 0ed0f4c6b4de..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 Object.assign({}, 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-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index 1ae5e602ae0e..ad25d241be55 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -19,6 +19,7 @@ type Options = [ ignoreVoid?: boolean; ignoreIIFE?: boolean; allowForKnownSafePromises?: TypeOrValueSpecifier[]; + allowForKnownSafeCalls?: TypeOrValueSpecifier[]; }, ]; @@ -85,6 +86,7 @@ export default createRule({ type: 'boolean', }, allowForKnownSafePromises: readonlynessOptionsSchema.properties.allow, + allowForKnownSafeCalls: readonlynessOptionsSchema.properties.allow, }, additionalProperties: false, }, @@ -96,6 +98,7 @@ export default createRule({ ignoreVoid: true, ignoreIIFE: false, allowForKnownSafePromises: readonlynessOptionsDefaults.allow, + allowForKnownSafeCalls: readonlynessOptionsDefaults.allow, }, ], @@ -103,8 +106,10 @@ export default createRule({ const services = getParserServices(context); const checker = services.program.getTypeChecker(); // 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 { @@ -118,6 +123,10 @@ export default createRule({ expression = expression.expression; } + if (isKnownSafePromiseReturn(expression)) { + return; + } + const { isUnhandled, nonFunctionHandler, promiseArray } = isUnhandledPromise(checker, expression); @@ -197,6 +206,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 dee23d62d275..b2bfac55203a 100644 --- a/packages/eslint-plugin/src/rules/no-implied-eval.ts +++ b/packages/eslint-plugin/src/rules/no-implied-eval.ts @@ -3,7 +3,11 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; -import { createRule, getParserServices } from '../util'; +import { + createRule, + getParserServices, + isReferenceToGlobalFunction, +} from '../util'; const FUNCTION_CONSTRUCTOR = 'Function'; const GLOBAL_CANDIDATES = new Set(['global', 'window', 'globalThis']); @@ -119,18 +123,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 { @@ -165,7 +157,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-mixed-enums.ts b/packages/eslint-plugin/src/rules/no-mixed-enums.ts index e8a06d9861e0..d95d4a9058b7 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 @@ -185,12 +185,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; } @@ -199,7 +199,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..abd0a891b05e 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( 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 da2d79716a35..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,23 +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 = Object.assign( - {}, - extendDefaults ? defaultTypes : {}, - customTypes, - ); + create(context, [{ types = {} }]) { const bannedTypes = new Map( Object.entries(types).map(([type, data]) => [removeSpaces(type), data]), ); @@ -272,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); } }, @@ -291,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-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 3fac4070a0cb..9a9b836bcc92 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -622,13 +622,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-unsafe-function-type.ts b/packages/eslint-plugin/src/rules/no-unsafe-function-type.ts new file mode 100644 index 000000000000..624c038f8ff7 --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-unsafe-function-type.ts @@ -0,0 +1,50 @@ +import type { TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; + +import { createRule, isReferenceToGlobalFunction } from '../util'; + +export default createRule({ + name: 'no-unsafe-function-type', + meta: { + type: 'problem', + docs: { + description: 'Disallow using the unsafe built-in Function type', + recommended: 'recommended', + }, + fixable: 'code', + messages: { + bannedFunctionType: [ + 'The `Function` type accepts any function-like value.', + 'Prefer explicitly defining any function parameters and return type.', + ].join('\n'), + }, + schema: [], + }, + defaultOptions: [], + create(context) { + function checkBannedTypes(node: TSESTree.Node): void { + if ( + node.type === AST_NODE_TYPES.Identifier && + node.name === 'Function' && + isReferenceToGlobalFunction('Function', node, context.sourceCode) + ) { + context.report({ + node, + messageId: 'bannedFunctionType', + }); + } + } + + return { + TSClassImplements(node): void { + checkBannedTypes(node.expression); + }, + TSInterfaceHeritage(node): void { + checkBannedTypes(node.expression); + }, + TSTypeReference(node): void { + checkBannedTypes(node.typeName); + }, + }; + }, +}); 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 402e83763440..1e6f09fcbace 100644 --- a/packages/eslint-plugin/src/rules/no-unused-vars.ts +++ b/packages/eslint-plugin/src/rules/no-unused-vars.ts @@ -104,7 +104,7 @@ export default createRule({ vars: 'all', args: 'after-used', ignoreRestSiblings: false, - caughtErrors: 'none', + caughtErrors: 'all', }; if (typeof firstOption === 'string') { @@ -245,9 +245,7 @@ export default createRule({ ) { 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; 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 new file mode 100644 index 000000000000..f51b6c8564b5 --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-wrapper-object-types.ts @@ -0,0 +1,71 @@ +import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; + +import { createRule, isReferenceToGlobalFunction } from '../util'; + +const classNames = new Set([ + 'BigInt', + // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum + 'Boolean', + 'Number', + 'Object', + // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum + 'String', + 'Symbol', +]); + +export default createRule({ + name: 'no-wrapper-object-types', + meta: { + type: 'problem', + docs: { + description: 'Disallow using confusing built-in primitive class wrappers', + recommended: 'recommended', + }, + fixable: 'code', + messages: { + bannedClassType: + 'Prefer using the primitive `{{preferred}}` as a type name, rather than the upper-cased `{{typeName}}`.', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + function checkBannedTypes( + node: TSESTree.EntityName | TSESTree.Expression, + includeFix: boolean, + ): void { + const typeName = node.type === AST_NODE_TYPES.Identifier && node.name; + if ( + !typeName || + !classNames.has(typeName) || + !isReferenceToGlobalFunction(typeName, node, context.sourceCode) + ) { + return; + } + + const preferred = typeName.toLowerCase(); + + context.report({ + data: { typeName, preferred }, + fix: includeFix + ? (fixer): TSESLint.RuleFix => fixer.replaceText(node, preferred) + : undefined, + messageId: 'bannedClassType', + node, + }); + } + + return { + TSClassImplements(node): void { + checkBannedTypes(node.expression, false); + }, + TSInterfaceHeritage(node): void { + checkBannedTypes(node.expression, false); + }, + TSTypeReference(node): void { + checkBannedTypes(node.typeName, true); + }, + }; + }, +}); 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 4461ec21da0a..b3234fe8daa3 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 ffd58daf8be0..0354d84bbeed 100644 --- a/packages/eslint-plugin/src/rules/prefer-includes.ts +++ b/packages/eslint-plugin/src/rules/prefer-includes.ts @@ -19,7 +19,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 8a9bfae1928e..725bd115bbe0 100644 --- a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts +++ b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts @@ -100,7 +100,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..5e8e1774eda4 100644 --- a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts +++ b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts @@ -29,6 +29,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/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/strict-boolean-expressions.ts b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts index 01e15d3d88c5..a158dfff2ba0 100644 --- a/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts +++ b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts @@ -848,9 +848,9 @@ export default createRule({ // If incoming type is boolean, there will be two type objects with // intrinsicName set "true" and "false" each because of ts-api-utils.unionTypeParts() if (booleans.length === 1) { - tsutils.isTrueLiteralType(booleans[0]) - ? variantTypes.add('truthy boolean') - : variantTypes.add('boolean'); + variantTypes.add( + tsutils.isTrueLiteralType(booleans[0]) ? 'truthy boolean' : 'boolean', + ); } else if (booleans.length === 2) { variantTypes.add('boolean'); } 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/rules/unified-signatures.ts b/packages/eslint-plugin/src/rules/unified-signatures.ts index c8fa2f85f7df..9a6705576ed4 100644 --- a/packages/eslint-plugin/src/rules/unified-signatures.ts +++ b/packages/eslint-plugin/src/rules/unified-signatures.ts @@ -499,7 +499,9 @@ export default createRule({ parent: ScopeNode, typeParameters?: TSESTree.TSTypeParameterDeclaration, ): void { - currentScope && scopes.push(currentScope); + if (currentScope) { + scopes.push(currentScope); + } currentScope = { overloads: new Map(), parent, diff --git a/packages/eslint-plugin/src/util/collectUnusedVariables.ts b/packages/eslint-plugin/src/util/collectUnusedVariables.ts index 54a4e91750a4..443167a3e3c0 100644 --- a/packages/eslint-plugin/src/util/collectUnusedVariables.ts +++ b/packages/eslint-plugin/src/util/collectUnusedVariables.ts @@ -317,7 +317,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; diff --git a/packages/eslint-plugin/src/util/createRule.ts b/packages/eslint-plugin/src/util/createRule.ts index 1008ffcc11bd..b646e814c94a 100644 --- a/packages/eslint-plugin/src/util/createRule.ts +++ b/packages/eslint-plugin/src/util/createRule.ts @@ -1,5 +1,37 @@ import { ESLintUtils } from '@typescript-eslint/utils'; +import type { + RuleModuleWithMetaDocs, + RuleRecommendation, + RuleRecommendationAcrossConfigs, +} from '@typescript-eslint/utils/ts-eslint'; -export const createRule = ESLintUtils.RuleCreator( +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 const createRule = ESLintUtils.RuleCreator( name => `https://typescript-eslint.io/rules/${name}`, ); + +export type ESLintPluginRuleModule = RuleModuleWithMetaDocs< + string, + readonly unknown[], + ESLintPluginDocs +>; 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 a8f6bc40afbd..516fceda7699 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/index.ts b/packages/eslint-plugin/src/util/index.ts index 1f8d657cd542..083353a69233 100644 --- a/packages/eslint-plugin/src/util/index.ts +++ b/packages/eslint-plugin/src/util/index.ts @@ -14,6 +14,7 @@ export * from './isNullLiteral'; export * from './isUndefinedIdentifier'; export * from './misc'; export * from './objectIterators'; +export * from './scopeUtils'; export * from './types'; export * from './isAssignee'; diff --git a/packages/eslint-plugin/src/util/scopeUtils.ts b/packages/eslint-plugin/src/util/scopeUtils.ts new file mode 100644 index 000000000000..b350a7412106 --- /dev/null +++ b/packages/eslint-plugin/src/util/scopeUtils.ts @@ -0,0 +1,15 @@ +import type { TSESTree } from '@typescript-eslint/utils'; +import type { SourceCode } from '@typescript-eslint/utils/ts-eslint'; + +export function isReferenceToGlobalFunction( + calleeName: string, + node: TSESTree.Node, + sourceCode: SourceCode, +): boolean { + const ref = sourceCode + .getScope(node) + .references.find(ref => ref.identifier.name === calleeName); + + // ensure it's the "global" version + return !ref?.resolved?.defs.length; +} 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/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 new file mode 100644 index 000000000000..15ee654ff40a --- /dev/null +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-empty-object-type.shot @@ -0,0 +1,100 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validating rule docs no-empty-object-type.mdx code examples ESLint output 1`] = ` +"Incorrect + +let anyObject: {}; + ~~ The \`{}\` ("empty object") type 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 'allowObjectTypes' 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. +let anyValue: {}; + ~~ The \`{}\` ("empty object") type 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 'allowObjectTypes' 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. + +interface AnyObjectA {} + ~~~~~~~~~~ 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. +interface AnyValueA {} + ~~~~~~~~~ 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. + +type AnyObjectB = {}; + ~~ The \`{}\` ("empty object") type 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 'allowObjectTypes' 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. +type AnyValueB = {}; + ~~ The \`{}\` ("empty object") type 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 'allowObjectTypes' 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 2`] = ` +"Correct + +let anyObject: object; +let anyValue: unknown; + +type AnyObjectA = object; +type AnyValueA = unknown; + +type AnyObjectB = object; +type AnyValueB = unknown; + +let objectWith: { property: boolean }; + +interface InterfaceWith { + property: boolean; +} + +type TypeWith = { property: boolean }; +" +`; + +exports[`Validating rule docs no-empty-object-type.mdx code examples ESLint output 3`] = ` +"Options: { "allowInterfaces": "with-single-extends" } + +interface Base { + value: boolean; +} + +interface Derived extends Base {} +" +`; + +exports[`Validating rule docs no-empty-object-type.mdx code examples ESLint output 4`] = ` +"Incorrect +Options: { "allowWithName": "Props$" } + +interface InterfaceValue {} + ~~~~~~~~~~~~~~ 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. + +type TypeValue = {}; + ~~ The \`{}\` ("empty object") type 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 'allowObjectTypes' 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 5`] = ` +"Correct +Options: { "allowWithName": "Props$" } + +interface InterfaceProps {} + +type TypeProps = {}; +" +`; 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 44e303fc3131..e1f38e71fe3e 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 @@ -108,3 +108,25 @@ function returnsSafePromise(): SafePromise { returnsSafePromise(); " `; + +exports[`Validating rule docs no-floating-promises.mdx code examples ESLint output 7`] = ` +"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 8`] = ` +"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-unsafe-function-type.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-function-type.shot new file mode 100644 index 000000000000..c77f15d6feb3 --- /dev/null +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-function-type.shot @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validating rule docs no-unsafe-function-type.mdx code examples ESLint output 1`] = ` +"Incorrect + +let noParametersOrReturn: Function; + ~~~~~~~~ The \`Function\` type accepts any function-like value. + Prefer explicitly defining any function parameters and return type. +noParametersOrReturn = () => {}; + +let stringToNumber: Function; + ~~~~~~~~ The \`Function\` type accepts any function-like value. + Prefer explicitly defining any function parameters and return type. +stringToNumber = (text: string) => text.length; + +let identity: Function; + ~~~~~~~~ The \`Function\` type accepts any function-like value. + Prefer explicitly defining any function parameters and return type. +identity = value => value; +" +`; + +exports[`Validating rule docs no-unsafe-function-type.mdx code examples ESLint output 2`] = ` +"Correct + +let noParametersOrReturn: () => void; +noParametersOrReturn = () => {}; + +let stringToNumber: (text: string) => number; +stringToNumber = text => text.length; + +let identity: (value: T) => T; +identity = value => value; +" +`; 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/no-wrapper-object-types.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-wrapper-object-types.shot new file mode 100644 index 000000000000..4303b79a77a7 --- /dev/null +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-wrapper-object-types.shot @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validating rule docs no-wrapper-object-types.mdx code examples ESLint output 1`] = ` +"Incorrect + +let myBigInt: BigInt; + ~~~~~~ Prefer using the primitive \`bigint\` as a type name, rather than the upper-cased \`BigInt\`. +let myBoolean: Boolean; + ~~~~~~~ Prefer using the primitive \`boolean\` as a type name, rather than the upper-cased \`Boolean\`. +let myNumber: Number; + ~~~~~~ Prefer using the primitive \`number\` as a type name, rather than the upper-cased \`Number\`. +let myString: String; + ~~~~~~ Prefer using the primitive \`string\` as a type name, rather than the upper-cased \`String\`. +let mySymbol: Symbol; + ~~~~~~ Prefer using the primitive \`symbol\` as a type name, rather than the upper-cased \`Symbol\`. + +let myObject: Object = 'allowed by TypeScript'; + ~~~~~~ Prefer using the primitive \`object\` as a type name, rather than the upper-cased \`Object\`. +" +`; + +exports[`Validating rule docs no-wrapper-object-types.mdx code examples ESLint output 2`] = ` +"Correct + +let myBigint: bigint; +let myBoolean: boolean; +let myNumber: number; +let myString: string; +let mySymbol: symbol; + +let myObject: object = "Type 'string' is not assignable to type 'object'."; +" +`; 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/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/array-type.test.ts b/packages/eslint-plugin/tests/rules/array-type.test.ts index 44be83ff63db..09b5e39d2d5b 100644 --- a/packages/eslint-plugin/tests/rules/array-type.test.ts +++ b/packages/eslint-plugin/tests/rules/array-type.test.ts @@ -1924,7 +1924,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/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/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/func-call-spacing.test.ts b/packages/eslint-plugin/tests/rules/func-call-spacing.test.ts deleted file mode 100644 index e67567ada705..000000000000 --- a/packages/eslint-plugin/tests/rules/func-call-spacing.test.ts +++ /dev/null @@ -1,358 +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 type { TSESLint } from '@typescript-eslint/utils'; - -import type { MessageIds, Options } from '../../src/rules/func-call-spacing'; -import rule from '../../src/rules/func-call-spacing'; - -const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser' }); - -ruleTester.run('func-call-spacing', rule, { - valid: [ - ...[ - 'f();', - 'f(a, b);', - 'f.b();', - 'f.b().c();', - 'f()()', - '(function() {}())', - 'var f = new Foo()', - 'var f = new Foo', - 'f( (0) )', - '(function(){ if (foo) { bar(); } }());', - 'f(0, (1))', - "describe/**/('foo', function () {});", - 'new (foo())', - '( f )( 0 )', - '( (f) )( (0) )', - '( f()() )(0)', - 'f()', - 'f(b, b)', - 'f.b(b, b)', - '(function() {}())', - '((function() {})())', - '( f )( 0 )', - '( (f) )( (0) )', - '( f()() )(0)', - - // optional call - 'f?.();', - 'f?.(a, b);', - 'f?.b();', - 'f?.b()?.c();', - 'f.b?.();', - 'f.b?.().c();', - 'f()?.()', - '(function() {}?.())', - 'f?.( (0) )', - '(function(){ if (foo) { bar(); } }?.());', - 'f?.(0, (1))', - "describe/**/?.('foo', function () {});", - "describe?./**/('foo', function () {});", - '( f )?.( 0 )', - '( (f) )?.( (0) )', - '( f?.()() )(0)', - '( f()?.() )(0)', - '( f?.()?.() )(0)', - '( f?.()() )?.(0)', - '( f()?.() )?.(0)', - '( f?.()?.() )?.(0)', - 'f?.()', - 'f?.(b, b)', - 'f.b?.(b, b)', - 'f?.b(b, b)', - 'f?.b?.(b, b)', - '(function() {}?.())', - '((function() {})?.())', - '( f )?.( 0 )', - '( (f) )?.( (0) )', - '( f()() )?.(0)', - ].map>(code => ({ - code, - options: ['never'], - })), - - ...[ - 'f ();', - 'f (a, b);', - 'f.b ();', - 'f.b ().c ();', - 'f () ()', - '(function() {} ())', - 'var f = new Foo ()', - 'var f = new Foo', - 'f ( (0) )', - 'f (0) (1)', - 'f ();\n t ();', - '( f ) ( 0 )', - '( (f) ) ( (0) )', - 'f ()', - 'f (b, b)', - 'f.b (b, b)', - '(function() {} ())', - '((function() {}) ())', - '( f ) ( 0 )', - '( (f) ) ( (0) )', - '( f () ) (0)', - - // optional call - 'f?.b ();', - 'f?.b ()?.c ();', - 'f?.b (b, b)', - ].map>(code => ({ - code, - options: ['always'], - })), - ...[ - 'f\n();', - 'f.b \n ();', - 'f\n() ().b \n()\n ()', - 'var f = new Foo\n();', - 'f// comment\n()', - 'f // comment\n ()', - 'f\n/*\n*/\n()', - 'f\r();', - 'f\u2028();', - 'f\u2029();', - 'f\r\n();', - - // optional call - 'f?.b \n ();', - 'f\n() ()?.b \n()\n ()', - ].map>(code => ({ - code, - options: ['always', { allowNewlines: true }], - })), - ], - invalid: [ - // "never" - ...[ - { code: 'f ();', output: 'f();' }, - { code: 'f (a, b);', output: 'f(a, b);' }, - { - code: 'f.b ();', - output: 'f.b();', - errors: [{ messageId: 'unexpectedWhitespace' as const, column: 3 }], - }, - { - code: 'f.b().c ();', - output: 'f.b().c();', - errors: [{ messageId: 'unexpectedWhitespace' as const, column: 7 }], - }, - { code: 'f() ()', output: 'f()()' }, - { code: '(function() {} ())', output: '(function() {}())' }, - { code: 'var f = new Foo ()', output: 'var f = new Foo()' }, - { code: 'f ( (0) )', output: 'f( (0) )' }, - { code: 'f(0) (1)', output: 'f(0)(1)' }, - { - code: 'f ();\n t ();', - output: 'f();\n t();', - errors: [ - { messageId: 'unexpectedWhitespace' as const }, - { messageId: 'unexpectedWhitespace' as const }, - ], - }, - - // https://github.com/eslint/eslint/issues/7787 - { - code: 'f\n();', - output: null, // no change - }, - { - code: ` -this.cancelled.add(request) -this.decrement(request) -(request.reject(new api.Cancel())) - `, - output: null, // no change - errors: [ - { messageId: 'unexpectedWhitespace' as const, line: 3, column: 23 }, - ], - }, - { - code: ` -var a = foo -(function(global) {}(this)); - `, - output: null, // no change - errors: [ - { messageId: 'unexpectedWhitespace' as const, line: 2, column: 9 }, - ], - }, - { - code: ` -var a = foo -(baz()) - `, - output: null, // no change - errors: [ - { messageId: 'unexpectedWhitespace' as const, line: 2, column: 9 }, - ], - }, - { - code: 'f\r();', - output: null, // no change - }, - { - code: 'f\u2028();', - output: null, // no change - }, - { - code: 'f\u2029();', - output: null, // no change - }, - { - code: 'f\r\n();', - output: null, // no change - }, - ].map>(code => ({ - options: ['never'], - errors: [{ messageId: 'unexpectedWhitespace' }], - ...code, - })), - - // "always" - ...[ - { code: 'f();', output: 'f ();' }, - { code: 'f(a, b);', output: 'f (a, b);' }, - { code: 'f() ()', output: 'f () ()' }, - { code: 'var f = new Foo()', output: 'var f = new Foo ()' }, - { code: 'f( (0) )', output: 'f ( (0) )' }, - { code: 'f(0) (1)', output: 'f (0) (1)' }, - ].map>(code => ({ - options: ['always'], - errors: [{ messageId: 'missing' }], - ...code, - })), - ...[ - { code: 'f\n();', output: 'f ();' }, - { code: 'f\n(a, b);', output: 'f (a, b);' }, - { - code: 'f.b();', - output: 'f.b ();', - errors: [{ messageId: 'missing' as const, column: 3 }], - }, - { code: 'f.b\n();', output: 'f.b ();' }, - { - code: 'f.b().c ();', - output: 'f.b ().c ();', - errors: [{ messageId: 'missing' as const, column: 3 }], - }, - { code: 'f.b\n().c ();', output: 'f.b ().c ();' }, - { code: 'f\n() ()', output: 'f () ()' }, - { - code: 'f\n()()', - output: 'f () ()', - errors: [ - { messageId: 'unexpectedNewline' as const }, - { messageId: 'missing' as const }, - ], - }, - { - code: '(function() {}())', - output: '(function() {} ())', - errors: [{ messageId: 'missing' as const }], - }, - { - code: 'f();\n t();', - output: 'f ();\n t ();', - errors: [ - { messageId: 'missing' as const }, - { messageId: 'missing' as const }, - ], - }, - { code: 'f\r();', output: 'f ();' }, - { - code: 'f\u2028();', - output: 'f ();', - errors: [{ messageId: 'unexpectedNewline' as const }], - }, - { - code: 'f\u2029();', - output: 'f ();', - errors: [{ messageId: 'unexpectedNewline' as const }], - }, - { code: 'f\r\n();', output: 'f ();' }, - ].map>(code => ({ - options: ['always'], - errors: [ - { - messageId: - code.code.includes('\n') || code.code.includes('\r') - ? 'unexpectedNewline' - : 'unexpectedWhitespace', - }, - ], - ...code, - })), - - // "always", "allowNewlines": true - ...[ - { code: 'f();', output: 'f ();' }, - { code: 'f(a, b);', output: 'f (a, b);' }, - { - code: 'f.b();', - output: 'f.b ();', - errors: [{ messageId: 'missing' as const, column: 3 }], - }, - { code: 'f.b().c ();', output: 'f.b ().c ();' }, - { code: 'f() ()', output: 'f () ()' }, - { code: '(function() {}())', output: '(function() {} ())' }, - { code: 'var f = new Foo()', output: 'var f = new Foo ()' }, - { code: 'f( (0) )', output: 'f ( (0) )' }, - { code: 'f(0) (1)', output: 'f (0) (1)' }, - { - code: 'f();\n t();', - output: 'f ();\n t ();', - errors: [ - { messageId: 'missing' as const }, - { messageId: 'missing' as const }, - ], - }, - ].map>(code => ({ - options: ['always', { allowNewlines: true }], - errors: [{ messageId: 'missing' }], - ...code, - })), - - // optional chain - ...[ - 'f ?.();', - 'f?. ();', - 'f ?. ();', - 'f\n?.();', - 'f?.\n();', - 'f\n?.\n();', - ].reduce[]>((acc, code) => { - acc.push( - { - options: ['always', { allowNewlines: true }], - errors: [{ messageId: 'unexpectedWhitespace' }], - code, - // apply no fixers to it - output: null, - }, - { - options: ['always'], - errors: [{ messageId: 'unexpectedWhitespace' }], - code, - // apply no fixers to it - output: null, - }, - { - options: ['never'], - errors: [{ messageId: 'unexpectedWhitespace' }], - code, - // apply no fixers to it - output: null, - }, - ); - - return acc; - }, []), - ], -}); diff --git a/packages/eslint-plugin/tests/rules/indent/indent.test.ts b/packages/eslint-plugin/tests/rules/indent/indent.test.ts deleted file mode 100644 index 5974cb5198ee..000000000000 --- a/packages/eslint-plugin/tests/rules/indent/indent.test.ts +++ /dev/null @@ -1,1740 +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 type { TSESLint } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; - -import rule from '../../../src/rules/indent'; -import type { - InferMessageIdsTypeFromRule, - InferOptionsTypeFromRule, -} from '../../../src/util'; - -type MessageIds = InferMessageIdsTypeFromRule; -type Options = InferOptionsTypeFromRule; - -/** - * Marks a test case as a plain javascript case which should be indented the same - */ -function nonTsTestCase(example: TemplateStringsArray): string { - return [`// Non-TS Test Case`, example].join('\n'); -} - -const individualNodeTests = [ - { - node: AST_NODE_TYPES.ClassDeclaration, - code: [ - ` -abstract class Foo { - constructor() {} - method() { - console.log('hi'); - } -} - `, - ], - }, - { - node: AST_NODE_TYPES.TSAbstractPropertyDefinition, - code: [ - ` -class Foo { - abstract bar : baz; - abstract foo : { - a : number - b : number - }; -} - `, - ], - }, - { - node: AST_NODE_TYPES.TSAbstractMethodDefinition, - code: [ - ` -class Foo { - abstract bar() : baz; - abstract foo() : { - a : number - b : number - }; -} - `, - ], - }, - { - node: AST_NODE_TYPES.TSArrayType, - code: [ - ` -type foo = ArrType[]; - `, - ], - }, - { - node: AST_NODE_TYPES.TSAsExpression, - code: [ - ` -const foo = {} as { - foo: string, - bar: number, -}; - `, - nonTsTestCase` -const foo = {} === -{ - foo: string, - bar: number, -}; - `, - ` -const foo = {} as -{ - foo: string, - bar: number, -}; - `, - ], - }, - { - node: AST_NODE_TYPES.TSConditionalType, - code: [ - nonTsTestCase` -const Foo = T - ? { - a: number, - b: boolean - } - : { - c: string - }; - `, - ` -type Foo = T extends string - ? { - a: number, - b: boolean - } - : { - c: string - }; - `, - nonTsTestCase` -const Foo = T ? { - a: number, - b: boolean -} : string; - `, - ` -type Foo = T extends string ? { - a: number, - b: boolean -} : string; - `, - ], - }, - { - node: AST_NODE_TYPES.TSConstructorType, - code: [ - ` -type Constructor = new ( - ...args: any[] -) => T; - `, - ], - }, - { - node: 'TSConstructSignature', - code: [ - ` -interface Foo { - new () : Foo - new () : { - bar : string - baz : string - } -} - `, - ], - }, - { - node: AST_NODE_TYPES.TSDeclareFunction, - code: [ - ` -declare function foo() : { - bar : number, - baz : string, -}; - `, - ], - }, - { - node: AST_NODE_TYPES.TSEmptyBodyFunctionExpression, - code: [ - ` -class Foo { - constructor( - a : string, - b : { - c : number - } - ) -} - `, - ], - }, - { - node: 'TSEnumDeclaration, TSEnumMember', - code: [ - ` -enum Foo { - bar = 1, - baz = 1, -} - `, - ], - }, - { - node: AST_NODE_TYPES.TSExportAssignment, - code: [ - ` -export = { - a: 1, - b: 2, -} - `, - ], - }, - { - node: AST_NODE_TYPES.TSFunctionType, - code: [ - ` -const foo: () => void = () => ({ - a: 1, - b: 2, -}); - `, - ` -const foo: () => { - a: number, - b: number, -} = () => ({ - a: 1, - b: 2, -}); - `, - ` -const foo: ({ - a: number, - b: number, -}) => void = (arg) => ({ - a: 1, - b: 2, -}); - `, - ` -const foo: ({ - a: number, - b: number, -}) => { - a: number, - b: number, -} = (arg) => ({ - a: arg.a, - b: arg.b, -}); - `, - ], - }, - { - node: AST_NODE_TYPES.TSImportType, - code: [ - ` -const foo: import("bar") = { - a: 1, - b: 2, -}; - `, - ` -const foo: import( - "bar" -) = { - a: 1, - b: 2, -}; - `, - ], - }, - { - node: AST_NODE_TYPES.TSIndexedAccessType, - code: [ - nonTsTestCase` -const Foo = Bar[ - 'asdf' -]; - `, - ` -type Foo = Bar[ - 'asdf' -]; - `, - ], - }, - { - node: AST_NODE_TYPES.TSIndexSignature, - code: [ - ` -type Foo = { - [a : string] : { - x : foo - [b : number] : boolean - } -} - `, - ], - }, - { - node: AST_NODE_TYPES.TSInferType, - code: [ - ` -type Foo = T extends string - ? infer U - : { - a : string - }; - `, - ], - }, - { - node: 'TSInterfaceBody, TSInterfaceDeclaration', - code: [ - ` -interface Foo { - a : string - b : { - c : number - d : boolean - } -} - `, - ], - }, - { - node: AST_NODE_TYPES.TSInterfaceHeritage, - code: [ - ` -interface Foo extends Bar { - a : string - b : { - c : number - d : boolean - } -} - `, - ], - }, - { - node: AST_NODE_TYPES.TSIntersectionType, - code: [ - ` -type Foo = "string" & { - a : number -} & number; - `, - ], - }, - { - node: 'TSImportEqualsDeclaration, TSExternalModuleReference', - code: [ - nonTsTestCase` -const foo = require( - 'asdf' -); - `, - ` -import foo = require( - 'asdf' -); - `, - ], - }, - // TSLiteralType - { - node: AST_NODE_TYPES.TSMappedType, - code: [ - ` -type Partial = { - [P in keyof T]: T[P]; -} - `, - ` -// TSQuestionToken -type Partial = { - [P in keyof T]?: T[P]; -} - `, - ` -// TSPlusToken -type Partial = { - [P in keyof T]+?: T[P]; -} - `, - ` -// TSMinusToken -type Partial = { - [P in keyof T]-?: T[P]; -} - `, - ], - }, - { - node: AST_NODE_TYPES.TSMethodSignature, - code: [ - ` -interface Foo { - method() : string - method2() : { - a : number - b : string - } -} - `, - ], - }, - // TSMinusToken - tested in TSMappedType - { - node: 'TSModuleBlock, TSModuleDeclaration', - code: [ - ` -declare module "foo" { - export const bar : { - a : string, - b : number, - } -} - `, - ], - }, - { - node: AST_NODE_TYPES.TSNonNullExpression, - code: [ - nonTsTestCase` -const foo = a - .b. - c; - `, - ` -const foo = a! - .b!. - c; - `, - ], - }, - { - node: AST_NODE_TYPES.TSParameterProperty, - code: [ - ` -class Foo { - constructor( - private foo : string, - public bar : { - a : string, - b : number, - } - ) { - console.log('foo') - } -} - `, - ], - }, - // TSPlusToken - tested in TSMappedType - { - node: AST_NODE_TYPES.TSPropertySignature, - code: [ - ` -interface Foo { - bar : string - baz : { - a : string - b : number - } -} - `, - ], - }, - { - node: AST_NODE_TYPES.TSQualifiedName, - code: [ - ` -const a: Foo.bar = { - a: 1, - b: 2, -}; - `, - nonTsTestCase` -const a = Foo. - bar - .baz = { - a: 1, - b: 2, - }; - `, - ` -const a: Foo. - bar - .baz = { - a: 1, - b: 2, - }; - `, - ], - }, - // TSQuestionToken - tested in TSMappedType - { - node: AST_NODE_TYPES.TSRestType, - code: [ - ` -type foo = [ - string, - ...string[], -]; - `, - ], - }, - { - node: AST_NODE_TYPES.TSThisType, - code: [ - ` -declare class MyArray extends Array { - sort(compareFn?: (a: T, b: T) => number): this; - meth() : { - a: number, - } -} - `, - ], - }, - { - node: AST_NODE_TYPES.TSTupleType, - code: [ - nonTsTestCase` -const foo = [ - string, - number, -]; - `, - ` -type foo = [ - string, - number, -]; - `, - nonTsTestCase` -const foo = [ - [ - string, - number, - ], -]; - `, - ` -type foo = [ - [ - string, - number, - ], -]; - `, - ], - }, - // TSTypeAnnotation - tested in everything.. - // TSTypeLiteral - tested in everything.. - { - node: AST_NODE_TYPES.TSTypeOperator, - code: [ - ` -type T = keyof { - a: 1, - b: 2, -}; - `, - ], - }, - { - node: 'TSTypeParameter, TSTypeParameterDeclaration', - code: [ - ` -type Foo = { - a : unknown, - b : never, -} - `, - ` -function foo< - T, - U ->() { - console.log(''); -} - `, - ], - }, - // TSTypeReference - tested in everything.. - { - node: AST_NODE_TYPES.TSUnionType, - code: [ - ` -type Foo = string | { - a : number -} | number; - `, - ], - }, -].reduce>( - (acc, testCase) => { - const indent = ' '; - - const validCases = [...acc.valid]; - const invalidCases = [...acc.invalid]; - - const codeCases = testCase.code.map(code => - [ - '', // newline to make test error messages nicer - `// ${testCase.node}`, // add comment to easily identify which node a test belongs to - code.trim(), // remove leading/trailing spaces from the case - ].join('\n'), - ); - - codeCases.forEach(code => { - // valid test case is just the code - validCases.push(code); - - const invalid = { - // test the fixer by removing all the spaces - code: code.replace(new RegExp(indent, 'g'), ''), - output: code, - errors: code - .split('\n') - .map | null>((line, lineNum) => { - const indentCount = line.split(indent).length - 1; - const spaceCount = indentCount * indent.length; - - if (indentCount < 1) { - return null; - } - - return { - messageId: 'wrongIndentation', - data: { - expected: `${spaceCount} spaces`, - actual: 0, - }, - line: lineNum + 1, - column: 1, - }; - }) - .filter( - (error): error is TSESLint.TestCaseError => - error != null, - ), - }; - if (invalid.errors.length > 0) { - invalidCases.push(invalid); - } - }); - - return { ...acc, valid: validCases, invalid: invalidCases }; - }, - { valid: [], invalid: [] }, -); - -const ruleTester = new RuleTester({ - parserOptions: { - ecmaVersion: 6, - sourceType: 'module', - ecmaFeatures: {}, - }, - parser: '@typescript-eslint/parser', -}); - -ruleTester.run('indent', rule, { - valid: [ - ...individualNodeTests.valid, - ` -@Component({ - components: { - ErrorPage: () => import('@/components/ErrorPage.vue'), - }, - head: { - titleTemplate(title) { - if (title) { - return \`test\` - } - return 'Title' - }, - htmlAttrs: { - lang: 'en', - }, - meta: [ - { charset: 'utf-8' }, - { name: 'viewport', content: 'width=device-width, initial-scale=1' }, - ], - }, -}) -export default class App extends Vue -{ - get error() - { - return this.$store.state.errorHandler.error - } -} - `, - // https://github.com/eslint/typescript-eslint-parser/issues/474 - ` -/** - * @param {string} name - * @param {number} age - * @returns {string} - */ -function foo(name: string, age: number): string {} - `, - ` -const firebaseApp = firebase.apps.length - ? firebase.app() - : firebase.initializeApp({ - apiKey: __FIREBASE_API_KEY__, - authDomain: __FIREBASE_AUTH_DOMAIN__, - databaseURL: __FIREBASE_DATABASE_URL__, - projectId: __FIREBASE_PROJECT_ID__, - storageBucket: __FIREBASE_STORAGE_BUCKET__, - messagingSenderId: __FIREBASE_MESSAGING_SENDER_ID__, - }) - `, - // https://github.com/bradzacher/eslint-plugin-typescript/issues/271 - { - code: ` -const foo = { - a: 1, - b: 2 - }, - bar = 1; - `, - options: [4, { VariableDeclarator: { const: 3 } }], - }, - { - code: ` -const foo : Foo = { - a: 1, - b: 2 - }, - bar = 1; - `, - options: [4, { VariableDeclarator: { const: 3 } }], - }, - { - code: ` -const name: string = ' Typescript ' - .toUpperCase() - .trim(), - - greeting: string = (" Hello " + name) - .toUpperCase() - .trim(); - `, - options: [2, { VariableDeclarator: { const: 3 } }], - }, - { - code: ` -const div: JQuery = $('
') - .addClass('some-class') - .appendTo($('body')), - - button: JQuery = $('