diff --git a/.cspell.json b/.cspell.json index 7ee50214e7fd..9df7d65a1b56 100644 --- a/.cspell.json +++ b/.cspell.json @@ -35,45 +35,47 @@ "\\(#.+?\\)" ], "words": [ - "ASTs", "Airbnb", "Airbnb's", - "Codecov", - "Crockford", - "errored", - "IDE's", - "IIFE", - "IIFEs", - "OOM", - "OOMs", - "Premade", - "ROADMAP", + "ASTs", "autofix", "autofixers", "backticks", "bigint", + "bivariant", "blockless", "codebases", + "Codecov", + "contravariant", + "Crockford", "declarators", "destructure", "destructured", + "errored", "erroring", "ESLint", "ESLint's", "espree", "estree", + "IDE's", + "IIFE", + "IIFEs", "linebreaks", "necroing", "nocheck", "nullish", + "OOM", + "OOMs", "parameterised", "performant", "pluggable", "postprocess", + "Premade", "prettier's", "recurse", "reimplement", "resync", + "ROADMAP", "ruleset", "rulesets", "superset", diff --git a/.github/ISSUE_TEMPLATE/eslint-plugin-tslint.md b/.github/ISSUE_TEMPLATE/eslint-plugin-tslint.md index b94f19fb5b82..e999830e4f18 100644 --- a/.github/ISSUE_TEMPLATE/eslint-plugin-tslint.md +++ b/.github/ISSUE_TEMPLATE/eslint-plugin-tslint.md @@ -15,7 +15,7 @@ The more relevant information you can include, the faster we can find the issue **Repro** diff --git a/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md b/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md index 3c0fb548f139..b9ab13f92496 100644 --- a/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md +++ b/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md @@ -28,7 +28,7 @@ Are you opening an issue because the rule you're trying to use is not found? **Repro** diff --git a/.github/ISSUE_TEMPLATE/typescript-eslint-parser.md b/.github/ISSUE_TEMPLATE/typescript-eslint-parser.md index aaa4bfd356c0..ca8d245b263f 100644 --- a/.github/ISSUE_TEMPLATE/typescript-eslint-parser.md +++ b/.github/ISSUE_TEMPLATE/typescript-eslint-parser.md @@ -15,7 +15,7 @@ The more relevant information you can include, the faster we can find the issue **Repro** diff --git a/.github/ISSUE_TEMPLATE/typescript-eslint-utils.md b/.github/ISSUE_TEMPLATE/typescript-eslint-utils.md index 5b365a85ea85..48bb39e6fe0e 100644 --- a/.github/ISSUE_TEMPLATE/typescript-eslint-utils.md +++ b/.github/ISSUE_TEMPLATE/typescript-eslint-utils.md @@ -15,7 +15,7 @@ The more relevant information you can include, the faster we can find the issue **Repro** diff --git a/.github/ISSUE_TEMPLATE/typescript-estree.md b/.github/ISSUE_TEMPLATE/typescript-estree.md index 21f4c800d548..f4a72b75b0ea 100644 --- a/.github/ISSUE_TEMPLATE/typescript-estree.md +++ b/.github/ISSUE_TEMPLATE/typescript-estree.md @@ -15,7 +15,7 @@ The more relevant information you can include, the faster we can find the issue **Repro** diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 18f5b8797272..a9f0f65f8917 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -107,6 +107,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int | [`@typescript-eslint/explicit-module-boundary-types`](./docs/rules/explicit-module-boundary-types.md) | Require explicit return and argument types on exported functions' and classes' public class methods | | | | | [`@typescript-eslint/member-delimiter-style`](./docs/rules/member-delimiter-style.md) | Require a specific member delimiter style for interfaces and type literals | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/member-ordering`](./docs/rules/member-ordering.md) | Require a consistent member declaration order | | | | +| [`@typescript-eslint/method-signature-style`](./docs/rules/method-signature-style.md) | Enforces using a particular method signature syntax. | | :wrench: | | | [`@typescript-eslint/naming-convention`](./docs/rules/naming-convention.md) | Enforces naming conventions for everything across a codebase | | | :thought_balloon: | | [`@typescript-eslint/no-base-to-string`](./docs/rules/no-base-to-string.md) | Requires that `.toString()` is only called on objects which provide useful information when stringified | | | :thought_balloon: | | [`@typescript-eslint/no-dynamic-delete`](./docs/rules/no-dynamic-delete.md) | Disallow the delete operator with computed key expressions | | :wrench: | | diff --git a/packages/eslint-plugin/docs/rules/member-ordering.md b/packages/eslint-plugin/docs/rules/member-ordering.md index d028d844368a..cccf2bbbe0ab 100644 --- a/packages/eslint-plugin/docs/rules/member-ordering.md +++ b/packages/eslint-plugin/docs/rules/member-ordering.md @@ -1,29 +1,54 @@ # Require a consistent member declaration order (`member-ordering`) -A consistent ordering of fields, methods and constructors can make interfaces, type literals, classes and class -expressions easier to read, navigate and edit. +A consistent ordering of fields, methods and constructors can make interfaces, type literals, classes and class expressions easier to read, navigate and edit. ## Rule Details -This rule aims to standardize the way class declarations, class expressions, interfaces and type literals are structured. +This rule aims to standardize the way class declarations, class expressions, interfaces and type literals are structured and ordered. -It allows to group members by their type (e.g. `public-static-field`, `protected-static-field`, `private-static-field`, `public-instance-field`, ...). By default, their order is the same inside `classes`, `classExpressions`, `interfaces` and `typeLiterals` (note: not all member types apply to `interfaces` and `typeLiterals`). It is possible to define the order for any of those individually or to change the default order for all of them by setting the `default` option. +### Grouping and sorting member groups + +It allows to group members by their type (e.g. `public-static-field`, `protected-static-field`, `private-static-field`, `public-instance-field`, ...) and enforce a certain order for these groups. By default, their order is the same inside `classes`, `classExpressions`, `interfaces` and `typeLiterals` (note: not all member types apply to `interfaces` and `typeLiterals`). It is possible to define the order for any of those individually or to change the default order for all of them by setting the `default` option. + +### Sorting members + +Besides grouping the members and sorting their groups, this rule also allows to sort the members themselves (e.g. `a`, `b`, `c`, ...). You have 2 options: Sort all of them while ignoring their type or sort them while respecting their types (e.g. sort all fields in an interface alphabetically). ## Options +These options allow to specify how to group the members and sort their groups. + +- Sort groups, don't enforce member order: Use `memberTypes` +- Sort members, don't enforce group order: Use `order` +- Sort members within groups: Use `memberTypes` and `order` + ```ts +type TypeOptions = + | { + memberTypes: Array | 'never', + order?: 'alphabetically' | 'as-written', + } + | { + order: 'alphabetically', + }; + { - default?: Array | never - classes?: Array | never - classExpressions?: Array | never + default?: TypeOptions, + + classes?: TypeOptions, + classExpressions?: TypeOptions, - interfaces?: ['signature' | 'field' | 'method' | 'constructor'] | never - typeLiterals?: ['signature' | 'field' | 'method' | 'constructor'] | never + interfaces?: TypeOptions<'signature' | 'field' | 'method' | 'constructor'>, + typeLiterals?: TypeOptions<'signature' | 'field' | 'method' | 'constructor'>, } ``` See below for the possible definitions of `MemberType`. +### Deprecated syntax + +Note: There is a deprecated syntax to specify the member types as an array. + ### Member types (granular form) There are multiple ways to specify the member types. The most explicit and granular form is the following: @@ -138,62 +163,72 @@ The third grouping option is to ignore both scope and accessibility. The default configuration looks as follows: -```json +```json5 { - "default": [ - "signature", + default: [ + // Index signature + 'signature', - "public-static-field", - "protected-static-field", - "private-static-field", + // Fields + 'public-static-field', + 'protected-static-field', + 'private-static-field', - "public-instance-field", - "protected-instance-field", - "private-instance-field", + 'public-instance-field', + 'protected-instance-field', + 'private-instance-field', - "public-abstract-field", - "protected-abstract-field", - "private-abstract-field", + 'public-abstract-field', + 'protected-abstract-field', + 'private-abstract-field', - "public-field", - "protected-field", - "private-field", + 'public-field', + 'protected-field', + 'private-field', - "static-field", - "instance-field", - "abstract-field", + 'static-field', + 'instance-field', + 'abstract-field', - "field", + 'field', - "constructor", + // Constructors + 'public-constructor', + 'protected-constructor', + 'private-constructor', - "public-static-method", - "protected-static-method", - "private-static-method", + 'constructor', - "public-instance-method", - "protected-instance-method", - "private-instance-method", + // Methods + 'public-static-method', + 'protected-static-method', + 'private-static-method', - "public-abstract-method", - "protected-abstract-method", - "private-abstract-method", + 'public-instance-method', + 'protected-instance-method', + 'private-instance-method', - "public-method", - "protected-method", - "private-method", + 'public-abstract-method', + 'protected-abstract-method', + 'private-abstract-method', - "static-method", - "instance-method", - "abstract-method", + 'public-method', + 'protected-method', + 'private-method', - "method" - ] + 'static-method', + 'instance-method', + 'abstract-method', + + 'method', + ], } ``` Note: The default configuration contains member group types which contain other member types (see above). This is intentional to provide better error messages. +Note: By default, the members are not sorted. If you want to sort them alphabetically, you have to provide a custom configuration. + ## Examples ### Custom `default` configuration @@ -448,7 +483,7 @@ const foo = class { }; ``` -Issue: Public static fields should come first, followed by static fields and instance fields. +Note: Public static fields should come first, followed by static fields and instance fields. ##### Correct examples @@ -542,21 +577,19 @@ class Foo { ##### Correct example -Examples of **correct** code for `{ "classes": [...] }` option: - ```ts class Foo { private C: string; // (irrelevant) public D: string; // (irrelevant) - public static E: string; // -> public static field + public B(): void {} // -> public instance method constructor() {} // (irrelevant) public static A(): void {} // (irrelevant) - public B(): void {} // -> public instance method + public static E: string; // -> public static field } ``` @@ -712,6 +745,73 @@ type Foo = { }; ``` +### Sorting alphabetically within member groups + +It is possible to sort all members within a group alphabetically. + +#### Configuration: `{ default: { memberTypes: , order: 'alphabetically' } }` + +This will apply the default order (see above) and enforce an alphabetic order within each group. + +##### Incorrect examples + +```ts +interface Foo { + a: x; + b: x; + c: x; + + new (): Bar; + (): Baz; + + a(): void; + b(): void; + c(): void; + + // Wrong group order, should be placed before all field definitions + [a: string]: number; +} +``` + +```ts +interface Foo { + [a: string]: number; + + a: x; + b: x; + c: x; + + new (): Bar; + (): Baz; + + // Wrong alphabetic order within group + c(): void; + b(): void; + a(): void; +} +``` + +### Sorting alphabetically while ignoring member groups + +It is also possible to sort all members and ignore the member groups completely. + +#### Configuration: `{ default: { memberTypes: 'never', order: 'alphabetically' } }` + +##### Incorrect example + +```ts +interface Foo { + b(): void; + a: b; + + [a: string]: number; // Order doesn't matter (no sortable identifier) + new (): Bar; // Order doesn't matter (no sortable identifier) + (): Baz; // Order doesn't matter (no sortable identifier) +} +``` + +Note: Wrong alphabetic order `b(): void` should come after `a: b`. + ## When Not To Use It If you don't care about the general structure of your classes and interfaces, then you will not need this rule. diff --git a/packages/eslint-plugin/docs/rules/method-signature-style.md b/packages/eslint-plugin/docs/rules/method-signature-style.md new file mode 100644 index 000000000000..d760c8d01932 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/method-signature-style.md @@ -0,0 +1,83 @@ +# Enforces using a particular method signature syntax. (`method-signature-style`) + +There are two ways to define an object/interface function property. + +```ts +// method shorthand syntax +interface T1 { + func(arg: string): number; +} + +// regular property with function type +interface T2 { + func: (arg: string) => number; +} +``` + +A good practice is to use the TypeScript's `strict` option (which implies `strictFunctionTypes`) which enables correct typechecking for function properties only (method signatures get old behavior). + +TypeScript FAQ: + +> A method and a function property of the same type behave differently. +> Methods are always bivariant in their argument, while function properties are contravariant in their argument under `strictFunctionTypes`. + +See the reasoning behind that in the [TypeScript PR for the compiler option](https://github.com/microsoft/TypeScript/pull/18654). + +## Options + +This rule accepts one string option: + +- `"property"`: Enforce using property signature for functions. Use this to enforce maximum correctness together with TypeScript's strict mode. +- `"method"`: Enforce using method signature for functions. Use this if you aren't using TypeScript's strict mode and prefer this style. + +The default is `"property"`. + +## Rule Details + +Examples of **incorrect** code with `property` option. + +```ts +interface T1 { + func(arg: string): number; +} +type T2 = { + func(arg: boolean): void; +}; +``` + +Examples of **correct** code with `property` option. + +```ts +interface T1 { + func: (arg: string) => number; +} +type T2 = { + func: (arg: boolean) => void; +}; +``` + +Examples of **incorrect** code with `method` option. + +```ts +interface T1 { + func: (arg: string) => number; +} +type T2 = { + func: (arg: boolean) => void; +}; +``` + +Examples of **correct** code with `method` option. + +```ts +interface T1 { + func(arg: string): number; +} +type T2 = { + func(arg: boolean): void; +}; +``` + +## When Not To Use It + +If you don't want to enforce a particular style for object/interface function types, and/or if you don't use `strictFunctionTypes`, then you don't need this rule. diff --git a/packages/eslint-plugin/docs/rules/no-explicit-any.md b/packages/eslint-plugin/docs/rules/no-explicit-any.md index 0c2713923492..c440d3bdc602 100644 --- a/packages/eslint-plugin/docs/rules/no-explicit-any.md +++ b/packages/eslint-plugin/docs/rules/no-explicit-any.md @@ -119,30 +119,23 @@ function foo2(...args: readonly any[]): void {} function foo3(...args: Array): void {} function foo4(...args: ReadonlyArray): void {} -const bar1 = (...args: any[]): void {} -const bar2 = (...args: readonly any[]): void {} -const bar3 = (...args: Array): void {} -const bar4 = (...args: ReadonlyArray): void {} +declare function bar(...args: any[]): void; -const baz1 = function (...args: any[]) {} -const baz2 = function (...args: readonly any[]) {} -const baz3 = function (...args: Array) {} -const baz4 = function (...args: ReadonlyArray) {} +const baz = (...args: any[]) => {}; +const qux = function(...args: any[]) {}; -interface Qux1 { (...args: any[]): void; } -interface Qux2 { (...args: readonly any[]): void; } -interface Qux3 { (...args: Array): void; } -interface Qux4 { (...args: ReadonlyArray): void; } +type Quux = (...args: any[]) => void; +type Quuz = new (...args: any[]) => void; -function quux1(fn: (...args: any[]) => void): void {} -function quux2(fn: (...args: readonly any[]) => void): void {} -function quux3(fn: (...args: Array) => void): void {} -function quux4(fn: (...args: ReadonlyArray) => void): void {} - -function quuz1(): ((...args: any[]) => void) {} -function quuz2(): ((...args: readonly any[]) => void) {} -function quuz3(): ((...args: Array) => void) {} -function quuz4(): ((...args: ReadonlyArray) => void) {} +interface Grault { + (...args: any[]): void; +} +interface Corge { + new (...args: any[]): void; +} +interface Garply { + f(...args: any[]): void; +} ``` Examples of **correct** code for the `{ "ignoreRestArgs": true }` option: @@ -155,30 +148,23 @@ function foo2(...args: readonly any[]): void {} function foo3(...args: Array): void {} function foo4(...args: ReadonlyArray): void {} -const bar1 = (...args: any[]): void {} -const bar2 = (...args: readonly any[]): void {} -const bar3 = (...args: Array): void {} -const bar4 = (...args: ReadonlyArray): void {} - -const baz1 = function (...args: any[]) {} -const baz2 = function (...args: readonly any[]) {} -const baz3 = function (...args: Array) {} -const baz4 = function (...args: ReadonlyArray) {} - -interface Qux1 { (...args: any[]): void; } -interface Qux2 { (...args: readonly any[]): void; } -interface Qux3 { (...args: Array): void; } -interface Qux4 { (...args: ReadonlyArray): void; } - -function quux1(fn: (...args: any[]) => void): void {} -function quux2(fn: (...args: readonly any[]) => void): void {} -function quux3(fn: (...args: Array) => void): void {} -function quux4(fn: (...args: ReadonlyArray) => void): void {} - -function quuz1(): ((...args: any[]) => void) {} -function quuz2(): ((...args: readonly any[]) => void) {} -function quuz3(): ((...args: Array) => void) {} -function quuz4(): ((...args: ReadonlyArray) => void) {} +declare function bar(...args: any[]): void; + +const baz = (...args: any[]) => {}; +const qux = function(...args: any[]) {}; + +type Quux = (...args: any[]) => void; +type Quuz = new (...args: any[]) => void; + +interface Grault { + (...args: any[]): void; +} +interface Corge { + new (...args: any[]): void; +} +interface Garply { + f(...args: any[]): void; +} ``` ## When Not To Use It diff --git a/packages/eslint-plugin/docs/rules/no-type-alias.md b/packages/eslint-plugin/docs/rules/no-type-alias.md index 9d7f1d47744a..5b720b2f331d 100644 --- a/packages/eslint-plugin/docs/rules/no-type-alias.md +++ b/packages/eslint-plugin/docs/rules/no-type-alias.md @@ -69,7 +69,7 @@ On the other hand, using a type alias as an interface can limit your ability to: - Debug your code: interfaces create a new name, so is easy to identify the base type of an object while debugging the application. -Finally, mapping types is an advance technique, leaving it open can quickly become a pain point +Finally, mapping types is an advanced technique and leaving it open can quickly become a pain point in your application. ## Rule Details diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json index 990ddbd5aec2..21f532062cf4 100644 --- a/packages/eslint-plugin/src/configs/all.json +++ b/packages/eslint-plugin/src/configs/all.json @@ -24,6 +24,7 @@ "@typescript-eslint/indent": "error", "@typescript-eslint/member-delimiter-style": "error", "@typescript-eslint/member-ordering": "error", + "@typescript-eslint/method-signature-style": "error", "@typescript-eslint/naming-convention": "error", "no-array-constructor": "off", "@typescript-eslint/no-array-constructor": "error", diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index d3c450bae235..d2b33ce4a42c 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -22,6 +22,7 @@ import interfaceNamePrefix from './interface-name-prefix'; import memberDelimiterStyle from './member-delimiter-style'; import memberNaming from './member-naming'; import memberOrdering from './member-ordering'; +import methodSignatureStyle from './method-signature-style'; import namingConvention from './naming-convention'; import noArrayConstructor from './no-array-constructor'; import noBaseToString from './no-base-to-string'; @@ -30,10 +31,10 @@ import noDynamicDelete from './no-dynamic-delete'; import noEmptyFunction from './no-empty-function'; import noEmptyInterface from './no-empty-interface'; import noExplicitAny from './no-explicit-any'; -import noExtraneousClass from './no-extraneous-class'; 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'; import noImpliedEval from './no-implied-eval'; @@ -100,7 +101,6 @@ export default { 'ban-ts-comment': banTsComment, 'ban-ts-ignore': banTsIgnore, 'ban-types': banTypes, - 'no-base-to-string': noBaseToString, 'brace-style': braceStyle, camelcase: camelcase, 'class-name-casing': classNameCasing, @@ -119,8 +119,10 @@ export default { 'member-delimiter-style': memberDelimiterStyle, 'member-naming': memberNaming, 'member-ordering': memberOrdering, + 'method-signature-style': methodSignatureStyle, 'naming-convention': namingConvention, 'no-array-constructor': noArrayConstructor, + 'no-base-to-string': noBaseToString, 'no-dupe-class-members': noDupeClassMembers, 'no-dynamic-delete': noDynamicDelete, 'no-empty-function': noEmptyFunction, diff --git a/packages/eslint-plugin/src/rules/member-ordering.ts b/packages/eslint-plugin/src/rules/member-ordering.ts index b9fb0ee8ef0d..14ddcc236a90 100644 --- a/packages/eslint-plugin/src/rules/member-ordering.ts +++ b/packages/eslint-plugin/src/rules/member-ordering.ts @@ -1,12 +1,21 @@ import { - TSESTree, AST_NODE_TYPES, + TSESLint, + TSESTree, } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; -type MessageIds = 'incorrectOrder'; -type OrderConfig = string[] | 'never'; -type Options = [ +export type MessageIds = 'incorrectGroupOrder' | 'incorrectOrder'; + +interface SortedOrderConfig { + memberTypes?: string[] | 'never'; + order: 'alphabetically' | 'as-written'; +} + +type OrderConfig = string[] | SortedOrderConfig | 'never'; +type Member = TSESTree.ClassElement | TSESTree.TypeElement; + +export type Options = [ { default?: OrderConfig; classes?: OrderConfig; @@ -16,30 +25,287 @@ type Options = [ }, ]; -const allMemberTypes = ['field', 'method', 'constructor'].reduce( - (all, type) => { - all.push(type); +const neverConfig = { + type: 'string', + enum: ['never'], +}; + +const arrayConfig = (memberTypes: string[]): object => ({ + type: 'array', + items: { + enum: memberTypes, + }, +}); + +const objectConfig = (memberTypes: string[]): object => ({ + type: 'object', + properties: { + memberTypes: { + oneOf: [arrayConfig(memberTypes), neverConfig], + }, + order: { + type: 'string', + enum: ['alphabetically', 'as-written'], + }, + }, + additionalProperties: false, +}); + +export const defaultOrder = [ + // Index signature + 'signature', - ['public', 'protected', 'private'].forEach(accessibility => { + // Fields + 'public-static-field', + 'protected-static-field', + 'private-static-field', + + 'public-instance-field', + 'protected-instance-field', + 'private-instance-field', + + 'public-abstract-field', + 'protected-abstract-field', + 'private-abstract-field', + + 'public-field', + 'protected-field', + 'private-field', + + 'static-field', + 'instance-field', + 'abstract-field', + + 'field', + + // Constructors + 'public-constructor', + 'protected-constructor', + 'private-constructor', + + 'constructor', + + // Methods + 'public-static-method', + 'protected-static-method', + 'private-static-method', + + 'public-instance-method', + 'protected-instance-method', + 'private-instance-method', + + 'public-abstract-method', + 'protected-abstract-method', + 'private-abstract-method', + + 'public-method', + 'protected-method', + 'private-method', + + 'static-method', + 'instance-method', + 'abstract-method', + + 'method', +]; + +const allMemberTypes = ['signature', 'field', 'method', 'constructor'].reduce< + string[] +>((all, type) => { + all.push(type); + + ['public', 'protected', 'private'].forEach(accessibility => { + if (type !== 'signature') { all.push(`${accessibility}-${type}`); // e.g. `public-field` + } - if (type !== 'constructor') { - // There is no `static-constructor` or `instance-constructor or `abstract-constructor` - ['static', 'instance', 'abstract'].forEach(scope => { - if (!all.includes(`${scope}-${type}`)) { - all.push(`${scope}-${type}`); - } + if (type !== 'constructor' && type !== 'signature') { + // There is no `static-constructor` or `instance-constructor` or `abstract-constructor` + ['static', 'instance', 'abstract'].forEach(scope => { + if (!all.includes(`${scope}-${type}`)) { + all.push(`${scope}-${type}`); + } - all.push(`${accessibility}-${scope}-${type}`); - }); - } - }); + all.push(`${accessibility}-${scope}-${type}`); + }); + } + }); - return all; - }, - [], -); -allMemberTypes.unshift('signature'); + return all; +}, []); + +const functionExpressions = [ + AST_NODE_TYPES.FunctionExpression, + AST_NODE_TYPES.ArrowFunctionExpression, +]; + +/** + * Gets the node type. + * + * @param node the node to be evaluated. + */ +function getNodeType(node: Member): string | null { + // TODO: add missing TSCallSignatureDeclaration + switch (node.type) { + case AST_NODE_TYPES.TSAbstractMethodDefinition: + case AST_NODE_TYPES.MethodDefinition: + return node.kind; + case AST_NODE_TYPES.TSMethodSignature: + return 'method'; + case AST_NODE_TYPES.TSConstructSignatureDeclaration: + return 'constructor'; + case AST_NODE_TYPES.TSAbstractClassProperty: + case AST_NODE_TYPES.ClassProperty: + return node.value && functionExpressions.includes(node.value.type) + ? 'method' + : 'field'; + case AST_NODE_TYPES.TSPropertySignature: + return 'field'; + case AST_NODE_TYPES.TSIndexSignature: + return 'signature'; + default: + return null; + } +} + +/** + * Gets the member name based on the member type. + * + * @param node the node to be evaluated. + * @param sourceCode + */ +function getMemberName( + node: Member, + sourceCode: TSESLint.SourceCode, +): string | null { + switch (node.type) { + case AST_NODE_TYPES.TSPropertySignature: + case AST_NODE_TYPES.TSMethodSignature: + case AST_NODE_TYPES.TSAbstractClassProperty: + case AST_NODE_TYPES.ClassProperty: + return util.getNameFromMember(node, sourceCode); + case AST_NODE_TYPES.TSAbstractMethodDefinition: + case AST_NODE_TYPES.MethodDefinition: + return node.kind === 'constructor' + ? 'constructor' + : util.getNameFromMember(node, sourceCode); + case AST_NODE_TYPES.TSConstructSignatureDeclaration: + return 'new'; + case AST_NODE_TYPES.TSIndexSignature: + return util.getNameFromIndexSignature(node); + default: + return null; + } +} + +/** + * Gets the calculated rank using the provided method definition. + * The algorithm is as follows: + * - Get the rank based on the accessibility-scope-type name, e.g. public-instance-field + * - If there is no order for accessibility-scope-type, then strip out the accessibility. + * - If there is no order for scope-type, then strip out the scope. + * - If there is no order for type, then return -1 + * @param memberGroups the valid names to be validated. + * @param orderConfig the current order to be validated. + * + * @return Index of the matching member type in the order configuration. + */ +function getRankOrder(memberGroups: string[], orderConfig: string[]): number { + let rank = -1; + const stack = memberGroups.slice(); // Get a copy of the member groups + + while (stack.length > 0 && rank === -1) { + rank = orderConfig.indexOf(stack.shift()!); + } + + return rank; +} + +/** + * Gets the rank of the node given the order. + * @param node the node to be evaluated. + * @param orderConfig the current order to be validated. + * @param supportsModifiers a flag indicating whether the type supports modifiers (scope or accessibility) or not. + */ +function getRank( + node: Member, + orderConfig: string[], + supportsModifiers: boolean, +): number { + const type = getNodeType(node); + + if (type === null) { + // shouldn't happen but just in case, put it on the end + return orderConfig.length - 1; + } + + const abstract = + node.type === AST_NODE_TYPES.TSAbstractClassProperty || + node.type === AST_NODE_TYPES.TSAbstractMethodDefinition; + + const scope = + 'static' in node && node.static + ? 'static' + : abstract + ? 'abstract' + : 'instance'; + const accessibility = + 'accessibility' in node && node.accessibility + ? node.accessibility + : 'public'; + + // Collect all existing member groups (e.g. 'public-instance-field', 'instance-field', 'public-field', 'constructor' etc.) + const memberGroups = []; + + if (supportsModifiers) { + if (type !== 'constructor') { + // Constructors have no scope + memberGroups.push(`${accessibility}-${scope}-${type}`); + memberGroups.push(`${scope}-${type}`); + } + + memberGroups.push(`${accessibility}-${type}`); + } + + memberGroups.push(type); + + return getRankOrder(memberGroups, orderConfig); +} + +/** + * Gets the lowest possible rank higher than target. + * e.g. given the following order: + * ... + * public-static-method + * protected-static-method + * private-static-method + * public-instance-method + * protected-instance-method + * private-instance-method + * ... + * and considering that a public-instance-method has already been declared, so ranks contains + * public-instance-method, then the lowest possible rank for public-static-method is + * public-instance-method. + * @param ranks the existing ranks in the object. + * @param target the target rank. + * @param order the current order to be validated. + * @returns the name of the lowest possible rank without dashes (-). + */ +function getLowestRank( + ranks: number[], + target: number, + order: string[], +): string { + let lowest = ranks[ranks.length - 1]; + + ranks.forEach(rank => { + if (rank > target) { + lowest = Math.min(lowest, rank); + } + }); + + return order[lowest].replace(/-/g, ' '); +} export default util.createRule({ name: 'member-ordering', @@ -52,6 +318,8 @@ export default util.createRule({ }, messages: { incorrectOrder: + 'Member "{{member}}" should be declared before member "{{beforeMember}}".', + incorrectGroupOrder: 'Member {{name}} should be declared before all {{rank}} definitions.', }, schema: [ @@ -60,67 +328,37 @@ export default util.createRule({ properties: { default: { oneOf: [ - { - enum: ['never'], - }, - { - type: 'array', - items: { - enum: allMemberTypes, - }, - }, + neverConfig, + arrayConfig(allMemberTypes), + objectConfig(allMemberTypes), ], }, classes: { oneOf: [ - { - enum: ['never'], - }, - { - type: 'array', - items: { - enum: allMemberTypes, - }, - }, + neverConfig, + arrayConfig(allMemberTypes), + objectConfig(allMemberTypes), ], }, classExpressions: { oneOf: [ - { - enum: ['never'], - }, - { - type: 'array', - items: { - enum: allMemberTypes, - }, - }, + neverConfig, + arrayConfig(allMemberTypes), + objectConfig(allMemberTypes), ], }, interfaces: { oneOf: [ - { - enum: ['never'], - }, - { - type: 'array', - items: { - enum: ['signature', 'field', 'method', 'constructor'], - }, - }, + neverConfig, + arrayConfig(['signature', 'field', 'method', 'constructor']), + objectConfig(['signature', 'field', 'method', 'constructor']), ], }, typeLiterals: { oneOf: [ - { - enum: ['never'], - }, - { - type: 'array', - items: { - enum: ['signature', 'field', 'method', 'constructor'], - }, - }, + neverConfig, + arrayConfig(['signature', 'field', 'method', 'constructor']), + objectConfig(['signature', 'field', 'method', 'constructor']), ], }, }, @@ -130,263 +368,140 @@ export default util.createRule({ }, defaultOptions: [ { - default: [ - 'signature', - - 'public-static-field', - 'protected-static-field', - 'private-static-field', - - 'public-instance-field', - 'protected-instance-field', - 'private-instance-field', - - 'public-abstract-field', - 'protected-abstract-field', - 'private-abstract-field', - - 'public-field', - 'protected-field', - 'private-field', - - 'static-field', - 'instance-field', - 'abstract-field', - - 'field', - - 'constructor', - - 'public-static-method', - 'protected-static-method', - 'private-static-method', - - 'public-instance-method', - 'protected-instance-method', - 'private-instance-method', - - 'public-abstract-method', - 'protected-abstract-method', - 'private-abstract-method', - - 'public-method', - 'protected-method', - 'private-method', - - 'static-method', - 'instance-method', - 'abstract-method', - - 'method', - ], + default: defaultOrder, }, ], create(context, [options]) { - const sourceCode = context.getSourceCode(); - - const functionExpressions = [ - AST_NODE_TYPES.FunctionExpression, - AST_NODE_TYPES.ArrowFunctionExpression, - ]; - /** - * Gets the node type. - * @param node the node to be evaluated. - */ - function getNodeType( - node: TSESTree.ClassElement | TSESTree.TypeElement, - ): string | null { - // TODO: add missing TSCallSignatureDeclaration - switch (node.type) { - case AST_NODE_TYPES.TSAbstractMethodDefinition: - case AST_NODE_TYPES.MethodDefinition: - return node.kind; - case AST_NODE_TYPES.TSMethodSignature: - return 'method'; - case AST_NODE_TYPES.TSConstructSignatureDeclaration: - return 'constructor'; - case AST_NODE_TYPES.TSAbstractClassProperty: - case AST_NODE_TYPES.ClassProperty: - return node.value && functionExpressions.includes(node.value.type) - ? 'method' - : 'field'; - case AST_NODE_TYPES.TSPropertySignature: - return 'field'; - case AST_NODE_TYPES.TSIndexSignature: - return 'signature'; - default: - return null; - } - } - - /** - * Gets the member name based on the member type. - * @param node the node to be evaluated. - */ - function getMemberName( - node: TSESTree.ClassElement | TSESTree.TypeElement, - ): string | null { - switch (node.type) { - case AST_NODE_TYPES.TSPropertySignature: - case AST_NODE_TYPES.TSMethodSignature: - case AST_NODE_TYPES.TSAbstractClassProperty: - case AST_NODE_TYPES.ClassProperty: - return util.getNameFromMember(node, sourceCode); - case AST_NODE_TYPES.TSAbstractMethodDefinition: - case AST_NODE_TYPES.MethodDefinition: - return node.kind === 'constructor' - ? 'constructor' - : util.getNameFromMember(node, sourceCode); - case AST_NODE_TYPES.TSConstructSignatureDeclaration: - return 'new'; - case AST_NODE_TYPES.TSIndexSignature: - return util.getNameFromIndexSignature(node); - default: - return null; - } - } - - /** - * Gets the calculated rank using the provided method definition. - * The algorithm is as follows: - * - Get the rank based on the accessibility-scope-type name, e.g. public-instance-field - * - If there is no order for accessibility-scope-type, then strip out the accessibility. - * - If there is no order for scope-type, then strip out the scope. - * - If there is no order for type, then return -1 - * @param memberTypes the valid names to be validated. - * @param order the current order to be validated. + * Checks if the member groups are correctly sorted. * - * @return Index of the matching member type in the order configuration. - */ - function getRankOrder(memberTypes: string[], order: string[]): number { - let rank = -1; - const stack = memberTypes.slice(); // Get a copy of the member types - - while (stack.length > 0 && rank === -1) { - rank = order.indexOf(stack.shift()!); - } - - return rank; - } - - /** - * Gets the rank of the node given the order. - * @param node the node to be evaluated. - * @param order the current order to be validated. - * @param supportsModifiers a flag indicating whether the type supports modifiers (scope or accessibility) or not. + * @param members Members to be validated. + * @param groupOrder Group order to be validated. + * @param supportsModifiers A flag indicating whether the type supports modifiers (scope or accessibility) or not. + * + * @return Array of member groups or null if one of the groups is not correctly sorted. */ - function getRank( - node: TSESTree.ClassElement | TSESTree.TypeElement, - order: string[], + function checkGroupSort( + members: Member[], + groupOrder: string[], supportsModifiers: boolean, - ): number { - const type = getNodeType(node); - if (type === null) { - // shouldn't happen but just in case, put it on the end - return order.length - 1; - } - - const abstract = - node.type === AST_NODE_TYPES.TSAbstractClassProperty || - node.type === AST_NODE_TYPES.TSAbstractMethodDefinition; - - const scope = - 'static' in node && node.static - ? 'static' - : abstract - ? 'abstract' - : 'instance'; - const accessibility = - 'accessibility' in node && node.accessibility - ? node.accessibility - : 'public'; - - const memberTypes = []; - - if (supportsModifiers) { - if (type !== 'constructor') { - // Constructors have no scope - memberTypes.push(`${accessibility}-${scope}-${type}`); - memberTypes.push(`${scope}-${type}`); + ): Array | null { + const previousRanks: number[] = []; + const memberGroups: Array = []; + let isCorrectlySorted = true; + + // Find first member which isn't correctly sorted + members.forEach(member => { + const rank = getRank(member, groupOrder, supportsModifiers); + const name = getMemberName(member, context.getSourceCode()); + const rankLastMember = previousRanks[previousRanks.length - 1]; + + if (rank !== -1) { + // Works for 1st item because x < undefined === false for any x (typeof string) + if (rank < rankLastMember) { + context.report({ + node: member, + messageId: 'incorrectGroupOrder', + data: { + name, + rank: getLowestRank(previousRanks, rank, groupOrder), + }, + }); + + isCorrectlySorted = false; + } else if (rank === rankLastMember) { + // Same member group --> Push to existing member group array + memberGroups[memberGroups.length - 1].push(member); + } else { + // New member group --> Create new member group array + previousRanks.push(rank); + memberGroups.push([member]); + } } + }); - memberTypes.push(`${accessibility}-${type}`); - } - - memberTypes.push(type); - - return getRankOrder(memberTypes, order); + return isCorrectlySorted ? memberGroups : null; } /** - * Gets the lowest possible rank higher than target. - * e.g. given the following order: - * ... - * public-static-method - * protected-static-method - * private-static-method - * public-instance-method - * protected-instance-method - * private-instance-method - * ... - * and considering that a public-instance-method has already been declared, so ranks contains - * public-instance-method, then the lowest possible rank for public-static-method is - * public-instance-method. - * @param ranks the existing ranks in the object. - * @param target the target rank. - * @param order the current order to be validated. - * @returns the name of the lowest possible rank without dashes (-). + * Checks if the members are alphabetically sorted. + * + * @param members Members to be validated. + * + * @return True if all members are correctly sorted. */ - function getLowestRank( - ranks: number[], - target: number, - order: string[], - ): string { - let lowest = ranks[ranks.length - 1]; - - ranks.forEach(rank => { - if (rank > target) { - lowest = Math.min(lowest, rank); + function checkAlphaSort(members: Member[]): boolean { + let previousName = ''; + let isCorrectlySorted = true; + + // Find first member which isn't correctly sorted + members.forEach(member => { + const name = getMemberName(member, context.getSourceCode()); + + // Note: Not all members have names + if (name) { + if (name < previousName) { + context.report({ + node: member, + messageId: 'incorrectOrder', + data: { + member: name, + beforeMember: previousName, + }, + }); + + isCorrectlySorted = false; + } + + previousName = name; } }); - return order[lowest].replace(/-/g, ' '); + return isCorrectlySorted; } /** * Validates if all members are correctly sorted. * * @param members Members to be validated. - * @param order Current order to be validated. + * @param orderConfig Order config to be validated. * @param supportsModifiers A flag indicating whether the type supports modifiers (scope or accessibility) or not. */ function validateMembersOrder( - members: (TSESTree.ClassElement | TSESTree.TypeElement)[], - order: OrderConfig, + members: Member[], + orderConfig: OrderConfig, supportsModifiers: boolean, ): void { - if (members && order !== 'never') { - const previousRanks: number[] = []; - - // Find first member which isn't correctly sorted - members.forEach(member => { - const rank = getRank(member, order, supportsModifiers); - - if (rank !== -1) { - if (rank < previousRanks[previousRanks.length - 1]) { - context.report({ - node: member, - messageId: 'incorrectOrder', - data: { - name: getMemberName(member), - rank: getLowestRank(previousRanks, rank, order), - }, - }); - } else { - previousRanks.push(rank); - } + if (orderConfig !== 'never') { + // Standardize config + let order = null; + let memberTypes; + + if (Array.isArray(orderConfig)) { + memberTypes = orderConfig; + } else { + order = orderConfig.order; + memberTypes = orderConfig.memberTypes; + } + + // Check order + if (Array.isArray(memberTypes)) { + const grouped = checkGroupSort( + members, + memberTypes, + supportsModifiers, + ); + + if (grouped === null) { + return; } - }); + + if (order === 'alphabetically') { + grouped.some(groupMember => !checkAlphaSort(groupMember)); + } + } else if (order === 'alphabetically') { + checkAlphaSort(members); + } } } diff --git a/packages/eslint-plugin/src/rules/method-signature-style.ts b/packages/eslint-plugin/src/rules/method-signature-style.ts new file mode 100644 index 000000000000..6ef8db5a0c9b --- /dev/null +++ b/packages/eslint-plugin/src/rules/method-signature-style.ts @@ -0,0 +1,124 @@ +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; +import * as util from '../util'; + +export type Options = ['property' | 'method']; + +export type MessageId = 'errorMethod' | 'errorProperty'; + +export default util.createRule({ + name: 'method-signature-style', + meta: { + type: 'suggestion', + docs: { + description: 'Enforces using a particular method signature syntax.', + category: 'Best Practices', + recommended: false, + }, + fixable: 'code', + messages: { + errorMethod: + 'Shorthand method signature is forbidden. Use a function property instead.', + errorProperty: + 'Function property signature is forbidden. Use a method shorthand instead.', + }, + schema: [ + { + enum: ['property', 'method'], + }, + ], + }, + defaultOptions: ['property'], + + create(context, [mode]) { + const sourceCode = context.getSourceCode(); + + function getMethodKey( + node: TSESTree.TSMethodSignature | TSESTree.TSPropertySignature, + ): string { + let key = sourceCode.getText(node.key); + if (node.computed) { + key = `[${key}]`; + } + if (node.optional) { + key = `${key}?`; + } + if (node.readonly) { + key = `readonly ${key}`; + } + return key; + } + + function getMethodParams( + node: TSESTree.TSMethodSignature | TSESTree.TSFunctionType, + ): string { + let params = '()'; + if (node.params.length > 0) { + params = sourceCode.text.substring( + sourceCode.getTokenBefore(node.params[0])!.range[0], + sourceCode.getTokenAfter(node.params[node.params.length - 1])! + .range[1], + ); + } + if (node.typeParameters != null) { + const typeParams = sourceCode.getText(node.typeParameters); + params = `${typeParams}${params}`; + } + return params; + } + + function getMethodReturnType( + node: TSESTree.TSMethodSignature | TSESTree.TSFunctionType, + ): string { + return sourceCode.getText(node.returnType!.typeAnnotation); + } + + return { + TSMethodSignature(methodNode): void { + if (mode === 'method') { + return; + } + + context.report({ + node: methodNode, + messageId: 'errorMethod', + fix: fixer => { + const key = getMethodKey(methodNode); + const params = getMethodParams(methodNode); + const returnType = getMethodReturnType(methodNode); + return fixer.replaceText( + methodNode, + `${key}: ${params} => ${returnType}`, + ); + }, + }); + }, + TSPropertySignature(propertyNode): void { + const typeNode = propertyNode.typeAnnotation?.typeAnnotation; + if (typeNode?.type !== AST_NODE_TYPES.TSFunctionType) { + return; + } + + if (mode === 'property') { + return; + } + + context.report({ + node: propertyNode, + messageId: 'errorProperty', + fix: fixer => { + const key = getMethodKey(propertyNode); + const params = getMethodParams(typeNode); + const returnType = getMethodReturnType(typeNode); + return fixer.replaceText( + propertyNode, + `${key}${params}: ${returnType}`, + ); + }, + }); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/no-explicit-any.ts b/packages/eslint-plugin/src/rules/no-explicit-any.ts index 91765c40e262..795dae017253 100644 --- a/packages/eslint-plugin/src/rules/no-explicit-any.ts +++ b/packages/eslint-plugin/src/rules/no-explicit-any.ts @@ -53,18 +53,23 @@ export default util.createRule({ ], create(context, [{ ignoreRestArgs, fixToUnknown }]) { /** - * Checks if the node is an arrow function, function declaration or function expression + * Checks if the node is an arrow function, function/constructor declaration or function expression * @param node the node to be validated. - * @returns true if the node is an arrow function, function declaration, function expression, function type, or call signature + * @returns true if the node is any kind of function declaration or expression * @private */ function isNodeValidFunction(node: TSESTree.Node): boolean { return [ - AST_NODE_TYPES.ArrowFunctionExpression, - AST_NODE_TYPES.FunctionDeclaration, - AST_NODE_TYPES.FunctionExpression, - AST_NODE_TYPES.TSFunctionType, - AST_NODE_TYPES.TSCallSignatureDeclaration, + AST_NODE_TYPES.ArrowFunctionExpression, // const x = (...args: any[]) => {}; + AST_NODE_TYPES.FunctionDeclaration, // function f(...args: any[]) {} + AST_NODE_TYPES.FunctionExpression, // const x = function(...args: any[]) {}; + AST_NODE_TYPES.TSEmptyBodyFunctionExpression, // declare class A { f(...args: any[]): unknown; } + AST_NODE_TYPES.TSFunctionType, // type T = (...args: any[]) => unknown; + AST_NODE_TYPES.TSConstructorType, // type T = new (...args: any[]) => unknown + AST_NODE_TYPES.TSCallSignatureDeclaration, // type T = {(...args: any[]): unknown}; + AST_NODE_TYPES.TSConstructSignatureDeclaration, // type T = {new (...args: any[]): unknown}; + AST_NODE_TYPES.TSMethodSignature, // type T = {f(...args: any[]): unknown}; + AST_NODE_TYPES.TSDeclareFunction, // declare function _8(...args: any[]): unknown; ].includes(node.type); } diff --git a/packages/eslint-plugin/src/rules/no-throw-literal.ts b/packages/eslint-plugin/src/rules/no-throw-literal.ts index c8c5bcc1837f..ffa8a614e6b6 100644 --- a/packages/eslint-plugin/src/rules/no-throw-literal.ts +++ b/packages/eslint-plugin/src/rules/no-throw-literal.ts @@ -43,10 +43,11 @@ export default util.createRule({ } } - const baseTypes = checker.getBaseTypes(type as ts.InterfaceType); - for (const baseType of baseTypes) { - if (isErrorLike(baseType)) { - return true; + if (symbol.flags & (ts.SymbolFlags.Class | ts.SymbolFlags.Interface)) { + for (const baseType of checker.getBaseTypes(type as ts.InterfaceType)) { + if (isErrorLike(baseType)) { + return true; + } } } diff --git a/packages/eslint-plugin/tests/rules/member-ordering.test.ts b/packages/eslint-plugin/tests/rules/member-ordering.test.ts index ddea73e8d1d9..c64c57c45919 100644 --- a/packages/eslint-plugin/tests/rules/member-ordering.test.ts +++ b/packages/eslint-plugin/tests/rules/member-ordering.test.ts @@ -1,14 +1,16 @@ -// TODO - migrate this test to the rule -/* eslint-disable @typescript-eslint/internal/plugin-test-formatting */ - -import rule from '../../src/rules/member-ordering'; +import rule, { + defaultOrder, + MessageIds, + Options, +} from '../../src/rules/member-ordering'; import { RuleTester } from '../RuleTester'; +import { TSESLint } from '@typescript-eslint/experimental-utils'; const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', }); -ruleTester.run('member-ordering', rule, { +const grouped: TSESLint.RunTests = { valid: [ ` // no accessibility === public @@ -1339,7 +1341,7 @@ interface Foo { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'method', @@ -1372,7 +1374,7 @@ interface Foo { options: [{ default: ['signature', 'method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -1381,7 +1383,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -1390,7 +1392,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -1399,7 +1401,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -1408,7 +1410,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -1417,7 +1419,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -1426,7 +1428,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'field', @@ -1435,7 +1437,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'Z', rank: 'field', @@ -1470,7 +1472,7 @@ interface Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -1479,7 +1481,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -1488,7 +1490,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -1497,7 +1499,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -1506,7 +1508,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -1515,7 +1517,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -1524,7 +1526,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'field', @@ -1533,7 +1535,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'Z', rank: 'field', @@ -1571,7 +1573,7 @@ interface Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -1580,7 +1582,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -1589,7 +1591,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -1598,7 +1600,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -1607,7 +1609,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -1616,7 +1618,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -1625,7 +1627,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'field', @@ -1634,7 +1636,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'Z', rank: 'field', @@ -1671,7 +1673,7 @@ interface Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'B', rank: 'method', @@ -1680,7 +1682,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'C', rank: 'method', @@ -1689,7 +1691,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'D', rank: 'method', @@ -1698,7 +1700,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'E', rank: 'method', @@ -1707,7 +1709,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'F', rank: 'method', @@ -1739,7 +1741,7 @@ type Foo = { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'method', @@ -1772,7 +1774,7 @@ type Foo = { options: [{ default: ['method', 'constructor', 'signature', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -1781,7 +1783,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -1790,7 +1792,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -1799,7 +1801,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -1808,7 +1810,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -1817,7 +1819,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -1826,7 +1828,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'Z', rank: 'field', @@ -1835,7 +1837,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'field', @@ -1870,7 +1872,7 @@ type Foo = { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'signature', @@ -1879,7 +1881,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'signature', @@ -1888,7 +1890,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'signature', @@ -1897,7 +1899,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'signature', @@ -1906,7 +1908,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'signature', @@ -1915,7 +1917,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'signature', @@ -1924,7 +1926,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'signature', @@ -1962,7 +1964,7 @@ type Foo = { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -1971,7 +1973,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -1980,7 +1982,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -1989,7 +1991,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -1998,7 +2000,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -2007,7 +2009,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -2016,7 +2018,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'field', @@ -2025,7 +2027,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'Z', rank: 'field', @@ -2062,7 +2064,7 @@ type Foo = { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'B', rank: 'method', @@ -2071,7 +2073,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'C', rank: 'method', @@ -2080,7 +2082,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'D', rank: 'method', @@ -2089,7 +2091,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'E', rank: 'method', @@ -2098,7 +2100,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'F', rank: 'method', @@ -2129,7 +2131,7 @@ class Foo { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'public instance method', @@ -2138,7 +2140,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'public instance method', @@ -2147,7 +2149,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'public instance method', @@ -2179,7 +2181,7 @@ class Foo { options: [{ default: ['field', 'constructor', 'method', 'signature'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'constructor', @@ -2188,7 +2190,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'B', rank: 'constructor', @@ -2197,7 +2199,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'C', rank: 'constructor', @@ -2206,7 +2208,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'D', rank: 'constructor', @@ -2215,7 +2217,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'E', rank: 'constructor', @@ -2224,7 +2226,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'F', rank: 'constructor', @@ -2255,7 +2257,7 @@ class Foo { options: [{ default: ['field', 'method'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'method', @@ -2286,7 +2288,7 @@ class Foo { options: [{ default: ['method', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -2317,7 +2319,7 @@ class Foo { options: [{ classes: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -2326,7 +2328,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -2335,7 +2337,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -2344,7 +2346,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -2353,7 +2355,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'field', @@ -2389,7 +2391,7 @@ class Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -2398,7 +2400,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -2407,7 +2409,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -2416,7 +2418,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -2425,7 +2427,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -2434,7 +2436,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -2443,7 +2445,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'field', @@ -2486,7 +2488,7 @@ class Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'private field', @@ -2495,7 +2497,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'F', rank: 'protected field', @@ -2539,7 +2541,7 @@ class Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'public instance method', @@ -2548,7 +2550,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'public field', @@ -2589,7 +2591,7 @@ class Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'method', @@ -2632,7 +2634,7 @@ class Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'private static method', @@ -2641,7 +2643,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'private static method', @@ -2676,7 +2678,7 @@ class Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'protected static field', @@ -2712,7 +2714,7 @@ class Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'protected static field', @@ -2742,7 +2744,7 @@ const foo = class Foo { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'public instance method', @@ -2751,7 +2753,7 @@ const foo = class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'public instance method', @@ -2760,7 +2762,7 @@ const foo = class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'public instance method', @@ -2792,7 +2794,7 @@ const foo = class { options: [{ default: ['signature', 'field', 'constructor', 'method'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'constructor', @@ -2801,7 +2803,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'B', rank: 'constructor', @@ -2810,7 +2812,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'C', rank: 'constructor', @@ -2819,7 +2821,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'D', rank: 'constructor', @@ -2828,7 +2830,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'E', rank: 'constructor', @@ -2837,7 +2839,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'F', rank: 'constructor', @@ -2869,7 +2871,7 @@ const foo = class { options: [{ default: ['field', 'method'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'method', @@ -2900,7 +2902,7 @@ const foo = class { options: [{ default: ['method', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -2932,7 +2934,7 @@ const foo = class { options: [{ classExpressions: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -2941,7 +2943,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -2950,7 +2952,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -2959,7 +2961,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -2968,7 +2970,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'field', @@ -3004,7 +3006,7 @@ const foo = class { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -3013,7 +3015,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -3022,7 +3024,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -3031,7 +3033,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -3040,7 +3042,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -3049,7 +3051,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -3058,7 +3060,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'field', @@ -3101,7 +3103,7 @@ const foo = class { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'private field', @@ -3110,7 +3112,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'F', rank: 'protected field', @@ -3154,7 +3156,7 @@ const foo = class { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'public instance method', @@ -3163,7 +3165,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'public field', @@ -3204,7 +3206,7 @@ const foo = class { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'method', @@ -3247,7 +3249,7 @@ const foo = class { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'private static method', @@ -3256,7 +3258,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'private static method', @@ -3295,7 +3297,7 @@ const foo = class { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'protected static field', @@ -3331,7 +3333,7 @@ const foo = class { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'protected static field', @@ -3341,7 +3343,6 @@ const foo = class { }, ], }, - { code: ` class Foo { @@ -3354,7 +3355,7 @@ class Foo { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'public instance method', @@ -3363,7 +3364,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'public instance method', @@ -3372,7 +3373,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'Z', rank: 'public instance method', @@ -3395,7 +3396,7 @@ class Foo { options: [{ default: ['method', 'constructor', 'field', 'signature'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'constructor', @@ -3418,7 +3419,7 @@ class Foo { options: [{ default: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'constructor', @@ -3438,7 +3439,7 @@ interface Foo { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'method', @@ -3458,7 +3459,7 @@ type Foo = { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'method', @@ -3479,7 +3480,7 @@ type Foo = { options: [{ default: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -3498,7 +3499,7 @@ abstract class Foo { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'B', rank: 'public abstract method', @@ -3520,7 +3521,7 @@ abstract class Foo { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'B', rank: 'public abstract field', @@ -3541,7 +3542,7 @@ abstract class Foo { options: [{ default: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'C', rank: 'field', @@ -3573,7 +3574,7 @@ class Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'D', rank: 'signature', @@ -3589,13 +3590,12 @@ abstract class Foo { abstract B: string; abstract A(): void; public C(): {}; - } `, options: [{ default: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'field', @@ -3604,7 +3604,7 @@ abstract class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'C', rank: 'field', @@ -3615,4 +3615,2255 @@ abstract class Foo { ], }, ], +}; + +const sortedWithoutGroupingDefaultOption: TSESLint.RunTests< + MessageIds, + Options +> = { + valid: [ + // default option + interface + multiple types + { + code: ` +interface Foo { + a(): Foo; + (): Foo; + b(): Foo; +} + `, + options: [ + { default: { memberTypes: defaultOrder, order: 'alphabetically' } }, + ], + }, + + // default option + interface + lower/upper case + { + code: ` +interface Foo { + A : b; + a : b; +} + `, + options: [{ default: { memberTypes: 'never', order: 'alphabetically' } }], + }, + + // default option + interface + numbers + { + code: ` +interface Foo { + a1 : b; + aa : b; +} + `, + options: [{ default: { memberTypes: 'never', order: 'alphabetically' } }], + }, + + // default option + type literal + multiple types + { + code: ` +type Foo = { + a : b; + [a: string] : number; + b() : void; + new () : Bar; + () : Baz; +} + `, + options: [{ default: { memberTypes: 'never', order: 'alphabetically' } }], + }, + + // default option + type literal + lower/upper case + { + code: ` +type Foo = { + A : b; + a : b; +} + `, + options: [{ default: { memberTypes: 'never', order: 'alphabetically' } }], + }, + + // default option + type literal + numbers + { + code: ` +type Foo = { + a1 : b; + aa : b; +} + `, + options: [{ default: { memberTypes: 'never', order: 'alphabetically' } }], + }, + + // default option + class + multiple types + { + code: ` +class Foo { + public static a : string; + protected static b : string = ""; + private static c : string = ""; + constructor() {} + public d : string = ""; + protected e : string = ""; + private f : string = ""; +} + `, + options: [{ default: { memberTypes: 'never', order: 'alphabetically' } }], + }, + + // default option + class + lower/upper case + { + code: ` +class Foo { + public static A : string; + public static a : string; +} + `, + options: [{ default: { memberTypes: 'never', order: 'alphabetically' } }], + }, + + // default option + class + numbers + { + code: ` +class Foo { + public static a1 : string; + public static aa : string; +} + `, + options: [{ default: { memberTypes: 'never', order: 'alphabetically' } }], + }, + + // default option + class expression + multiple types + { + code: ` +const foo = class Foo { + public static a : string; + protected static b : string = ""; + private static c : string = ""; + constructor() {} + public d : string = ""; + protected e : string = ""; + private f : string = ""; +} + `, + options: [{ default: { memberTypes: 'never', order: 'alphabetically' } }], + }, + + // default option + class expression + lower/upper case + { + code: ` +const foo = class Foo { + public static A : string; + public static a : string; +} + `, + options: [{ default: { memberTypes: 'never', order: 'alphabetically' } }], + }, + + // default option + class expression + numbers + { + code: ` +const foo = class Foo { + public static a1 : string; + public static aa : string; +} + `, + options: [{ default: { memberTypes: 'never', order: 'alphabetically' } }], + }, + ], + invalid: [ + // default option + interface + wrong order + { + code: ` +interface Foo { + b() : void; + a : b; + [a: string] : number; + new () : Bar; + () : Baz; +} + `, + options: [{ default: { memberTypes: 'never', order: 'alphabetically' } }], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + ], + }, + + // default option + interface + wrong order (multiple) + { + code: ` +interface Foo { + c : string; + b : string; + a : string; +} + `, + options: [{ default: { memberTypes: 'never', order: 'alphabetically' } }], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'b', + beforeMember: 'c', + }, + }, + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + ], + }, + + // default option + type literal + wrong order + { + code: ` +type Foo = { + b() : void; + a : b; + [a: string] : number; + new () : Bar; + () : Baz; +} + `, + options: [{ default: { memberTypes: 'never', order: 'alphabetically' } }], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + ], + }, + + // default option + type literal + wrong order (multiple) + { + code: ` +type Foo = { + c : string; + b : string; + a : string; +} + `, + options: [{ default: { memberTypes: 'never', order: 'alphabetically' } }], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'b', + beforeMember: 'c', + }, + }, + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + ], + }, + + // default option + class + wrong order + { + code: ` +class Foo { + protected static b : string = ""; + public static a : string; + private static c : string = ""; + constructor() {} + public d : string = ""; + protected e : string = ""; + private f : string = ""; +} + `, + options: [{ default: { memberTypes: 'never', order: 'alphabetically' } }], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + ], + }, + + // default option + class + wrong order (multiple) + { + code: ` +class Foo { + public static c: string; + public static b: string; + public static a: string; +} + `, + options: [{ default: { memberTypes: 'never', order: 'alphabetically' } }], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'b', + beforeMember: 'c', + }, + }, + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + ], + }, + + // default option + class expression + wrong order + { + code: ` +const foo = class Foo { + protected static b : string = ""; + public static a : string; + private static c : string = ""; + constructor() {} + public d : string = ""; + protected e : string = ""; + private f : string = ""; +} + `, + options: [{ default: { memberTypes: 'never', order: 'alphabetically' } }], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + ], + }, + + // default option + class expression + wrong order (multiple) + { + code: ` +const foo = class Foo { + public static c: string; + public static b: string; + public static a: string; +} + `, + options: [{ default: { memberTypes: 'never', order: 'alphabetically' } }], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'b', + beforeMember: 'c', + }, + }, + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + ], + }, + ], +}; + +const sortedWithoutGroupingClassesOption: TSESLint.RunTests< + MessageIds, + Options +> = { + valid: [ + // classes option + interface + multiple types --> Only member group order is checked (default config) + { + code: ` +interface Foo { + [a: string] : number; + c : b; + new () : Bar; + b() : void; + () : Baz; +} + `, + options: [{ classes: { memberTypes: 'never', order: 'alphabetically' } }], + }, + + // classes option + interface + lower/upper case --> Only member group order is checked (default config) + { + code: ` +interface Foo { + a : b; + A : b; +} + `, + options: [{ classes: { memberTypes: 'never', order: 'alphabetically' } }], + }, + + // classes option + interface + numbers --> Only member group order is checked (default config) + { + code: ` +interface Foo { + aa : b; + a1 : b; +} + `, + options: [{ classes: { memberTypes: 'never', order: 'alphabetically' } }], + }, + + // classes option + type literal + multiple types --> Only member group order is checked (default config) + { + code: ` +type Foo = { + [a: string] : number; + c : b; + new () : Bar; + b() : void; + () : Baz; +} + `, + options: [{ classes: { memberTypes: 'never', order: 'alphabetically' } }], + }, + + // classes option + type literal + lower/upper case --> Only member group order is checked (default config) + { + code: ` +type Foo = { + a : b; + A : b; +} + `, + options: [{ classes: { memberTypes: 'never', order: 'alphabetically' } }], + }, + + // classes option + type literal + numbers --> Only member group order is checked (default config) + { + code: ` +type Foo = { + aa : b; + a1 : b; +} + `, + options: [{ classes: { memberTypes: 'never', order: 'alphabetically' } }], + }, + + // classes option + class + multiple types + { + code: ` +class Foo { + public static a : string; + protected static b : string = ""; + private static c : string = ""; + constructor() {} + public d : string = ""; + protected e : string = ""; + private f : string = ""; +} + `, + options: [{ classes: { memberTypes: 'never', order: 'alphabetically' } }], + }, + + // classes option + class + lower/upper case + { + code: ` +class Foo { + public static A : string; + public static a : string; +} + `, + options: [{ classes: { memberTypes: 'never', order: 'alphabetically' } }], + }, + + // classes option + class + numbers + { + code: ` +class Foo { + public static a1 : string; + public static aa : string; +} + `, + options: [{ classes: { memberTypes: 'never', order: 'alphabetically' } }], + }, + + // classes option + class expression + multiple types --> Only member group order is checked (default config) + { + code: ` +const foo = class Foo { + public static a : string; + protected static b : string = ""; + private static c : string = ""; + public d : string = ""; + protected e : string = ""; + private f : string = ""; + constructor() {} +} + `, + options: [{ classes: { memberTypes: 'never', order: 'alphabetically' } }], + }, + + // classes option + class expression + lower/upper case --> Only member group order is checked (default config) + { + code: ` +const foo = class Foo { + public static a : string; + public static A : string; +} + `, + options: [{ classes: { memberTypes: 'never', order: 'alphabetically' } }], + }, + + // classes option + class expression + numbers --> Only member group order is checked (default config) + { + code: ` +const foo = class Foo { + public static aa : string; + public static a1 : string; +} + `, + options: [{ classes: { memberTypes: 'never', order: 'alphabetically' } }], + }, + ], + invalid: [ + // classes option + class + wrong order + { + code: ` +class Foo { + protected static b : string = ""; + public static a : string; + private static c : string = ""; + constructor() {} + public d : string = ""; + protected e : string = ""; + private f : string = ""; +} + `, + options: [{ classes: { memberTypes: 'never', order: 'alphabetically' } }], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + ], + }, + + // classes option + class + wrong order (multiple) + { + code: ` +class Foo { + public static c: string; + public static b: string; + public static a: string; +} + `, + options: [{ classes: { memberTypes: 'never', order: 'alphabetically' } }], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'b', + beforeMember: 'c', + }, + }, + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + ], + }, + ], +}; + +const sortedWithoutGroupingClassExpressionsOption: TSESLint.RunTests< + MessageIds, + Options +> = { + valid: [ + // classExpressions option + interface + multiple types --> Only member group order is checked (default config) + { + code: ` +interface Foo { + [a: string] : number; + c : b; + new () : Bar; + b() : void; + () : Baz; +} + `, + options: [ + { classExpressions: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // classExpressions option + interface + lower/upper case --> Only member group order is checked (default config) + { + code: ` +interface Foo { + a : b; + A : b; +} + `, + options: [ + { classExpressions: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // classExpressions option + interface + numbers --> Only member group order is checked (default config) + { + code: ` +interface Foo { + aa : b; + a1 : b; +} + `, + options: [ + { classExpressions: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // classExpressions option + type literal + multiple types --> Only member group order is checked (default config) + { + code: ` +type Foo = { + [a: string] : number; + c : b; + new () : Bar; + b() : void; + () : Baz; +} + `, + options: [ + { classExpressions: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // classExpressions option + type literal + lower/upper case --> Only member group order is checked (default config) + { + code: ` +type Foo = { + a : b; + A : b; +} + `, + options: [ + { classExpressions: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // classExpressions option + type literal + numbers --> Only member group order is checked (default config) + { + code: ` +type Foo = { + aa : b; + a1 : b; +} + `, + options: [ + { classExpressions: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // classExpressions option + class + multiple types --> Only member group order is checked (default config) + { + code: ` +class Foo { + public static a : string; + protected static b : string = ""; + private static c : string = ""; + public d : string = ""; + protected e : string = ""; + private f : string = ""; + constructor() {} +} + `, + options: [ + { classExpressions: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // classExpressions option + class + lower/upper case --> Only member group order is checked (default config) + { + code: ` +class Foo { + public static a : string; + public static A : string; +} + `, + options: [ + { classExpressions: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // classExpressions option + class + numbers --> Only member group order is checked (default config) + { + code: ` +class Foo { + public static aa : string; + public static a1 : string; +} + `, + options: [ + { classExpressions: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // classExpressions option + class expression + multiple types + { + code: ` +const foo = class Foo { + public static a : string; + protected static b : string = ""; + private static c : string = ""; + constructor() {} + public d : string = ""; + protected e : string = ""; + private f : string = ""; +} + `, + options: [ + { classExpressions: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // classExpressions option + class expression + lower/upper case + { + code: ` +const foo = class Foo { + public static A : string; + public static a : string; +} + `, + options: [ + { classExpressions: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // classExpressions option + class expression + numbers + { + code: ` +const foo = class Foo { + public static a1 : string; + public static aa : string; +} + `, + options: [ + { classExpressions: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + ], + invalid: [ + // classExpressions option + class expression + wrong order + { + code: ` +const foo = class Foo { + protected static b : string = ""; + public static a : string; + private static c : string = ""; + constructor() {} + public d : string = ""; + protected e : string = ""; + private f : string = ""; +} + `, + options: [ + { classExpressions: { memberTypes: 'never', order: 'alphabetically' } }, + ], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + ], + }, + + // classExpressions option + class expression + wrong order (multiple) + { + code: ` +const foo = class Foo { + public static c: string; + public static b: string; + public static a: string; +} + `, + options: [ + { classExpressions: { memberTypes: 'never', order: 'alphabetically' } }, + ], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'b', + beforeMember: 'c', + }, + }, + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + ], + }, + ], +}; + +const sortedWithoutGroupingInterfacesOption: TSESLint.RunTests< + MessageIds, + Options +> = { + valid: [ + // interfaces option + interface + multiple types + { + code: ` +interface Foo { + [a: string] : number; + a : b; + b() : void; + new () : Bar; + () : Baz; +} + `, + options: [ + { interfaces: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // interfaces option + interface + lower/upper case + { + code: ` +interface Foo { + A : b; + a : b; +} + `, + options: [ + { interfaces: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // interfaces option + interface + numbers + { + code: ` +interface Foo { + a1 : b; + aa : b; +} + `, + options: [ + { interfaces: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // interfaces option + type literal + multiple types --> Only member group order is checked (default config) + { + code: ` +type Foo = { + [a: string] : number; + c : b; + new () : Bar; + b() : void; + () : Baz; +} + `, + options: [ + { interfaces: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // interfaces option + type literal + lower/upper case --> Only member group order is checked (default config) + { + code: ` +type Foo = { + a : b; + A : b; +} + `, + options: [ + { interfaces: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // interfaces option + type literal + numbers --> Only member group order is checked (default config) + { + code: ` +type Foo = { + aa : b; + a1 : b; +} + `, + options: [ + { interfaces: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // interfaces option + class + multiple types --> Only member group order is checked (default config) + { + code: ` +class Foo { + public static a : string; + protected static b : string = ""; + private static c : string = ""; + public d : string = ""; + protected e : string = ""; + private f : string = ""; + constructor() {} +} + `, + options: [ + { interfaces: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // interfaces option + class + lower/upper case --> Only member group order is checked (default config) + { + code: ` +class Foo { + public static a : string; + public static A : string; +} + `, + options: [ + { interfaces: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // interfaces option + class + numbers --> Only member group order is checked (default config) + { + code: ` +class Foo { + public static aa : string; + public static a1 : string; +} + `, + options: [ + { interfaces: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // interfaces option + class expression + multiple types --> Only member group order is checked (default config) + { + code: ` +const foo = class Foo { + public static a : string; + protected static b : string = ""; + private static c : string = ""; + public d : string = ""; + protected e : string = ""; + private f : string = ""; + constructor() {} +} + `, + options: [ + { interfaces: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // interfaces option + class expression + lower/upper case --> Only member group order is checked (default config) + { + code: ` +const foo = class Foo { + public static a : string; + public static A : string; +} + `, + options: [ + { interfaces: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // interfaces option + class expression + numbers --> Only member group order is checked (default config) + { + code: ` +const foo = class Foo { + public static aa : string; + public static a1 : string; +} + `, + options: [ + { interfaces: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + ], + invalid: [ + // interfaces option + interface + wrong order + { + code: ` +interface Foo { + b() : void; + a : b; + [a: string] : number; + new () : Bar; + () : Baz; +} + `, + options: [ + { interfaces: { memberTypes: 'never', order: 'alphabetically' } }, + ], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + ], + }, + + // interfaces option + interface + wrong order (multiple) + { + code: ` +interface Foo { + c : string; + b : string; + a : string; +} + `, + options: [ + { interfaces: { memberTypes: 'never', order: 'alphabetically' } }, + ], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'b', + beforeMember: 'c', + }, + }, + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + ], + }, + ], +}; + +const sortedWithoutGroupingTypeLiteralsOption: TSESLint.RunTests< + MessageIds, + Options +> = { + valid: [ + // typeLiterals option + interface + multiple types --> Only member group order is checked (default config) + { + code: ` +interface Foo { + [a: string] : number; + c : b; + new () : Bar; + b() : void; + () : Baz; +} + `, + options: [ + { typeLiterals: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // typeLiterals option + interface + lower/upper case --> Only member group order is checked (default config) + { + code: ` +interface Foo { + a : b; + A : b; +} + `, + options: [ + { typeLiterals: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // typeLiterals option + interface + numbers --> Only member group order is checked (default config) + { + code: ` +interface Foo { + aa : b; + a1 : b; +} + `, + options: [ + { typeLiterals: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // typeLiterals option + type literal + multiple types + { + code: ` +type Foo = { + [a: string] : number; + a : b; + b() : void; + new () : Bar; + () : Baz; +} + `, + options: [ + { typeLiterals: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // typeLiterals option + type literal + lower/upper case + { + code: ` +type Foo = { + A : b; + a : b; +} + `, + options: [ + { typeLiterals: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // typeLiterals option + type literal + numbers + { + code: ` +type Foo = { + a1 : b; + aa : b; +} + `, + options: [ + { typeLiterals: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // typeLiterals option + class + multiple types --> Only member group order is checked (default config) + { + code: ` +class Foo { + public static a : string; + protected static b : string = ""; + private static c : string = ""; + public d : string = ""; + protected e : string = ""; + private f : string = ""; + constructor() {} +} + `, + options: [ + { typeLiterals: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // typeLiterals option + class + lower/upper case --> Only member group order is checked (default config) + { + code: ` +class Foo { + public static a : string; + public static A : string; +} + `, + options: [ + { typeLiterals: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // typeLiterals option + class + numbers --> Only member group order is checked (default config) + { + code: ` +class Foo { + public static aa : string; + public static a1 : string; +} + `, + options: [ + { typeLiterals: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // typeLiterals option + class expression + multiple types --> Only member group order is checked (default config) + { + code: ` +const foo = class Foo { + public static a : string; + protected static b : string = ""; + private static c : string = ""; + public d : string = ""; + protected e : string = ""; + private f : string = ""; + constructor() {} +} + `, + options: [ + { typeLiterals: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // typeLiterals option + class expression + lower/upper case --> Only member group order is checked (default config) + { + code: ` +const foo = class Foo { + public static a : string; + public static A : string; +} + `, + options: [ + { typeLiterals: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + + // typeLiterals option + class expression + numbers --> Only member group order is checked (default config) + { + code: ` +const foo = class Foo { + public static aa : string; + public static a1 : string; +} + `, + options: [ + { typeLiterals: { memberTypes: 'never', order: 'alphabetically' } }, + ], + }, + ], + invalid: [ + // typeLiterals option + type literal + wrong order + { + code: ` +type Foo = { + b() : void; + a : b; + [a: string] : number; + new () : Bar; + () : Baz; +} + `, + options: [ + { typeLiterals: { memberTypes: 'never', order: 'alphabetically' } }, + ], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + ], + }, + + // typeLiterals option + type literal + wrong order (multiple) + { + code: ` +type Foo = { + c : string; + b : string; + a : string; +} + `, + options: [ + { typeLiterals: { memberTypes: 'never', order: 'alphabetically' } }, + ], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'b', + beforeMember: 'c', + }, + }, + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + ], + }, + ], +}; + +const sortedWithGroupingDefaultOption: TSESLint.RunTests< + MessageIds, + Options +> = { + valid: [ + // default option + interface + default order + alphabetically + { + code: ` +interface Foo { + [a: string] : number; + + a : x; + b : x; + c : x; + + new () : Bar; + + a() : void; + b() : void; + c() : void; + + () : Baz; +} + `, + options: [ + { default: { memberTypes: defaultOrder, order: 'alphabetically' } }, + ], + }, + + // default option + interface + custom order + alphabetically + { + code: ` +interface Foo { + new () : Bar; + + a() : void; + b() : void; + c() : void; + + a : x; + b : x; + c : x; + + [a: string] : number; + () : Baz; +} + `, + options: [ + { + default: { + memberTypes: ['constructor', 'method', 'field'], + order: 'alphabetically', + }, + }, + ], + }, + + // default option + type literal + default order + alphabetically + { + code: ` +type Foo = { + [a: string] : number; + + a : x; + b : x; + c : x; + + new () : Bar; + + a() : void; + b() : void; + c() : void; + + () : Baz; +} + `, + options: [ + { default: { memberTypes: defaultOrder, order: 'alphabetically' } }, + ], + }, + + // default option + type literal + custom order + alphabetically + { + code: ` +type Foo = { + [a: string] : number; + + new () : Bar; + + a() : void; + b() : void; + c() : void; + + a : x; + b : x; + c : x; + + () : Baz; +} + `, + options: [ + { + default: { + memberTypes: ['constructor', 'method', 'field'], + order: 'alphabetically', + }, + }, + ], + }, + + // default option + class + default order + alphabetically + { + code: ` +class Foo { + public static a: string; + protected static b: string = ""; + private static c: string = ""; + + public d: string = ""; + protected e: string = ""; + private f: string = ""; + + constructor() {} +} + `, + options: [ + { default: { memberTypes: defaultOrder, order: 'alphabetically' } }, + ], + }, + + // default option + class + custom order + alphabetically + { + code: ` +class Foo { + constructor() {} + + public d: string = ""; + protected e: string = ""; + private f: string = ""; + + public static a: string; + protected static b: string = ""; + private static c: string = ""; +} + `, + options: [ + { + default: { + memberTypes: ['constructor', 'instance-field', 'static-field'], + order: 'alphabetically', + }, + }, + ], + }, + + // default option + class expression + default order + alphabetically + { + code: ` +const foo = class Foo { + public static a: string; + protected static b: string = ""; + private static c: string = ""; + + public d: string = ""; + protected e: string = ""; + private f: string = ""; + + constructor() {} +} + `, + options: [ + { default: { memberTypes: defaultOrder, order: 'alphabetically' } }, + ], + }, + + // default option + class expression + custom order + alphabetically + { + code: ` +const foo = class Foo { + constructor() {} + + public d: string = ""; + protected e: string = ""; + private f: string = ""; + + public static a: string; + protected static b: string = ""; + private static c: string = ""; +} + `, + options: [ + { + default: { + memberTypes: ['constructor', 'instance-field', 'static-field'], + order: 'alphabetically', + }, + }, + ], + }, + ], + invalid: [ + // default option + interface + wrong order within group and wrong group order + alphabetically + { + code: ` +interface Foo { + [a: string] : number; + + a : x; + b : x; + c : x; + + c() : void; + b() : void; + a() : void; + + () : Baz; + + new () : Bar; +} + `, + options: [ + { default: { memberTypes: defaultOrder, order: 'alphabetically' } }, + ], + errors: [ + { + messageId: 'incorrectGroupOrder', + data: { + name: 'new', + rank: 'method', + }, + }, + ], + }, + + // default option + type literal + wrong order within group and wrong group order + alphabetically + { + code: ` +type Foo = { + [a: string] : number; + + a : x; + b : x; + c : x; + + c() : void; + b() : void; + a() : void; + + () : Baz; + + new () : Bar; +} + `, + options: [ + { default: { memberTypes: defaultOrder, order: 'alphabetically' } }, + ], + errors: [ + { + messageId: 'incorrectGroupOrder', + data: { + name: 'new', + rank: 'method', + }, + }, + ], + }, + + // default option + class + wrong order within group and wrong group order + alphabetically + { + code: ` +class Foo { + public static c: string = ""; + public static b: string = ""; + public static a: string; + + constructor() {} + + public d: string = ""; +} + `, + options: [ + { default: { memberTypes: defaultOrder, order: 'alphabetically' } }, + ], + errors: [ + { + messageId: 'incorrectGroupOrder', + data: { + name: 'd', + rank: 'public constructor', + }, + }, + ], + }, + + // default option + class expression + wrong order within group and wrong group order + alphabetically + { + code: ` +const foo = class Foo { + public static c: string = ""; + public static b: string = ""; + public static a: string; + + constructor() {} + + public d: string = ""; +} + `, + options: [ + { default: { memberTypes: defaultOrder, order: 'alphabetically' } }, + ], + errors: [ + { + messageId: 'incorrectGroupOrder', + data: { + name: 'd', + rank: 'public constructor', + }, + }, + ], + }, + ], +}; + +const sortedWithGroupingClassesOption: TSESLint.RunTests< + MessageIds, + Options +> = { + valid: [ + // classes option + interface + alphabetically --> Default order applies + { + code: ` +interface Foo { + [a: string] : number; + + c : x; + b : x; + a : x; + + new () : Bar; + + c() : void; + b() : void; + a() : void; + + () : Baz; +} + `, + options: [{ classes: { order: 'alphabetically' } }], + }, + + // classes option + type literal + alphabetically --> Default order applies + { + code: ` +type Foo = { + [a: string] : number; + + c : x; + b : x; + a : x; + + new () : Bar; + + c() : void; + b() : void; + a() : void; + + () : Baz; +} + `, + options: [{ classes: { order: 'alphabetically' } }], + }, + + // classes option + class + default order + alphabetically + { + code: ` +class Foo { + public static a: string; + protected static b: string = ""; + private static c: string = ""; + + public d: string = ""; + protected e: string = ""; + private f: string = ""; + + constructor() {} +} + `, + options: [ + { classes: { memberTypes: defaultOrder, order: 'alphabetically' } }, + ], + }, + + // classes option + class + custom order + alphabetically + { + code: ` +class Foo { + constructor() {} + + public d: string = ""; + protected e: string = ""; + private f: string = ""; + + public static a: string; + protected static b: string = ""; + private static c: string = ""; +} + `, + options: [ + { + classes: { + memberTypes: ['constructor', 'instance-field', 'static-field'], + order: 'alphabetically', + }, + }, + ], + }, + + // classes option + class expression + alphabetically --> Default order applies + { + code: ` +const foo = class Foo { + public static a: string; + protected static b: string = ""; + private static c: string = ""; + + public d: string = ""; + protected e: string = ""; + private f: string = ""; + + constructor() {} +} + `, + options: [{ classes: { order: 'alphabetically' } }], + }, + ], + invalid: [ + // default option + class + wrong order within group and wrong group order + alphabetically + { + code: ` +class Foo { + public static c: string = ""; + public static b: string = ""; + public static a: string; + + constructor() {} + + public d: string = ""; +} + `, + options: [ + { default: { memberTypes: defaultOrder, order: 'alphabetically' } }, + ], + errors: [ + { + messageId: 'incorrectGroupOrder', + data: { + name: 'd', + rank: 'public constructor', + }, + }, + ], + }, + ], +}; + +const sortedWithGroupingClassExpressionsOption: TSESLint.RunTests< + MessageIds, + Options +> = { + valid: [ + // classExpressions option + interface + alphabetically --> Default order applies + { + code: ` +interface Foo { + [a: string] : number; + + c : x; + b : x; + a : x; + + new () : Bar; + + c() : void; + b() : void; + a() : void; + + () : Baz; +} + `, + options: [{ classExpressions: { order: 'alphabetically' } }], + }, + + // classExpressions option + type literal + alphabetically --> Default order applies + { + code: ` +type Foo = { + [a: string] : number; + + c : x; + b : x; + a : x; + + new () : Bar; + + c() : void; + b() : void; + a() : void; + + () : Baz; +} + `, + options: [{ classExpressions: { order: 'alphabetically' } }], + }, + + // classExpressions option + class + alphabetically --> Default order applies + { + code: ` +class Foo { + public static a: string; + protected static b: string = ""; + private static c: string = ""; + + public d: string = ""; + protected e: string = ""; + private f: string = ""; + + constructor() {} +} + `, + options: [{ classExpressions: { order: 'alphabetically' } }], + }, + + // classExpressions option + class expression + default order + alphabetically + { + code: ` +const foo = class Foo { + public static a: string; + protected static b: string = ""; + private static c: string = ""; + + public d: string = ""; + protected e: string = ""; + private f: string = ""; + + constructor() {} +} + `, + options: [ + { + classExpressions: { + memberTypes: defaultOrder, + order: 'alphabetically', + }, + }, + ], + }, + + // classExpressions option + class expression + custom order + alphabetically + { + code: ` +const foo = class Foo { + constructor() {} + + public d: string = ""; + protected e: string = ""; + private f: string = ""; + + public static a: string; + protected static b: string = ""; + private static c: string = ""; +} + `, + options: [ + { + classExpressions: { + memberTypes: ['constructor', 'instance-field', 'static-field'], + order: 'alphabetically', + }, + }, + ], + }, + ], + invalid: [ + // default option + class expression + wrong order within group and wrong group order + alphabetically + { + code: ` +const foo = class Foo { + public static c: string = ""; + public static b: string = ""; + public static a: string; + + constructor() {} + + public d: string = ""; +} + `, + options: [ + { default: { memberTypes: defaultOrder, order: 'alphabetically' } }, + ], + errors: [ + { + messageId: 'incorrectGroupOrder', + data: { + name: 'd', + rank: 'public constructor', + }, + }, + ], + }, + ], +}; + +const sortedWithGroupingInterfacesOption: TSESLint.RunTests< + MessageIds, + Options +> = { + valid: [ + // interfaces option + interface + default order + alphabetically + { + code: ` +interface Foo { + [a: string] : number; + + a : x; + b : x; + c : x; + + a() : void; + b() : void; + c() : void; + + new () : Bar; + + () : Baz; +} + `, + options: [ + { + interfaces: { + memberTypes: ['signature', 'field', 'method', 'constructor'], + order: 'alphabetically', + }, + }, + ], + }, + + // interfaces option + interface + custom order + alphabetically + { + code: ` +interface Foo { + new () : Bar; + + a() : void; + b() : void; + c() : void; + + a : x; + b : x; + c : x; + + [a: string] : number; + () : Baz; +} + `, + options: [ + { + interfaces: { + memberTypes: ['constructor', 'method', 'field'], + order: 'alphabetically', + }, + }, + ], + }, + + // interfaces option + type literal + alphabetically --> Default order applies + { + code: ` +type Foo = { + [a: string] : number; + + c : x; + b : x; + a : x; + + new () : Bar; + + c() : void; + b() : void; + a() : void; + + () : Baz; +} + `, + options: [{ interfaces: { order: 'alphabetically' } }], + }, + + // interfaces option + class + alphabetically --> Default order applies + { + code: ` +class Foo { + public static a: string; + protected static b: string = ""; + private static c: string = ""; + + public d: string = ""; + protected e: string = ""; + private f: string = ""; + + constructor() {} +} + `, + options: [{ interfaces: { order: 'alphabetically' } }], + }, + + // interfaces option + class expression + alphabetically --> Default order applies + { + code: ` +const foo = class Foo { + public static a: string; + protected static b: string = ""; + private static c: string = ""; + + public d: string = ""; + protected e: string = ""; + private f: string = ""; + + constructor() {} +} + `, + options: [{ interfaces: { order: 'alphabetically' } }], + }, + ], + invalid: [ + // default option + interface + wrong order within group and wrong group order + alphabetically + { + code: ` +interface Foo { + [a: string] : number; + + a : x; + b : x; + c : x; + + c() : void; + b() : void; + a() : void; + + () : Baz; + + new () : Bar; +} + `, + options: [ + { default: { memberTypes: defaultOrder, order: 'alphabetically' } }, + ], + errors: [ + { + messageId: 'incorrectGroupOrder', + data: { + name: 'new', + rank: 'method', + }, + }, + ], + }, + ], +}; + +const sortedWithGroupingTypeLiteralsOption: TSESLint.RunTests< + MessageIds, + Options +> = { + valid: [ + // typeLiterals option + interface + alphabetically --> Default order applies + { + code: ` +interface Foo { + [a: string] : number; + + c : x; + b : x; + a : x; + + new () : Bar; + + c() : void; + b() : void; + a() : void; + + () : Baz; +} + `, + options: [{ typeLiterals: { order: 'alphabetically' } }], + }, + + // typeLiterals option + type literal + default order + alphabetically + { + code: ` +type Foo = { + [a: string] : number; + + a : x; + b : x; + c : x; + + a() : void; + b() : void; + c() : void; + + new () : Bar; + + () : Baz; +} + `, + options: [ + { + typeLiterals: { + memberTypes: ['signature', 'field', 'method', 'constructor'], + order: 'alphabetically', + }, + }, + ], + }, + + // typeLiterals option + type literal + custom order + alphabetically + { + code: ` +type Foo = { + new () : Bar; + + a() : void; + b() : void; + c() : void; + + a : x; + b : x; + c : x; + + [a: string] : number; + () : Baz; +} + `, + options: [ + { + typeLiterals: { + memberTypes: ['constructor', 'method', 'field'], + order: 'alphabetically', + }, + }, + ], + }, + + // typeLiterals option + class + alphabetically --> Default order applies + { + code: ` +class Foo { + public static a: string; + protected static b: string = ""; + private static c: string = ""; + + public d: string = ""; + protected e: string = ""; + private f: string = ""; + + constructor() {} +} + `, + options: [{ typeLiterals: { order: 'alphabetically' } }], + }, + + // typeLiterals option + class expression + alphabetically --> Default order applies + { + code: ` +const foo = class Foo { + public static a: string; + protected static b: string = ""; + private static c: string = ""; + + public d: string = ""; + protected e: string = ""; + private f: string = ""; + + constructor() {} +} + `, + options: [{ typeLiterals: { order: 'alphabetically' } }], + }, + ], + invalid: [ + // default option + type literal + wrong order within group and wrong group order + alphabetically + { + code: ` +type Foo = { + [a: string] : number; + + a : x; + b : x; + c : x; + + c() : void; + b() : void; + a() : void; + + () : Baz; + + new () : Bar; +} + `, + options: [ + { default: { memberTypes: defaultOrder, order: 'alphabetically' } }, + ], + errors: [ + { + messageId: 'incorrectGroupOrder', + data: { + name: 'new', + rank: 'method', + }, + }, + ], + }, + ], +}; + +const sortedWithoutGrouping = { + valid: [ + ...sortedWithoutGroupingDefaultOption.valid, + ...sortedWithoutGroupingClassesOption.valid, + ...sortedWithoutGroupingClassExpressionsOption.valid, + ...sortedWithoutGroupingInterfacesOption.valid, + ...sortedWithoutGroupingTypeLiteralsOption.valid, + ], + invalid: [ + ...sortedWithoutGroupingDefaultOption.invalid, + ...sortedWithoutGroupingClassesOption.invalid, + ...sortedWithoutGroupingClassExpressionsOption.invalid, + ...sortedWithoutGroupingInterfacesOption.invalid, + ...sortedWithoutGroupingTypeLiteralsOption.invalid, + ], +}; + +const sortedWithGrouping = { + valid: [ + ...sortedWithGroupingDefaultOption.valid, + ...sortedWithGroupingClassesOption.valid, + ...sortedWithGroupingClassExpressionsOption.valid, + ...sortedWithGroupingInterfacesOption.valid, + ...sortedWithGroupingTypeLiteralsOption.valid, + ], + invalid: [ + ...sortedWithGroupingDefaultOption.invalid, + ...sortedWithGroupingClassesOption.invalid, + ...sortedWithGroupingClassExpressionsOption.invalid, + ...sortedWithGroupingInterfacesOption.invalid, + ...sortedWithGroupingTypeLiteralsOption.invalid, + ], +}; + +ruleTester.run('member-ordering', rule, { + valid: [ + ...grouped.valid, + ...sortedWithoutGrouping.valid, + ...sortedWithGrouping.valid, + ], + invalid: [ + ...grouped.invalid, + ...sortedWithoutGrouping.invalid, + ...sortedWithGrouping.invalid, + ], }); diff --git a/packages/eslint-plugin/tests/rules/method-signature-style.test.ts b/packages/eslint-plugin/tests/rules/method-signature-style.test.ts new file mode 100644 index 000000000000..76003e67d84c --- /dev/null +++ b/packages/eslint-plugin/tests/rules/method-signature-style.test.ts @@ -0,0 +1,111 @@ +import rule from '../../src/rules/method-signature-style'; +import { batchedSingleLineTests, noFormat, RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('method-signature-style', rule, { + valid: [ + ...batchedSingleLineTests({ + code: noFormat` + interface Test { f: (a: string) => number } + interface Test { ['f']: (a: boolean) => void } + interface Test { f: (a: T) => T } + interface Test { ['f']: (a: T, b: T) => T } + interface Test { 'f!': (/* b */ x: any /* c */) => void } + type Test = { readonly f: (a: string) => number } + type Test = { ['f']?: (a: boolean) => void } + type Test = { readonly f?: (a?: T) => T } + type Test = { readonly ['f']?: (a: T, b: T) => T } + `, + }), + ...batchedSingleLineTests({ + options: ['method'], + code: noFormat` + interface Test { f(a: string): number } + interface Test { ['f'](a: boolean): void } + interface Test { f(a: T): T } + interface Test { ['f'](a: T, b: T): T } + interface Test { 'f!'(/* b */ x: any /* c */): void } + type Test = { readonly f(a: string): number } + type Test = { ['f']?(a: boolean): void } + type Test = { readonly f?(a?: T): T } + type Test = { readonly ['f']?(a: T, b: T): T } + `, + }), + ], + invalid: [ + ...batchedSingleLineTests({ + code: noFormat` + interface Test { f(a: string): number } + interface Test { ['f'](a: boolean): void } + interface Test { f(a: T): T } + interface Test { ['f'](a: T, b: T): T } + interface Test { 'f!'(/* b */ x: any /* c */): void } + type Test = { readonly f(a: string): number } + type Test = { ['f']?(a: boolean): void } + type Test = { readonly f?(a?: T): T } + type Test = { readonly ['f']?(a: T, b: T): T } + `, + errors: [ + { messageId: 'errorMethod', line: 2 }, + { messageId: 'errorMethod', line: 3 }, + { messageId: 'errorMethod', line: 4 }, + { messageId: 'errorMethod', line: 5 }, + { messageId: 'errorMethod', line: 6 }, + { messageId: 'errorMethod', line: 7 }, + { messageId: 'errorMethod', line: 8 }, + { messageId: 'errorMethod', line: 9 }, + { messageId: 'errorMethod', line: 10 }, + ], + output: noFormat` + interface Test { f: (a: string) => number } + interface Test { ['f']: (a: boolean) => void } + interface Test { f: (a: T) => T } + interface Test { ['f']: (a: T, b: T) => T } + interface Test { 'f!': (/* b */ x: any /* c */) => void } + type Test = { readonly f: (a: string) => number } + type Test = { ['f']?: (a: boolean) => void } + type Test = { readonly f?: (a?: T) => T } + type Test = { readonly ['f']?: (a: T, b: T) => T } + `, + }), + ...batchedSingleLineTests({ + options: ['method'], + code: noFormat` + interface Test { f: (a: string) => number } + interface Test { ['f']: (a: boolean) => void } + interface Test { f: (a: T) => T } + interface Test { ['f']: (a: T, b: T) => T } + interface Test { 'f!': (/* b */ x: any /* c */) => void } + type Test = { readonly f: (a: string) => number } + type Test = { ['f']?: (a: boolean) => void } + type Test = { readonly f?: (a?: T) => T } + type Test = { readonly ['f']?: (a: T, b: T) => T } + `, + errors: [ + { messageId: 'errorProperty', line: 2 }, + { messageId: 'errorProperty', line: 3 }, + { messageId: 'errorProperty', line: 4 }, + { messageId: 'errorProperty', line: 5 }, + { messageId: 'errorProperty', line: 6 }, + { messageId: 'errorProperty', line: 7 }, + { messageId: 'errorProperty', line: 8 }, + { messageId: 'errorProperty', line: 9 }, + { messageId: 'errorProperty', line: 10 }, + ], + output: noFormat` + interface Test { f(a: string): number } + interface Test { ['f'](a: boolean): void } + interface Test { f(a: T): T } + interface Test { ['f'](a: T, b: T): T } + interface Test { 'f!'(/* b */ x: any /* c */): void } + type Test = { readonly f(a: string): number } + type Test = { ['f']?(a: boolean): void } + type Test = { readonly f?(a?: T): T } + type Test = { readonly ['f']?(a: T, b: T): T } + `, + }), + ], +}); diff --git a/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts b/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts index dde8b938175a..1a5b9c5778cc 100644 --- a/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts +++ b/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts @@ -255,6 +255,118 @@ interface Qux4 { code: 'function quuz4(): (...args: ReadonlyArray) => void {}', options: [{ ignoreRestArgs: true }], }, + { + code: 'type Fred1 = (...args: any[]) => void;', + options: [{ ignoreRestArgs: true }], + }, + { + code: 'type Fred2 = (...args: readonly any[]) => void;', + options: [{ ignoreRestArgs: true }], + }, + { + code: 'type Fred3 = (...args: Array) => void;', + options: [{ ignoreRestArgs: true }], + }, + { + code: 'type Fred4 = (...args: ReadonlyArray) => void;', + options: [{ ignoreRestArgs: true }], + }, + { + code: 'type Corge1 = new (...args: any[]) => void;', + options: [{ ignoreRestArgs: true }], + }, + { + code: 'type Corge2 = new (...args: readonly any[]) => void;', + options: [{ ignoreRestArgs: true }], + }, + { + code: 'type Corge3 = new (...args: Array) => void;', + options: [{ ignoreRestArgs: true }], + }, + { + code: 'type Corge4 = new (...args: ReadonlyArray) => void;', + options: [{ ignoreRestArgs: true }], + }, + { + code: ` +interface Grault1 { + new (...args: any[]): void; +} + `, + options: [{ ignoreRestArgs: true }], + }, + { + code: ` +interface Grault2 { + new (...args: readonly any[]): void; +} + `, + options: [{ ignoreRestArgs: true }], + }, + { + code: ` +interface Grault3 { + new (...args: Array): void; +} + `, + options: [{ ignoreRestArgs: true }], + }, + { + code: ` +interface Grault4 { + new (...args: ReadonlyArray): void; +} + `, + options: [{ ignoreRestArgs: true }], + }, + { + code: ` +interface Garply1 { + f(...args: any[]): void; +} + `, + options: [{ ignoreRestArgs: true }], + }, + { + code: ` +interface Garply2 { + f(...args: readonly any[]): void; +} + `, + options: [{ ignoreRestArgs: true }], + }, + { + code: ` +interface Garply3 { + f(...args: Array): void; +} + `, + options: [{ ignoreRestArgs: true }], + }, + { + code: ` +interface Garply4 { + f(...args: ReadonlyArray): void; +} + `, + options: [{ ignoreRestArgs: true }], + }, + { + code: 'declare function waldo1(...args: any[]): void;', + options: [{ ignoreRestArgs: true }], + }, + { + code: 'declare function waldo2(...args: readonly any[]): void;', + options: [{ ignoreRestArgs: true }], + }, + { + code: 'declare function waldo3(...args: Array): void;', + options: [{ ignoreRestArgs: true }], + }, + { + code: 'declare function waldo4(...args: ReadonlyArray): void;', + options: [{ ignoreRestArgs: true }], + }, ], invalid: ([ { @@ -737,7 +849,7 @@ type obj = { ], }, { - code: `class Foo extends Bar {}`, + code: 'class Foo extends Bar {}', errors: [ { messageId: 'unexpectedAny', @@ -746,11 +858,11 @@ type obj = { suggestions: [ { messageId: 'suggestUnknown', - output: `class Foo extends Bar {}`, + output: 'class Foo extends Bar {}', }, { messageId: 'suggestNever', - output: `class Foo extends Bar {}`, + output: 'class Foo extends Bar {}', }, ], }, @@ -761,18 +873,18 @@ type obj = { suggestions: [ { messageId: 'suggestUnknown', - output: `class Foo extends Bar {}`, + output: 'class Foo extends Bar {}', }, { messageId: 'suggestNever', - output: `class Foo extends Bar {}`, + output: 'class Foo extends Bar {}', }, ], }, ], }, { - code: `abstract class Foo extends Bar {}`, + code: 'abstract class Foo extends Bar {}', errors: [ { messageId: 'unexpectedAny', @@ -781,11 +893,11 @@ type obj = { suggestions: [ { messageId: 'suggestUnknown', - output: `abstract class Foo extends Bar {}`, + output: 'abstract class Foo extends Bar {}', }, { messageId: 'suggestNever', - output: `abstract class Foo extends Bar {}`, + output: 'abstract class Foo extends Bar {}', }, ], }, @@ -796,18 +908,18 @@ type obj = { suggestions: [ { messageId: 'suggestUnknown', - output: `abstract class Foo extends Bar {}`, + output: 'abstract class Foo extends Bar {}', }, { messageId: 'suggestNever', - output: `abstract class Foo extends Bar {}`, + output: 'abstract class Foo extends Bar {}', }, ], }, ], }, { - code: `abstract class Foo implements Bar, Baz {}`, + code: 'abstract class Foo implements Bar, Baz {}', errors: [ { messageId: 'unexpectedAny', @@ -816,11 +928,13 @@ type obj = { suggestions: [ { messageId: 'suggestUnknown', - output: `abstract class Foo implements Bar, Baz {}`, + output: + 'abstract class Foo implements Bar, Baz {}', }, { messageId: 'suggestNever', - output: `abstract class Foo implements Bar, Baz {}`, + output: + 'abstract class Foo implements Bar, Baz {}', }, ], }, @@ -831,11 +945,13 @@ type obj = { suggestions: [ { messageId: 'suggestUnknown', - output: `abstract class Foo implements Bar, Baz {}`, + output: + 'abstract class Foo implements Bar, Baz {}', }, { messageId: 'suggestNever', - output: `abstract class Foo implements Bar, Baz {}`, + output: + 'abstract class Foo implements Bar, Baz {}', }, ], }, @@ -846,18 +962,20 @@ type obj = { suggestions: [ { messageId: 'suggestUnknown', - output: `abstract class Foo implements Bar, Baz {}`, + output: + 'abstract class Foo implements Bar, Baz {}', }, { messageId: 'suggestNever', - output: `abstract class Foo implements Bar, Baz {}`, + output: + 'abstract class Foo implements Bar, Baz {}', }, ], }, ], }, { - code: `new Foo()`, + code: 'new Foo()', errors: [ { messageId: 'unexpectedAny', @@ -867,7 +985,7 @@ type obj = { ], }, { - code: `Foo()`, + code: 'Foo()', errors: [ { messageId: 'unexpectedAny', @@ -943,7 +1061,7 @@ const test = >() => {}; ], }, { - code: `type Any = any;`, + code: 'type Any = any;', options: [{ ignoreRestArgs: true }], errors: [ { @@ -954,7 +1072,7 @@ const test = >() => {}; ], }, { - code: `function foo5(...args: any) {}`, + code: 'function foo5(...args: any) {}', options: [{ ignoreRestArgs: true }], errors: [ { @@ -965,7 +1083,7 @@ const test = >() => {}; ], }, { - code: `const bar5 = function (...args: any) {}`, + code: 'const bar5 = function (...args: any) {}', options: [{ ignoreRestArgs: true }], errors: [ { @@ -976,7 +1094,7 @@ const test = >() => {}; ], }, { - code: `const baz5 = (...args: any) => {}`, + code: 'const baz5 = (...args: any) => {}', options: [{ ignoreRestArgs: true }], errors: [ { @@ -987,7 +1105,7 @@ const test = >() => {}; ], }, { - code: `interface Qux5 { (...args: any): void; }`, + code: 'interface Qux5 { (...args: any): void; }', options: [{ ignoreRestArgs: true }], errors: [ { @@ -998,7 +1116,7 @@ const test = >() => {}; ], }, { - code: `function quux5(fn: (...args: any) => void): void {}`, + code: 'function quux5(fn: (...args: any) => void): void {}', options: [{ ignoreRestArgs: true }], errors: [ { @@ -1009,7 +1127,7 @@ const test = >() => {}; ], }, { - code: `function quuz5(): ((...args: any) => void) {}`, + code: 'function quuz5(): ((...args: any) => void) {}', options: [{ ignoreRestArgs: true }], errors: [ { @@ -1019,6 +1137,61 @@ const test = >() => {}; }, ], }, + { + code: 'type Fred5 = (...args: any) => void;', + options: [{ ignoreRestArgs: true }], + errors: [ + { + messageId: 'unexpectedAny', + line: 1, + column: 24, + }, + ], + }, + { + code: 'type Corge5 = new (...args: any) => void;', + options: [{ ignoreRestArgs: true }], + errors: [ + { + messageId: 'unexpectedAny', + line: 1, + column: 29, + }, + ], + }, + { + code: 'interface Grault5 { new (...args: any): void; }', + options: [{ ignoreRestArgs: true }], + errors: [ + { + messageId: 'unexpectedAny', + line: 1, + column: 35, + }, + ], + }, + { + code: 'interface Garply5 { f(...args: any): void; }', + options: [{ ignoreRestArgs: true }], + errors: [ + { + messageId: 'unexpectedAny', + line: 1, + column: 32, + }, + ], + }, + { + code: 'declare function waldo5(...args: any): void;', + options: [{ ignoreRestArgs: true }], + errors: [ + { + messageId: 'unexpectedAny', + line: 1, + column: 34, + }, + ], + }, ] as InvalidTestCase[]).reduce((acc, testCase) => { const suggestions = (code: string): SuggestionOutput[] => [ { diff --git a/packages/eslint-plugin/tests/rules/no-throw-literal.test.ts b/packages/eslint-plugin/tests/rules/no-throw-literal.test.ts index 1367c6a8f695..7d7b95acbb2e 100644 --- a/packages/eslint-plugin/tests/rules/no-throw-literal.test.ts +++ b/packages/eslint-plugin/tests/rules/no-throw-literal.test.ts @@ -321,5 +321,36 @@ throw new CustomError(); }, ], }, + { + code: ` +function foo() { + const res: T; + throw res; +} + `, + errors: [ + { + messageId: 'object', + line: 4, + column: 9, + }, + ], + }, + { + code: ` +function foo(fn: () => Promise) { + const promise = fn(); + const res = promise.then(() => {}).catch(() => {}); + throw res; +} + `, + errors: [ + { + messageId: 'object', + line: 5, + column: 9, + }, + ], + }, ], }); diff --git a/packages/experimental-utils/src/ts-eslint/Linter.ts b/packages/experimental-utils/src/ts-eslint/Linter.ts index 0233f2f8408d..5363059b14d7 100644 --- a/packages/experimental-utils/src/ts-eslint/Linter.ts +++ b/packages/experimental-utils/src/ts-eslint/Linter.ts @@ -81,6 +81,12 @@ namespace Linter { reportUnusedDisableDirectives?: boolean; } + export interface LintSuggestion { + desc: string; + fix: RuleFix; + messageId?: string; + } + export interface LintMessage { column: number; line: number; @@ -88,11 +94,13 @@ namespace Linter { endLine?: number; ruleId: string | null; message: string; + messageId?: string; nodeType: string; fatal?: true; severity: Severity; fix?: RuleFix; source: string | null; + suggestions?: LintSuggestion[]; } export interface FixOptions extends LintOptions { diff --git a/packages/experimental-utils/src/ts-eslint/Rule.ts b/packages/experimental-utils/src/ts-eslint/Rule.ts index 1f74150292eb..8301947ee6bd 100644 --- a/packages/experimental-utils/src/ts-eslint/Rule.ts +++ b/packages/experimental-utils/src/ts-eslint/Rule.ts @@ -129,8 +129,6 @@ interface ReportDescriptorBase { */ messageId: TMessageIds; // we disallow this because it's much better to use messageIds for reusable errors that are easily testable - // message?: string; - // suggestions instead have this property that works the same, but again it's much better to use messageIds // desc?: string; } interface ReportDescriptorWithSuggestion diff --git a/packages/typescript-estree/src/create-program/WatchCompilerHostOfConfigFile.ts b/packages/typescript-estree/src/create-program/WatchCompilerHostOfConfigFile.ts index 471a6d6b6fe6..565f58417c84 100644 --- a/packages/typescript-estree/src/create-program/WatchCompilerHostOfConfigFile.ts +++ b/packages/typescript-estree/src/create-program/WatchCompilerHostOfConfigFile.ts @@ -32,6 +32,7 @@ interface WatchCompilerHostOfConfigFile onCachedDirectoryStructureHostCreate( host: CachedDirectoryStructureHost, ): void; + extraFileExtensions?: readonly ts.FileExtensionInfo[]; } export { WatchCompilerHostOfConfigFile }; diff --git a/packages/typescript-estree/src/create-program/createWatchProgram.ts b/packages/typescript-estree/src/create-program/createWatchProgram.ts index 682e1d7c731a..cf31a5c383d3 100644 --- a/packages/typescript-estree/src/create-program/createWatchProgram.ts +++ b/packages/typescript-estree/src/create-program/createWatchProgram.ts @@ -314,6 +314,14 @@ function createWatchProgram( ); oldOnDirectoryStructureHostCreate(host); }; + // This works only on 3.9 + watchCompilerHost.extraFileExtensions = extra.extraFileExtensions.map( + extension => ({ + extension, + isMixedContent: true, + scriptKind: ts.ScriptKind.Deferred, + }), + ); watchCompilerHost.trace = log; // Since we don't want to asynchronously update program we want to disable timeout methods diff --git a/packages/typescript-estree/src/ts-estree/ts-estree.ts b/packages/typescript-estree/src/ts-estree/ts-estree.ts index cbf7787c8055..24486b974b30 100644 --- a/packages/typescript-estree/src/ts-estree/ts-estree.ts +++ b/packages/typescript-estree/src/ts-estree/ts-estree.ts @@ -308,11 +308,9 @@ export type BindingPattern = ArrayPattern | ObjectPattern; export type BindingName = BindingPattern | Identifier; export type ClassElement = | ClassProperty - | FunctionExpression | MethodDefinition | TSAbstractClassProperty | TSAbstractMethodDefinition - | TSEmptyBodyFunctionExpression | TSIndexSignature; export type ClassProperty = | ClassPropertyComputedName