From bef3d8315a4e34667a3a52a13898a3b022d62ad4 Mon Sep 17 00:00:00 2001 From: Tim Kraut Date: Tue, 12 Feb 2019 17:30:34 +0100 Subject: [PATCH] feat(eslint-plugin): sort members alphabetically --- .../docs/rules/member-ordering.md | 86 +- .../src/rules/member-ordering.ts | 652 +++-- .../src/rules/sort-interface-members.ts | 199 ++ .../tests/rules/member-ordering.test.ts | 2556 ++++++++++++++++- .../rules/sort-interface-members.test.ts | 80 + 5 files changed, 3176 insertions(+), 397 deletions(-) create mode 100644 packages/eslint-plugin/src/rules/sort-interface-members.ts create mode 100644 packages/eslint-plugin/tests/rules/sort-interface-members.test.ts diff --git a/packages/eslint-plugin/docs/rules/member-ordering.md b/packages/eslint-plugin/docs/rules/member-ordering.md index dfd256502fa5..54a81dc8eefc 100644 --- a/packages/eslint-plugin/docs/rules/member-ordering.md +++ b/packages/eslint-plugin/docs/rules/member-ordering.md @@ -7,16 +7,21 @@ A consistent ordering of fields, methods and constructors can make interfaces, t This rule aims to standardize the way class declarations, class expressions, interfaces and type literals are structured and ordered. ### Grouping and sorting member groups + It allows to group members by their type (e.g. `public-static-field`, `protected-static-field`, `private-static-field`, `public-instance-field`, ...) and enforce a certain order for these groups. By default, their order is the same inside `classes`, `classExpressions`, `interfaces` and `typeLiterals` (note: not all member types apply to `interfaces` and `typeLiterals`). It is possible to define the order for any of those individually or to change the default order for all of them by setting the `default` option. ### Sorting members + Besides grouping the members and sorting their groups, this rule also allows to sort the members themselves. You have 2 options: Sort all of them while ignoring their type or sort them inside of the member 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. + ```ts { default?: Array | never + classes?: Array | never classExpressions?: Array | never @@ -26,14 +31,16 @@ These options allow to specify how to group the members and sort their groups. ``` If you want to enforce an alphabetic order, you have to use this form + ```ts { - default?: { memberTypes?: Array, order?: 'alphabetically' } | never - classes?: { memberTypes?: Array, order?: 'alphabetically' } | never - classExpressions?: { memberTypes?: Array, order?: 'alphabetically' } | never + default?: Array | { memberTypes?: Array | 'never', order?: 'alphabetically' } - interfaces?: { memberTypes?: Array, order?: 'alphabetically' } | never - typeLiterals?: { memberTypes?: Array, order?: 'alphabetically' } | never + classes?: Array | { memberTypes?: Array | 'never', order?: 'alphabetically' } + classExpressions?: Array | { memberTypes?: Array | 'never', order?: 'alphabetically' } + + interfaces?: ['field' | 'method' | 'constructor'] | { memberTypes?: Array | 'never', order?: 'alphabetically' } + typeLiterals?: ['field' | 'method' | 'constructor'] | { memberTypes?: Array | 'never', order?: 'alphabetically' } } ``` @@ -657,6 +664,75 @@ type Foo = { }; ``` +### Sorting alphabetically within member groups + +It is possible to sort all members within a group alphabetically. + +#### Configuration: `{ default: { 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; + + a(): void; + b(): void; + c(): void; + + [a: string]: number; + (): Baz; + + new (): Bar; +} +``` + +Note: Wrong group order for `new () : Bar`. + +```ts +interface Foo { + a: x; + b: x; + c: x; + + new (): Bar; + + c(): void; + b(): void; + a(): void; + + [a: string]: number; + (): Baz; +} +``` + +Note: Wrong alphabetic order (should be `a(): void` --> `b(): void` --> `c(): 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 72020afc6008..9f4c12738c25 100644 --- a/packages/eslint-plugin/src/rules/member-ordering.ts +++ b/packages/eslint-plugin/src/rules/member-ordering.ts @@ -1,11 +1,19 @@ import { - TSESTree, AST_NODE_TYPES, + TSESTree, } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; +import { SourceCode } from '@typescript-eslint/experimental-utils/dist/ts-eslint'; + +type MessageIds = 'incorrectGroupOrder' | 'incorrectOrder'; + +interface SortedOrderConfig { + memberTypes: string[] | 'never'; + order: string; +} + +type OrderConfig = string[] | SortedOrderConfig | 'never'; -type MessageIds = 'incorrectOrder'; -type OrderConfig = string[] | 'never'; type Options = [ { default?: OrderConfig; @@ -16,6 +24,44 @@ type Options = [ }, ]; +const defaultOrder = [ + 'public-static-field', + 'protected-static-field', + 'private-static-field', + + 'public-instance-field', + 'protected-instance-field', + 'private-instance-field', + + 'public-field', + 'protected-field', + 'private-field', + + 'static-field', + 'instance-field', + + 'field', + + 'constructor', + + 'public-static-method', + 'protected-static-method', + 'private-static-method', + + 'public-instance-method', + 'protected-instance-method', + 'private-instance-method', + + 'public-method', + 'protected-method', + 'private-method', + + 'static-method', + 'instance-method', + + 'method', +]; + const allMemberTypes = ['field', 'method', 'constructor'].reduce( (all, type) => { all.push(type); @@ -40,6 +86,215 @@ const allMemberTypes = ['field', 'method', 'constructor'].reduce( [], ); +const neverConfig = { + type: 'string', + enum: ['never'], +}; + +const allMemberTypesArrayConfig = { + type: 'array', + items: { + enum: allMemberTypes, + }, +}; + +const allMemberTypesDefaultConfig = { + type: 'string', + enum: ['never'], +}; + +const allMemberTypesObjectConfig = { + type: 'object', + properties: { + memberTypes: { + oneOf: [allMemberTypesDefaultConfig, allMemberTypesArrayConfig], + }, + order: { + type: 'string', + enum: ['alphabetically'], + }, + }, + additionalProperties: false, +}; + +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 { + switch (node.type) { + 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.ClassProperty: + return node.value && functionExpressions.includes(node.value.type) + ? 'method' + : 'field'; + case AST_NODE_TYPES.TSPropertySignature: + return 'field'; + default: + return null; + } +} + +/** + * Gets the member name based on the member type. + * + * @param node the node to be evaluated. + * @param sourceCode + */ +function getMemberName( + node: TSESTree.ClassElement | TSESTree.TypeElement, + sourceCode: SourceCode, +): string | null { + switch (node.type) { + case AST_NODE_TYPES.TSPropertySignature: + case AST_NODE_TYPES.TSMethodSignature: + case AST_NODE_TYPES.ClassProperty: + return util.getNameFromPropertyName(node.key); + case AST_NODE_TYPES.MethodDefinition: + return node.kind === 'constructor' + ? 'constructor' + : util.getNameFromClassMember(node, sourceCode); + case AST_NODE_TYPES.TSConstructSignatureDeclaration: + return 'new'; + default: + return null; + } +} + +/** + * Gets the member identifier (null if there is no identifier). + * + * @param node the node to be evaluated. + * @param sourceCode + */ +function getIdentifier( + node: TSESTree.ClassElement | TSESTree.TypeElement, + sourceCode: SourceCode, +): string | null { + switch (node.type) { + case AST_NODE_TYPES.TSPropertySignature: + case AST_NODE_TYPES.TSMethodSignature: + case AST_NODE_TYPES.ClassProperty: + return util.getNameFromPropertyName(node.key); + case AST_NODE_TYPES.MethodDefinition: + return util.getNameFromClassMember(node, sourceCode); + 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: TSESTree.ClassElement | TSESTree.TypeElement, + 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 scope = 'static' in node && node.static ? 'static' : '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', meta: { @@ -51,6 +306,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: [ @@ -59,67 +316,37 @@ export default util.createRule({ properties: { default: { oneOf: [ - { - enum: ['never'], - }, - { - type: 'array', - items: { - enum: allMemberTypes, - }, - }, + neverConfig, + allMemberTypesArrayConfig, + allMemberTypesObjectConfig, ], }, classes: { oneOf: [ - { - enum: ['never'], - }, - { - type: 'array', - items: { - enum: allMemberTypes, - }, - }, + neverConfig, + allMemberTypesArrayConfig, + allMemberTypesObjectConfig, ], }, classExpressions: { oneOf: [ - { - enum: ['never'], - }, - { - type: 'array', - items: { - enum: allMemberTypes, - }, - }, + neverConfig, + allMemberTypesArrayConfig, + allMemberTypesObjectConfig, ], }, interfaces: { oneOf: [ - { - enum: ['never'], - }, - { - type: 'array', - items: { - enum: ['field', 'method', 'constructor'], - }, - }, + neverConfig, + allMemberTypesArrayConfig, + allMemberTypesObjectConfig, ], }, typeLiterals: { oneOf: [ - { - enum: ['never'], - }, - { - type: 'array', - items: { - enum: ['field', 'method', 'constructor'], - }, - }, + neverConfig, + allMemberTypesArrayConfig, + allMemberTypesObjectConfig, ], }, }, @@ -129,237 +356,132 @@ export default util.createRule({ }, defaultOptions: [ { - default: [ - 'public-static-field', - 'protected-static-field', - 'private-static-field', - - 'public-instance-field', - 'protected-instance-field', - 'private-instance-field', - - 'public-field', - 'protected-field', - 'private-field', - - 'static-field', - 'instance-field', - - 'field', - - 'constructor', - - 'public-static-method', - 'protected-static-method', - 'private-static-method', - - 'public-instance-method', - 'protected-instance-method', - 'private-instance-method', - - 'public-method', - 'protected-method', - 'private-method', - - 'static-method', - 'instance-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 - // TODO: add missing TSIndexSignature - // TODO: add missing TSAbstractClassProperty - // TODO: add missing TSAbstractMethodDefinition - switch (node.type) { - 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.ClassProperty: - return node.value && functionExpressions.includes(node.value.type) - ? 'method' - : 'field'; - case AST_NODE_TYPES.TSPropertySignature: - return 'field'; - 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.ClassProperty: - return util.getNameFromPropertyName(node.key); - case AST_NODE_TYPES.MethodDefinition: - return node.kind === 'constructor' - ? 'constructor' - : util.getNameFromClassMember(node, sourceCode); - case AST_NODE_TYPES.TSConstructSignatureDeclaration: - return 'new'; - 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. - * - * @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. - */ - function getRank( - node: TSESTree.ClassElement | TSESTree.TypeElement, - order: 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 scope = 'static' in node && node.static ? 'static' : '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}`); - } - - memberTypes.push(`${accessibility}-${type}`); - } - - memberTypes.push(type); - - return getRankOrder(memberTypes, order); - } - - /** - * 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, ' '); - } - /** * 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, + 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 (members.length > 0 && orderConfig !== 'never') { + if (Array.isArray(orderConfig)) { + // Sort member groups (= ignore alphabetic order) + let memberGroupsOrder = orderConfig; + + const previousRanks: number[] = []; + + // Find first member which isn't correctly sorted + members.forEach(member => { + const rank = getRank(member, memberGroupsOrder, supportsModifiers); + const name = getMemberName(member, context.getSourceCode()); + + if (rank !== -1) { + // Make sure member types are correctly grouped + // Works for 1st item because x < undefined === false for any x (typeof string) + if (rank < previousRanks[previousRanks.length - 1]) { + context.report({ + node: member, + messageId: 'incorrectGroupOrder', + data: { + name, + rank: getLowestRank(previousRanks, rank, memberGroupsOrder), + }, + }); + } else { + previousRanks.push(rank); + } } - } - }); + }); + } else if (orderConfig.memberTypes === 'never') { + // Sort members alphabetically + ignore groups + + let previousName: string = ''; + + // console.log(members) + + // Find first member which isn't correctly sorted + members.forEach(member => { + const name = getIdentifier(member, context.getSourceCode()); + + // Same member group --> Check alphabetic order + if (name) { + // Not all members have sortable identifiers + if (name < previousName) { + context.report({ + node: member, + messageId: 'incorrectOrder', + data: { + member: name, + beforeMember: previousName, + }, + }); + } + + previousName = name; + } + }); + } else { + // Sort groups + sort alphabetically within each group + let memberGroupsOrder = orderConfig.memberTypes || defaultOrder; + + const previousRanks: number[] = []; + let previousName: string = ''; + + // Find first member which isn't correctly sorted + members.forEach(member => { + const rank = getRank(member, memberGroupsOrder, supportsModifiers); + const name = getIdentifier(member, context.getSourceCode()); + + if (rank !== -1) { + // Make sure member types are correctly grouped + // Works for 1st item because x < undefined === false for any x (typeof string) + if (rank < previousRanks[previousRanks.length - 1]) { + context.report({ + node: member, + messageId: 'incorrectGroupOrder', + data: { + name: getMemberName(member, context.getSourceCode()), + rank: getLowestRank(previousRanks, rank, memberGroupsOrder), + }, + }); + } else if (rank === previousRanks[previousRanks.length - 1]) { + // Same member group --> Check alphabetic order + if (name) { + // Not all members have sortable identifiers + if (previousName) { + if (name < previousName) { + context.report({ + node: member, + messageId: 'incorrectOrder', + data: { + member: name, + beforeMember: previousName, + }, + }); + } + + previousName = name; + } + } + } else { + previousRanks.push(rank); + + if (name) { + previousName = name; + } + } + } + }); + } } } diff --git a/packages/eslint-plugin/src/rules/sort-interface-members.ts b/packages/eslint-plugin/src/rules/sort-interface-members.ts new file mode 100644 index 000000000000..7d7c53a8cd79 --- /dev/null +++ b/packages/eslint-plugin/src/rules/sort-interface-members.ts @@ -0,0 +1,199 @@ +/** + * @fileoverview Forbids unsorted interface members + */ + +import * as util from '../util'; +import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; + +type Options = []; +type MessageIds = 'notSorted'; + +function isPropertySignature( + member: TSESTree.TypeElement, +): member is TSESTree.TSPropertySignature { + return member.type === AST_NODE_TYPES.TSPropertySignature; +} + +function isMethodSignature( + member: TSESTree.TypeElement, +): member is TSESTree.TSMethodSignature { + return member.type === AST_NODE_TYPES.TSMethodSignature; +} + +function isIndexSignature( + member: TSESTree.TypeElement, +): member is TSESTree.TSIndexSignature { + return member.type === AST_NODE_TYPES.TSIndexSignature; +} + +function isConstructSignatureDeclaration( + member: TSESTree.TypeElement, +): member is TSESTree.TSConstructSignatureDeclaration { + return member.type === AST_NODE_TYPES.TSConstructSignatureDeclaration; +} + +function isCallSignatureDeclaration( + member: TSESTree.TypeElement, +): member is TSESTree.TSCallSignatureDeclaration { + return member.type === AST_NODE_TYPES.TSCallSignatureDeclaration; +} + +export default util.createRule({ + name: 'sort-interface-members', + meta: { + type: 'suggestion', + docs: { + description: 'Forbids unsorted interface members', + category: 'Stylistic Issues', + recommended: false, + }, + schema: [], + messages: { + notSorted: 'The interface members are not sorted alphabetically.', + }, + }, + defaultOptions: [], + create(context) { + return { + TSInterfaceBody(node) { + const members = node.body; + const propertySignatures: TSESTree.TSPropertySignature[] = []; + const methodSignatures: TSESTree.TSMethodSignature[] = []; + const indexSignatures: TSESTree.TSIndexSignature[] = []; + const constructSignatureDeclarations: TSESTree.TSConstructSignatureDeclaration[] = []; + const callSignatureDeclarations: TSESTree.TSCallSignatureDeclaration[] = []; + + // TODO This algorithm assumes an order of TSESTree.TSPropertySignature > TSMethodSignature > TSIndexSignature > TSConstructSignatureDeclaration > TSCallSignatureDeclaration - it is only used to evaluate if it works alongside the member-ordering rule + for (let i = 0; i < members.length; i++) { + if (isPropertySignature(members[i])) { + if ( + methodSignatures.length > 0 || + indexSignatures.length > 0 || + constructSignatureDeclarations.length > 0 || + callSignatureDeclarations.length > 0 + ) { + return context.report({ + messageId: 'notSorted', + node: members[i], + }); + } + + propertySignatures.push(members[i] as TSESTree.TSPropertySignature); + } else if (isMethodSignature(members[i])) { + if ( + indexSignatures.length > 0 || + constructSignatureDeclarations.length > 0 || + callSignatureDeclarations.length > 0 + ) { + return context.report({ + messageId: 'notSorted', + node: members[i], + }); + } + + methodSignatures.push(members[i] as TSESTree.TSMethodSignature); + } else if (isIndexSignature(members[i])) { + if ( + constructSignatureDeclarations.length > 0 || + callSignatureDeclarations.length > 0 + ) { + return context.report({ + messageId: 'notSorted', + node: members[i], + }); + } + + indexSignatures.push(members[i] as TSESTree.TSIndexSignature); + } else if (isConstructSignatureDeclaration(members[i])) { + if (callSignatureDeclarations.length > 0) { + return context.report({ + messageId: 'notSorted', + node: members[i], + }); + } + + constructSignatureDeclarations.push(members[ + i + ] as TSESTree.TSConstructSignatureDeclaration); + } else if (isCallSignatureDeclaration(members[i])) { + callSignatureDeclarations.push(members[ + i + ] as TSESTree.TSCallSignatureDeclaration); + } + } + + for (let i = 0; i < propertySignatures.length - 1; i++) { + const currentItem = propertySignatures[i].key as TSESTree.Identifier; + const nextItem = propertySignatures[i + 1].key as TSESTree.Identifier; + + if (currentItem.name > nextItem.name) { + return context.report({ + messageId: 'notSorted', + node: currentItem, + }); + } + } + + for (let i = 0; i < methodSignatures.length - 1; i++) { + const currentItem = methodSignatures[i].key as TSESTree.Identifier; + const nextItem = methodSignatures[i + 1].key as TSESTree.Identifier; + + if (currentItem.name > nextItem.name) { + return context.report({ + messageId: 'notSorted', + node: currentItem, + }); + } + } + + for (let i = 0; i < indexSignatures.length - 1; i++) { + const currentItem = indexSignatures[i] + .parameters[0] as TSESTree.Identifier; + const nextItem = indexSignatures[i + 1] + .parameters[0] as TSESTree.Identifier; + + if (currentItem.name > nextItem.name) { + return context.report({ + messageId: 'notSorted', + node: currentItem, + }); + } + } + + for (let i = 0; i < constructSignatureDeclarations.length - 1; i++) { + const currentItem = ((constructSignatureDeclarations[i] + .returnType as TSESTree.TSTypeAnnotation) + .typeAnnotation as TSESTree.TSTypeReference).typeName; + const nextItem = ((constructSignatureDeclarations[i + 1] + .returnType as TSESTree.TSTypeAnnotation) + .typeAnnotation as TSESTree.TSTypeReference).typeName; + + if (currentItem > nextItem) { + return context.report({ + messageId: 'notSorted', + node: currentItem, + }); + } + } + + for (let i = 0; i < callSignatureDeclarations.length - 1; i++) { + const currentItem = ((callSignatureDeclarations[i] + .returnType as TSESTree.TSTypeAnnotation) + .typeAnnotation as TSESTree.TSTypeReference).typeName; + const nextItem = ((callSignatureDeclarations[i + 1] + .returnType as TSESTree.TSTypeAnnotation) + .typeAnnotation as TSESTree.TSTypeReference).typeName; + + if (currentItem > nextItem) { + return context.report({ + messageId: 'notSorted', + node: currentItem, + }); + } + } + + return; // No rule violation found + }, + }; + }, +}); diff --git a/packages/eslint-plugin/tests/rules/member-ordering.test.ts b/packages/eslint-plugin/tests/rules/member-ordering.test.ts index 81e67999b8dd..527c65189189 100644 --- a/packages/eslint-plugin/tests/rules/member-ordering.test.ts +++ b/packages/eslint-plugin/tests/rules/member-ordering.test.ts @@ -1,11 +1,12 @@ import rule 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 @@ -1240,7 +1241,7 @@ interface Foo { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'method', @@ -1272,7 +1273,7 @@ interface Foo { options: [{ default: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -1281,7 +1282,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -1290,7 +1291,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -1299,7 +1300,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -1308,7 +1309,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -1317,7 +1318,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -1326,7 +1327,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'field', @@ -1358,7 +1359,7 @@ interface Foo { options: [{ interfaces: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -1367,7 +1368,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -1376,7 +1377,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -1385,7 +1386,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -1394,7 +1395,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -1403,7 +1404,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -1412,7 +1413,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'field', @@ -1449,7 +1450,7 @@ interface Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -1458,7 +1459,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -1467,7 +1468,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -1476,7 +1477,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -1485,7 +1486,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -1494,7 +1495,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -1503,7 +1504,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'field', @@ -1539,7 +1540,7 @@ interface Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'B', rank: 'method', @@ -1548,7 +1549,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'C', rank: 'method', @@ -1557,7 +1558,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'D', rank: 'method', @@ -1566,7 +1567,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'E', rank: 'method', @@ -1575,7 +1576,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'F', rank: 'method', @@ -1606,7 +1607,7 @@ type Foo = { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'method', @@ -1638,7 +1639,7 @@ type Foo = { options: [{ default: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -1647,7 +1648,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -1656,7 +1657,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -1665,7 +1666,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -1674,7 +1675,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -1683,7 +1684,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -1692,7 +1693,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'field', @@ -1724,7 +1725,7 @@ type Foo = { options: [{ typeLiterals: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -1733,7 +1734,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -1742,7 +1743,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -1751,7 +1752,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -1760,7 +1761,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -1769,7 +1770,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -1778,7 +1779,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'field', @@ -1815,7 +1816,7 @@ type Foo = { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -1824,7 +1825,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -1833,7 +1834,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -1842,7 +1843,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -1851,7 +1852,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -1860,7 +1861,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -1869,7 +1870,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'field', @@ -1905,7 +1906,7 @@ type Foo = { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'B', rank: 'method', @@ -1914,7 +1915,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'C', rank: 'method', @@ -1923,7 +1924,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'D', rank: 'method', @@ -1932,7 +1933,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'E', rank: 'method', @@ -1941,7 +1942,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'F', rank: 'method', @@ -1971,7 +1972,7 @@ class Foo { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'public instance method', @@ -1980,7 +1981,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'public instance method', @@ -1989,7 +1990,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'public instance method', @@ -2020,7 +2021,7 @@ class Foo { options: [{ default: ['field', 'constructor', 'method'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'constructor', @@ -2029,7 +2030,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'B', rank: 'constructor', @@ -2038,7 +2039,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'C', rank: 'constructor', @@ -2047,7 +2048,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'D', rank: 'constructor', @@ -2056,7 +2057,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'E', rank: 'constructor', @@ -2065,7 +2066,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'F', rank: 'constructor', @@ -2096,7 +2097,7 @@ class Foo { options: [{ default: ['field', 'method'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'method', @@ -2127,7 +2128,7 @@ class Foo { options: [{ default: ['method', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -2158,7 +2159,7 @@ class Foo { options: [{ classes: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -2167,7 +2168,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -2176,7 +2177,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -2185,7 +2186,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -2194,7 +2195,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'field', @@ -2230,7 +2231,7 @@ class Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -2239,7 +2240,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -2248,7 +2249,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -2257,7 +2258,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -2266,7 +2267,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -2275,7 +2276,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -2284,7 +2285,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'field', @@ -2327,7 +2328,7 @@ class Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'private field', @@ -2336,7 +2337,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'F', rank: 'protected field', @@ -2380,7 +2381,7 @@ class Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'public instance method', @@ -2389,7 +2390,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'public field', @@ -2430,7 +2431,7 @@ class Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'method', @@ -2473,7 +2474,7 @@ class Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'private static method', @@ -2482,7 +2483,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'private static method', @@ -2517,7 +2518,7 @@ class Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'protected static field', @@ -2553,7 +2554,7 @@ class Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'protected static field', @@ -2583,7 +2584,7 @@ const foo = class Foo { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'public instance method', @@ -2592,7 +2593,7 @@ const foo = class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'public instance method', @@ -2601,7 +2602,7 @@ const foo = class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'public instance method', @@ -2632,7 +2633,7 @@ const foo = class { options: [{ default: ['field', 'constructor', 'method'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'constructor', @@ -2641,7 +2642,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'B', rank: 'constructor', @@ -2650,7 +2651,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'C', rank: 'constructor', @@ -2659,7 +2660,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'D', rank: 'constructor', @@ -2668,7 +2669,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'E', rank: 'constructor', @@ -2677,7 +2678,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'F', rank: 'constructor', @@ -2708,7 +2709,7 @@ const foo = class { options: [{ default: ['field', 'method'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'method', @@ -2739,7 +2740,7 @@ const foo = class { options: [{ default: ['method', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -2770,7 +2771,7 @@ const foo = class { options: [{ classExpressions: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -2779,7 +2780,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -2788,7 +2789,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -2797,7 +2798,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -2806,7 +2807,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'field', @@ -2842,7 +2843,7 @@ const foo = class { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -2851,7 +2852,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -2860,7 +2861,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -2869,7 +2870,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -2878,7 +2879,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -2887,7 +2888,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -2896,7 +2897,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'field', @@ -2939,7 +2940,7 @@ const foo = class { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'private field', @@ -2948,7 +2949,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'F', rank: 'protected field', @@ -2992,7 +2993,7 @@ const foo = class { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'public instance method', @@ -3001,7 +3002,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'public field', @@ -3042,7 +3043,7 @@ const foo = class { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'method', @@ -3085,7 +3086,7 @@ const foo = class { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'private static method', @@ -3094,7 +3095,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'private static method', @@ -3133,7 +3134,7 @@ const foo = class { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'protected static field', @@ -3169,7 +3170,7 @@ const foo = class { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'protected static field', @@ -3191,7 +3192,7 @@ class Foo { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'public instance method', @@ -3200,7 +3201,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'public instance method', @@ -3222,7 +3223,7 @@ class Foo { options: [{ default: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'constructor', @@ -3245,7 +3246,7 @@ class Foo { options: [{ default: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'constructor', @@ -3265,7 +3266,7 @@ interface Foo { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'method', @@ -3285,7 +3286,7 @@ type Foo = { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'method', @@ -3306,7 +3307,7 @@ type Foo = { options: [{ default: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -3325,7 +3326,7 @@ abstract class Foo { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'B', rank: 'method', @@ -3336,4 +3337,2305 @@ abstract class Foo { ], }, ], +}; + +const sortedWithoutGroupingDefaultOption: TSESLint.RunTests< + any, + Readonly +> = { + valid: [ + // default option + interface + multiple types + { + code: ` +interface Foo { + a : b; + b() : void; + [a: string] : number; // Will be ignored (no sortable identifier) + new () : Bar; // Will be ignored (no sortable identifier) + () : Baz; // Will be ignored (no sortable identifier) +} + `, + options: [{ default: { memberTypes: 'never', 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; + b() : void; + [a: string] : number; // Will be ignored (no sortable identifier) + new () : Bar; // Will be ignored (no sortable identifier) + () : Baz; // Will be ignored (no sortable identifier) +} + `, + 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() {} // Will be ignored (no sortable identifier) + 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() {} // Will be ignored (no sortable identifier) + 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; // Will be ignored (no sortable identifier) + new () : Bar; // Will be ignored (no sortable identifier) + () : Baz; // Will be ignored (no sortable identifier) +} + `, + 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; // Will be ignored (no sortable identifier) + new () : Bar; // Will be ignored (no sortable identifier) + () : Baz; // Will be ignored (no sortable identifier) +} + `, + 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() {} // Will be ignored (no sortable identifier) + 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() {} // Will be ignored (no sortable identifier) + 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< + any, + Readonly +> = { + valid: [ + // classes option + interface + multiple types --> Only member group order is checked (default config) + { + code: ` +interface Foo { + c : b; + new () : Bar; + b() : void; + [a: string] : number; + () : 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 = { + c : b; + new () : Bar; + b() : void; + [a: string] : number; + () : 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() {} // Will be ignored (no sortable identifier) + 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() {} // Will be ignored (no sortable identifier) + 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< + any, + Readonly +> = { + valid: [ + // classExpressions option + interface + multiple types --> Only member group order is checked (default config) + { + code: ` +interface Foo { + c : b; + new () : Bar; + b() : void; + [a: string] : number; + () : 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 = { + c : b; + new () : Bar; + b() : void; + [a: string] : number; + () : 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() {} // Will be ignored (no sortable identifier) + 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() {} // Will be ignored (no sortable identifier) + 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< + any, + Readonly +> = { + valid: [ + // interfaces option + interface + multiple types + { + code: ` +interface Foo { + a : b; + b() : void; + [a: string] : number; // Will be ignored (no sortable identifier) + new () : Bar; // Will be ignored (no sortable identifier) + () : Baz; // Will be ignored (no sortable identifier) +} + `, + 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 = { + c : b; + new () : Bar; + b() : void; + [a: string] : number; + () : 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; // Will be ignored (no sortable identifier) + new () : Bar; // Will be ignored (no sortable identifier) + () : Baz; // Will be ignored (no sortable identifier) +} + `, + 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< + any, + Readonly +> = { + valid: [ + // typeLiterals option + interface + multiple types --> Only member group order is checked (default config) + { + code: ` +interface Foo { + c : b; + new () : Bar; + b() : void; + [a: string] : number; + () : 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 : b; + b() : void; + [a: string] : number; // Will be ignored (no sortable identifier) + new () : Bar; // Will be ignored (no sortable identifier) + () : Baz; // Will be ignored (no sortable identifier) +} + `, + 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; // Will be ignored (no sortable identifier) + new () : Bar; // Will be ignored (no sortable identifier) + () : Baz; // Will be ignored (no sortable identifier) +} + `, + 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< + any, + Readonly +> = { + valid: [ + // default option + interface + default order + alphabetically + { + code: ` +interface Foo { + a : x; + b : x; + c : x; + + new () : Bar; + + a() : void; + b() : void; + c() : void; + + [a: string] : number; // Will be ignored (no sortable identifier) + () : Baz; // Will be ignored (no sortable identifier) +} + `, + options: [{ default: { 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; // Will be ignored (no sortable identifier) + () : Baz; // Will be ignored (no sortable identifier) +} + `, + options: [ + { + default: { + memberTypes: ['constructor', 'method', 'field'], + order: 'alphabetically', + }, + }, + ], + }, + + // default option + type literal + default order + alphabetically + { + code: ` +type Foo = { + a : x; + b : x; + c : x; + + new () : Bar; + + a() : void; + b() : void; + c() : void; + + [a: string] : number; // Will be ignored (no sortable identifier) + () : Baz; // Will be ignored (no sortable identifier) +} + `, + options: [{ default: { order: 'alphabetically' } }], + }, + + // default 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; // Will be ignored (no sortable identifier) + () : Baz; // Will be ignored (no sortable identifier) +} + `, + 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: { 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: { 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 : x; + b : x; + c : x; + + c() : void; + b() : void; + a() : void; + + [a: string] : number; // Will be ignored (no sortable identifier) + () : Baz; // Will be ignored (no sortable identifier) + + new () : Bar; +} + `, + options: [{ default: { order: 'alphabetically' } }], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'b', + beforeMember: 'c', + }, + }, + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + { + messageId: 'incorrectGroupOrder', + data: { + name: 'new', + rank: 'method', + }, + }, + ], + }, + + // default option + type literal + wrong order within group and wrong group order + alphabetically + { + code: ` +type Foo = { + a : x; + b : x; + c : x; + + c() : void; + b() : void; + a() : void; + + [a: string] : number; // Will be ignored (no sortable identifier) + () : Baz; // Will be ignored (no sortable identifier) + + new () : Bar; +} + `, + options: [{ default: { order: 'alphabetically' } }], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'b', + beforeMember: 'c', + }, + }, + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + { + 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: { order: 'alphabetically' } }], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'b', + beforeMember: 'c', + }, + }, + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + { + messageId: 'incorrectGroupOrder', + data: { + name: 'd', + rank: '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: { order: 'alphabetically' } }], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'b', + beforeMember: 'c', + }, + }, + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + { + messageId: 'incorrectGroupOrder', + data: { + name: 'd', + rank: 'constructor', + }, + }, + ], + }, + ], +}; + +const sortedWithGroupingClassesOption: TSESLint.RunTests< + any, + Readonly +> = { + valid: [ + // classes option + interface + alphabetically --> Default order applies + { + code: ` +interface Foo { + c : x; + b : x; + a : x; + + new () : Bar; + + c() : void; + b() : void; + a() : void; + + [a: string] : number; + () : Baz; +} + `, + options: [{ classes: { order: 'alphabetically' } }], + }, + + // classes option + type literal + alphabetically --> Default order applies + { + code: ` +type Foo = { + c : x; + b : x; + a : x; + + new () : Bar; + + c() : void; + b() : void; + a() : void; + + [a: string] : number; + () : 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: { 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: { order: 'alphabetically' } }], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'b', + beforeMember: 'c', + }, + }, + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + { + messageId: 'incorrectGroupOrder', + data: { + name: 'd', + rank: 'constructor', + }, + }, + ], + }, + ], +}; + +const sortedWithGroupingClassExpressionsOption: TSESLint.RunTests< + any, + Readonly +> = { + valid: [ + // classExpressions option + interface + alphabetically --> Default order applies + { + code: ` +interface Foo { + c : x; + b : x; + a : x; + + new () : Bar; + + c() : void; + b() : void; + a() : void; + + [a: string] : number; + () : Baz; +} + `, + options: [{ classExpressions: { order: 'alphabetically' } }], + }, + + // classExpressions option + type literal + alphabetically --> Default order applies + { + code: ` +type Foo = { + c : x; + b : x; + a : x; + + new () : Bar; + + c() : void; + b() : void; + a() : void; + + [a: string] : number; + () : 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: { 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: { order: 'alphabetically' } }], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'b', + beforeMember: 'c', + }, + }, + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + { + messageId: 'incorrectGroupOrder', + data: { + name: 'd', + rank: 'constructor', + }, + }, + ], + }, + ], +}; + +const sortedWithGroupingInterfacesOption: TSESLint.RunTests< + any, + Readonly +> = { + valid: [ + // interfaces option + interface + default order + alphabetically + { + code: ` +interface Foo { + a : x; + b : x; + c : x; + + new () : Bar; + + a() : void; + b() : void; + c() : void; + + [a: string] : number; // Will be ignored (no sortable identifier) + () : Baz; // Will be ignored (no sortable identifier) +} + `, + options: [{ interfaces: { 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; // Will be ignored (no sortable identifier) + () : Baz; // Will be ignored (no sortable identifier) +} + `, + options: [ + { + interfaces: { + memberTypes: ['constructor', 'method', 'field'], + order: 'alphabetically', + }, + }, + ], + }, + + // interfaces option + type literal + alphabetically --> Default order applies + { + code: ` +type Foo = { + c : x; + b : x; + a : x; + + new () : Bar; + + c() : void; + b() : void; + a() : void; + + [a: string] : number; + () : 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 : x; + b : x; + c : x; + + c() : void; + b() : void; + a() : void; + + [a: string] : number; // Will be ignored (no sortable identifier) + () : Baz; // Will be ignored (no sortable identifier) + + new () : Bar; +} + `, + options: [{ default: { order: 'alphabetically' } }], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'b', + beforeMember: 'c', + }, + }, + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + { + messageId: 'incorrectGroupOrder', + data: { + name: 'new', + rank: 'method', + }, + }, + ], + }, + ], +}; + +const sortedWithGroupingTypeLiteralsOption: TSESLint.RunTests< + any, + Readonly +> = { + valid: [ + // typeLiterals option + interface + alphabetically --> Default order applies + { + code: ` +interface Foo { + c : x; + b : x; + a : x; + + new () : Bar; + + c() : void; + b() : void; + a() : void; + + [a: string] : number; + () : Baz; +} + `, + options: [{ typeLiterals: { order: 'alphabetically' } }], + }, + + // typeLiterals option + type literal + default order + alphabetically + { + code: ` +type Foo = { + a : x; + b : x; + c : x; + + new () : Bar; + + a() : void; + b() : void; + c() : void; + + [a: string] : number; + () : Baz; +} + `, + options: [{ typeLiterals: { 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; // Will be ignored (no sortable identifier) + () : Baz; // Will be ignored (no sortable identifier) +} + `, + 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 : x; + b : x; + c : x; + + c() : void; + b() : void; + a() : void; + + [a: string] : number; // Will be ignored (no sortable identifier) + () : Baz; // Will be ignored (no sortable identifier) + + new () : Bar; +} + `, + options: [{ default: { order: 'alphabetically' } }], + errors: [ + { + messageId: 'incorrectOrder', + data: { + member: 'b', + beforeMember: 'c', + }, + }, + { + messageId: 'incorrectOrder', + data: { + member: 'a', + beforeMember: 'b', + }, + }, + { + messageId: 'incorrectGroupOrder', + data: { + name: 'new', + rank: 'method', + }, + }, + ], + }, + ], +}; + +const sortedWithoutGrouping = { + valid: [ + ...sortedWithoutGroupingDefaultOption.valid, + ...sortedWithoutGroupingClassesOption.valid, + ...sortedWithoutGroupingClassExpressionsOption.valid, + ...sortedWithoutGroupingInterfacesOption.valid, + ...sortedWithoutGroupingTypeLiteralsOption.valid, + ], + invalid: [ + ...sortedWithoutGroupingDefaultOption.invalid, + ...sortedWithoutGroupingClassesOption.invalid, + ...sortedWithoutGroupingClassExpressionsOption.invalid, + ...sortedWithoutGroupingInterfacesOption.invalid, + ...sortedWithoutGroupingTypeLiteralsOption.invalid, + ], +}; + +const sortedWithGrouping = { + valid: [ + ...sortedWithGroupingDefaultOption.valid, + ...sortedWithGroupingClassesOption.valid, + ...sortedWithGroupingClassExpressionsOption.valid, + ...sortedWithGroupingInterfacesOption.valid, + ...sortedWithGroupingTypeLiteralsOption.valid, + ], + invalid: [ + ...sortedWithGroupingDefaultOption.invalid, + ...sortedWithGroupingClassesOption.invalid, + ...sortedWithGroupingClassExpressionsOption.invalid, + ...sortedWithGroupingInterfacesOption.invalid, + ...sortedWithGroupingTypeLiteralsOption.invalid, + ], +}; + +ruleTester.run('member-ordering', rule, { + valid: [ + ...grouped.valid, + ...sortedWithoutGrouping.valid, + ...sortedWithGrouping.valid, + ], + invalid: [ + ...grouped.invalid, + ...sortedWithoutGrouping.invalid, + ...sortedWithGrouping.invalid, + ], }); diff --git a/packages/eslint-plugin/tests/rules/sort-interface-members.test.ts b/packages/eslint-plugin/tests/rules/sort-interface-members.test.ts new file mode 100644 index 000000000000..4a45ee31f2b6 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/sort-interface-members.test.ts @@ -0,0 +1,80 @@ +import { RuleTester } from '../RuleTester'; +import rule from '../../src/rules/sort-interface-members'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +// TODO Add tests for lowercase/uppercase letters +// TODO Add tests for special characters +// TODO Add tests for the order of the groups +// TODO Add tests for the order inside of the groups +ruleTester.run('sort-interface-members', rule, { + valid: [ + ` +interface Foo { + a : string; + a() : string; + [a: string]: string; + new () : A; + () : A; +} + `, + ` +interface Foo { + a : string; + a : string; + a() : string; + a() : string; + [a: string]: string; + [a: string]: string; + new () : A; + new () : A; + () : A; + () : A; +} + `, + ` +interface Foo { + a : string; + b : string; + a() : string; + b() : string; + [a: string]: string; + [b: string]: string; + new () : A; + new () : B; + () : A; + () : B; +} + `, + ], + invalid: [ + { + code: ` +interface Foo { + a() : string; + a : string; +} + `, + errors: [ + { + messageId: 'notSorted', + }, + ], + }, + { + code: ` +interface Foo { + b : string; + a : string; +} + `, + errors: [ + { + messageId: 'notSorted', + }, + ], + }, + ], +});