diff --git a/docs/content/rules/sort-object-types.mdx b/docs/content/rules/sort-object-types.mdx index 2eec880b8..df7730419 100644 --- a/docs/content/rules/sort-object-types.mdx +++ b/docs/content/rules/sort-object-types.mdx @@ -123,11 +123,22 @@ Controls whether sorting should be case-sensitive or not. - `true` — Ignore case when sorting alphabetically or naturally (e.g., “A” and “a” are the same). - `false` — Consider case when sorting (e.g., “A” comes before “a”). +### partitionByComment + +default: `false` + +Allows you to use comments to separate the members of types into logical groups. This can help in organizing and maintaining large enums by creating partitions within the enum based on comments. + +- `true` — All comments will be treated as delimiters, creating partitions. +- `false` — Comments will not be used as delimiters. +- `string` — A glob pattern to specify which comments should act as delimiters. +- `string[]` — A list of glob patterns to specify which comments should act as delimiters. + ### partitionByNewLine default: `false` -When `true`, the rule will not sort the members of an interface if there is an empty line between them. This can be useful for keeping logically separated groups of members in their defined order. +When `true`, the rule will not sort the members of an object if there is an empty line between them. This can be useful for keeping logically separated groups of members in their defined order. ```ts type User = { @@ -215,6 +226,7 @@ Example: type: 'alphabetical', order: 'asc', ignoreCase: true, + partitionByComment: false, partitionByNewLine: false, groups: [], customGroups: {}, @@ -241,6 +253,7 @@ Example: type: 'alphabetical', order: 'asc', ignoreCase: true, + partitionByComment: false, partitionByNewLine: false, groups: [], customGroups: {}, diff --git a/rules/sort-object-types.ts b/rules/sort-object-types.ts index bcab50822..6fd3d2c43 100644 --- a/rules/sort-object-types.ts +++ b/rules/sort-object-types.ts @@ -3,7 +3,9 @@ import type { TSESTree } from '@typescript-eslint/types' import type { SortingNode } from '../typings' import { validateGroupsConfiguration } from '../utils/validate-groups-configuration' +import { hasPartitionComment } from '../utils/is-partition-comment' import { sortNodesByGroups } from '../utils/sort-nodes-by-groups' +import { getCommentsBefore } from '../utils/get-comments-before' import { createEslintRule } from '../utils/create-eslint-rule' import { getLinesBetween } from '../utils/get-lines-between' import { getGroupNumber } from '../utils/get-group-number' @@ -27,6 +29,7 @@ type Options = [ groupKind: 'required-first' | 'optional-first' | 'mixed' customGroups: { [key in T[number]]: string[] | string } type: 'alphabetical' | 'line-length' | 'natural' + partitionByComment: string[] | boolean | string groups: (Group[] | Group)[] partitionByNewLine: boolean order: 'desc' | 'asc' @@ -34,6 +37,8 @@ type Options = [ }>, ] +type SortObjectTypesSortingNode = SortingNode + export default createEslintRule, MESSAGE_ID>({ name: 'sort-object-types', meta: { @@ -62,6 +67,24 @@ export default createEslintRule, MESSAGE_ID>({ 'Controls whether sorting should be case-sensitive or not.', type: 'boolean', }, + partitionByComment: { + description: + 'Allows you to use comments to separate the type members into logical groups.', + anyOf: [ + { + type: 'array', + items: { + type: 'string', + }, + }, + { + type: 'boolean', + }, + { + type: 'string', + }, + ], + }, partitionByNewLine: { description: 'Allows to use spaces to separate the nodes into logical groups.', @@ -122,6 +145,7 @@ export default createEslintRule, MESSAGE_ID>({ type: 'alphabetical', order: 'asc', ignoreCase: true, + partitionByComment: false, partitionByNewLine: false, groupKind: 'mixed', groups: [], @@ -134,6 +158,7 @@ export default createEslintRule, MESSAGE_ID>({ let settings = getSettings(context.settings) let options = complete(context.options.at(0), settings, { + partitionByComment: false, partitionByNewLine: false, type: 'alphabetical', groupKind: 'mixed', @@ -150,16 +175,17 @@ export default createEslintRule, MESSAGE_ID>({ ) let sourceCode = getSourceCode(context) + let partitionComment = options.partitionByComment - let formattedMembers: SortingNode[][] = + let formattedMembers: SortObjectTypesSortingNode[][] = node.members.reduce( - (accumulator: SortingNode[][], member) => { + (accumulator: SortObjectTypesSortingNode[][], member) => { let name: string let raw = sourceCode.text.slice( member.range.at(0), member.range.at(1), ) - let lastMember = accumulator.at(-1)?.at(-1) + let lastSortingNode = accumulator.at(-1)?.at(-1) let { getGroup, defineGroup, setCustomGroups } = useGroups( options.groups, @@ -201,24 +227,27 @@ export default createEslintRule, MESSAGE_ID>({ let endsWithComma = raw.endsWith(';') || raw.endsWith(',') let endSize = endsWithComma ? 1 : 0 - let memberSortingNode = { + let sortingNode: SortObjectTypesSortingNode = { size: rangeToDiff(member.range) - endSize, + group: getGroup(), node: member, name, } if ( - options.partitionByNewLine && - lastMember && - getLinesBetween(sourceCode, lastMember, memberSortingNode) + (partitionComment && + hasPartitionComment( + partitionComment, + getCommentsBefore(member, sourceCode), + )) || + (options.partitionByNewLine && + lastSortingNode && + getLinesBetween(sourceCode, lastSortingNode, sortingNode)) ) { accumulator.push([]) } - accumulator.at(-1)?.push({ - ...memberSortingNode, - group: getGroup(), - }) + accumulator.at(-1)?.push(sortingNode) return accumulator }, @@ -228,7 +257,7 @@ export default createEslintRule, MESSAGE_ID>({ for (let nodes of formattedMembers) { let groupedByKind if (options.groupKind !== 'mixed') { - groupedByKind = nodes.reduce[][]>( + groupedByKind = nodes.reduce( (accumulator, currentNode) => { let requiredIndex = options.groupKind === 'required-first' ? 0 : 1 @@ -251,7 +280,7 @@ export default createEslintRule, MESSAGE_ID>({ groupedByKind = [nodes] } - let sortedNodes: SortingNode[] = [] + let sortedNodes: SortObjectTypesSortingNode[] = [] for (let nodesByKind of groupedByKind) { sortedNodes.push(...sortNodesByGroups(nodesByKind, options)) @@ -275,7 +304,10 @@ export default createEslintRule, MESSAGE_ID>({ rightGroup: right.group, }, node: right.node, - fix: fixer => makeFixes(fixer, nodes, sortedNodes, sourceCode), + fix: fixer => + makeFixes(fixer, nodes, sortedNodes, sourceCode, { + partitionComment, + }), }) } }) diff --git a/test/sort-object-types.test.ts b/test/sort-object-types.test.ts index 961cff28a..619785427 100644 --- a/test/sort-object-types.test.ts +++ b/test/sort-object-types.test.ts @@ -481,6 +481,153 @@ describe(ruleName, () => { }, ) + describe(`${ruleName}(${type}): partition comments`, () => { + ruleTester.run( + `${ruleName}(${type}): allows to use partition comments`, + rule, + { + valid: [], + invalid: [ + { + code: dedent` + type Type = { + // Part: A + cc: string + d: string + // Not partition comment + bbb: string + // Part: B + aaaa: string + e: string + // Part: C + 'gg': string + // Not partition comment + fff: string + } + `, + output: dedent` + type Type = { + // Part: A + // Not partition comment + bbb: string + cc: string + d: string + // Part: B + aaaa: string + e: string + // Part: C + // Not partition comment + fff: string + 'gg': string + } + `, + options: [ + { + ...options, + partitionByComment: 'Part**', + }, + ], + errors: [ + { + messageId: 'unexpectedObjectTypesOrder', + data: { + left: 'd', + right: 'bbb', + }, + }, + { + messageId: 'unexpectedObjectTypesOrder', + data: { + left: 'gg', + right: 'fff', + }, + }, + ], + }, + ], + }, + ) + + ruleTester.run( + `${ruleName}(${type}): allows to use all comments as parts`, + rule, + { + valid: [ + { + code: dedent` + type Type = { + // Comment + bb: string + // Other comment + a: string + } + `, + options: [ + { + ...options, + partitionByComment: true, + }, + ], + }, + ], + invalid: [], + }, + ) + + ruleTester.run( + `${ruleName}(${type}): allows to use multiple partition comments`, + rule, + { + valid: [], + invalid: [ + { + code: dedent` + type Type = { + /* Partition Comment */ + // Part: A + d: string + // Part: B + aaa: string + c: string + bb: string + /* Other */ + e: string + } + `, + output: dedent` + type Type = { + /* Partition Comment */ + // Part: A + d: string + // Part: B + aaa: string + bb: string + c: string + /* Other */ + e: string + } + `, + options: [ + { + ...options, + partitionByComment: ['Partition Comment', 'Part: *', 'Other'], + }, + ], + errors: [ + { + messageId: 'unexpectedObjectTypesOrder', + data: { + left: 'c', + right: 'bb', + }, + }, + ], + }, + ], + }, + ) + }) + ruleTester.run( `${ruleName}(${type}): allows to sort required values first`, rule,