From aa293358d0d1fcd655ac0a73761a3aecdc33c7c4 Mon Sep 17 00:00:00 2001 From: Hugo <60015232+hugop95@users.noreply.github.com> Date: Sun, 22 Sep 2024 16:08:28 +0200 Subject: [PATCH] feat: add partition by comment and partition by new line in sort-variable-declarations --- .../rules/sort-variable-declarations.mdx | 36 ++++ rules/sort-variable-declarations.ts | 67 +++++- test/sort-variable-declarations.test.ts | 196 ++++++++++++++++++ 3 files changed, 294 insertions(+), 5 deletions(-) diff --git a/docs/content/rules/sort-variable-declarations.mdx b/docs/content/rules/sort-variable-declarations.mdx index f2318663c..d7e9d4ce0 100644 --- a/docs/content/rules/sort-variable-declarations.mdx +++ b/docs/content/rules/sort-variable-declarations.mdx @@ -106,6 +106,38 @@ 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 variable declarations 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 variable declaration if there is an empty line between them. This can be useful for keeping logically separated groups of members in their defined order. + +```ts +const + // Group 1 + fiat = "Fiat", + honda = "Honda", + + // Group 2 + ferrari = "Ferrari", + + // Group 3 + chevrolet = "Chevrolet", + ford = "Ford" +``` + ## Usage , @@ -56,6 +61,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 variable declarations 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, }, @@ -72,6 +100,8 @@ export default createEslintRule({ type: 'alphabetical', order: 'asc', ignoreCase: true, + partitionByComment: false, + partitionByNewLine: false, }, ], create: context => ({ @@ -82,10 +112,13 @@ export default createEslintRule({ let options = complete(context.options.at(0), settings, { type: 'alphabetical', ignoreCase: true, + partitionByNewLine: false, + partitionByComment: false, order: 'asc', } as const) let sourceCode = getSourceCode(context) + let partitionComment = options.partitionByComment let extractDependencies = (init: TSESTree.Expression): string[] => { let dependencies: string[] = [] @@ -170,8 +203,8 @@ export default createEslintRule({ return dependencies } - let nodes = node.declarations.map( - (declaration): SortingNodeWithDependencies => { + let formattedMembers = node.declarations.reduce( + (accumulator: SortingNodeWithDependencies[][], declaration) => { let name if ( @@ -188,16 +221,37 @@ export default createEslintRule({ dependencies = extractDependencies(declaration.init) } - return { + let lastSortingNode = accumulator.at(-1)?.at(-1) + let sortingNode: SortingNodeWithDependencies = { size: rangeToDiff(declaration.range), node: declaration, dependencies, name, } + if ( + (partitionComment && + hasPartitionComment( + partitionComment, + getCommentsBefore(declaration, sourceCode), + )) || + (options.partitionByNewLine && + lastSortingNode && + getLinesBetween(sourceCode, lastSortingNode, sortingNode)) + ) { + accumulator.push([]) + } + + accumulator.at(-1)?.push(sortingNode) + + return accumulator }, + [[]], ) - let sortedNodes = sortNodesByDependencies(sortNodes(nodes, options)) + let sortedNodes = sortNodesByDependencies( + formattedMembers.map(nodes => sortNodes(nodes, options)).flat(), + ) + let nodes = formattedMembers.flat() pairwise(nodes, (left, right) => { let indexOfLeft = sortedNodes.indexOf(left) let indexOfRight = sortedNodes.indexOf(right) @@ -214,7 +268,10 @@ export default createEslintRule({ nodeDependentOnRight: firstUnorderedNodeDependentOnRight?.name, }, node: right.node, - fix: fixer => makeFixes(fixer, nodes, sortedNodes, sourceCode), + fix: fixer => + makeFixes(fixer, nodes, sortedNodes, sourceCode, { + partitionComment, + }), }) } }) diff --git a/test/sort-variable-declarations.test.ts b/test/sort-variable-declarations.test.ts index 4861bfe34..378d70fa9 100644 --- a/test/sort-variable-declarations.test.ts +++ b/test/sort-variable-declarations.test.ts @@ -649,6 +649,202 @@ describe(ruleName, () => { invalid: [], }, ) + + ruleTester.run( + `${ruleName}(${type}): allows to use new line as partition`, + rule, + { + valid: [], + invalid: [ + { + code: dedent` + const + d = 'D', + a = 'A', + + c = 'C', + + e = 'E', + b = 'B' + `, + output: dedent` + const + a = 'A', + d = 'D', + + c = 'C', + + b = 'B', + e = 'E' + `, + options: [ + { + type: 'alphabetical', + partitionByNewLine: true, + }, + ], + errors: [ + { + messageId: 'unexpectedVariableDeclarationsOrder', + data: { + left: 'd', + right: 'a', + }, + }, + { + messageId: 'unexpectedVariableDeclarationsOrder', + data: { + left: 'e', + right: 'b', + }, + }, + ], + }, + ], + }, + ) + + describe(`${ruleName}(${type}): partition comments`, () => { + ruleTester.run( + `${ruleName}(${type}): allows to use partition comments`, + rule, + { + valid: [], + invalid: [ + { + code: dedent` + const + // Part: A + cc = 'CC', + d = 'D', + // Not partition comment + bbb = 'BBB', + // Part: B + aaa = 'AAA', + e = 'E', + // Part: C + gg = 'GG', + // Not partition comment + fff = 'FFF' + `, + output: dedent` + const + // Part: A + // Not partition comment + bbb = 'BBB', + cc = 'CC', + d = 'D', + // Part: B + aaa = 'AAA', + e = 'E', + // Part: C + // Not partition comment + fff = 'FFF', + gg = 'GG' + `, + options: [ + { + ...options, + partitionByComment: 'Part**', + }, + ], + errors: [ + { + messageId: 'unexpectedVariableDeclarationsOrder', + data: { + left: 'd', + right: 'bbb', + }, + }, + { + messageId: 'unexpectedVariableDeclarationsOrder', + data: { + left: 'gg', + right: 'fff', + }, + }, + ], + }, + ], + }, + ) + + ruleTester.run( + `${ruleName}(${type}): allows to use all comments as parts`, + rule, + { + valid: [ + { + code: dedent` + const + // 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` + const + /* Partition Comment */ + // Part: A + d = 'D', + // Part: B + aaa = 'AAA', + c = 'C', + bb = 'BB', + /* Other */ + e = 'E' + `, + output: dedent` + const + /* 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: 'unexpectedVariableDeclarationsOrder', + data: { + left: 'c', + right: 'bb', + }, + }, + ], + }, + ], + }, + ) + }) }) describe(`${ruleName}: sorting by natural order`, () => {