Repository: azat-io/eslint-plugin-perfectionist
Pull request #448 (2025-01-25 14:11:46)

refactor: creates additional common options
### Description

This PR:
- Puts all error messages in common in the same file.
- Refactors types to avoid duplicating them in the codebase.
  - `NewlinesBetweenOption`
  - `SpecialCharacterOption`
  - `TypeOption`
  - `OrderOption`
  - `CustomGroupsOption`


### What is the purpose of this pull request?

- [x] Other



Patch:
diff --git a/rules/sort-array-includes.ts b/rules/sort-array-includes.ts
index 9154c5e..d934546 100644
--- a/rules/sort-array-includes.ts
+++ b/rules/sort-array-includes.ts
@@ -16,6 +16,12 @@ import {
   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 { validateGeneratedGroupsConfiguration } from '../utils/validate-generated-groups-configuration'
 import { validateCustomSortConfiguration } from '../utils/validate-custom-sort-configuration'
@@ -128,14 +134,10 @@ export default createEslintRule<Options, MESSAGE_ID>({
   }),
   meta: {
     messages: {
-      unexpectedArrayIncludesGroupOrder:
-        'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
-      missedSpacingBetweenArrayIncludesMembers:
-        'Missed spacing between "{{left}}" and "{{right}}" members.',
-      extraSpacingBetweenArrayIncludesMembers:
-        'Extra spacing between "{{left}}" and "{{right}}" members.',
-      unexpectedArrayIncludesOrder:
-        'Expected "{{right}}" to come before "{{left}}".',
+      missedSpacingBetweenArrayIncludesMembers: MISSED_SPACING_ERROR,
+      extraSpacingBetweenArrayIncludesMembers: EXTRA_SPACING_ERROR,
+      unexpectedArrayIncludesGroupOrder: GROUP_ORDER_ERROR,
+      unexpectedArrayIncludesOrder: ORDER_ERROR,
     },
     docs: {
       description: 'Enforce sorted arrays before include method.',
@@ -193,10 +195,9 @@ export let sortArray = <MessageIds extends string>({
   }
   validateCustomSortConfiguration(options)
   validateGeneratedGroupsConfiguration({
-    customGroups: options.customGroups,
     selectors: allSelectors,
-    groups: options.groups,
     modifiers: [],
+    options,
   })
   validateNewlinesAndPartitionConfiguration(options)
 
diff --git a/rules/sort-array-includes/does-custom-group-match.ts b/rules/sort-array-includes/does-custom-group-match.ts
index 801df37..d8deabe 100644
--- a/rules/sort-array-includes/does-custom-group-match.ts
+++ b/rules/sort-array-includes/does-custom-group-match.ts
@@ -1,22 +1,23 @@
-import type { SingleCustomGroup, AnyOfCustomGroup, Selector } from './types'
+import type { AnyOfCustomGroup } from '../../types/common-options'
+import type { SingleCustomGroup, Selector } from './types'
 
 import { matches } from '../../utils/matches'
 
-interface DoesCustomGroupMatchProps {
-  customGroup: SingleCustomGroup | AnyOfCustomGroup
+interface DoesCustomGroupMatchParameters {
+  customGroup: AnyOfCustomGroup<SingleCustomGroup> | SingleCustomGroup
   selectors: Selector[]
   elementName: string
 }
 
 /**
  * Determines whether a custom group matches the given properties.
- * @param {DoesCustomGroupMatchProps} props - The properties to compare with the
- * custom group, including selectors, modifiers, and element name.
+ * @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: DoesCustomGroupMatchProps,
+  props: DoesCustomGroupMatchParameters,
 ): boolean => {
   if ('anyOf' in props.customGroup) {
     // At least one subgroup must match
diff --git a/rules/sort-array-includes/types.ts b/rules/sort-array-includes/types.ts
index 105938d..5873b90 100644
--- a/rules/sort-array-includes/types.ts
+++ b/rules/sort-array-includes/types.ts
@@ -2,8 +2,11 @@ import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema'
 
 import type {
   PartitionByCommentOption,
+  NewlinesBetweenOption,
+  CustomGroupsOption,
   CommonOptions,
   GroupsOptions,
+  TypeOption,
 } from '../../types/common-options'
 
 import {
@@ -13,7 +16,6 @@ import {
 
 export type Options = Partial<
   {
-    type: 'alphabetical' | 'line-length' | 'unsorted' | 'natural' | 'custom'
     useConfigurationIf: {
       allNamesMatchPattern?: string
     }
@@ -21,10 +23,11 @@ export type Options = Partial<
      * @deprecated for {@link `groups`}
      */
     groupKind: 'literals-first' | 'spreads-first' | 'mixed'
-    newlinesBetween: 'ignore' | 'always' | 'never'
+    customGroups: CustomGroupsOption<SingleCustomGroup>
     partitionByComment: PartitionByCommentOption
+    newlinesBetween: NewlinesBetweenOption
+    type: TypeOption | 'unsorted'
     groups: GroupsOptions<Group>
-    customGroups: CustomGroup[]
     partitionByNewLine: boolean
   } & CommonOptions
 >[]
@@ -34,25 +37,8 @@ export interface SingleCustomGroup {
   selector?: Selector
 }
 
-export interface AnyOfCustomGroup {
-  anyOf: SingleCustomGroup[]
-}
-
 export type Selector = LiteralSelector | SpreadSelector
 
-type CustomGroup = (
-  | {
-      order?: Options[0]['order']
-      type?: Options[0]['type']
-    }
-  | {
-      type?: 'unsorted'
-    }
-) &
-  (SingleCustomGroup | AnyOfCustomGroup) & {
-    groupName: string
-  }
-
 type LiteralSelector = 'literal'
 
 type Group = 'unknown' | string
diff --git a/rules/sort-classes.ts b/rules/sort-classes.ts
index dbe8822..2bbaad2 100644
--- a/rules/sort-classes.ts
+++ b/rules/sort-classes.ts
@@ -16,6 +16,13 @@ import {
   commonJsonSchemas,
   groupsJsonSchema,
 } from '../utils/common-json-schemas'
+import {
+  DEPENDENCY_ORDER_ERROR,
+  MISSED_SPACING_ERROR,
+  EXTRA_SPACING_ERROR,
+  GROUP_ORDER_ERROR,
+  ORDER_ERROR,
+} from '../utils/report-errors'
 import { validateNewlinesAndPartitionConfiguration } from '../utils/validate-newlines-and-partition-configuration'
 import { validateGeneratedGroupsConfiguration } from '../utils/validate-generated-groups-configuration'
 import {
@@ -105,10 +112,9 @@ export default createEslintRule<SortClassesOptions, MESSAGE_ID>({
       let options = complete(context.options.at(0), settings, defaultOptions)
       validateCustomSortConfiguration(options)
       validateGeneratedGroupsConfiguration({
-        customGroups: options.customGroups,
         modifiers: allModifiers,
         selectors: allSelectors,
-        groups: options.groups,
+        options,
       })
       validateNewlinesAndPartitionConfiguration(options)
 
@@ -655,15 +661,11 @@ export default createEslintRule<SortClassesOptions, MESSAGE_ID>({
       },
     ],
     messages: {
-      unexpectedClassesGroupOrder:
-        'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
-      unexpectedClassesDependencyOrder:
-        'Expected dependency "{{right}}" to come before "{{nodeDependentOnRight}}".',
-      missedSpacingBetweenClassMembers:
-        'Missed spacing between "{{left}}" and "{{right}}" objects.',
-      extraSpacingBetweenClassMembers:
-        'Extra spacing between "{{left}}" and "{{right}}" objects.',
-      unexpectedClassesOrder: 'Expected "{{right}}" to come before "{{left}}".',
+      unexpectedClassesDependencyOrder: DEPENDENCY_ORDER_ERROR,
+      missedSpacingBetweenClassMembers: MISSED_SPACING_ERROR,
+      extraSpacingBetweenClassMembers: EXTRA_SPACING_ERROR,
+      unexpectedClassesGroupOrder: GROUP_ORDER_ERROR,
+      unexpectedClassesOrder: ORDER_ERROR,
     },
     docs: {
       url: 'https://perfectionist.dev/rules/sort-classes',
diff --git a/rules/sort-classes/does-custom-group-match.ts b/rules/sort-classes/does-custom-group-match.ts
index 55d65ac..72c1295 100644
--- a/rules/sort-classes/does-custom-group-match.ts
+++ b/rules/sort-classes/does-custom-group-match.ts
@@ -1,14 +1,10 @@
-import type {
-  SingleCustomGroup,
-  AnyOfCustomGroup,
-  Modifier,
-  Selector,
-} from './types'
+import type { SingleCustomGroup, Modifier, Selector } from './types'
+import type { AnyOfCustomGroup } from '../../types/common-options'
 
 import { matches } from '../../utils/matches'
 
-interface DoesCustomGroupMatchProps {
-  customGroup: SingleCustomGroup | AnyOfCustomGroup
+interface DoesCustomGroupMatchParameters {
+  customGroup: AnyOfCustomGroup<SingleCustomGroup> | SingleCustomGroup
   elementValue: undefined | string
   selectors: Selector[]
   modifiers: Modifier[]
@@ -18,14 +14,14 @@ interface DoesCustomGroupMatchProps {
 
 /**
  * Determines whether a custom group matches the given properties.
- * @param {DoesCustomGroupMatchProps} props - The properties to match against
- * the custom group, including selectors, modifiers, decorators, and element
- * names.
+ * @param {DoesCustomGroupMatchParameters} props - The properties to match
+ * against the custom group, including selectors, modifiers, decorators, and
+ * element names.
  * @returns {boolean} `true` if the custom group matches the properties;
  * otherwise, `false`.
  */
 export let doesCustomGroupMatch = (
-  props: DoesCustomGroupMatchProps,
+  props: DoesCustomGroupMatchParameters,
 ): boolean => {
   if ('anyOf' in props.customGroup) {
     // At least one subgroup must match.
diff --git a/rules/sort-classes/types.ts b/rules/sort-classes/types.ts
index d28e7e4..133af4b 100644
--- a/rules/sort-classes/types.ts
+++ b/rules/sort-classes/types.ts
@@ -2,8 +2,11 @@ import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema'
 
 import type {
   PartitionByCommentOption,
+  NewlinesBetweenOption,
+  CustomGroupsOption,
   CommonOptions,
   GroupsOptions,
+  TypeOption,
 } from '../../types/common-options'
 import type { JoinWithDash } from '../../types/join-with-dash'
 
@@ -28,30 +31,17 @@ export type SingleCustomGroup =
 export type SortClassesOptions = [
   Partial<
     {
-      type: 'alphabetical' | 'line-length' | 'natural' | 'custom'
-      newlinesBetween: 'ignore' | 'always' | 'never'
+      customGroups: CustomGroupsOption<SingleCustomGroup>
       partitionByComment: PartitionByCommentOption
       ignoreCallbackDependenciesPatterns: string[]
+      newlinesBetween: NewlinesBetweenOption
       groups: GroupsOptions<Group>
       partitionByNewLine: boolean
-      customGroups: CustomGroup[]
+      type: TypeOption
     } & CommonOptions
   >,
 ]
 
-export type CustomGroup = (
-  | {
-      order?: SortClassesOptions[0]['order']
-      type?: SortClassesOptions[0]['type']
-    }
-  | {
-      type?: 'unsorted'
-    }
-) & {
-  newlinesInside?: 'always' | 'never'
-  groupName: string
-} & (SingleCustomGroup | AnyOfCustomGroup)
-
 export type NonDeclarePropertyGroup = JoinWithDash<
   [
     PublicOrProtectedOrPrivateModifier,
@@ -149,10 +139,6 @@ export type ConstructorGroup = JoinWithDash<
   [PublicOrProtectedOrPrivateModifier, ConstructorSelector]
 >
 
-export interface AnyOfCustomGroup {
-  anyOf: SingleCustomGroup[]
-}
-
 /**
  * Only used in code as well
  */
diff --git a/rules/sort-decorators.ts b/rules/sort-decorators.ts
index 5b0ceb5..e8d0c68 100644
--- a/rules/sort-decorators.ts
+++ b/rules/sort-decorators.ts
@@ -4,6 +4,7 @@ import type { TSESTree } from '@typescript-eslint/types'
 import type {
   PartitionByCommentOption,
   CommonOptions,
+  TypeOption,
 } from '../types/common-options'
 import type { SortingNode } from '../types/sorting-node'
 
@@ -19,6 +20,7 @@ import { validateGroupsConfiguration } from '../utils/validate-groups-configurat
 import { getEslintDisabledLines } from '../utils/get-eslint-disabled-lines'
 import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled'
 import { getDecoratorName } from './sort-decorators/get-decorator-name'
+import { GROUP_ORDER_ERROR, ORDER_ERROR } from '../utils/report-errors'
 import { sortNodesByGroups } from '../utils/sort-nodes-by-groups'
 import { getNodeDecorators } from '../utils/get-node-decorators'
 import { createEslintRule } from '../utils/create-eslint-rule'
@@ -34,7 +36,6 @@ import { complete } from '../utils/complete'
 export type Options<T extends string = string> = [
   Partial<
     {
-      type: 'alphabetical' | 'line-length' | 'natural' | 'custom'
       partitionByComment: PartitionByCommentOption
       customGroups: Record<T, string[] | string>
       groups: (Group<T>[] | Group<T>)[]
@@ -43,6 +44,7 @@ export type Options<T extends string = string> = [
       sortOnAccessors: boolean
       sortOnMethods: boolean
       sortOnClasses: boolean
+      type: TypeOption
     } & CommonOptions
   >,
 ]
@@ -71,59 +73,6 @@ let defaultOptions: Required<Options[0]> = {
 }
 
 export default createEslintRule<Options, MESSAGE_ID>({
-  meta: {
-    schema: [
-      {
-        properties: {
-          ...commonJsonSchemas,
-          sortOnParameters: {
-            description:
-              'Controls whether sorting should be enabled for method parameter decorators.',
-            type: 'boolean',
-          },
-          sortOnProperties: {
-            description:
-              'Controls whether sorting should be enabled for class property decorators.',
-            type: 'boolean',
-          },
-          sortOnAccessors: {
-            description:
-              'Controls whether sorting should be enabled for class accessor decorators.',
-            type: 'boolean',
-          },
-          sortOnMethods: {
-            description:
-              'Controls whether sorting should be enabled for class method decorators.',
-            type: 'boolean',
-          },
-          sortOnClasses: {
-            description:
-              'Controls whether sorting should be enabled for class decorators.',
-            type: 'boolean',
-          },
-          partitionByComment: partitionByCommentJsonSchema,
-          customGroups: customGroupsJsonSchema,
-          type: buildTypeJsonSchema(),
-          groups: groupsJsonSchema,
-        },
-        additionalProperties: false,
-        type: 'object',
-      },
-    ],
-    messages: {
-      unexpectedDecoratorsGroupOrder:
-        'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
-      unexpectedDecoratorsOrder:
-        'Expected "{{right}}" to come before "{{left}}".',
-    },
-    docs: {
-      url: 'https://perfectionist.dev/rules/sort-decorators',
-      description: 'Enforce sorted decorators.',
-      recommended: true,
-    },
-    type: 'suggestion',
-    fixable: 'code',
-  },
   create: context => {
     let settings = getSettings(context.settings)
 
@@ -182,6 +131,57 @@ export default createEslintRule<Options, MESSAGE_ID>({
           : null,
     }
   },
+  meta: {
+    schema: [
+      {
+        properties: {
+          ...commonJsonSchemas,
+          sortOnParameters: {
+            description:
+              'Controls whether sorting should be enabled for method parameter decorators.',
+            type: 'boolean',
+          },
+          sortOnProperties: {
+            description:
+              'Controls whether sorting should be enabled for class property decorators.',
+            type: 'boolean',
+          },
+          sortOnAccessors: {
+            description:
+              'Controls whether sorting should be enabled for class accessor decorators.',
+            type: 'boolean',
+          },
+          sortOnMethods: {
+            description:
+              'Controls whether sorting should be enabled for class method decorators.',
+            type: 'boolean',
+          },
+          sortOnClasses: {
+            description:
+              'Controls whether sorting should be enabled for class decorators.',
+            type: 'boolean',
+          },
+          partitionByComment: partitionByCommentJsonSchema,
+          customGroups: customGroupsJsonSchema,
+          type: buildTypeJsonSchema(),
+          groups: groupsJsonSchema,
+        },
+        additionalProperties: false,
+        type: 'object',
+      },
+    ],
+    docs: {
+      url: 'https://perfectionist.dev/rules/sort-decorators',
+      description: 'Enforce sorted decorators.',
+      recommended: true,
+    },
+    messages: {
+      unexpectedDecoratorsGroupOrder: GROUP_ORDER_ERROR,
+      unexpectedDecoratorsOrder: ORDER_ERROR,
+    },
+    type: 'suggestion',
+    fixable: 'code',
+  },
   defaultOptions: [defaultOptions],
   name: 'sort-decorators',
 })
diff --git a/rules/sort-enums.ts b/rules/sort-enums.ts
index d93a1d3..0320a45 100644
--- a/rules/sort-enums.ts
+++ b/rules/sort-enums.ts
@@ -14,6 +14,13 @@ import {
   commonJsonSchemas,
   groupsJsonSchema,
 } from '../utils/common-json-schemas'
+import {
+  DEPENDENCY_ORDER_ERROR,
+  MISSED_SPACING_ERROR,
+  EXTRA_SPACING_ERROR,
+  GROUP_ORDER_ERROR,
+  ORDER_ERROR,
+} from '../utils/report-errors'
 import { validateNewlinesAndPartitionConfiguration } from '../utils/validate-newlines-and-partition-configuration'
 import { validateGeneratedGroupsConfiguration } from '../utils/validate-generated-groups-configuration'
 import { validateCustomSortConfiguration } from '../utils/validate-custom-sort-configuration'
@@ -78,10 +85,9 @@ export default createEslintRule<Options, MESSAGE_ID>({
       let options = complete(context.options.at(0), settings, defaultOptions)
       validateCustomSortConfiguration(options)
       validateGeneratedGroupsConfiguration({
-        customGroups: options.customGroups,
-        groups: options.groups,
         selectors: [],
         modifiers: [],
+        options,
       })
       validateNewlinesAndPartitionConfiguration(options)
 
@@ -289,15 +295,11 @@ export default createEslintRule<Options, MESSAGE_ID>({
       },
     ],
     messages: {
-      unexpectedEnumsGroupOrder:
-        'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
-      unexpectedEnumsDependencyOrder:
-        'Expected dependency "{{right}}" to come before "{{nodeDependentOnRight}}".',
-      missedSpacingBetweenEnumsMembers:
-        'Missed spacing between "{{left}}" and "{{right}}" members.',
-      extraSpacingBetweenEnumsMembers:
-        'Extra spacing between "{{left}}" and "{{right}}" members.',
-      unexpectedEnumsOrder: 'Expected "{{right}}" to come before "{{left}}".',
+      unexpectedEnumsDependencyOrder: DEPENDENCY_ORDER_ERROR,
+      missedSpacingBetweenEnumsMembers: MISSED_SPACING_ERROR,
+      extraSpacingBetweenEnumsMembers: EXTRA_SPACING_ERROR,
+      unexpectedEnumsGroupOrder: GROUP_ORDER_ERROR,
+      unexpectedEnumsOrder: ORDER_ERROR,
     },
     docs: {
       url: 'https://perfectionist.dev/rules/sort-enums',
diff --git a/rules/sort-enums/does-custom-group-match.ts b/rules/sort-enums/does-custom-group-match.ts
index a5392c3..5fb5e3d 100644
--- a/rules/sort-enums/does-custom-group-match.ts
+++ b/rules/sort-enums/does-custom-group-match.ts
@@ -1,9 +1,10 @@
-import type { SingleCustomGroup, AnyOfCustomGroup } from './types'
+import type { AnyOfCustomGroup } from '../../types/common-options'
+import type { SingleCustomGroup } from './types'
 
 import { matches } from '../../utils/matches'
 
 interface DoesCustomGroupMatchProps {
-  customGroup: SingleCustomGroup | AnyOfCustomGroup
+  customGroup: AnyOfCustomGroup<SingleCustomGroup> | SingleCustomGroup
   elementValue: string
   elementName: string
 }
diff --git a/rules/sort-enums/types.ts b/rules/sort-enums/types.ts
index e083605..462413d 100644
--- a/rules/sort-enums/types.ts
+++ b/rules/sort-enums/types.ts
@@ -2,8 +2,11 @@ import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema'
 
 import type {
   PartitionByCommentOption,
+  NewlinesBetweenOption,
+  CustomGroupsOption,
   CommonOptions,
   GroupsOptions,
+  TypeOption,
 } from '../../types/common-options'
 
 import {
@@ -13,14 +16,14 @@ import {
 
 export type Options = Partial<
   {
-    type: 'alphabetical' | 'line-length' | 'natural' | 'custom'
-    newlinesBetween: 'ignore' | 'always' | 'never'
+    customGroups: CustomGroupsOption<SingleCustomGroup>
     partitionByComment: PartitionByCommentOption
+    newlinesBetween: NewlinesBetweenOption
     groups: GroupsOptions<Group>
     partitionByNewLine: boolean
-    customGroups: CustomGroup[]
     forceNumericSort: boolean
     sortByValue: boolean
+    type: TypeOption
   } & CommonOptions
 >[]
 
@@ -29,23 +32,6 @@ export interface SingleCustomGroup {
   elementNamePattern?: string
 }
 
-export interface AnyOfCustomGroup {
-  anyOf: SingleCustomGroup[]
-}
-
-type CustomGroup = (
-  | {
-      order?: Options[0]['order']
-      type?: Options[0]['type']
-    }
-  | {
-      type?: 'unsorted'
-    }
-) &
-  (SingleCustomGroup | AnyOfCustomGroup) & {
-    groupName: string
-  }
-
 type Group = 'unknown' | string
 
 export let singleCustomGroupJsonSchema: Record<string, JSONSchema4> = {
diff --git a/rules/sort-exports.ts b/rules/sort-exports.ts
index 5216c40..0dc0910 100644
--- a/rules/sort-exports.ts
+++ b/rules/sort-exports.ts
@@ -3,6 +3,7 @@ import type { TSESTree } from '@typescript-eslint/types'
 import type {
   PartitionByCommentOption,
   CommonOptions,
+  TypeOption,
 } from '../types/common-options'
 import type { SortingNode } from '../types/sorting-node'
 
@@ -20,6 +21,7 @@ import { reportAllErrors } from '../utils/report-all-errors'
 import { shouldPartition } from '../utils/should-partition'
 import { getSourceCode } from '../utils/get-source-code'
 import { rangeToDiff } from '../utils/range-to-diff'
+import { ORDER_ERROR } from '../utils/report-errors'
 import { getSettings } from '../utils/get-settings'
 import { sortNodes } from '../utils/sort-nodes'
 import { complete } from '../utils/complete'
@@ -27,10 +29,10 @@ import { complete } from '../utils/complete'
 type Options = [
   Partial<
     {
-      type: 'alphabetical' | 'line-length' | 'natural' | 'custom'
       groupKind: 'values-first' | 'types-first' | 'mixed'
       partitionByComment: PartitionByCommentOption
       partitionByNewLine: boolean
+      type: TypeOption
     } & CommonOptions
   >,
 ]
@@ -171,7 +173,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
       recommended: true,
     },
     messages: {
-      unexpectedExportsOrder: 'Expected "{{right}}" to come before "{{left}}".',
+      unexpectedExportsOrder: ORDER_ERROR,
     },
     type: 'suggestion',
     fixable: 'code',
diff --git a/rules/sort-heritage-clauses.ts b/rules/sort-heritage-clauses.ts
index fddce00..c9fbc81 100644
--- a/rules/sort-heritage-clauses.ts
+++ b/rules/sort-heritage-clauses.ts
@@ -1,7 +1,7 @@
 import type { RuleContext } from '@typescript-eslint/utils/ts-eslint'
 import type { TSESTree } from '@typescript-eslint/types'
 
-import type { CommonOptions } from '../types/common-options'
+import type { CommonOptions, TypeOption } from '../types/common-options'
 import type { SortingNode } from '../types/sorting-node'
 
 import {
@@ -14,6 +14,7 @@ import { validateCustomSortConfiguration } from '../utils/validate-custom-sort-c
 import { validateGroupsConfiguration } from '../utils/validate-groups-configuration'
 import { getEslintDisabledLines } from '../utils/get-eslint-disabled-lines'
 import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled'
+import { GROUP_ORDER_ERROR, ORDER_ERROR } from '../utils/report-errors'
 import { sortNodesByGroups } from '../utils/sort-nodes-by-groups'
 import { createEslintRule } from '../utils/create-eslint-rule'
 import { reportAllErrors } from '../utils/report-all-errors'
@@ -27,9 +28,9 @@ import { complete } from '../utils/complete'
 export type Options<T extends string = string> = [
   Partial<
     {
-      type: 'alphabetical' | 'line-length' | 'natural' | 'custom'
       customGroups: Record<T, string[] | string>
       groups: (Group<T>[] | Group<T>)[]
+      type: TypeOption
     } & CommonOptions
   >,
 ]
@@ -65,17 +66,15 @@ export default createEslintRule<Options, MESSAGE_ID>({
         type: 'object',
       },
     ],
-    messages: {
-      unexpectedHeritageClausesGroupOrder:
-        'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
-      unexpectedHeritageClausesOrder:
-        'Expected "{{right}}" to come before "{{left}}".',
-    },
     docs: {
       url: 'https://perfectionist.dev/rules/sort-heritage-clauses',
       description: 'Enforce sorted heritage clauses.',
       recommended: true,
     },
+    messages: {
+      unexpectedHeritageClausesGroupOrder: GROUP_ORDER_ERROR,
+      unexpectedHeritageClausesOrder: ORDER_ERROR,
+    },
     type: 'suggestion',
     fixable: 'code',
   },
diff --git a/rules/sort-imports.ts b/rules/sort-imports.ts
index 57a5904..465221d 100644
--- a/rules/sort-imports.ts
+++ b/rules/sort-imports.ts
@@ -4,7 +4,11 @@ import { builtinModules } from 'node:module'
 
 import type {
   PartitionByCommentOption,
+  SpecialCharactersOption,
+  NewlinesBetweenOption,
   GroupsOptions,
+  OrderOption,
+  TypeOption,
 } from '../types/common-options'
 import type { SortingNode } from '../types/sorting-node'
 
@@ -16,6 +20,12 @@ import {
   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 { validateCustomSortConfiguration } from '../utils/validate-custom-sort-configuration'
 import { readClosestTsConfigByPath } from './sort-imports/read-closest-ts-config-by-path'
@@ -42,11 +52,10 @@ export type Options<T extends string = string> = [
       value?: Record<T, string[] | string>
       type?: Record<T, string[] | string>
     }
-    type: 'alphabetical' | 'line-length' | 'natural' | 'custom'
-    newlinesBetween: 'ignore' | 'always' | 'never'
-    specialCharacters: 'remove' | 'trim' | 'keep'
     partitionByComment: PartitionByCommentOption
+    specialCharacters: SpecialCharactersOption
     locales: NonNullable<Intl.LocalesArgument>
+    newlinesBetween: NewlinesBetweenOption
     groups: GroupsOptions<Group<T>>
     environment: 'node' | 'bun'
     partitionByNewLine: boolean
@@ -54,8 +63,9 @@ export type Options<T extends string = string> = [
     sortSideEffects: boolean
     tsconfigRootDir?: string
     maxLineLength?: number
-    order: 'desc' | 'asc'
     ignoreCase: boolean
+    order: OrderOption
+    type: TypeOption
     alphabet: string
   }>,
 ]
@@ -164,7 +174,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
 
     let isSideEffectOnlyGroup = (
       group:
-        | { newlinesBetween: 'ignore' | 'always' | 'never' }
+        | { newlinesBetween: NewlinesBetweenOption }
         | undefined
         | string[]
         | string,
@@ -623,13 +633,10 @@ export default createEslintRule<Options, MESSAGE_ID>({
       },
     ],
     messages: {
-      unexpectedImportsGroupOrder:
-        'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
-      missedSpacingBetweenImports:
-        'Missed spacing between "{{left}}" and "{{right}}" imports.',
-      extraSpacingBetweenImports:
-        'Extra spacing between "{{left}}" and "{{right}}" imports.',
-      unexpectedImportsOrder: 'Expected "{{right}}" to come before "{{left}}".',
+      missedSpacingBetweenImports: MISSED_SPACING_ERROR,
+      extraSpacingBetweenImports: EXTRA_SPACING_ERROR,
+      unexpectedImportsGroupOrder: GROUP_ORDER_ERROR,
+      unexpectedImportsOrder: ORDER_ERROR,
     },
     docs: {
       url: 'https://perfectionist.dev/rules/sort-imports',
diff --git a/rules/sort-imports/read-closest-ts-config-by-path.ts b/rules/sort-imports/read-closest-ts-config-by-path.ts
index abdeac0..b6b1496 100644
--- a/rules/sort-imports/read-closest-ts-config-by-path.ts
+++ b/rules/sort-imports/read-closest-ts-config-by-path.ts
@@ -10,23 +10,26 @@ import { getTypescriptImport } from './get-typescript-import'
  * https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts
  */
 
-interface OutputProps {
+interface ReadClosestTsConfigByPathValue {
   compilerOptions: ts.CompilerOptions
   cache: ts.ModuleResolutionCache
 }
 
-interface InputProps {
+interface ReadClosestTsConfigByPathParameters {
   tsconfigRootDir: string
   contextCwd: string
   filePath: string
 }
 
 export let directoryCacheByPath = new Map<string, string>()
-export let contentCacheByPath = new Map<string, OutputProps>()
+export let contentCacheByPath = new Map<
+  string,
+  ReadClosestTsConfigByPathValue
+>()
 
 export let readClosestTsConfigByPath = (
-  input: InputProps,
-): OutputProps | null => {
+  input: ReadClosestTsConfigByPathParameters,
+): ReadClosestTsConfigByPathValue | null => {
   let typescriptImport = getTypescriptImport()
   if (!typescriptImport) {
     return null
@@ -69,7 +72,7 @@ let getCompilerOptions = (
   typescriptImport: typeof ts,
   contextCwd: string,
   filePath: string,
-): OutputProps => {
+): ReadClosestTsConfigByPathValue => {
   if (contentCacheByPath.has(filePath)) {
     return contentCacheByPath.get(filePath)!
   }
@@ -105,7 +108,7 @@ let getCompilerOptions = (
     fileName => typescriptImport.sys.resolvePath(fileName),
     compilerOptionsConverted.options,
   )
-  let output: OutputProps = {
+  let output: ReadClosestTsConfigByPathValue = {
     compilerOptions: compilerOptionsConverted.options,
     cache,
   }
diff --git a/rules/sort-interfaces.ts b/rules/sort-interfaces.ts
index c7f5b24..774bf08 100644
--- a/rules/sort-interfaces.ts
+++ b/rules/sort-interfaces.ts
@@ -1,5 +1,11 @@
 import type { Options as SortObjectTypesOptions } from './sort-object-types/types'
 
+import {
+  MISSED_SPACING_ERROR,
+  EXTRA_SPACING_ERROR,
+  GROUP_ORDER_ERROR,
+  ORDER_ERROR,
+} from '../utils/report-errors'
 import { sortObjectTypeElements, jsonSchema } from './sort-object-types'
 import { createEslintRule } from '../utils/create-eslint-rule'
 
@@ -29,26 +35,6 @@ let defaultOptions: Required<Options[0]> = {
 }
 
 export default createEslintRule<Options, MESSAGE_ID>({
-  meta: {
-    messages: {
-      unexpectedInterfacePropertiesGroupOrder:
-        'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
-      missedSpacingBetweenInterfaceMembers:
-        'Missed spacing between "{{left}}" and "{{right}}" properties.',
-      extraSpacingBetweenInterfaceMembers:
-        'Extra spacing between "{{left}}" and "{{right}}" properties.',
-      unexpectedInterfacePropertiesOrder:
-        'Expected "{{right}}" to come before "{{left}}".',
-    },
-    docs: {
-      url: 'https://perfectionist.dev/rules/sort-interfaces',
-      description: 'Enforce sorted interface properties.',
-      recommended: true,
-    },
-    schema: jsonSchema,
-    type: 'suggestion',
-    fixable: 'code',
-  },
   create: context => ({
     TSInterfaceDeclaration: node =>
       sortObjectTypeElements<MESSAGE_ID>({
@@ -63,6 +49,22 @@ export default createEslintRule<Options, MESSAGE_ID>({
         context,
       }),
   }),
+  meta: {
+    messages: {
+      unexpectedInterfacePropertiesGroupOrder: GROUP_ORDER_ERROR,
+      missedSpacingBetweenInterfaceMembers: MISSED_SPACING_ERROR,
+      extraSpacingBetweenInterfaceMembers: EXTRA_SPACING_ERROR,
+      unexpectedInterfacePropertiesOrder: ORDER_ERROR,
+    },
+    docs: {
+      url: 'https://perfectionist.dev/rules/sort-interfaces',
+      description: 'Enforce sorted interface properties.',
+      recommended: true,
+    },
+    schema: jsonSchema,
+    type: 'suggestion',
+    fixable: 'code',
+  },
   defaultOptions: [defaultOptions],
   name: 'sort-interfaces',
 })
diff --git a/rules/sort-intersection-types.ts b/rules/sort-intersection-types.ts
index de3cf5d..13ebbd0 100644
--- a/rules/sort-intersection-types.ts
+++ b/rules/sort-intersection-types.ts
@@ -1,5 +1,11 @@
 import type { Options as SortUnionTypesOptions } from './sort-union-types'
 
+import {
+  MISSED_SPACING_ERROR,
+  EXTRA_SPACING_ERROR,
+  GROUP_ORDER_ERROR,
+  ORDER_ERROR,
+} from '../utils/report-errors'
 import { sortUnionOrIntersectionTypes } from './sort-union-types'
 import { createEslintRule } from '../utils/create-eslint-rule'
 import { jsonSchema } from './sort-union-types'
@@ -28,14 +34,10 @@ let defaultOptions: Required<Options[0]> = {
 export default createEslintRule<Options, MESSAGE_ID>({
   meta: {
     messages: {
-      unexpectedIntersectionTypesGroupOrder:
-        'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
-      missedSpacingBetweenIntersectionTypes:
-        'Missed spacing between "{{left}}" and "{{right}}" types.',
-      extraSpacingBetweenIntersectionTypes:
-        'Extra spacing between "{{left}}" and "{{right}}" types.',
-      unexpectedIntersectionTypesOrder:
-        'Expected "{{right}}" to come before "{{left}}".',
+      missedSpacingBetweenIntersectionTypes: MISSED_SPACING_ERROR,
+      extraSpacingBetweenIntersectionTypes: EXTRA_SPACING_ERROR,
+      unexpectedIntersectionTypesGroupOrder: GROUP_ORDER_ERROR,
+      unexpectedIntersectionTypesOrder: ORDER_ERROR,
     },
     docs: {
       url: 'https://perfectionist.dev/rules/sort-intersection-types',
diff --git a/rules/sort-jsx-props.ts b/rules/sort-jsx-props.ts
index 0a2f573..ed0957b 100644
--- a/rules/sort-jsx-props.ts
+++ b/rules/sort-jsx-props.ts
@@ -1,6 +1,11 @@
 import type { TSESTree } from '@typescript-eslint/types'
 
-import type { CommonOptions, GroupsOptions } from '../types/common-options'
+import type {
+  NewlinesBetweenOption,
+  CommonOptions,
+  GroupsOptions,
+  TypeOption,
+} from '../types/common-options'
 import type { SortingNode } from '../types/sorting-node'
 
 import {
@@ -11,6 +16,12 @@ import {
   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 { validateCustomSortConfiguration } from '../utils/validate-custom-sort-configuration'
 import { validateGroupsConfiguration } from '../utils/validate-groups-configuration'
@@ -31,12 +42,12 @@ import { matches } from '../utils/matches'
 type Options<T extends string = string> = [
   Partial<
     {
-      type: 'alphabetical' | 'line-length' | 'natural' | 'custom'
-      newlinesBetween: 'ignore' | 'always' | 'never'
       customGroups: Record<T, string[] | string>
+      newlinesBetween: NewlinesBetweenOption
       groups: GroupsOptions<Group<T>>
       partitionByNewLine: boolean
       ignorePattern: string[]
+      type: TypeOption
     } & CommonOptions
   >,
 ]
@@ -200,14 +211,10 @@ export default createEslintRule<Options, MESSAGE_ID>({
       },
     ],
     messages: {
-      unexpectedJSXPropsGroupOrder:
-        'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
-      missedSpacingBetweenJSXPropsMembers:
-        'Missed spacing between "{{left}}" and "{{right}}" props.',
-      extraSpacingBetweenJSXPropsMembers:
-        'Extra spacing between "{{left}}" and "{{right}}" props.',
-      unexpectedJSXPropsOrder:
-        'Expected "{{right}}" to come before "{{left}}".',
+      missedSpacingBetweenJSXPropsMembers: MISSED_SPACING_ERROR,
+      extraSpacingBetweenJSXPropsMembers: EXTRA_SPACING_ERROR,
+      unexpectedJSXPropsGroupOrder: GROUP_ORDER_ERROR,
+      unexpectedJSXPropsOrder: ORDER_ERROR,
     },
     docs: {
       url: 'https://perfectionist.dev/rules/sort-jsx-props',
diff --git a/rules/sort-maps.ts b/rules/sort-maps.ts
index fd2a8cf..06904eb 100644
--- a/rules/sort-maps.ts
+++ b/rules/sort-maps.ts
@@ -15,6 +15,12 @@ import {
   commonJsonSchemas,
   groupsJsonSchema,
 } from '../utils/common-json-schemas'
+import {
+  MISSED_SPACING_ERROR,
+  EXTRA_SPACING_ERROR,
+  GROUP_ORDER_ERROR,
+  ORDER_ERROR,
+} from '../utils/report-errors'
 import { validateGeneratedGroupsConfiguration } from '../utils/validate-generated-groups-configuration'
 import { validateCustomSortConfiguration } from '../utils/validate-custom-sort-configuration'
 import { getCustomGroupsCompareOptions } from '../utils/get-custom-groups-compare-options'
@@ -88,10 +94,9 @@ export default createEslintRule<Options, MESSAGE_ID>({
       let options = complete(matchedContextOptions[0], settings, defaultOptions)
       validateCustomSortConfiguration(options)
       validateGeneratedGroupsConfiguration({
-        customGroups: options.customGroups,
-        groups: options.groups,
         selectors: [],
         modifiers: [],
+        options,
       })
 
       let eslintDisabledLines = getEslintDisabledLines({
@@ -218,14 +223,10 @@ export default createEslintRule<Options, MESSAGE_ID>({
       type: 'array',
     },
     messages: {
-      unexpectedMapElementsGroupOrder:
-        'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
-      missedSpacingBetweenMapElementsMembers:
-        'Missed spacing between "{{left}}" and "{{right}}" members.',
-      extraSpacingBetweenMapElementsMembers:
-        'Extra spacing between "{{left}}" and "{{right}}" members.',
-      unexpectedMapElementsOrder:
-        'Expected "{{right}}" to come before "{{left}}".',
+      missedSpacingBetweenMapElementsMembers: MISSED_SPACING_ERROR,
+      extraSpacingBetweenMapElementsMembers: EXTRA_SPACING_ERROR,
+      unexpectedMapElementsGroupOrder: GROUP_ORDER_ERROR,
+      unexpectedMapElementsOrder: ORDER_ERROR,
     },
     docs: {
       url: 'https://perfectionist.dev/rules/sort-maps',
diff --git a/rules/sort-maps/does-custom-group-match.ts b/rules/sort-maps/does-custom-group-match.ts
index 20eace8..f98dbec 100644
--- a/rules/sort-maps/does-custom-group-match.ts
+++ b/rules/sort-maps/does-custom-group-match.ts
@@ -1,14 +1,15 @@
-import type { SingleCustomGroup, AnyOfCustomGroup } from './types'
+import type { AnyOfCustomGroup } from '../../types/common-options'
+import type { SingleCustomGroup } from './types'
 
 import { matches } from '../../utils/matches'
 
-interface DoesCustomGroupMatchProps {
-  customGroup: SingleCustomGroup | AnyOfCustomGroup
+interface DoesCustomGroupMatchParameters {
+  customGroup: AnyOfCustomGroup<SingleCustomGroup> | SingleCustomGroup
   elementName: string
 }
 
 export let doesCustomGroupMatch = (
-  props: DoesCustomGroupMatchProps,
+  props: DoesCustomGroupMatchParameters,
 ): boolean => {
   if ('anyOf' in props.customGroup) {
     // At least one subgroup must match
diff --git a/rules/sort-maps/types.ts b/rules/sort-maps/types.ts
index d9f285f..61d7efe 100644
--- a/rules/sort-maps/types.ts
+++ b/rules/sort-maps/types.ts
@@ -2,8 +2,11 @@ import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema'
 
 import type {
   PartitionByCommentOption,
+  NewlinesBetweenOption,
+  CustomGroupsOption,
   CommonOptions,
   GroupsOptions,
+  TypeOption,
 } from '../../types/common-options'
 
 import { elementNamePatternJsonSchema } from '../../utils/common-json-schemas'
@@ -13,12 +16,12 @@ export type Options = Partial<
     useConfigurationIf: {
       allNamesMatchPattern?: string
     }
-    type: 'alphabetical' | 'line-length' | 'natural' | 'custom'
-    newlinesBetween: 'ignore' | 'always' | 'never'
+    customGroups: CustomGroupsOption<SingleCustomGroup>
     partitionByComment: PartitionByCommentOption
+    newlinesBetween: NewlinesBetweenOption
     groups: GroupsOptions<Group>
     partitionByNewLine: boolean
-    customGroups: CustomGroup[]
+    type: TypeOption
   } & CommonOptions
 >[]
 
@@ -26,23 +29,6 @@ export interface SingleCustomGroup {
   elementNamePattern?: string
 }
 
-export interface AnyOfCustomGroup {
-  anyOf: SingleCustomGroup[]
-}
-
-type CustomGroup = (
-  | {
-      order?: Options[0]['order']
-      type?: Options[0]['type']
-    }
-  | {
-      type?: 'unsorted'
-    }
-) &
-  (SingleCustomGroup | AnyOfCustomGroup) & {
-    groupName: string
-  }
-
 type Group = 'unknown' | string
 
 export let singleCustomGroupJsonSchema: Record<string, JSONSchema4> = {
diff --git a/rules/sort-modules.ts b/rules/sort-modules.ts
index 66d5520..a4cea73 100644
--- a/rules/sort-modules.ts
+++ b/rules/sort-modules.ts
@@ -19,6 +19,13 @@ import {
   commonJsonSchemas,
   groupsJsonSchema,
 } from '../utils/common-json-schemas'
+import {
+  DEPENDENCY_ORDER_ERROR,
+  MISSED_SPACING_ERROR,
+  EXTRA_SPACING_ERROR,
+  GROUP_ORDER_ERROR,
+  ORDER_ERROR,
+} from '../utils/report-errors'
 import { validateNewlinesAndPartitionConfiguration } from '../utils/validate-newlines-and-partition-configuration'
 import { validateGeneratedGroupsConfiguration } from '../utils/validate-generated-groups-configuration'
 import {
@@ -88,17 +95,6 @@ let defaultOptions: Required<SortModulesOptions[0]> = {
 
 export default createEslintRule<SortModulesOptions, MESSAGE_ID>({
   meta: {
-    messages: {
-      unexpectedModulesGroupOrder:
-        'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
-      unexpectedModulesDependencyOrder:
-        'Expected dependency "{{right}}" to come before "{{nodeDependentOnRight}}".',
-      missedSpacingBetweenModulesMembers:
-        'Missed spacing between "{{left}}" and "{{right}}" objects.',
-      extraSpacingBetweenModulesMembers:
-        'Extra spacing between "{{left}}" and "{{right}}" objects.',
-      unexpectedModulesOrder: 'Expected "{{right}}" to come before "{{left}}".',
-    },
     schema: [
       {
         properties: {
@@ -116,6 +112,13 @@ export default createEslintRule<SortModulesOptions, MESSAGE_ID>({
         type: 'object',
       },
     ],
+    messages: {
+      unexpectedModulesDependencyOrder: DEPENDENCY_ORDER_ERROR,
+      missedSpacingBetweenModulesMembers: MISSED_SPACING_ERROR,
+      extraSpacingBetweenModulesMembers: EXTRA_SPACING_ERROR,
+      unexpectedModulesGroupOrder: GROUP_ORDER_ERROR,
+      unexpectedModulesOrder: ORDER_ERROR,
+    },
     docs: {
       url: 'https://perfectionist.dev/rules/sort-modules',
       description: 'Enforce sorted modules.',
@@ -129,10 +132,9 @@ export default createEslintRule<SortModulesOptions, MESSAGE_ID>({
     let options = complete(context.options.at(0), settings, defaultOptions)
     validateCustomSortConfiguration(options)
     validateGeneratedGroupsConfiguration({
-      customGroups: options.customGroups,
       modifiers: allModifiers,
       selectors: allSelectors,
-      groups: options.groups,
+      options,
     })
     validateNewlinesAndPartitionConfiguration(options)
 
diff --git a/rules/sort-modules/does-custom-group-match.ts b/rules/sort-modules/does-custom-group-match.ts
index d54a2f2..6cf9fcc 100644
--- a/rules/sort-modules/does-custom-group-match.ts
+++ b/rules/sort-modules/does-custom-group-match.ts
@@ -1,14 +1,10 @@
-import type {
-  SingleCustomGroup,
-  AnyOfCustomGroup,
-  Modifier,
-  Selector,
-} from './types'
+import type { SingleCustomGroup, Modifier, Selector } from './types'
+import type { AnyOfCustomGroup } from '../../types/common-options'
 
 import { matches } from '../../utils/matches'
 
-interface DoesCustomGroupMatchProps {
-  customGroup: SingleCustomGroup | AnyOfCustomGroup
+interface DoesCustomGroupMatchParameters {
+  customGroup: AnyOfCustomGroup<SingleCustomGroup> | SingleCustomGroup
   selectors: Selector[]
   modifiers: Modifier[]
   decorators: string[]
@@ -17,13 +13,13 @@ interface DoesCustomGroupMatchProps {
 
 /**
  * Determines whether a custom group matches the given properties.
- * @param {DoesCustomGroupMatchProps} props - The properties to compare with the
- * custom group, including selectors, modifiers, decorators, and element name.
+ * @param {DoesCustomGroupMatchParameters} props - The properties to compare
+ * with the custom group, including selectors, modifiers, decorators, and element name.
  * @returns {boolean} `true` if the custom group matches the properties;
  * otherwise, `false`.
  */
 export let doesCustomGroupMatch = (
-  props: DoesCustomGroupMatchProps,
+  props: DoesCustomGroupMatchParameters,
 ): boolean => {
   if ('anyOf' in props.customGroup) {
     // At least one subgroup must match.
diff --git a/rules/sort-modules/types.ts b/rules/sort-modules/types.ts
index a22f56d..a35d29f 100644
--- a/rules/sort-modules/types.ts
+++ b/rules/sort-modules/types.ts
@@ -2,8 +2,11 @@ import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema'
 
 import type {
   PartitionByCommentOption,
+  NewlinesBetweenOption,
+  CustomGroupsOption,
   CommonOptions,
   GroupsOptions,
+  TypeOption,
 } from '../../types/common-options'
 import type { JoinWithDash } from '../../types/join-with-dash'
 
@@ -13,19 +16,6 @@ import {
   elementNamePatternJsonSchema,
 } from '../../utils/common-json-schemas'
 
-export type SortModulesOptions = [
-  Partial<
-    {
-      type: 'alphabetical' | 'line-length' | 'natural' | 'custom'
-      newlinesBetween: 'ignore' | 'always' | 'never'
-      partitionByComment: PartitionByCommentOption
-      groups: GroupsOptions<Group>
-      customGroups: CustomGroup[]
-      partitionByNewLine: boolean
-    } & CommonOptions
-  >,
-]
-
 export type SingleCustomGroup = (
   | (DecoratorNamePatternFilterCustomGroup &
       BaseSingleCustomGroup<ClassSelector>)
@@ -36,6 +26,19 @@ export type SingleCustomGroup = (
 ) &
   ElementNamePatternFilterCustomGroup
 
+export type SortModulesOptions = [
+  Partial<
+    {
+      customGroups: CustomGroupsOption<SingleCustomGroup>
+      partitionByComment: PartitionByCommentOption
+      newlinesBetween: NewlinesBetweenOption
+      groups: GroupsOptions<Group>
+      partitionByNewLine: boolean
+      type: TypeOption
+    } & CommonOptions
+  >,
+]
+
 export type Selector =
   // | NamespaceSelector
   | InterfaceSelector
@@ -52,10 +55,6 @@ export type Modifier =
   | ExportModifier
   | AsyncModifier
 
-export interface AnyOfCustomGroup {
-  anyOf: SingleCustomGroup[]
-}
-
 /**
  * Only used in code as well
  */
@@ -69,18 +68,6 @@ interface AllowedModifiersPerSelector {
   type: DeclareModifier | ExportModifier
 }
 
-type CustomGroup = (
-  | {
-      order?: SortModulesOptions[0]['order']
-      type?: SortModulesOptions[0]['type']
-    }
-  | {
-      type?: 'unsorted'
-    }
-) & {
-  newlinesInside?: 'always' | 'never'
-  groupName: string
-} & (SingleCustomGroup | AnyOfCustomGroup)
 /**
  * Only used in code, so I don't know if it's worth maintaining this.
  */
diff --git a/rules/sort-named-exports.ts b/rules/sort-named-exports.ts
index 5c6bee7..57a945e 100644
--- a/rules/sort-named-exports.ts
+++ b/rules/sort-named-exports.ts
@@ -3,6 +3,7 @@ import type { TSESTree } from '@typescript-eslint/types'
 import type {
   PartitionByCommentOption,
   CommonOptions,
+  TypeOption,
 } from '../types/common-options'
 import type { SortingNode } from '../types/sorting-node'
 
@@ -20,6 +21,7 @@ import { reportAllErrors } from '../utils/report-all-errors'
 import { shouldPartition } from '../utils/should-partition'
 import { getSourceCode } from '../utils/get-source-code'
 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'
@@ -28,10 +30,10 @@ import { complete } from '../utils/complete'
 type Options = [
   Partial<
     {
-      type: 'alphabetical' | 'line-length' | 'natural' | 'custom'
       groupKind: 'values-first' | 'types-first' | 'mixed'
       partitionByComment: PartitionByCommentOption
       partitionByNewLine: boolean
+      type: TypeOption
     } & CommonOptions
   >,
 ]
@@ -173,8 +175,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
       recommended: true,
     },
     messages: {
-      unexpectedNamedExportsOrder:
-        'Expected "{{right}}" to come before "{{left}}".',
+      unexpectedNamedExportsOrder: ORDER_ERROR,
     },
     type: 'suggestion',
     fixable: 'code',
diff --git a/rules/sort-named-imports.ts b/rules/sort-named-imports.ts
index ce213da..d237336 100644
--- a/rules/sort-named-imports.ts
+++ b/rules/sort-named-imports.ts
@@ -3,6 +3,7 @@ import type { TSESTree } from '@typescript-eslint/types'
 import type {
   PartitionByCommentOption,
   CommonOptions,
+  TypeOption,
 } from '../types/common-options'
 import type { SortingNode } from '../types/sorting-node'
 
@@ -20,6 +21,7 @@ import { reportAllErrors } from '../utils/report-all-errors'
 import { shouldPartition } from '../utils/should-partition'
 import { getSourceCode } from '../utils/get-source-code'
 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'
@@ -28,11 +30,11 @@ import { complete } from '../utils/complete'
 type Options = [
   Partial<
     {
-      type: 'alphabetical' | 'line-length' | 'natural' | 'custom'
       groupKind: 'values-first' | 'types-first' | 'mixed'
       partitionByComment: PartitionByCommentOption
       partitionByNewLine: boolean
       ignoreAlias: boolean
+      type: TypeOption
     } & CommonOptions
   >,
 ]
@@ -185,8 +187,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
       recommended: true,
     },
     messages: {
-      unexpectedNamedImportsOrder:
-        'Expected "{{right}}" to come before "{{left}}".',
+      unexpectedNamedImportsOrder: ORDER_ERROR,
     },
     type: 'suggestion',
     fixable: 'code',
diff --git a/rules/sort-object-types.ts b/rules/sort-object-types.ts
index bc7d35e..95b61a1 100644
--- a/rules/sort-object-types.ts
+++ b/rules/sort-object-types.ts
@@ -17,6 +17,12 @@ import {
   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,
@@ -121,26 +127,6 @@ export let jsonSchema: JSONSchema4 = {
 }
 
 export default createEslintRule<Options, MESSAGE_ID>({
-  meta: {
-    messages: {
-      unexpectedObjectTypesGroupOrder:
-        'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
-      missedSpacingBetweenObjectTypeMembers:
-        'Missed spacing between "{{left}}" and "{{right}}" properties.',
-      extraSpacingBetweenObjectTypeMembers:
-        'Extra spacing between "{{left}}" and "{{right}}" properties.',
-      unexpectedObjectTypesOrder:
-        'Expected "{{right}}" to come before "{{left}}".',
-    },
-    docs: {
-      url: 'https://perfectionist.dev/rules/sort-object-types',
-      description: 'Enforce sorted object types.',
-      recommended: true,
-    },
-    schema: jsonSchema,
-    type: 'suggestion',
-    fixable: 'code',
-  },
   create: context => ({
     TSTypeLiteral: node =>
       sortObjectTypeElements<MESSAGE_ID>({
@@ -158,6 +144,22 @@ export default createEslintRule<Options, MESSAGE_ID>({
         context,
       }),
   }),
+  meta: {
+    messages: {
+      missedSpacingBetweenObjectTypeMembers: MISSED_SPACING_ERROR,
+      extraSpacingBetweenObjectTypeMembers: EXTRA_SPACING_ERROR,
+      unexpectedObjectTypesGroupOrder: GROUP_ORDER_ERROR,
+      unexpectedObjectTypesOrder: ORDER_ERROR,
+    },
+    docs: {
+      url: 'https://perfectionist.dev/rules/sort-object-types',
+      description: 'Enforce sorted object types.',
+      recommended: true,
+    },
+    schema: jsonSchema,
+    type: 'suggestion',
+    fixable: 'code',
+  },
   defaultOptions: [defaultOptions],
   name: 'sort-object-types',
 })
@@ -216,10 +218,9 @@ export let sortObjectTypeElements = <MessageIds extends string>({
   }
   validateCustomSortConfiguration(options)
   validateGeneratedGroupsConfiguration({
-    customGroups: options.customGroups,
     selectors: allSelectors,
     modifiers: allModifiers,
-    groups: options.groups,
+    options,
   })
   validateNewlinesAndPartitionConfiguration(options)
 
diff --git a/rules/sort-object-types/does-custom-group-match.ts b/rules/sort-object-types/does-custom-group-match.ts
index 063b5d9..304f456 100644
--- a/rules/sort-object-types/does-custom-group-match.ts
+++ b/rules/sort-object-types/does-custom-group-match.ts
@@ -1,14 +1,10 @@
-import type {
-  SingleCustomGroup,
-  AnyOfCustomGroup,
-  Modifier,
-  Selector,
-} from './types'
+import type { SingleCustomGroup, Modifier, Selector } from './types'
+import type { AnyOfCustomGroup } from '../../types/common-options'
 
 import { matches } from '../../utils/matches'
 
-interface DoesCustomGroupMatchProps {
-  customGroup: SingleCustomGroup | AnyOfCustomGroup
+interface DoesCustomGroupMatchParameters {
+  customGroup: AnyOfCustomGroup<SingleCustomGroup> | SingleCustomGroup
   selectors: Selector[]
   modifiers: Modifier[]
   elementName: string
@@ -16,13 +12,13 @@ interface DoesCustomGroupMatchProps {
 
 /**
  * Determines whether a custom group matches the given properties.
- * @param {DoesCustomGroupMatchProps} props - The properties to compare with the
- * custom group, including selectors, modifiers, and element name.
+ * @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: DoesCustomGroupMatchProps,
+  props: DoesCustomGroupMatchParameters,
 ): boolean => {
   if ('anyOf' in props.customGroup) {
     // At least one subgroup must match.
diff --git a/rules/sort-object-types/types.ts b/rules/sort-object-types/types.ts
index edb7e9e..28f75f8 100644
--- a/rules/sort-object-types/types.ts
+++ b/rules/sort-object-types/types.ts
@@ -1,9 +1,13 @@
 import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema'
 
 import type {
+  DeprecatedCustomGroupsOption,
   PartitionByCommentOption,
+  NewlinesBetweenOption,
+  CustomGroupsOption,
   CommonOptions,
   GroupsOptions,
+  TypeOption,
 } from '../../types/common-options'
 import type { JoinWithDash } from '../../types/join-with-dash'
 
@@ -19,14 +23,16 @@ export type Options = Partial<
       declarationMatchesPattern?: string
       allNamesMatchPattern?: string
     }
-    type: 'alphabetical' | 'line-length' | 'unsorted' | 'natural' | 'custom'
-    customGroups: Record<string, string[] | string> | CustomGroup[]
+    customGroups:
+      | CustomGroupsOption<SingleCustomGroup>
+      | DeprecatedCustomGroupsOption
     /**
      * @deprecated for {@link `groups`}
      */
     groupKind: 'required-first' | 'optional-first' | 'mixed'
-    newlinesBetween: 'ignore' | 'always' | 'never'
     partitionByComment: PartitionByCommentOption
+    newlinesBetween: NewlinesBetweenOption
+    type: TypeOption | 'unsorted'
     groups: GroupsOptions<Group>
     partitionByNewLine: boolean
     /**
@@ -54,10 +60,6 @@ export type Selector =
 
 export type Modifier = MultilineModifier | RequiredModifier | OptionalModifier
 
-export interface AnyOfCustomGroup {
-  anyOf: SingleCustomGroup[]
-}
-
 /**
  * Only used in code as well
  */
@@ -69,19 +71,6 @@ interface AllowedModifiersPerSelector {
   'index-signature': never
 }
 
-type CustomGroup = (
-  | {
-      order?: Options[0]['order']
-      type?: Options[0]['type']
-    }
-  | {
-      type?: 'unsorted'
-    }
-) & {
-  newlinesInside?: 'always' | 'never'
-  groupName: string
-} & (SingleCustomGroup | AnyOfCustomGroup)
-
 type IndexSignatureGroup = JoinWithDash<
   [
     OptionalModifier,
diff --git a/rules/sort-objects.ts b/rules/sort-objects.ts
index 3b3fe64..d27e041 100644
--- a/rules/sort-objects.ts
+++ b/rules/sort-objects.ts
@@ -15,6 +15,13 @@ import {
   commonJsonSchemas,
   groupsJsonSchema,
 } from '../utils/common-json-schemas'
+import {
+  DEPENDENCY_ORDER_ERROR,
+  MISSED_SPACING_ERROR,
+  EXTRA_SPACING_ERROR,
+  GROUP_ORDER_ERROR,
+  ORDER_ERROR,
+} from '../utils/report-errors'
 import { validateNewlinesAndPartitionConfiguration } from '../utils/validate-newlines-and-partition-configuration'
 import { validateGeneratedGroupsConfiguration } from '../utils/validate-generated-groups-configuration'
 import { validateCustomSortConfiguration } from '../utils/validate-custom-sort-configuration'
@@ -129,10 +136,9 @@ export default createEslintRule<Options, MESSAGE_ID>({
       }
       validateCustomSortConfiguration(options)
       validateGeneratedGroupsConfiguration({
-        customGroups: options.customGroups,
         selectors: allSelectors,
         modifiers: allModifiers,
-        groups: options.groups,
+        options,
       })
       validateNewlinesAndPartitionConfiguration(options)
 
@@ -515,15 +521,11 @@ export default createEslintRule<Options, MESSAGE_ID>({
       type: 'array',
     },
     messages: {
-      unexpectedObjectsGroupOrder:
-        'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
-      unexpectedObjectsDependencyOrder:
-        'Expected dependency "{{right}}" to come before "{{nodeDependentOnRight}}".',
-      missedSpacingBetweenObjectMembers:
-        'Missed spacing between "{{left}}" and "{{right}}" objects.',
-      extraSpacingBetweenObjectMembers:
-        'Extra spacing between "{{left}}" and "{{right}}" objects.',
-      unexpectedObjectsOrder: 'Expected "{{right}}" to come before "{{left}}".',
+      unexpectedObjectsDependencyOrder: DEPENDENCY_ORDER_ERROR,
+      missedSpacingBetweenObjectMembers: MISSED_SPACING_ERROR,
+      extraSpacingBetweenObjectMembers: EXTRA_SPACING_ERROR,
+      unexpectedObjectsGroupOrder: GROUP_ORDER_ERROR,
+      unexpectedObjectsOrder: ORDER_ERROR,
     },
     docs: {
       url: 'https://perfectionist.dev/rules/sort-objects',
diff --git a/rules/sort-objects/does-custom-group-match.ts b/rules/sort-objects/does-custom-group-match.ts
index c7d1207..5d46bd4 100644
--- a/rules/sort-objects/does-custom-group-match.ts
+++ b/rules/sort-objects/does-custom-group-match.ts
@@ -1,14 +1,10 @@
-import type {
-  SingleCustomGroup,
-  AnyOfCustomGroup,
-  Modifier,
-  Selector,
-} from './types'
+import type { SingleCustomGroup, Modifier, Selector } from './types'
+import type { AnyOfCustomGroup } from '../../types/common-options'
 
 import { matches } from '../../utils/matches'
 
-interface DoesCustomGroupMatchProps {
-  customGroup: SingleCustomGroup | AnyOfCustomGroup
+interface DoesCustomGroupMatchParameters {
+  customGroup: AnyOfCustomGroup<SingleCustomGroup> | SingleCustomGroup
   elementValue: string | null
   selectors: Selector[]
   modifiers: Modifier[]
@@ -17,13 +13,13 @@ interface DoesCustomGroupMatchProps {
 
 /**
  * Determines whether a custom group matches the given properties.
- * @param {DoesCustomGroupMatchProps} props - The properties to compare with the
- * custom group, including selectors, modifiers, and element name.
+ * @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: DoesCustomGroupMatchProps,
+  props: DoesCustomGroupMatchParameters,
 ): boolean => {
   if ('anyOf' in props.customGroup) {
     // At least one subgroup must match.
diff --git a/rules/sort-objects/types.ts b/rules/sort-objects/types.ts
index 30a40a5..3e303b5 100644
--- a/rules/sort-objects/types.ts
+++ b/rules/sort-objects/types.ts
@@ -1,9 +1,13 @@
 import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema'
 
 import type {
+  DeprecatedCustomGroupsOption,
   PartitionByCommentOption,
+  NewlinesBetweenOption,
+  CustomGroupsOption,
   CommonOptions,
   GroupsOptions,
+  TypeOption,
 } from '../../types/common-options'
 import type { JoinWithDash } from '../../types/join-with-dash'
 
@@ -20,11 +24,13 @@ export type Options = Partial<
       callingFunctionNamePattern?: string
       allNamesMatchPattern?: string
     }
-    type: 'alphabetical' | 'line-length' | 'unsorted' | 'natural' | 'custom'
-    customGroups: Record<string, string[] | string> | CustomGroup[]
+    customGroups:
+      | CustomGroupsOption<SingleCustomGroup>
+      | DeprecatedCustomGroupsOption
     destructuredObjects: { groups: boolean } | boolean
-    newlinesBetween: 'ignore' | 'always' | 'never'
     partitionByComment: PartitionByCommentOption
+    newlinesBetween: NewlinesBetweenOption
+    type: TypeOption | 'unsorted'
     groups: GroupsOptions<Group>
     partitionByNewLine: boolean
     objectDeclarations: boolean
@@ -55,10 +61,6 @@ export type Selector =
 
 export type Modifier = MultilineModifier | RequiredModifier | OptionalModifier
 
-export interface AnyOfCustomGroup {
-  anyOf: SingleCustomGroup[]
-}
-
 /**
  * Only used in code as well
  */
@@ -70,19 +72,6 @@ interface AllowedModifiersPerSelector {
   'index-signature': never
 }
 
-type CustomGroup = (
-  | {
-      order?: Options[0]['order']
-      type?: Options[0]['type']
-    }
-  | {
-      type?: 'unsorted'
-    }
-) & {
-  newlinesInside?: 'always' | 'never'
-  groupName: string
-} & (SingleCustomGroup | AnyOfCustomGroup)
-
 interface BaseSingleCustomGroup<T extends Selector> {
   modifiers?: AllowedModifiersPerSelector[T][]
   selector?: T
diff --git a/rules/sort-sets.ts b/rules/sort-sets.ts
index c206f12..d20e856 100644
--- a/rules/sort-sets.ts
+++ b/rules/sort-sets.ts
@@ -1,5 +1,11 @@
 import type { Options } from './sort-array-includes/types'
 
+import {
+  MISSED_SPACING_ERROR,
+  EXTRA_SPACING_ERROR,
+  GROUP_ORDER_ERROR,
+  ORDER_ERROR,
+} from '../utils/report-errors'
 import { defaultOptions, jsonSchema, sortArray } from './sort-array-includes'
 import { createEslintRule } from '../utils/create-eslint-rule'
 
@@ -40,13 +46,10 @@ export default createEslintRule<Options, MESSAGE_ID>({
   }),
   meta: {
     messages: {
-      unexpectedSetsGroupOrder:
-        'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
-      missedSpacingBetweenSetsMembers:
-        'Missed spacing between "{{left}}" and "{{right}}" members.',
-      extraSpacingBetweenSetsMembers:
-        'Extra spacing between "{{left}}" and "{{right}}" members.',
-      unexpectedSetsOrder: 'Expected "{{right}}" to come before "{{left}}".',
+      missedSpacingBetweenSetsMembers: MISSED_SPACING_ERROR,
+      extraSpacingBetweenSetsMembers: EXTRA_SPACING_ERROR,
+      unexpectedSetsGroupOrder: GROUP_ORDER_ERROR,
+      unexpectedSetsOrder: ORDER_ERROR,
     },
     docs: {
       url: 'https://perfectionist.dev/rules/sort-sets',
diff --git a/rules/sort-switch-case.ts b/rules/sort-switch-case.ts
index 25f43f3..6c243d9 100644
--- a/rules/sort-switch-case.ts
+++ b/rules/sort-switch-case.ts
@@ -1,7 +1,7 @@
 import type { TSESTree } from '@typescript-eslint/types'
 import type { TSESLint } from '@typescript-eslint/utils'
 
-import type { CommonOptions } from '../types/common-options'
+import type { CommonOptions, TypeOption } from '../types/common-options'
 import type { SortingNode } from '../types/sorting-node'
 
 import { makeSingleNodeCommentAfterFixes } from '../utils/make-single-node-comment-after-fixes'
@@ -10,10 +10,10 @@ import {
   buildTypeJsonSchema,
   commonJsonSchemas,
 } from '../utils/common-json-schemas'
+import { reportErrors, ORDER_ERROR, RIGHT, LEFT } from '../utils/report-errors'
 import { createNodeIndexMap } from '../utils/create-node-index-map'
 import { createEslintRule } from '../utils/create-eslint-rule'
 import { getSourceCode } from '../utils/get-source-code'
-import { reportErrors } from '../utils/report-errors'
 import { rangeToDiff } from '../utils/range-to-diff'
 import { getSettings } from '../utils/get-settings'
 import { isSortable } from '../utils/is-sortable'
@@ -23,18 +23,18 @@ import { pairwise } from '../utils/pairwise'
 import { complete } from '../utils/complete'
 import { compare } from '../utils/compare'
 
+interface SortSwitchCaseSortingNode extends SortingNode<TSESTree.SwitchCase> {
+  isDefaultClause: boolean
+}
+
 type Options = [
   Partial<
     {
-      type: 'alphabetical' | 'line-length' | 'natural' | 'custom'
+      type: TypeOption
     } & CommonOptions
   >,
 ]
 
-interface SortSwitchCaseSortingNode extends SortingNode<TSESTree.SwitchCase> {
-  isDefaultClause: boolean
-}
-
 type MESSAGE_ID = 'unexpectedSwitchCaseOrder'
 
 let defaultOptions: Required<Options[0]> = {
@@ -186,8 +186,8 @@ export default createEslintRule<Options, MESSAGE_ID>({
             ]
           },
           data: {
-            left: defaultCase.name,
-            right: lastCase.name,
+            [LEFT]: defaultCase.name,
+            [RIGHT]: lastCase.name,
           },
           messageId: 'unexpectedSwitchCaseOrder',
           node: defaultCase.node,
@@ -250,8 +250,8 @@ export default createEslintRule<Options, MESSAGE_ID>({
                   fixer,
                 }),
           data: {
-            right: right.name,
-            left: left.name,
+            [RIGHT]: right.name,
+            [LEFT]: left.name,
           },
           messageId: 'unexpectedSwitchCaseOrder',
           node: right.node,
@@ -276,8 +276,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
       recommended: true,
     },
     messages: {
-      unexpectedSwitchCaseOrder:
-        'Expected "{{right}}" to come before "{{left}}".',
+      unexpectedSwitchCaseOrder: ORDER_ERROR,
     },
     type: 'suggestion',
     fixable: 'code',
diff --git a/rules/sort-union-types.ts b/rules/sort-union-types.ts
index 77796e7..fa37160 100644
--- a/rules/sort-union-types.ts
+++ b/rules/sort-union-types.ts
@@ -4,8 +4,10 @@ import type { TSESTree } from '@typescript-eslint/types'
 
 import type {
   PartitionByCommentOption,
+  NewlinesBetweenOption,
   CommonOptions,
   GroupsOptions,
+  TypeOption,
 } from '../types/common-options'
 import type { SortingNode } from '../types/sorting-node'
 
@@ -17,6 +19,12 @@ import {
   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 { validateCustomSortConfiguration } from '../utils/validate-custom-sort-configuration'
 import { validateGroupsConfiguration } from '../utils/validate-groups-configuration'
@@ -35,11 +43,11 @@ import { complete } from '../utils/complete'
 export type Options = [
   Partial<
     {
-      type: 'alphabetical' | 'line-length' | 'natural' | 'custom'
-      newlinesBetween: 'ignore' | 'always' | 'never'
       partitionByComment: PartitionByCommentOption
+      newlinesBetween: NewlinesBetweenOption
       groups: GroupsOptions<Group>
       partitionByNewLine: boolean
+      type: TypeOption
     } & CommonOptions
   >,
 ]
@@ -92,26 +100,6 @@ export let jsonSchema: JSONSchema4 = {
 }
 
 export default createEslintRule<Options, MESSAGE_ID>({
-  meta: {
-    messages: {
-      unexpectedUnionTypesGroupOrder:
-        'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
-      missedSpacingBetweenUnionTypes:
-        'Missed spacing between "{{left}}" and "{{right}}" types.',
-      extraSpacingBetweenUnionTypes:
-        'Extra spacing between "{{left}}" and "{{right}}" types.',
-      unexpectedUnionTypesOrder:
-        'Expected "{{right}}" to come before "{{left}}".',
-    },
-    docs: {
-      url: 'https://perfectionist.dev/rules/sort-union-types',
-      description: 'Enforce sorted union types.',
-      recommended: true,
-    },
-    schema: [jsonSchema],
-    type: 'suggestion',
-    fixable: 'code',
-  },
   create: context => ({
     TSUnionType: node => {
       sortUnionOrIntersectionTypes({
@@ -127,6 +115,22 @@ export default createEslintRule<Options, MESSAGE_ID>({
       })
     },
   }),
+  meta: {
+    messages: {
+      missedSpacingBetweenUnionTypes: MISSED_SPACING_ERROR,
+      extraSpacingBetweenUnionTypes: EXTRA_SPACING_ERROR,
+      unexpectedUnionTypesGroupOrder: GROUP_ORDER_ERROR,
+      unexpectedUnionTypesOrder: ORDER_ERROR,
+    },
+    docs: {
+      url: 'https://perfectionist.dev/rules/sort-union-types',
+      description: 'Enforce sorted union types.',
+      recommended: true,
+    },
+    schema: [jsonSchema],
+    type: 'suggestion',
+    fixable: 'code',
+  },
   defaultOptions: [defaultOptions],
   name: 'sort-union-types',
 })
diff --git a/rules/sort-variable-declarations.ts b/rules/sort-variable-declarations.ts
index 1b9bdaf..0887c74 100644
--- a/rules/sort-variable-declarations.ts
+++ b/rules/sort-variable-declarations.ts
@@ -3,6 +3,7 @@ import type { TSESTree } from '@typescript-eslint/types'
 import type {
   PartitionByCommentOption,
   CommonOptions,
+  TypeOption,
 } from '../types/common-options'
 import type { SortingNodeWithDependencies } from '../utils/sort-nodes-by-dependencies'
 
@@ -14,6 +15,7 @@ import {
 } from '../utils/common-json-schemas'
 import { validateCustomSortConfiguration } from '../utils/validate-custom-sort-configuration'
 import { sortNodesByDependencies } from '../utils/sort-nodes-by-dependencies'
+import { DEPENDENCY_ORDER_ERROR, ORDER_ERROR } from '../utils/report-errors'
 import { getEslintDisabledLines } from '../utils/get-eslint-disabled-lines'
 import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled'
 import { createEslintRule } from '../utils/create-eslint-rule'
@@ -29,9 +31,9 @@ import { complete } from '../utils/complete'
 type Options = [
   Partial<
     {
-      type: 'alphabetical' | 'line-length' | 'natural' | 'custom'
       partitionByComment: PartitionByCommentOption
       partitionByNewLine: boolean
+      type: TypeOption
     } & CommonOptions
   >,
 ]
@@ -251,17 +253,15 @@ export default createEslintRule<Options, MESSAGE_ID>({
         type: 'object',
       },
     ],
-    messages: {
-      unexpectedVariableDeclarationsDependencyOrder:
-        'Expected dependency "{{right}}" to come before "{{nodeDependentOnRight}}".',
-      unexpectedVariableDeclarationsOrder:
-        'Expected "{{right}}" to come before "{{left}}".',
-    },
     docs: {
       url: 'https://perfectionist.dev/rules/sort-variable-declarations',
       description: 'Enforce sorted variable declarations.',
       recommended: true,
     },
+    messages: {
+      unexpectedVariableDeclarationsDependencyOrder: DEPENDENCY_ORDER_ERROR,
+      unexpectedVariableDeclarationsOrder: ORDER_ERROR,
+    },
     type: 'suggestion',
     fixable: 'code',
   },
diff --git a/types/common-options.ts b/types/common-options.ts
index 5e4508f..eebb830 100644
--- a/types/common-options.ts
+++ b/types/common-options.ts
@@ -1,8 +1,21 @@
+export type CustomGroupsOption<SingleCustomGroup = object> = ((
+  | {
+      type?: TypeOption | 'unsorted'
+      order?: OrderOption
+    }
+  | {
+      type?: 'unsorted'
+    }
+) & {
+  newlinesInside?: 'always' | 'never'
+  groupName: string
+} & (AnyOfCustomGroup<SingleCustomGroup> | SingleCustomGroup))[]
+
 export interface CommonOptions {
-  specialCharacters: 'remove' | 'trim' | 'keep'
+  specialCharacters: SpecialCharactersOption
   locales: NonNullable<Intl.LocalesArgument>
-  order: 'desc' | 'asc'
   ignoreCase: boolean
+  order: OrderOption
   alphabet: string
 }
 
@@ -16,7 +29,21 @@ export type PartitionByCommentOption =
   | string
 
 export type GroupsOptions<T> = (
-  | { newlinesBetween: 'ignore' | 'always' | 'never' }
+  | { newlinesBetween: NewlinesBetweenOption }
   | T[]
   | T
 )[]
+
+export interface AnyOfCustomGroup<SingleCustomGroup> {
+  anyOf: SingleCustomGroup[]
+}
+
+export type TypeOption = 'alphabetical' | 'line-length' | 'natural' | 'custom'
+
+export type DeprecatedCustomGroupsOption = Record<string, string[] | string>
+
+export type NewlinesBetweenOption = 'ignore' | 'always' | 'never'
+
+export type SpecialCharactersOption = 'remove' | 'trim' | 'keep'
+
+export type OrderOption = 'desc' | 'asc'
diff --git a/utils/compare.ts b/utils/compare.ts
index 1a7531a..96d2184 100644
--- a/utils/compare.ts
+++ b/utils/compare.ts
@@ -1,5 +1,9 @@
 import { compare as createNaturalCompare } from 'natural-orderby'
 
+import type {
+  SpecialCharactersOption,
+  OrderOption,
+} from '../types/common-options'
 import type { SortingNode } from '../types/sorting-node'
 
 import { convertBooleanToSign } from './convert-boolean-to-sign'
@@ -16,12 +20,12 @@ interface BaseCompareOptions<T extends SortingNode> {
    * node's name.
    */
   nodeValueGetter?: ((node: T) => string) | null
-  order: 'desc' | 'asc'
+  order: OrderOption
 }
 
 interface AlphabeticalCompareOptions<T extends SortingNode>
   extends BaseCompareOptions<T> {
-  specialCharacters: 'remove' | 'trim' | 'keep'
+  specialCharacters: SpecialCharactersOption
   locales: NonNullable<Intl.LocalesArgument>
   type: 'alphabetical'
   ignoreCase: boolean
@@ -29,7 +33,7 @@ interface AlphabeticalCompareOptions<T extends SortingNode>
 
 interface NaturalCompareOptions<T extends SortingNode>
   extends BaseCompareOptions<T> {
-  specialCharacters: 'remove' | 'trim' | 'keep'
+  specialCharacters: SpecialCharactersOption
   locales: NonNullable<Intl.LocalesArgument>
   ignoreCase: boolean
   type: 'natural'
@@ -37,7 +41,7 @@ interface NaturalCompareOptions<T extends SortingNode>
 
 interface CustomCompareOptions<T extends SortingNode>
   extends BaseCompareOptions<T> {
-  specialCharacters: 'remove' | 'trim' | 'keep'
+  specialCharacters: SpecialCharactersOption
   ignoreCase: boolean
   alphabet: string
   type: 'custom'
@@ -168,7 +172,7 @@ let getLineLengthSortingFunction =
   }
 
 let getFormatStringFunction =
-  (ignoreCase: boolean, specialCharacters: 'remove' | 'trim' | 'keep') =>
+  (ignoreCase: boolean, specialCharacters: SpecialCharactersOption) =>
   (value: string) => {
     let valueToCompare = value
     if (ignoreCase) {
diff --git a/utils/generate-predefined-groups.ts b/utils/generate-predefined-groups.ts
index 88d4b42..afa81db 100644
--- a/utils/generate-predefined-groups.ts
+++ b/utils/generate-predefined-groups.ts
@@ -1,6 +1,6 @@
 import { getArrayCombinations } from './get-array-combinations'
 
-interface Props {
+interface GeneratePredefinedGroupsParameters {
   cache: Map<string, string[]>
   modifiers: string[]
   selectors: string[]
@@ -13,7 +13,7 @@ interface Props {
  * prioritized over the quantity of modifiers. For example,
  * `protected abstract override get fields();` should prioritize the
  * `'get-method'` group over the `'protected-abstract-override-method'` group.
- * @param {Props} props - The properties including selectors, modifiers, and
+ * @param {GeneratePredefinedGroupsParameters} props - The properties including selectors, modifiers, and
  * cache.
  * @param {string[]} props.selectors - The list of selectors.
  * @param {string[]} props.modifiers - The list of modifiers.
@@ -24,7 +24,7 @@ export let generatePredefinedGroups = ({
   selectors,
   modifiers,
   cache,
-}: Props): string[] => {
+}: GeneratePredefinedGroupsParameters): string[] => {
   let modifiersAndSelectorsKey = `${modifiers.join('&')}/${selectors.join('&')}`
   let cachedValue = cache.get(modifiersAndSelectorsKey)
   if (cachedValue) {
diff --git a/utils/get-custom-groups-compare-options.ts b/utils/get-custom-groups-compare-options.ts
index cc4d996..2f6a25f 100644
--- a/utils/get-custom-groups-compare-options.ts
+++ b/utils/get-custom-groups-compare-options.ts
@@ -1,30 +1,25 @@
-import type { GroupsOptions } from '../types/common-options'
+import type {
+  DeprecatedCustomGroupsOption,
+  SpecialCharactersOption,
+  CustomGroupsOption,
+  GroupsOptions,
+  OrderOption,
+  TypeOption,
+} from '../types/common-options'
 import type { SortingNode } from '../types/sorting-node'
 import type { CompareOptions } from './compare'
 
 interface Options {
-  customGroups: Record<string, string[] | string> | CustomGroup[]
-  type: 'alphabetical' | 'line-length' | 'natural' | 'custom'
-  specialCharacters: 'remove' | 'trim' | 'keep'
+  customGroups: DeprecatedCustomGroupsOption | CustomGroupsOption
+  specialCharacters: SpecialCharactersOption
   locales: NonNullable<Intl.LocalesArgument>
   groups: GroupsOptions<string>
-  order: 'desc' | 'asc'
   ignoreCase: boolean
+  order: OrderOption
+  type: TypeOption
   alphabet: string
 }
 
-type CustomGroup = (
-  | {
-      type?: 'alphabetical' | 'line-length' | 'natural' | 'custom'
-      order?: 'desc' | 'asc'
-    }
-  | {
-      type?: 'unsorted'
-    }
-) & {
-  groupName: string
-}
-
 /**
  * Retrieves the compare options used to sort a given group. If the group is a
  * custom group, its options will be favored over the default options. Returns
diff --git a/utils/get-group-number.ts b/utils/get-group-number.ts
index 2ffe6e1..8082d19 100644
--- a/utils/get-group-number.ts
+++ b/utils/get-group-number.ts
@@ -1,9 +1,7 @@
+import type { NewlinesBetweenOption } from '../types/common-options'
 import type { SortingNode } from '../types/sorting-node'
 
-type Group =
-  | { newlinesBetween: 'ignore' | 'always' | 'never' }
-  | string[]
-  | string
+type Group = { newlinesBetween: NewlinesBetweenOption } | string[] | string
 
 export let getGroupNumber = (groups: Group[], node: SortingNode): number => {
   for (let max = groups.length, i = 0; i < max; i++) {
diff --git a/utils/get-newlines-between-option.ts b/utils/get-newlines-between-option.ts
index d418335..8f9d95b 100644
--- a/utils/get-newlines-between-option.ts
+++ b/utils/get-newlines-between-option.ts
@@ -1,23 +1,21 @@
-import type { GroupsOptions } from '../types/common-options'
+import type {
+  DeprecatedCustomGroupsOption,
+  NewlinesBetweenOption,
+  CustomGroupsOption,
+  GroupsOptions,
+} from '../types/common-options'
 import type { SortingNode } from '../types/sorting-node'
 
 import { getGroupNumber } from './get-group-number'
 
 export interface GetNewlinesBetweenOptionParameters {
+  options: {
+    customGroups?: DeprecatedCustomGroupsOption | CustomGroupsOption
+    newlinesBetween: NewlinesBetweenOption
+    groups: GroupsOptions<string>
+  }
   nextSortingNode: SortingNode
   sortingNode: SortingNode
-  options: Options
-}
-
-interface Options {
-  customGroups?: Record<string, string[] | string> | CustomGroup[]
-  newlinesBetween: 'ignore' | 'always' | 'never'
-  groups: GroupsOptions<string>
-}
-
-interface CustomGroup {
-  newlinesInside?: 'always' | 'never'
-  groupName: string
 }
 
 /**
@@ -29,15 +27,15 @@ interface CustomGroup {
  * @param {GetNewlinesBetweenOptionParameters} props - The function arguments
  * @param {SortingNode} props.nextSortingNode - The next node to sort
  * @param {SortingNode} props.sortingNode - The current node to sort
- * @param {Options} props.options - Newlines between related options
- * @returns {'ignore' | 'always' | 'never'} - The `newlinesBetween` option to
+ * @param {GetNewlinesBetweenOptionParameters['options']} props.options - Newlines between related options
+ * @returns {NewlinesBetweenOption} - The `newlinesBetween` option to
  * use
  */
 export let getNewlinesBetweenOption = ({
   nextSortingNode,
   sortingNode,
   options,
-}: GetNewlinesBetweenOptionParameters): 'ignore' | 'always' | 'never' => {
+}: GetNewlinesBetweenOptionParameters): NewlinesBetweenOption => {
   let nodeGroupNumber = getGroupNumber(options.groups, sortingNode)
   let nextNodeGroupNumber = getGroupNumber(options.groups, nextSortingNode)
   let globalNewlinesBetweenOption = getGlobalNewlinesBetweenOption({
@@ -88,7 +86,7 @@ let getGlobalNewlinesBetweenOption = ({
   newlinesBetween,
   nodeGroupNumber,
 }: {
-  newlinesBetween: 'ignore' | 'always' | 'never'
+  newlinesBetween: NewlinesBetweenOption
   nextNodeGroupNumber: number
   nodeGroupNumber: number
 }): 'always' | 'ignore' | 'never' => {
diff --git a/utils/get-newlines-errors.ts b/utils/get-newlines-errors.ts
index c440646..040ecab 100644
--- a/utils/get-newlines-errors.ts
+++ b/utils/get-newlines-errors.ts
@@ -1,6 +1,11 @@
 import type { TSESLint } from '@typescript-eslint/utils'
 
-import type { GroupsOptions } from '../types/common-options'
+import type {
+  DeprecatedCustomGroupsOption,
+  NewlinesBetweenOption,
+  CustomGroupsOption,
+  GroupsOptions,
+} from '../types/common-options'
 import type { SortingNode } from '../types/sorting-node'
 
 import { getNewlinesBetweenOption } from './get-newlines-between-option'
@@ -8,8 +13,8 @@ import { getLinesBetween } from './get-lines-between'
 
 interface GetNewlinesErrorsParameters<T extends string> {
   options: {
-    customGroups?: Record<string, string[] | string> | CustomGroup[]
-    newlinesBetween: 'ignore' | 'always' | 'never'
+    customGroups?: DeprecatedCustomGroupsOption | CustomGroupsOption
+    newlinesBetween: NewlinesBetweenOption
     groups: GroupsOptions<string>
   }
   sourceCode: TSESLint.SourceCode
@@ -21,11 +26,6 @@ interface GetNewlinesErrorsParameters<T extends string> {
   leftNum: number
 }
 
-interface CustomGroup {
-  newlinesInside?: 'always' | 'never'
-  groupName: string
-}
-
 export let getNewlinesErrors = <T extends string>({
   missedSpacingError,
   extraSpacingError,
diff --git a/utils/get-settings.ts b/utils/get-settings.ts
index 8d03d01..a4aa83c 100644
--- a/utils/get-settings.ts
+++ b/utils/get-settings.ts
@@ -1,5 +1,11 @@
 import type { TSESLint } from '@typescript-eslint/utils'
 
+import type {
+  SpecialCharactersOption,
+  OrderOption,
+  TypeOption,
+} from '../types/common-options'
+
 export type Settings = Partial<{
   partitionByComment:
     | {
@@ -9,13 +15,13 @@ export type Settings = Partial<{
     | string[]
     | boolean
     | string
-  type: 'alphabetical' | 'line-length' | 'natural' | 'custom'
-  specialCharacters: 'remove' | 'trim' | 'keep'
+  specialCharacters: SpecialCharactersOption
   locales: NonNullable<Intl.LocalesArgument>
   partitionByNewLine: boolean
   ignorePattern: string[]
-  order: 'desc' | 'asc'
   ignoreCase: boolean
+  order: OrderOption
+  type: TypeOption
   alphabet: string
 }>
 
diff --git a/utils/is-positive.ts b/utils/is-positive.ts
deleted file mode 100644
index 80e9005..0000000
--- a/utils/is-positive.ts
+++ /dev/null
@@ -1,1 +0,0 @@
-export let isPositive = (number: number): boolean => number > 0
diff --git a/utils/make-fixes.ts b/utils/make-fixes.ts
index 6a97a5e..d9d60fe 100644
--- a/utils/make-fixes.ts
+++ b/utils/make-fixes.ts
@@ -1,7 +1,10 @@
 import type { TSESLint } from '@typescript-eslint/utils'
 
 import type {
+  DeprecatedCustomGroupsOption,
   PartitionByCommentOption,
+  NewlinesBetweenOption,
+  CustomGroupsOption,
   GroupsOptions,
 } from '../types/common-options'
 import type { SortingNode } from '../types/sorting-node'
@@ -12,9 +15,9 @@ import { makeOrderFixes } from './make-order-fixes'
 
 export interface MakeFixesParameters {
   options?: {
-    customGroups?: Record<string, string[] | string> | CustomGroup[]
-    newlinesBetween?: 'ignore' | 'always' | 'never'
+    customGroups?: DeprecatedCustomGroupsOption | CustomGroupsOption
     partitionByComment?: PartitionByCommentOption
+    newlinesBetween?: NewlinesBetweenOption
     groups?: GroupsOptions<string>
   }
   ignoreFirstNodeHighestBlockComment?: boolean
@@ -24,11 +27,6 @@ export interface MakeFixesParameters {
   nodes: SortingNode[]
 }
 
-interface CustomGroup {
-  newlinesInside?: 'always' | 'never'
-  groupName: string
-}
-
 export let makeFixes = ({
   ignoreFirstNodeHighestBlockComment,
   sortedNodes,
diff --git a/utils/make-newlines-fixes.ts b/utils/make-newlines-fixes.ts
index 17dbbd1..5c1bd50 100644
--- a/utils/make-newlines-fixes.ts
+++ b/utils/make-newlines-fixes.ts
@@ -1,32 +1,27 @@
 import type { TSESLint } from '@typescript-eslint/utils'
 
+import type {
+  DeprecatedCustomGroupsOption,
+  NewlinesBetweenOption,
+  CustomGroupsOption,
+  GroupsOptions,
+} from '../types/common-options'
 import type { SortingNode } from '../types/sorting-node'
 
 import { getNewlinesBetweenOption } from './get-newlines-between-option'
 import { getLinesBetween } from './get-lines-between'
 import { getNodeRange } from './get-node-range'
 
-interface Options {
-  groups: (
-    | { newlinesBetween: 'ignore' | 'always' | 'never' }
-    | string[]
-    | string
-  )[]
-  customGroups?: Record<string, string[] | string> | CustomGroup[]
-  newlinesBetween: 'ignore' | 'always' | 'never'
-}
-
 interface MakeNewlinesFixesParameters {
+  options: {
+    customGroups?: DeprecatedCustomGroupsOption | CustomGroupsOption
+    newlinesBetween: NewlinesBetweenOption
+    groups: GroupsOptions<string>
+  }
   sourceCode: TSESLint.SourceCode
   sortedNodes: SortingNode[]
   fixer: TSESLint.RuleFixer
   nodes: SortingNode[]
-  options: Options
-}
-
-interface CustomGroup {
-  newlinesInside?: 'always' | 'never'
-  groupName: string
 }
 
 export let makeNewlinesFixes = ({
diff --git a/utils/report-all-errors.ts b/utils/report-all-errors.ts
index 771274f..4dee681 100644
--- a/utils/report-all-errors.ts
+++ b/utils/report-all-errors.ts
@@ -23,7 +23,7 @@ interface ReportAllErrorsParameters<MessageIds extends string> {
   sortNodesExcludingEslintDisabled(
     ignoreEslintDisabledNodes: boolean,
   ): SortingNode[]
-  options?: {
+  options: {
     groups?: GroupsOptions<string>
   } & MakeFixesParameters['options']
   context: TSESLint.RuleContext<MessageIds, unknown[]>
@@ -47,10 +47,8 @@ export let reportAllErrors = <MessageIds extends string>({
   let nodeIndexMap = createNodeIndexMap(sortedNodes)
 
   pairwise(nodes, (left, right) => {
-    let leftNumber = options?.groups ? getGroupNumber(options.groups, left) : 0
-    let rightNumber = options?.groups
-      ? getGroupNumber(options.groups, right)
-      : 0
+    let leftNumber = options.groups ? getGroupNumber(options.groups, left) : 0
+    let rightNumber = options.groups ? getGroupNumber(options.groups, right) : 0
 
     let leftIndex = nodeIndexMap.get(left)!
     let rightIndex = nodeIndexMap.get(right)!
@@ -88,7 +86,7 @@ export let reportAllErrors = <MessageIds extends string>({
     }
 
     if (
-      options?.newlinesBetween &&
+      options.newlinesBetween &&
       options.groups &&
       availableMessageIds.missedSpacingBetweenMembers &&
       availableMessageIds.extraSpacingBetweenMembers
diff --git a/utils/report-errors.ts b/utils/report-errors.ts
index dae0191..342891c 100644
--- a/utils/report-errors.ts
+++ b/utils/report-errors.ts
@@ -6,6 +6,23 @@ import type { MakeFixesParameters } from './make-fixes'
 import { toSingleLine } from './to-single-line'
 import { makeFixes } from './make-fixes'
 
+const NODE_DEPENDENT_ON_RIGHT = 'nodeDependentOnRight'
+export const RIGHT = 'right'
+const RIGHT_GROUP = 'rightGroup'
+export const LEFT = 'left'
+const LEFT_GROUP = 'leftGroup'
+
+export const ORDER_ERROR =
+  `Expected "{{${RIGHT}}" to come before "{{${LEFT}}".` as const
+export const DEPENDENCY_ORDER_ERROR =
+  `Expected dependency "{{${RIGHT}}" to come before "{{${NODE_DEPENDENT_ON_RIGHT}}}".` as const
+export const GROUP_ORDER_ERROR =
+  `Expected "{{${RIGHT}}" ({{${RIGHT_GROUP}}) to come before "{{${LEFT}}" ({{${LEFT_GROUP}}).` as const
+export const EXTRA_SPACING_ERROR =
+  `Extra spacing between "{{${LEFT}}" and "{{${RIGHT}}" objects.` as const
+export const MISSED_SPACING_ERROR =
+  `Missed spacing between "{{${LEFT}}" and "{{${RIGHT}}".` as const
+
 interface ReportErrorsParameters<MessageIds extends string> {
   context: TSESLint.RuleContext<MessageIds, unknown[]>
   firstUnorderedNodeDependentOnRight?: SortingNode
@@ -34,11 +51,11 @@ export let reportErrors = <MessageIds extends string>({
   for (let messageId of messageIds) {
     context.report({
       data: {
-        nodeDependentOnRight: firstUnorderedNodeDependentOnRight?.name,
-        right: toSingleLine(right.name),
-        left: toSingleLine(left.name),
-        rightGroup: right.group,
-        leftGroup: left.group,
+        [NODE_DEPENDENT_ON_RIGHT]: firstUnorderedNodeDependentOnRight?.name,
+        [RIGHT]: toSingleLine(right.name),
+        [LEFT]: toSingleLine(left.name),
+        [RIGHT_GROUP]: right.group,
+        [LEFT_GROUP]: left.group,
       },
       fix: (fixer: TSESLint.RuleFixer) =>
         makeFixes({
diff --git a/utils/sort-nodes-by-dependencies.ts b/utils/sort-nodes-by-dependencies.ts
index b6cdb0b..6b43796 100644
--- a/utils/sort-nodes-by-dependencies.ts
+++ b/utils/sort-nodes-by-dependencies.ts
@@ -15,7 +15,7 @@ export interface SortingNodeWithDependencies<
 }
 
 interface ExtraOptions {
-  ignoreEslintDisabledNodes?: boolean
+  ignoreEslintDisabledNodes: boolean
 }
 
 /**
@@ -27,7 +27,7 @@ interface ExtraOptions {
  */
 export let sortNodesByDependencies = <T extends SortingNodeWithDependencies>(
   nodes: T[],
-  extraOptions?: ExtraOptions,
+  extraOptions: ExtraOptions,
 ): T[] => {
   let result: T[] = []
   let visitedNodes = new Set<T>()
@@ -48,7 +48,7 @@ export let sortNodesByDependencies = <T extends SortingNodeWithDependencies>(
     )
     for (let dependentNode of dependentNodes) {
       if (
-        !extraOptions?.ignoreEslintDisabledNodes ||
+        !extraOptions.ignoreEslintDisabledNodes ||
         !dependentNode.isEslintDisabled
       ) {
         visitNode(dependentNode)
diff --git a/utils/sort-nodes-by-groups.ts b/utils/sort-nodes-by-groups.ts
index 3476cfa..69b122a 100644
--- a/utils/sort-nodes-by-groups.ts
+++ b/utils/sort-nodes-by-groups.ts
@@ -11,7 +11,7 @@ interface ExtraOptions<T extends SortingNode> {
    * will not be sorted within the group.
    */
   getGroupCompareOptions?(groupNumber: number): CompareOptions<T> | null
-  ignoreEslintDisabledNodes?: boolean
+  ignoreEslintDisabledNodes: boolean
   isNodeIgnored?(node: T): boolean
 }
 
diff --git a/utils/sort-nodes.ts b/utils/sort-nodes.ts
index ef9c2ec..25b3399 100644
--- a/utils/sort-nodes.ts
+++ b/utils/sort-nodes.ts
@@ -4,7 +4,7 @@ import type { CompareOptions } from './compare'
 import { compare } from './compare'
 
 interface ExtraOptions {
-  ignoreEslintDisabledNodes?: boolean
+  ignoreEslintDisabledNodes: boolean
 }
 
 export let sortNodes = <T extends SortingNode>(
diff --git a/utils/use-groups.ts b/utils/use-groups.ts
index 26a72c6..3a46cbd 100644
--- a/utils/use-groups.ts
+++ b/utils/use-groups.ts
@@ -1,8 +1,13 @@
+import type {
+  DeprecatedCustomGroupsOption,
+  NewlinesBetweenOption,
+} from '../types/common-options'
+
 import { matches } from './matches'
 
 interface UseGroupsValue {
   setCustomGroups(
-    customGroups: Record<string, string[] | string> | undefined,
+    customGroups: DeprecatedCustomGroupsOption | undefined,
     name: string,
     parameters?: {
       override?: boolean
@@ -12,15 +17,11 @@ interface UseGroupsValue {
   getGroup(): string
 }
 
-interface UseGroupProps {
-  groups: (
-    | { newlinesBetween: 'ignore' | 'always' | 'never' }
-    | string[]
-    | string
-  )[]
+interface UseGroupParameters {
+  groups: ({ newlinesBetween: NewlinesBetweenOption } | string[] | string)[]
 }
 
-export let useGroups = ({ groups }: UseGroupProps): UseGroupsValue => {
+export let useGroups = ({ groups }: UseGroupParameters): UseGroupsValue => {
   let group: undefined | string
   // For lookup performance.
   let groupsSet = new Set(groups.flat())
@@ -32,7 +33,7 @@ export let useGroups = ({ groups }: UseGroupProps): UseGroupsValue => {
   }
 
   let setCustomGroups = (
-    customGroups: Record<string, string[] | string> | undefined,
+    customGroups: DeprecatedCustomGroupsOption | undefined,
     name: string,
     parameters: { override?: boolean } = {},
   ): void => {
diff --git a/utils/validate-custom-sort-configuration.ts b/utils/validate-custom-sort-configuration.ts
index 792ee3b..77cfc08 100644
--- a/utils/validate-custom-sort-configuration.ts
+++ b/utils/validate-custom-sort-configuration.ts
@@ -1,5 +1,7 @@
+import type { TypeOption } from '../types/common-options'
+
 interface Options {
-  type: 'alphabetical' | 'line-length' | 'natural' | 'custom'
+  type: TypeOption
   alphabet: string
 }
 
diff --git a/utils/validate-generated-groups-configuration.ts b/utils/validate-generated-groups-configuration.ts
index 1c8dadc..00c1a0f 100644
--- a/utils/validate-generated-groups-configuration.ts
+++ b/utils/validate-generated-groups-configuration.ts
@@ -1,33 +1,31 @@
+import type {
+  DeprecatedCustomGroupsOption,
+  CustomGroupsOption,
+  GroupsOptions,
+} from '../types/common-options'
+
 import { validateNoDuplicatedGroups } from './validate-groups-configuration'
 
 interface ValidateGenerateGroupsConfigurationParameters {
-  customGroups: Record<string, string[] | string> | BaseCustomGroup[]
+  options: {
+    customGroups: DeprecatedCustomGroupsOption | CustomGroupsOption
+    groups: GroupsOptions<string>
+  }
   selectors: string[]
   modifiers: string[]
-  groups: Group[]
-}
-
-type Group =
-  | { newlinesBetween: 'ignore' | 'always' | 'never' }
-  | string[]
-  | string
-
-interface BaseCustomGroup {
-  groupName: string
 }
 
 export let validateGeneratedGroupsConfiguration = ({
-  customGroups,
   selectors,
   modifiers,
-  groups,
+  options,
 }: ValidateGenerateGroupsConfigurationParameters): void => {
   let availableCustomGroupNames = new Set(
-    Array.isArray(customGroups)
-      ? customGroups.map(customGroup => customGroup.groupName)
-      : Object.keys(customGroups),
+    Array.isArray(options.customGroups)
+      ? options.customGroups.map(customGroup => customGroup.groupName)
+      : Object.keys(options.customGroups),
   )
-  let invalidGroups = groups
+  let invalidGroups = options.groups
     .flat()
     .filter(group => typeof group === 'string')
     .filter(
@@ -38,7 +36,7 @@ export let validateGeneratedGroupsConfiguration = ({
   if (invalidGroups.length > 0) {
     throw new Error(`Invalid group(s): ${invalidGroups.join(', ')}`)
   }
-  validateNoDuplicatedGroups(groups)
+  validateNoDuplicatedGroups(options.groups)
 }
 
 let isPredefinedGroup = (
diff --git a/utils/validate-groups-configuration.ts b/utils/validate-groups-configuration.ts
index daf452d..63649a0 100644
--- a/utils/validate-groups-configuration.ts
+++ b/utils/validate-groups-configuration.ts
@@ -1,7 +1,6 @@
-type Group =
-  | { newlinesBetween: 'ignore' | 'always' | 'never' }
-  | string[]
-  | string
+import type { NewlinesBetweenOption } from '../types/common-options'
+
+type Group = { newlinesBetween: NewlinesBetweenOption } | string[] | string
 
 /**
  * Throws an error if one of the following conditions is met:
diff --git a/utils/validate-newlines-and-partition-configuration.ts b/utils/validate-newlines-and-partition-configuration.ts
index fb4138e..4ace88b 100644
--- a/utils/validate-newlines-and-partition-configuration.ts
+++ b/utils/validate-newlines-and-partition-configuration.ts
@@ -1,7 +1,10 @@
-import type { GroupsOptions } from '../types/common-options'
+import type {
+  NewlinesBetweenOption,
+  GroupsOptions,
+} from '../types/common-options'
 
 interface Options {
-  newlinesBetween: 'ignore' | 'always' | 'never'
+  newlinesBetween: NewlinesBetweenOption
   groups: GroupsOptions<string>
   partitionByNewLine: boolean
 }


Test patch:
diff --git a/test/utils/validate-generated-groups-configuration.test.ts b/test/utils/validate-generated-groups-configuration.test.ts
index 2266263..4c75277 100644
--- a/test/utils/validate-generated-groups-configuration.test.ts
+++ b/test/utils/validate-generated-groups-configuration.test.ts
@@ -18,10 +18,12 @@ describe('validate-generated-groups-configuration', () => {
     ]
     expect(() =>
       validateGeneratedGroupsConfiguration({
-        groups: allPredefinedGroups,
+        options: {
+          groups: allPredefinedGroups,
+          customGroups: [],
+        },
         selectors: allSelectors,
         modifiers: allModifiers,
-        customGroups: [],
       }),
     ).not.toThrow()
   })
@@ -29,12 +31,14 @@ describe('validate-generated-groups-configuration', () => {
   it('allows custom groups', () => {
     expect(() =>
       validateGeneratedGroupsConfiguration({
-        customGroups: [
-          {
-            groupName: 'myCustomGroup',
-          },
-        ],
-        groups: ['static-property', 'myCustomGroup'],
+        options: {
+          customGroups: [
+            {
+              groupName: 'myCustomGroup',
+            },
+          ],
+          groups: ['static-property', 'myCustomGroup'],
+        },
         selectors: allSelectors,
         modifiers: allModifiers,
       }),
@@ -44,10 +48,12 @@ describe('validate-generated-groups-configuration', () => {
   it('throws an error with predefined groups with duplicate modifiers', () => {
     expect(() =>
       validateGeneratedGroupsConfiguration({
-        groups: ['static-static-property'],
+        options: {
+          groups: ['static-static-property'],
+          customGroups: [],
+        },
         selectors: allSelectors,
         modifiers: allModifiers,
-        customGroups: [],
       }),
     ).toThrow('Invalid group(s): static-static-property')
   })
@@ -55,10 +61,12 @@ describe('validate-generated-groups-configuration', () => {
   it('throws an error if a duplicate group is provided', () => {
     expect(() =>
       validateGeneratedGroupsConfiguration({
-        groups: ['static-property', 'static-property'],
+        options: {
+          groups: ['static-property', 'static-property'],
+          customGroups: [],
+        },
         selectors: allSelectors,
         modifiers: allModifiers,
-        customGroups: [],
       }),
     ).toThrow('Duplicated group(s): static-property')
   })
@@ -66,12 +74,14 @@ describe('validate-generated-groups-configuration', () => {
   it('throws an error if invalid groups are provided', () => {
     expect(() =>
       validateGeneratedGroupsConfiguration({
-        customGroups: [
-          {
-            groupName: 'myCustomGroupNotReferenced',
-          },
-        ],
-        groups: ['static-property', 'myCustomGroup', ''],
+        options: {
+          customGroups: [
+            {
+              groupName: 'myCustomGroupNotReferenced',
+            },
+          ],
+          groups: ['static-property', 'myCustomGroup', ''],
+        },
         selectors: allSelectors,
         modifiers: allModifiers,
       }),