Repository: azat-io/eslint-plugin-perfectionist
Pull request #514 (2025-04-15 21:34:43)

fix(sort-imports): fix undetected typescript import-equals dependencies
- Fixes https://github.com/azat-io/eslint-plugin-perfectionist/issues/513.
- Waiting for https://github.com/azat-io/eslint-plugin-perfectionist/pull/505 to be merged before opening.

### Description

In typescript projects targetting Node.js, typescript import-equals re-order may result in runtime errors due to usage before initialization.

## List of all instances of dependencies **not** detected

### `default` import

```ts
import a = aImport.a1.a2;
import aImport from "b";
```

### `wildcard` import

```ts
import a = aImport.a1.a2;
import * as aImport from "b";
```

### `named` import

```ts
import a = aImport.a1.a2;
import { aImport } from "b";
```

### What is the purpose of this pull request?

- [x] Bug fix

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/azat-io/eslint-plugin-perfectionist/pull/514?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azat+S.) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 100.00%. Comparing base [(`4da787d`)](https://app.codecov.io/gh/azat-io/eslint-plugin-perfectionist/commit/4da787d715df5960032f6eab9e8141c124553c0d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azat+S.) to head [(`99c0d8a`)](https://app.codecov.io/gh/azat-io/eslint-plugin-perfectionist/commit/99c0d8a1042f3f4b0ce560b97f1d31b15b8e4ecb?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azat+S.).
> Report is 1 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##              main      #514    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files           96        98     +2     
  Lines         7743      7935   +192     
  Branches      1556      1599    +43     
==========================================
+ Hits          7743      7935   +192     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/azat-io/eslint-plugin-perfectionist/pull/514?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azat+S.).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azat+S.).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>

Patch:
diff --git a/rules/sort-classes.ts b/rules/sort-classes.ts
index ea58246..e40fb82 100644
--- a/rules/sort-classes.ts
+++ b/rules/sort-classes.ts
@@ -543,13 +543,15 @@ export default createEslintRule<SortClassesOptions, MESSAGE_ID>({
             overloadSignatureGroups[overloadSignatureGroupMemberIndex]?.at(-1)
 
           let sortingNode: SortClassSortingNodes = {
-            dependencyName: getDependencyName({
-              nodeNameWithoutStartingHash: name.startsWith('#')
-                ? name.slice(1)
-                : name,
-              isStatic: modifiers.includes('static'),
-              isPrivateHash,
-            }),
+            dependencyNames: [
+              getDependencyName({
+                nodeNameWithoutStartingHash: name.startsWith('#')
+                  ? name.slice(1)
+                  : name,
+                isStatic: modifiers.includes('static'),
+                isPrivateHash,
+              }),
+            ],
             overloadSignaturesGroupId:
               overloadSignatureGroupMemberIndex === -1
                 ? null
diff --git a/rules/sort-enums.ts b/rules/sort-enums.ts
index 5c601e6..956a9c1 100644
--- a/rules/sort-enums.ts
+++ b/rules/sort-enums.ts
@@ -173,6 +173,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
                 : null,
             isEslintDisabled: isNodeEslintDisabled(member, eslintDisabledLines),
             size: rangeToDiff(member, sourceCode),
+            dependencyNames: [name],
             node: member,
             dependencies,
             group,
diff --git a/rules/sort-imports.ts b/rules/sort-imports.ts
index f4f4448..a57780b 100644
--- a/rules/sort-imports.ts
+++ b/rules/sort-imports.ts
@@ -23,6 +23,7 @@ import {
   regexJsonSchema,
 } from '../utils/common-json-schemas'
 import {
+  DEPENDENCY_ORDER_ERROR,
   MISSED_SPACING_ERROR,
   EXTRA_SPACING_ERROR,
   GROUP_ORDER_ERROR,
@@ -39,6 +40,7 @@ import { getOptionsWithCleanGroups } from '../utils/get-options-with-clean-group
 import { computeCommonSelectors } from './sort-imports/compute-common-selectors'
 import { isSideEffectOnlyGroup } from './sort-imports/is-side-effect-only-group'
 import { generatePredefinedGroups } from '../utils/generate-predefined-groups'
+import { sortNodesByDependencies } from '../utils/sort-nodes-by-dependencies'
 import { getEslintDisabledLines } from '../utils/get-eslint-disabled-lines'
 import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled'
 import { doesCustomGroupMatch } from '../utils/does-custom-group-match'
@@ -71,6 +73,7 @@ let defaultGroups = [
 ]
 
 export type MESSAGE_ID =
+  | 'unexpectedImportsDependencyOrder'
   | 'missedSpacingBetweenImports'
   | 'unexpectedImportsGroupOrder'
   | 'extraSpacingBetweenImports'
@@ -256,6 +259,8 @@ export default createEslintRule<Options, MESSAGE_ID>({
           !shouldRegroupSideEffectNodes &&
           (!isStyleSideEffect || !shouldRegroupSideEffectStyleNodes),
         isEslintDisabled: isNodeEslintDisabled(node, eslintDisabledLines),
+        dependencyNames: computeDependencyNames({ sourceCode, node }),
+        dependencies: computeDependencies(node),
         size: rangeToDiff(node, sourceCode),
         addSafetySemicolonWhenInline: true,
         group,
@@ -272,78 +277,99 @@ export default createEslintRule<Options, MESSAGE_ID>({
 
     return {
       'Program:exit': () => {
-        let formattedMembers: SortImportsSortingNode[][] = [[]]
+        let contentSeparatedSortingNodeGroups: SortImportsSortingNode[][][] = [
+          [[]],
+        ]
         for (let sortingNode of sortingNodes) {
-          let lastGroup = formattedMembers.at(-1)
-          let lastSortingNode = lastGroup?.at(-1)
+          let lastGroupWithNoContentBetween =
+            contentSeparatedSortingNodeGroups.at(-1)!
+          let lastGroup = lastGroupWithNoContentBetween.at(-1)!
+          let lastSortingNode = lastGroup.at(-1)
 
           if (
+            lastSortingNode &&
+            hasContentBetweenNodes(sourceCode, lastSortingNode, sortingNode)
+          ) {
+            lastGroup = []
+            lastGroupWithNoContentBetween = [lastGroup]
+            contentSeparatedSortingNodeGroups.push(
+              lastGroupWithNoContentBetween,
+            )
+          } else if (
             shouldPartition({
               lastSortingNode,
               sortingNode,
               sourceCode,
               options,
-            }) ||
-            (lastSortingNode &&
-              hasContentBetweenNodes(sourceCode, lastSortingNode, sortingNode))
+            })
           ) {
             lastGroup = []
-            formattedMembers.push(lastGroup)
+            lastGroupWithNoContentBetween.push(lastGroup)
           }
 
-          lastGroup!.push(sortingNode)
+          lastGroup.push(sortingNode)
         }
 
-        for (let nodes of formattedMembers) {
+        for (let sortingNodeGroups of contentSeparatedSortingNodeGroups) {
           let sortNodesExcludingEslintDisabled = (
             ignoreEslintDisabledNodes: boolean,
-          ): SortImportsSortingNode[] =>
-            sortNodesByGroups({
-              getOptionsByGroupNumber: groupNumber => {
-                let customGroupOverriddenOptions =
-                  getCustomGroupOverriddenOptions({
-                    options: {
-                      ...options,
-                      customGroups: Array.isArray(options.customGroups)
-                        ? options.customGroups
-                        : [],
-                    },
-                    groupNumber,
-                  })
-
-                if (options.sortSideEffects) {
+          ): SortImportsSortingNode[] => {
+            let nodesSortedByGroups = sortingNodeGroups.flatMap(nodes =>
+              sortNodesByGroups({
+                getOptionsByGroupNumber: groupNumber => {
+                  let customGroupOverriddenOptions =
+                    getCustomGroupOverriddenOptions({
+                      options: {
+                        ...options,
+                        customGroups: Array.isArray(options.customGroups)
+                          ? options.customGroups
+                          : [],
+                      },
+                      groupNumber,
+                    })
+
+                  if (options.sortSideEffects) {
+                    return {
+                      options: {
+                        ...options,
+                        ...customGroupOverriddenOptions,
+                      },
+                    }
+                  }
+                  let overriddenOptions = {
+                    ...options,
+                    ...customGroupOverriddenOptions,
+                  }
                   return {
                     options: {
-                      ...options,
-                      ...customGroupOverriddenOptions,
+                      ...overriddenOptions,
+                      type:
+                        overriddenOptions.groups[groupNumber] &&
+                        isSideEffectOnlyGroup(
+                          overriddenOptions.groups[groupNumber],
+                        )
+                          ? 'unsorted'
+                          : overriddenOptions.type,
                     },
                   }
-                }
-                let overriddenOptions = {
-                  ...options,
-                  ...customGroupOverriddenOptions,
-                }
-                return {
-                  options: {
-                    ...overriddenOptions,
-                    type:
-                      overriddenOptions.groups[groupNumber] &&
-                      isSideEffectOnlyGroup(
-                        overriddenOptions.groups[groupNumber],
-                      )
-                        ? 'unsorted'
-                        : overriddenOptions.type,
-                  },
-                }
-              },
-              isNodeIgnored: node => node.isIgnored,
+                },
+                isNodeIgnored: node => node.isIgnored,
+                ignoreEslintDisabledNodes,
+                groups: options.groups,
+                nodes,
+              }),
+            )
+
+            return sortNodesByDependencies(nodesSortedByGroups, {
               ignoreEslintDisabledNodes,
-              groups: options.groups,
-              nodes,
             })
+          }
+
+          let nodes = sortingNodeGroups.flat()
 
           reportAllErrors<MESSAGE_ID>({
             availableMessageIds: {
+              unexpectedDependencyOrder: 'unexpectedImportsDependencyOrder',
               missedSpacingBetweenMembers: 'missedSpacingBetweenImports',
               extraSpacingBetweenMembers: 'extraSpacingBetweenImports',
               unexpectedGroupOrder: 'unexpectedImportsGroupOrder',
@@ -467,6 +493,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
       type: 'array',
     },
     messages: {
+      unexpectedImportsDependencyOrder: DEPENDENCY_ORDER_ERROR,
       missedSpacingBetweenImports: MISSED_SPACING_ERROR,
       extraSpacingBetweenImports: EXTRA_SPACING_ERROR,
       unexpectedImportsGroupOrder: GROUP_ORDER_ERROR,
@@ -607,3 +634,71 @@ let computeGroupExceptUnknown = ({
   }
   return computedCustomGroup
 }
+
+let computeDependencies = (
+  node:
+    | TSESTree.TSImportEqualsDeclaration
+    | TSESTree.VariableDeclaration
+    | TSESTree.ImportDeclaration,
+): string[] => {
+  if (node.type !== 'TSImportEqualsDeclaration') {
+    return []
+  }
+  if (node.moduleReference.type !== 'TSQualifiedName') {
+    return []
+  }
+  let qualifiedName = getQualifiedNameDependencyName(node.moduleReference)
+  /* v8 ignore next 3 - Unsure how we can reach that case */
+  if (!qualifiedName) {
+    return []
+  }
+  return [qualifiedName]
+}
+
+let getQualifiedNameDependencyName = (
+  node: TSESTree.EntityName,
+): string | null => {
+  switch (node.type) {
+    case 'TSQualifiedName':
+      return getQualifiedNameDependencyName(node.left)
+    case 'Identifier':
+      return node.name
+    /* v8 ignore next 3 - Unsure how we can reach that case */
+  }
+  return null
+}
+
+let computeDependencyNames = ({
+  sourceCode,
+  node,
+}: {
+  node:
+    | TSESTree.TSImportEqualsDeclaration
+    | TSESTree.VariableDeclaration
+    | TSESTree.ImportDeclaration
+  sourceCode: TSESLint.SourceCode
+}): string[] => {
+  if (node.type === 'VariableDeclaration') {
+    return []
+  }
+
+  if (node.type === 'TSImportEqualsDeclaration') {
+    return [node.id.name]
+  }
+
+  let returnValue: string[] = []
+  for (let specifier of node.specifiers) {
+    switch (specifier.type) {
+      case 'ImportNamespaceSpecifier':
+        returnValue.push(sourceCode.getText(specifier.local))
+        break
+      case 'ImportDefaultSpecifier':
+        returnValue.push(sourceCode.getText(specifier.local))
+        break
+      case 'ImportSpecifier':
+        returnValue.push(sourceCode.getText(specifier.imported))
+        break
+    }
+  }
+  return returnValue
+}
diff --git a/rules/sort-imports/types.ts b/rules/sort-imports/types.ts
index d4570ce..cf3a509 100644
--- a/rules/sort-imports/types.ts
+++ b/rules/sort-imports/types.ts
@@ -12,8 +12,8 @@ import type {
   RegexOption,
   TypeOption,
 } from '../../types/common-options'
+import type { SortingNodeWithDependencies } from '../../utils/sort-nodes-by-dependencies'
 import type { JoinWithDash } from '../../types/join-with-dash'
-import type { SortingNode } from '../../types/sorting-node'
 
 import {
   buildCustomGroupModifiersJsonSchema,
@@ -73,7 +73,7 @@ export type SingleCustomGroup = {
   elementNamePattern?: RegexOption
 }
 
-export interface SortImportsSortingNode extends SortingNode {
+export interface SortImportsSortingNode extends SortingNodeWithDependencies {
   isIgnored: boolean
 }
 
diff --git a/rules/sort-modules.ts b/rules/sort-modules.ts
index 0f6de76..53b3218 100644
--- a/rules/sort-modules.ts
+++ b/rules/sort-modules.ts
@@ -311,7 +311,7 @@ let analyzeModule = ({
       isEslintDisabled: isNodeEslintDisabled(node, eslintDisabledLines),
       size: rangeToDiff(node, sourceCode),
       addSafetySemicolonWhenInline,
-      dependencyName: name,
+      dependencyNames: [name],
       dependencies,
       group,
       name,
diff --git a/rules/sort-objects.ts b/rules/sort-objects.ts
index 53452f1..221e141 100644
--- a/rules/sort-objects.ts
+++ b/rules/sort-objects.ts
@@ -342,7 +342,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
                 eslintDisabledLines,
               ),
               size: rangeToDiff(property, sourceCode),
-              dependencyName,
+              dependencyNames: [dependencyName],
               node: property,
               dependencies,
               group,
diff --git a/rules/sort-variable-declarations.ts b/rules/sort-variable-declarations.ts
index de4caad..9427bc9 100644
--- a/rules/sort-variable-declarations.ts
+++ b/rules/sort-variable-declarations.ts
@@ -184,6 +184,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
               eslintDisabledLines,
             ),
             size: rangeToDiff(declaration, sourceCode),
+            dependencyNames: [name],
             node: declaration,
             dependencies,
             name,
diff --git a/utils/compute-nodes-in-circular-dependencies.ts b/utils/compute-nodes-in-circular-dependencies.ts
index 15b581b..d4e10fa 100644
--- a/utils/compute-nodes-in-circular-dependencies.ts
+++ b/utils/compute-nodes-in-circular-dependencies.ts
@@ -28,11 +28,11 @@ export let computeNodesInCircularDependencies = <
     path.push(element)
 
     for (let dependency of element.dependencies) {
-      let dependencyElement = elements.find(
-        currentElement =>
-          currentElement.dependencyName === dependency ||
-          currentElement.name === dependency,
-      )
+      let dependencyElement = elements
+        .filter(currentElement => currentElement !== element)
+        .find(currentElement =>
+          currentElement.dependencyNames.includes(dependency),
+        )
       if (dependencyElement) {
         depthFirstSearch(dependencyElement, [...path])
       }
diff --git a/utils/is-node-dependent-on-other-node.ts b/utils/is-node-dependent-on-other-node.ts
new file mode 100644
index 0000000..8cf7cac
--- /dev/null
+++ b/utils/is-node-dependent-on-other-node.ts
@@ -0,0 +1,13 @@
+import type { SortingNodeWithDependencies } from './sort-nodes-by-dependencies'
+
+export let isNodeDependentOnOtherNode = (
+  sortingNode1: SortingNodeWithDependencies,
+  sortingNode2: SortingNodeWithDependencies,
+): boolean => {
+  if (sortingNode1 === sortingNode2) {
+    return false
+  }
+  return sortingNode1.dependencyNames.some(dependency =>
+    sortingNode2.dependencies.includes(dependency),
+  )
+}
diff --git a/utils/report-all-errors.ts b/utils/report-all-errors.ts
index a5462c3..28ee6a4 100644
--- a/utils/report-all-errors.ts
+++ b/utils/report-all-errors.ts
@@ -7,6 +7,7 @@ import type { SortingNode } from '../types/sorting-node'
 import type { MakeFixesParameters } from './make-fixes'
 
 import { computeNodesInCircularDependencies } from './compute-nodes-in-circular-dependencies'
+import { isNodeDependentOnOtherNode } from './is-node-dependent-on-other-node'
 import { createNodeIndexMap } from './create-node-index-map'
 import { getNewlinesErrors } from './get-newlines-errors'
 import { getGroupNumber } from './get-group-number'
@@ -152,9 +153,7 @@ let getFirstUnorderedNodeDependentOn = <T extends SortingNodeWithDependencies>({
   let nodesDependentOnNode = nodes.filter(
     currentlyOrderedNode =>
       !nodesInCircularDependencies.has(currentlyOrderedNode) &&
-      currentlyOrderedNode.dependencies.includes(
-        node.dependencyName ?? node.name,
-      ),
+      isNodeDependentOnOtherNode(node, currentlyOrderedNode),
   )
   return nodesDependentOnNode.find(firstNodeDependentOnNode => {
     let currentIndexOfNode = nodes.indexOf(node)
diff --git a/utils/sort-nodes-by-dependencies.ts b/utils/sort-nodes-by-dependencies.ts
index 52718b6..d568585 100644
--- a/utils/sort-nodes-by-dependencies.ts
+++ b/utils/sort-nodes-by-dependencies.ts
@@ -3,16 +3,12 @@ import type { TSESTree } from '@typescript-eslint/types'
 import type { SortingNode } from '../types/sorting-node'
 
 import { computeNodesInCircularDependencies } from './compute-nodes-in-circular-dependencies'
+import { isNodeDependentOnOtherNode } from './is-node-dependent-on-other-node'
 
 export interface SortingNodeWithDependencies<
   Node extends TSESTree.Node = TSESTree.Node,
 > extends SortingNode<Node> {
-  /**
-   * Custom name used to check if a node is a dependency of another node. If
-   * unspecified, defaults to the SortingNode's name.
-   */
-  dependencyName?: string
-  /** List of dependencies for the node */
+  dependencyNames: string[]
   dependencies: string[]
 }
 
@@ -42,10 +38,8 @@ export let sortNodesByDependencies = <T extends SortingNodeWithDependencies>(
     }
 
     let dependentNodes = nodes
-      .filter(dependentNode => !nodesInCircularDependencies.has(dependentNode))
-      .filter(({ dependencyName, name }) =>
-        sortingNode.dependencies.includes(dependencyName ?? name),
-      )
+      .filter(node => !nodesInCircularDependencies.has(node))
+      .filter(node => isNodeDependentOnOtherNode(node, sortingNode))
 
     for (let dependentNode of dependentNodes) {
       if (


Test patch:
diff --git a/test/rules/sort-imports.test.ts b/test/rules/sort-imports.test.ts
index 50ee9d2..91d5afb 100644
--- a/test/rules/sort-imports.test.ts
+++ b/test/rules/sort-imports.test.ts
@@ -3359,6 +3359,334 @@ describe(ruleName, () => {
         })
       })
     })
+
+    describe('detects dependencies', () => {
+      ruleTester.run(
+        `${ruleName}(${type}): detects typescript import-equals dependencies`,
+        rule,
+        {
+          invalid: [
+            {
+              errors: [
+                {
+                  data: {
+                    nodeDependentOnRight: 'aImport.a1.a2',
+                    right: 'b',
+                  },
+                  messageId: 'unexpectedImportsDependencyOrder',
+                },
+              ],
+              options: [
+                {
+                  ...options,
+                  groups: ['unknown'],
+                },
+              ],
+              output: dedent`
+                import { aImport } from "b";
+                import a = aImport.a1.a2;
+              `,
+              code: dedent`
+                import a = aImport.a1.a2;
+                import { aImport } from "b";
+              `,
+            },
+            {
+              errors: [
+                {
+                  data: {
+                    nodeDependentOnRight: 'aImport.a1.a2',
+                    right: 'b',
+                  },
+                  messageId: 'unexpectedImportsDependencyOrder',
+                },
+              ],
+              options: [
+                {
+                  ...options,
+                  groups: ['unknown'],
+                },
+              ],
+              output: dedent`
+                import * as aImport from "b";
+                import a = aImport.a1.a2;
+              `,
+              code: dedent`
+                import a = aImport.a1.a2;
+                import * as aImport from "b";
+              `,
+            },
+            {
+              errors: [
+                {
+                  data: {
+                    nodeDependentOnRight: 'aImport.a1.a2',
+                    right: 'b',
+                  },
+                  messageId: 'unexpectedImportsDependencyOrder',
+                },
+              ],
+              options: [
+                {
+                  ...options,
+                  groups: ['unknown'],
+                },
+              ],
+              output: dedent`
+                import aImport from "b";
+                import a = aImport.a1.a2;
+              `,
+              code: dedent`
+                import a = aImport.a1.a2;
+                import aImport from "b";
+              `,
+            },
+            {
+              errors: [
+                {
+                  data: {
+                    nodeDependentOnRight: 'aImport.a1.a2',
+                    right: 'b',
+                  },
+                  messageId: 'unexpectedImportsDependencyOrder',
+                },
+              ],
+              options: [
+                {
+                  ...options,
+                  groups: ['unknown'],
+                },
+              ],
+              output: dedent`
+                import aImport = require("b")
+                import a = aImport.a1.a2;
+              `,
+              code: dedent`
+                import a = aImport.a1.a2;
+                import aImport = require("b")
+              `,
+            },
+          ],
+          valid: [],
+        },
+      )
+
+      ruleTester.run(
+        `${ruleName}(${type}): prioritizes dependencies over group configuration`,
+        rule,
+        {
+          invalid: [
+            {
+              options: [
+                {
+                  ...options,
+                  customGroups: [
+                    {
+                      groupName: 'importsStartingWithA',
+                      elementNamePattern: '^a',
+                    },
+                    {
+                      groupName: 'importsStartingWithB',
+                      elementNamePattern: '^b',
+                    },
+                  ],
+                  groups: ['importsStartingWithA', 'importsStartingWithB'],
+                },
+              ],
+              errors: [
+                {
+                  data: {
+                    nodeDependentOnRight: 'aImport.a1.a2',
+                    right: 'b',
+                  },
+                  messageId: 'unexpectedImportsDependencyOrder',
+                },
+                {
+                  data: {
+                    left: 'aImport.a1.a2',
+                    right: 'b',
+                  },
+                  messageId: 'missedSpacingBetweenImports',
+                },
+              ],
+              output: dedent`
+                import aImport from "b";
+
+                import a = aImport.a1.a2;
+              `,
+              code: dedent`
+                import a = aImport.a1.a2;
+                import aImport from "b";
+              `,
+            },
+          ],
+          valid: [
+            {
+              options: [
+                {
+                  ...options,
+                  customGroups: [
+                    {
+                      groupName: 'importsStartingWithA',
+                      elementNamePattern: '^a',
+                    },
+                    {
+                      groupName: 'importsStartingWithB',
+                      elementNamePattern: '^b',
+                    },
+                  ],
+                  groups: ['importsStartingWithA', 'importsStartingWithB'],
+                },
+              ],
+              code: dedent`
+                import aImport from "b";
+                import a = aImport.a1.a2;
+              `,
+            },
+          ],
+        },
+      )
+
+      ruleTester.run(
+        `${ruleName}(${type}): prioritizes dependencies over partitionByComment`,
+        rule,
+        {
+          invalid: [
+            {
+              errors: [
+                {
+                  data: {
+                    nodeDependentOnRight: 'aImport.a1.a2',
+                    right: 'b',
+                  },
+                  messageId: 'unexpectedImportsDependencyOrder',
+                },
+              ],
+              output: dedent`
+                import aImport from "b";
+                // Part: 1
+                import a = aImport.a1.a2;
+              `,
+              options: [
+                {
+                  ...options,
+                  partitionByComment: '^Part',
+                },
+              ],
+              code: dedent`
+                import a = aImport.a1.a2;
+                // Part: 1
+                import aImport from "b";
+              `,
+            },
+          ],
+          valid: [],
+        },
+      )
+
+      ruleTester.run(
+        `${ruleName}(${type}): prioritizes dependencies over partitionByNewLine`,
+        rule,
+        {
+          invalid: [
+            {
+              errors: [
+                {
+                  data: {
+                    nodeDependentOnRight: 'aImport.a1.a2',
+                    right: 'b',
+                  },
+                  messageId: 'unexpectedImportsDependencyOrder',
+                },
+              ],
+              options: [
+                {
+                  ...options,
+                  newlinesBetween: 'ignore',
+                  partitionByNewLine: true,
+                },
+              ],
+              output: dedent`
+                import aImport from "b";
+
+                import a = aImport.a1.a2;
+              `,
+              code: dedent`
+                import a = aImport.a1.a2;
+
+                import aImport from "b";
+              `,
+            },
+          ],
+          valid: [],
+        },
+      )
+
+      ruleTester.run(
+        `${ruleName}(${type}): prioritizes content-separation over dependencies`,
+        rule,
+        {
+          invalid: [
+            {
+              errors: [
+                {
+                  data: {
+                    nodeDependentOnRight: 'yImport.y1.y2',
+                    right: 'z',
+                  },
+                  messageId: 'unexpectedImportsDependencyOrder',
+                },
+                {
+                  data: {
+                    nodeDependentOnRight: 'aImport.a1.a2',
+                    right: 'b',
+                  },
+                  messageId: 'unexpectedImportsDependencyOrder',
+                },
+              ],
+              output: dedent`
+                import f = fImport.f1.f2;
+
+                import yImport from "z";
+
+                import y = yImport.y1.y2;
+
+                export { something } from "something";
+
+                import aImport from "b";
+
+                import a = aImport.a1.a2;
+
+                import fImport from "g";
+              `,
+              code: dedent`
+                import f = fImport.f1.f2;
+
+                import y = yImport.y1.y2;
+
+                import yImport from "z";
+
+                export { something } from "something";
+
+                import a = aImport.a1.a2;
+
+                import aImport from "b";
+
+                import fImport from "g";
+              `,
+              options: [
+                {
+                  ...options,
+                  newlinesBetween: 'ignore',
+                  partitionByNewLine: true,
+                },
+              ],
+            },
+          ],
+          valid: [],
+        },
+      )
+    })
   })
 
   describe(`${ruleName}: sorting by natural order`, () => {
diff --git a/test/utils/compute-nodes-in-circular-dependencies.test.ts b/test/utils/compute-nodes-in-circular-dependencies.test.ts
index c3798e5..0ce8ed5 100644
--- a/test/utils/compute-nodes-in-circular-dependencies.test.ts
+++ b/test/utils/compute-nodes-in-circular-dependencies.test.ts
@@ -37,6 +37,7 @@ describe('computeNodesInCircularDependencies', () => {
   let createTestNode = (name: string): SortingNodeWithDependencies =>
     ({
       dependencies: [] as string[],
+      dependencyNames: [name],
       name,
     }) as SortingNodeWithDependencies
 })
diff --git a/test/utils/is-node-dependent-on-other-node.test.ts b/test/utils/is-node-dependent-on-other-node.test.ts
new file mode 100644
index 0000000..1102c8a
--- /dev/null
+++ b/test/utils/is-node-dependent-on-other-node.test.ts
@@ -0,0 +1,41 @@
+import { describe, expect, it } from 'vitest'
+
+import type { SortingNodeWithDependencies } from '../../utils/sort-nodes-by-dependencies'
+
+import { isNodeDependentOnOtherNode } from '../../utils/is-node-dependent-on-other-node'
+
+describe('isNodeDependentOnOtherNode', () => {
+  it('should return false if the two nodes are strictly equal', () => {
+    let node = createNodeWithDependencies({
+      dependencyNames: [],
+      dependencies: [],
+    })
+
+    expect(isNodeDependentOnOtherNode(node, node)).toBeFalsy()
+  })
+
+  it('should return true if a dependency is among the dependency names', () => {
+    let node1 = createNodeWithDependencies({
+      dependencyNames: ['a', 'b'],
+      dependencies: [],
+    })
+    let node2 = createNodeWithDependencies({
+      dependencyNames: [],
+      dependencies: ['b'],
+    })
+
+    expect(isNodeDependentOnOtherNode(node1, node2)).toBeTruthy()
+  })
+
+  let createNodeWithDependencies = ({
+    dependencyNames,
+    dependencies,
+  }: {
+    dependencyNames: string[]
+    dependencies: string[]
+  }): SortingNodeWithDependencies =>
+    ({
+      dependencyNames,
+      dependencies,
+    }) as SortingNodeWithDependencies
+})