diff --git a/docs/content/rules/sort-maps.mdx b/docs/content/rules/sort-maps.mdx index 6b84bfcc..98459d28 100644 --- a/docs/content/rules/sort-maps.mdx +++ b/docs/content/rules/sort-maps.mdx @@ -107,6 +107,43 @@ 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 maps 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 a map if there is an empty line between them. This can be useful for keeping logically separated groups of members in their defined order. + +```ts +new Map([ + // Group 1 + ['Drone', 0], + ['Keyboard', 1], + ['Mouse', 3], + ['Smartphone', 4], + + // Group 2 + ['Laptop', 5], + ['Monitor', 6], + ['Smartwatch', 7], + ['Tablet', 8], + + // Group 3 + ['Headphones', 9], + ['Router', 10], + ]) +``` + ## Usage , @@ -52,6 +57,29 @@ export default createEslintRule({ 'Controls whether sorting should be case-sensitive or not.', type: 'boolean', }, + partitionByComment: { + description: + 'Allows you to use comments to separate the maps 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.', + type: 'boolean', + }, }, additionalProperties: false, }, @@ -66,6 +94,8 @@ export default createEslintRule({ type: 'alphabetical', order: 'asc', ignoreCase: true, + partitionByComment: false, + partitionByNewLine: false, }, ], create: context => ({ @@ -85,9 +115,12 @@ export default createEslintRule({ type: 'alphabetical', ignoreCase: true, order: 'asc', + partitionByComment: false, + partitionByNewLine: false, } as const) let sourceCode = getSourceCode(context) + let partitionComment = options.partitionByComment let parts: TSESTree.Expression[][] = elements.reduce( ( @@ -105,7 +138,8 @@ export default createEslintRule({ ) for (let part of parts) { - let nodes: SortingNode[] = part.map(element => { + let formattedMembers: SortingNode[][] = [[]] + for (let element of part) { let name: string if (element.type === 'ArrayExpression') { @@ -122,32 +156,53 @@ export default createEslintRule({ name = sourceCode.text.slice(...element.range) } - return { + let lastSortingNode = formattedMembers.at(-1)?.at(-1) + let sortingNode: SortingNode = { size: rangeToDiff(element.range), node: element, name, } - }) - - pairwise(nodes, (left, right) => { - if (isPositive(compare(left, right, options))) { - context.report({ - messageId: 'unexpectedMapElementsOrder', - data: { - left: toSingleLine(left.name), - right: toSingleLine(right.name), - }, - node: right.node, - fix: fixer => - makeFixes( - fixer, - nodes, - sortNodes(nodes, options), - sourceCode, - ), - }) + + if ( + (partitionComment && + hasPartitionComment( + partitionComment, + getCommentsBefore(element, sourceCode), + )) || + (options.partitionByNewLine && + lastSortingNode && + getLinesBetween(sourceCode, lastSortingNode, sortingNode)) + ) { + formattedMembers.push([]) } - }) + + formattedMembers.at(-1)!.push(sortingNode) + } + + for (let nodes of formattedMembers) { + pairwise(nodes, (left, right) => { + if (isPositive(compare(left, right, options))) { + context.report({ + messageId: 'unexpectedMapElementsOrder', + data: { + left: toSingleLine(left.name), + right: toSingleLine(right.name), + }, + node: right.node, + fix: fixer => + makeFixes( + fixer, + nodes, + sortNodes(nodes, options), + sourceCode, + { + partitionComment, + }, + ), + }) + } + }) + } } } } diff --git a/test/sort-maps.test.ts b/test/sort-maps.test.ts index 9f58841d..d67a19b3 100644 --- a/test/sort-maps.test.ts +++ b/test/sort-maps.test.ts @@ -237,6 +237,209 @@ describe(ruleName, () => { }, ], }) + + ruleTester.run( + `${ruleName}(${type}): allows to use new line as partition`, + rule, + { + valid: [], + invalid: [ + { + code: dedent` + new Map([ + [d, 'd'], + [a, 'a'], + + [c, 'c'], + + [e, 'e'], + [b, 'b'], + ]) + `, + output: dedent` + new Map([ + [a, 'a'], + [d, 'd'], + + [c, 'c'], + + [b, 'b'], + [e, 'e'], + ]) + `, + options: [ + { + ...options, + partitionByNewLine: true, + }, + ], + errors: [ + { + messageId: 'unexpectedMapElementsOrder', + data: { + left: 'd', + right: 'a', + }, + }, + { + messageId: 'unexpectedMapElementsOrder', + data: { + left: 'e', + right: 'b', + }, + }, + ], + }, + ], + }, + ) + + describe(`${ruleName}(${type}): partition comments`, () => { + ruleTester.run( + `${ruleName}(${type}): allows to use partition comments`, + rule, + { + valid: [], + invalid: [ + { + code: dedent` + new Map([ + // Part: A + [cc, 'cc'], + [d, 'd'], + // Not partition comment + [bbb, 'bbb'], + // Part: B + [aaaa, 'aaaa'], + [e, 'e'], + // Part: C + [gg, 'gg'], + // Not partition comment + [fff, 'fff'], + ]) + `, + output: dedent` + new Map([ + // Part: A + // Not partition comment + [bbb, 'bbb'], + [cc, 'cc'], + [d, 'd'], + // Part: B + [aaaa, 'aaaa'], + [e, 'e'], + // Part: C + // Not partition comment + [fff, 'fff'], + [gg, 'gg'], + ]) + `, + options: [ + { + ...options, + partitionByComment: 'Part**', + }, + ], + errors: [ + { + messageId: 'unexpectedMapElementsOrder', + data: { + left: 'd', + right: 'bbb', + }, + }, + { + messageId: 'unexpectedMapElementsOrder', + data: { + left: 'gg', + right: 'fff', + }, + }, + ], + }, + ], + }, + ) + + ruleTester.run( + `${ruleName}(${type}): allows to use all comments as parts`, + rule, + { + valid: [ + { + code: dedent` + new Map([ + // Comment + [bb, 'bb'], + // Other comment + [a, 'a'], + ]) + `, + options: [ + { + ...options, + partitionByComment: true, + }, + ], + }, + ], + invalid: [], + }, + ) + + ruleTester.run( + `${ruleName}(${type}): allows to use multiple partition comments`, + rule, + { + valid: [], + invalid: [ + { + code: dedent` + new Map([ + /* Partition Comment */ + // Part: A + [d, 'd'], + // Part: B + [aaa, 'aaa'], + [c, 'c'], + [bb, 'bb'], + /* Other */ + [e, 'e'], + ]) + `, + output: dedent` + new Map([ + /* Partition Comment */ + // Part: A + [d, 'd'], + // Part: B + [aaa, 'aaa'], + [bb, 'bb'], + [c, 'c'], + /* Other */ + [e, 'e'], + ]) + `, + options: [ + { + ...options, + partitionByComment: ['Partition Comment', 'Part: *', 'Other'], + }, + ], + errors: [ + { + messageId: 'unexpectedMapElementsOrder', + data: { + left: 'c', + right: 'bb', + }, + }, + ], + }, + ], + }, + ) + }) }) describe(`${ruleName}: sorting by natural order`, () => {