diff --git a/packages/eslint-plugin/docs/rules/member-ordering.md b/packages/eslint-plugin/docs/rules/member-ordering.md index d028d844368..cccf2bbbe0a 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/src/rules/member-ordering.ts b/packages/eslint-plugin/src/rules/member-ordering.ts index b9fb0ee8ef0..14ddcc236a9 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/tests/rules/member-ordering.test.ts b/packages/eslint-plugin/tests/rules/member-ordering.test.ts index ddea73e8d1d..c64c57c4591 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/typescript-estree/src/ts-estree/ts-estree.ts b/packages/typescript-estree/src/ts-estree/ts-estree.ts index cbf7787c805..24486b974b3 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