diff --git a/packages/eslint-plugin/docs/rules/member-ordering.md b/packages/eslint-plugin/docs/rules/member-ordering.md index ab54967aa3a..6c476e4f411 100644 --- a/packages/eslint-plugin/docs/rules/member-ordering.md +++ b/packages/eslint-plugin/docs/rules/member-ordering.md @@ -2,318 +2,58 @@ Require a consistent member declaration order. -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 and ordered. -### 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 SortedOrderConfig = { - memberTypes?: MemberType[] | 'never'; - order: 'alphabetically' | 'alphabetically-case-insensitive' | 'as-written'; -}; - -type OrderConfig = MemberType[] | SortedOrderConfig | 'never'; - -type Options = { +interface Options { default?: OrderConfig; classes?: OrderConfig; classExpressions?: OrderConfig; interfaces?: OrderConfig; typeLiterals?: OrderConfig; -}; -``` - -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: - -```jsonc -[ - // Index signature - "signature", - - // Fields - "public-static-field", - "protected-static-field", - "private-static-field", - "public-decorated-field", - "protected-decorated-field", - "private-decorated-field", - "public-instance-field", - "protected-instance-field", - "private-instance-field", - "public-abstract-field", - "protected-abstract-field", - "private-abstract-field", - - // Constructors - "public-constructor", - "protected-constructor", - "private-constructor", - - // Getters - "public-static-get", - "protected-static-get", - "private-static-get", - - "public-decorated-get", - "protected-decorated-get", - "private-decorated-get", - - "public-instance-get", - "protected-instance-get", - "private-instance-get", - - "public-abstract-get", - "protected-abstract-get", - "private-abstract-get", - - "public-get", - "protected-get", - "private-get", - - "static-get", - "instance-get", - "abstract-get", - - "decorated-get", - - "get", - - // Setters - "public-static-set", - "protected-static-set", - "private-static-set", - - "public-decorated-set", - "protected-decorated-set", - "private-decorated-set", - - "public-instance-set", - "protected-instance-set", - "private-instance-set", - - "public-abstract-set", - "protected-abstract-set", - "private-abstract-set", - - "public-set", - "protected-set", - "private-set", - - "static-set", - "instance-set", - "abstract-set", - - "decorated-set", - - "set", - - // Methods - "public-static-method", - "protected-static-method", - "private-static-method", - "public-decorated-method", - "protected-decorated-method", - "private-decorated-method", - "public-instance-method", - "protected-instance-method", - "private-instance-method", - "public-abstract-method", - "protected-abstract-method", - "private-abstract-method" -] -``` - -Note: If you only specify some of the possible types, the non-specified ones can have any particular order. This means that they can be placed before, within or after the specified types and the linter won't complain about it. - -### Member group types (with accessibility, ignoring scope) - -It is also possible to group member types by their accessibility (`static`, `instance`, `abstract`), ignoring their scope. - -```jsonc -[ - // Index signature - // No accessibility for index signature. See above. - - // Fields - "public-field", // = ["public-static-field", "public-instance-field"] - "protected-field", // = ["protected-static-field", "protected-instance-field"] - "private-field", // = ["private-static-field", "private-instance-field"] - - // Constructors - // Only the accessibility of constructors is configurable. See below. - - // Getters - "public-get", // = ["public-static-get", "public-instance-get"] - "protected-get", // = ["protected-static-get", "protected-instance-get"] - "private-get", // = ["private-static-get", "private-instance-get"] - - // Setters - "public-set", // = ["public-static-set", "public-instance-set"] - "protected-set", // = ["protected-static-set", "protected-instance-set"] - "private-set", // = ["private-static-set", "private-instance-set"] - - // Methods - "public-method", // = ["public-static-method", "public-instance-method"] - "protected-method", // = ["protected-static-method", "protected-instance-method"] - "private-method" // = ["private-static-method", "private-instance-method"] -] -``` - -### Member group types (with accessibility and a decorator) - -It is also possible to group methods or fields with a decorator separately, optionally specifying -their accessibility. - -```jsonc -[ - // Index signature - // No decorators for index signature. - - // Fields - "public-decorated-field", - "protected-decorated-field", - "private-decorated-field", - - "decorated-field", // = ["public-decorated-field", "protected-decorated-field", "private-decorated-field"] - - // Constructors - // There are no decorators for constructors. - - // Getters - "public-decorated-get", - "protected-decorated-get", - "private-decorated-get", - - "decorated-get" // = ["public-decorated-get", "protected-decorated-get", "private-decorated-get"] - - // Setters - "public-decorated-set", - "protected-decorated-set", - "private-decorated-set", - - "decorated-set" // = ["public-decorated-set", "protected-decorated-set", "private-decorated-set"] - - // Methods - "public-decorated-method", - "protected-decorated-method", - "private-decorated-method", - - "decorated-method" // = ["public-decorated-method", "protected-decorated-method", "private-decorated-method"] -] -``` - -### Member group types (with scope, ignoring accessibility) - -Another option is to group the member types by their scope (`public`, `protected`, `private`), ignoring their accessibility. - -```jsonc -[ - // Index signature - // No scope for index signature. See above. - - // Fields - "static-field", // = ["public-static-field", "protected-static-field", "private-static-field"] - "instance-field", // = ["public-instance-field", "protected-instance-field", "private-instance-field"] - "abstract-field", // = ["public-abstract-field", "protected-abstract-field", "private-abstract-field"] - - // Constructors - "constructor", // = ["public-constructor", "protected-constructor", "private-constructor"] +} - // Getters - "static-get", // = ["public-static-get", "protected-static-get", "private-static-get"] - "instance-get", // = ["public-instance-get", "protected-instance-get", "private-instance-get"] - "abstract-get" // = ["public-abstract-get", "protected-abstract-get", "private-abstract-get"] +type OrderConfig = MemberType[] | SortedOrderConfig | 'never'; - // Setters - "static-set", // = ["public-static-set", "protected-static-set", "private-static-set"] - "instance-set", // = ["public-instance-set", "protected-instance-set", "private-instance-set"] - "abstract-set" // = ["public-abstract-set", "protected-abstract-set", "private-abstract-set"] +interface SortedOrderConfig { + memberTypes?: MemberType[] | 'never'; + order: 'alphabetically' | 'alphabetically-case-insensitive' | 'as-written'; +} - // Methods - "static-method", // = ["public-static-method", "protected-static-method", "private-static-method"] - "instance-method", // = ["public-instance-method", "protected-instance-method", "private-instance-method"] - "abstract-method" // = ["public-abstract-method", "protected-abstract-method", "private-abstract-method"] -] +// See below for the more specific MemberType strings +type MemberType = string | string[]; ``` -### Member group types (with scope and accessibility) - -The third grouping option is to ignore both scope and accessibility. - -```jsonc -[ - // Index signature - // No grouping for index signature. See above. - - // Fields - "field", // = ["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"] - - // Constructors - // Only the accessibility of constructors is configurable. See above. - - // Getters - "get" // = ["public-static-get", "protected-static-get", "private-static-get", "public-instance-get", "protected-instance-get", "private-instance-get", - // "public-abstract-get", "protected-abstract-get", "private-abstract-get"] - - // Setters - "set" // = ["public-static-set", "protected-static-set", "private-static-set", "public-instance-set", "protected-instance-set", "private-instance-set", - // "public-abstract-set", "protected-abstract-set", "private-abstract-set"] - - // Methods - "method" // = ["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"] -] -``` +You can configure `OrderConfig` options for: -### Grouping different member types at the same rank +- **`default`**: all constructs (used as a fallback) +- **`classes`**?: override ordering specifically for classes +- **`classExpressions`**?: override ordering specifically for class expressions +- **`interfaces`**?: override ordering specifically for interfaces +- **`typeLiterals`**?: override ordering specifically for type literals -It is also possible to group different member types at the same rank. +The `OrderConfig` settings for each kind of construct may configure sorting on one or both two levels: -```jsonc -[ - // Index signature - "signature", +- **`memberType`**: organizing on member type groups such as methods vs. properties +- **`order`**: organizing based on member names, such as alphabetically - // Fields - "field", +### Groups - // Constructors - "constructor", +You can define many different groups based on different attributes of members. +The supported member attributes are, in order: - // Getters and Setters at the same rank - ["get", "set"], +- **Accessibility** (`'public' | 'protected' | 'private'`) +- **Decoration** (`'decorated'`): Whether the member has an explicit accessibility decorator +- **Kind** (`'call-signature' | 'constructor' | 'field' | 'get' | 'method' | 'set' | 'signature'`) - // Methods - "method" -] -``` +Member attributes may be joined with a `'-'` to combine into more specific groups. +For example, `'public-field'` would come before `'private-field'`. ### Default configuration @@ -451,19 +191,39 @@ The default configuration looks as follows: } ``` -Note: The default configuration contains member group types which contain other member types (see above). This is intentional to provide better error messages. +:::note +The default configuration contains member group types which contain other member types. +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. +:::tip +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 +### General Order on All Constructs -Note: The `default` options are overwritten in these examples. +This config specifies the order for all constructs. +It ignores member types other than signatures, methods, constructors, and fields. +It also ignores accessibility and scope. + +```jsonc +// .eslintrc.json +{ + "rules": { + "@typescript-eslint/no-non-null-assertion": [ + "error", + { "default": ["signature", "method", "constructor", "field"] } + ] + } +} +``` -#### Configuration: `{ "default": ["signature", "method", "constructor", "field"] }` + -##### Incorrect examples +#### ❌ Incorrect ```ts interface Foo { @@ -477,8 +237,6 @@ interface Foo { } ``` -Note: Wrong order. - ```ts type Foo = { B: string; // -> field @@ -491,8 +249,6 @@ type Foo = { }; ``` -Note: Not all specified member types have to exist. - ```ts class Foo { private C: string; // -> field @@ -508,8 +264,6 @@ class Foo { } ``` -Note: Accessibility or scope are ignored with this configuration. - ```ts const Foo = class { private C: string; // -> field @@ -526,9 +280,7 @@ const Foo = class { }; ``` -Note: Not all members have to be grouped to find rule violations. - -##### Correct examples +#### ✅ Correct ```ts interface Foo { @@ -584,11 +336,29 @@ const Foo = class { }; ``` -#### Configuration: `{ "default": ["public-instance-method", "public-static-field"] }` +### Classes + +#### Public Instance Methods Before Public Static Fields + +This config specifies that public instance methods should come first before public static fields. +Everything else can be placed anywhere. +It doesn't apply to interfaces or type literals as accessibility and scope are not part of them. + +```jsonc +// .eslintrc.json +{ + "rules": { + "@typescript-eslint/no-non-null-assertion": [ + "error", + { "default": ["public-instance-method", "public-static-field"] } + ] + } +} +``` -Note: This configuration does not apply to interfaces/type literals as accessibility and scope are not part of interfaces/type literals. + -##### Incorrect examples +##### ❌ Incorrect ```ts class Foo { @@ -608,8 +378,6 @@ class Foo { } ``` -Note: Public instance methods should come first before public static fields. Everything else can be placed anywhere. - ```ts const Foo = class { private C: string; // (irrelevant) @@ -628,9 +396,7 @@ const Foo = class { }; ``` -Note: Public instance methods should come first before public static fields. Everything else can be placed anywhere. - -##### Correct examples +##### ✅ Correct ```ts class Foo { @@ -668,11 +434,25 @@ const Foo = class { }; ``` -#### Configuration: `{ "default": ["public-static-field", "static-field", "instance-field"] }` +#### Static Fields Before Instance Fields + +This config specifies that static fields should come before instance fields, with public static fields first. +It doesn't apply to interfaces or type literals as accessibility and scope are not part of them. + +```jsonc +{ + "rules": { + "@typescript-eslint/no-non-null-assertion": [ + "error", + { "default": ["public-static-field", "static-field", "instance-field"] } + ] + } +} +``` -Note: This configuration does not apply to interfaces/type literals as accessibility and scope are not part of interfaces/type literals. + -##### Incorrect examples +##### ❌ Incorrect ```ts class Foo { @@ -688,30 +468,26 @@ class Foo { } ``` -Note: Public static fields should come first, followed by static fields and instance fields. - ```ts const foo = class { - public T(): void {} // (irrelevant) + public T(): void {} // method (irrelevant) private static B: string; // -> static field - constructor() {} // (irrelevant) + constructor() {} // constructor (irrelevant) private E: string; // -> instance field protected static C: string; // -> static field private static D: string; // -> static field - [Z: string]: any; // (irrelevant) + [Z: string]: any; // signature (irrelevant) public static A: string; // -> public static field }; ``` -Note: Public static fields should come first, followed by static fields and instance fields. - -##### Correct examples +##### ✅ Correct ```ts class Foo { @@ -722,16 +498,18 @@ class Foo { private static D: string; // -> static field private E: string; // -> instance field + + [Z: string]: any; // (irrelevant) } ``` ```ts const foo = class { - [Z: string]: any; // -> signature + [Z: string]: any; // -> signature (irrelevant) public static A: string; // -> public static field - constructor() {} // -> constructor + constructor() {} // -> constructor (irrelevant) private static B: string; // -> static field protected static C: string; // -> static field @@ -739,19 +517,31 @@ const foo = class { private E: string; // -> instance field - public T(): void {} // -> method + public T(): void {} // -> method (irrelevant) }; ``` -### Custom `classes` configuration +#### Class Declarations -Note: If this is not set, the `default` will automatically be applied to classes as well. If a `classes` configuration is provided, only this configuration will be used for `classes` (i.e. nothing will be merged with `default`). +This config only specifies an order for classes: methods, then the constructor, then fields. +It does not apply to class expressions (use `classExpressions` for them). +Default settings will be used for class declarations and all other syntax constructs other than class declarations. -Note: The configuration for `classes` does not apply to class expressions (use `classExpressions` for them). +```jsonc +// .eslintrc.json +{ + "rules": { + "@typescript-eslint/no-non-null-assertion": [ + "error", + { "classes": ["method", "constructor", "field"] } + ] + } +} +``` -#### Configuration: `{ "classes": ["method", "constructor", "field"] }` + -##### Incorrect example +##### ❌ Incorrect ```ts class Foo { @@ -766,7 +556,7 @@ class Foo { } ``` -##### Correct example +##### ✅ Correct ```ts class Foo { @@ -781,333 +571,594 @@ class Foo { } ``` -#### Configuration: `{ "classes": ["public-instance-method", "public-static-field"] }` +#### Class Expressions + +This config only specifies an order for classes expressions: methods, then the constructor, then fields. +It does not apply to class declarations (use `classes` for them). +Default settings will be used for class declarations and all other syntax constructs other than class expressions. + +```jsonc +// .eslintrc.json +{ + "rules": { + "@typescript-eslint/no-non-null-assertion": [ + "error", + { "classExpressions": ["method", "constructor", "field"] } + ] + } +} +``` + + -##### Incorrect example +##### ❌ Incorrect ```ts -class Foo { - private C: string; // (irrelevant) +const foo = class { + private C: string; // -> field + public D: string; // -> field + protected static E: string; // -> field - public D: string; // (irrelevant) + constructor() {} // -> constructor - public static E: string; // -> public static field + public static A(): void {} // -> method + public B(): void {} // -> method +}; +``` - constructor() {} // (irrelevant) +##### ✅ Correct - public static A(): void {} // (irrelevant) +```ts +const foo = class { + public static A(): void {} // -> method + public B(): void {} // -> method - public B(): void {} // -> public instance method + constructor() {} // -> constructor + + private C: string; // -> field + public D: string; // -> field + protected static E: string; // -> field +}; +``` + +### Interfaces + +This config only specifies an order for interfaces: signatures, then methods, then constructors, then fields. +It does not apply to type literals (use `typeLiterals` for them). +Default settings will be used for type literals and all other syntax constructs other than class expressions. + +:::note +These member types are the only ones allowed for `interfaces`. +::: + +```jsonc +// .eslintrc.json +{ + "rules": { + "@typescript-eslint/no-non-null-assertion": [ + "error", + { "interfaces": ["signature", "method", "constructor", "field"] } + ] + } } ``` -##### Correct example + + +#### ❌ Incorrect ```ts -class Foo { - private C: string; // (irrelevant) +interface Foo { + B: string; // -> field - public D: string; // (irrelevant) + new (); // -> constructor - public B(): void {} // -> public instance method + A(): void; // -> method - constructor() {} // (irrelevant) + [Z: string]: any; // -> signature +} +``` - public static A(): void {} // (irrelevant) +#### ✅ Correct - public static E: string; // -> public static field +```ts +interface Foo { + [Z: string]: any; // -> signature + + A(): void; // -> method + + new (); // -> constructor + + B: string; // -> field } ``` -### Custom `classExpressions` configuration +### Type Literals -Note: If this is not set, the `default` will automatically be applied to classes expressions as well. If a `classExpressions` configuration is provided, only this configuration will be used for `classExpressions` (i.e. nothing will be merged with `default`). +This config only specifies an order for type literals: signatures, then methods, then constructors, then fields. +It does not apply to interfaces (use `interfaces` for them). +Default settings will be used for interfaces and all other syntax constructs other than class expressions. -Note: The configuration for `classExpressions` does not apply to classes (use `classes` for them). +:::note +These member types are the only ones allowed for `typeLiterals`. +::: + +```jsonc +// .eslintrc.json +{ + "rules": { + "@typescript-eslint/no-non-null-assertion": [ + "error", + { "typeLiterals": ["signature", "method", "constructor", "field"] } + ] + } +} +``` -#### Configuration: `{ "classExpressions": ["method", "constructor", "field"] }` + -##### Incorrect example +#### ❌ Incorrect ```ts -const foo = class { - private C: string; // -> field - public D: string; // -> field - protected static E: string; // -> field +type Foo = { + B: string; // -> field - constructor() {} // -> constructor + A(): void; // -> method - public static A(): void {} // -> method - public B(): void {} // -> method + new (); // -> constructor + + [Z: string]: any; // -> signature }; ``` -##### Correct example +#### ✅ Correct ```ts -const foo = class { - public static A(): void {} // -> method - public B(): void {} // -> method +type Foo = { + [Z: string]: any; // -> signature - constructor() {} // -> constructor + A(): void; // -> method - private C: string; // -> field - public D: string; // -> field - protected static E: string; // -> field + new (); // -> constructor + + B: string; // -> field }; ``` -#### Configuration: `{ "classExpressions": ["public-instance-method", "public-static-field"] }` +### Sorting Options + +#### Sorting Alphabetically Within Member Groups + +This config specifies that within each `memberTypes` group, members are in an alphabetic case-sensitive order. +You can copy and paste the default order from [Default Configuration](#default-configuration). + +```jsonc +// .eslintrc.json +{ + "rules": { + "@typescript-eslint/no-non-null-assertion": [ + "error", + { + "default": { + "memberTypes": [ + /* */ + ], + "order": "alphabetically" + } + } + ] + } +} +``` + + -##### Incorrect example +##### ❌ Incorrect ```ts -const foo = class { - private C: string; // (irrelevant) +interface Foo { + a: x; + B: x; + c: x; - public D: string; // (irrelevant) + B(): void; + c(): void; + a(): void; +} +``` - public static E: string; // -> public static field +##### ✅ Correct - constructor() {} // (irrelevant) +```ts +interface Foo { + B: x; + a: x; + c: x; - public static A(): void {} // (irrelevant) + B(): void; + a(): void; + c(): void; +} +``` - public B(): void {} // -> public instance method -}; +#### Sorting Alphabetically Case Insensitive Within Member Groups + +This config specifies that within each `memberTypes` group, members are in an alphabetic case-sensitive order. +You can copy and paste the default order from [Default Configuration](#default-configuration). + +```jsonc +// .eslintrc.json +{ + "rules": { + "@typescript-eslint/no-non-null-assertion": [ + "error", + { + "default": { + "memberTypes": [ + /* */ + ], + "order": "alphabetically-case-insensitive" + } + } + ] + } +} ``` -##### Correct example + + +##### ❌ Incorrect ```ts -const foo = class { - private C: string; // (irrelevant) +interface Foo { + B: x; + a: x; + c: x; - public D: string; // (irrelevant) + B(): void; + c(): void; + a(): void; +} +``` - public B(): void {} // -> public instance method +##### ✅ Correct - public static E: string; // -> public static field +```ts +interface Foo { + a: x; + B: x; + c: x; - constructor() {} // (irrelevant) + a(): void; + B(): void; + c(): void; +} +``` - public static A(): void {} // (irrelevant) -}; +#### Sorting Alphabetically Ignoring Member Groups + +This config specifies that members are all sorted in an alphabetic case-sensitive order. +It ignores any member group types completely by specifying `"never"` for `memberTypes`. + +```jsonc +// .eslintrc.json +{ + "rules": { + "@typescript-eslint/no-non-null-assertion": [ + "error", + { "default": { "memberTypes": "never", "order": "alphabetically" } } + ] + } +} +``` + + + +##### ❌ Incorrect + +```ts +interface Foo { + static c = 0; + b(): void; + a: boolean; + + [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) +} +``` + +##### ✅ Correct + +```ts +interface Foo { + a: boolean; + b(): void; + static c = 0; + + [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) +} ``` -### Custom `interfaces` configuration +## All Supported Options + +### Member Types (Granular Form) -Note: If this is not set, the `default` will automatically be applied to classes expressions as well. If a `interfaces` configuration is provided, only this configuration will be used for `interfaces` (i.e. nothing will be merged with `default`). +There are multiple ways to specify the member types. +The most explicit and granular form is the following: + +```jsonc +[ + // Index signature + "signature", -Note: The configuration for `interfaces` only allows a limited set of member types: `signature`, `field`, `constructor` and `method`. + // Fields + "public-static-field", + "protected-static-field", + "private-static-field", + "public-decorated-field", + "protected-decorated-field", + "private-decorated-field", + "public-instance-field", + "protected-instance-field", + "private-instance-field", + "public-abstract-field", + "protected-abstract-field", + "private-abstract-field", -Note: The configuration for `interfaces` does not apply to type literals (use `typeLiterals` for them). + // Constructors + "public-constructor", + "protected-constructor", + "private-constructor", -#### Configuration: `{ "interfaces": ["signature", "method", "constructor", "field"] }` + // Getters + "public-static-get", + "protected-static-get", + "private-static-get", -##### Incorrect example + "public-decorated-get", + "protected-decorated-get", + "private-decorated-get", -```ts -interface Foo { - B: string; // -> field + "public-instance-get", + "protected-instance-get", + "private-instance-get", - new (); // -> constructor + "public-abstract-get", + "protected-abstract-get", + "private-abstract-get", - A(): void; // -> method + "public-get", + "protected-get", + "private-get", - [Z: string]: any; // -> signature -} -``` + "static-get", + "instance-get", + "abstract-get", -##### Correct example + "decorated-get", -```ts -interface Foo { - [Z: string]: any; // -> signature + "get", - A(): void; // -> method + // Setters + "public-static-set", + "protected-static-set", + "private-static-set", - new (); // -> constructor + "public-decorated-set", + "protected-decorated-set", + "private-decorated-set", - B: string; // -> field -} -``` + "public-instance-set", + "protected-instance-set", + "private-instance-set", -### Custom `typeLiterals` configuration + "public-abstract-set", + "protected-abstract-set", + "private-abstract-set", -Note: If this is not set, the `default` will automatically be applied to classes expressions as well. If a `typeLiterals` configuration is provided, only this configuration will be used for `typeLiterals` (i.e. nothing will be merged with `default`). + "public-set", + "protected-set", + "private-set", -Note: The configuration for `typeLiterals` only allows a limited set of member types: `signature`, `field`, `constructor` and `method`. + "static-set", + "instance-set", + "abstract-set", -Note: The configuration for `typeLiterals` does not apply to interfaces (use `interfaces` for them). + "decorated-set", -#### Configuration: `{ "typeLiterals": ["signature", "method", "constructor", "field"] }` + "set", -##### Incorrect example + // Methods + "public-static-method", + "protected-static-method", + "private-static-method", + "public-decorated-method", + "protected-decorated-method", + "private-decorated-method", + "public-instance-method", + "protected-instance-method", + "private-instance-method", + "public-abstract-method", + "protected-abstract-method", + "private-abstract-method" +] +``` -```ts -type Foo = { - B: string; // -> field +:::note +If you only specify some of the possible types, the non-specified ones can have any particular order. +This means that they can be placed before, within or after the specified types and the linter won't complain about it. +::: - A(): void; // -> method +### Member Group Types (With Accessibility, Ignoring Scope) - new (); // -> constructor +It is also possible to group member types by their accessibility (`static`, `instance`, `abstract`), ignoring their scope. - [Z: string]: any; // -> signature -}; -``` +```jsonc +[ + // Index signature + // No accessibility for index signature. -##### Correct example + // Fields + "public-field", // = ["public-static-field", "public-instance-field"] + "protected-field", // = ["protected-static-field", "protected-instance-field"] + "private-field", // = ["private-static-field", "private-instance-field"] -```ts -type Foo = { - [Z: string]: any; // -> signature + // Constructors + // Only the accessibility of constructors is configurable. See below. - A(): void; // -> method + // Getters + "public-get", // = ["public-static-get", "public-instance-get"] + "protected-get", // = ["protected-static-get", "protected-instance-get"] + "private-get", // = ["private-static-get", "private-instance-get"] - new (); // -> constructor + // Setters + "public-set", // = ["public-static-set", "public-instance-set"] + "protected-set", // = ["protected-static-set", "protected-instance-set"] + "private-set", // = ["private-static-set", "private-instance-set"] - B: string; // -> field -}; + // Methods + "public-method", // = ["public-static-method", "public-instance-method"] + "protected-method", // = ["protected-static-method", "protected-instance-method"] + "private-method" // = ["private-static-method", "private-instance-method"] +] ``` -### Sorting alphabetically within member groups - -It is possible to sort all members within a group alphabetically. +### Member Group Types (With Accessibility and a Decorator) -#### Configuration: `{ "default": { "memberTypes": , "order": "alphabetically" } }` +It is also possible to group methods or fields with a decorator separately, optionally specifying +their accessibility. -This will apply the default order (see above) and enforce an alphabetic case-sensitive order within each group. +```jsonc +[ + // Index signature + // No decorators for index signature. -##### Incorrect examples + // Fields + "public-decorated-field", + "protected-decorated-field", + "private-decorated-field", -```ts -interface Foo { - B: x; - a: x; - c: x; + "decorated-field", // = ["public-decorated-field", "protected-decorated-field", "private-decorated-field"] - new (): Bar; - (): Baz; + // Constructors + // There are no decorators for constructors. - B(): void; - a(): void; - c(): void; + // Getters + "public-decorated-get", + "protected-decorated-get", + "private-decorated-get", - // Wrong group order, should be placed before all field definitions - [a: string]: number; -} -``` + "decorated-get" // = ["public-decorated-get", "protected-decorated-get", "private-decorated-get"] -```ts -interface Foo { - [a: string]: number; + // Setters + "public-decorated-set", + "protected-decorated-set", + "private-decorated-set", - B: x; - a: x; - c: x; + "decorated-set" // = ["public-decorated-set", "protected-decorated-set", "private-decorated-set"] - new (): Bar; - (): Baz; + // Methods + "public-decorated-method", + "protected-decorated-method", + "private-decorated-method", - // Wrong alphabetic order within group - c(): void; - B(): void; - a(): void; -} + "decorated-method" // = ["public-decorated-method", "protected-decorated-method", "private-decorated-method"] +] ``` -### Sorting alphabetically while ignoring member groups +### Member Group Types (With Scope, Ignoring Accessibility) -It is also possible to sort all members and ignore the member groups completely. +Another option is to group the member types by their scope (`public`, `protected`, `private`), ignoring their accessibility. -#### Configuration: `{ "default": { "memberTypes": "never", "order": "alphabetically" } }` +```jsonc +[ + // Index signature + // No scope for index signature. -##### Incorrect example + // Fields + "static-field", // = ["public-static-field", "protected-static-field", "private-static-field"] + "instance-field", // = ["public-instance-field", "protected-instance-field", "private-instance-field"] + "abstract-field", // = ["public-abstract-field", "protected-abstract-field", "private-abstract-field"] -```ts -interface Foo { - b(): void; - a: b; + // Constructors + "constructor", // = ["public-constructor", "protected-constructor", "private-constructor"] - [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) -} -``` + // Getters + "static-get", // = ["public-static-get", "protected-static-get", "private-static-get"] + "instance-get", // = ["public-instance-get", "protected-instance-get", "private-instance-get"] + "abstract-get" // = ["public-abstract-get", "protected-abstract-get", "private-abstract-get"] -Note: Wrong alphabetic order `b(): void` should come after `a: b`. + // Setters + "static-set", // = ["public-static-set", "protected-static-set", "private-static-set"] + "instance-set", // = ["public-instance-set", "protected-instance-set", "private-instance-set"] + "abstract-set" // = ["public-abstract-set", "protected-abstract-set", "private-abstract-set"] -### Sorting alphabetically case-insensitive within member groups + // Methods + "static-method", // = ["public-static-method", "protected-static-method", "private-static-method"] + "instance-method", // = ["public-instance-method", "protected-instance-method", "private-instance-method"] + "abstract-method" // = ["public-abstract-method", "protected-abstract-method", "private-abstract-method"] +] +``` -It is possible to sort all members within a group alphabetically with case insensitivity. +### Member Group Types (With Scope and Accessibility) -#### Configuration: `{ "default": { "memberTypes": , "order": "alphabetically-case-insensitive" } }` +The third grouping option is to ignore both scope and accessibility. -This will apply the default order (see above) and enforce an alphabetic case-insensitive order within each group. +```jsonc +[ + // Index signature + // No grouping for index signature. -##### Incorrect examples + // Fields + "field", // = ["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"] -```ts -interface Foo { - a: x; - B: x; - c: x; + // Constructors + // Only the accessibility of constructors is configurable. - new (): Bar; - (): Baz; + // Getters + "get" // = ["public-static-get", "protected-static-get", "private-static-get", "public-instance-get", "protected-instance-get", "private-instance-get", + // "public-abstract-get", "protected-abstract-get", "private-abstract-get"] - a(): void; - b(): void; - C(): void; + // Setters + "set" // = ["public-static-set", "protected-static-set", "private-static-set", "public-instance-set", "protected-instance-set", "private-instance-set", + // "public-abstract-set", "protected-abstract-set", "private-abstract-set"] - // Wrong group order, should be placed before all field definitions - [a: string]: number; -} + // Methods + "method" // = ["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"] +] ``` -```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; -} -``` +### Grouping Different Member Types at the Same Rank -### Sorting alphabetically case-insensitive while ignoring member groups +It is also possible to group different member types at the same rank. -It is also possible to sort all members with case insensitivity and ignore the member groups completely. +```jsonc +[ + // Index signature + "signature", -#### Configuration: `{ "default": { "memberTypes": "never", "order": "alphabetically-case-insensitive" } }` + // Fields + "field", -##### Incorrect example + // Constructors + "constructor", -```ts -interface Foo { - B(): void; - a: number; + // Getters and Setters at the same rank + ["get", "set"], - [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) -} + // Methods + "method" +] ``` -Note: Wrong alphabetic order `B(): void` should come after `a: number`. - ## 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. +If you don't care about the general order of your members, then you will not need this rule. ## Related To diff --git a/packages/eslint-plugin/src/rules/member-ordering.ts b/packages/eslint-plugin/src/rules/member-ordering.ts index 8c423b2f104..4ddb4954593 100644 --- a/packages/eslint-plugin/src/rules/member-ordering.ts +++ b/packages/eslint-plugin/src/rules/member-ordering.ts @@ -8,13 +8,36 @@ import * as util from '../util'; export type MessageIds = 'incorrectGroupOrder' | 'incorrectOrder'; +type MemberKind = + | 'call-signature' + | 'constructor' + | 'field' + | 'get' + | 'method' + | 'set' + | 'signature'; + +type DecoratedMemberKind = 'field' | 'method' | 'get' | 'set'; + +type NonCallableMemberKind = Exclude; + +type MemberScope = 'static' | 'instance' | 'abstract'; + +type BaseMemberType = + | MemberKind + | `${TSESTree.Accessibility}-${Exclude}` + | `${TSESTree.Accessibility}-decorated-${DecoratedMemberKind}` + | `decorated-${DecoratedMemberKind}` + | `${TSESTree.Accessibility}-${MemberScope}-${NonCallableMemberKind}` + | `${MemberScope}-${NonCallableMemberKind}`; + +type MemberType = BaseMemberType | BaseMemberType[]; + type Order = | 'alphabetically' | 'alphabetically-case-insensitive' | 'as-written'; -type MemberType = string | string[]; - interface SortedOrderConfig { memberTypes?: MemberType[] | 'never'; order: Order; @@ -69,7 +92,7 @@ const objectConfig = (memberTypes: MemberType[]): JSONSchema.JSONSchema4 => ({ additionalProperties: false, }); -export const defaultOrder = [ +export const defaultOrder: MemberType[] = [ // Index signature 'signature', 'call-signature', @@ -198,53 +221,48 @@ export const defaultOrder = [ 'method', ]; -const allMemberTypes = [ - 'signature', - 'field', - 'method', - 'call-signature', - 'constructor', - 'get', - 'set', -].reduce((all, type) => { - all.push(type); - - ['public', 'protected', 'private'].forEach(accessibility => { - if (type !== 'signature') { - all.push(`${accessibility}-${type}`); // e.g. `public-field` - } - - // Only class instance fields, methods, get and set can have decorators attached to them - if ( - type === 'field' || - type === 'method' || - type === 'get' || - type === 'set' - ) { - const decoratedMemberType = `${accessibility}-decorated-${type}`; - const decoratedMemberTypeNoAccessibility = `decorated-${type}`; - if (!all.includes(decoratedMemberType)) { - all.push(decoratedMemberType); +const allMemberTypes = Array.from( + ( + [ + 'signature', + 'field', + 'method', + 'call-signature', + 'constructor', + 'get', + 'set', + ] as const + ).reduce>((all, type) => { + all.add(type); + + (['public', 'protected', 'private'] as const).forEach(accessibility => { + if (type !== 'signature') { + all.add(`${accessibility}-${type}`); // e.g. `public-field` } - if (!all.includes(decoratedMemberTypeNoAccessibility)) { - all.push(decoratedMemberTypeNoAccessibility); - } - } - 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}`); - } + // Only class instance fields, methods, get and set can have decorators attached to them + if ( + type === 'field' || + type === 'method' || + type === 'get' || + type === 'set' + ) { + all.add(`${accessibility}-decorated-${type}`); + all.add(`decorated-${type}`); + } - all.push(`${accessibility}-${scope}-${type}`); - }); - } - }); + if (type !== 'constructor' && type !== 'signature') { + // There is no `static-constructor` or `instance-constructor` or `abstract-constructor` + (['static', 'instance', 'abstract'] as const).forEach(scope => { + all.add(`${scope}-${type}`); + all.add(`${accessibility}-${scope}-${type}`); + }); + } + }); - return all; -}, []); + return all; + }, new Set()), +); const functionExpressions = [ AST_NODE_TYPES.FunctionExpression, @@ -256,7 +274,7 @@ const functionExpressions = [ * * @param node the node to be evaluated. */ -function getNodeType(node: Member): string | null { +function getNodeType(node: Member): MemberKind | null { switch (node.type) { case AST_NODE_TYPES.TSAbstractMethodDefinition: case AST_NODE_TYPES.MethodDefinition: @@ -352,7 +370,7 @@ function getMemberName( * @return Index of the matching member type in the order configuration. */ function getRankOrder( - memberGroups: string[], + memberGroups: BaseMemberType[], orderConfig: MemberType[], ): number { let rank = -1; @@ -403,8 +421,9 @@ function getRank( ? node.accessibility : 'public'; - // Collect all existing member groups (e.g. 'public-instance-field', 'instance-field', 'public-field', 'constructor' etc.) - const memberGroups = []; + // Collect all existing member groups that apply to this node... + // (e.g. 'public-instance-field', 'instance-field', 'public-field', 'constructor' etc.) + const memberGroups: BaseMemberType[] = []; if (supportsModifiers) { const decorated = 'decorators' in node && node.decorators!.length > 0; @@ -419,17 +438,20 @@ function getRank( memberGroups.push(`decorated-${type}`); } - if (type !== 'constructor') { - // Constructors have no scope - memberGroups.push(`${accessibility}-${scope}-${type}`); - memberGroups.push(`${scope}-${type}`); - } + if (type !== 'signature') { + if (type !== 'constructor') { + // Constructors have no scope + memberGroups.push(`${accessibility}-${scope}-${type}`); + memberGroups.push(`${scope}-${type}`); + } - memberGroups.push(`${accessibility}-${type}`); + memberGroups.push(`${accessibility}-${type}`); + } } memberGroups.push(type); + // ...then get the rank order for those member groups based on the node return getRankOrder(memberGroups, orderConfig); }