From 47de023872076fa903c297dbb49aa33f867279d7 Mon Sep 17 00:00:00 2001 From: "hugo.prunaux" Date: Tue, 3 Dec 2024 01:21:55 +0100 Subject: [PATCH 1/4] feat(sort-object-types): adds `useConfigurationIf.allNamesMatchPattern` - Also applies to `sort-interfaces`. --- docs/content/rules/sort-interfaces.mdx | 36 +++++ docs/content/rules/sort-object-types.mdx | 36 +++++ rules/sort-interfaces.ts | 3 +- rules/sort-object-types.ts | 163 +++++++++++++---------- rules/sort-object-types.types.ts | 41 +++--- test/sort-interfaces.test.ts | 68 ++++++++++ test/sort-object-types.test.ts | 68 ++++++++++ 7 files changed, 324 insertions(+), 91 deletions(-) diff --git a/docs/content/rules/sort-interfaces.mdx b/docs/content/rules/sort-interfaces.mdx index 4947477ec..386577ec8 100644 --- a/docs/content/rules/sort-interfaces.mdx +++ b/docs/content/rules/sort-interfaces.mdx @@ -258,6 +258,42 @@ Specifies how optional and required members should be ordered in TypeScript inte - `'required-first'` — Put all required members before optional members. - `'mixed'` — Do not enforce any specific order based on optionality. +### useConfigurationIf + + + type: `{ allNamesMatchPattern?: string }` + +default: `{}` + +Allows you to specify filters to match a particular options configuration for a given interface. + +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 keys must match. + +Example configuration: +```ts +{ + 'perfectionist/sort-interfaces': [ + 'error', + { + groups: ['r', 'g', 'b'], // Sort colors types by RGB + customGroups: { + r: '^r$', + g: '^g$', + b: '^b$', + }, + useConfigurationIf: { + allNamesMatchPattern: '^r|g|b$', + }, + }, + { + type: 'alphabetical' // Fallback configuration + } + ], +} +``` + ### groups diff --git a/docs/content/rules/sort-object-types.mdx b/docs/content/rules/sort-object-types.mdx index 4ee7dbcf6..42d231b62 100644 --- a/docs/content/rules/sort-object-types.mdx +++ b/docs/content/rules/sort-object-types.mdx @@ -223,6 +223,42 @@ Allows you to group type object keys by their kind, determining whether required - `required-first` — Group all required values before optional. - `optional-first` — Group all optional values before required. +### useConfigurationIf + + + type: `{ allNamesMatchPattern?: string }` + +default: `{}` + +Allows you to specify filters to match a particular options configuration for a given object type. + +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 keys must match. + +Example configuration: +```ts +{ + 'perfectionist/sort-object-types': [ + 'error', + { + groups: ['r', 'g', 'b'], // Sort colors types by RGB + customGroups: { + r: '^r$', + g: '^g$', + b: '^b$', + }, + useConfigurationIf: { + allNamesMatchPattern: '^r|g|b$', + }, + }, + { + type: 'alphabetical' // Fallback configuration + } + ], +} +``` + ### groups diff --git a/rules/sort-interfaces.ts b/rules/sort-interfaces.ts index 6267fcf9f..9a33ea44f 100644 --- a/rules/sort-interfaces.ts +++ b/rules/sort-interfaces.ts @@ -16,6 +16,7 @@ let defaultOptions: Required = { partitionByNewLine: false, newlinesBetween: 'ignore', specialCharacters: 'keep', + useConfigurationIf: {}, type: 'alphabetical', groupKind: 'mixed', ignorePattern: [], @@ -44,7 +45,7 @@ export default createEslintRule({ description: 'Enforce sorted interface properties.', recommended: true, }, - schema: [jsonSchema], + schema: jsonSchema, type: 'suggestion', fixable: 'code', }, diff --git a/rules/sort-object-types.ts b/rules/sort-object-types.ts index 18e3506bd..3096874b6 100644 --- a/rules/sort-object-types.ts +++ b/rules/sort-object-types.ts @@ -1,11 +1,13 @@ import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema' import type { RuleContext } from '@typescript-eslint/utils/ts-eslint' import type { TSESTree } from '@typescript-eslint/types' +import type { TSESLint } from '@typescript-eslint/utils' import type { Modifier, Selector, Options } from './sort-object-types.types' import type { SortingNode } from '../typings' import { + buildUseConfigurationIfJsonSchema, buildCustomGroupsArrayJsonSchema, partitionByCommentJsonSchema, partitionByNewLineJsonSchema, @@ -23,6 +25,7 @@ import { validateNewlinesAndPartitionConfiguration } from '../utils/validate-new 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 { generatePredefinedGroups } from '../utils/generate-predefined-groups' import { getEslintDisabledLines } from '../utils/get-eslint-disabled-lines' import { singleCustomGroupJsonSchema } from './sort-object-types.types' @@ -70,6 +73,7 @@ let defaultOptions: Required = { partitionByNewLine: false, newlinesBetween: 'ignore', specialCharacters: 'keep', + useConfigurationIf: {}, type: 'alphabetical', groupKind: 'mixed', ignorePattern: [], @@ -82,43 +86,48 @@ let defaultOptions: Required = { } export let jsonSchema: JSONSchema4 = { - properties: { - ignorePattern: { - description: - 'Specifies names or patterns for nodes that should be ignored by rule.', - items: { + items: { + properties: { + ignorePattern: { + description: + 'Specifies names or patterns for nodes that should be ignored by rule.', + items: { + type: 'string', + }, + type: 'array', + }, + partitionByComment: { + ...partitionByCommentJsonSchema, + description: + 'Allows you to use comments to separate members into logical groups.', + }, + customGroups: { + oneOf: [ + customGroupsJsonSchema, + buildCustomGroupsArrayJsonSchema({ singleCustomGroupJsonSchema }), + ], + }, + groupKind: { + enum: ['mixed', 'required-first', 'optional-first'], + description: 'Specifies top-level groups.', type: 'string', }, - type: 'array', - }, - partitionByComment: { - ...partitionByCommentJsonSchema, - description: - 'Allows you to use comments to separate members into logical groups.', - }, - customGroups: { - oneOf: [ - customGroupsJsonSchema, - buildCustomGroupsArrayJsonSchema({ singleCustomGroupJsonSchema }), - ], + useConfigurationIf: buildUseConfigurationIfJsonSchema(), + partitionByNewLine: partitionByNewLineJsonSchema, + specialCharacters: specialCharactersJsonSchema, + newlinesBetween: newlinesBetweenJsonSchema, + ignoreCase: ignoreCaseJsonSchema, + alphabet: alphabetJsonSchema, + type: buildTypeJsonSchema(), + locales: localesJsonSchema, + groups: groupsJsonSchema, + order: orderJsonSchema, }, - groupKind: { - enum: ['mixed', 'required-first', 'optional-first'], - description: 'Specifies top-level groups.', - type: 'string', - }, - partitionByNewLine: partitionByNewLineJsonSchema, - specialCharacters: specialCharactersJsonSchema, - newlinesBetween: newlinesBetweenJsonSchema, - ignoreCase: ignoreCaseJsonSchema, - alphabet: alphabetJsonSchema, - type: buildTypeJsonSchema(), - locales: localesJsonSchema, - groups: groupsJsonSchema, - order: orderJsonSchema, + additionalProperties: false, + type: 'object', }, - additionalProperties: false, - type: 'object', + uniqueItems: true, + type: 'array', } export default createEslintRule({ @@ -138,7 +147,7 @@ export default createEslintRule({ description: 'Enforce sorted object types.', recommended: true, }, - schema: [jsonSchema], + schema: jsonSchema, type: 'suggestion', fixable: 'code', }, @@ -184,7 +193,14 @@ export let sortObjectTypeElements = ({ } let settings = getSettings(context.settings) - let options = complete(context.options.at(0), settings, defaultOptions) + let sourceCode = getSourceCode(context) + let matchedContextOptions = getMatchingContextOptions({ + nodeNames: elements.map(node => + getNodeName({ typeElement: node, sourceCode }), + ), + contextOptions: context.options, + }) + let options = complete(matchedContextOptions[0], settings, defaultOptions) validateCustomSortConfiguration(options) validateGeneratedGroupsConfiguration({ customGroups: options.customGroups, @@ -202,7 +218,6 @@ export let sortObjectTypeElements = ({ return } - let sourceCode = getSourceCode(context) let eslintDisabledLines = getEslintDisabledLines({ ruleName: context.id, sourceCode, @@ -218,44 +233,10 @@ export let sortObjectTypeElements = ({ return accumulator } - let name: string let lastSortingNode = accumulator.at(-1)?.at(-1) let { setCustomGroups, defineGroup, getGroup } = useGroups(options) - let formatName = (value: string): string => value.replace(/[,;]$/u, '') - - if (typeElement.type === 'TSPropertySignature') { - if (typeElement.key.type === 'Identifier') { - ;({ name } = typeElement.key) - } else if (typeElement.key.type === 'Literal') { - name = `${typeElement.key.value}` - } else { - let end: number = - typeElement.typeAnnotation?.range.at(0) ?? - typeElement.range.at(1)! - (typeElement.optional ? '?'.length : 0) - name = sourceCode.text.slice(typeElement.range.at(0), end) - } - } else if (typeElement.type === 'TSIndexSignature') { - let endIndex: number = - typeElement.typeAnnotation?.range.at(0) ?? typeElement.range.at(1)! - - name = formatName( - sourceCode.text.slice(typeElement.range.at(0), endIndex), - ) - } else if ('name' in typeElement.key) { - // TSMethodSignature - ;({ name } = typeElement.key) - /* v8 ignore next 8 - Unsure if we can reach it */ - } else { - name = formatName( - sourceCode.text.slice( - typeElement.range.at(0), - typeElement.range.at(1), - ), - ) - } - let selectors: Selector[] = [] let modifiers: Modifier[] = [] @@ -295,6 +276,7 @@ export let sortObjectTypeElements = ({ defineGroup(predefinedGroup) } + let name = getNodeName({ typeElement, sourceCode }) if (Array.isArray(options.customGroups)) { for (let customGroup of options.customGroups) { if ( @@ -450,3 +432,44 @@ export let sortObjectTypeElements = ({ }) } } + +let getNodeName = ({ + typeElement, + sourceCode, +}: { + typeElement: TSESTree.TypeElement + sourceCode: TSESLint.SourceCode +}): string => { + let name: string + + let formatName = (value: string): string => value.replace(/[,;]$/u, '') + + if (typeElement.type === 'TSPropertySignature') { + if (typeElement.key.type === 'Identifier') { + ;({ name } = typeElement.key) + } else if (typeElement.key.type === 'Literal') { + name = `${typeElement.key.value}` + } else { + let end: number = + typeElement.typeAnnotation?.range.at(0) ?? + typeElement.range.at(1)! - (typeElement.optional ? '?'.length : 0) + name = sourceCode.text.slice(typeElement.range.at(0), end) + } + } else if (typeElement.type === 'TSIndexSignature') { + let endIndex: number = + typeElement.typeAnnotation?.range.at(0) ?? typeElement.range.at(1)! + + name = formatName(sourceCode.text.slice(typeElement.range.at(0), endIndex)) + } else if ( + typeElement.type === 'TSMethodSignature' && + 'name' in typeElement.key + ) { + ;({ name } = typeElement.key) + /* v8 ignore next 8 - Unsure if we can reach it */ + } else { + name = formatName( + sourceCode.text.slice(typeElement.range.at(0), typeElement.range.at(1)), + ) + } + return name +} diff --git a/rules/sort-object-types.types.ts b/rules/sort-object-types.types.ts index 5f26f6023..782ce80cb 100644 --- a/rules/sort-object-types.types.ts +++ b/rules/sort-object-types.types.ts @@ -8,26 +8,27 @@ import { elementNamePatternJsonSchema, } from '../utils/common-json-schemas' -export type Options = [ - Partial<{ - customGroups: Record | CustomGroup[] - type: 'alphabetical' | 'line-length' | 'natural' | 'custom' - /** - * @deprecated for {@link `groups`} - */ - groupKind: 'required-first' | 'optional-first' | 'mixed' - partitionByComment: string[] | boolean | string - newlinesBetween: 'ignore' | 'always' | 'never' - specialCharacters: 'remove' | 'trim' | 'keep' - locales: NonNullable - groups: (Group[] | Group)[] - partitionByNewLine: boolean - ignorePattern: string[] - order: 'desc' | 'asc' - ignoreCase: boolean - alphabet: string - }>, -] +export type Options = Partial<{ + customGroups: Record | CustomGroup[] + useConfigurationIf: { + allNamesMatchPattern?: string + } + type: 'alphabetical' | 'line-length' | 'natural' | 'custom' + /** + * @deprecated for {@link `groups`} + */ + groupKind: 'required-first' | 'optional-first' | 'mixed' + partitionByComment: string[] | boolean | string + newlinesBetween: 'ignore' | 'always' | 'never' + specialCharacters: 'remove' | 'trim' | 'keep' + locales: NonNullable + groups: (Group[] | Group)[] + partitionByNewLine: boolean + ignorePattern: string[] + order: 'desc' | 'asc' + ignoreCase: boolean + alphabet: string +}>[] export type SingleCustomGroup = ( | BaseSingleCustomGroup diff --git a/test/sort-interfaces.test.ts b/test/sort-interfaces.test.ts index 6acd0ef0d..5757d2908 100644 --- a/test/sort-interfaces.test.ts +++ b/test/sort-interfaces.test.ts @@ -2038,6 +2038,74 @@ describe(ruleName, () => { valid: [], }, ) + + describe(`${ruleName}(${type}): allows to use 'useConfigurationIf'`, () => { + ruleTester.run( + `${ruleName}(${type}): allows to use 'allNamesMatchPattern'`, + rule, + { + invalid: [ + { + errors: [ + { + data: { + rightGroup: 'g', + leftGroup: 'b', + right: 'g', + left: 'b', + }, + messageId: 'unexpectedInterfacePropertiesGroupOrder', + }, + { + data: { + rightGroup: 'r', + leftGroup: 'g', + right: 'r', + left: 'g', + }, + messageId: 'unexpectedInterfacePropertiesGroupOrder', + }, + ], + options: [ + { + ...options, + useConfigurationIf: { + allNamesMatchPattern: 'foo', + }, + }, + { + ...options, + customGroups: { + r: 'r', + g: 'g', + b: 'b', + }, + useConfigurationIf: { + allNamesMatchPattern: '^r|g|b$', + }, + groups: ['r', 'g', 'b'], + }, + ], + output: dedent` + interface Interface { + r: string + g: string + b: string + } + `, + code: dedent` + interface Interface { + b: string + g: string + r: string + } + `, + }, + ], + valid: [], + }, + ) + }) }) describe(`${ruleName}: sorting by natural order`, () => { diff --git a/test/sort-object-types.test.ts b/test/sort-object-types.test.ts index 52f347e84..9521e1327 100644 --- a/test/sort-object-types.test.ts +++ b/test/sort-object-types.test.ts @@ -1885,6 +1885,74 @@ describe(ruleName, () => { invalid: [], }, ) + + describe(`${ruleName}(${type}): allows to use 'useConfigurationIf'`, () => { + ruleTester.run( + `${ruleName}(${type}): allows to use 'allNamesMatchPattern'`, + rule, + { + invalid: [ + { + errors: [ + { + data: { + rightGroup: 'g', + leftGroup: 'b', + right: 'g', + left: 'b', + }, + messageId: 'unexpectedObjectTypesGroupOrder', + }, + { + data: { + rightGroup: 'r', + leftGroup: 'g', + right: 'r', + left: 'g', + }, + messageId: 'unexpectedObjectTypesGroupOrder', + }, + ], + options: [ + { + ...options, + useConfigurationIf: { + allNamesMatchPattern: 'foo', + }, + }, + { + ...options, + customGroups: { + r: 'r', + g: 'g', + b: 'b', + }, + useConfigurationIf: { + allNamesMatchPattern: '^r|g|b$', + }, + groups: ['r', 'g', 'b'], + }, + ], + output: dedent` + type Type = { + r: string + g: string + b: string + } + `, + code: dedent` + type Type = { + b: string + g: string + r: string + } + `, + }, + ], + valid: [], + }, + ) + }) }) describe(`${ruleName}: sorting by natural order`, () => { From 7de3c20bc11129a77be3458684932e1bfef60b99 Mon Sep 17 00:00:00 2001 From: "hugo.prunaux" Date: Sun, 15 Dec 2024 12:03:34 +0100 Subject: [PATCH 2/4] feat(sort-object-types): adds `unsorted` type --- docs/content/rules/sort-interfaces.mdx | 1 + docs/content/rules/sort-object-types.mdx | 1 + rules/sort-object-types.ts | 16 ++++++++++++++-- rules/sort-object-types.types.ts | 2 +- test/sort-interfaces.test.ts | 20 ++++++++++++++++++++ test/sort-object-types.test.ts | 20 ++++++++++++++++++++ 6 files changed, 57 insertions(+), 3 deletions(-) diff --git a/docs/content/rules/sort-interfaces.mdx b/docs/content/rules/sort-interfaces.mdx index 386577ec8..325fb5922 100644 --- a/docs/content/rules/sort-interfaces.mdx +++ b/docs/content/rules/sort-interfaces.mdx @@ -143,6 +143,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 diff --git a/docs/content/rules/sort-object-types.mdx b/docs/content/rules/sort-object-types.mdx index 42d231b62..cac0e0524 100644 --- a/docs/content/rules/sort-object-types.mdx +++ b/docs/content/rules/sort-object-types.mdx @@ -105,6 +105,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 diff --git a/rules/sort-object-types.ts b/rules/sort-object-types.ts index 3096874b6..2f144a30f 100644 --- a/rules/sort-object-types.ts +++ b/rules/sort-object-types.ts @@ -113,12 +113,12 @@ export let jsonSchema: JSONSchema4 = { type: 'string', }, useConfigurationIf: buildUseConfigurationIfJsonSchema(), + type: buildTypeJsonSchema({ withUnsorted: true }), partitionByNewLine: partitionByNewLineJsonSchema, specialCharacters: specialCharactersJsonSchema, newlinesBetween: newlinesBetweenJsonSchema, ignoreCase: ignoreCaseJsonSchema, alphabet: alphabetJsonSchema, - type: buildTypeJsonSchema(), locales: localesJsonSchema, groups: groupsJsonSchema, order: orderJsonSchema, @@ -200,7 +200,19 @@ export let sortObjectTypeElements = ({ ), contextOptions: context.options, }) - let options = complete(matchedContextOptions[0], settings, defaultOptions) + let completeOptions = complete( + matchedContextOptions[0], + settings, + defaultOptions, + ) + let { type } = completeOptions + if (type === 'unsorted') { + return + } + let options = { + ...completeOptions, + type, + } validateCustomSortConfiguration(options) validateGeneratedGroupsConfiguration({ customGroups: options.customGroups, diff --git a/rules/sort-object-types.types.ts b/rules/sort-object-types.types.ts index 782ce80cb..560981e39 100644 --- a/rules/sort-object-types.types.ts +++ b/rules/sort-object-types.types.ts @@ -9,11 +9,11 @@ import { } from '../utils/common-json-schemas' export type Options = Partial<{ + type: 'alphabetical' | 'line-length' | 'unsorted' | 'natural' | 'custom' customGroups: Record | CustomGroup[] useConfigurationIf: { allNamesMatchPattern?: string } - type: 'alphabetical' | 'line-length' | 'natural' | 'custom' /** * @deprecated for {@link `groups`} */ diff --git a/test/sort-interfaces.test.ts b/test/sort-interfaces.test.ts index 5757d2908..c1b0ff9cd 100644 --- a/test/sort-interfaces.test.ts +++ b/test/sort-interfaces.test.ts @@ -3967,6 +3967,26 @@ describe(ruleName, () => { }) describe(`${ruleName}: misc`, () => { + ruleTester.run(`${ruleName}: allows to use "unsorted" as type`, rule, { + valid: [ + { + code: dedent` + interface Interface { + b: string; + c: string; + a: string; + } + `, + options: [ + { + type: 'unsorted', + }, + ], + }, + ], + invalid: [], + }) + ruleTester.run( `${ruleName}: sets alphabetical asc sorting as default`, rule, diff --git a/test/sort-object-types.test.ts b/test/sort-object-types.test.ts index 9521e1327..e2726da31 100644 --- a/test/sort-object-types.test.ts +++ b/test/sort-object-types.test.ts @@ -3201,6 +3201,26 @@ describe(ruleName, () => { }) describe('misc', () => { + ruleTester.run(`${ruleName}: allows to use "unsorted" as type`, rule, { + valid: [ + { + code: dedent` + type Type = { + b: string; + c: string; + a: string; + } + `, + options: [ + { + type: 'unsorted', + }, + ], + }, + ], + invalid: [], + }) + ruleTester.run(`${ruleName}: ignores semi at the end of value`, rule, { valid: [ dedent` From 3bf5bfebe4ec7c62061662a87f51a5dd0e3a9513 Mon Sep 17 00:00:00 2001 From: "hugo.prunaux" Date: Mon, 16 Dec 2024 10:59:32 +0100 Subject: [PATCH 3/4] feat(sort-object-types): adds `useConfigurationIf.declarationMatchesPattern` option - Deprecates `ignorePattern`. --- docs/content/rules/sort-interfaces.mdx | 26 +++++- docs/content/rules/sort-object-types.mdx | 26 +++++- rules/sort-object-types.ts | 21 ++++- rules/sort-object-types.types.ts | 8 +- test/sort-interfaces.test.ts | 63 +++++++++++++ test/sort-object-types.test.ts | 109 +++++++++++++++++++++++ 6 files changed, 245 insertions(+), 8 deletions(-) diff --git a/docs/content/rules/sort-interfaces.mdx b/docs/content/rules/sort-interfaces.mdx index 325fb5922..ffde4e7ea 100644 --- a/docs/content/rules/sort-interfaces.mdx +++ b/docs/content/rules/sort-interfaces.mdx @@ -192,10 +192,12 @@ Specifies the sorting locales. See [String.prototype.localeCompare() - locales]( - `string` — A BCP 47 language tag (e.g. `'en'`, `'en-US'`, `'zh-CN'`). - `string[]` — An array of BCP 47 language tags. -### ignorePattern +### [DEPRECATED] ignorePattern default: `[]` +Use the [useConfigurationIf.declarationMatchesPattern](#useconfigurationif) option alongside [type: unsorted](#type) instead. + Allows you to specify names or patterns for interfaces that should be ignored by this rule. This can be useful if you have specific interfaces that you do not want to sort. You can specify their names or a regexp pattern to ignore, for example: `'^Component.+'` to ignore all interfaces whose names begin with the word “Component”. @@ -262,7 +264,7 @@ Specifies how optional and required members should be ordered in TypeScript inte ### useConfigurationIf - type: `{ allNamesMatchPattern?: string }` + type: `{ allNamesMatchPattern?: string; declarationMatchesPattern?: string }` default: `{}` @@ -295,6 +297,26 @@ Example configuration: } ``` +- `declarationMatchesPattern` — A regexp pattern that the interface declaration must match. + +Example configuration: +```ts +{ + 'perfectionist/sort-interfaces': [ + 'error', + { + type: 'unsorted', // Do not sort Metadata interfaces + useConfigurationIf: { + declarationMatchesPattern: '*Metadata$', + }, + }, + { + type: 'alphabetical' // Fallback configuration + } + ], +} +``` + ### groups diff --git a/docs/content/rules/sort-object-types.mdx b/docs/content/rules/sort-object-types.mdx index cac0e0524..7c15e6340 100644 --- a/docs/content/rules/sort-object-types.mdx +++ b/docs/content/rules/sort-object-types.mdx @@ -154,10 +154,12 @@ Specifies the sorting locales. See [String.prototype.localeCompare() - locales]( - `string` — A BCP 47 language tag (e.g. `'en'`, `'en-US'`, `'zh-CN'`). - `string[]` — An array of BCP 47 language tags. -### ignorePattern +### [DEPRECATED] ignorePattern default: `[]` +Use the [useConfigurationIf.declarationMatchesPattern](#useconfigurationif) option alongside [type: unsorted](#type) instead. + Allows you to specify names or patterns for object types that should be ignored by this rule. This can be useful if you have specific object types that you do not want to sort. You can specify their names or a regexp pattern to ignore, for example: `'^Component.+'` to ignore all object types whose names begin with the word “Component”. @@ -227,7 +229,7 @@ Allows you to group type object keys by their kind, determining whether required ### useConfigurationIf - type: `{ allNamesMatchPattern?: string }` + type: `{ allNamesMatchPattern?: string; declarationMatchesPattern?: string }` default: `{}` @@ -260,6 +262,26 @@ Example configuration: } ``` +- `declarationMatchesPattern` — A regexp pattern that the object type declaration must match. + +Example configuration: +```ts +{ + 'perfectionist/sort-object-types': [ + 'error', + { + type: 'unsorted', // Do not sort Metadata types + useConfigurationIf: { + declarationMatchesPattern: '*Metadata$', + }, + }, + { + type: 'alphabetical' // Fallback configuration + } + ], +} +``` + ### groups diff --git a/rules/sort-object-types.ts b/rules/sort-object-types.ts index 2f144a30f..b62a8a545 100644 --- a/rules/sort-object-types.ts +++ b/rules/sort-object-types.ts @@ -96,6 +96,13 @@ export let jsonSchema: JSONSchema4 = { }, type: 'array', }, + useConfigurationIf: buildUseConfigurationIfJsonSchema({ + additionalProperties: { + declarationMatchesPattern: { + type: 'string', + }, + }, + }), partitionByComment: { ...partitionByCommentJsonSchema, description: @@ -112,7 +119,6 @@ export let jsonSchema: JSONSchema4 = { description: 'Specifies top-level groups.', type: 'string', }, - useConfigurationIf: buildUseConfigurationIfJsonSchema(), type: buildTypeJsonSchema({ withUnsorted: true }), partitionByNewLine: partitionByNewLineJsonSchema, specialCharacters: specialCharactersJsonSchema, @@ -199,9 +205,20 @@ export let sortObjectTypeElements = ({ getNodeName({ typeElement: node, sourceCode }), ), contextOptions: context.options, + }).find(options => { + if (!options.useConfigurationIf?.declarationMatchesPattern) { + return true + } + if (!parentNodeName) { + return false + } + return matches( + parentNodeName, + options.useConfigurationIf.declarationMatchesPattern, + ) }) let completeOptions = complete( - matchedContextOptions[0], + matchedContextOptions, settings, defaultOptions, ) diff --git a/rules/sort-object-types.types.ts b/rules/sort-object-types.types.ts index 560981e39..ed61ad027 100644 --- a/rules/sort-object-types.types.ts +++ b/rules/sort-object-types.types.ts @@ -9,11 +9,12 @@ import { } from '../utils/common-json-schemas' export type Options = Partial<{ - type: 'alphabetical' | 'line-length' | 'unsorted' | 'natural' | 'custom' - customGroups: Record | CustomGroup[] useConfigurationIf: { + declarationMatchesPattern?: string allNamesMatchPattern?: string } + type: 'alphabetical' | 'line-length' | 'unsorted' | 'natural' | 'custom' + customGroups: Record | CustomGroup[] /** * @deprecated for {@link `groups`} */ @@ -24,6 +25,9 @@ export type Options = Partial<{ locales: NonNullable groups: (Group[] | Group)[] partitionByNewLine: boolean + /** + * @deprecated for {@link `useConfigurationIf.declarationMatchesPattern`} + */ ignorePattern: string[] order: 'desc' | 'asc' ignoreCase: boolean diff --git a/test/sort-interfaces.test.ts b/test/sort-interfaces.test.ts index c1b0ff9cd..34f82a5bb 100644 --- a/test/sort-interfaces.test.ts +++ b/test/sort-interfaces.test.ts @@ -2105,6 +2105,69 @@ describe(ruleName, () => { valid: [], }, ) + + describe(`${ruleName}(${type}): allows to use 'declarationMatchesPattern'`, () => { + ruleTester.run( + `${ruleName}(${type}): detects declaration name by pattern`, + rule, + { + invalid: [ + { + options: [ + { + useConfigurationIf: { + declarationMatchesPattern: '^Interface$', + }, + type: 'unsorted', + }, + options, + ], + errors: [ + { + data: { + right: 'a', + left: 'b', + }, + messageId: 'unexpectedInterfacePropertiesOrder', + }, + ], + output: dedent` + interface OtherInterface { + a: string + b: string + } + `, + code: dedent` + interface OtherInterface { + b: string + a: string + } + `, + }, + ], + valid: [ + { + options: [ + { + useConfigurationIf: { + declarationMatchesPattern: '^Interface$', + }, + type: 'unsorted', + }, + options, + ], + code: dedent` + interface Interface { + b: string + c: string + a: string + } + `, + }, + ], + }, + ) + }) }) }) diff --git a/test/sort-object-types.test.ts b/test/sort-object-types.test.ts index e2726da31..3f9ded11a 100644 --- a/test/sort-object-types.test.ts +++ b/test/sort-object-types.test.ts @@ -1952,6 +1952,115 @@ describe(ruleName, () => { valid: [], }, ) + + describe(`${ruleName}(${type}): allows to use 'declarationMatchesPattern'`, () => { + ruleTester.run( + `${ruleName}(${type}): detects declaration name by pattern`, + rule, + { + invalid: [ + { + options: [ + { + useConfigurationIf: { + declarationMatchesPattern: '^Type$', + }, + type: 'unsorted', + }, + options, + ], + errors: [ + { + data: { + right: 'a', + left: 'b', + }, + messageId: 'unexpectedObjectTypesOrder', + }, + ], + output: dedent` + type OtherType = { + a: string + b: string + } + `, + code: dedent` + type OtherType = { + b: string + a: string + } + `, + }, + ], + valid: [ + { + options: [ + { + useConfigurationIf: { + declarationMatchesPattern: '^Type$', + }, + type: 'unsorted', + }, + options, + ], + code: dedent` + type Type = { + b: string + c: string + a: string + } + `, + }, + ], + }, + ) + + ruleTester.run( + `${ruleName}(${type}): does not match configuration if no declaration name`, + rule, + { + invalid: [ + { + options: [ + { + useConfigurationIf: { + declarationMatchesPattern: '^Type$', + }, + type: 'unsorted', + }, + options, + ], + errors: [ + { + data: { + right: 'b', + left: 'c', + }, + messageId: 'unexpectedObjectTypesOrder', + }, + ], + output: dedent` + type Type = { + a: { + b: string + c: string + } + } + `, + code: dedent` + type Type = { + a: { + c: string + b: string + } + } + `, + }, + ], + valid: [], + }, + ) + }) }) }) From 80bdb9805548684f3c65dec5d05face77b880867 Mon Sep 17 00:00:00 2001 From: "hugo.prunaux" Date: Sun, 15 Dec 2024 12:26:08 +0100 Subject: [PATCH 4/4] docs: fixes invalid documentation - internal links seem to only work when in lowercase --- docs/content/rules/sort-array-includes.mdx | 2 +- docs/content/rules/sort-objects.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/rules/sort-array-includes.mdx b/docs/content/rules/sort-array-includes.mdx index 2b3d1056a..4c1b7b944 100644 --- a/docs/content/rules/sort-array-includes.mdx +++ b/docs/content/rules/sort-array-includes.mdx @@ -129,7 +129,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. +- `'unsorted'` — Do not sort items. To be used with the [`useConfigurationIf`](#useconfigurationif) option. ### order diff --git a/docs/content/rules/sort-objects.mdx b/docs/content/rules/sort-objects.mdx index 369d6021c..e6f6247fa 100644 --- a/docs/content/rules/sort-objects.mdx +++ b/docs/content/rules/sort-objects.mdx @@ -163,7 +163,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. +- `'unsorted'` — Do not sort items. To be used with the [`useConfigurationIf`](#useconfigurationif) option. ### order