diff --git a/docs/content/rules/sort-objects.mdx b/docs/content/rules/sort-objects.mdx index ec2dd2f3..29369fc6 100644 --- a/docs/content/rules/sort-objects.mdx +++ b/docs/content/rules/sort-objects.mdx @@ -264,11 +264,29 @@ Allows you to specify names or patterns for object types that should be ignored You can specify their names or a regexp pattern to ignore, for example: `'^User.+'` to ignore all object types whose names begin with the word “User”. -### destructureOnly +### [DEPRECATED] destructureOnly default: `false` -Allows you to sort only objects that are part of a destructuring pattern. When set to `true`, the rule will apply sorting exclusively to destructured objects, leaving other object declarations unchanged. +Use the [objectDeclarations](#objectdeclarations) and [destructuredObjects](#destructuredobjects) options instead. + +Allows you to only sort objects that are part of a destructuring pattern. When set to `true`, the rule will apply sorting exclusively to destructured objects, leaving other object declarations unchanged. + +### objectDeclarations + +default: `true` + +Allows you to choose whether to sort standard object declarations. + +### destructuredObjects + + + type: `boolean | { groups: boolean }` + +default: `true` + +Allows you to choose whether to sort destructured objects. +The `groups` attribute allows you to specify whether to use groups to sort destructured objects. ### groups diff --git a/rules/sort-objects.ts b/rules/sort-objects.ts index 95448224..130a0edd 100644 --- a/rules/sort-objects.ts +++ b/rules/sort-objects.ts @@ -37,12 +37,14 @@ import { getSettings } from '../utils/get-settings' import { isSortable } from '../utils/is-sortable' import { useGroups } from '../utils/use-groups' import { makeFixes } from '../utils/make-fixes' +import { sortNodes } from '../utils/sort-nodes' 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 @@ -51,7 +53,11 @@ type Options = [ 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' @@ -73,6 +79,8 @@ let defaultOptions: Required = { partitionByComment: false, newlinesBetween: 'ignore', specialCharacters: 'keep', + destructuredObjects: true, + objectDeclarations: true, styledComponents: true, destructureOnly: false, type: 'alphabetical', @@ -87,8 +95,12 @@ let defaultOptions: Required = { export default createEslintRule({ create: context => { let sortObject = ( - node: TSESTree.ObjectExpression | TSESTree.ObjectPattern, + nodeObject: TSESTree.ObjectExpression | TSESTree.ObjectPattern, ): void => { + if (!isSortable(nodeObject.properties)) { + return + } + let settings = getSettings(context.settings) let options = complete(context.options.at(0), settings, defaultOptions) @@ -100,14 +112,17 @@ export default createEslintRule({ ) validateNewlinesAndPartitionConfiguration(options) - let shouldIgnore = false - - if (options.destructureOnly) { - shouldIgnore = node.type !== 'ObjectPattern' + let isDestructuredObject = nodeObject.type === 'ObjectPattern' + if (isDestructuredObject) { + if (!options.destructuredObjects) { + return + } + } else if (options.destructureOnly || !options.objectDeclarations) { + return } - if (!shouldIgnore && options.ignorePattern.length) { - let variableParent = getNodeParent(node, [ + if (options.ignorePattern.length) { + let variableParent = getNodeParent(nodeObject, [ 'VariableDeclarator', 'Property', ]) @@ -126,10 +141,10 @@ export default createEslintRule({ typeof variableIdentifier === 'string' && checkMatch(variableIdentifier) ) { - shouldIgnore = true + return } - let callParent = getNodeParent(node, ['CallExpression']) + let callParent = getNodeParent(nodeObject, ['CallExpression']) let callIdentifier = callParent?.type === 'CallExpression' && callParent.callee.type === 'Identifier' @@ -137,12 +152,9 @@ export default createEslintRule({ : null if (callIdentifier && checkMatch(callIdentifier)) { - shouldIgnore = true + return } } - if (shouldIgnore || !isSortable(node.properties)) { - return - } let isStyledCallExpression = (identifier: TSESTree.Expression): boolean => identifier.type === 'Identifier' && identifier.name === 'styled' @@ -163,9 +175,9 @@ export default createEslintRule({ styledNode.parent.name.name === 'style')) if ( !options.styledComponents && - (isStyledComponents(node.parent) || - (node.parent.type === 'ArrowFunctionExpression' && - isStyledComponents(node.parent.parent))) + (isStyledComponents(nodeObject.parent) || + (nodeObject.parent.type === 'ArrowFunctionExpression' && + isStyledComponents(nodeObject.parent.parent))) ) { return } @@ -352,14 +364,20 @@ export default createEslintRule({ }, [[]], ) - let formattedMembers = formatProperties(node.properties) - + let formattedMembers = formatProperties(nodeObject.properties) + + let nodesSortingFunction = + isDestructuredObject && + typeof options.destructuredObjects === 'object' && + !options.destructuredObjects.groups + ? sortNodes + : sortNodesByGroups let sortNodesIgnoringEslintDisabledNodes = ( ignoreEslintDisabledNodes: boolean, ): SortingNodeWithDependencies[] => sortNodesByDependencies( formattedMembers.flatMap(nodes => - sortNodesByGroups(nodes, options, { + nodesSortingFunction(nodes, options, { ignoreEslintDisabledNodes, }), ), @@ -460,6 +478,25 @@ export default createEslintRule({ schema: [ { properties: { + destructuredObjects: { + oneOf: [ + { + type: 'boolean', + }, + { + properties: { + groups: { + description: + 'Controls whether to use groups to sort destructured objects.', + type: 'boolean', + }, + }, + additionalProperties: false, + type: 'object', + }, + ], + description: 'Controls whether to sort destructured objects.', + }, ignorePattern: { description: 'Specifies names or patterns for nodes that should be ignored by rule.', @@ -477,6 +514,10 @@ export default createEslintRule({ description: 'Controls whether to sort only destructured objects.', type: 'boolean', }, + objectDeclarations: { + description: 'Controls whether to sort object declarations.', + type: 'boolean', + }, styledComponents: { description: 'Controls whether to sort styled components.', type: 'boolean', diff --git a/test/sort-objects.test.ts b/test/sort-objects.test.ts index e8cf82b3..d1d48267 100644 --- a/test/sort-objects.test.ts +++ b/test/sort-objects.test.ts @@ -3850,7 +3850,7 @@ describe(ruleName, () => { ], }) - ruleTester.run(`${ruleName}: allow to use for destructuring only`, rule, { + ruleTester.run(`${ruleName}: allow to use 'destructureOnly'`, rule, { invalid: [ { errors: [ @@ -3914,6 +3914,202 @@ describe(ruleName, () => { ], }) + ruleTester.run(`${ruleName}: allow to use 'objectDeclarations'`, rule, { + invalid: [ + { + errors: [ + { + data: { + right: 'b', + left: 'c', + }, + messageId: 'unexpectedObjectsOrder', + }, + { + data: { + right: 'a', + left: 'b', + }, + messageId: 'unexpectedObjectsOrder', + }, + ], + output: dedent` + let obj = { + c: 'c', + a: 'a', + b: 'b', + } + + let { a, b, c } = obj + `, + code: dedent` + let obj = { + c: 'c', + a: 'a', + b: 'b', + } + + let { c, b, a } = obj + `, + options: [ + { + objectDeclarations: false, + }, + ], + }, + ], + valid: [ + { + code: dedent` + let obj = { + c: 'c', + a: 'a', + b: 'b', + } + + let { a, b, c } = obj + `, + options: [ + { + objectDeclarations: false, + }, + ], + }, + ], + }) + + describe(`${ruleName}: allow to use 'destructuredObjects'`, () => { + ruleTester.run(`${ruleName}: boolean 'destructuredObjects'`, rule, { + invalid: [ + { + errors: [ + { + data: { + right: 'b', + left: 'c', + }, + messageId: 'unexpectedObjectsOrder', + }, + { + data: { + right: 'a', + left: 'b', + }, + messageId: 'unexpectedObjectsOrder', + }, + ], + output: dedent` + let obj = { + a: 'a', + b: 'b', + c: 'c', + } + + let { c, a, b } = obj + `, + code: dedent` + let obj = { + c: 'c', + b: 'b', + a: 'a', + } + + let { c, a, b } = obj + `, + options: [ + { + destructuredObjects: false, + }, + ], + }, + ], + valid: [ + { + code: dedent` + let obj = { + a: 'a', + b: 'b', + c: 'c', + } + + let { b, c, a } = obj + `, + options: [ + { + destructuredObjects: false, + }, + ], + }, + ], + }) + + ruleTester.run( + `${ruleName}: object 'destructuredObjects': 'groups' attribute`, + rule, + { + invalid: [ + { + errors: [ + { + data: { + leftGroup: 'unknown', + rightGroup: 'top', + right: 'c', + left: 'a', + }, + messageId: 'unexpectedObjectsGroupOrder', + }, + ], + options: [ + { + customGroups: { + top: 'c', + }, + destructuredObjects: { groups: true }, + groups: ['top', 'unknown'], + }, + ], + output: dedent` + let { c, a, b } = obj + `, + code: dedent` + let { a, c, b } = obj + `, + }, + { + errors: [ + { + data: { + rightGroup: 'unknown', + leftGroup: 'top', + right: 'b', + left: 'c', + }, + messageId: 'unexpectedObjectsGroupOrder', + }, + ], + options: [ + { + customGroups: { + top: 'c', + }, + destructuredObjects: { groups: false }, + groups: ['top', 'unknown'], + }, + ], + output: dedent` + let { a, b, c } = obj + `, + code: dedent` + let { a, c, b } = obj + `, + }, + ], + valid: [], + }, + ) + }) + ruleTester.run(`${ruleName}: works with settings`, rule, { valid: [ {