From d6d972c4a2bb4dff76dc601cc744b46a3e3aac3c 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 | 15 +- .../src/rules/member-ordering.ts | 654 +++-- .../src/rules/sort-interface-members.ts | 199 ++ .../tests/rules/member-ordering.test.ts | 2562 ++++++++++++++++- .../rules/sort-interface-members.test.ts | 80 + 5 files changed, 3113 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..1cac086db183 100644 --- a/packages/eslint-plugin/docs/rules/member-ordering.md +++ b/packages/eslint-plugin/docs/rules/member-ordering.md @@ -7,13 +7,17 @@ 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 @@ -26,14 +30,15 @@ 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, order?: 'alphabetically' } | never + classes?: Array | { memberTypes?: Array, order?: 'alphabetically' } | never + classExpressions?: Array | { memberTypes?: Array, order?: 'alphabetically' } | never - interfaces?: { memberTypes?: Array, order?: 'alphabetically' } | never - typeLiterals?: { memberTypes?: Array, order?: 'alphabetically' } | never + interfaces?: ['field' | 'method' | 'constructor'] | { memberTypes?: Array, order?: 'alphabetically' } | never + typeLiterals?: ['field' | 'method' | 'constructor'] | { memberTypes?: Array, order?: 'alphabetically' } | never } ``` diff --git a/packages/eslint-plugin/src/rules/member-ordering.ts b/packages/eslint-plugin/src/rules/member-ordering.ts index 72020afc6008..5227c3992bf4 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,217 @@ 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 { + // 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; + } +} + +// TODO Improve documentation +/** + * 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; + } +} + +// TODO Document +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; + } +} + +// TODO Improve documentation +/** + * 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; +} + +// TODO Improve documentation +/** + * 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); +} + +// TODO Improve documentation +/** + * 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 +308,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 +318,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 +358,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..846081cbeee6 100644 --- a/packages/eslint-plugin/tests/rules/member-ordering.test.ts +++ b/packages/eslint-plugin/tests/rules/member-ordering.test.ts @@ -1,11 +1,16 @@ import rule from '../../src/rules/member-ordering'; import { RuleTester } from '../RuleTester'; +import { TSESLint } from '@typescript-eslint/experimental-utils'; + +// TODO Document sorting options +// TODO Check if all // Will be ignored (no sortable identifier) options are valid +// TODO Check if all tests make sense + document in MR const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', }); -ruleTester.run('member-ordering', rule, { +const grouped: TSESLint.RunTests> = { valid: [ ` // no accessibility === public @@ -1240,7 +1245,7 @@ interface Foo { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'method', @@ -1272,7 +1277,7 @@ interface Foo { options: [{ default: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -1281,7 +1286,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -1290,7 +1295,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -1299,7 +1304,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -1308,7 +1313,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -1317,7 +1322,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -1326,7 +1331,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'field', @@ -1358,7 +1363,7 @@ interface Foo { options: [{ interfaces: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -1367,7 +1372,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -1376,7 +1381,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -1385,7 +1390,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -1394,7 +1399,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -1403,7 +1408,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -1412,7 +1417,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'field', @@ -1449,7 +1454,7 @@ interface Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -1458,7 +1463,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -1467,7 +1472,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -1476,7 +1481,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -1485,7 +1490,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -1494,7 +1499,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -1503,7 +1508,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'field', @@ -1539,7 +1544,7 @@ interface Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'B', rank: 'method', @@ -1548,7 +1553,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'C', rank: 'method', @@ -1557,7 +1562,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'D', rank: 'method', @@ -1566,7 +1571,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'E', rank: 'method', @@ -1575,7 +1580,7 @@ interface Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'F', rank: 'method', @@ -1606,7 +1611,7 @@ type Foo = { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'method', @@ -1638,7 +1643,7 @@ type Foo = { options: [{ default: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -1647,7 +1652,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -1656,7 +1661,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -1665,7 +1670,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -1674,7 +1679,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -1683,7 +1688,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -1692,7 +1697,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'field', @@ -1724,7 +1729,7 @@ type Foo = { options: [{ typeLiterals: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -1733,7 +1738,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -1742,7 +1747,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -1751,7 +1756,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -1760,7 +1765,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -1769,7 +1774,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -1778,7 +1783,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'field', @@ -1815,7 +1820,7 @@ type Foo = { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -1824,7 +1829,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -1833,7 +1838,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -1842,7 +1847,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -1851,7 +1856,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -1860,7 +1865,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -1869,7 +1874,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'new', rank: 'field', @@ -1905,7 +1910,7 @@ type Foo = { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'B', rank: 'method', @@ -1914,7 +1919,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'C', rank: 'method', @@ -1923,7 +1928,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'D', rank: 'method', @@ -1932,7 +1937,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'E', rank: 'method', @@ -1941,7 +1946,7 @@ type Foo = { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'F', rank: 'method', @@ -1971,7 +1976,7 @@ class Foo { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'public instance method', @@ -1980,7 +1985,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'public instance method', @@ -1989,7 +1994,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'public instance method', @@ -2020,7 +2025,7 @@ class Foo { options: [{ default: ['field', 'constructor', 'method'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'constructor', @@ -2029,7 +2034,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'B', rank: 'constructor', @@ -2038,7 +2043,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'C', rank: 'constructor', @@ -2047,7 +2052,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'D', rank: 'constructor', @@ -2056,7 +2061,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'E', rank: 'constructor', @@ -2065,7 +2070,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'F', rank: 'constructor', @@ -2096,7 +2101,7 @@ class Foo { options: [{ default: ['field', 'method'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'method', @@ -2127,7 +2132,7 @@ class Foo { options: [{ default: ['method', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -2158,7 +2163,7 @@ class Foo { options: [{ classes: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -2167,7 +2172,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -2176,7 +2181,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -2185,7 +2190,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -2194,7 +2199,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'field', @@ -2230,7 +2235,7 @@ class Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -2239,7 +2244,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -2248,7 +2253,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -2257,7 +2262,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -2266,7 +2271,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -2275,7 +2280,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -2284,7 +2289,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'field', @@ -2327,7 +2332,7 @@ class Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'private field', @@ -2336,7 +2341,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'F', rank: 'protected field', @@ -2380,7 +2385,7 @@ class Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'public instance method', @@ -2389,7 +2394,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'public field', @@ -2430,7 +2435,7 @@ class Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'method', @@ -2473,7 +2478,7 @@ class Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'private static method', @@ -2482,7 +2487,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'private static method', @@ -2517,7 +2522,7 @@ class Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'protected static field', @@ -2553,7 +2558,7 @@ class Foo { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'protected static field', @@ -2583,7 +2588,7 @@ const foo = class Foo { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'public instance method', @@ -2592,7 +2597,7 @@ const foo = class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'public instance method', @@ -2601,7 +2606,7 @@ const foo = class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'public instance method', @@ -2632,7 +2637,7 @@ const foo = class { options: [{ default: ['field', 'constructor', 'method'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'constructor', @@ -2641,7 +2646,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'B', rank: 'constructor', @@ -2650,7 +2655,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'C', rank: 'constructor', @@ -2659,7 +2664,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'D', rank: 'constructor', @@ -2668,7 +2673,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'E', rank: 'constructor', @@ -2677,7 +2682,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'F', rank: 'constructor', @@ -2708,7 +2713,7 @@ const foo = class { options: [{ default: ['field', 'method'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'method', @@ -2739,7 +2744,7 @@ const foo = class { options: [{ default: ['method', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -2770,7 +2775,7 @@ const foo = class { options: [{ classExpressions: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -2779,7 +2784,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -2788,7 +2793,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -2797,7 +2802,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -2806,7 +2811,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'field', @@ -2842,7 +2847,7 @@ const foo = class { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'field', @@ -2851,7 +2856,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'field', @@ -2860,7 +2865,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'I', rank: 'field', @@ -2869,7 +2874,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -2878,7 +2883,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'field', @@ -2887,7 +2892,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'field', @@ -2896,7 +2901,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'field', @@ -2939,7 +2944,7 @@ const foo = class { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'private field', @@ -2948,7 +2953,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'F', rank: 'protected field', @@ -2992,7 +2997,7 @@ const foo = class { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'public instance method', @@ -3001,7 +3006,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'public field', @@ -3042,7 +3047,7 @@ const foo = class { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'method', @@ -3085,7 +3090,7 @@ const foo = class { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'G', rank: 'private static method', @@ -3094,7 +3099,7 @@ const foo = class { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'H', rank: 'private static method', @@ -3133,7 +3138,7 @@ const foo = class { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'L', rank: 'protected static field', @@ -3169,7 +3174,7 @@ const foo = class { ], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'protected static field', @@ -3191,7 +3196,7 @@ class Foo { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'public instance method', @@ -3200,7 +3205,7 @@ class Foo { column: 5, }, { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'constructor', rank: 'public instance method', @@ -3222,7 +3227,7 @@ class Foo { options: [{ default: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'constructor', @@ -3245,7 +3250,7 @@ class Foo { options: [{ default: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'K', rank: 'constructor', @@ -3265,7 +3270,7 @@ interface Foo { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'method', @@ -3285,7 +3290,7 @@ type Foo = { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'A', rank: 'method', @@ -3306,7 +3311,7 @@ type Foo = { options: [{ default: ['method', 'constructor', 'field'] }], errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'J', rank: 'field', @@ -3325,7 +3330,7 @@ abstract class Foo { `, errors: [ { - messageId: 'incorrectOrder', + messageId: 'incorrectGroupOrder', data: { name: 'B', rank: 'method', @@ -3336,4 +3341,2307 @@ abstract class Foo { ], }, ], +}; + +const sortedWithoutGroupingDefaultOption: TSESLint.RunTests< + string, + 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< + string, + 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< + string, + 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< + string, + 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< + string, + 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< + string, + 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< + string, + 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< + string, + 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< + string, + 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< + string, + 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, + ], + // TODO Fix typing issue + invalid: [ + ...(sortedWithoutGroupingDefaultOption.invalid as any[]), + ...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: [ + // TODO Fix typing issue + ...(grouped.invalid as any[]), + ...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', + }, + ], + }, + ], +});