Repository: azat-io/eslint-plugin-perfectionist (ts)

Issue:
Bug: "sort-objects" sorts nested keys inside styled(), including adaptive breakpoints
### Describe the bug

Good afternoon. 

Problem: I use perfectionist together with styled components, in particular with MUI. I may have a nested object inside the styled component, and the perfectionist/sort-objects rule applies sorting to the nested object as well, ignoring the option  `styledComponents:false` (an example of the component is below). 
We use a theme.breakpoints.down(...) as calculated keys. Their order is important — it reflects the priority of adaptive rules, so it cannot be changed and the plugin sorts the img object, including calculated keys from the theme.breakpoints.down(...), and thus breaks their order.

I tried adding an exception via useConfigurationIf, but this doesn't work because the object also has regular keys (bottom, width, etc.), the useConfigurationIf allNamesMatchPattern condition does not work because not all keys match the pattern.

Is there any way to solve this problem using useConfigurationIf or is it still a bug?

### Code example

### current setting for sort-objects
```js
'perfectionist/sort-objects': [
  'warn',
  {
    type: 'line-length',
    order: 'asc',
    objectDeclarations: true,
    destructuredObjects: true,
    styledComponents: false,
    fallbackSort: { type: 'natural', order: 'asc' },
  },
]
```


### styled component example
```js
export const ImageWrapper = styled('picture')(({ theme }) => ({
  img: {
    position: 'absolute',
    right: '160px',
    [theme.breakpoints.down('max1')]: {
      right: '75px',
    },
    [theme.breakpoints.down('max2')]: {
      right: '-30px',
    },
    [theme.breakpoints.down('mid1')]: {
      right: '24px',
    },
    [theme.breakpoints.down('mid2')]: {
      bottom: '-273px',
      display: 'block',
      height: '867px',
      right: '50%',
      transform: 'translateX(50%)',
      width: '1048px',
    },
    [theme.breakpoints.down('min1')]: {
      bottom: '-119px',
      display: 'block',
      height: '415px',
      right: '50%',
      transform: 'translateX(50%)',
      width: '513px',
    },
  },
}))
```

![Image](https://github.com/user-attachments/assets/e607eb5e-d40d-4396-a0b9-defaf2445d2e)

### ESLint version

8.57.1

### ESLint Plugin Perfectionist version

4.11.0

### Additional comments

_No response_

### Validations

- [x] Read the [docs](https://perfectionist.dev).
- [x] Check that there isn't [already an issue](https://github.com/azat-io/eslint-plugin-perfectionist/issues) that reports the same bug to avoid creating a duplicate.

Pull request description:
fix(sort-objects): fix nested objects not impacted by `styledComponents`
- Fixes #516

### Description

This PR simply applies the `styledComponents` detection to nested objects.

### What is the purpose of this pull request?
- [x] Bug fix


Patch:
diff --git a/rules/sort-objects.ts b/rules/sort-objects.ts
index ca60a01..53452f1 100644
--- a/rules/sort-objects.ts
+++ b/rules/sort-objects.ts
@@ -155,28 +155,14 @@ export default createEslintRule<Options, MESSAGE_ID>({
         return
       }
 
-      let isStyledCallExpression = (identifier: TSESTree.Expression): boolean =>
-        identifier.type === 'Identifier' && identifier.name === 'styled'
-      let isCssCallExpression = (identifier: TSESTree.Expression): boolean =>
-        identifier.type === 'Identifier' && identifier.name === 'css'
-      let isStyledComponents = (
-        styledNode: TSESTree.Node | undefined,
-      ): boolean =>
-        !!styledNode &&
-        ((styledNode.type === 'CallExpression' &&
-          (isCssCallExpression(styledNode.callee) ||
-            (styledNode.callee.type === 'MemberExpression' &&
-              isStyledCallExpression(styledNode.callee.object)) ||
-            (styledNode.callee.type === 'CallExpression' &&
-              isStyledCallExpression(styledNode.callee.callee)))) ||
-          (styledNode.type === 'JSXExpressionContainer' &&
-            styledNode.parent.type === 'JSXAttribute' &&
-            styledNode.parent.name.name === 'style'))
+      let objectRoot =
+        nodeObject.type === 'ObjectPattern' ? null : getRootObject(nodeObject)
       if (
+        objectRoot &&
         !options.styledComponents &&
-        (isStyledComponents(nodeObject.parent) ||
-          (nodeObject.parent.type === 'ArrowFunctionExpression' &&
-            isStyledComponents(nodeObject.parent.parent)))
+        (isStyledComponents(objectRoot.parent) ||
+          (objectRoot.parent.type === 'ArrowFunctionExpression' &&
+            isStyledComponents(objectRoot.parent.parent)))
       ) {
         return
       }
@@ -575,6 +561,19 @@ let getObjectParent = ({
   return null
 }
 
+let getRootObject = (
+  node: TSESTree.ObjectExpression,
+): TSESTree.ObjectExpression => {
+  let objectRoot = node
+  while (
+    objectRoot.parent.type === 'Property' &&
+    objectRoot.parent.parent.type === 'ObjectExpression'
+  ) {
+    objectRoot = objectRoot.parent.parent
+  }
+  return objectRoot
+}
+
 let getVariableParentName = ({
   onlyFirstParent,
   node,
@@ -624,3 +623,31 @@ let getCallExpressionParentName = ({
 
   return callParent.callee.type === 'Identifier' ? callParent.callee.name : null
 }
+
+let isStyledCallExpression = (identifier: TSESTree.Expression): boolean =>
+  identifier.type === 'Identifier' && identifier.name === 'styled'
+
+let isCssCallExpression = (identifier: TSESTree.Expression): boolean =>
+  identifier.type === 'Identifier' && identifier.name === 'css'
+
+let isStyledComponents = (styledNode: TSESTree.Node): boolean => {
+  if (
+    styledNode.type === 'JSXExpressionContainer' &&
+    styledNode.parent.type === 'JSXAttribute' &&
+    styledNode.parent.name.name === 'style'
+  ) {
+    return true
+  }
+
+  if (styledNode.type !== 'CallExpression') {
+    return false
+  }
+
+  return (
+    isCssCallExpression(styledNode.callee) ||
+    (styledNode.callee.type === 'MemberExpression' &&
+      isStyledCallExpression(styledNode.callee.object)) ||
+    (styledNode.callee.type === 'CallExpression' &&
+      isStyledCallExpression(styledNode.callee.callee))
+  )
+}


Test patch:
diff --git a/test/rules/sort-objects.test.ts b/test/rules/sort-objects.test.ts
index 993a10c..6728a0b 100644
--- a/test/rules/sort-objects.test.ts
+++ b/test/rules/sort-objects.test.ts
@@ -5507,6 +5507,27 @@ describe(ruleName, () => {
               },
             ],
           },
+          {
+            code: dedent`
+              const PropsBox = styled.div((props) => ({
+                nested1: {
+                  nested2: {
+                    [theme.breakpoints.down('mid2')]: {
+                      right: '24px',
+                    },
+                    [theme.breakpoints.down('mid1')]: {
+                      bottom: '-273px',
+                    }
+                  }
+                }
+              }))
+            `,
+            options: [
+              {
+                styledComponents: false,
+              },
+            ],
+          },
         ],
         invalid: [],
       },