Repository: azat-io/eslint-plugin-perfectionist
Pull request #506 (2025-04-06 21:51:45)

refactor(sort-imports): use `computeGroup` instead of `useGroups`
- Follow-up of https://github.com/azat-io/eslint-plugin-perfectionist/pull/500.

### Description

In this PR, we are finally removing `useGroups` in favor of `computeGroup`. Additionally, we are simplifying options in tests to lighten the total number of lines of code. 

No actual test input/output changed.

### What is the purpose of this pull request?

- [x] Other



codecov[bot]: ## [Codecov](https://app.codecov.io/gh/azat-io/eslint-plugin-perfectionist/pull/506?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azat+S.) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 100.00%. Comparing base [(`b05fb54`)](https://app.codecov.io/gh/azat-io/eslint-plugin-perfectionist/commit/b05fb549bd2f72efb5b229036a4c7745b2d02161?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azat+S.) to head [(`9a182af`)](https://app.codecov.io/gh/azat-io/eslint-plugin-perfectionist/commit/9a182afec5051efa405ba0e414290870c22de586?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azat+S.).

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


```diff
@@            Coverage Diff            @@
##              main      #506   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           96        95    -1     
  Lines         7651      7665   +14     
  Branches      1578      1572    -6     
=========================================
+ Hits          7651      7665   +14     
```

</details>

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

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

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

Patch:
diff --git a/docs/content/rules/sort-array-includes.mdx b/docs/content/rules/sort-array-includes.mdx
index ecfa1da..bc7b56a 100644
--- a/docs/content/rules/sort-array-includes.mdx
+++ b/docs/content/rules/sort-array-includes.mdx
@@ -366,7 +366,7 @@ This feature is only applicable when `partitionByNewLine` is false.
 <sub>
   type: `Array<CustomGroupDefinition | CustomGroupAnyOfDefinition>`
 </sub>
-<sub>default: `{}`</sub>
+<sub>default: `[]`</sub>
 
 You can define your own groups and use regexp patterns to match specific object type members.
 
diff --git a/docs/content/rules/sort-enums.mdx b/docs/content/rules/sort-enums.mdx
index 7b66bd4..d8e258b 100644
--- a/docs/content/rules/sort-enums.mdx
+++ b/docs/content/rules/sort-enums.mdx
@@ -280,7 +280,7 @@ This feature is only applicable when `partitionByNewLine` is false.
 <sub>
   type: `Array<CustomGroupDefinition | CustomGroupAnyOfDefinition>`
 </sub>
-<sub>default: `{}`</sub>
+<sub>default: `[]`</sub>
 
 You can define your own groups and use regexp patterns to match specific elements.
 
diff --git a/docs/content/rules/sort-exports.mdx b/docs/content/rules/sort-exports.mdx
index c8dc749..d9c1ce3 100644
--- a/docs/content/rules/sort-exports.mdx
+++ b/docs/content/rules/sort-exports.mdx
@@ -282,7 +282,7 @@ This feature is only applicable when `partitionByNewLine` is false.
 <sub>
   type: `Array<CustomGroupDefinition | CustomGroupAnyOfDefinition>`
 </sub>
-<sub>default: `{}`</sub>
+<sub>default: `[]`</sub>
 
 You can define your own groups and use regexp patterns to match specific exports.
 
diff --git a/docs/content/rules/sort-maps.mdx b/docs/content/rules/sort-maps.mdx
index 567d1f1..aa30eca 100644
--- a/docs/content/rules/sort-maps.mdx
+++ b/docs/content/rules/sort-maps.mdx
@@ -310,7 +310,7 @@ This feature is only applicable when `partitionByNewLine` is false.
 <sub>
   type: `Array<CustomGroupDefinition | CustomGroupAnyOfDefinition>`
 </sub>
-<sub>default: `{}`</sub>
+<sub>default: `[]`</sub>
 
 You can define your own groups and use regexp patterns to match specific elements.
 
diff --git a/docs/content/rules/sort-named-exports.mdx b/docs/content/rules/sort-named-exports.mdx
index 6ffc6e9..fd9f44b 100644
--- a/docs/content/rules/sort-named-exports.mdx
+++ b/docs/content/rules/sort-named-exports.mdx
@@ -107,7 +107,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 the length of the code line (shorter lines first).
 - `'custom'` — Sort items using the alphabet entered 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
 
@@ -190,10 +190,12 @@ Determines whether to use the export alias as the name for sorting instead of th
 - `true` — Use the export alias for sorting.
 - `false` — Use the local name for sorting.
 
-### groupKind
+### [DEPRECATED] groupKind
 
 <sub>default: `'mixed'`</sub>
 
+Use the [groups](#groups) option with the `value` and `type` modifiers instead.
+
 Allows you to group named exports by their kind, determining whether value exports should come before or after type exports.
 
 - `mixed` — Do not group named exports by their kind; export statements are sorted together regardless of their type.
@@ -240,6 +242,138 @@ export {
 
 Each group of members (separated by empty lines) is treated independently, and the order within each group is preserved.
 
+### newlinesBetween
+
+<sub>default: `'ignore'`</sub>
+
+Specifies how new lines should be handled 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` is `false`.
+
+### groups
+
+<sub>
+  type: `Array<string | string[]>`
+</sub>
+<sub>default: `[]`</sub>
+
+Allows you to specify a list of named exports groups for sorting. Groups help organize named exports into categories, making them more readable and maintainable.
+
+Each named export 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 single selector and potentially multiple modifiers. You may enter modifiers in any order, but the selector must always come at the end.
+
+#### Selectors
+
+The only selector possible for this rule is `export`.
+
+#### Modifiers
+
+- `value`: Matches value exports.
+- `type`: Matches type exports.
+
+Example: `type-export`.
+
+#### Important notes
+
+##### The `unknown` group
+
+Members that don’t fit into any group specified in the `groups` option will be placed in the `unknown` group. If the `unknown` group is not specified in the `groups` option,
+it will automatically be added to the end of the list.
+
+##### 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` 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>
+
+You can define your own groups and use regexp patterns to match specific named exports.
+
+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 named export 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 named export 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` 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 sort type for that custom group. `unsorted` will not sort the group.
+- `order`: Overrides the sort order for that custom group
+- `fallbackSort`: Overrides the 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
@@ -267,6 +401,9 @@ Each group of members (separated by empty lines) is treated independently, and t
                   groupKind: 'mixed',
                   partitionByNewLine: false,
                   partitionByComment: false,
+                  newlinesBetween: 'ignore',
+                  groups: [],
+                  customGroups: [],
                 },
               ],
             },
@@ -296,6 +433,9 @@ Each group of members (separated by empty lines) is treated independently, and t
                 groupKind: 'mixed',
                 partitionByNewLine: false,
                 partitionByComment: false,
+                newlinesBetween: 'ignore',
+                groups: [],
+                customGroups: [],
               },
             ],
           },
diff --git a/docs/content/rules/sort-named-imports.mdx b/docs/content/rules/sort-named-imports.mdx
index 51ee7df..8130e41 100644
--- a/docs/content/rules/sort-named-imports.mdx
+++ b/docs/content/rules/sort-named-imports.mdx
@@ -108,7 +108,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 the length of the code line (shorter lines first).
 - `'custom'` — Sort items using the alphabet entered 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
 
@@ -191,13 +191,15 @@ Determines whether to use the import alias as the name for sorting instead of th
 - `true` — Use the import alias for sorting.
 - `false` — Use the exported name for sorting.
 
-### groupKind
+### [DEPRECATED] groupKind
 
 <sub>default: `'mixed'`</sub>
 
+Use the [groups](#groups) option with the `value` and `type` modifiers instead.
+
 Allows you to group named imports by their kind, determining whether value imports should come before or after type imports.
 
-- `mixed` — Do not group named imports by their kind; export statements are sorted together regardless of their type.
+- `mixed` — Do not group named imports by their kind; named imports statements are sorted together regardless of their type.
 - `values-first` — Group all value imports before type imports.
 - `types-first` — Group all type imports before value imports.
 
@@ -239,6 +241,137 @@ import {
 } from './devices'
 ```
 
+### newlinesBetween
+
+<sub>default: `'ignore'`</sub>
+
+Specifies how new lines should be handled 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` is `false`.
+
+### groups
+
+<sub>
+  type: `Array<string | string[]>`
+</sub>
+<sub>default: `[]`</sub>
+
+Allows you to specify a list of named imports groups for sorting. Groups help organize named imports into categories, making them more readable and maintainable.
+
+Each named import 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 single selector and potentially multiple modifiers. You may enter modifiers in any order, but the selector must always come at the end.
+
+#### Selectors
+
+The only selector possible for this rule is `import`.
+
+#### Modifiers
+
+- `value`: Matches value imports.
+- `type`: Matches type imports.
+
+Example: `type-import`.
+
+#### Important notes
+
+##### The `unknown` group
+
+Members that don’t fit into any group specified in the `groups` option will be placed in the `unknown` group. If the `unknown` group is not specified in the `groups` option,
+it will automatically be added to the end of the list.
+
+##### 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` 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>
+
+You can define your own groups and use regexp patterns to match specific named imports.
+
+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 named import 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 named import 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` 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 sort type for that custom group. `unsorted` will not sort the group.
+- `order`: Overrides the sort order for that custom group
+- `fallbackSort`: Overrides the 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
 
@@ -267,6 +400,9 @@ import {
                   groupKind: 'mixed',
                   partitionByNewLine: false,
                   partitionByComment: false,
+                  newlinesBetween: 'ignore',
+                  groups: [],
+                  customGroups: [],
                 },
               ],
             },
@@ -296,6 +432,9 @@ import {
                 groupKind: 'mixed',
                 partitionByNewLine: false,
                 partitionByComment: false,
+                newlinesBetween: 'ignore',
+                groups: [],
+                customGroups: [],
               },
             ],
           },
diff --git a/docs/content/rules/sort-sets.mdx b/docs/content/rules/sort-sets.mdx
index 283c25a..a5f04aa 100644
--- a/docs/content/rules/sort-sets.mdx
+++ b/docs/content/rules/sort-sets.mdx
@@ -325,7 +325,7 @@ This feature is only applicable when `partitionByNewLine` is false.
 <sub>
   type: `Array<CustomGroupDefinition | CustomGroupAnyOfDefinition>`
 </sub>
-<sub>default: `{}`</sub>
+<sub>default: `[]`</sub>
 
 You can define your own groups and use regexp patterns to match specific elements.
 
diff --git a/rules/sort-imports.ts b/rules/sort-imports.ts
index e1a2d0a..41945db 100644
--- a/rules/sort-imports.ts
+++ b/rules/sort-imports.ts
@@ -1,7 +1,12 @@
 import type { TSESTree } from '@typescript-eslint/types'
 import type { TSESLint } from '@typescript-eslint/utils'
 
-import type { SortImportsSortingNode, Options } from './sort-imports/types'
+import type {
+  SortImportsSortingNode,
+  Options,
+  Group,
+} from './sort-imports/types'
+import type { DeprecatedCustomGroupsOption } from '../types/common-options'
 
 import {
   partitionByCommentJsonSchema,
@@ -31,10 +36,10 @@ 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 { useGroups } from '../utils/use-groups'
 import { complete } from '../utils/complete'
 
 export type MESSAGE_ID =
@@ -133,8 +138,6 @@ export default createEslintRule<Options, MESSAGE_ID>({
         | TSESTree.VariableDeclaration
         | TSESTree.ImportDeclaration,
     ): void => {
-      let { setCustomGroups, defineGroup, getGroup } = useGroups(options)
-
       let name = getNodeName({
         sourceCode,
         node,
@@ -147,16 +150,30 @@ export default createEslintRule<Options, MESSAGE_ID>({
         name,
       })
 
+      let predefinedGroups: Group[] = []
+      let group: Group | null = null
+
       if (node.type !== 'VariableDeclaration' && node.importKind === 'type') {
         if (node.type === 'ImportDeclaration') {
-          setCustomGroups(options.customGroups.type, name)
+          group = computeGroupExceptUnknown({
+            customGroups: options.customGroups.type,
+            predefinedGroups: [],
+            options,
+            name,
+          })
 
-          for (let group of commonPredefinedGroups) {
-            defineGroup(`${group}-type`)
+          for (let predefinedGroup of commonPredefinedGroups) {
+            predefinedGroups.push(`${predefinedGroup}-type`)
           }
         }
 
-        defineGroup('type')
+        predefinedGroups.push('type')
+
+        group ??= computeGroupExceptUnknown({
+          predefinedGroups,
+          options,
+          name,
+        })
       }
 
       let isSideEffect = isSideEffectImport({ sourceCode, node })
@@ -168,25 +185,37 @@ export default createEslintRule<Options, MESSAGE_ID>({
         let isStyleValue = isStyle(name)
         isStyleSideEffect = isSideEffect && isStyleValue
 
-        setCustomGroups(options.customGroups.value, name)
+        group ??= computeGroupExceptUnknown({
+          customGroups: options.customGroups.value,
+          predefinedGroups: [],
+          options,
+          name,
+        })
 
         if (isStyleSideEffect) {
-          defineGroup('side-effect-style')
+          predefinedGroups.push('side-effect-style')
         }
 
         if (isSideEffect) {
-          defineGroup('side-effect')
+          predefinedGroups.push('side-effect')
         }
 
         if (isStyleValue) {
-          defineGroup('style')
+          predefinedGroups.push('style')
         }
 
-        for (let group of commonPredefinedGroups) {
-          defineGroup(group)
+        for (let predefinedGroup of commonPredefinedGroups) {
+          predefinedGroups.push(predefinedGroup)
         }
       }
 
+      group ??=
+        computeGroupExceptUnknown({
+          predefinedGroups,
+          options,
+          name,
+        }) ?? 'unknown'
+
       sortingNodes.push({
         isIgnored:
           !options.sortSideEffects &&
@@ -196,7 +225,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
         isEslintDisabled: isNodeEslintDisabled(node, eslintDisabledLines),
         size: rangeToDiff(node, sourceCode),
         addSafetySemicolonWhenInline: true,
-        group: getGroup(),
+        group,
         name,
         node,
         ...(options.type === 'line-length' &&
@@ -480,3 +509,31 @@ let getNodeName = ({
   let { value } = callExpression.arguments[0] as TSESTree.Literal
   return value!.toString()
 }
+
+let computeGroupExceptUnknown = ({
+  predefinedGroups,
+  customGroups,
+  options,
+  name,
+}: {
+  options: Omit<
+    Required<Options[0]>,
+    'tsconfigRootDir' | 'maxLineLength' | 'customGroups'
+  >
+  customGroups?: DeprecatedCustomGroupsOption | undefined
+  predefinedGroups: Group[]
+  name: string
+}): Group | null => {
+  let computedCustomGroup = computeGroup({
+    options: {
+      ...options,
+      customGroups,
+    },
+    predefinedGroups,
+    name,
+  })
+  if (computedCustomGroup === 'unknown') {
+    return null
+  }
+  return computedCustomGroup
+}
diff --git a/rules/sort-jsx-props.ts b/rules/sort-jsx-props.ts
index c116213..68e410f 100644
--- a/rules/sort-jsx-props.ts
+++ b/rules/sort-jsx-props.ts
@@ -25,10 +25,10 @@ import { buildGetCustomGroupOverriddenOptionsFunction } from '../utils/get-custo
 import { validateGeneratedGroupsConfiguration } from '../utils/validate-generated-groups-configuration'
 import { validateCustomSortConfiguration } from '../utils/validate-custom-sort-configuration'
 import { getMatchingContextOptions } from '../utils/get-matching-context-options'
-import { doesCustomGroupMatch } from './sort-jsx-props/does-custom-group-match'
 import { generatePredefinedGroups } from '../utils/generate-predefined-groups'
 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 { singleCustomGroupJsonSchema } from './sort-jsx-props/types'
 import { allModifiers, allSelectors } from './sort-jsx-props/types'
 import { sortNodesByGroups } from '../utils/sort-nodes-by-groups'
diff --git a/rules/sort-jsx-props/does-custom-group-match.ts b/rules/sort-jsx-props/does-custom-group-match.ts
deleted file mode 100644
index f5d9d54..0000000
--- a/rules/sort-jsx-props/does-custom-group-match.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import type { SingleCustomGroup, Selector, Modifier } from './types'
-import type { AnyOfCustomGroup } from '../../types/common-options'
-
-import { matches } from '../../utils/matches'
-
-interface DoesCustomGroupMatchParameters {
-  customGroup: AnyOfCustomGroup<SingleCustomGroup> | SingleCustomGroup
-  elementValue: string | null
-  selectors: Selector[]
-  modifiers: Modifier[]
-  elementName: string
-}
-
-/**
- * Determines whether a custom group matches the given properties.
- * @param {DoesCustomGroupMatchParameters} props - The properties to compare
- * with the custom group, including selectors, modifiers, and element name.
- * @returns {boolean} `true` if the custom group matches the properties;
- * otherwise, `false`.
- */
-export let doesCustomGroupMatch = (
-  props: DoesCustomGroupMatchParameters,
-): boolean => {
-  if ('anyOf' in props.customGroup) {
-    // At least one subgroup must match
-    return props.customGroup.anyOf.some(subgroup =>
-      doesCustomGroupMatch({ ...props, customGroup: subgroup }),
-    )
-  }
-  if (
-    props.customGroup.selector &&
-    !props.selectors.includes(props.customGroup.selector)
-  ) {
-    return false
-  }
-
-  if (props.customGroup.modifiers) {
-    for (let modifier of props.customGroup.modifiers) {
-      if (!props.modifiers.includes(modifier)) {
-        return false
-      }
-    }
-  }
-
-  if (
-    'elementNamePattern' in props.customGroup &&
-    props.customGroup.elementNamePattern
-  ) {
-    let matchesElementNamePattern: boolean = matches(
-      props.elementName,
-      props.customGroup.elementNamePattern,
-    )
-    if (!matchesElementNamePattern) {
-      return false
-    }
-  }
-
-  if (
-    'elementValuePattern' in props.customGroup &&
-    props.customGroup.elementValuePattern
-  ) {
-    let matchesElementNamePattern: boolean = matches(
-      props.elementValue ?? '',
-      props.customGroup.elementValuePattern,
-    )
-    if (!matchesElementNamePattern) {
-      return false
-    }
-  }
-
-  return true
-}
diff --git a/rules/sort-named-exports.ts b/rules/sort-named-exports.ts
index 3a891a4..4ac8594 100644
--- a/rules/sort-named-exports.ts
+++ b/rules/sort-named-exports.ts
@@ -1,59 +1,73 @@
-import type { TSESTree } from '@typescript-eslint/types'
-
 import type {
-  PartitionByCommentOption,
-  CommonOptions,
-} from '../types/common-options'
-import type { SortingNode } from '../types/sorting-node'
+  SortNamedExportsSortingNode,
+  Modifier,
+  Selector,
+  Options,
+} from './sort-named-exports/types'
 
 import {
+  buildCustomGroupsArrayJsonSchema,
   partitionByCommentJsonSchema,
   partitionByNewLineJsonSchema,
+  newlinesBetweenJsonSchema,
   commonJsonSchemas,
+  groupsJsonSchema,
 } from '../utils/common-json-schemas'
+import {
+  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 {
+  singleCustomGroupJsonSchema,
+  allModifiers,
+  allSelectors,
+} from './sort-named-exports/types'
+import { buildGetCustomGroupOverriddenOptionsFunction } from '../utils/get-custom-groups-compare-options'
+import { validateGeneratedGroupsConfiguration } from '../utils/validate-generated-groups-configuration'
 import { validateCustomSortConfiguration } from '../utils/validate-custom-sort-configuration'
+import { generatePredefinedGroups } from '../utils/generate-predefined-groups'
 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 { ORDER_ERROR } from '../utils/report-errors'
 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<
-    {
-      groupKind: 'values-first' | 'types-first' | 'mixed'
-      partitionByComment: PartitionByCommentOption
-      partitionByNewLine: boolean
-      ignoreAlias: boolean
-    } & CommonOptions
-  >,
-]
-
-interface SortNamedExportsSortingNode
-  extends SortingNode<TSESTree.ExportSpecifier> {
-  groupKind: 'value' | 'type'
-}
+type MESSAGE_ID =
+  | 'unexpectedNamedExportsGroupOrder'
+  | 'missedSpacingBetweenNamedExports'
+  | 'extraSpacingBetweenNamedExports'
+  | 'unexpectedNamedExportsOrder'
 
-type MESSAGE_ID = 'unexpectedNamedExportsOrder'
+/**
+ * Cache computed groups by modifiers and selectors for performance
+ */
+let cachedGroupsByModifiersAndSelectors = new Map<string, string[]>()
 
 let defaultOptions: Required<Options[0]> = {
   fallbackSort: { type: 'unsorted' },
   specialCharacters: 'keep',
   partitionByNewLine: false,
   partitionByComment: false,
+  newlinesBetween: 'ignore',
   type: 'alphabetical',
   ignoreAlias: false,
   groupKind: 'mixed',
+  customGroups: [],
   ignoreCase: true,
   locales: 'en-US',
   alphabet: '',
   order: 'asc',
+  groups: [],
 }
 
 export default createEslintRule<Options, MESSAGE_ID>({
@@ -66,6 +80,12 @@ export default createEslintRule<Options, MESSAGE_ID>({
       let settings = getSettings(context.settings)
       let options = complete(context.options.at(0), settings, defaultOptions)
       validateCustomSortConfiguration(options)
+      validateGeneratedGroupsConfiguration({
+        modifiers: allModifiers,
+        selectors: allSelectors,
+        options,
+      })
+      validateNewlinesAndPartitionConfiguration(options)
 
       let { sourceCode, id } = context
       let eslintDisabledLines = getEslintDisabledLines({
@@ -94,7 +114,31 @@ export default createEslintRule<Options, MESSAGE_ID>({
           }
         }
 
-        let lastSortingNode = formattedMembers.at(-1)?.at(-1)
+        let selector: Selector = 'export'
+        let modifiers: Modifier[] = []
+        if (specifier.exportKind === 'value') {
+          modifiers.push('value')
+        } else {
+          modifiers.push('type')
+        }
+
+        let predefinedGroups = generatePredefinedGroups({
+          cache: cachedGroupsByModifiersAndSelectors,
+          selectors: [selector],
+          modifiers,
+        })
+        let group = computeGroup({
+          customGroupMatcher: customGroup =>
+            doesCustomGroupMatch({
+              selectors: [selector],
+              elementName: name,
+              customGroup,
+              modifiers,
+            }),
+          predefinedGroups,
+          options,
+        })
+
         let sortingNode: SortNamedExportsSortingNode = {
           isEslintDisabled: isNodeEslintDisabled(
             specifier,
@@ -103,9 +147,11 @@ export default createEslintRule<Options, MESSAGE_ID>({
           groupKind: specifier.exportKind === 'type' ? 'type' : 'value',
           size: rangeToDiff(specifier, sourceCode),
           node: specifier,
+          group,
           name,
         }
 
+        let lastSortingNode = formattedMembers.at(-1)?.at(-1)
         if (
           shouldPartition({
             lastSortingNode,
@@ -140,15 +186,20 @@ export default createEslintRule<Options, MESSAGE_ID>({
           ignoreEslintDisabledNodes: boolean,
         ): SortNamedExportsSortingNode[] =>
           filteredGroupKindNodes.flatMap(groupedNodes =>
-            sortNodes({
+            sortNodesByGroups({
+              getOptionsByGroupNumber:
+                buildGetCustomGroupOverriddenOptionsFunction(options),
               ignoreEslintDisabledNodes,
+              groups: options.groups,
               nodes: groupedNodes,
-              options,
             }),
           )
 
         reportAllErrors<MESSAGE_ID>({
           availableMessageIds: {
+            missedSpacingBetweenMembers: 'missedSpacingBetweenNamedExports',
+            extraSpacingBetweenMembers: 'extraSpacingBetweenNamedExports',
+            unexpectedGroupOrder: 'unexpectedNamedExportsGroupOrder',
             unexpectedOrder: 'unexpectedNamedExportsOrder',
           },
           sortNodesExcludingEslintDisabled,
@@ -161,34 +212,44 @@ export default createEslintRule<Options, MESSAGE_ID>({
     },
   }),
   meta: {
-    schema: [
-      {
+    schema: {
+      items: {
         properties: {
           ...commonJsonSchemas,
           groupKind: {
+            description: '[DEPRECATED] Specifies top-level groups.',
             enum: ['mixed', 'values-first', 'types-first'],
-            description: 'Specifies top-level groups.',
             type: 'string',
           },
           ignoreAlias: {
             description: 'Controls whether to ignore alias names.',
             type: 'boolean',
           },
+          customGroups: buildCustomGroupsArrayJsonSchema({
+            singleCustomGroupJsonSchema,
+          }),
           partitionByComment: partitionByCommentJsonSchema,
           partitionByNewLine: partitionByNewLineJsonSchema,
+          newlinesBetween: newlinesBetweenJsonSchema,
+          groups: groupsJsonSchema,
         },
         additionalProperties: false,
         type: 'object',
       },
-    ],
+      uniqueItems: true,
+      type: 'array',
+    },
+    messages: {
+      missedSpacingBetweenNamedExports: MISSED_SPACING_ERROR,
+      extraSpacingBetweenNamedExports: EXTRA_SPACING_ERROR,
+      unexpectedNamedExportsGroupOrder: GROUP_ORDER_ERROR,
+      unexpectedNamedExportsOrder: ORDER_ERROR,
+    },
     docs: {
       url: 'https://perfectionist.dev/rules/sort-named-exports',
       description: 'Enforce sorted named exports.',
       recommended: true,
     },
-    messages: {
-      unexpectedNamedExportsOrder: ORDER_ERROR,
-    },
     type: 'suggestion',
     fixable: 'code',
   },
diff --git a/rules/sort-named-exports/types.ts b/rules/sort-named-exports/types.ts
new file mode 100644
index 0000000..dddb6ca
--- /dev/null
+++ b/rules/sort-named-exports/types.ts
@@ -0,0 +1,69 @@
+import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema'
+import type { TSESTree } from '@typescript-eslint/types'
+
+import type {
+  PartitionByCommentOption,
+  NewlinesBetweenOption,
+  CustomGroupsOption,
+  CommonOptions,
+  GroupsOptions,
+  RegexOption,
+} from '../../types/common-options'
+import type { JoinWithDash } from '../../types/join-with-dash'
+import type { SortingNode } from '../../types/sorting-node'
+
+import {
+  buildCustomGroupModifiersJsonSchema,
+  buildCustomGroupSelectorJsonSchema,
+  regexJsonSchema,
+} from '../../utils/common-json-schemas'
+
+export type Options = Partial<
+  {
+    /**
+     * @deprecated for {@link `groups`}
+     */
+    groupKind: 'values-first' | 'types-first' | 'mixed'
+    customGroups: CustomGroupsOption<SingleCustomGroup>
+    partitionByComment: PartitionByCommentOption
+    newlinesBetween: NewlinesBetweenOption
+    groups: GroupsOptions<Group>
+    partitionByNewLine: boolean
+    ignoreAlias: boolean
+  } & CommonOptions
+>[]
+
+export interface SortNamedExportsSortingNode
+  extends SortingNode<TSESTree.ExportSpecifier> {
+  groupKind: 'value' | 'type'
+}
+
+export type SingleCustomGroup = {
+  modifiers?: Modifier[]
+  selector?: Selector
+} & {
+  elementNamePattern?: RegexOption
+}
+
+export type Modifier = ValueModifier | TypeModifier
+
+export type Selector = ExportSelector
+
+type ExportGroup = JoinWithDash<[ValueModifier, TypeModifier, ExportSelector]>
+
+type Group = ExportGroup | 'unknown' | string
+
+type ExportSelector = 'export'
+
+type ValueModifier = 'value'
+
+type TypeModifier = 'type'
+
+export let allSelectors: Selector[] = ['export']
+export let allModifiers: Modifier[] = ['value', 'type']
+
+export let singleCustomGroupJsonSchema: Record<string, JSONSchema4> = {
+  modifiers: buildCustomGroupModifiersJsonSchema(allModifiers),
+  selector: buildCustomGroupSelectorJsonSchema(allSelectors),
+  elementNamePattern: regexJsonSchema,
+}
diff --git a/rules/sort-named-imports.ts b/rules/sort-named-imports.ts
index 45e7cf6..e9f85b8 100644
--- a/rules/sort-named-imports.ts
+++ b/rules/sort-named-imports.ts
@@ -1,59 +1,73 @@
-import type { TSESTree } from '@typescript-eslint/types'
-
 import type {
-  PartitionByCommentOption,
-  CommonOptions,
-} from '../types/common-options'
-import type { SortingNode } from '../types/sorting-node'
+  SortNamedImportsSortingNode,
+  Modifier,
+  Selector,
+  Options,
+} from './sort-named-imports/types'
 
 import {
+  buildCustomGroupsArrayJsonSchema,
   partitionByCommentJsonSchema,
   partitionByNewLineJsonSchema,
+  newlinesBetweenJsonSchema,
   commonJsonSchemas,
+  groupsJsonSchema,
 } from '../utils/common-json-schemas'
+import {
+  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 {
+  singleCustomGroupJsonSchema,
+  allModifiers,
+  allSelectors,
+} from './sort-named-imports/types'
+import { buildGetCustomGroupOverriddenOptionsFunction } from '../utils/get-custom-groups-compare-options'
+import { validateGeneratedGroupsConfiguration } from '../utils/validate-generated-groups-configuration'
 import { validateCustomSortConfiguration } from '../utils/validate-custom-sort-configuration'
+import { generatePredefinedGroups } from '../utils/generate-predefined-groups'
 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 { ORDER_ERROR } from '../utils/report-errors'
 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<
-    {
-      groupKind: 'values-first' | 'types-first' | 'mixed'
-      partitionByComment: PartitionByCommentOption
-      partitionByNewLine: boolean
-      ignoreAlias: boolean
-    } & CommonOptions
-  >,
-]
-
-interface SortNamedImportsSortingNode
-  extends SortingNode<TSESTree.ImportClause> {
-  groupKind: 'value' | 'type'
-}
+type MESSAGE_ID =
+  | 'unexpectedNamedImportsGroupOrder'
+  | 'missedSpacingBetweenNamedImports'
+  | 'extraSpacingBetweenNamedImports'
+  | 'unexpectedNamedImportsOrder'
 
-type MESSAGE_ID = 'unexpectedNamedImportsOrder'
+/**
+ * Cache computed groups by modifiers and selectors for performance
+ */
+let cachedGroupsByModifiersAndSelectors = new Map<string, string[]>()
 
 let defaultOptions: Required<Options[0]> = {
   fallbackSort: { type: 'unsorted' },
   specialCharacters: 'keep',
   partitionByNewLine: false,
   partitionByComment: false,
+  newlinesBetween: 'ignore',
   type: 'alphabetical',
   ignoreAlias: false,
   groupKind: 'mixed',
+  customGroups: [],
   ignoreCase: true,
   locales: 'en-US',
   alphabet: '',
   order: 'asc',
+  groups: [],
 }
 
 export default createEslintRule<Options, MESSAGE_ID>({
@@ -69,6 +83,12 @@ export default createEslintRule<Options, MESSAGE_ID>({
       let settings = getSettings(context.settings)
       let options = complete(context.options.at(0), settings, defaultOptions)
       validateCustomSortConfiguration(options)
+      validateGeneratedGroupsConfiguration({
+        modifiers: allModifiers,
+        selectors: allSelectors,
+        options,
+      })
+      validateNewlinesAndPartitionConfiguration(options)
 
       let { sourceCode, id } = context
       let eslintDisabledLines = getEslintDisabledLines({
@@ -88,7 +108,34 @@ export default createEslintRule<Options, MESSAGE_ID>({
           }
         }
 
-        let lastSortingNode = formattedMembers.at(-1)?.at(-1)
+        let selector: Selector = 'import'
+        let modifiers: Modifier[] = []
+        if (
+          specifier.type === 'ImportSpecifier' &&
+          specifier.importKind === 'type'
+        ) {
+          modifiers.push('type')
+        } else {
+          modifiers.push('value')
+        }
+
+        let predefinedGroups = generatePredefinedGroups({
+          cache: cachedGroupsByModifiersAndSelectors,
+          selectors: [selector],
+          modifiers,
+        })
+        let group = computeGroup({
+          customGroupMatcher: customGroup =>
+            doesCustomGroupMatch({
+              selectors: [selector],
+              elementName: name,
+              customGroup,
+              modifiers,
+            }),
+          predefinedGroups,
+          options,
+        })
+
         let sortingNode: SortNamedImportsSortingNode = {
           groupKind:
             specifier.type === 'ImportSpecifier' &&
@@ -101,9 +148,11 @@ export default createEslintRule<Options, MESSAGE_ID>({
           ),
           size: rangeToDiff(specifier, sourceCode),
           node: specifier,
+          group,
           name,
         }
 
+        let lastSortingNode = formattedMembers.at(-1)?.at(-1)
         if (
           shouldPartition({
             lastSortingNode,
@@ -138,15 +187,20 @@ export default createEslintRule<Options, MESSAGE_ID>({
           ignoreEslintDisabledNodes: boolean,
         ): SortNamedImportsSortingNode[] =>
           filteredGroupKindNodes.flatMap(groupedNodes =>
-            sortNodes({
+            sortNodesByGroups({
+              getOptionsByGroupNumber:
+                buildGetCustomGroupOverriddenOptionsFunction(options),
               ignoreEslintDisabledNodes,
+              groups: options.groups,
               nodes: groupedNodes,
-              options,
             }),
           )
 
         reportAllErrors<MESSAGE_ID>({
           availableMessageIds: {
+            missedSpacingBetweenMembers: 'missedSpacingBetweenNamedImports',
+            extraSpacingBetweenMembers: 'extraSpacingBetweenNamedImports',
+            unexpectedGroupOrder: 'unexpectedNamedImportsGroupOrder',
             unexpectedOrder: 'unexpectedNamedImportsOrder',
           },
           sortNodesExcludingEslintDisabled,
@@ -159,34 +213,44 @@ export default createEslintRule<Options, MESSAGE_ID>({
     },
   }),
   meta: {
-    schema: [
-      {
+    schema: {
+      items: {
         properties: {
           ...commonJsonSchemas,
           groupKind: {
+            description: '[DEPRECATED] Specifies top-level groups.',
             enum: ['mixed', 'values-first', 'types-first'],
-            description: 'Specifies top-level groups.',
             type: 'string',
           },
           ignoreAlias: {
             description: 'Controls whether to ignore alias names.',
             type: 'boolean',
           },
+          customGroups: buildCustomGroupsArrayJsonSchema({
+            singleCustomGroupJsonSchema,
+          }),
           partitionByComment: partitionByCommentJsonSchema,
           partitionByNewLine: partitionByNewLineJsonSchema,
+          newlinesBetween: newlinesBetweenJsonSchema,
+          groups: groupsJsonSchema,
         },
         additionalProperties: false,
         type: 'object',
       },
-    ],
+      uniqueItems: true,
+      type: 'array',
+    },
+    messages: {
+      missedSpacingBetweenNamedImports: MISSED_SPACING_ERROR,
+      extraSpacingBetweenNamedImports: EXTRA_SPACING_ERROR,
+      unexpectedNamedImportsGroupOrder: GROUP_ORDER_ERROR,
+      unexpectedNamedImportsOrder: ORDER_ERROR,
+    },
     docs: {
       url: 'https://perfectionist.dev/rules/sort-named-imports',
       description: 'Enforce sorted named imports.',
       recommended: true,
     },
-    messages: {
-      unexpectedNamedImportsOrder: ORDER_ERROR,
-    },
     type: 'suggestion',
     fixable: 'code',
   },
diff --git a/rules/sort-named-imports/types.ts b/rules/sort-named-imports/types.ts
new file mode 100644
index 0000000..65642e9
--- /dev/null
+++ b/rules/sort-named-imports/types.ts
@@ -0,0 +1,68 @@
+import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema'
+import type { TSESTree } from '@typescript-eslint/types'
+
+import type {
+  PartitionByCommentOption,
+  NewlinesBetweenOption,
+  CustomGroupsOption,
+  CommonOptions,
+  GroupsOptions,
+  RegexOption,
+} from '../../types/common-options'
+import type { JoinWithDash } from '../../types/join-with-dash'
+import type { SortingNode } from '../../types/sorting-node'
+
+import {
+  buildCustomGroupModifiersJsonSchema,
+  buildCustomGroupSelectorJsonSchema,
+  regexJsonSchema,
+} from '../../utils/common-json-schemas'
+
+export type Options = Partial<
+  {
+    /**
+     * @deprecated for {@link `groups`}
+     */
+    groupKind: 'values-first' | 'types-first' | 'mixed'
+    customGroups: CustomGroupsOption<SingleCustomGroup>
+    partitionByComment: PartitionByCommentOption
+    newlinesBetween: NewlinesBetweenOption
+    groups: GroupsOptions<Group>
+    partitionByNewLine: boolean
+    ignoreAlias: boolean
+  } & CommonOptions
+>[]
+
+export interface SortNamedImportsSortingNode
+  extends SortingNode<TSESTree.ImportClause> {
+  groupKind: 'value' | 'type'
+}
+
+export type SingleCustomGroup = {
+  modifiers?: Modifier[]
+  selector?: Selector
+} & {
+  elementNamePattern?: RegexOption
+}
+export type Modifier = ValueModifier | TypeModifier
+
+export type Selector = ImportSelector
+
+type ImportGroup = JoinWithDash<[ValueModifier, TypeModifier, ImportSelector]>
+
+type Group = ImportGroup | 'unknown' | string
+
+type ImportSelector = 'import'
+
+type ValueModifier = 'value'
+
+type TypeModifier = 'type'
+
+export let allSelectors: Selector[] = ['import']
+export let allModifiers: Modifier[] = ['value', 'type']
+
+export let singleCustomGroupJsonSchema: Record<string, JSONSchema4> = {
+  modifiers: buildCustomGroupModifiersJsonSchema(allModifiers),
+  selector: buildCustomGroupSelectorJsonSchema(allSelectors),
+  elementNamePattern: regexJsonSchema,
+}
diff --git a/utils/compute-group.ts b/utils/compute-group.ts
index 265d573..6843c62 100644
--- a/utils/compute-group.ts
+++ b/utils/compute-group.ts
@@ -27,7 +27,7 @@ export let computeGroup = <SingleCustomGroup>({
   options,
   name,
 }: GetGroupParameters<SingleCustomGroup>): string => {
-  let group: undefined | string
+  let group: undefined | 'unknown' | string
   // For lookup performance.
   let groupsSet = new Set(options.groups.flat())
 
diff --git a/utils/use-groups.ts b/utils/use-groups.ts
deleted file mode 100644
index 11d9b45..0000000
--- a/utils/use-groups.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import type {
-  DeprecatedCustomGroupsOption,
-  NewlinesBetweenOption,
-} from '../types/common-options'
-
-import { matches } from './matches'
-
-interface UseGroupsValue {
-  setCustomGroups(
-    customGroups: DeprecatedCustomGroupsOption | undefined,
-    name: string,
-  ): void
-  defineGroup(value: string): void
-  getGroup(): string
-}
-
-interface UseGroupParameters {
-  groups: ({ newlinesBetween: NewlinesBetweenOption } | string[] | string)[]
-}
-
-/**
- * Functions to set and define groups
- * @deprecated for computeGroups function
- * @param {object} params - The parameters for the operation
- * @param {string[]} params.groups - groups array
- * @returns {UseGroupsValue} functions to set and define groups
- */
-export let useGroups = ({ groups }: UseGroupParameters): UseGroupsValue => {
-  let group: undefined | string
-  // For lookup performance.
-  let groupsSet = new Set(groups.flat())
-
-  let defineGroup = (value: string): void => {
-    if (!group && groupsSet.has(value)) {
-      group = value
-    }
-  }
-
-  let setCustomGroups = (
-    customGroups: DeprecatedCustomGroupsOption | undefined,
-    name: string,
-  ): void => {
-    if (!customGroups) {
-      return
-    }
-
-    for (let [key, pattern] of Object.entries(customGroups)) {
-      if (matches(name, pattern)) {
-        defineGroup(key)
-      }
-    }
-  }
-
-  return {
-    getGroup: () => group ?? 'unknown',
-    setCustomGroups,
-    defineGroup,
-  }
-}


Test patch:
diff --git a/test/rules/sort-classes.test.ts b/test/rules/sort-classes.test.ts
index 24503f1..b1d0da8 100644
--- a/test/rules/sort-classes.test.ts
+++ b/test/rules/sort-classes.test.ts
@@ -2791,11 +2791,7 @@ describe(ruleName, () => {
                   b = () => null
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
           ],
           invalid: [],
@@ -3117,11 +3113,7 @@ describe(ruleName, () => {
                   c = 10
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
           ],
           valid: [],
@@ -3295,11 +3287,7 @@ describe(ruleName, () => {
                    static a = Class.b.bMethod().anotherNestedMethod(this.c).finalMethod()
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
           ],
           invalid: [],
@@ -3745,11 +3733,7 @@ describe(ruleName, () => {
                static #a = this.#b
               }
             `,
-            options: [
-              {
-                ...options,
-              },
-            ],
+            options: [options],
           },
           {
             code: dedent`
@@ -3758,11 +3742,7 @@ describe(ruleName, () => {
                static #a = this.#b()
               }
             `,
-            options: [
-              {
-                ...options,
-              },
-            ],
+            options: [options],
           },
           {
             code: dedent`
@@ -7010,11 +6990,7 @@ describe(ruleName, () => {
                 messageId: 'unexpectedClassesGroupOrder',
               },
             ],
-            options: [
-              {
-                ...options,
-              },
-            ],
+            options: [options],
           },
         ],
         valid: [
@@ -8716,11 +8692,7 @@ describe(ruleName, () => {
               b
             }
           `,
-          options: [
-            {
-              ...options,
-            },
-          ],
+          options: [options],
         },
       ],
       valid: [],
diff --git a/test/rules/sort-enums.test.ts b/test/rules/sort-enums.test.ts
index a6d629f..e4104f2 100644
--- a/test/rules/sort-enums.test.ts
+++ b/test/rules/sort-enums.test.ts
@@ -3005,11 +3005,7 @@ describe(ruleName, () => {
               b = 1,
             }
           `,
-          options: [
-            {
-              ...options,
-            },
-          ],
+          options: [options],
         },
       ],
       valid: [],
diff --git a/test/rules/sort-imports.test.ts b/test/rules/sort-imports.test.ts
index 965a060..e463f64 100644
--- a/test/rules/sort-imports.test.ts
+++ b/test/rules/sort-imports.test.ts
@@ -201,23 +201,7 @@ describe(ruleName, () => {
             import { j } from '../j'
             import { K, L, M } from '../k'
           `,
-          options: [
-            {
-              ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-              internalPattern: ['^~/.*'],
-              newlinesBetween: 'always',
-            },
-          ],
+          options: [options],
         },
       ],
       valid: [
@@ -248,23 +232,7 @@ describe(ruleName, () => {
             import { K, L, M } from '../k'
             import './style.css'
           `,
-          options: [
-            {
-              ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-              internalPattern: ['^~/.*'],
-              newlinesBetween: 'always',
-            },
-          ],
+          options: [options],
         },
       ],
     })
@@ -301,23 +269,6 @@ describe(ruleName, () => {
               messageId: 'unexpectedImportsGroupOrder',
             },
           ],
-          options: [
-            {
-              ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-              internalPattern: ['^~/.*'],
-              newlinesBetween: 'never',
-            },
-          ],
           code: dedent`
             import d from '.'
             import { a1, a2, a3 } from 'a'
@@ -336,27 +287,16 @@ describe(ruleName, () => {
             import d from '.'
             import { e1, e2, e3 } from '../../e'
           `,
-        },
-      ],
-      valid: [
-        {
           options: [
             {
               ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-              internalPattern: ['^~/.*'],
               newlinesBetween: 'never',
             },
           ],
+        },
+      ],
+      valid: [
+        {
           code: dedent`
             import type { T } from 't'
             import { a1, a2, a3 } from 'a'
@@ -365,6 +305,12 @@ describe(ruleName, () => {
             import d from '.'
             import { e1, e2, e3 } from '../../e'
           `,
+          options: [
+            {
+              ...options,
+              newlinesBetween: 'never',
+            },
+          ],
         },
       ],
     })
@@ -372,23 +318,6 @@ describe(ruleName, () => {
     ruleTester.run(`${ruleName}(${type}): disallow extra spaces`, rule, {
       invalid: [
         {
-          options: [
-            {
-              ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-              internalPattern: ['^~/.*'],
-              newlinesBetween: 'always',
-            },
-          ],
           errors: [
             {
               data: {
@@ -421,27 +350,11 @@ describe(ruleName, () => {
             import c from '~/c'
             import d from '~/d'
           `,
+          options: [options],
         },
       ],
       valid: [
         {
-          options: [
-            {
-              ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-              internalPattern: ['^~/.*'],
-              newlinesBetween: 'always',
-            },
-          ],
           code: dedent`
             import { A } from 'a'
 
@@ -449,6 +362,7 @@ describe(ruleName, () => {
             import c from '~/c'
             import d from '~/d'
           `,
+          options: [options],
         },
       ],
     })
@@ -459,23 +373,6 @@ describe(ruleName, () => {
       {
         invalid: [
           {
-            options: [
-              {
-                ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
-              },
-            ],
             errors: [
               {
                 data: {
@@ -511,27 +408,11 @@ describe(ruleName, () => {
               import log = console.log
               import c = require('c/c')
             `,
+            options: [options],
           },
         ],
         valid: [
           {
-            options: [
-              {
-                ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
-              },
-            ],
             code: dedent`
               import type T = require("T")
 
@@ -542,6 +423,7 @@ describe(ruleName, () => {
               import c = require('c/c')
               import log = console.log
             `,
+            options: [options],
           },
         ],
       },
@@ -578,8 +460,6 @@ describe(ruleName, () => {
                   'internal',
                   ['parent', 'sibling', 'index'],
                 ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
               },
             ],
             code: dedent`
@@ -607,8 +487,6 @@ describe(ruleName, () => {
                   'internal',
                   ['parent', 'sibling', 'index'],
                 ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
               },
             ],
             code: dedent`
@@ -624,28 +502,12 @@ describe(ruleName, () => {
     ruleTester.run(`${ruleName}(${type}): ignores inline comments`, rule, {
       valid: [
         {
-          options: [
-            {
-              ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-              internalPattern: ['^~/.*'],
-              newlinesBetween: 'always',
-            },
-          ],
           code: dedent`
             import { a } from 'a'
             import { b1, b2 } from 'b' // Comment
             import { c } from 'c'
           `,
+          options: [options],
         },
       ],
       invalid: [],
@@ -657,29 +519,13 @@ describe(ruleName, () => {
       {
         valid: [
           {
-            options: [
-              {
-                ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
-              },
-            ],
             code: dedent`
               import type { T } from 't'
 
               // @ts-expect-error missing types
               import { t } from 't'
             `,
+            options: [options],
           },
         ],
         invalid: [],
@@ -692,23 +538,6 @@ describe(ruleName, () => {
       {
         valid: [
           {
-            options: [
-              {
-                ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
-              },
-            ],
             code: dedent`
               import type { V } from 'v'
 
@@ -716,6 +545,7 @@ describe(ruleName, () => {
 
               import type { T1, T2 } from 't'
             `,
+            options: [options],
           },
         ],
         invalid: [],
@@ -742,8 +572,6 @@ describe(ruleName, () => {
                   'object',
                   'unknown',
                 ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
               },
             ],
             code: dedent`
@@ -778,8 +606,6 @@ describe(ruleName, () => {
                   'object',
                   'unknown',
                 ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
               },
             ],
             code: dedent`
@@ -805,8 +631,6 @@ describe(ruleName, () => {
               {
                 ...options,
                 groups: ['builtin-type', 'type'],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
               },
             ],
             code: dedent`
@@ -838,8 +662,6 @@ describe(ruleName, () => {
                   ['parent', 'sibling', 'index'],
                   ['object', 'unknown'],
                 ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
               },
             ],
             errors: [
@@ -887,21 +709,6 @@ describe(ruleName, () => {
               messageId: 'unexpectedImportsGroupOrder',
             },
           ],
-          options: [
-            {
-              ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-            },
-          ],
           code: dedent`
             import { a } from 'a'
 
@@ -918,6 +725,7 @@ describe(ruleName, () => {
 
             import { b } from './b'
           `,
+          options: [options],
         },
       ],
       valid: [],
@@ -1059,23 +867,6 @@ describe(ruleName, () => {
       {
         invalid: [
           {
-            options: [
-              {
-                ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                newlinesBetween: 'always',
-                internalPattern: ['#.+'],
-              },
-            ],
             errors: [
               {
                 data: {
@@ -1115,27 +906,16 @@ describe(ruleName, () => {
 
               import { d } from '../d'
             `,
-          },
-        ],
-        valid: [
-          {
             options: [
               {
                 ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                newlinesBetween: 'always',
                 internalPattern: ['#.+'],
               },
             ],
+          },
+        ],
+        valid: [
+          {
             code: dedent`
               import type { T } from 'a'
 
@@ -1148,6 +928,12 @@ describe(ruleName, () => {
 
               import { d } from '../d'
             `,
+            options: [
+              {
+                ...options,
+                internalPattern: ['#.+'],
+              },
+            ],
           },
         ],
       },
@@ -1315,23 +1101,7 @@ describe(ruleName, () => {
               const { j } = require('../j')
               const { K, L, M } = require('../k')
             `,
-            options: [
-              {
-                ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
-              },
-            ],
+            options: [options],
           },
         ],
         valid: [
@@ -1352,23 +1122,7 @@ describe(ruleName, () => {
               const { j } = require('../j')
               const { K, L, M } = require('../k')
             `,
-            options: [
-              {
-                ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
-              },
-            ],
+            options: [options],
           },
         ],
       },
@@ -1409,8 +1163,6 @@ describe(ruleName, () => {
               {
                 ...options,
                 groups: ['external', 'side-effect', 'unknown'],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
                 sortSideEffects: false,
               },
             ],
@@ -1451,8 +1203,6 @@ describe(ruleName, () => {
               {
                 ...options,
                 groups: ['external', 'side-effect', 'unknown'],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
                 sortSideEffects: true,
               },
             ],
@@ -1474,8 +1224,6 @@ describe(ruleName, () => {
               {
                 ...options,
                 groups: ['external', 'side-effect', 'unknown'],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
                 sortSideEffects: false,
               },
             ],
@@ -1492,8 +1240,6 @@ describe(ruleName, () => {
               {
                 ...options,
                 groups: ['external', 'side-effect', 'unknown'],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
                 sortSideEffects: false,
               },
             ],
@@ -1549,7 +1295,6 @@ describe(ruleName, () => {
               options: [
                 {
                   ...options,
-                  newlinesBetween: 'always',
                   groups: ['unknown'],
                 },
               ],
@@ -1611,7 +1356,6 @@ describe(ruleName, () => {
                 {
                   ...options,
                   groups: ['side-effect', 'unknown'],
-                  newlinesBetween: 'always',
                 },
               ],
             },
@@ -1673,7 +1417,6 @@ describe(ruleName, () => {
                 {
                   ...options,
                   groups: [['side-effect', 'side-effect-style'], 'unknown'],
-                  newlinesBetween: 'always',
                 },
               ],
             },
@@ -1751,7 +1494,6 @@ describe(ruleName, () => {
                 {
                   ...options,
                   groups: ['side-effect', 'side-effect-style', 'unknown'],
-                  newlinesBetween: 'always',
                 },
               ],
             },
@@ -1822,7 +1564,6 @@ describe(ruleName, () => {
                 {
                   ...options,
                   groups: ['side-effect-style', 'unknown'],
-                  newlinesBetween: 'always',
                 },
               ],
             },
@@ -2881,13 +2622,6 @@ describe(ruleName, () => {
                 messageId: 'unexpectedImportsGroupOrder',
               },
             ],
-            options: [
-              {
-                ...options,
-                groups: ['style', 'unknown'],
-                newlinesBetween: 'always',
-              },
-            ],
             output: dedent`
               import b from './b.css?raw'
               import c from './c.css'
@@ -2899,23 +2633,28 @@ describe(ruleName, () => {
               import b from './b.css?raw'
               import c from './c.css'
             `,
-          },
-        ],
-        valid: [
-          {
             options: [
               {
                 ...options,
                 groups: ['style', 'unknown'],
-                newlinesBetween: 'always',
               },
             ],
+          },
+        ],
+        valid: [
+          {
             code: dedent`
               import b from './b.css?raw'
               import c from './c.css'
 
               import a from './a.js'
             `,
+            options: [
+              {
+                ...options,
+                groups: ['style', 'unknown'],
+              },
+            ],
           },
         ],
       },
@@ -3091,23 +2830,7 @@ describe(ruleName, () => {
             import { j } from '../j'
             import { K, L, M } from '../k'
           `,
-          options: [
-            {
-              ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-              internalPattern: ['^~/.*'],
-              newlinesBetween: 'always',
-            },
-          ],
+          options: [options],
         },
       ],
       valid: [
@@ -3138,23 +2861,7 @@ describe(ruleName, () => {
             import { K, L, M } from '../k'
             import './style.css'
           `,
-          options: [
-            {
-              ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-              internalPattern: ['^~/.*'],
-              newlinesBetween: 'always',
-            },
-          ],
+          options: [options],
         },
       ],
     })
@@ -3191,23 +2898,6 @@ describe(ruleName, () => {
               messageId: 'unexpectedImportsGroupOrder',
             },
           ],
-          options: [
-            {
-              ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-              internalPattern: ['^~/.*'],
-              newlinesBetween: 'never',
-            },
-          ],
           code: dedent`
             import d from '.'
             import { a1, a2, a3 } from 'a'
@@ -3226,27 +2916,16 @@ describe(ruleName, () => {
             import d from '.'
             import { e1, e2, e3 } from '../../e'
           `,
-        },
-      ],
-      valid: [
-        {
           options: [
             {
               ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-              internalPattern: ['^~/.*'],
               newlinesBetween: 'never',
             },
           ],
+        },
+      ],
+      valid: [
+        {
           code: dedent`
             import type { T } from 't'
             import { a1, a2, a3 } from 'a'
@@ -3255,30 +2934,19 @@ describe(ruleName, () => {
             import d from '.'
             import { e1, e2, e3 } from '../../e'
           `,
-        },
+          options: [
+            {
+              ...options,
+              newlinesBetween: 'never',
+            },
+          ],
+        },
       ],
     })
 
     ruleTester.run(`${ruleName}(${type}): disallow extra spaces`, rule, {
       invalid: [
         {
-          options: [
-            {
-              ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-              internalPattern: ['^~/.*'],
-              newlinesBetween: 'always',
-            },
-          ],
           errors: [
             {
               data: {
@@ -3311,27 +2979,11 @@ describe(ruleName, () => {
             import c from '~/c'
             import d from '~/d'
           `,
+          options: [options],
         },
       ],
       valid: [
         {
-          options: [
-            {
-              ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-              internalPattern: ['^~/.*'],
-              newlinesBetween: 'always',
-            },
-          ],
           code: dedent`
             import { A } from 'a'
 
@@ -3339,6 +2991,7 @@ describe(ruleName, () => {
             import c from '~/c'
             import d from '~/d'
           `,
+          options: [options],
         },
       ],
     })
@@ -3349,23 +3002,6 @@ describe(ruleName, () => {
       {
         invalid: [
           {
-            options: [
-              {
-                ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
-              },
-            ],
             errors: [
               {
                 data: {
@@ -3401,27 +3037,11 @@ describe(ruleName, () => {
               import log = console.log
               import c = require('c/c')
             `,
+            options: [options],
           },
         ],
         valid: [
           {
-            options: [
-              {
-                ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
-              },
-            ],
             code: dedent`
               import type T = require("T")
 
@@ -3432,6 +3052,7 @@ describe(ruleName, () => {
               import c = require('c/c')
               import log = console.log
             `,
+            options: [options],
           },
         ],
       },
@@ -3468,8 +3089,6 @@ describe(ruleName, () => {
                   'internal',
                   ['parent', 'sibling', 'index'],
                 ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
               },
             ],
             code: dedent`
@@ -3497,8 +3116,6 @@ describe(ruleName, () => {
                   'internal',
                   ['parent', 'sibling', 'index'],
                 ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
               },
             ],
             code: dedent`
@@ -3514,28 +3131,12 @@ describe(ruleName, () => {
     ruleTester.run(`${ruleName}(${type}): ignores inline comments`, rule, {
       valid: [
         {
-          options: [
-            {
-              ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-              internalPattern: ['^~/.*'],
-              newlinesBetween: 'always',
-            },
-          ],
           code: dedent`
             import { a } from 'a'
             import { b1, b2 } from 'b' // Comment
             import { c } from 'c'
           `,
+          options: [options],
         },
       ],
       invalid: [],
@@ -3547,29 +3148,13 @@ describe(ruleName, () => {
       {
         valid: [
           {
-            options: [
-              {
-                ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
-              },
-            ],
             code: dedent`
               import type { T } from 't'
 
               // @ts-expect-error missing types
               import { t } from 't'
             `,
+            options: [options],
           },
         ],
         invalid: [],
@@ -3582,23 +3167,6 @@ describe(ruleName, () => {
       {
         valid: [
           {
-            options: [
-              {
-                ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
-              },
-            ],
             code: dedent`
               import type { V } from 'v'
 
@@ -3606,6 +3174,7 @@ describe(ruleName, () => {
 
               import type { T1, T2 } from 't'
             `,
+            options: [options],
           },
         ],
         invalid: [],
@@ -3632,8 +3201,6 @@ describe(ruleName, () => {
                   'object',
                   'unknown',
                 ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
               },
             ],
             code: dedent`
@@ -3668,8 +3235,6 @@ describe(ruleName, () => {
                   'object',
                   'unknown',
                 ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
               },
             ],
             code: dedent`
@@ -3695,8 +3260,6 @@ describe(ruleName, () => {
               {
                 ...options,
                 groups: ['builtin-type', 'type'],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
               },
             ],
             code: dedent`
@@ -3728,8 +3291,6 @@ describe(ruleName, () => {
                   ['parent', 'sibling', 'index'],
                   ['object', 'unknown'],
                 ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
               },
             ],
             errors: [
@@ -3777,21 +3338,6 @@ describe(ruleName, () => {
               messageId: 'unexpectedImportsGroupOrder',
             },
           ],
-          options: [
-            {
-              ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-            },
-          ],
           code: dedent`
             import { a } from 'a'
 
@@ -3808,6 +3354,7 @@ describe(ruleName, () => {
 
             import { b } from './b'
           `,
+          options: [options],
         },
       ],
       valid: [],
@@ -3949,23 +3496,6 @@ describe(ruleName, () => {
       {
         invalid: [
           {
-            options: [
-              {
-                ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                newlinesBetween: 'always',
-                internalPattern: ['#.+'],
-              },
-            ],
             errors: [
               {
                 data: {
@@ -4005,27 +3535,16 @@ describe(ruleName, () => {
 
               import { d } from '../d'
             `,
-          },
-        ],
-        valid: [
-          {
             options: [
               {
                 ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                newlinesBetween: 'always',
                 internalPattern: ['#.+'],
               },
             ],
+          },
+        ],
+        valid: [
+          {
             code: dedent`
               import type { T } from 'a'
 
@@ -4038,6 +3557,12 @@ describe(ruleName, () => {
 
               import { d } from '../d'
             `,
+            options: [
+              {
+                ...options,
+                internalPattern: ['#.+'],
+              },
+            ],
           },
         ],
       },
@@ -4205,23 +3730,7 @@ describe(ruleName, () => {
               const { j } = require('../j')
               const { K, L, M } = require('../k')
             `,
-            options: [
-              {
-                ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
-              },
-            ],
+            options: [options],
           },
         ],
         valid: [
@@ -4242,23 +3751,7 @@ describe(ruleName, () => {
               const { j } = require('../j')
               const { K, L, M } = require('../k')
             `,
-            options: [
-              {
-                ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
-              },
-            ],
+            options: [options],
           },
         ],
       },
@@ -4299,8 +3792,6 @@ describe(ruleName, () => {
               {
                 ...options,
                 groups: ['external', 'side-effect', 'unknown'],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
                 sortSideEffects: false,
               },
             ],
@@ -4341,8 +3832,6 @@ describe(ruleName, () => {
               {
                 ...options,
                 groups: ['external', 'side-effect', 'unknown'],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
                 sortSideEffects: true,
               },
             ],
@@ -4364,8 +3853,6 @@ describe(ruleName, () => {
               {
                 ...options,
                 groups: ['external', 'side-effect', 'unknown'],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
                 sortSideEffects: false,
               },
             ],
@@ -4382,8 +3869,6 @@ describe(ruleName, () => {
               {
                 ...options,
                 groups: ['external', 'side-effect', 'unknown'],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
                 sortSideEffects: false,
               },
             ],
@@ -4414,13 +3899,6 @@ describe(ruleName, () => {
                 messageId: 'unexpectedImportsGroupOrder',
               },
             ],
-            options: [
-              {
-                ...options,
-                groups: ['style', 'unknown'],
-                newlinesBetween: 'always',
-              },
-            ],
             output: dedent`
               import b from './b.css?raw'
               import c from './c.css'
@@ -4432,23 +3910,28 @@ describe(ruleName, () => {
               import b from './b.css?raw'
               import c from './c.css'
             `,
-          },
-        ],
-        valid: [
-          {
             options: [
               {
                 ...options,
                 groups: ['style', 'unknown'],
-                newlinesBetween: 'always',
               },
             ],
+          },
+        ],
+        valid: [
+          {
             code: dedent`
               import b from './b.css?raw'
               import c from './c.css'
 
               import a from './a.js'
             `,
+            options: [
+              {
+                ...options,
+                groups: ['style', 'unknown'],
+              },
+            ],
           },
         ],
       },
@@ -4691,23 +4174,7 @@ describe(ruleName, () => {
             import { j } from '../j'
             import { K, L, M } from '../k'
           `,
-          options: [
-            {
-              ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-              internalPattern: ['^~/.*'],
-              newlinesBetween: 'always',
-            },
-          ],
+          options: [options],
         },
       ],
       valid: [
@@ -4738,23 +4205,7 @@ describe(ruleName, () => {
             import './style.css'
             import a from '.'
           `,
-          options: [
-            {
-              ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-              internalPattern: ['^~/.*'],
-              newlinesBetween: 'always',
-            },
-          ],
+          options: [options],
         },
       ],
     })
@@ -4791,23 +4242,6 @@ describe(ruleName, () => {
               messageId: 'unexpectedImportsGroupOrder',
             },
           ],
-          options: [
-            {
-              ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-              internalPattern: ['^~/.*'],
-              newlinesBetween: 'never',
-            },
-          ],
           code: dedent`
             import d from '.'
             import { a1, a2, a3 } from 'a'
@@ -4826,27 +4260,16 @@ describe(ruleName, () => {
             import { e1, e2, e3 } from '../../e'
             import d from '.'
           `,
-        },
-      ],
-      valid: [
-        {
           options: [
             {
               ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-              internalPattern: ['^~/.*'],
               newlinesBetween: 'never',
             },
           ],
+        },
+      ],
+      valid: [
+        {
           code: dedent`
             import type { T } from 't'
             import { a1, a2, a3 } from 'a'
@@ -4855,6 +4278,12 @@ describe(ruleName, () => {
             import { e1, e2, e3 } from '../../e'
             import d from '.'
           `,
+          options: [
+            {
+              ...options,
+              newlinesBetween: 'never',
+            },
+          ],
         },
       ],
     })
@@ -4862,23 +4291,6 @@ describe(ruleName, () => {
     ruleTester.run(`${ruleName}(${type}): disallow extra spaces`, rule, {
       invalid: [
         {
-          options: [
-            {
-              ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-              internalPattern: ['^~/.*'],
-              newlinesBetween: 'always',
-            },
-          ],
           errors: [
             {
               data: {
@@ -4911,27 +4323,11 @@ describe(ruleName, () => {
             import c from '~/c'
             import d from '~/d'
           `,
+          options: [options],
         },
       ],
       valid: [
         {
-          options: [
-            {
-              ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-              internalPattern: ['^~/.*'],
-              newlinesBetween: 'always',
-            },
-          ],
           code: dedent`
             import { A } from 'a'
 
@@ -4939,6 +4335,7 @@ describe(ruleName, () => {
             import c from '~/c'
             import d from '~/d'
           `,
+          options: [options],
         },
       ],
     })
@@ -4949,23 +4346,6 @@ describe(ruleName, () => {
       {
         invalid: [
           {
-            options: [
-              {
-                ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
-              },
-            ],
             errors: [
               {
                 data: {
@@ -5001,27 +4381,11 @@ describe(ruleName, () => {
               import log = console.log
               import c = require('c/c')
             `,
+            options: [options],
           },
         ],
         valid: [
           {
-            options: [
-              {
-                ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
-              },
-            ],
             code: dedent`
               import type T = require("T")
 
@@ -5032,6 +4396,7 @@ describe(ruleName, () => {
               import c = require('c/c')
               import log = console.log
             `,
+            options: [options],
           },
         ],
       },
@@ -5068,8 +4433,6 @@ describe(ruleName, () => {
                   'internal',
                   ['parent', 'sibling', 'index'],
                 ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
               },
             ],
             code: dedent`
@@ -5097,8 +4460,6 @@ describe(ruleName, () => {
                   'internal',
                   ['parent', 'sibling', 'index'],
                 ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
               },
             ],
             code: dedent`
@@ -5117,29 +4478,13 @@ describe(ruleName, () => {
       {
         valid: [
           {
-            options: [
-              {
-                ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
-              },
-            ],
             code: dedent`
               import type { T } from 't'
 
               // @ts-expect-error missing types
               import { t } from 't'
             `,
+            options: [options],
           },
         ],
         invalid: [],
@@ -5152,23 +4497,6 @@ describe(ruleName, () => {
       {
         valid: [
           {
-            options: [
-              {
-                ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
-              },
-            ],
             code: dedent`
               import type { V } from 'v'
 
@@ -5176,6 +4504,7 @@ describe(ruleName, () => {
 
               import type { T1, T2 } from 't'
             `,
+            options: [options],
           },
         ],
         invalid: [],
@@ -5202,8 +4531,6 @@ describe(ruleName, () => {
                   'object',
                   'unknown',
                 ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
               },
             ],
             code: dedent`
@@ -5238,8 +4565,6 @@ describe(ruleName, () => {
                   'object',
                   'unknown',
                 ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
               },
             ],
             code: dedent`
@@ -5265,8 +4590,6 @@ describe(ruleName, () => {
               {
                 ...options,
                 groups: ['builtin-type', 'type'],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
               },
             ],
             code: dedent`
@@ -5298,8 +4621,6 @@ describe(ruleName, () => {
                   ['parent', 'sibling', 'index'],
                   ['object', 'unknown'],
                 ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
               },
             ],
             errors: [
@@ -5347,21 +4668,6 @@ describe(ruleName, () => {
               messageId: 'unexpectedImportsGroupOrder',
             },
           ],
-          options: [
-            {
-              ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
-            },
-          ],
           code: dedent`
             import { a } from 'a'
 
@@ -5378,6 +4684,7 @@ describe(ruleName, () => {
 
             import { b } from './b'
           `,
+          options: [options],
         },
       ],
       valid: [],
@@ -5512,23 +4819,6 @@ describe(ruleName, () => {
       {
         invalid: [
           {
-            options: [
-              {
-                ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                newlinesBetween: 'always',
-                internalPattern: ['#.+'],
-              },
-            ],
             errors: [
               {
                 data: {
@@ -5568,27 +4858,16 @@ describe(ruleName, () => {
 
               import { d } from '../d'
             `,
-          },
-        ],
-        valid: [
-          {
             options: [
               {
                 ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                newlinesBetween: 'always',
                 internalPattern: ['#.+'],
               },
             ],
+          },
+        ],
+        valid: [
+          {
             code: dedent`
               import type { T } from 'a'
 
@@ -5601,6 +4880,12 @@ describe(ruleName, () => {
 
               import { d } from '../d'
             `,
+            options: [
+              {
+                ...options,
+                internalPattern: ['#.+'],
+              },
+            ],
           },
         ],
       },
@@ -5669,16 +4954,6 @@ describe(ruleName, () => {
           options: [
             {
               ...options,
-              groups: [
-                'type',
-                ['builtin', 'external'],
-                'internal-type',
-                'internal',
-                ['parent-type', 'sibling-type', 'index-type'],
-                ['parent', 'sibling', 'index'],
-                'object',
-                'unknown',
-              ],
               maxLineLength: 80,
               order: 'asc',
             },
@@ -5857,23 +5132,7 @@ describe(ruleName, () => {
               const { j } = require('../j')
               const { K, L, M } = require('../k')
             `,
-            options: [
-              {
-                ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
-              },
-            ],
+            options: [options],
           },
         ],
         valid: [
@@ -5894,23 +5153,7 @@ describe(ruleName, () => {
               const h = require('../../h')
               const a = require('.')
             `,
-            options: [
-              {
-                ...options,
-                groups: [
-                  'type',
-                  ['builtin', 'external'],
-                  'internal-type',
-                  'internal',
-                  ['parent-type', 'sibling-type', 'index-type'],
-                  ['parent', 'sibling', 'index'],
-                  'object',
-                  'unknown',
-                ],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
-              },
-            ],
+            options: [options],
           },
         ],
       },
@@ -5951,8 +5194,6 @@ describe(ruleName, () => {
               {
                 ...options,
                 groups: ['external', 'side-effect', 'unknown'],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
                 sortSideEffects: false,
               },
             ],
@@ -5993,8 +5234,6 @@ describe(ruleName, () => {
               {
                 ...options,
                 groups: ['external', 'side-effect', 'unknown'],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
                 sortSideEffects: true,
               },
             ],
@@ -6016,8 +5255,6 @@ describe(ruleName, () => {
               {
                 ...options,
                 groups: ['external', 'side-effect', 'unknown'],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
                 sortSideEffects: false,
               },
             ],
@@ -6034,8 +5271,6 @@ describe(ruleName, () => {
               {
                 ...options,
                 groups: ['external', 'side-effect', 'unknown'],
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
                 sortSideEffects: false,
               },
             ],
@@ -6066,13 +5301,6 @@ describe(ruleName, () => {
                 messageId: 'unexpectedImportsGroupOrder',
               },
             ],
-            options: [
-              {
-                ...options,
-                groups: ['style', 'unknown'],
-                newlinesBetween: 'always',
-              },
-            ],
             output: dedent`
               import b from './b.css?raw'
               import c from './c.css'
@@ -6084,23 +5312,28 @@ describe(ruleName, () => {
               import b from './b.css?raw'
               import c from './c.css'
             `,
-          },
-        ],
-        valid: [
-          {
             options: [
               {
                 ...options,
                 groups: ['style', 'unknown'],
-                newlinesBetween: 'always',
               },
             ],
+          },
+        ],
+        valid: [
+          {
             code: dedent`
               import b from './b.css?raw'
               import c from './c.css'
 
               import a from './a.js'
             `,
+            options: [
+              {
+                ...options,
+                groups: ['style', 'unknown'],
+              },
+            ],
           },
         ],
       },
@@ -6604,8 +5837,6 @@ describe(ruleName, () => {
                     ui: ['~/ui/.+'],
                   },
                 },
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
                 type: 'line-length',
               },
             ],
@@ -6691,8 +5922,6 @@ describe(ruleName, () => {
                     ui: ['^~/ui/.+'],
                   },
                 },
-                internalPattern: ['^~/.*'],
-                newlinesBetween: 'always',
                 type: 'line-length',
               },
             ],
diff --git a/test/rules/sort-modules.test.ts b/test/rules/sort-modules.test.ts
index 3f0ee42..3c2b28e 100644
--- a/test/rules/sort-modules.test.ts
+++ b/test/rules/sort-modules.test.ts
@@ -1782,11 +1782,7 @@ describe(ruleName, () => {
                 static b = 1
               }
             `,
-            options: [
-              {
-                ...options,
-              },
-            ],
+            options: [options],
           },
         ],
         valid: [],
@@ -1883,11 +1879,7 @@ describe(ruleName, () => {
                   messageId: 'unexpectedModulesOrder',
                 },
               ],
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
           ],
           valid: [],
@@ -3554,11 +3546,7 @@ describe(ruleName, () => {
             class A extends B {}
             class B {}
           `,
-          options: [
-            {
-              ...options,
-            },
-          ],
+          options: [options],
         },
       ],
       valid: [],
diff --git a/test/rules/sort-named-exports.test.ts b/test/rules/sort-named-exports.test.ts
index 87ff591..2776b2b 100644
--- a/test/rules/sort-named-exports.test.ts
+++ b/test/rules/sort-named-exports.test.ts
@@ -842,6 +842,857 @@ describe(ruleName, () => {
       ],
       valid: [],
     })
+
+    describe(`${ruleName}: custom groups`, () => {
+      ruleTester.run(`${ruleName}: filters on modifier`, rule, {
+        invalid: [
+          {
+            errors: [
+              {
+                data: {
+                  rightGroup: 'typeElements',
+                  leftGroup: 'unknown',
+                  right: 'b',
+                  left: 'a',
+                },
+                messageId: 'unexpectedNamedExportsGroupOrder',
+              },
+            ],
+            options: [
+              {
+                customGroups: [
+                  {
+                    groupName: 'typeElements',
+                    modifiers: ['type'],
+                  },
+                ],
+                groups: ['typeElements', 'unknown'],
+              },
+            ],
+            output: dedent`
+              export {
+                type b,
+                a,
+              }
+            `,
+            code: dedent`
+              export {
+                a,
+                type b,
+              }
+            `,
+          },
+        ],
+        valid: [],
+      })
+
+      for (let elementNamePattern of [
+        'hello',
+        ['noMatch', 'hello'],
+        { pattern: 'HELLO', flags: 'i' },
+        ['noMatch', { pattern: 'HELLO', flags: 'i' }],
+      ]) {
+        ruleTester.run(`${ruleName}: filters on elementNamePattern`, rule, {
+          invalid: [
+            {
+              options: [
+                {
+                  customGroups: [
+                    {
+                      groupName: 'typesStartingWithHello',
+                      modifiers: ['type'],
+                      elementNamePattern,
+                    },
+                  ],
+                  groups: ['typesStartingWithHello', 'unknown'],
+                },
+              ],
+              errors: [
+                {
+                  data: {
+                    rightGroup: 'typesStartingWithHello',
+                    leftGroup: 'unknown',
+                    right: 'helloType',
+                    left: 'b',
+                  },
+                  messageId: 'unexpectedNamedExportsGroupOrder',
+                },
+              ],
+              output: dedent`
+                export {
+                  type helloType,
+                  a,
+                  b,
+                }
+              `,
+              code: dedent`
+                export {
+                  a,
+                  b,
+                  type helloType,
+                }
+              `,
+            },
+          ],
+          valid: [],
+        })
+      }
+
+      ruleTester.run(
+        `${ruleName}: sort custom groups by overriding 'type' and 'order'`,
+        rule,
+        {
+          invalid: [
+            {
+              errors: [
+                {
+                  data: {
+                    right: 'bb',
+                    left: 'a',
+                  },
+                  messageId: 'unexpectedNamedExportsOrder',
+                },
+                {
+                  data: {
+                    right: 'ccc',
+                    left: 'bb',
+                  },
+                  messageId: 'unexpectedNamedExportsOrder',
+                },
+                {
+                  data: {
+                    right: 'dddd',
+                    left: 'ccc',
+                  },
+                  messageId: 'unexpectedNamedExportsOrder',
+                },
+                {
+                  data: {
+                    rightGroup: 'reversedTypesByLineLength',
+                    leftGroup: 'unknown',
+                    right: 'eee',
+                    left: 'm',
+                  },
+                  messageId: 'unexpectedNamedExportsGroupOrder',
+                },
+              ],
+              options: [
+                {
+                  customGroups: [
+                    {
+                      groupName: 'reversedTypesByLineLength',
+                      modifiers: ['type'],
+                      type: 'line-length',
+                      order: 'desc',
+                    },
+                  ],
+                  groups: ['reversedTypesByLineLength', 'unknown'],
+                  type: 'alphabetical',
+                  order: 'asc',
+                },
+              ],
+              output: dedent`
+                export {
+                  type dddd,
+                  type ccc,
+                  type eee,
+                  type bb,
+                  type ff,
+                  type a,
+                  type g,
+                  m,
+                  o,
+                  p,
+                }
+              `,
+              code: dedent`
+                export {
+                  type a,
+                  type bb,
+                  type ccc,
+                  type dddd,
+                  m,
+                  type eee,
+                  type ff,
+                  type g,
+                  o,
+                  p,
+                }
+              `,
+            },
+          ],
+          valid: [],
+        },
+      )
+
+      ruleTester.run(
+        `${ruleName}: sort custom groups by overriding 'fallbackSort'`,
+        rule,
+        {
+          invalid: [
+            {
+              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: 'fooBar',
+                    left: 'fooZar',
+                  },
+                  messageId: 'unexpectedNamedExportsOrder',
+                },
+              ],
+              output: dedent`
+                export {
+                  fooBar,
+                  fooZar,
+                }
+              `,
+              code: dedent`
+                export {
+                  fooZar,
+                  fooBar,
+                }
+              `,
+            },
+          ],
+          valid: [],
+        },
+      )
+
+      ruleTester.run(
+        `${ruleName}: does not sort custom groups with 'unsorted' type`,
+        rule,
+        {
+          invalid: [
+            {
+              options: [
+                {
+                  customGroups: [
+                    {
+                      groupName: 'unsortedTypes',
+                      modifiers: ['type'],
+                      type: 'unsorted',
+                    },
+                  ],
+                  groups: ['unsortedTypes', 'unknown'],
+                },
+              ],
+              errors: [
+                {
+                  data: {
+                    rightGroup: 'unsortedTypes',
+                    leftGroup: 'unknown',
+                    right: 'c',
+                    left: 'm',
+                  },
+                  messageId: 'unexpectedNamedExportsGroupOrder',
+                },
+              ],
+              output: dedent`
+                export {
+                  type b,
+                  type a,
+                  type d,
+                  type e,
+                  type c,
+                  m,
+                }
+              `,
+              code: dedent`
+                export {
+                  type b,
+                  type a,
+                  type d,
+                  type e,
+                  m,
+                  type c,
+                }
+              `,
+            },
+          ],
+          valid: [],
+        },
+      )
+
+      ruleTester.run(`${ruleName}: sort custom group blocks`, rule, {
+        invalid: [
+          {
+            options: [
+              {
+                customGroups: [
+                  {
+                    anyOf: [
+                      {
+                        elementNamePattern: 'foo|Foo',
+                        modifiers: ['type'],
+                      },
+                      {
+                        elementNamePattern: 'foo|Foo',
+                      },
+                    ],
+                    groupName: 'elementsIncludingFoo',
+                  },
+                ],
+                groups: ['elementsIncludingFoo', 'unknown'],
+              },
+            ],
+            errors: [
+              {
+                data: {
+                  rightGroup: 'elementsIncludingFoo',
+                  leftGroup: 'unknown',
+                  right: 'cFoo',
+                  left: 'a',
+                },
+                messageId: 'unexpectedNamedExportsGroupOrder',
+              },
+            ],
+            output: dedent`
+              export {
+                type cFoo,
+                foo,
+                type a,
+              }
+            `,
+            code: dedent`
+              export {
+                type a,
+                type cFoo,
+                foo,
+              }
+            `,
+          },
+        ],
+        valid: [],
+      })
+
+      ruleTester.run(
+        `${ruleName}: allows to use regex for element names in custom groups`,
+        rule,
+        {
+          valid: [
+            {
+              options: [
+                {
+                  customGroups: [
+                    {
+                      elementNamePattern: '^(?!.*Foo).*$',
+                      groupName: 'elementsWithoutFoo',
+                    },
+                  ],
+                  groups: ['unknown', 'elementsWithoutFoo'],
+                  type: 'alphabetical',
+                },
+              ],
+              code: dedent`
+                export {
+                  iHaveFooInMyName,
+                  meTooIHaveFoo,
+                  a,
+                  b,
+                }
+              `,
+            },
+          ],
+          invalid: [],
+        },
+      )
+    })
+
+    describe(`${ruleName}: newlinesBetween`, () => {
+      ruleTester.run(
+        `${ruleName}(${type}): removes newlines when never`,
+        rule,
+        {
+          invalid: [
+            {
+              errors: [
+                {
+                  data: {
+                    right: 'y',
+                    left: 'a',
+                  },
+                  messageId: 'extraSpacingBetweenNamedExports',
+                },
+                {
+                  data: {
+                    right: 'b',
+                    left: 'z',
+                  },
+                  messageId: 'unexpectedNamedExportsOrder',
+                },
+                {
+                  data: {
+                    right: 'b',
+                    left: 'z',
+                  },
+                  messageId: 'extraSpacingBetweenNamedExports',
+                },
+              ],
+              options: [
+                {
+                  ...options,
+                  customGroups: [
+                    {
+                      elementNamePattern: 'a',
+                      groupName: 'a',
+                    },
+                  ],
+                  groups: ['a', 'unknown'],
+                  newlinesBetween: 'never',
+                },
+              ],
+              code: dedent`
+                export {
+                    a,
+
+
+                   y,
+                  z,
+
+                      b,
+                }
+              `,
+              output: dedent`
+                export {
+                    a,
+                   b,
+                  y,
+                      z,
+                }
+              `,
+            },
+          ],
+          valid: [],
+        },
+      )
+
+      ruleTester.run(
+        `${ruleName}(${type}): keeps one newline when always`,
+        rule,
+        {
+          invalid: [
+            {
+              errors: [
+                {
+                  data: {
+                    right: 'z',
+                    left: 'a',
+                  },
+                  messageId: 'extraSpacingBetweenNamedExports',
+                },
+                {
+                  data: {
+                    right: 'y',
+                    left: 'z',
+                  },
+                  messageId: 'unexpectedNamedExportsOrder',
+                },
+                {
+                  data: {
+                    right: 'b',
+                    left: 'y',
+                  },
+                  messageId: 'missedSpacingBetweenNamedExports',
+                },
+              ],
+              options: [
+                {
+                  ...options,
+                  customGroups: [
+                    {
+                      elementNamePattern: 'a',
+                      groupName: 'a',
+                    },
+                    {
+                      elementNamePattern: 'b',
+                      groupName: 'b',
+                    },
+                  ],
+                  groups: ['a', 'unknown', 'b'],
+                  newlinesBetween: 'always',
+                },
+              ],
+              output: dedent`
+                export {
+                    a,
+
+                   y,
+                  z,
+
+                      b,
+                }
+              `,
+              code: dedent`
+                export {
+                    a,
+
+
+                   z,
+                  y,
+                      b,
+                }
+              `,
+            },
+          ],
+          valid: [],
+        },
+      )
+
+      describe(`${ruleName}(${type}): "newlinesBetween" inside groups`, () => {
+        ruleTester.run(
+          `${ruleName}(${type}): handles "newlinesBetween" between consecutive groups`,
+          rule,
+          {
+            invalid: [
+              {
+                options: [
+                  {
+                    ...options,
+                    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: 'missedSpacingBetweenNamedExports',
+                  },
+                  {
+                    data: {
+                      right: 'c',
+                      left: 'b',
+                    },
+                    messageId: 'extraSpacingBetweenNamedExports',
+                  },
+                  {
+                    data: {
+                      right: 'd',
+                      left: 'c',
+                    },
+                    messageId: 'extraSpacingBetweenNamedExports',
+                  },
+                ],
+                code: dedent`
+                    export {
+                    a,
+                    b,
+
+
+                    c,
+
+                    d,
+
+
+                    e,
+                  }
+                `,
+                output: dedent`
+                  export {
+                    a,
+
+                    b,
+
+                    c,
+                    d,
+
+
+                    e,
+                  }
+                `,
+              },
+            ],
+            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,
+              {
+                invalid: [
+                  {
+                    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: 'missedSpacingBetweenNamedExports',
+                      },
+                    ],
+                    output: dedent`
+                      export {
+                        a,
+
+                        b,
+                      }
+                    `,
+                    code: dedent`
+                      export {
+                        a,
+                        b,
+                      }
+                    `,
+                  },
+                ],
+                valid: [],
+              },
+            )
+          }
+
+          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,
+              {
+                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: 'extraSpacingBetweenNamedExports',
+                      },
+                    ],
+                    output: dedent`
+                      export {
+                        a,
+                        b,
+                      }
+                    `,
+                    code: dedent`
+                      export {
+                        a,
+
+                        b,
+                      }
+                    `,
+                  },
+                ],
+                valid: [],
+              },
+            )
+          }
+
+          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,
+              {
+                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`
+                      export {
+                        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`
+                      export {
+                        a,
+                        b,
+                      }
+                    `,
+                  },
+                ],
+                invalid: [],
+              },
+            )
+          }
+        })
+      })
+
+      ruleTester.run(
+        `${ruleName}(${type}): handles newlines and comment after fixes`,
+        rule,
+        {
+          invalid: [
+            {
+              output: [
+                dedent`
+                  export {
+                    a, // Comment after
+                    b,
+
+                    c,
+                  }
+                `,
+                dedent`
+                  export {
+                    a, // Comment after
+
+                    b,
+                    c,
+                  }
+                `,
+              ],
+              options: [
+                {
+                  customGroups: [
+                    {
+                      elementNamePattern: 'b|c',
+                      groupName: 'b|c',
+                    },
+                  ],
+                  groups: ['unknown', 'b|c'],
+                  newlinesBetween: 'always',
+                },
+              ],
+              errors: [
+                {
+                  data: {
+                    rightGroup: 'unknown',
+                    leftGroup: 'b|c',
+                    right: 'a',
+                    left: 'b',
+                  },
+                  messageId: 'unexpectedNamedExportsGroupOrder',
+                },
+              ],
+              code: dedent`
+                export {
+                  b,
+                  a, // Comment after
+
+                  c,
+                }
+              `,
+            },
+          ],
+          valid: [],
+        },
+      )
+    })
   })
 
   describe(`${ruleName}: sorting by natural order`, () => {
@@ -1278,6 +2129,53 @@ 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: 'missedSpacingBetweenNamedExports',
+            },
+          ],
+          output: dedent`
+            export {
+                b,
+
+                a,
+            }
+          `,
+          code: dedent`
+            export {
+                b,
+                a,
+            }
+          `,
+        },
+      ],
+      valid: [],
+    })
   })
 
   describe(`${ruleName}: misc`, () => {
diff --git a/test/rules/sort-named-imports.test.ts b/test/rules/sort-named-imports.test.ts
index 9518bd1..72a7ec7 100644
--- a/test/rules/sort-named-imports.test.ts
+++ b/test/rules/sort-named-imports.test.ts
@@ -1006,6 +1006,857 @@ describe(ruleName, () => {
         valid: [],
       },
     )
+
+    describe(`${ruleName}: custom groups`, () => {
+      ruleTester.run(`${ruleName}: filters on modifier`, rule, {
+        invalid: [
+          {
+            errors: [
+              {
+                data: {
+                  rightGroup: 'typeElements',
+                  leftGroup: 'unknown',
+                  right: 'b',
+                  left: 'a',
+                },
+                messageId: 'unexpectedNamedImportsGroupOrder',
+              },
+            ],
+            options: [
+              {
+                customGroups: [
+                  {
+                    groupName: 'typeElements',
+                    modifiers: ['type'],
+                  },
+                ],
+                groups: ['typeElements', 'unknown'],
+              },
+            ],
+            output: dedent`
+              import {
+                type b,
+                a,
+              } from 'module'
+            `,
+            code: dedent`
+              import {
+                a,
+                type b,
+              } from 'module'
+            `,
+          },
+        ],
+        valid: [],
+      })
+
+      for (let elementNamePattern of [
+        'hello',
+        ['noMatch', 'hello'],
+        { pattern: 'HELLO', flags: 'i' },
+        ['noMatch', { pattern: 'HELLO', flags: 'i' }],
+      ]) {
+        ruleTester.run(`${ruleName}: filters on elementNamePattern`, rule, {
+          invalid: [
+            {
+              options: [
+                {
+                  customGroups: [
+                    {
+                      groupName: 'typesStartingWithHello',
+                      modifiers: ['type'],
+                      elementNamePattern,
+                    },
+                  ],
+                  groups: ['typesStartingWithHello', 'unknown'],
+                },
+              ],
+              errors: [
+                {
+                  data: {
+                    rightGroup: 'typesStartingWithHello',
+                    leftGroup: 'unknown',
+                    right: 'helloType',
+                    left: 'b',
+                  },
+                  messageId: 'unexpectedNamedImportsGroupOrder',
+                },
+              ],
+              output: dedent`
+                import {
+                  type helloType,
+                  a,
+                  b,
+                } from 'module'
+              `,
+              code: dedent`
+                import {
+                  a,
+                  b,
+                  type helloType,
+                } from 'module'
+              `,
+            },
+          ],
+          valid: [],
+        })
+      }
+
+      ruleTester.run(
+        `${ruleName}: sort custom groups by overriding 'type' and 'order'`,
+        rule,
+        {
+          invalid: [
+            {
+              errors: [
+                {
+                  data: {
+                    right: 'bb',
+                    left: 'a',
+                  },
+                  messageId: 'unexpectedNamedImportsOrder',
+                },
+                {
+                  data: {
+                    right: 'ccc',
+                    left: 'bb',
+                  },
+                  messageId: 'unexpectedNamedImportsOrder',
+                },
+                {
+                  data: {
+                    right: 'dddd',
+                    left: 'ccc',
+                  },
+                  messageId: 'unexpectedNamedImportsOrder',
+                },
+                {
+                  data: {
+                    rightGroup: 'reversedTypesByLineLength',
+                    leftGroup: 'unknown',
+                    right: 'eee',
+                    left: 'm',
+                  },
+                  messageId: 'unexpectedNamedImportsGroupOrder',
+                },
+              ],
+              options: [
+                {
+                  customGroups: [
+                    {
+                      groupName: 'reversedTypesByLineLength',
+                      modifiers: ['type'],
+                      type: 'line-length',
+                      order: 'desc',
+                    },
+                  ],
+                  groups: ['reversedTypesByLineLength', 'unknown'],
+                  type: 'alphabetical',
+                  order: 'asc',
+                },
+              ],
+              output: dedent`
+                import {
+                  type dddd,
+                  type ccc,
+                  type eee,
+                  type bb,
+                  type ff,
+                  type a,
+                  type g,
+                  m,
+                  o,
+                  p,
+                } from 'module'
+              `,
+              code: dedent`
+                import {
+                  type a,
+                  type bb,
+                  type ccc,
+                  type dddd,
+                  m,
+                  type eee,
+                  type ff,
+                  type g,
+                  o,
+                  p,
+                } from 'module'
+              `,
+            },
+          ],
+          valid: [],
+        },
+      )
+
+      ruleTester.run(
+        `${ruleName}: sort custom groups by overriding 'fallbackSort'`,
+        rule,
+        {
+          invalid: [
+            {
+              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: 'fooBar',
+                    left: 'fooZar',
+                  },
+                  messageId: 'unexpectedNamedImportsOrder',
+                },
+              ],
+              output: dedent`
+                import {
+                  fooBar,
+                  fooZar,
+                } from 'module'
+              `,
+              code: dedent`
+                import {
+                  fooZar,
+                  fooBar,
+                } from 'module'
+              `,
+            },
+          ],
+          valid: [],
+        },
+      )
+
+      ruleTester.run(
+        `${ruleName}: does not sort custom groups with 'unsorted' type`,
+        rule,
+        {
+          invalid: [
+            {
+              options: [
+                {
+                  customGroups: [
+                    {
+                      groupName: 'unsortedTypes',
+                      modifiers: ['type'],
+                      type: 'unsorted',
+                    },
+                  ],
+                  groups: ['unsortedTypes', 'unknown'],
+                },
+              ],
+              errors: [
+                {
+                  data: {
+                    rightGroup: 'unsortedTypes',
+                    leftGroup: 'unknown',
+                    right: 'c',
+                    left: 'm',
+                  },
+                  messageId: 'unexpectedNamedImportsGroupOrder',
+                },
+              ],
+              output: dedent`
+                import {
+                  type b,
+                  type a,
+                  type d,
+                  type e,
+                  type c,
+                  m,
+                } from 'module'
+              `,
+              code: dedent`
+                import {
+                  type b,
+                  type a,
+                  type d,
+                  type e,
+                  m,
+                  type c,
+                } from 'module'
+              `,
+            },
+          ],
+          valid: [],
+        },
+      )
+
+      ruleTester.run(`${ruleName}: sort custom group blocks`, rule, {
+        invalid: [
+          {
+            options: [
+              {
+                customGroups: [
+                  {
+                    anyOf: [
+                      {
+                        elementNamePattern: 'foo|Foo',
+                        modifiers: ['type'],
+                      },
+                      {
+                        elementNamePattern: 'foo|Foo',
+                      },
+                    ],
+                    groupName: 'elementsIncludingFoo',
+                  },
+                ],
+                groups: ['elementsIncludingFoo', 'unknown'],
+              },
+            ],
+            errors: [
+              {
+                data: {
+                  rightGroup: 'elementsIncludingFoo',
+                  leftGroup: 'unknown',
+                  right: 'cFoo',
+                  left: 'a',
+                },
+                messageId: 'unexpectedNamedImportsGroupOrder',
+              },
+            ],
+            output: dedent`
+              import {
+                type cFoo,
+                foo,
+                type a,
+              } from 'module'
+            `,
+            code: dedent`
+              import {
+                type a,
+                type cFoo,
+                foo,
+              } from 'module'
+            `,
+          },
+        ],
+        valid: [],
+      })
+
+      ruleTester.run(
+        `${ruleName}: allows to use regex for element names in custom groups`,
+        rule,
+        {
+          valid: [
+            {
+              options: [
+                {
+                  customGroups: [
+                    {
+                      elementNamePattern: '^(?!.*Foo).*$',
+                      groupName: 'elementsWithoutFoo',
+                    },
+                  ],
+                  groups: ['unknown', 'elementsWithoutFoo'],
+                  type: 'alphabetical',
+                },
+              ],
+              code: dedent`
+                import {
+                  iHaveFooInMyName,
+                  meTooIHaveFoo,
+                  a,
+                  b,
+                } from 'module'
+              `,
+            },
+          ],
+          invalid: [],
+        },
+      )
+    })
+
+    describe(`${ruleName}: newlinesBetween`, () => {
+      ruleTester.run(
+        `${ruleName}(${type}): removes newlines when never`,
+        rule,
+        {
+          invalid: [
+            {
+              errors: [
+                {
+                  data: {
+                    right: 'y',
+                    left: 'a',
+                  },
+                  messageId: 'extraSpacingBetweenNamedImports',
+                },
+                {
+                  data: {
+                    right: 'b',
+                    left: 'z',
+                  },
+                  messageId: 'unexpectedNamedImportsOrder',
+                },
+                {
+                  data: {
+                    right: 'b',
+                    left: 'z',
+                  },
+                  messageId: 'extraSpacingBetweenNamedImports',
+                },
+              ],
+              options: [
+                {
+                  ...options,
+                  customGroups: [
+                    {
+                      elementNamePattern: 'a',
+                      groupName: 'a',
+                    },
+                  ],
+                  groups: ['a', 'unknown'],
+                  newlinesBetween: 'never',
+                },
+              ],
+              code: dedent`
+                import {
+                    a,
+
+
+                   y,
+                  z,
+
+                      b,
+                } from 'module'
+              `,
+              output: dedent`
+                import {
+                    a,
+                   b,
+                  y,
+                      z,
+                } from 'module'
+              `,
+            },
+          ],
+          valid: [],
+        },
+      )
+
+      ruleTester.run(
+        `${ruleName}(${type}): keeps one newline when always`,
+        rule,
+        {
+          invalid: [
+            {
+              errors: [
+                {
+                  data: {
+                    right: 'z',
+                    left: 'a',
+                  },
+                  messageId: 'extraSpacingBetweenNamedImports',
+                },
+                {
+                  data: {
+                    right: 'y',
+                    left: 'z',
+                  },
+                  messageId: 'unexpectedNamedImportsOrder',
+                },
+                {
+                  data: {
+                    right: 'b',
+                    left: 'y',
+                  },
+                  messageId: 'missedSpacingBetweenNamedImports',
+                },
+              ],
+              options: [
+                {
+                  ...options,
+                  customGroups: [
+                    {
+                      elementNamePattern: 'a',
+                      groupName: 'a',
+                    },
+                    {
+                      elementNamePattern: 'b',
+                      groupName: 'b',
+                    },
+                  ],
+                  groups: ['a', 'unknown', 'b'],
+                  newlinesBetween: 'always',
+                },
+              ],
+              output: dedent`
+                import {
+                    a,
+
+                   y,
+                  z,
+
+                      b,
+                } from 'module'
+              `,
+              code: dedent`
+                import {
+                    a,
+
+
+                   z,
+                  y,
+                      b,
+                } from 'module'
+              `,
+            },
+          ],
+          valid: [],
+        },
+      )
+
+      describe(`${ruleName}(${type}): "newlinesBetween" inside groups`, () => {
+        ruleTester.run(
+          `${ruleName}(${type}): handles "newlinesBetween" between consecutive groups`,
+          rule,
+          {
+            invalid: [
+              {
+                options: [
+                  {
+                    ...options,
+                    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: 'missedSpacingBetweenNamedImports',
+                  },
+                  {
+                    data: {
+                      right: 'c',
+                      left: 'b',
+                    },
+                    messageId: 'extraSpacingBetweenNamedImports',
+                  },
+                  {
+                    data: {
+                      right: 'd',
+                      left: 'c',
+                    },
+                    messageId: 'extraSpacingBetweenNamedImports',
+                  },
+                ],
+                code: dedent`
+                    import {
+                    a,
+                    b,
+
+
+                    c,
+
+                    d,
+
+
+                    e,
+                  } from 'module'
+                `,
+                output: dedent`
+                  import {
+                    a,
+
+                    b,
+
+                    c,
+                    d,
+
+
+                    e,
+                  } from 'module'
+                `,
+              },
+            ],
+            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,
+              {
+                invalid: [
+                  {
+                    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: 'missedSpacingBetweenNamedImports',
+                      },
+                    ],
+                    output: dedent`
+                      import {
+                        a,
+
+                        b,
+                      } from 'module'
+                    `,
+                    code: dedent`
+                      import {
+                        a,
+                        b,
+                      } from 'module'
+                    `,
+                  },
+                ],
+                valid: [],
+              },
+            )
+          }
+
+          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,
+              {
+                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: 'extraSpacingBetweenNamedImports',
+                      },
+                    ],
+                    output: dedent`
+                      import {
+                        a,
+                        b,
+                      } from 'module'
+                    `,
+                    code: dedent`
+                      import {
+                        a,
+
+                        b,
+                      } from 'module'
+                    `,
+                  },
+                ],
+                valid: [],
+              },
+            )
+          }
+
+          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,
+              {
+                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`
+                      import {
+                        a,
+
+                        b,
+                      } from 'module'
+                    `,
+                  },
+                  {
+                    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`
+                      import {
+                        a,
+                        b,
+                      } from 'module'
+                    `,
+                  },
+                ],
+                invalid: [],
+              },
+            )
+          }
+        })
+      })
+
+      ruleTester.run(
+        `${ruleName}(${type}): handles newlines and comment after fixes`,
+        rule,
+        {
+          invalid: [
+            {
+              output: [
+                dedent`
+                  import {
+                    a, // Comment after
+                    b,
+
+                    c,
+                  } from 'module'
+                `,
+                dedent`
+                  import {
+                    a, // Comment after
+
+                    b,
+                    c,
+                  } from 'module'
+                `,
+              ],
+              options: [
+                {
+                  customGroups: [
+                    {
+                      elementNamePattern: 'b|c',
+                      groupName: 'b|c',
+                    },
+                  ],
+                  groups: ['unknown', 'b|c'],
+                  newlinesBetween: 'always',
+                },
+              ],
+              errors: [
+                {
+                  data: {
+                    rightGroup: 'unknown',
+                    leftGroup: 'b|c',
+                    right: 'a',
+                    left: 'b',
+                  },
+                  messageId: 'unexpectedNamedImportsGroupOrder',
+                },
+              ],
+              code: dedent`
+                import {
+                  b,
+                  a, // Comment after
+
+                  c,
+                } from 'module'
+              `,
+            },
+          ],
+          valid: [],
+        },
+      )
+    })
   })
 
   describe(`${ruleName}: sorting by natural order`, () => {
@@ -1822,6 +2673,53 @@ 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: 'missedSpacingBetweenNamedImports',
+            },
+          ],
+          output: dedent`
+            import {
+                b,
+
+                a,
+            } from 'module'
+          `,
+          code: dedent`
+            import {
+                b,
+                a,
+            } from 'module'
+          `,
+        },
+      ],
+      valid: [],
+    })
   })
 
   describe(`${ruleName}: misc`, () => {
diff --git a/test/rules/sort-objects.test.ts b/test/rules/sort-objects.test.ts
index 0979e52..993a10c 100644
--- a/test/rules/sort-objects.test.ts
+++ b/test/rules/sort-objects.test.ts
@@ -748,11 +748,7 @@ describe(ruleName, () => {
                   // ...
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
             {
               code: dedent`
@@ -763,11 +759,7 @@ describe(ruleName, () => {
                   // ...
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
             {
               code: dedent`
@@ -778,11 +770,7 @@ describe(ruleName, () => {
                   // ...
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
           ],
           invalid: [],
@@ -803,11 +791,7 @@ describe(ruleName, () => {
                   // ...
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
             {
               code: dedent`
@@ -818,11 +802,7 @@ describe(ruleName, () => {
                   // ...
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
           ],
           invalid: [],
@@ -843,11 +823,7 @@ describe(ruleName, () => {
                   // ...
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
             {
               code: dedent`
@@ -858,11 +834,7 @@ describe(ruleName, () => {
                   // ...
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
           ],
           invalid: [],
@@ -883,11 +855,7 @@ describe(ruleName, () => {
                   // ...
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
           ],
           invalid: [],
@@ -908,11 +876,7 @@ describe(ruleName, () => {
                   // ...
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
           ],
           invalid: [],
@@ -930,11 +894,7 @@ describe(ruleName, () => {
                 // ...
               }
             `,
-            options: [
-              {
-                ...options,
-              },
-            ],
+            options: [options],
           },
         ],
         invalid: [],
@@ -954,11 +914,7 @@ describe(ruleName, () => {
                   // ...
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
             {
               code: dedent`
@@ -969,11 +925,7 @@ describe(ruleName, () => {
                   // ...
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
           ],
           invalid: [],
@@ -994,11 +946,7 @@ describe(ruleName, () => {
                   // ...
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
             {
               code: dedent`
@@ -1009,11 +957,7 @@ describe(ruleName, () => {
                   // ...
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
             {
               code: dedent`
@@ -1024,11 +968,7 @@ describe(ruleName, () => {
                   // ...
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
           ],
           invalid: [],
@@ -1049,11 +989,7 @@ describe(ruleName, () => {
                   // ...
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
           ],
           invalid: [],
@@ -1074,11 +1010,7 @@ describe(ruleName, () => {
                   // ...
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
           ],
           invalid: [],
@@ -1099,11 +1031,7 @@ describe(ruleName, () => {
                   // ...
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
           ],
           invalid: [],
@@ -1124,11 +1052,7 @@ describe(ruleName, () => {
                   // ...
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
             {
               code: dedent`
@@ -1139,11 +1063,7 @@ describe(ruleName, () => {
                   // ...
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
             {
               code: dedent`
@@ -1154,11 +1074,7 @@ describe(ruleName, () => {
                   // ...
                 }
               `,
-              options: [
-                {
-                  ...options,
-                },
-              ],
+              options: [options],
             },
           ],
           invalid: [],
@@ -5465,11 +5381,7 @@ describe(ruleName, () => {
               // ...
             }
           `,
-          options: [
-            {
-              ...options,
-            },
-          ],
+          options: [options],
         },
       ],
       valid: [],
diff --git a/test/rules/sort-variable-declarations.test.ts b/test/rules/sort-variable-declarations.test.ts
index 961e762..da52e8c 100644
--- a/test/rules/sort-variable-declarations.test.ts
+++ b/test/rules/sort-variable-declarations.test.ts
@@ -318,37 +318,25 @@ describe(ruleName, () => {
       {
         valid: [
           {
-            options: [
-              {
-                ...options,
-              },
-            ],
             code: dedent`
               let b = () => 1,
               a = b();
             `,
+            options: [options],
           },
           {
             code: dedent`
               let b = function() { return 1 },
               a = b();
             `,
-            options: [
-              {
-                ...options,
-              },
-            ],
+            options: [options],
           },
           {
             code: dedent`
               let b = () => 1,
               a = a.map(b);
             `,
-            options: [
-              {
-                ...options,
-              },
-            ],
+            options: [options],
           },
         ],
         invalid: [],
@@ -361,26 +349,18 @@ describe(ruleName, () => {
       {
         valid: [
           {
-            options: [
-              {
-                ...options,
-              },
-            ],
             code: dedent`
               let b = 1,
               a = {x: b};
             `,
+            options: [options],
           },
           {
-            options: [
-              {
-                ...options,
-              },
-            ],
             code: dedent`
               let b = 1,
               a = {[b]: 0};
             `,
+            options: [options],
           },
         ],
         invalid: [],
@@ -390,26 +370,18 @@ describe(ruleName, () => {
     ruleTester.run(`${ruleName}(${type}) detects chained dependencies`, rule, {
       valid: [
         {
-          options: [
-            {
-              ...options,
-            },
-          ],
           code: dedent`
             let b = {x: 1},
             a = b.x;
           `,
+          options: [options],
         },
         {
           code: dedent`
             let b = new Subject(),
             a = b.asObservable();
           `,
-          options: [
-            {
-              ...options,
-            },
-          ],
+          options: [options],
         },
       ],
       invalid: [],
@@ -421,15 +393,11 @@ describe(ruleName, () => {
       {
         valid: [
           {
-            options: [
-              {
-                ...options,
-              },
-            ],
             code: dedent`
               let b = {x: 1},
               a = b?.x;
             `,
+            options: [options],
           },
         ],
         invalid: [],
@@ -442,15 +410,11 @@ describe(ruleName, () => {
       {
         valid: [
           {
-            options: [
-              {
-                ...options,
-              },
-            ],
             code: dedent`
               let b = 1,
               a = b!;
             `,
+            options: [options],
           },
         ],
         invalid: [],
@@ -460,15 +424,11 @@ describe(ruleName, () => {
     ruleTester.run(`${ruleName}(${type}) detects unary dependencies`, rule, {
       valid: [
         {
-          options: [
-            {
-              ...options,
-            },
-          ],
           code: dedent`
             let b = true,
             a = !b;
           `,
+          options: [options],
         },
       ],
       invalid: [],
@@ -480,26 +440,18 @@ describe(ruleName, () => {
       {
         valid: [
           {
-            options: [
-              {
-                ...options,
-              },
-            ],
             code: dedent`
               let b = {x: 1},
               a = {...b};
             `,
+            options: [options],
           },
           {
-            options: [
-              {
-                ...options,
-              },
-            ],
             code: dedent`
               let b = [1]
               a = [...b];
             `,
+            options: [options],
           },
         ],
         invalid: [],
@@ -512,37 +464,25 @@ describe(ruleName, () => {
       {
         valid: [
           {
-            options: [
-              {
-                ...options,
-              },
-            ],
             code: dedent`
               let b = 0,
               a = b ? 1 : 0;
             `,
+            options: [options],
           },
           {
-            options: [
-              {
-                ...options,
-              },
-            ],
             code: dedent`
               let b = 0,
               a = x ? b : 0;
             `,
+            options: [options],
           },
           {
-            options: [
-              {
-                ...options,
-              },
-            ],
             code: dedent`
               let b = 0,
               a = x ? 0 : b;
             `,
+            options: [options],
           },
         ],
         invalid: [],
@@ -555,15 +495,11 @@ describe(ruleName, () => {
       {
         valid: [
           {
-            options: [
-              {
-                ...options,
-              },
-            ],
             code: dedent`
               let b = 'b',
               a = b as any;
             `,
+            options: [options],
           },
         ],
         invalid: [],
@@ -576,15 +512,11 @@ describe(ruleName, () => {
       {
         valid: [
           {
-            options: [
-              {
-                ...options,
-              },
-            ],
             code: dedent`
               let b = 'b',
               a = <any>b;
             `,
+            options: [options],
           },
         ],
         invalid: [],
@@ -597,15 +529,11 @@ describe(ruleName, () => {
       {
         valid: [
           {
-            options: [
-              {
-                ...options,
-              },
-            ],
             code: dedent`
               let b = 'b',
               a = \`\${b}\`
             `,
+            options: [options],
           },
         ],
         invalid: [],
@@ -618,37 +546,25 @@ describe(ruleName, () => {
       {
         valid: [
           {
-            options: [
-              {
-                ...options,
-              },
-            ],
             code: dedent`
               let a = () => b,
               b = 1;
             `,
+            options: [options],
           },
           {
             code: dedent`
               let a = function() { return b },
               b = 1;
             `,
-            options: [
-              {
-                ...options,
-              },
-            ],
+            options: [options],
           },
           {
             code: dedent`
               let a = () => {return b},
               b = 1;
             `,
-            options: [
-              {
-                ...options,
-              },
-            ],
+            options: [options],
           },
         ],
         invalid: [],
@@ -1750,11 +1666,7 @@ describe(ruleName, () => {
               a = b,
               b = 1,
           `,
-          options: [
-            {
-              ...options,
-            },
-          ],
+          options: [options],
         },
       ],
       valid: [],