From 1438df58340d44120f1998bd6487c380a5ba2a33 Mon Sep 17 00:00:00 2001 From: "hugo.prunaux" Date: Sun, 5 Jan 2025 23:56:41 +0400 Subject: [PATCH] feat(sort-maps): adds `useConfigurationIf.allNamesMatchPattern` --- docs/content/rules/sort-maps.mdx | 48 ++++++++++++++++++++ rules/sort-maps.ts | 71 ++++++++++++++++++++--------- rules/sort-maps/types.ts | 3 ++ test/rules/sort-maps.test.ts | 77 ++++++++++++++++++++++++++++++++ 4 files changed, 178 insertions(+), 21 deletions(-) diff --git a/docs/content/rules/sort-maps.mdx b/docs/content/rules/sort-maps.mdx index 2e0d2fd1f..0ecb6ba65 100644 --- a/docs/content/rules/sort-maps.mdx +++ b/docs/content/rules/sort-maps.mdx @@ -89,6 +89,7 @@ Specifies the sorting method. - `'natural'` — Sort items in a [natural](https://github.com/yobacca/natural-orderby) order (e.g., “item2” < “item10”). - `'line-length'` — Sort items by the length of the code line (shorter lines first). - `'custom'` — Sort items using the alphabet entered in the [`alphabet`](#alphabet) option. +- `'unsorted'` — Do not sort items. To be used with the [`useConfigurationIf`](#useconfigurationif) option. ### order @@ -189,6 +190,51 @@ Specifies how new lines should be handled between map members. This option is only applicable when `partitionByNewLine` is `false`. +### useConfigurationIf + + + type: `{ allNamesMatchPattern?: string }` + +default: `{}` + +Allows you to specify filters to match a particular options configuration for a given object. + +The first matching options configuration will be used. If no configuration matches, the default options configuration will be used. + +- `allNamesMatchPattern` — A regexp pattern that all object keys must match. + +Example configuration: +```ts +{ + 'perfectionist/sort-maps': [ + 'error', + { + groups: ['r', 'g', 'b'], // Sort colors by RGB + customGroups: [ + { + elementNamePattern: '^r$', + groupName: 'r', + }, + { + elementNamePattern: '^g$', + groupName: 'g', + }, + { + elementNamePattern: '^b$', + groupName: 'b', + }, + ], + useConfigurationIf: { + allNamesMatchPattern: '^r|g|b$', + }, + }, + { + type: 'alphabetical' // Fallback configuration + } + ], +} +``` + ### groups @@ -282,6 +328,7 @@ Custom groups have a higher priority than any predefined group. partitionByNewLine: false, partitionByComment: false, newlinesBetween: false, + useConfigurationIf: {}, groups: [], customGroups: [], }, @@ -311,6 +358,7 @@ Custom groups have a higher priority than any predefined group. partitionByNewLine: false, partitionByComment: false, newlinesBetween: false, + useConfigurationIf: {}, groups: [], customGroups: [], }, diff --git a/rules/sort-maps.ts b/rules/sort-maps.ts index 066134c69..e660465c7 100644 --- a/rules/sort-maps.ts +++ b/rules/sort-maps.ts @@ -1,9 +1,12 @@ -import type { TSESTree } from '@typescript-eslint/types' +import type { TSESLint } from '@typescript-eslint/utils' + +import { TSESTree } from '@typescript-eslint/types' import type { SortingNode } from '../types/sorting-node' import type { Options } from './sort-maps/types' import { + buildUseConfigurationIfJsonSchema, buildCustomGroupsArrayJsonSchema, partitionByCommentJsonSchema, partitionByNewLineJsonSchema, @@ -19,6 +22,7 @@ import { import { validateGeneratedGroupsConfiguration } from '../utils/validate-generated-groups-configuration' import { validateCustomSortConfiguration } from '../utils/validate-custom-sort-configuration' import { getCustomGroupsCompareOptions } from '../utils/get-custom-groups-compare-options' +import { getMatchingContextOptions } from '../utils/get-matching-context-options' import { getEslintDisabledLines } from '../utils/get-eslint-disabled-lines' import { doesCustomGroupMatch } from './sort-maps/does-custom-group-match' import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled' @@ -52,6 +56,7 @@ let defaultOptions: Required = { partitionByComment: false, partitionByNewLine: false, newlinesBetween: 'ignore', + useConfigurationIf: {}, type: 'alphabetical', ignoreCase: true, customGroups: [], @@ -77,8 +82,21 @@ export default createEslintRule({ return } + let sourceCode = getSourceCode(context) let settings = getSettings(context.settings) - let options = complete(context.options.at(0), settings, defaultOptions) + + let matchedContextOptions = getMatchingContextOptions({ + nodeNames: elements + .filter( + element => + element !== null && + element.type !== TSESTree.AST_NODE_TYPES.SpreadElement, + ) + .map(element => getNodeName({ sourceCode, element })), + contextOptions: context.options, + }) + + let options = complete(matchedContextOptions[0], settings, defaultOptions) validateCustomSortConfiguration(options) validateGeneratedGroupsConfiguration({ customGroups: options.customGroups, @@ -87,7 +105,6 @@ export default createEslintRule({ modifiers: [], }) - let sourceCode = getSourceCode(context) let eslintDisabledLines = getEslintDisabledLines({ ruleName: context.id, sourceCode, @@ -110,21 +127,10 @@ export default createEslintRule({ for (let part of parts) { let formattedMembers: SortingNode[][] = [[]] for (let element of part) { - let name: string - - if (element.type === 'ArrayExpression') { - let [left] = element.elements - - if (!left) { - name = `${left}` - } else if (left.type === 'Literal') { - name = left.raw - } else { - name = sourceCode.getText(left) - } - } else { - name = sourceCode.getText(element) - } + let name: string = getNodeName({ + sourceCode, + element, + }) let lastSortingNode = formattedMembers.at(-1)?.at(-1) @@ -254,8 +260,8 @@ export default createEslintRule({ }, }), meta: { - schema: [ - { + schema: { + items: { properties: { partitionByComment: { ...partitionByCommentJsonSchema, @@ -265,6 +271,7 @@ export default createEslintRule({ customGroups: buildCustomGroupsArrayJsonSchema({ singleCustomGroupJsonSchema, }), + useConfigurationIf: buildUseConfigurationIfJsonSchema(), partitionByNewLine: partitionByNewLineJsonSchema, specialCharacters: specialCharactersJsonSchema, newlinesBetween: newlinesBetweenJsonSchema, @@ -278,7 +285,9 @@ export default createEslintRule({ additionalProperties: false, type: 'object', }, - ], + uniqueItems: true, + type: 'array', + }, messages: { unexpectedMapElementsGroupOrder: 'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).', @@ -300,3 +309,23 @@ export default createEslintRule({ defaultOptions: [defaultOptions], name: 'sort-maps', }) + +let getNodeName = ({ + sourceCode, + element, +}: { + sourceCode: TSESLint.SourceCode + element: TSESTree.Expression +}): string => { + if (element.type === 'ArrayExpression') { + let [left] = element.elements + + if (!left) { + return `${left}` + } else if (left.type === 'Literal') { + return left.raw + } + return sourceCode.getText(left) + } + return sourceCode.getText(element) +} diff --git a/rules/sort-maps/types.ts b/rules/sort-maps/types.ts index 5e4635537..8946f21d5 100644 --- a/rules/sort-maps/types.ts +++ b/rules/sort-maps/types.ts @@ -16,6 +16,9 @@ export type Options = Partial<{ | Group[] | Group )[] + useConfigurationIf: { + allNamesMatchPattern?: string + } type: 'alphabetical' | 'line-length' | 'natural' | 'custom' newlinesBetween: 'ignore' | 'always' | 'never' specialCharacters: 'remove' | 'trim' | 'keep' diff --git a/test/rules/sort-maps.test.ts b/test/rules/sort-maps.test.ts index b59911d45..45dc1a2cf 100644 --- a/test/rules/sort-maps.test.ts +++ b/test/rules/sort-maps.test.ts @@ -1351,6 +1351,83 @@ describe(ruleName, () => { }, ) }) + + describe(`${ruleName}(${type}): allows to use 'useConfigurationIf'`, () => { + ruleTester.run( + `${ruleName}(${type}): allows to use 'allNamesMatchPattern'`, + rule, + { + invalid: [ + { + options: [ + { + ...options, + useConfigurationIf: { + allNamesMatchPattern: 'foo', + }, + }, + { + ...options, + customGroups: [ + { + elementNamePattern: '^r$', + groupName: 'r', + }, + { + elementNamePattern: '^g$', + groupName: 'g', + }, + { + elementNamePattern: '^b$', + groupName: 'b', + }, + ], + useConfigurationIf: { + allNamesMatchPattern: '^r|g|b$', + }, + groups: ['r', 'g', 'b'], + }, + ], + errors: [ + { + data: { + rightGroup: 'g', + leftGroup: 'b', + right: 'g', + left: 'b', + }, + messageId: 'unexpectedMapElementsGroupOrder', + }, + { + data: { + rightGroup: 'r', + leftGroup: 'g', + right: 'r', + left: 'g', + }, + messageId: 'unexpectedMapElementsGroupOrder', + }, + ], + output: dedent` + new Map([ + [r, null], + [g, null], + [b, null] + ]) + `, + code: dedent` + new Map([ + [b, null], + [g, null], + [r, null] + ]) + `, + }, + ], + valid: [], + }, + ) + }) }) describe(`${ruleName}: sorting by natural order`, () => {