diff --git a/docs/content/rules/sort-objects.mdx b/docs/content/rules/sort-objects.mdx
index 29369fc66..b509d2f3a 100644
--- a/docs/content/rules/sort-objects.mdx
+++ b/docs/content/rules/sort-objects.mdx
@@ -288,6 +288,42 @@ Allows you to choose whether to sort standard object declarations.
Allows you to choose whether to sort destructured objects.
The `groups` attribute allows you to specify whether to use groups to sort destructured objects.
+### 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-objects': [
+ 'error',
+ {
+ groups: ['r', 'g', 'b'], // Sort colors by RGB
+ customGroups: {
+ r: 'r',
+ g: 'g',
+ b: 'b',
+ },
+ useConfigurationIf: {
+ allNamesMatchPattern: '^r|g|b$',
+ },
+ },
+ {
+ type: 'alphabetical' // Fallback configuration
+ }
+ ],
+}
+```
+
### groups
@@ -391,8 +427,11 @@ const user = {
partitionByComment: false,
partitionByNewLine: false,
newlinesBetween: 'ignore',
+ objectDeclarations: true,
+ destructuredObjects: true,
styledComponents: true,
ignorePattern: [],
+ useConfigurationIf: {},
groups: [],
customGroups: {},
},
@@ -422,8 +461,11 @@ const user = {
partitionByComment: false,
partitionByNewLine: false,
newlinesBetween: 'ignore',
+ objectDeclarations: true,
+ destructuredObjects: true,
styledComponents: true,
ignorePattern: [],
+ useConfigurationIf: {},
groups: [],
customGroups: {},
},
diff --git a/rules/sort-objects.ts b/rules/sort-objects.ts
index a0b3fc7ec..c79643e11 100644
--- a/rules/sort-objects.ts
+++ b/rules/sort-objects.ts
@@ -5,6 +5,7 @@ import type { SortingNodeWithDependencies } from '../utils/sort-nodes-by-depende
import {
partitionByCommentJsonSchema,
partitionByNewLineJsonSchema,
+ useConfigurationIfJsonSchema,
specialCharactersJsonSchema,
newlinesBetweenJsonSchema,
customGroupsJsonSchema,
@@ -20,6 +21,7 @@ import {
} from '../utils/sort-nodes-by-dependencies'
import { validateNewlinesAndPartitionConfiguration } from '../utils/validate-newlines-and-partition-configuration'
import { validateGroupsConfiguration } from '../utils/validate-groups-configuration'
+import { getMatchingContextOptions } from '../utils/get-matching-context-options'
import { getEslintDisabledLines } from '../utils/get-eslint-disabled-lines'
import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled'
import { hasPartitionComment } from '../utils/is-partition-comment'
@@ -42,28 +44,29 @@ import { complete } from '../utils/complete'
import { pairwise } from '../utils/pairwise'
import { matches } from '../utils/matches'
-type Options = [
- Partial<{
- destructuredObjects: { groups: boolean } | boolean
- type: 'alphabetical' | 'line-length' | 'natural'
- customGroups: Record
- partitionByComment: string[] | boolean | string
- newlinesBetween: 'ignore' | 'always' | 'never'
- specialCharacters: 'remove' | 'trim' | 'keep'
- locales: NonNullable
- groups: (Group[] | Group)[]
- partitionByNewLine: boolean
- objectDeclarations: boolean
- styledComponents: boolean
- /**
- * @deprecated for {@link `destructuredObjects`} and {@link `objectDeclarations`}
- */
- destructureOnly: boolean
- ignorePattern: string[]
- order: 'desc' | 'asc'
- ignoreCase: boolean
- }>,
-]
+type Options = Partial<{
+ useConfigurationIf: {
+ allNamesMatchPattern?: string
+ }
+ destructuredObjects: { groups: boolean } | boolean
+ type: 'alphabetical' | 'line-length' | 'natural'
+ customGroups: Record
+ partitionByComment: string[] | boolean | string
+ newlinesBetween: 'ignore' | 'always' | 'never'
+ specialCharacters: 'remove' | 'trim' | 'keep'
+ locales: NonNullable
+ groups: (Group[] | Group)[]
+ partitionByNewLine: boolean
+ objectDeclarations: boolean
+ styledComponents: boolean
+ /**
+ * @deprecated for {@link `destructuredObjects`} and {@link `objectDeclarations`}
+ */
+ destructureOnly: boolean
+ ignorePattern: string[]
+ order: 'desc' | 'asc'
+ ignoreCase: boolean
+}>[]
type MESSAGE_ID =
| 'missedSpacingBetweenObjectMembers'
@@ -83,6 +86,7 @@ let defaultOptions: Required = {
objectDeclarations: true,
styledComponents: true,
destructureOnly: false,
+ useConfigurationIf: {},
type: 'alphabetical',
ignorePattern: [],
ignoreCase: true,
@@ -102,9 +106,14 @@ export default createEslintRule({
}
let settings = getSettings(context.settings)
-
- let options = complete(context.options.at(0), settings, defaultOptions)
-
+ let sourceCode = getSourceCode(context)
+ let matchedContextOptions = getMatchingContextOptions({
+ nodeNames: nodeObject.properties
+ .map(property => getNodeName({ sourceCode, property }))
+ .filter(nodeName => nodeName !== null),
+ contextOptions: context.options,
+ })
+ let options = complete(matchedContextOptions, settings, defaultOptions)
validateGroupsConfiguration(
options.groups,
['multiline', 'method', 'unknown'],
@@ -182,7 +191,6 @@ export default createEslintRule({
return
}
- let sourceCode = getSourceCode(context)
let eslintDisabledLines = getEslintDisabledLines({
ruleName: context.id,
sourceCode,
@@ -480,8 +488,8 @@ export default createEslintRule({
}
},
meta: {
- schema: [
- {
+ schema: {
+ items: {
properties: {
destructuredObjects: {
oneOf: [
@@ -528,6 +536,7 @@ export default createEslintRule({
type: 'boolean',
},
partitionByNewLine: partitionByNewLineJsonSchema,
+ useConfigurationIf: useConfigurationIfJsonSchema,
specialCharacters: specialCharactersJsonSchema,
newlinesBetween: newlinesBetweenJsonSchema,
customGroups: customGroupsJsonSchema,
@@ -540,7 +549,9 @@ export default createEslintRule({
additionalProperties: false,
type: 'object',
},
- ],
+ uniqueItems: true,
+ type: 'array',
+ },
messages: {
unexpectedObjectsGroupOrder:
'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
@@ -563,3 +574,24 @@ export default createEslintRule({
defaultOptions: [defaultOptions],
name: 'sort-objects',
})
+
+let getNodeName = ({
+ sourceCode,
+ property,
+}: {
+ property:
+ | TSESTree.ObjectLiteralElement
+ | TSESTree.RestElement
+ | TSESTree.Property
+ sourceCode: ReturnType
+}): string | null => {
+ if (property.type === 'SpreadElement' || property.type === 'RestElement') {
+ return null
+ }
+ if (property.key.type === 'Identifier') {
+ return property.key.name
+ } else if (property.key.type === 'Literal') {
+ return `${property.key.value}`
+ }
+ return sourceCode.getText(property.key)
+}
diff --git a/test/get-matching-context-options.test.ts b/test/get-matching-context-options.test.ts
new file mode 100644
index 000000000..a673705f7
--- /dev/null
+++ b/test/get-matching-context-options.test.ts
@@ -0,0 +1,44 @@
+import { describe, expect, it } from 'vitest'
+
+import { getMatchingContextOptions } from '../utils/get-matching-context-options'
+
+describe('get-matching-context-options', () => {
+ describe('`allNamesMatchPattern`', () => {
+ it('matches the appropriate context options with `allNamesMatchPattern`', () => {
+ let barContextOptions = buildContextOptions('bar')
+ let contextOptions = [buildContextOptions('foo'), barContextOptions]
+ let nodeNames = ['bar1', 'bar2']
+
+ expect(getMatchingContextOptions({ contextOptions, nodeNames })).toEqual(
+ barContextOptions,
+ )
+ })
+
+ it('returns `undefined` if no configuration matches', () => {
+ let contextOptions = [buildContextOptions('foo')]
+ let nodeNames = ['bar1', 'bar2']
+
+ expect(
+ getMatchingContextOptions({ contextOptions, nodeNames }),
+ ).toBeUndefined()
+ })
+
+ it('returns the first context options if no filters are entered', () => {
+ let emptyContextOptions = buildContextOptions()
+ let contextOptions = [emptyContextOptions, buildContextOptions()]
+ let nodeNames = ['bar1', 'bar2']
+
+ expect(getMatchingContextOptions({ contextOptions, nodeNames })).toEqual(
+ emptyContextOptions,
+ )
+ })
+ })
+
+ let buildContextOptions = (
+ allNamesMatchPattern?: string,
+ ): { useConfigurationIf: { allNamesMatchPattern?: string } } => ({
+ useConfigurationIf: {
+ ...(allNamesMatchPattern ? { allNamesMatchPattern } : {}),
+ },
+ })
+})
diff --git a/test/sort-objects.test.ts b/test/sort-objects.test.ts
index d1d482677..b82eff75a 100644
--- a/test/sort-objects.test.ts
+++ b/test/sort-objects.test.ts
@@ -1850,6 +1850,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: 'unexpectedObjectsGroupOrder',
+ },
+ {
+ data: {
+ rightGroup: 'r',
+ leftGroup: 'g',
+ right: 'r',
+ left: 'g',
+ },
+ messageId: 'unexpectedObjectsGroupOrder',
+ },
+ ],
+ options: [
+ {
+ ...options,
+ useConfigurationIf: {
+ allNamesMatchPattern: 'foo',
+ },
+ },
+ {
+ ...options,
+ customGroups: {
+ r: 'r',
+ g: 'g',
+ b: 'b',
+ },
+ useConfigurationIf: {
+ allNamesMatchPattern: '^r|g|b$',
+ },
+ groups: ['r', 'g', 'b'],
+ },
+ ],
+ output: dedent`
+ let obj = {
+ r: string,
+ g: string,
+ b: string
+ }
+ `,
+ code: dedent`
+ let obj = {
+ b: string,
+ g: string,
+ r: string
+ }
+ `,
+ },
+ ],
+ valid: [],
+ },
+ )
+ })
})
describe(`${ruleName}: sorting by natural order`, () => {
diff --git a/utils/common-json-schemas.ts b/utils/common-json-schemas.ts
index efdd07838..29564ece4 100644
--- a/utils/common-json-schemas.ts
+++ b/utils/common-json-schemas.ts
@@ -106,6 +106,16 @@ export let newlinesBetweenJsonSchema: JSONSchema4 = {
type: 'string',
}
+export let useConfigurationIfJsonSchema: JSONSchema4 = {
+ properties: {
+ allNamesMatchPattern: {
+ type: 'string',
+ },
+ },
+ additionalProperties: false,
+ type: 'object',
+}
+
let customGroupSortJsonSchema: Record = {
type: {
enum: ['alphabetical', 'line-length', 'natural', 'unsorted'],
diff --git a/utils/get-matching-context-options.ts b/utils/get-matching-context-options.ts
new file mode 100644
index 000000000..0237729c9
--- /dev/null
+++ b/utils/get-matching-context-options.ts
@@ -0,0 +1,22 @@
+import { matches } from './matches'
+
+interface Options {
+ useConfigurationIf?: {
+ allNamesMatchPattern?: string
+ }
+}
+
+export let getMatchingContextOptions = ({
+ contextOptions,
+ nodeNames,
+}: {
+ contextOptions: Options[]
+ nodeNames: string[]
+}): undefined | Options =>
+ contextOptions.find(options => {
+ let allNamesMatchPattern = options.useConfigurationIf?.allNamesMatchPattern
+ return (
+ !allNamesMatchPattern ||
+ nodeNames.every(nodeName => matches(nodeName, allNamesMatchPattern))
+ )
+ })