Repository: azat-io/eslint-plugin-perfectionist
Pull request #544 (2025-05-12 16:51:48)

feat(sort-imports): use @/ as internal import pattern by default
### Description

#543

### Additional context

N/A.

---

### What is the purpose of this pull request? <!-- (put an "X" next to an item) -->

- [ ] Bug fix
- [x] New Feature
- [ ] Documentation update
- [ ] Other

### Before submitting the PR, please make sure you do the following

- [x] Check that there isn't already a PR that solves the problem the same way to avoid creating a duplicate.
- [x] Provide a description in this PR that addresses **what** the PR is solving, or reference the issue that it solves.
- [x] Ideally, include relevant tests that fail without this PR but pass with it.
- [x] Read [contribution guidelines](https://github.com/azat-io/eslint-plugin-perfectionist/blob/main/contributing.md).



Patch:
diff --git a/docs/content/rules/sort-imports.mdx b/docs/content/rules/sort-imports.mdx
index de4967d..d3a880d 100644
--- a/docs/content/rules/sort-imports.mdx
+++ b/docs/content/rules/sort-imports.mdx
@@ -203,7 +203,7 @@ Specifies the sorting locales. Refer To [String.prototype.localeCompare() - loca
 
 ### internalPattern
 
-<sub>default: `['^~/.*']`</sub>
+<sub>default: `['^~/.+', '^@/.+']`</sub>
 
 Specifies a pattern for identifying internal imports. This is useful for distinguishing your own modules from external dependencies.
 
diff --git a/docs/content/rules/sort-variable-declarations.mdx b/docs/content/rules/sort-variable-declarations.mdx
index d061c0b..e13c377 100644
--- a/docs/content/rules/sort-variable-declarations.mdx
+++ b/docs/content/rules/sort-variable-declarations.mdx
@@ -88,7 +88,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 code line length (shorter lines first).
 - `'custom'` — Sort items using the alphabet specified in the [`alphabet`](#alphabet) option.
-- `'unsorted'` — Do not sort items.
+- `'unsorted'` — Do not sort items. [`grouping`](#groups) and [`newlines behavior`](#newlinesbetween) are still enforced.
 
 ### order
 
@@ -196,6 +196,125 @@ const
 
 Each group of variables (separated by empty lines) is treated independently, and the order within each group is preserved.
 
+### newlinesBetween
+
+<sub>default: `'ignore'`</sub>
+
+Specifies how to handle new lines between groups.
+
+- `ignore` — Do not report errors related to new lines.
+- `always` — Enforce one new line between each group, and forbid new lines inside a group.
+- `never` — No new lines are allowed.
+
+You can also enforce the newline behavior between two specific groups through the `groups` options.
+
+See the [`groups`](#newlines-between-groups) option.
+
+This option is only applicable when [`partitionByNewLine`](#partitionbynewline) is `false`.
+
+### groups
+
+<sub>
+  type: `Array<string | string[]>`
+</sub>
+<sub>default: `[]`</sub>
+
+Specifies a list of groups for sorting. Groups help organize elements into categories.
+
+Each element will be assigned a single group specified in the `groups` option (or the `unknown` group if no match is found).
+The order of items in the `groups` option determines how groups are ordered.
+
+Within a given group, members will be sorted according to the `type`, `order`, `ignoreCase`, etc. options.
+
+Individual groups can be combined together by placing them in an array. The order of groups in that array does not matter.
+All members of the groups in the array will be sorted together as if they were part of a single group.
+
+Predefined groups are characterized by a selector.
+
+##### List of selectors
+
+- `initialized` — Initialized declarations.
+- `uninitialized` — Uninitialized declarations.
+
+#### Newlines between groups
+
+You may place `newlinesBetween` objects between your groups to enforce the newline behavior between two specific groups.
+
+See the [`newlinesBetween`](#newlinesbetween) option.
+
+This feature is only applicable when [`partitionByNewLine`](#partitionbynewline) is `false`.
+
+```ts
+{
+  newlinesBetween: 'always',
+  groups: [
+    'a',
+    { newlinesBetween: 'never' }, // Overrides the global newlinesBetween option
+    'b',
+  ]
+}
+```
+
+### customGroups
+
+<sub>
+  type: `Array<CustomGroupDefinition | CustomGroupAnyOfDefinition>`
+</sub>
+<sub>default: `[]`</sub>
+
+Defines custom groups to match specific object type members.
+
+A custom group definition may follow one of the two following interfaces:
+
+```ts
+interface CustomGroupDefinition {
+  groupName: string
+  type?: 'alphabetical' | 'natural' | 'line-length' | 'unsorted'
+  order?: 'asc' | 'desc'
+  fallbackSort?: { type: string; order?: 'asc' | 'desc' }
+  newlinesInside?: 'always' | 'never'
+  selector?: string
+  elementNamePattern?: string | string[] | { pattern: string; flags?: string } | { pattern: string; flags?: string }[]
+}
+
+```
+A declaration will match a `CustomGroupDefinition` group if it matches all the filters of the custom group's definition.
+
+or:
+
+```ts
+interface CustomGroupAnyOfDefinition {
+  groupName: string
+  type?: 'alphabetical' | 'natural' | 'line-length' | 'unsorted'
+  order?: 'asc' | 'desc'
+  fallbackSort?: { type: string; order?: 'asc' | 'desc' }
+  newlinesInside?: 'always' | 'never'
+  anyOf: Array<{
+      selector?: string
+      elementNamePattern?: string | string[] | { pattern: string; flags?: string } | { pattern: string; flags?: string }[]
+  }>
+}
+```
+
+A declaration will match a `CustomGroupAnyOfDefinition` group if it matches all the filters of at least one of the `anyOf` items.
+
+#### Attributes
+
+- `groupName` — The group's name, which needs to be put in the [`groups`](#groups) option.
+- `selector` — Filter on the `selector` of the element.
+- `elementNamePattern` — If entered, will check that the name of the element matches the pattern entered.
+- `type` — Overrides the [`type`](#type) option for that custom group. `unsorted` will not sort the group.
+- `order` — Overrides the [`order`](#order) option for that custom group.
+- `fallbackSort` — Overrides the [`fallbackSort`](#fallbacksort) option for that custom group.
+- `newlinesInside` — Enforces a specific newline behavior between elements of the group.
+
+#### Match importance
+
+The `customGroups` list is ordered:
+The first custom group definition that matches an element will be used.
+
+Custom groups have a higher priority than any predefined group.
+
 ## Usage
 
 <CodeTabs
@@ -221,6 +340,9 @@ Each group of variables (separated by empty lines) is treated independently, and
                   specialCharacters: 'keep',
                   partitionByNewLine: false,
                   partitionByComment: false,
+                  newlinesBetween: 'ignore',
+                  groups: [],
+                  customGroups: [],
                 },
               ],
             },
@@ -248,6 +370,9 @@ Each group of variables (separated by empty lines) is treated independently, and
                 specialCharacters: 'keep',
                 partitionByNewLine: false,
                 partitionByComment: false,
+                newlinesBetween: 'ignore',
+                groups: [],
+                customGroups: [],
               },
             ],
           },
diff --git a/rules/sort-imports.ts b/rules/sort-imports.ts
index de89d84..a399bd5 100644
--- a/rules/sort-imports.ts
+++ b/rules/sort-imports.ts
@@ -87,8 +87,8 @@ export default createEslintRule<Options, MESSAGE_ID>({
     let userOptions = context.options.at(0)
     let options = getOptionsWithCleanGroups(
       complete(userOptions, settings, {
+        internalPattern: ['^~/.+', '^@/.+'],
         fallbackSort: { type: 'unsorted' },
-        internalPattern: ['^~/.+'],
         partitionByComment: false,
         partitionByNewLine: false,
         newlinesBetween: 'always',
@@ -489,7 +489,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
   defaultOptions: [
     {
       customGroups: { value: {}, type: {} },
-      internalPattern: ['^~/.+'],
+      internalPattern: ['^~/.+', '^@/.+'],
       partitionByComment: false,
       partitionByNewLine: false,
       specialCharacters: 'keep',
diff --git a/rules/sort-intersection-types.ts b/rules/sort-intersection-types.ts
index 36ae0cf..59dc1d7 100644
--- a/rules/sort-intersection-types.ts
+++ b/rules/sort-intersection-types.ts
@@ -45,7 +45,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
       description: 'Enforce sorted intersection types.',
       recommended: true,
     },
-    schema: [jsonSchema],
+    schema: jsonSchema,
     type: 'suggestion',
     fixable: 'code',
   },
diff --git a/rules/sort-union-types.ts b/rules/sort-union-types.ts
index d18a2eb..86c55f2 100644
--- a/rules/sort-union-types.ts
+++ b/rules/sort-union-types.ts
@@ -65,18 +65,22 @@ let defaultOptions: Required<Options[0]> = {
 }
 
 export let jsonSchema: JSONSchema4 = {
-  properties: {
-    ...commonJsonSchemas,
-    customGroups: buildCustomGroupsArrayJsonSchema({
-      singleCustomGroupJsonSchema,
-    }),
-    partitionByComment: partitionByCommentJsonSchema,
-    partitionByNewLine: partitionByNewLineJsonSchema,
-    newlinesBetween: newlinesBetweenJsonSchema,
-    groups: groupsJsonSchema,
+  items: {
+    properties: {
+      ...commonJsonSchemas,
+      customGroups: buildCustomGroupsArrayJsonSchema({
+        singleCustomGroupJsonSchema,
+      }),
+      partitionByComment: partitionByCommentJsonSchema,
+      partitionByNewLine: partitionByNewLineJsonSchema,
+      newlinesBetween: newlinesBetweenJsonSchema,
+      groups: groupsJsonSchema,
+    },
+    additionalProperties: false,
+    type: 'object',
   },
-  additionalProperties: false,
-  type: 'object',
+  uniqueItems: true,
+  type: 'array',
 }
 
 export default createEslintRule<Options, MESSAGE_ID>({
@@ -107,7 +111,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
       description: 'Enforce sorted union types.',
       recommended: true,
     },
-    schema: [jsonSchema],
+    schema: jsonSchema,
     type: 'suggestion',
     fixable: 'code',
   },
diff --git a/rules/sort-union-types/types.ts b/rules/sort-union-types/types.ts
index d0270c3..44a3427 100644
--- a/rules/sort-union-types/types.ts
+++ b/rules/sort-union-types/types.ts
@@ -14,17 +14,15 @@ import {
   regexJsonSchema,
 } from '../../utils/common-json-schemas'
 
-export type Options = [
-  Partial<
-    {
-      customGroups: CustomGroupsOption<SingleCustomGroup>
-      partitionByComment: PartitionByCommentOption
-      newlinesBetween: NewlinesBetweenOption
-      groups: GroupsOptions<Group>
-      partitionByNewLine: boolean
-    } & CommonOptions
-  >,
-]
+export type Options = Partial<
+  {
+    customGroups: CustomGroupsOption<SingleCustomGroup>
+    partitionByComment: PartitionByCommentOption
+    newlinesBetween: NewlinesBetweenOption
+    groups: GroupsOptions<Group>
+    partitionByNewLine: boolean
+  } & CommonOptions
+>[]
 
 export type Selector =
   | IntersectionSelector
diff --git a/rules/sort-variable-declarations.ts b/rules/sort-variable-declarations.ts
index 9427bc9..9adc4e9 100644
--- a/rules/sort-variable-declarations.ts
+++ b/rules/sort-variable-declarations.ts
@@ -1,41 +1,52 @@
 import type { TSESTree } from '@typescript-eslint/types'
 
-import type {
-  PartitionByCommentOption,
-  CommonOptions,
-} from '../types/common-options'
 import type { SortingNodeWithDependencies } from '../utils/sort-nodes-by-dependencies'
+import type { Selector, Options } from './sort-variable-declarations/types'
 
 import {
+  buildCustomGroupsArrayJsonSchema,
   partitionByCommentJsonSchema,
   partitionByNewLineJsonSchema,
+  newlinesBetweenJsonSchema,
   commonJsonSchemas,
+  groupsJsonSchema,
 } from '../utils/common-json-schemas'
+import {
+  DEPENDENCY_ORDER_ERROR,
+  MISSED_SPACING_ERROR,
+  EXTRA_SPACING_ERROR,
+  GROUP_ORDER_ERROR,
+  ORDER_ERROR,
+} from '../utils/report-errors'
+import { validateNewlinesAndPartitionConfiguration } from '../utils/validate-newlines-and-partition-configuration'
+import { buildGetCustomGroupOverriddenOptionsFunction } from '../utils/get-custom-groups-compare-options'
 import { validateCustomSortConfiguration } from '../utils/validate-custom-sort-configuration'
+import { singleCustomGroupJsonSchema } from './sort-variable-declarations/types'
+import { generatePredefinedGroups } from '../utils/generate-predefined-groups'
 import { sortNodesByDependencies } from '../utils/sort-nodes-by-dependencies'
-import { DEPENDENCY_ORDER_ERROR, ORDER_ERROR } from '../utils/report-errors'
 import { getEslintDisabledLines } from '../utils/get-eslint-disabled-lines'
 import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled'
+import { doesCustomGroupMatch } from '../utils/does-custom-group-match'
+import { sortNodesByGroups } from '../utils/sort-nodes-by-groups'
 import { createEslintRule } from '../utils/create-eslint-rule'
 import { reportAllErrors } from '../utils/report-all-errors'
 import { shouldPartition } from '../utils/should-partition'
+import { computeGroup } from '../utils/compute-group'
 import { rangeToDiff } from '../utils/range-to-diff'
 import { getSettings } from '../utils/get-settings'
 import { isSortable } from '../utils/is-sortable'
-import { sortNodes } from '../utils/sort-nodes'
 import { complete } from '../utils/complete'
 
-type Options = [
-  Partial<
-    {
-      partitionByComment: PartitionByCommentOption
-      partitionByNewLine: boolean
-    } & CommonOptions
-  >,
-]
+/**
+ * Cache computed groups by modifiers and selectors for performance
+ */
+let cachedGroupsByModifiersAndSelectors = new Map<string, string[]>()
 
 type MESSAGE_ID =
+  | 'missedSpacingBetweenVariableDeclarationsMembers'
+  | 'extraSpacingBetweenVariableDeclarationsMembers'
   | 'unexpectedVariableDeclarationsDependencyOrder'
+  | 'unexpectedVariableDeclarationsGroupOrder'
   | 'unexpectedVariableDeclarationsOrder'
 
 let defaultOptions: Required<Options[0]> = {
@@ -43,11 +54,14 @@ let defaultOptions: Required<Options[0]> = {
   specialCharacters: 'keep',
   partitionByNewLine: false,
   partitionByComment: false,
+  newlinesBetween: 'ignore',
   type: 'alphabetical',
+  customGroups: [],
   ignoreCase: true,
   locales: 'en-US',
   alphabet: '',
   order: 'asc',
+  groups: [],
 }
 
 export default createEslintRule<Options, MESSAGE_ID>({
@@ -59,7 +73,9 @@ export default createEslintRule<Options, MESSAGE_ID>({
 
       let settings = getSettings(context.settings)
       let options = complete(context.options.at(0), settings, defaultOptions)
+
       validateCustomSortConfiguration(options)
+      validateNewlinesAndPartitionConfiguration(options)
 
       let { sourceCode, id } = context
       let eslintDisabledLines = getEslintDisabledLines({
@@ -67,98 +83,6 @@ export default createEslintRule<Options, MESSAGE_ID>({
         sourceCode,
       })
 
-      let extractDependencies = (init: TSESTree.Expression): string[] => {
-        let dependencies: string[] = []
-
-        let checkNode = (nodeValue: TSESTree.Node): void => {
-          /**
-           * No need to check the body of functions and arrow functions
-           */
-          if (
-            nodeValue.type === 'ArrowFunctionExpression' ||
-            nodeValue.type === 'FunctionExpression'
-          ) {
-            return
-          }
-
-          if (nodeValue.type === 'Identifier') {
-            dependencies.push(nodeValue.name)
-          }
-
-          if (nodeValue.type === 'Property') {
-            traverseNode(nodeValue.key)
-            traverseNode(nodeValue.value)
-          }
-
-          if (nodeValue.type === 'ConditionalExpression') {
-            traverseNode(nodeValue.test)
-            traverseNode(nodeValue.consequent)
-            traverseNode(nodeValue.alternate)
-          }
-
-          if (
-            'expression' in nodeValue &&
-            typeof nodeValue.expression !== 'boolean'
-          ) {
-            traverseNode(nodeValue.expression)
-          }
-
-          if ('object' in nodeValue) {
-            traverseNode(nodeValue.object)
-          }
-
-          if ('callee' in nodeValue) {
-            traverseNode(nodeValue.callee)
-          }
-
-          if ('left' in nodeValue) {
-            traverseNode(nodeValue.left)
-          }
-
-          if ('right' in nodeValue) {
-            traverseNode(nodeValue.right as TSESTree.Node)
-          }
-
-          if ('elements' in nodeValue) {
-            let elements = nodeValue.elements.filter(
-              currentNode => currentNode !== null,
-            )
-
-            for (let element of elements) {
-              traverseNode(element)
-            }
-          }
-
-          if ('argument' in nodeValue && nodeValue.argument) {
-            traverseNode(nodeValue.argument)
-          }
-
-          if ('arguments' in nodeValue) {
-            for (let argument of nodeValue.arguments) {
-              traverseNode(argument)
-            }
-          }
-
-          if ('properties' in nodeValue) {
-            for (let property of nodeValue.properties) {
-              traverseNode(property)
-            }
-          }
-
-          if ('expressions' in nodeValue) {
-            for (let nodeExpression of nodeValue.expressions) {
-              traverseNode(nodeExpression)
-            }
-          }
-        }
-
-        let traverseNode = (nodeValue: TSESTree.Node): void => {
-          checkNode(nodeValue)
-        }
-
-        traverseNode(init)
-        return dependencies
-      }
       let formattedMembers = node.declarations.reduce(
         (accumulator: SortingNodeWithDependencies[][], declaration) => {
           let name
@@ -172,13 +96,34 @@ export default createEslintRule<Options, MESSAGE_ID>({
             ;({ name } = declaration.id)
           }
 
+          let selector: Selector
+
           let dependencies: string[] = []
           if (declaration.init) {
             dependencies = extractDependencies(declaration.init)
+            selector = 'initialized'
+          } else {
+            selector = 'uninitialized'
           }
+          let predefinedGroups = generatePredefinedGroups({
+            cache: cachedGroupsByModifiersAndSelectors,
+            selectors: [selector],
+            modifiers: [],
+          })
 
           let lastSortingNode = accumulator.at(-1)?.at(-1)
           let sortingNode: SortingNodeWithDependencies = {
+            group: computeGroup({
+              customGroupMatcher: customGroup =>
+                doesCustomGroupMatch({
+                  selectors: [selector],
+                  elementName: name,
+                  modifiers: [],
+                  customGroup,
+                }),
+              predefinedGroups,
+              options,
+            }),
             isEslintDisabled: isNodeEslintDisabled(
               declaration,
               eslintDisabledLines,
@@ -210,26 +155,33 @@ export default createEslintRule<Options, MESSAGE_ID>({
 
       let sortNodesExcludingEslintDisabled = (
         ignoreEslintDisabledNodes: boolean,
-      ): SortingNodeWithDependencies[] =>
-        sortNodesByDependencies(
-          formattedMembers.flatMap(nodes =>
-            sortNodes({
-              ignoreEslintDisabledNodes,
-              options,
-              nodes,
-            }),
-          ),
-          {
+      ): SortingNodeWithDependencies[] => {
+        let nodesSortedByGroups = formattedMembers.flatMap(nodes =>
+          sortNodesByGroups({
+            getOptionsByGroupNumber:
+              buildGetCustomGroupOverriddenOptionsFunction(options),
             ignoreEslintDisabledNodes,
-          },
+            groups: options.groups,
+            nodes,
+          }),
         )
 
+        return sortNodesByDependencies(nodesSortedByGroups, {
+          ignoreEslintDisabledNodes,
+        })
+      }
+
       let nodes = formattedMembers.flat()
 
       reportAllErrors<MESSAGE_ID>({
         availableMessageIds: {
+          missedSpacingBetweenMembers:
+            'missedSpacingBetweenVariableDeclarationsMembers',
+          extraSpacingBetweenMembers:
+            'extraSpacingBetweenVariableDeclarationsMembers',
           unexpectedDependencyOrder:
             'unexpectedVariableDeclarationsDependencyOrder',
+          unexpectedGroupOrder: 'unexpectedVariableDeclarationsGroupOrder',
           unexpectedOrder: 'unexpectedVariableDeclarationsOrder',
         },
         sortNodesExcludingEslintDisabled,
@@ -245,25 +197,126 @@ export default createEslintRule<Options, MESSAGE_ID>({
       {
         properties: {
           ...commonJsonSchemas,
+          customGroups: buildCustomGroupsArrayJsonSchema({
+            singleCustomGroupJsonSchema,
+          }),
           partitionByComment: partitionByCommentJsonSchema,
           partitionByNewLine: partitionByNewLineJsonSchema,
+          newlinesBetween: newlinesBetweenJsonSchema,
+          groups: groupsJsonSchema,
         },
         additionalProperties: false,
         type: 'object',
       },
     ],
+    messages: {
+      missedSpacingBetweenVariableDeclarationsMembers: MISSED_SPACING_ERROR,
+      unexpectedVariableDeclarationsDependencyOrder: DEPENDENCY_ORDER_ERROR,
+      extraSpacingBetweenVariableDeclarationsMembers: EXTRA_SPACING_ERROR,
+      unexpectedVariableDeclarationsGroupOrder: GROUP_ORDER_ERROR,
+      unexpectedVariableDeclarationsOrder: ORDER_ERROR,
+    },
     docs: {
       url: 'https://perfectionist.dev/rules/sort-variable-declarations',
       description: 'Enforce sorted variable declarations.',
       recommended: true,
     },
-    messages: {
-      unexpectedVariableDeclarationsDependencyOrder: DEPENDENCY_ORDER_ERROR,
-      unexpectedVariableDeclarationsOrder: ORDER_ERROR,
-    },
     type: 'suggestion',
     fixable: 'code',
   },
   name: 'sort-variable-declarations',
   defaultOptions: [defaultOptions],
 })
+
+let extractDependencies = (init: TSESTree.Expression): string[] => {
+  let dependencies: string[] = []
+
+  let checkNode = (nodeValue: TSESTree.Node): void => {
+    /**
+     * No need to check the body of functions and arrow functions
+     */
+    if (
+      nodeValue.type === 'ArrowFunctionExpression' ||
+      nodeValue.type === 'FunctionExpression'
+    ) {
+      return
+    }
+
+    if (nodeValue.type === 'Identifier') {
+      dependencies.push(nodeValue.name)
+    }
+
+    if (nodeValue.type === 'Property') {
+      traverseNode(nodeValue.key)
+      traverseNode(nodeValue.value)
+    }
+
+    if (nodeValue.type === 'ConditionalExpression') {
+      traverseNode(nodeValue.test)
+      traverseNode(nodeValue.consequent)
+      traverseNode(nodeValue.alternate)
+    }
+
+    if (
+      'expression' in nodeValue &&
+      typeof nodeValue.expression !== 'boolean'
+    ) {
+      traverseNode(nodeValue.expression)
+    }
+
+    if ('object' in nodeValue) {
+      traverseNode(nodeValue.object)
+    }
+
+    if ('callee' in nodeValue) {
+      traverseNode(nodeValue.callee)
+    }
+
+    if ('left' in nodeValue) {
+      traverseNode(nodeValue.left)
+    }
+
+    if ('right' in nodeValue) {
+      traverseNode(nodeValue.right as TSESTree.Node)
+    }
+
+    if ('elements' in nodeValue) {
+      let elements = nodeValue.elements.filter(
+        currentNode => currentNode !== null,
+      )
+
+      for (let element of elements) {
+        traverseNode(element)
+      }
+    }
+
+    if ('argument' in nodeValue && nodeValue.argument) {
+      traverseNode(nodeValue.argument)
+    }
+
+    if ('arguments' in nodeValue) {
+      for (let argument of nodeValue.arguments) {
+        traverseNode(argument)
+      }
+    }
+
+    if ('properties' in nodeValue) {
+      for (let property of nodeValue.properties) {
+        traverseNode(property)
+      }
+    }
+
+    if ('expressions' in nodeValue) {
+      for (let nodeExpression of nodeValue.expressions) {
+        traverseNode(nodeExpression)
+      }
+    }
+  }
+
+  let traverseNode = (nodeValue: TSESTree.Node): void => {
+    checkNode(nodeValue)
+  }
+
+  traverseNode(init)
+  return dependencies
+}
diff --git a/rules/sort-variable-declarations/types.ts b/rules/sort-variable-declarations/types.ts
new file mode 100644
index 0000000..4cba0a1
--- /dev/null
+++ b/rules/sort-variable-declarations/types.ts
@@ -0,0 +1,45 @@
+import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema'
+
+import type {
+  PartitionByCommentOption,
+  NewlinesBetweenOption,
+  CustomGroupsOption,
+  CommonOptions,
+  GroupsOptions,
+  RegexOption,
+} from '../../types/common-options'
+
+import {
+  buildCustomGroupSelectorJsonSchema,
+  regexJsonSchema,
+} from '../../utils/common-json-schemas'
+
+export type Options = Partial<
+  {
+    customGroups: CustomGroupsOption<SingleCustomGroup>
+    partitionByComment: PartitionByCommentOption
+    newlinesBetween: NewlinesBetweenOption
+    groups: GroupsOptions<Group>
+    partitionByNewLine: boolean
+  } & CommonOptions
+>[]
+
+export interface SingleCustomGroup {
+  elementNamePattern?: RegexOption
+  selector?: Selector
+}
+
+export type Selector = UninitializedSelector | InitializedSelector
+
+type UninitializedSelector = 'uninitialized'
+
+type Group = 'unknown' | Selector | string
+
+type InitializedSelector = 'initialized'
+
+export let allSelectors: Selector[] = ['initialized', 'uninitialized']
+
+export let singleCustomGroupJsonSchema: Record<string, JSONSchema4> = {
+  selector: buildCustomGroupSelectorJsonSchema(allSelectors),
+  elementNamePattern: regexJsonSchema,
+}
diff --git a/utils/report-all-errors.ts b/utils/report-all-errors.ts
index 28ee6a4..3e67821 100644
--- a/utils/report-all-errors.ts
+++ b/utils/report-all-errors.ts
@@ -22,11 +22,11 @@ interface ReportAllErrorsParameters<
     missedSpacingBetweenMembers?: MessageIds
     extraSpacingBetweenMembers?: MessageIds
     unexpectedDependencyOrder?: MessageIds
-    unexpectedGroupOrder?: MessageIds
+    unexpectedGroupOrder: MessageIds
     unexpectedOrder: MessageIds
   }
   options: {
-    groups?: GroupsOptions<string>
+    groups: GroupsOptions<string>
   } & MakeFixesParameters<T>['options']
   sortNodesExcludingEslintDisabled(ignoreEslintDisabledNodes: boolean): T[]
   newlinesBetweenValueGetter?: NewlinesBetweenValueGetter<T>
@@ -61,8 +61,8 @@ export let reportAllErrors = <
       : new Set<SortingNodeWithDependencies>()
 
   pairwise(nodes, (left, right) => {
-    let leftNumber = options.groups ? getGroupNumber(options.groups, left) : 0
-    let rightNumber = options.groups ? getGroupNumber(options.groups, right) : 0
+    let leftNumber = getGroupNumber(options.groups, left)
+    let rightNumber = getGroupNumber(options.groups, right)
 
     let leftIndex = nodeIndexMap.get(left)!
     let rightIndex = nodeIndexMap.get(right)!
@@ -101,7 +101,6 @@ export let reportAllErrors = <
 
     if (
       options.newlinesBetween &&
-      options.groups &&
       availableMessageIds.missedSpacingBetweenMembers &&
       availableMessageIds.extraSpacingBetweenMembers
     ) {
@@ -111,7 +110,6 @@ export let reportAllErrors = <
           options: {
             ...options,
             newlinesBetween: options.newlinesBetween,
-            groups: options.groups,
           },
           missedSpacingError: availableMessageIds.missedSpacingBetweenMembers,
           extraSpacingError: availableMessageIds.extraSpacingBetweenMembers,


Test patch:
diff --git a/test/rules/sort-imports.test.ts b/test/rules/sort-imports.test.ts
index 3ca3346..05f1184 100644
--- a/test/rules/sort-imports.test.ts
+++ b/test/rules/sort-imports.test.ts
@@ -3962,6 +3962,43 @@ describe(ruleName, () => {
       ],
       valid: [],
     })
+
+    ruleTester.run(
+      `${ruleName}(${type}): use pattern @ as internal import`,
+      rule,
+      {
+        invalid: [
+          {
+            errors: [
+              {
+                data: {
+                  right: '@/a',
+                  left: 'b',
+                },
+                messageId: 'missedSpacingBetweenImports',
+              },
+            ],
+            options: [
+              {
+                ...options,
+                groups: ['external', 'internal'],
+                newlinesBetween: 'always',
+              },
+            ],
+            output: dedent`
+              import { b } from 'b'
+
+              import { a } from '@/a'
+            `,
+            code: dedent`
+              import { b } from 'b'
+              import { a } from '@/a'
+            `,
+          },
+        ],
+        valid: [],
+      },
+    )
   })
 
   describe(`${ruleName}: sorting by natural order`, () => {
@@ -5238,6 +5275,43 @@ describe(ruleName, () => {
         ],
       },
     )
+
+    ruleTester.run(
+      `${ruleName}(${type}): use pattern @ as internal import`,
+      rule,
+      {
+        invalid: [
+          {
+            errors: [
+              {
+                data: {
+                  right: '@/a',
+                  left: 'b',
+                },
+                messageId: 'missedSpacingBetweenImports',
+              },
+            ],
+            options: [
+              {
+                ...options,
+                groups: ['external', 'internal'],
+                newlinesBetween: 'always',
+              },
+            ],
+            output: dedent`
+              import { b } from 'b'
+
+              import { a } from '@/a'
+            `,
+            code: dedent`
+              import { b } from 'b'
+              import { a } from '@/a'
+            `,
+          },
+        ],
+        valid: [],
+      },
+    )
   })
 
   describe(`${ruleName}: sorts by custom alphabet`, () => {
@@ -6708,6 +6782,43 @@ describe(ruleName, () => {
         valid: [],
       },
     )
+
+    ruleTester.run(
+      `${ruleName}(${type}): use pattern @ as internal import`,
+      rule,
+      {
+        invalid: [
+          {
+            errors: [
+              {
+                data: {
+                  right: '@/a',
+                  left: 'b',
+                },
+                messageId: 'missedSpacingBetweenImports',
+              },
+            ],
+            options: [
+              {
+                ...options,
+                groups: ['external', 'internal'],
+                newlinesBetween: 'always',
+              },
+            ],
+            output: dedent`
+              import { b } from 'b'
+
+              import { a } from '@/a'
+            `,
+            code: dedent`
+              import { b } from 'b'
+              import { a } from '@/a'
+            `,
+          },
+        ],
+        valid: [],
+      },
+    )
   })
 
   describe(`${ruleName}: validating group configuration`, () => {
diff --git a/test/rules/sort-variable-declarations.test.ts b/test/rules/sort-variable-declarations.test.ts
index d86c6ad..afd9c2e 100644
--- a/test/rules/sort-variable-declarations.test.ts
+++ b/test/rules/sort-variable-declarations.test.ts
@@ -311,213 +311,856 @@ describe(ruleName, () => {
           ],
         },
       )
-    })
 
-    ruleTester.run(
-      `${ruleName}(${type}) detects function expression dependencies`,
-      rule,
-      {
+      ruleTester.run(
+        `${ruleName}(${type}) detects function expression dependencies`,
+        rule,
+        {
+          valid: [
+            {
+              code: dedent`
+                let b = () => 1,
+                a = b();
+              `,
+              options: [options],
+            },
+            {
+              code: dedent`
+                let b = function() { return 1 },
+                a = b();
+              `,
+              options: [options],
+            },
+            {
+              code: dedent`
+                let b = () => 1,
+                a = a.map(b);
+              `,
+              options: [options],
+            },
+          ],
+          invalid: [],
+        },
+      )
+
+      ruleTester.run(
+        `${ruleName}(${type}) detects dependencies in objects`,
+        rule,
+        {
+          valid: [
+            {
+              code: dedent`
+                let b = 1,
+                a = {x: b};
+              `,
+              options: [options],
+            },
+            {
+              code: dedent`
+                let b = 1,
+                a = {[b]: 0};
+              `,
+              options: [options],
+            },
+          ],
+          invalid: [],
+        },
+      )
+
+      ruleTester.run(
+        `${ruleName}(${type}) detects chained dependencies`,
+        rule,
+        {
+          valid: [
+            {
+              code: dedent`
+                let b = {x: 1},
+                a = b.x;
+              `,
+              options: [options],
+            },
+            {
+              code: dedent`
+                let b = new Subject(),
+                a = b.asObservable();
+              `,
+              options: [options],
+            },
+          ],
+          invalid: [],
+        },
+      )
+
+      ruleTester.run(
+        `${ruleName}(${type}) detects optional chained dependencies`,
+        rule,
+        {
+          valid: [
+            {
+              code: dedent`
+                let b = {x: 1},
+                a = b?.x;
+              `,
+              options: [options],
+            },
+          ],
+          invalid: [],
+        },
+      )
+
+      ruleTester.run(
+        `${ruleName}(${type}) detects non-null asserted dependencies`,
+        rule,
+        {
+          valid: [
+            {
+              code: dedent`
+                let b = 1,
+                a = b!;
+              `,
+              options: [options],
+            },
+          ],
+          invalid: [],
+        },
+      )
+
+      ruleTester.run(`${ruleName}(${type}) detects unary dependencies`, rule, {
         valid: [
           {
             code: dedent`
-              let b = () => 1,
-              a = b();
-            `,
-            options: [options],
-          },
-          {
-            code: dedent`
-              let b = function() { return 1 },
-              a = b();
-            `,
-            options: [options],
-          },
-          {
-            code: dedent`
-              let b = () => 1,
-              a = a.map(b);
+              let b = true,
+              a = !b;
             `,
             options: [options],
           },
         ],
         invalid: [],
-      },
-    )
+      })
+
+      ruleTester.run(
+        `${ruleName}(${type}) detects spread elements dependencies`,
+        rule,
+        {
+          valid: [
+            {
+              code: dedent`
+                let b = {x: 1},
+                a = {b = 'b',};
+              `,
+              options: [options],
+            },
+            {
+              code: dedent`
+                let b = [1]
+                a = [b = 'b',];
+              `,
+              options: [options],
+            },
+          ],
+          invalid: [],
+        },
+      )
+
+      ruleTester.run(
+        `${ruleName}(${type}) detects dependencies in conditional expressions`,
+        rule,
+        {
+          valid: [
+            {
+              code: dedent`
+                let b = 0,
+                a = b ? 1 : 0;
+              `,
+              options: [options],
+            },
+            {
+              code: dedent`
+                let b = 0,
+                a = x ? b : 0;
+              `,
+              options: [options],
+            },
+            {
+              code: dedent`
+                let b = 0,
+                a = x ? 0 : b;
+              `,
+              options: [options],
+            },
+          ],
+          invalid: [],
+        },
+      )
+
+      ruleTester.run(
+        `${ruleName}(${type}) detects dependencies in 'as' expressions`,
+        rule,
+        {
+          valid: [
+            {
+              code: dedent`
+                let b = 'b',
+                a = b as any;
+              `,
+              options: [options],
+            },
+          ],
+          invalid: [],
+        },
+      )
+
+      ruleTester.run(
+        `${ruleName}(${type}) detects dependencies in type assertion expressions`,
+        rule,
+        {
+          valid: [
+            {
+              code: dedent`
+                let b = 'b',
+                a = <any>b;
+              `,
+              options: [options],
+            },
+          ],
+          invalid: [],
+        },
+      )
+
+      ruleTester.run(
+        `${ruleName}(${type}) detects dependencies in template literal expressions`,
+        rule,
+        {
+          valid: [
+            {
+              code: dedent`
+                let b = 'b',
+                a = \`\${b}\`
+              `,
+              options: [options],
+            },
+          ],
+          invalid: [],
+        },
+      )
+
+      ruleTester.run(
+        `${ruleName}(${type}) ignores function body dependencies`,
+        rule,
+        {
+          valid: [
+            {
+              code: dedent`
+                let a = () => b,
+                b = 1;
+              `,
+              options: [options],
+            },
+            {
+              code: dedent`
+                let a = function() { return b },
+                b = 1;
+              `,
+              options: [options],
+            },
+            {
+              code: dedent`
+                let a = () => {return b},
+                b = 1;
+              `,
+              options: [options],
+            },
+          ],
+          invalid: [],
+        },
+      )
+
+      ruleTester.run(
+        `${ruleName}(${type}): prioritizes dependencies over group configuration`,
+        rule,
+        {
+          valid: [
+            {
+              options: [
+                {
+                  ...options,
+                  customGroups: [
+                    {
+                      groupName: 'variablesStartingWithA',
+                      elementNamePattern: 'a',
+                    },
+                    {
+                      groupName: 'variablesStartingWithB',
+                      elementNamePattern: 'b',
+                    },
+                  ],
+                  groups: ['variablesStartingWithA', 'variablesStartingWithB'],
+                },
+              ],
+              code: dedent`
+                let
+                  b,
+                  a = b,
+              `,
+            },
+          ],
+          invalid: [],
+        },
+      )
+
+      ruleTester.run(
+        `${ruleName}(${type}): prioritizes dependencies over partitionByComment`,
+        rule,
+        {
+          invalid: [
+            {
+              errors: [
+                {
+                  data: {
+                    nodeDependentOnRight: 'b',
+                    right: 'a',
+                  },
+                  messageId: 'unexpectedVariableDeclarationsDependencyOrder',
+                },
+              ],
+              options: [
+                {
+                  ...options,
+                  partitionByComment: '^Part',
+                },
+              ],
+              output: dedent`
+                let
+                  a = 0,
+                  // Part: 1
+                  b = a,
+              `,
+              code: dedent`
+                let
+                  b = a,
+                  // Part: 1
+                  a = 0,
+              `,
+            },
+          ],
+          valid: [],
+        },
+      )
+
+      ruleTester.run(
+        `${ruleName}(${type}): prioritizes dependencies over partitionByNewLine`,
+        rule,
+        {
+          invalid: [
+            {
+              errors: [
+                {
+                  data: {
+                    nodeDependentOnRight: 'b',
+                    right: 'a',
+                  },
+                  messageId: 'unexpectedVariableDeclarationsDependencyOrder',
+                },
+              ],
+              options: [
+                {
+                  ...options,
+                  partitionByNewLine: true,
+                },
+              ],
+              output: dedent`
+                let
+                  a = 0,
+
+                  b = a,
+              `,
+              code: dedent`
+                let
+                  b = a,
+
+                  a = 0,
+              `,
+            },
+          ],
+          valid: [],
+        },
+      )
+    })
+
+    ruleTester.run(
+      `${ruleName}(${type}): allows to use new line as partition`,
+      rule,
+      {
+        invalid: [
+          {
+            errors: [
+              {
+                data: {
+                  right: 'a',
+                  left: 'd',
+                },
+                messageId: 'unexpectedVariableDeclarationsOrder',
+              },
+              {
+                data: {
+                  right: 'b',
+                  left: 'e',
+                },
+                messageId: 'unexpectedVariableDeclarationsOrder',
+              },
+            ],
+            output: dedent`
+              const
+                a = 'A',
+                d = 'D',
 
-    ruleTester.run(
-      `${ruleName}(${type}) detects dependencies in objects`,
-      rule,
-      {
-        valid: [
-          {
-            code: dedent`
-              let b = 1,
-              a = {x: b};
+                c = 'C',
+
+                b = 'B',
+                e = 'E'
             `,
-            options: [options],
-          },
-          {
             code: dedent`
-              let b = 1,
-              a = {[b]: 0};
+              const
+                d = 'D',
+                a = 'A',
+
+                c = 'C',
+
+                e = 'E',
+                b = 'B'
             `,
-            options: [options],
+            options: [
+              {
+                partitionByNewLine: true,
+                type: 'alphabetical',
+              },
+            ],
           },
         ],
-        invalid: [],
+        valid: [],
       },
     )
 
-    ruleTester.run(`${ruleName}(${type}) detects chained dependencies`, rule, {
-      valid: [
+    describe(`${ruleName}(${type}): partition comments`, () => {
+      ruleTester.run(
+        `${ruleName}(${type}): allows to use partition comments`,
+        rule,
         {
-          code: dedent`
-            let b = {x: 1},
-            a = b.x;
-          `,
-          options: [options],
+          invalid: [
+            {
+              errors: [
+                {
+                  data: {
+                    right: 'bbb',
+                    left: 'd',
+                  },
+                  messageId: 'unexpectedVariableDeclarationsOrder',
+                },
+                {
+                  data: {
+                    right: 'fff',
+                    left: 'gg',
+                  },
+                  messageId: 'unexpectedVariableDeclarationsOrder',
+                },
+              ],
+              output: dedent`
+                const
+                  // Part: A
+                  // Not partition comment
+                  bbb = 'BBB',
+                  cc = 'CC',
+                  d = 'D',
+                  // Part: B
+                  aaa = 'AAA',
+                  e = 'E',
+                  // Part: C
+                  // Not partition comment
+                  fff = 'FFF',
+                  gg = 'GG'
+              `,
+              code: dedent`
+                const
+                  // Part: A
+                  cc = 'CC',
+                  d = 'D',
+                  // Not partition comment
+                  bbb = 'BBB',
+                  // Part: B
+                  aaa = 'AAA',
+                  e = 'E',
+                  // Part: C
+                  gg = 'GG',
+                  // Not partition comment
+                  fff = 'FFF'
+              `,
+              options: [
+                {
+                  ...options,
+                  partitionByComment: '^Part',
+                },
+              ],
+            },
+          ],
+          valid: [],
         },
+      )
+
+      ruleTester.run(
+        `${ruleName}(${type}): allows to use all comments as parts`,
+        rule,
         {
-          code: dedent`
-            let b = new Subject(),
-            a = b.asObservable();
-          `,
-          options: [options],
+          valid: [
+            {
+              code: dedent`
+                const
+                  // Comment
+                  bb = 'bb',
+                  // Other comment
+                  a = 'a'
+              `,
+              options: [
+                {
+                  ...options,
+                  partitionByComment: true,
+                },
+              ],
+            },
+          ],
+          invalid: [],
         },
-      ],
-      invalid: [],
-    })
+      )
 
-    ruleTester.run(
-      `${ruleName}(${type}) detects optional chained dependencies`,
-      rule,
-      {
-        valid: [
-          {
-            code: dedent`
-              let b = {x: 1},
-              a = b?.x;
-            `,
-            options: [options],
-          },
-        ],
-        invalid: [],
-      },
-    )
+      ruleTester.run(
+        `${ruleName}(${type}): allows to use multiple partition comments`,
+        rule,
+        {
+          invalid: [
+            {
+              output: dedent`
+                const
+                  /* Partition Comment */
+                  // Part: A
+                  d = 'D',
+                  // Part: B
+                  aaa = 'AAA',
+                  bb = 'BB',
+                  c = 'C',
+                  /* Other */
+                  e = 'E'
+              `,
+              code: dedent`
+                const
+                  /* Partition Comment */
+                  // Part: A
+                  d = 'D',
+                  // Part: B
+                  aaa = 'AAA',
+                  c = 'C',
+                  bb = 'BB',
+                  /* Other */
+                  e = 'E'
+              `,
+              errors: [
+                {
+                  data: {
+                    right: 'bb',
+                    left: 'c',
+                  },
+                  messageId: 'unexpectedVariableDeclarationsOrder',
+                },
+              ],
+              options: [
+                {
+                  ...options,
+                  partitionByComment: ['Partition Comment', 'Part:', 'Other'],
+                },
+              ],
+            },
+          ],
+          valid: [],
+        },
+      )
 
-    ruleTester.run(
-      `${ruleName}(${type}) detects non-null asserted dependencies`,
-      rule,
-      {
+      ruleTester.run(`${ruleName}(${type}): allows to use regex`, rule, {
         valid: [
           {
             code: dedent`
-              let b = 1,
-              a = b!;
+              const
+                e = 'e',
+                f = 'f',
+                // I am a partition comment because I don't have f o o
+                a = 'a',
+                b = 'b'
             `,
-            options: [options],
+            options: [
+              {
+                ...options,
+                partitionByComment: ['^(?!.*foo).*$'],
+              },
+            ],
           },
         ],
         invalid: [],
-      },
-    )
+      })
 
-    ruleTester.run(`${ruleName}(${type}) detects unary dependencies`, rule, {
-      valid: [
-        {
-          code: dedent`
-            let b = true,
-            a = !b;
-          `,
-          options: [options],
-        },
-      ],
-      invalid: [],
-    })
+      describe(`${ruleName}(${type}): allows to use "partitionByComment.line"`, () => {
+        ruleTester.run(`${ruleName}(${type}): ignores block comments`, rule, {
+          invalid: [
+            {
+              errors: [
+                {
+                  data: {
+                    right: 'a',
+                    left: 'b',
+                  },
+                  messageId: 'unexpectedVariableDeclarationsOrder',
+                },
+              ],
+              options: [
+                {
+                  ...options,
+                  partitionByComment: {
+                    line: true,
+                  },
+                },
+              ],
+              output: dedent`
+                const
+                  /* Comment */
+                  a: 'a',
+                  b: 'b',
+              `,
+              code: dedent`
+                const
+                  b: 'b',
+                  /* Comment */
+                  a: 'a',
+              `,
+            },
+          ],
+          valid: [],
+        })
 
-    ruleTester.run(
-      `${ruleName}(${type}) detects spread elements dependencies`,
-      rule,
-      {
-        valid: [
+        ruleTester.run(
+          `${ruleName}(${type}): allows to use all comments as parts`,
+          rule,
           {
-            code: dedent`
-              let b = {x: 1},
-              a = {...b};
-            `,
-            options: [options],
+            valid: [
+              {
+                options: [
+                  {
+                    ...options,
+                    partitionByComment: {
+                      line: true,
+                    },
+                  },
+                ],
+                code: dedent`
+                  const
+                    b: 'b',
+                    // Comment
+                    a: 'a',
+                `,
+              },
+            ],
+            invalid: [],
           },
+        )
+
+        ruleTester.run(
+          `${ruleName}(${type}): allows to use multiple partition comments`,
+          rule,
           {
-            code: dedent`
-              let b = [1]
-              a = [...b];
-            `,
-            options: [options],
+            valid: [
+              {
+                options: [
+                  {
+                    ...options,
+                    partitionByComment: {
+                      line: ['a', 'b'],
+                    },
+                  },
+                ],
+                code: dedent`
+                  const
+                    c: 'c',
+                    // b
+                    b: 'b',
+                    // a
+                    a: 'a',
+                `,
+              },
+            ],
+            invalid: [],
           },
-        ],
-        invalid: [],
-      },
-    )
+        )
 
-    ruleTester.run(
-      `${ruleName}(${type}) detects dependencies in conditional expressions`,
-      rule,
-      {
-        valid: [
+        ruleTester.run(
+          `${ruleName}(${type}): allows to use regex for partition comments`,
+          rule,
           {
-            code: dedent`
-              let b = 0,
-              a = b ? 1 : 0;
-            `,
-            options: [options],
+            valid: [
+              {
+                options: [
+                  {
+                    ...options,
+                    partitionByComment: {
+                      line: ['^(?!.*foo).*$'],
+                    },
+                  },
+                ],
+                code: dedent`
+                  const
+                    b: 'b',
+                    // I am a partition comment because I don't have f o o
+                    a: 'a',
+                `,
+              },
+            ],
+            invalid: [],
           },
+        )
+      })
+
+      describe(`${ruleName}(${type}): allows to use "partitionByComment.block"`, () => {
+        ruleTester.run(`${ruleName}(${type}): ignores line comments`, rule, {
+          invalid: [
+            {
+              errors: [
+                {
+                  data: {
+                    right: 'a',
+                    left: 'b',
+                  },
+                  messageId: 'unexpectedVariableDeclarationsOrder',
+                },
+              ],
+              options: [
+                {
+                  ...options,
+                  partitionByComment: {
+                    block: true,
+                  },
+                },
+              ],
+              output: dedent`
+                const
+                  // Comment
+                  a: 'a',
+                  b: 'b',
+              `,
+              code: dedent`
+                const
+                  b: 'b',
+                  // Comment
+                  a: 'a',
+              `,
+            },
+          ],
+          valid: [],
+        })
+
+        ruleTester.run(
+          `${ruleName}(${type}): allows to use all comments as parts`,
+          rule,
           {
-            code: dedent`
-              let b = 0,
-              a = x ? b : 0;
-            `,
-            options: [options],
+            valid: [
+              {
+                options: [
+                  {
+                    ...options,
+                    partitionByComment: {
+                      block: true,
+                    },
+                  },
+                ],
+                code: dedent`
+                  const
+                    b: 'b',
+                    /* Comment */
+                    a: 'a',
+                `,
+              },
+            ],
+            invalid: [],
           },
+        )
+
+        ruleTester.run(
+          `${ruleName}(${type}): allows to use multiple partition comments`,
+          rule,
           {
-            code: dedent`
-              let b = 0,
-              a = x ? 0 : b;
-            `,
-            options: [options],
+            valid: [
+              {
+                options: [
+                  {
+                    ...options,
+                    partitionByComment: {
+                      block: ['a', 'b'],
+                    },
+                  },
+                ],
+                code: dedent`
+                  const
+                    c: 'c',
+                    /* b */
+                    b: 'b',
+                    /* a */
+                    a: 'a',
+                `,
+              },
+            ],
+            invalid: [],
           },
-        ],
-        invalid: [],
-      },
-    )
+        )
 
-    ruleTester.run(
-      `${ruleName}(${type}) detects dependencies in 'as' expressions`,
-      rule,
-      {
-        valid: [
+        ruleTester.run(
+          `${ruleName}(${type}): allows to use regex for partition comments`,
+          rule,
           {
-            code: dedent`
-              let b = 'b',
-              a = b as any;
-            `,
-            options: [options],
+            valid: [
+              {
+                options: [
+                  {
+                    ...options,
+                    partitionByComment: {
+                      block: ['^(?!.*foo).*$'],
+                    },
+                  },
+                ],
+                code: dedent`
+                  const
+                    b: 'b',
+                    /* I am a partition comment because I don't have f o o */
+                    a: 'a',
+                `,
+              },
+            ],
+            invalid: [],
           },
-        ],
-        invalid: [],
-      },
-    )
+        )
+      })
+    })
 
     ruleTester.run(
-      `${ruleName}(${type}) detects dependencies in type assertion expressions`,
+      `${ruleName}(${type}): allows to trim special characters`,
       rule,
       {
         valid: [
           {
+            options: [
+              {
+                ...options,
+                specialCharacters: 'trim',
+              },
+            ],
             code: dedent`
-              let b = 'b',
-              a = <any>b;
+              const
+                _a = 'a',
+                b = 'b',
+                _c = 'c'
             `,
-            options: [options],
           },
         ],
         invalid: [],
@@ -525,55 +1168,98 @@ describe(ruleName, () => {
     )
 
     ruleTester.run(
-      `${ruleName}(${type}) detects dependencies in template literal expressions`,
+      `${ruleName}(${type}): allows to remove special characters`,
       rule,
       {
         valid: [
           {
+            options: [
+              {
+                ...options,
+                specialCharacters: 'remove',
+              },
+            ],
             code: dedent`
-              let b = 'b',
-              a = \`\${b}\`
+              const
+                ab = 'ab',
+                a_c = 'ac'
             `,
-            options: [options],
           },
         ],
         invalid: [],
       },
     )
 
+    ruleTester.run(`${ruleName}(${type}): allows to use locale`, rule, {
+      valid: [
+        {
+          code: dedent`
+            const
+              你好 = '你好',
+              世界 = '世界',
+              a = 'a',
+              A = 'A',
+              b = 'b',
+              B = 'B'
+          `,
+          options: [{ ...options, locales: 'zh-CN' }],
+        },
+      ],
+      invalid: [],
+    })
+
     ruleTester.run(
-      `${ruleName}(${type}) ignores function body dependencies`,
+      `${ruleName}(${type}): sorts inline elements correctly`,
       rule,
       {
-        valid: [
+        invalid: [
           {
-            code: dedent`
-              let a = () => b,
-              b = 1;
+            errors: [
+              {
+                data: {
+                  right: 'a',
+                  left: 'b',
+                },
+                messageId: 'unexpectedVariableDeclarationsOrder',
+              },
+            ],
+            output: dedent`
+              const
+                a = 'a', b = 'b'
             `,
-            options: [options],
-          },
-          {
             code: dedent`
-              let a = function() { return b },
-              b = 1;
+              const
+                b = 'b', a = 'a'
             `,
             options: [options],
           },
           {
+            errors: [
+              {
+                data: {
+                  right: 'a',
+                  left: 'b',
+                },
+                messageId: 'unexpectedVariableDeclarationsOrder',
+              },
+            ],
+            output: dedent`
+              const
+                a = 'a', b = 'b',
+            `,
             code: dedent`
-              let a = () => {return b},
-              b = 1;
+              const
+                b = 'b', a = 'a',
             `,
             options: [options],
           },
         ],
-        invalid: [],
+        valid: [],
       },
     )
 
     ruleTester.run(
-      `${ruleName}(${type}): allows to use new line as partition`,
+      `${ruleName}(${type}): allows to use predefined groups`,
       rule,
       {
         invalid: [
@@ -581,567 +1267,863 @@ describe(ruleName, () => {
             errors: [
               {
                 data: {
-                  right: 'a',
-                  left: 'd',
+                  leftGroup: 'uninitialized',
+                  rightGroup: 'initialized',
+                  right: 'b',
+                  left: 'a',
                 },
-                messageId: 'unexpectedVariableDeclarationsOrder',
+                messageId: 'unexpectedVariableDeclarationsGroupOrder',
               },
+            ],
+            options: [
               {
-                data: {
-                  right: 'b',
-                  left: 'e',
-                },
-                messageId: 'unexpectedVariableDeclarationsOrder',
+                ...options,
+                groups: ['initialized', 'uninitialized'],
               },
             ],
             output: dedent`
-              const
-                a = 'A',
-                d = 'D',
-
-                c = 'C',
-
-                b = 'B',
-                e = 'E'
+              let
+                b ='b',
+                a,
             `,
             code: dedent`
-              const
-                d = 'D',
-                a = 'A',
-
-                c = 'C',
-
-                e = 'E',
-                b = 'B'
+              let
+                a,
+                b ='b',
             `,
+          },
+        ],
+        valid: [],
+      },
+    )
+
+    describe(`${ruleName}: custom groups`, () => {
+      ruleTester.run(`${ruleName}: filters on selector`, rule, {
+        invalid: [
+          {
+            errors: [
+              {
+                data: {
+                  rightGroup: 'uninitializedElements',
+                  leftGroup: 'unknown',
+                  right: 'a',
+                  left: 'b',
+                },
+                messageId: 'unexpectedVariableDeclarationsGroupOrder',
+              },
+            ],
             options: [
               {
-                partitionByNewLine: true,
-                type: 'alphabetical',
+                customGroups: [
+                  {
+                    groupName: 'uninitializedElements',
+                    selector: 'uninitialized',
+                  },
+                ],
+                groups: ['uninitializedElements', 'unknown'],
               },
             ],
+            output: dedent`
+              let
+                a,
+                b = 'b',
+            `,
+            code: dedent`
+              let
+                b = 'b',
+                a,
+            `,
           },
         ],
         valid: [],
-      },
-    )
+      })
 
-    describe(`${ruleName}(${type}): partition comments`, () => {
-      ruleTester.run(
-        `${ruleName}(${type}): allows to use partition comments`,
-        rule,
-        {
+      for (let elementNamePattern of [
+        'hello',
+        ['noMatch', 'hello'],
+        { pattern: 'HELLO', flags: 'i' },
+        ['noMatch', { pattern: 'HELLO', flags: 'i' }],
+      ]) {
+        ruleTester.run(`${ruleName}: filters on elementNamePattern`, rule, {
           invalid: [
             {
-              errors: [
+              options: [
                 {
-                  data: {
-                    right: 'bbb',
-                    left: 'd',
-                  },
-                  messageId: 'unexpectedVariableDeclarationsOrder',
+                  customGroups: [
+                    {
+                      groupName: 'uninitializedStartingWithHello',
+                      selector: 'uninitialized',
+                      elementNamePattern,
+                    },
+                  ],
+                  groups: ['uninitializedStartingWithHello', 'unknown'],
                 },
+              ],
+              errors: [
                 {
                   data: {
-                    right: 'fff',
-                    left: 'gg',
+                    rightGroup: 'uninitializedStartingWithHello',
+                    right: 'helloUninitialized',
+                    leftGroup: 'unknown',
+                    left: 'b',
                   },
-                  messageId: 'unexpectedVariableDeclarationsOrder',
+                  messageId: 'unexpectedVariableDeclarationsGroupOrder',
                 },
               ],
               output: dedent`
-                const
-                  // Part: A
-                  // Not partition comment
-                  bbb = 'BBB',
-                  cc = 'CC',
-                  d = 'D',
-                  // Part: B
-                  aaa = 'AAA',
-                  e = 'E',
-                  // Part: C
-                  // Not partition comment
-                  fff = 'FFF',
-                  gg = 'GG'
+                let
+                  helloUninitialized,
+                  a,
+                  b,
               `,
               code: dedent`
-                const
-                  // Part: A
-                  cc = 'CC',
-                  d = 'D',
-                  // Not partition comment
-                  bbb = 'BBB',
-                  // Part: B
-                  aaa = 'AAA',
-                  e = 'E',
-                  // Part: C
-                  gg = 'GG',
-                  // Not partition comment
-                  fff = 'FFF'
+                let
+                  a,
+                  b,
+                  helloUninitialized,
               `,
-              options: [
-                {
-                  ...options,
-                  partitionByComment: '^Part',
-                },
-              ],
             },
           ],
           valid: [],
-        },
-      )
+        })
+      }
 
       ruleTester.run(
-        `${ruleName}(${type}): allows to use all comments as parts`,
+        `${ruleName}: sort custom groups by overriding 'type' and 'order'`,
         rule,
         {
-          valid: [
+          invalid: [
             {
-              code: dedent`
-                const
-                  // Comment
-                  bb = 'bb',
-                  // Other comment
-                  a = 'a'
-              `,
+              errors: [
+                {
+                  data: {
+                    right: 'bb',
+                    left: 'a',
+                  },
+                  messageId: 'unexpectedVariableDeclarationsOrder',
+                },
+                {
+                  data: {
+                    right: 'ccc',
+                    left: 'bb',
+                  },
+                  messageId: 'unexpectedVariableDeclarationsOrder',
+                },
+                {
+                  data: {
+                    right: 'dddd',
+                    left: 'ccc',
+                  },
+                  messageId: 'unexpectedVariableDeclarationsOrder',
+                },
+                {
+                  data: {
+                    rightGroup: 'reversedUninitializedByLineLength',
+                    leftGroup: 'unknown',
+                    right: 'eee',
+                    left: 'm',
+                  },
+                  messageId: 'unexpectedVariableDeclarationsGroupOrder',
+                },
+              ],
               options: [
                 {
-                  ...options,
-                  partitionByComment: true,
+                  customGroups: [
+                    {
+                      groupName: 'reversedUninitializedByLineLength',
+                      selector: 'uninitialized',
+                      type: 'line-length',
+                      order: 'desc',
+                    },
+                  ],
+                  groups: ['reversedUninitializedByLineLength', 'unknown'],
+                  type: 'alphabetical',
+
+                  order: 'asc',
                 },
               ],
+              output: dedent`
+                let
+                  dddd,
+                  ccc,
+                  eee,
+                  bb,
+                  ff,
+                  a,
+                  g,
+                  m = 'm',
+                  o = 'o',
+                  p = 'p',
+              `,
+              code: dedent`
+                let
+                  a,
+                  bb,
+                  ccc,
+                  dddd,
+                  m = 'm',
+                  eee,
+                  ff,
+                  g,
+                  o = 'o',
+                  p = 'p',
+              `,
             },
           ],
-          invalid: [],
+          valid: [],
         },
       )
 
       ruleTester.run(
-        `${ruleName}(${type}): allows to use multiple partition comments`,
+        `${ruleName}: sort custom groups by overriding 'fallbackSort'`,
         rule,
         {
           invalid: [
             {
-              output: dedent`
-                const
-                  /* Partition Comment */
-                  // Part: A
-                  d = 'D',
-                  // Part: B
-                  aaa = 'AAA',
-                  bb = 'BB',
-                  c = 'C',
-                  /* Other */
-                  e = 'E'
-              `,
-              code: dedent`
-                const
-                  /* Partition Comment */
-                  // Part: A
-                  d = 'D',
-                  // Part: B
-                  aaa = 'AAA',
-                  c = 'C',
-                  bb = 'BB',
-                  /* Other */
-                  e = 'E'
-              `,
+              options: [
+                {
+                  customGroups: [
+                    {
+                      fallbackSort: {
+                        type: 'alphabetical',
+                        order: 'asc',
+                      },
+                      elementNamePattern: '^foo',
+                      type: 'line-length',
+                      groupName: 'foo',
+                      order: 'desc',
+                    },
+                  ],
+                  type: 'alphabetical',
+                  groups: ['foo'],
+                  order: 'asc',
+                },
+              ],
               errors: [
                 {
                   data: {
-                    right: 'bb',
-                    left: 'c',
+                    right: 'fooBar',
+                    left: 'fooZar',
                   },
                   messageId: 'unexpectedVariableDeclarationsOrder',
                 },
               ],
-              options: [
-                {
-                  ...options,
-                  partitionByComment: ['Partition Comment', 'Part:', 'Other'],
-                },
-              ],
+              output: dedent`
+                let
+                  fooBar,
+                  fooZar,
+              `,
+              code: dedent`
+                let
+                  fooZar,
+                  fooBar,
+              `,
             },
           ],
           valid: [],
         },
       )
 
-      ruleTester.run(`${ruleName}(${type}): allows to use regex`, rule, {
-        valid: [
-          {
-            code: dedent`
-              const
-                e = 'e',
-                f = 'f',
-                // I am a partition comment because I don't have f o o
-                a = 'a',
-                b = 'b'
-            `,
-            options: [
-              {
-                ...options,
-                partitionByComment: ['^(?!.*foo).*$'],
-              },
-            ],
-          },
-        ],
-        invalid: [],
-      })
-
-      describe(`${ruleName}(${type}): allows to use "partitionByComment.line"`, () => {
-        ruleTester.run(`${ruleName}(${type}): ignores block comments`, rule, {
+      ruleTester.run(
+        `${ruleName}: does not sort custom groups with 'unsorted' type`,
+        rule,
+        {
           invalid: [
             {
-              errors: [
+              options: [
                 {
-                  data: {
-                    right: 'a',
-                    left: 'b',
-                  },
-                  messageId: 'unexpectedVariableDeclarationsOrder',
+                  customGroups: [
+                    {
+                      groupName: 'unsortedUninitialized',
+                      selector: 'uninitialized',
+                      type: 'unsorted',
+                    },
+                  ],
+                  groups: ['unsortedUninitialized', 'unknown'],
                 },
               ],
-              options: [
+              errors: [
                 {
-                  ...options,
-                  partitionByComment: {
-                    line: true,
+                  data: {
+                    rightGroup: 'unsortedUninitialized',
+                    leftGroup: 'unknown',
+                    right: 'c',
+                    left: 'm',
                   },
+                  messageId: 'unexpectedVariableDeclarationsGroupOrder',
                 },
               ],
               output: dedent`
-                const
-                  /* Comment */
-                  a: 'a',
-                  b: 'b',
+                let
+                  b,
+                  a,
+                  d,
+                  e,
+                  c,
+                  m = 'm',
               `,
               code: dedent`
-                const
-                  b: 'b',
-                  /* Comment */
-                  a: 'a',
+                let
+                  b,
+                  a,
+                  d,
+                  e,
+                  m = 'm',
+                  c,
               `,
             },
           ],
           valid: [],
-        })
+        },
+      )
 
-        ruleTester.run(
-          `${ruleName}(${type}): allows to use all comments as parts`,
-          rule,
+      ruleTester.run(`${ruleName}: sort custom group blocks`, rule, {
+        invalid: [
           {
-            valid: [
+            options: [
               {
-                options: [
+                customGroups: [
                   {
-                    ...options,
-                    partitionByComment: {
-                      line: true,
-                    },
+                    anyOf: [
+                      {
+                        elementNamePattern: 'foo|Foo',
+                        selector: 'uninitialized',
+                      },
+                      {
+                        elementNamePattern: 'foo|Foo',
+                        selector: 'initialized',
+                      },
+                    ],
+                    groupName: 'elementsIncludingFoo',
                   },
                 ],
-                code: dedent`
-                  const
-                    b: 'b',
-                    // Comment
-                    a: 'a',
-                `,
+                groups: ['elementsIncludingFoo', 'unknown'],
               },
             ],
-            invalid: [],
-          },
-        )
-
-        ruleTester.run(
-          `${ruleName}(${type}): allows to use multiple partition comments`,
-          rule,
-          {
-            valid: [
+            errors: [
               {
-                options: [
-                  {
-                    ...options,
-                    partitionByComment: {
-                      line: ['a', 'b'],
-                    },
-                  },
-                ],
-                code: dedent`
-                  const
-                    c: 'c',
-                    // b
-                    b: 'b',
-                    // a
-                    a: 'a',
-                `,
+                data: {
+                  rightGroup: 'elementsIncludingFoo',
+                  leftGroup: 'unknown',
+                  right: 'cFoo',
+                  left: 'a',
+                },
+                messageId: 'unexpectedVariableDeclarationsGroupOrder',
               },
             ],
-            invalid: [],
+            output: dedent`
+              let
+                cFoo,
+                foo = 'foo',
+                a,
+            `,
+            code: dedent`
+              let
+                a,
+                cFoo,
+                foo = 'foo',
+            `,
           },
-        )
+        ],
+        valid: [],
+      })
 
-        ruleTester.run(
-          `${ruleName}(${type}): allows to use regex for partition comments`,
-          rule,
-          {
-            valid: [
-              {
-                options: [
-                  {
-                    ...options,
-                    partitionByComment: {
-                      line: ['^(?!.*foo).*$'],
+      ruleTester.run(
+        `${ruleName}: allows to use regex for element names in custom groups`,
+        rule,
+        {
+          valid: [
+            {
+              options: [
+                {
+                  customGroups: [
+                    {
+                      elementNamePattern: '^(?!.*Foo).*$',
+                      groupName: 'elementsWithoutFoo',
                     },
-                  },
-                ],
-                code: dedent`
-                  const
-                    b: 'b',
-                    // I am a partition comment because I don't have f o o
-                    a: 'a',
-                `,
-              },
-            ],
-            invalid: [],
-          },
-        )
-      })
+                  ],
+                  groups: ['unknown', 'elementsWithoutFoo'],
+                  type: 'alphabetical',
+                },
+              ],
+              code: dedent`
+                let
+                  iHaveFooInMyName,
+                  meTooIHaveFoo,
+                  a,
+                  b,
+              `,
+            },
+          ],
+          invalid: [],
+        },
+      )
+    })
 
-      describe(`${ruleName}(${type}): allows to use "partitionByComment.block"`, () => {
-        ruleTester.run(`${ruleName}(${type}): ignores line comments`, rule, {
+    describe(`${ruleName}: newlinesBetween`, () => {
+      ruleTester.run(
+        `${ruleName}(${type}): removes newlines when never`,
+        rule,
+        {
           invalid: [
             {
               errors: [
                 {
                   data: {
-                    right: 'a',
-                    left: 'b',
+                    right: 'y',
+                    left: 'a',
+                  },
+                  messageId: 'extraSpacingBetweenVariableDeclarationsMembers',
+                },
+                {
+                  data: {
+                    right: 'b',
+                    left: 'z',
                   },
                   messageId: 'unexpectedVariableDeclarationsOrder',
                 },
+                {
+                  data: {
+                    right: 'b',
+                    left: 'z',
+                  },
+                  messageId: 'extraSpacingBetweenVariableDeclarationsMembers',
+                },
               ],
               options: [
                 {
                   ...options,
-                  partitionByComment: {
-                    block: true,
-                  },
+                  customGroups: [
+                    {
+                      elementNamePattern: 'a',
+                      groupName: 'a',
+                    },
+                  ],
+                  groups: ['a', 'unknown'],
+                  newlinesBetween: 'never',
                 },
               ],
-              output: dedent`
-                const
-                  // Comment
-                  a: 'a',
-                  b: 'b',
-              `,
               code: dedent`
-                const
-                  b: 'b',
-                  // Comment
-                  a: 'a',
+                let
+                  a,
+
+
+                 y,
+                z,
+
+                    b,
+              `,
+              output: dedent`
+                let
+                  a,
+                 b,
+                y,
+                    z,
               `,
             },
           ],
           valid: [],
-        })
+        },
+      )
 
-        ruleTester.run(
-          `${ruleName}(${type}): allows to use all comments as parts`,
-          rule,
-          {
-            valid: [
-              {
-                options: [
-                  {
-                    ...options,
-                    partitionByComment: {
-                      block: true,
-                    },
+      ruleTester.run(
+        `${ruleName}(${type}): keeps one newline when always`,
+        rule,
+        {
+          invalid: [
+            {
+              errors: [
+                {
+                  data: {
+                    right: 'z',
+                    left: 'a',
                   },
-                ],
-                code: dedent`
-                  const
-                    b: 'b',
-                    /* Comment */
-                    a: 'a',
-                `,
-              },
-            ],
-            invalid: [],
-          },
-        )
+                  messageId: 'extraSpacingBetweenVariableDeclarationsMembers',
+                },
+                {
+                  data: {
+                    right: 'y',
+                    left: 'z',
+                  },
+                  messageId: 'unexpectedVariableDeclarationsOrder',
+                },
+                {
+                  data: {
+                    right: 'b',
+                    left: 'y',
+                  },
+                  messageId: 'missedSpacingBetweenVariableDeclarationsMembers',
+                },
+              ],
+              options: [
+                {
+                  ...options,
+                  customGroups: [
+                    {
+                      elementNamePattern: 'a',
+                      groupName: 'a',
+                    },
+                    {
+                      elementNamePattern: 'b',
+                      groupName: 'b',
+                    },
+                  ],
+                  groups: ['a', 'unknown', 'b'],
+                  newlinesBetween: 'always',
+                },
+              ],
+              output: dedent`
+                let
+                  a,
+
+                 y,
+                z,
+
+                    b,
+              `,
+              code: dedent`
+                let
+                  a,
+
+
+                 z,
+                y,
+                    b,
+              `,
+            },
+          ],
+          valid: [],
+        },
+      )
 
+      describe(`${ruleName}(${type}): "newlinesBetween" inside groups`, () => {
         ruleTester.run(
-          `${ruleName}(${type}): allows to use multiple partition comments`,
+          `${ruleName}(${type}): handles "newlinesBetween" between consecutive groups`,
           rule,
           {
-            valid: [
+            invalid: [
               {
                 options: [
                   {
                     ...options,
-                    partitionByComment: {
-                      block: ['a', 'b'],
+                    groups: [
+                      'a',
+                      { newlinesBetween: 'always' },
+                      'b',
+                      { newlinesBetween: 'always' },
+                      'c',
+                      { newlinesBetween: 'never' },
+                      'd',
+                      { newlinesBetween: 'ignore' },
+                      'e',
+                    ],
+                    customGroups: [
+                      { elementNamePattern: 'a', groupName: 'a' },
+                      { elementNamePattern: 'b', groupName: 'b' },
+                      { elementNamePattern: 'c', groupName: 'c' },
+                      { elementNamePattern: 'd', groupName: 'd' },
+                      { elementNamePattern: 'e', groupName: 'e' },
+                    ],
+                    newlinesBetween: 'always',
+                  },
+                ],
+                errors: [
+                  {
+                    data: {
+                      right: 'b',
+                      left: 'a',
+                    },
+                    messageId:
+                      'missedSpacingBetweenVariableDeclarationsMembers',
+                  },
+                  {
+                    data: {
+                      right: 'c',
+                      left: 'b',
+                    },
+                    messageId: 'extraSpacingBetweenVariableDeclarationsMembers',
+                  },
+                  {
+                    data: {
+                      right: 'd',
+                      left: 'c',
                     },
+                    messageId: 'extraSpacingBetweenVariableDeclarationsMembers',
                   },
                 ],
+                output: dedent`
+                  let
+                    a,
+
+                    b,
+
+                    c,
+                    d,
+
+
+                    e
+                `,
                 code: dedent`
-                  const
-                    c: 'c',
-                    /* b */
-                    b: 'b',
-                    /* a */
-                    a: 'a',
+                  let
+                    a,
+                    b,
+
+
+                    c,
+
+                    d,
+
+
+                    e
                 `,
               },
             ],
-            invalid: [],
+            valid: [],
           },
         )
 
-        ruleTester.run(
-          `${ruleName}(${type}): allows to use regex for partition comments`,
-          rule,
-          {
-            valid: [
+        describe(`${ruleName}(${type}): "newlinesBetween" between non-consecutive groups`, () => {
+          for (let [globalNewlinesBetween, groupNewlinesBetween] of [
+            ['always', 'never'] as const,
+            ['always', 'ignore'] as const,
+            ['never', 'always'] as const,
+            ['ignore', 'always'] as const,
+          ]) {
+            ruleTester.run(
+              `${ruleName}(${type}): enforces a newline if the global option is "${globalNewlinesBetween}" and the group option is "${groupNewlinesBetween}"`,
+              rule,
               {
-                options: [
+                invalid: [
                   {
-                    ...options,
-                    partitionByComment: {
-                      block: ['^(?!.*foo).*$'],
-                    },
+                    options: [
+                      {
+                        ...options,
+                        customGroups: [
+                          { elementNamePattern: 'a', groupName: 'a' },
+                          { elementNamePattern: 'b', groupName: 'b' },
+                          { groupName: 'unusedGroup', elementNamePattern: 'X' },
+                        ],
+                        groups: [
+                          'a',
+                          'unusedGroup',
+                          { newlinesBetween: groupNewlinesBetween },
+                          'b',
+                        ],
+                        newlinesBetween: globalNewlinesBetween,
+                      },
+                    ],
+                    errors: [
+                      {
+                        data: {
+                          right: 'b',
+                          left: 'a',
+                        },
+                        messageId:
+                          'missedSpacingBetweenVariableDeclarationsMembers',
+                      },
+                    ],
+                    output: dedent`
+                      let
+                        a,
+
+                        b,
+                    `,
+                    code: dedent`
+                      let
+                        a,
+                        b,
+                    `,
                   },
                 ],
-                code: dedent`
-                  const
-                    b: 'b',
-                    /* I am a partition comment because I don't have f o o */
-                    a: 'a',
-                `,
+                valid: [],
               },
-            ],
-            invalid: [],
-          },
-        )
-      })
-    })
-
-    ruleTester.run(
-      `${ruleName}(${type}): allows to trim special characters`,
-      rule,
-      {
-        valid: [
-          {
-            options: [
+            )
+          }
+
+          for (let globalNewlinesBetween of [
+            'always',
+            'ignore',
+            'never',
+          ] as const) {
+            ruleTester.run(
+              `${ruleName}(${type}): enforces no newline if the global option is "${globalNewlinesBetween}" and "newlinesBetween: never" exists between all groups`,
+              rule,
               {
-                ...options,
-                specialCharacters: 'trim',
+                invalid: [
+                  {
+                    options: [
+                      {
+                        ...options,
+                        groups: [
+                          'a',
+                          { newlinesBetween: 'never' },
+                          'unusedGroup',
+                          { newlinesBetween: 'never' },
+                          'b',
+                          { newlinesBetween: 'always' },
+                          'c',
+                        ],
+                        customGroups: [
+                          { elementNamePattern: 'a', groupName: 'a' },
+                          { elementNamePattern: 'b', groupName: 'b' },
+                          { elementNamePattern: 'c', groupName: 'c' },
+                          { groupName: 'unusedGroup', elementNamePattern: 'X' },
+                        ],
+                        newlinesBetween: globalNewlinesBetween,
+                      },
+                    ],
+                    errors: [
+                      {
+                        data: {
+                          right: 'b',
+                          left: 'a',
+                        },
+                        messageId:
+                          'extraSpacingBetweenVariableDeclarationsMembers',
+                      },
+                    ],
+                    output: dedent`
+                      let
+                        a,
+                        b,
+                    `,
+                    code: dedent`
+                      let
+                        a,
+
+                        b,
+                    `,
+                  },
+                ],
+                valid: [],
               },
-            ],
-            code: dedent`
-              const
-                _a = 'a',
-                b = 'b',
-                _c = 'c'
-            `,
-          },
-        ],
-        invalid: [],
-      },
-    )
-
-    ruleTester.run(
-      `${ruleName}(${type}): allows to remove special characters`,
-      rule,
-      {
-        valid: [
-          {
-            options: [
+            )
+          }
+
+          for (let [globalNewlinesBetween, groupNewlinesBetween] of [
+            ['ignore', 'never'] as const,
+            ['never', 'ignore'] as const,
+          ]) {
+            ruleTester.run(
+              `${ruleName}(${type}): does not enforce a newline if the global option is "${globalNewlinesBetween}" and the group option is "${groupNewlinesBetween}"`,
+              rule,
               {
-                ...options,
-                specialCharacters: 'remove',
+                valid: [
+                  {
+                    options: [
+                      {
+                        ...options,
+                        customGroups: [
+                          { elementNamePattern: 'a', groupName: 'a' },
+                          { elementNamePattern: 'b', groupName: 'b' },
+                          { groupName: 'unusedGroup', elementNamePattern: 'X' },
+                        ],
+                        groups: [
+                          'a',
+                          'unusedGroup',
+                          { newlinesBetween: groupNewlinesBetween },
+                          'b',
+                        ],
+                        newlinesBetween: globalNewlinesBetween,
+                      },
+                    ],
+                    code: dedent`
+                      let
+                        a,
+
+                        b,
+                    `,
+                  },
+                  {
+                    options: [
+                      {
+                        ...options,
+                        customGroups: [
+                          { elementNamePattern: 'a', groupName: 'a' },
+                          { elementNamePattern: 'b', groupName: 'b' },
+                          { groupName: 'unusedGroup', elementNamePattern: 'X' },
+                        ],
+                        groups: [
+                          'a',
+                          'unusedGroup',
+                          { newlinesBetween: groupNewlinesBetween },
+                          'b',
+                        ],
+                        newlinesBetween: globalNewlinesBetween,
+                      },
+                    ],
+                    code: dedent`
+                      let
+                        a,
+                        b,
+                    `,
+                  },
+                ],
+                invalid: [],
               },
-            ],
-            code: dedent`
-              const
-                ab = 'ab',
-                a_c = 'ac'
-            `,
-          },
-        ],
-        invalid: [],
-      },
-    )
+            )
+          }
+        })
+      })
 
-    ruleTester.run(`${ruleName}(${type}): allows to use locale`, rule, {
-      valid: [
+      ruleTester.run(
+        `${ruleName}(${type}): handles newlines and comment after fixes`,
+        rule,
         {
-          code: dedent`
-            const
-              你好 = '你好',
-              世界 = '世界',
-              a = 'a',
-              A = 'A',
-              b = 'b',
-              B = 'B'
-          `,
-          options: [{ ...options, locales: 'zh-CN' }],
-        },
-      ],
-      invalid: [],
-    })
-
-    ruleTester.run(
-      `${ruleName}(${type}): sorts inline elements correctly`,
-      rule,
-      {
-        invalid: [
-          {
-            errors: [
-              {
-                data: {
-                  right: 'a',
-                  left: 'b',
+          invalid: [
+            {
+              options: [
+                {
+                  customGroups: [
+                    {
+                      elementNamePattern: 'b|c',
+                      groupName: 'b|c',
+                    },
+                  ],
+                  groups: ['unknown', 'b|c'],
+                  newlinesBetween: 'always',
                 },
-                messageId: 'unexpectedVariableDeclarationsOrder',
-              },
-            ],
-            output: dedent`
-              const
-                a = 'a', b = 'b'
-            `,
-            code: dedent`
-              const
-                b = 'b', a = 'a'
-            `,
-            options: [options],
-          },
-          {
-            errors: [
-              {
-                data: {
-                  right: 'a',
-                  left: 'b',
+              ],
+              output: [
+                dedent`
+                  let
+                    a, // Comment after
+                    b,
+
+                    c
+                `,
+                dedent`
+                  let
+                    a, // Comment after
+
+                    b,
+                    c
+                `,
+              ],
+              errors: [
+                {
+                  data: {
+                    rightGroup: 'unknown',
+                    leftGroup: 'b|c',
+                    right: 'a',
+                    left: 'b',
+                  },
+                  messageId: 'unexpectedVariableDeclarationsGroupOrder',
                 },
-                messageId: 'unexpectedVariableDeclarationsOrder',
-              },
-            ],
-            output: dedent`
-              const
-                a = 'a', b = 'b',
-            `,
-            code: dedent`
-              const
-                b = 'b', a = 'a',
-            `,
-            options: [options],
-          },
-        ],
-        valid: [],
-      },
-    )
+              ],
+              code: dedent`
+                let
+                  b,
+                  a, // Comment after
+
+                  c
+              `,
+            },
+          ],
+          valid: [],
+        },
+      )
+    })
   })
 
   describe(`${ruleName}: sorting by natural order`, () => {
@@ -1645,6 +2627,51 @@ describe(ruleName, () => {
       invalid: [],
     })
 
+    ruleTester.run(`${ruleName}(${type}): enforces newlines between`, rule, {
+      invalid: [
+        {
+          options: [
+            {
+              ...options,
+              customGroups: [
+                {
+                  elementNamePattern: 'a',
+                  groupName: 'a',
+                },
+                {
+                  elementNamePattern: 'b',
+                  groupName: 'b',
+                },
+              ],
+              newlinesBetween: 'always',
+              groups: ['b', 'a'],
+            },
+          ],
+          errors: [
+            {
+              data: {
+                right: 'a',
+                left: 'b',
+              },
+              messageId: 'missedSpacingBetweenVariableDeclarationsMembers',
+            },
+          ],
+          output: dedent`
+            let
+              b,
+
+              a,
+          `,
+          code: dedent`
+            let
+              b,
+              a,
+          `,
+        },
+      ],
+      valid: [],
+    })
+
     ruleTester.run(`${ruleName}(${type}): enforces dependency sorting`, rule, {
       invalid: [
         {