Repository: azat-io/eslint-plugin-perfectionist
Pull request #496 (2025-03-23 14:48:39)

refactor: remove duplicate `does-custom-group-match`
### Description

This PR creates a `util/does-custom-group-match` function, allowing us to remove custom functions created for various rules.

### What is the purpose of this pull request?

- [x] Other


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

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


```diff
@@            Coverage Diff             @@
##              main      #496    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files           96        89     -7     
  Lines         7732      7485   -247     
  Branches      1656      1552   -104     
==========================================
- Hits          7732      7485   -247     
```

</details>

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

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

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

Patch:
diff --git a/rules/sort-array-includes.ts b/rules/sort-array-includes.ts
index 4fea1e1..e63fcc2 100644
--- a/rules/sort-array-includes.ts
+++ b/rules/sort-array-includes.ts
@@ -29,11 +29,11 @@ import {
   singleCustomGroupJsonSchema,
   allSelectors,
 } from './sort-array-includes/types'
-import { doesCustomGroupMatch } from './sort-array-includes/does-custom-group-match'
 import { getMatchingContextOptions } from '../utils/get-matching-context-options'
 import { generatePredefinedGroups } from '../utils/generate-predefined-groups'
 import { getEslintDisabledLines } from '../utils/get-eslint-disabled-lines'
 import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled'
+import { doesCustomGroupMatch } from '../utils/does-custom-group-match'
 import { sortNodesByGroups } from '../utils/sort-nodes-by-groups'
 import { createEslintRule } from '../utils/create-eslint-rule'
 import { reportAllErrors } from '../utils/report-all-errors'
@@ -228,6 +228,7 @@ export let sortArray = <MessageIds extends string>({
           doesCustomGroupMatch({
             selectors: [selector],
             elementName: name,
+            modifiers: [],
             customGroup,
           })
         ) {
diff --git a/rules/sort-array-includes/does-custom-group-match.ts b/rules/sort-array-includes/does-custom-group-match.ts
deleted file mode 100644
index d8deabe..0000000
--- a/rules/sort-array-includes/does-custom-group-match.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import type { AnyOfCustomGroup } from '../../types/common-options'
-import type { SingleCustomGroup, Selector } from './types'
-
-import { matches } from '../../utils/matches'
-
-interface DoesCustomGroupMatchParameters {
-  customGroup: AnyOfCustomGroup<SingleCustomGroup> | SingleCustomGroup
-  selectors: Selector[]
-  elementName: string
-}
-
-/**
- * Determines whether a custom group matches the given properties.
- * @param {DoesCustomGroupMatchParameters} props - The properties to compare
- * with the custom group, including selectors, modifiers, and element name.
- * @returns {boolean} `true` if the custom group matches the properties;
- * otherwise, `false`.
- */
-export let doesCustomGroupMatch = (
-  props: DoesCustomGroupMatchParameters,
-): boolean => {
-  if ('anyOf' in props.customGroup) {
-    // At least one subgroup must match
-    return props.customGroup.anyOf.some(subgroup =>
-      doesCustomGroupMatch({ ...props, customGroup: subgroup }),
-    )
-  }
-  if (
-    props.customGroup.selector &&
-    !props.selectors.includes(props.customGroup.selector)
-  ) {
-    return false
-  }
-
-  if (
-    'elementNamePattern' in props.customGroup &&
-    props.customGroup.elementNamePattern
-  ) {
-    let matchesElementNamePattern: boolean = matches(
-      props.elementName,
-      props.customGroup.elementNamePattern,
-    )
-    if (!matchesElementNamePattern) {
-      return false
-    }
-  }
-
-  return true
-}
diff --git a/rules/sort-classes.ts b/rules/sort-classes.ts
index 8351a21..5ebd586 100644
--- a/rules/sort-classes.ts
+++ b/rules/sort-classes.ts
@@ -36,9 +36,9 @@ import { validateCustomSortConfiguration } from '../utils/validate-custom-sort-c
 import { getOverloadSignatureGroups } from './sort-classes/get-overload-signature-groups'
 import { generatePredefinedGroups } from '../utils/generate-predefined-groups'
 import { sortNodesByDependencies } from '../utils/sort-nodes-by-dependencies'
-import { doesCustomGroupMatch } from './sort-classes/does-custom-group-match'
 import { getEslintDisabledLines } from '../utils/get-eslint-disabled-lines'
 import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled'
+import { doesCustomGroupMatch } from '../utils/does-custom-group-match'
 import { sortNodesByGroups } from '../utils/sort-nodes-by-groups'
 import { getNodeDecorators } from '../utils/get-node-decorators'
 import { createEslintRule } from '../utils/create-eslint-rule'
diff --git a/rules/sort-classes/does-custom-group-match.ts b/rules/sort-classes/does-custom-group-match.ts
deleted file mode 100644
index 72c1295..0000000
--- a/rules/sort-classes/does-custom-group-match.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-import type { SingleCustomGroup, Modifier, Selector } from './types'
-import type { AnyOfCustomGroup } from '../../types/common-options'
-
-import { matches } from '../../utils/matches'
-
-interface DoesCustomGroupMatchParameters {
-  customGroup: AnyOfCustomGroup<SingleCustomGroup> | SingleCustomGroup
-  elementValue: undefined | string
-  selectors: Selector[]
-  modifiers: Modifier[]
-  decorators: string[]
-  elementName: string
-}
-
-/**
- * Determines whether a custom group matches the given properties.
- * @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: DoesCustomGroupMatchParameters,
-): boolean => {
-  if ('anyOf' in props.customGroup) {
-    // At least one subgroup must match.
-    return props.customGroup.anyOf.some(subgroup =>
-      doesCustomGroupMatch({ ...props, customGroup: subgroup }),
-    )
-  }
-  if (
-    props.customGroup.selector &&
-    !props.selectors.includes(props.customGroup.selector)
-  ) {
-    return false
-  }
-
-  if (props.customGroup.modifiers) {
-    for (let modifier of props.customGroup.modifiers) {
-      if (!props.modifiers.includes(modifier)) {
-        return false
-      }
-    }
-  }
-
-  if (
-    'elementNamePattern' in props.customGroup &&
-    props.customGroup.elementNamePattern
-  ) {
-    let matchesElementNamePattern: boolean = matches(
-      props.elementName,
-      props.customGroup.elementNamePattern,
-    )
-    if (!matchesElementNamePattern) {
-      return false
-    }
-  }
-
-  if (
-    'elementValuePattern' in props.customGroup &&
-    props.customGroup.elementValuePattern
-  ) {
-    let matchesElementValuePattern: boolean = matches(
-      props.elementValue ?? '',
-      props.customGroup.elementValuePattern,
-    )
-    if (!matchesElementValuePattern) {
-      return false
-    }
-  }
-
-  if (
-    'decoratorNamePattern' in props.customGroup &&
-    props.customGroup.decoratorNamePattern
-  ) {
-    let decoratorPattern = props.customGroup.decoratorNamePattern
-    let matchesDecoratorNamePattern: boolean = props.decorators.some(
-      decorator => matches(decorator, decoratorPattern),
-    )
-    if (!matchesDecoratorNamePattern) {
-      return false
-    }
-  }
-
-  return true
-}
diff --git a/rules/sort-enums.ts b/rules/sort-enums.ts
index 5a696c0..3d8effb 100644
--- a/rules/sort-enums.ts
+++ b/rules/sort-enums.ts
@@ -27,8 +27,8 @@ import { validateCustomSortConfiguration } from '../utils/validate-custom-sort-c
 import { getCustomGroupOverriddenOptions } from '../utils/get-custom-groups-compare-options'
 import { sortNodesByDependencies } from '../utils/sort-nodes-by-dependencies'
 import { getEslintDisabledLines } from '../utils/get-eslint-disabled-lines'
-import { doesCustomGroupMatch } from './sort-enums/does-custom-group-match'
 import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled'
+import { doesCustomGroupMatch } from '../utils/does-custom-group-match'
 import { sortNodesByGroups } from '../utils/sort-nodes-by-groups'
 import { singleCustomGroupJsonSchema } from './sort-enums/types'
 import { createEslintRule } from '../utils/create-eslint-rule'
@@ -153,6 +153,8 @@ export default createEslintRule<Options, MESSAGE_ID>({
               doesCustomGroupMatch({
                 elementValue: sourceCode.getText(member.initializer),
                 elementName: name,
+                selectors: [],
+                modifiers: [],
                 customGroup,
               })
             ) {
diff --git a/rules/sort-enums/does-custom-group-match.ts b/rules/sort-enums/does-custom-group-match.ts
deleted file mode 100644
index 5fb5e3d..0000000
--- a/rules/sort-enums/does-custom-group-match.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import type { AnyOfCustomGroup } from '../../types/common-options'
-import type { SingleCustomGroup } from './types'
-
-import { matches } from '../../utils/matches'
-
-interface DoesCustomGroupMatchProps {
-  customGroup: AnyOfCustomGroup<SingleCustomGroup> | SingleCustomGroup
-  elementValue: string
-  elementName: string
-}
-
-export let doesCustomGroupMatch = (
-  props: DoesCustomGroupMatchProps,
-): boolean => {
-  if ('anyOf' in props.customGroup) {
-    // At least one subgroup must match
-    return props.customGroup.anyOf.some(subgroup =>
-      doesCustomGroupMatch({ ...props, customGroup: subgroup }),
-    )
-  }
-
-  if (
-    'elementNamePattern' in props.customGroup &&
-    props.customGroup.elementNamePattern
-  ) {
-    let matchesElementNamePattern: boolean = matches(
-      props.elementName,
-      props.customGroup.elementNamePattern,
-    )
-    if (!matchesElementNamePattern) {
-      return false
-    }
-  }
-
-  if (
-    'elementValuePattern' in props.customGroup &&
-    props.customGroup.elementValuePattern
-  ) {
-    let matchesElementValuePattern: boolean = matches(
-      props.elementValue,
-      props.customGroup.elementValuePattern,
-    )
-    if (!matchesElementValuePattern) {
-      return false
-    }
-  }
-
-  return true
-}
diff --git a/rules/sort-exports.ts b/rules/sort-exports.ts
index 29e740c..f2e2f18 100644
--- a/rules/sort-exports.ts
+++ b/rules/sort-exports.ts
@@ -22,9 +22,9 @@ import { buildGetCustomGroupOverriddenOptionsFunction } from '../utils/get-custo
 import { validateGeneratedGroupsConfiguration } from '../utils/validate-generated-groups-configuration'
 import { validateCustomSortConfiguration } from '../utils/validate-custom-sort-configuration'
 import { generatePredefinedGroups } from '../utils/generate-predefined-groups'
-import { doesCustomGroupMatch } from './sort-exports/does-custom-group-match'
 import { getEslintDisabledLines } from '../utils/get-eslint-disabled-lines'
 import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled'
+import { doesCustomGroupMatch } from '../utils/does-custom-group-match'
 import { singleCustomGroupJsonSchema } from './sort-exports/types'
 import { allModifiers, allSelectors } from './sort-exports/types'
 import { sortNodesByGroups } from '../utils/sort-nodes-by-groups'
@@ -119,6 +119,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
       for (let customGroup of options.customGroups) {
         if (
           doesCustomGroupMatch({
+            selectors: [selector],
             elementName: name,
             customGroup,
             modifiers,
diff --git a/rules/sort-exports/does-custom-group-match.ts b/rules/sort-exports/does-custom-group-match.ts
deleted file mode 100644
index 7e55cf1..0000000
--- a/rules/sort-exports/does-custom-group-match.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import type { AnyOfCustomGroup } from '../../types/common-options'
-import type { SingleCustomGroup, Modifier } from './types'
-
-import { matches } from '../../utils/matches'
-
-interface DoesCustomGroupMatchParameters {
-  customGroup: AnyOfCustomGroup<SingleCustomGroup> | SingleCustomGroup
-  modifiers: Modifier[]
-  elementName: string
-}
-
-export let doesCustomGroupMatch = (
-  props: DoesCustomGroupMatchParameters,
-): boolean => {
-  if ('anyOf' in props.customGroup) {
-    return props.customGroup.anyOf.some(subgroup =>
-      doesCustomGroupMatch({ ...props, customGroup: subgroup }),
-    )
-  }
-
-  if (props.customGroup.modifiers) {
-    for (let modifier of props.customGroup.modifiers) {
-      if (!props.modifiers.includes(modifier)) {
-        return false
-      }
-    }
-  }
-
-  if (
-    'elementNamePattern' in props.customGroup &&
-    props.customGroup.elementNamePattern
-  ) {
-    let matchesElementNamePattern: boolean = matches(
-      props.elementName,
-      props.customGroup.elementNamePattern,
-    )
-    if (!matchesElementNamePattern) {
-      return false
-    }
-  }
-
-  return true
-}
diff --git a/rules/sort-maps.ts b/rules/sort-maps.ts
index 725e46c..9142d39 100644
--- a/rules/sort-maps.ts
+++ b/rules/sort-maps.ts
@@ -24,8 +24,8 @@ import { validateGeneratedGroupsConfiguration } from '../utils/validate-generate
 import { validateCustomSortConfiguration } from '../utils/validate-custom-sort-configuration'
 import { getMatchingContextOptions } from '../utils/get-matching-context-options'
 import { getEslintDisabledLines } from '../utils/get-eslint-disabled-lines'
-import { doesCustomGroupMatch } from './sort-maps/does-custom-group-match'
 import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled'
+import { doesCustomGroupMatch } from '../utils/does-custom-group-match'
 import { sortNodesByGroups } from '../utils/sort-nodes-by-groups'
 import { singleCustomGroupJsonSchema } from './sort-maps/types'
 import { createEslintRule } from '../utils/create-eslint-rule'
@@ -129,6 +129,8 @@ export default createEslintRule<Options, MESSAGE_ID>({
             if (
               doesCustomGroupMatch({
                 elementName: name,
+                selectors: [],
+                modifiers: [],
                 customGroup,
               })
             ) {
diff --git a/rules/sort-maps/does-custom-group-match.ts b/rules/sort-maps/does-custom-group-match.ts
deleted file mode 100644
index f98dbec..0000000
--- a/rules/sort-maps/does-custom-group-match.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import type { AnyOfCustomGroup } from '../../types/common-options'
-import type { SingleCustomGroup } from './types'
-
-import { matches } from '../../utils/matches'
-
-interface DoesCustomGroupMatchParameters {
-  customGroup: AnyOfCustomGroup<SingleCustomGroup> | SingleCustomGroup
-  elementName: string
-}
-
-export let doesCustomGroupMatch = (
-  props: DoesCustomGroupMatchParameters,
-): boolean => {
-  if ('anyOf' in props.customGroup) {
-    // At least one subgroup must match
-    return props.customGroup.anyOf.some(subgroup =>
-      doesCustomGroupMatch({ ...props, customGroup: subgroup }),
-    )
-  }
-
-  if (
-    'elementNamePattern' in props.customGroup &&
-    props.customGroup.elementNamePattern
-  ) {
-    let matchesElementNamePattern: boolean = matches(
-      props.elementName,
-      props.customGroup.elementNamePattern,
-    )
-    if (!matchesElementNamePattern) {
-      return false
-    }
-  }
-
-  return true
-}
diff --git a/rules/sort-modules.ts b/rules/sort-modules.ts
index 180b971..66880ab 100644
--- a/rules/sort-modules.ts
+++ b/rules/sort-modules.ts
@@ -36,9 +36,9 @@ import {
 import { validateCustomSortConfiguration } from '../utils/validate-custom-sort-configuration'
 import { generatePredefinedGroups } from '../utils/generate-predefined-groups'
 import { sortNodesByDependencies } from '../utils/sort-nodes-by-dependencies'
-import { doesCustomGroupMatch } from './sort-modules/does-custom-group-match'
 import { getEslintDisabledLines } from '../utils/get-eslint-disabled-lines'
 import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled'
+import { doesCustomGroupMatch } from '../utils/does-custom-group-match'
 import { sortNodesByGroups } from '../utils/sort-nodes-by-groups'
 import { getNodeDecorators } from '../utils/get-node-decorators'
 import { createEslintRule } from '../utils/create-eslint-rule'
diff --git a/rules/sort-modules/does-custom-group-match.ts b/rules/sort-modules/does-custom-group-match.ts
deleted file mode 100644
index 6cf9fcc..0000000
--- a/rules/sort-modules/does-custom-group-match.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import type { SingleCustomGroup, Modifier, Selector } from './types'
-import type { AnyOfCustomGroup } from '../../types/common-options'
-
-import { matches } from '../../utils/matches'
-
-interface DoesCustomGroupMatchParameters {
-  customGroup: AnyOfCustomGroup<SingleCustomGroup> | SingleCustomGroup
-  selectors: Selector[]
-  modifiers: Modifier[]
-  decorators: string[]
-  elementName: string
-}
-
-/**
- * Determines whether a custom group matches the given properties.
- * @param {DoesCustomGroupMatchParameters} props - The properties to compare
- * with the custom group, including selectors, modifiers, decorators, and element name.
- * @returns {boolean} `true` if the custom group matches the properties;
- * otherwise, `false`.
- */
-export let doesCustomGroupMatch = (
-  props: DoesCustomGroupMatchParameters,
-): boolean => {
-  if ('anyOf' in props.customGroup) {
-    // At least one subgroup must match.
-    return props.customGroup.anyOf.some(subgroup =>
-      doesCustomGroupMatch({ ...props, customGroup: subgroup }),
-    )
-  }
-  if (
-    props.customGroup.selector &&
-    !props.selectors.includes(props.customGroup.selector)
-  ) {
-    return false
-  }
-
-  if (props.customGroup.modifiers) {
-    for (let modifier of props.customGroup.modifiers) {
-      if (!props.modifiers.includes(modifier)) {
-        return false
-      }
-    }
-  }
-
-  if (
-    'elementNamePattern' in props.customGroup &&
-    props.customGroup.elementNamePattern
-  ) {
-    let matchesElementNamePattern: boolean = matches(
-      props.elementName,
-      props.customGroup.elementNamePattern,
-    )
-    if (!matchesElementNamePattern) {
-      return false
-    }
-  }
-
-  if (
-    'decoratorNamePattern' in props.customGroup &&
-    props.customGroup.decoratorNamePattern
-  ) {
-    let decoratorPattern = props.customGroup.decoratorNamePattern
-    let matchesDecoratorNamePattern: boolean = props.decorators.some(
-      decorator => matches(decorator, decoratorPattern),
-    )
-    if (!matchesDecoratorNamePattern) {
-      return false
-    }
-  }
-
-  return true
-}
diff --git a/rules/sort-object-types.ts b/rules/sort-object-types.ts
index aac1370..2ec6838 100644
--- a/rules/sort-object-types.ts
+++ b/rules/sort-object-types.ts
@@ -36,12 +36,12 @@ import {
 import { validateGeneratedGroupsConfiguration } from '../utils/validate-generated-groups-configuration'
 import { getCustomGroupsCompareOptions } from './sort-object-types/get-custom-groups-compare-options'
 import { validateCustomSortConfiguration } from '../utils/validate-custom-sort-configuration'
-import { doesCustomGroupMatch } from './sort-object-types/does-custom-group-match'
 import { getMatchingContextOptions } from '../utils/get-matching-context-options'
 import { generatePredefinedGroups } from '../utils/generate-predefined-groups'
 import { getEslintDisabledLines } from '../utils/get-eslint-disabled-lines'
 import { isMemberOptional } from './sort-object-types/is-member-optional'
 import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled'
+import { doesCustomGroupMatch } from '../utils/does-custom-group-match'
 import { isNodeFunctionType } from '../utils/is-node-function-type'
 import { sortNodesByGroups } from '../utils/sort-nodes-by-groups'
 import { createEslintRule } from '../utils/create-eslint-rule'
diff --git a/rules/sort-object-types/does-custom-group-match.ts b/rules/sort-object-types/does-custom-group-match.ts
deleted file mode 100644
index 5d46bd4..0000000
--- a/rules/sort-object-types/does-custom-group-match.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import type { SingleCustomGroup, Modifier, Selector } from './types'
-import type { AnyOfCustomGroup } from '../../types/common-options'
-
-import { matches } from '../../utils/matches'
-
-interface DoesCustomGroupMatchParameters {
-  customGroup: AnyOfCustomGroup<SingleCustomGroup> | SingleCustomGroup
-  elementValue: string | null
-  selectors: Selector[]
-  modifiers: Modifier[]
-  elementName: string
-}
-
-/**
- * Determines whether a custom group matches the given properties.
- * @param {DoesCustomGroupMatchParameters} props - The properties to compare
- * with the custom group, including selectors, modifiers, and element name.
- * @returns {boolean} `true` if the custom group matches the properties;
- * otherwise, `false`.
- */
-export let doesCustomGroupMatch = (
-  props: DoesCustomGroupMatchParameters,
-): boolean => {
-  if ('anyOf' in props.customGroup) {
-    // At least one subgroup must match.
-    return props.customGroup.anyOf.some(subgroup =>
-      doesCustomGroupMatch({ ...props, customGroup: subgroup }),
-    )
-  }
-  if (
-    props.customGroup.selector &&
-    !props.selectors.includes(props.customGroup.selector)
-  ) {
-    return false
-  }
-
-  if (props.customGroup.modifiers) {
-    for (let modifier of props.customGroup.modifiers) {
-      if (!props.modifiers.includes(modifier)) {
-        return false
-      }
-    }
-  }
-
-  if (
-    'elementNamePattern' in props.customGroup &&
-    props.customGroup.elementNamePattern
-  ) {
-    let matchesElementNamePattern: boolean = matches(
-      props.elementName,
-      props.customGroup.elementNamePattern,
-    )
-    if (!matchesElementNamePattern) {
-      return false
-    }
-  }
-
-  if (
-    'elementValuePattern' in props.customGroup &&
-    props.customGroup.elementValuePattern
-  ) {
-    let matchesElementValuePattern: boolean = matches(
-      props.elementValue ?? '',
-      props.customGroup.elementValuePattern,
-    )
-    if (!matchesElementValuePattern) {
-      return false
-    }
-  }
-
-  return true
-}
diff --git a/rules/sort-objects.ts b/rules/sort-objects.ts
index 3fbb5be..2f98acf 100644
--- a/rules/sort-objects.ts
+++ b/rules/sort-objects.ts
@@ -37,9 +37,9 @@ import { getFirstNodeParentWithType } from './sort-objects/get-first-node-parent
 import { getMatchingContextOptions } from '../utils/get-matching-context-options'
 import { generatePredefinedGroups } from '../utils/generate-predefined-groups'
 import { sortNodesByDependencies } from '../utils/sort-nodes-by-dependencies'
-import { doesCustomGroupMatch } from './sort-objects/does-custom-group-match'
 import { getEslintDisabledLines } from '../utils/get-eslint-disabled-lines'
 import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled'
+import { doesCustomGroupMatch } from '../utils/does-custom-group-match'
 import { sortNodesByGroups } from '../utils/sort-nodes-by-groups'
 import { createEslintRule } from '../utils/create-eslint-rule'
 import { reportAllErrors } from '../utils/report-all-errors'
diff --git a/rules/sort-objects/does-custom-group-match.ts b/rules/sort-objects/does-custom-group-match.ts
deleted file mode 100644
index 5d46bd4..0000000
--- a/rules/sort-objects/does-custom-group-match.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import type { SingleCustomGroup, Modifier, Selector } from './types'
-import type { AnyOfCustomGroup } from '../../types/common-options'
-
-import { matches } from '../../utils/matches'
-
-interface DoesCustomGroupMatchParameters {
-  customGroup: AnyOfCustomGroup<SingleCustomGroup> | SingleCustomGroup
-  elementValue: string | null
-  selectors: Selector[]
-  modifiers: Modifier[]
-  elementName: string
-}
-
-/**
- * Determines whether a custom group matches the given properties.
- * @param {DoesCustomGroupMatchParameters} props - The properties to compare
- * with the custom group, including selectors, modifiers, and element name.
- * @returns {boolean} `true` if the custom group matches the properties;
- * otherwise, `false`.
- */
-export let doesCustomGroupMatch = (
-  props: DoesCustomGroupMatchParameters,
-): boolean => {
-  if ('anyOf' in props.customGroup) {
-    // At least one subgroup must match.
-    return props.customGroup.anyOf.some(subgroup =>
-      doesCustomGroupMatch({ ...props, customGroup: subgroup }),
-    )
-  }
-  if (
-    props.customGroup.selector &&
-    !props.selectors.includes(props.customGroup.selector)
-  ) {
-    return false
-  }
-
-  if (props.customGroup.modifiers) {
-    for (let modifier of props.customGroup.modifiers) {
-      if (!props.modifiers.includes(modifier)) {
-        return false
-      }
-    }
-  }
-
-  if (
-    'elementNamePattern' in props.customGroup &&
-    props.customGroup.elementNamePattern
-  ) {
-    let matchesElementNamePattern: boolean = matches(
-      props.elementName,
-      props.customGroup.elementNamePattern,
-    )
-    if (!matchesElementNamePattern) {
-      return false
-    }
-  }
-
-  if (
-    'elementValuePattern' in props.customGroup &&
-    props.customGroup.elementValuePattern
-  ) {
-    let matchesElementValuePattern: boolean = matches(
-      props.elementValue ?? '',
-      props.customGroup.elementValuePattern,
-    )
-    if (!matchesElementValuePattern) {
-      return false
-    }
-  }
-
-  return true
-}
diff --git a/utils/does-custom-group-match.ts b/utils/does-custom-group-match.ts
new file mode 100644
index 0000000..add928b
--- /dev/null
+++ b/utils/does-custom-group-match.ts
@@ -0,0 +1,104 @@
+import type { AnyOfCustomGroup, RegexOption } from '../types/common-options'
+
+import { matches } from './matches'
+
+export interface DoesCustomGroupMatchParameters {
+  elementValue?: string | null
+  decorators?: string[]
+  modifiers: string[]
+  selectors: string[]
+  elementName: string
+}
+
+interface BaseSingleCustomGroup {
+  decoratorNamePattern?: RegexOption
+  elementValuePattern?: RegexOption
+  elementNamePattern?: RegexOption
+  modifiers?: string[]
+  selector?: string
+}
+
+export let doesCustomGroupMatch = <
+  SingleCustomGroup extends BaseSingleCustomGroup,
+>(
+  props: {
+    customGroup: AnyOfCustomGroup<SingleCustomGroup> | SingleCustomGroup
+  } & DoesCustomGroupMatchParameters,
+): boolean => {
+  if ('anyOf' in props.customGroup) {
+    return props.customGroup.anyOf.some(subgroup =>
+      doesSingleCustomGroupMatch({
+        ...props,
+        customGroup: subgroup,
+      }),
+    )
+  }
+
+  return doesSingleCustomGroupMatch({
+    ...props,
+    customGroup: props.customGroup,
+  })
+}
+
+let doesSingleCustomGroupMatch = ({
+  elementValue,
+  customGroup,
+  elementName,
+  decorators,
+  selectors,
+  modifiers,
+}: {
+  customGroup: BaseSingleCustomGroup
+  elementValue?: string | null
+  decorators?: string[]
+  selectors?: string[]
+  modifiers?: string[]
+  elementName: string
+}): boolean => {
+  if (customGroup.selector && !selectors?.includes(customGroup.selector)) {
+    return false
+  }
+
+  if (customGroup.modifiers) {
+    for (let modifier of customGroup.modifiers) {
+      if (!modifiers?.includes(modifier)) {
+        return false
+      }
+    }
+  }
+
+  if ('elementNamePattern' in customGroup && customGroup.elementNamePattern) {
+    let matchesElementNamePattern: boolean = matches(
+      elementName,
+      customGroup.elementNamePattern,
+    )
+    if (!matchesElementNamePattern) {
+      return false
+    }
+  }
+
+  if ('elementValuePattern' in customGroup && customGroup.elementValuePattern) {
+    let matchesElementValuePattern: boolean = matches(
+      elementValue ?? '',
+      customGroup.elementValuePattern,
+    )
+    if (!matchesElementValuePattern) {
+      return false
+    }
+  }
+
+  if (
+    'decoratorNamePattern' in customGroup &&
+    customGroup.decoratorNamePattern
+  ) {
+    let decoratorPattern = customGroup.decoratorNamePattern
+    let matchesDecoratorNamePattern = decorators?.some(decorator =>
+      matches(decorator, decoratorPattern),
+    )
+    if (!matchesDecoratorNamePattern) {
+      return false
+    }
+  }
+
+  return true
+}


Test patch:
diff --git a/test/utils/does-custom-group-match.test.ts b/test/utils/does-custom-group-match.test.ts
new file mode 100644
index 0000000..ddc807f
--- /dev/null
+++ b/test/utils/does-custom-group-match.test.ts
@@ -0,0 +1,126 @@
+import { describe, expect, it } from 'vitest'
+
+import { doesCustomGroupMatch } from '../../utils/does-custom-group-match'
+
+describe('does-custom-group-match', () => {
+  describe('selectors', () => {
+    it('returns true if custom group matches at least one selector', () => {
+      expect(
+        doesCustomGroupMatch({
+          customGroup: {
+            selector: 'selector1',
+          },
+          selectors: ['selector1'],
+          elementName: '',
+          modifiers: [],
+        }),
+      ).toBeTruthy()
+    })
+
+    it('returns false if custom group does not match at least one selector', () => {
+      expect(
+        doesCustomGroupMatch({
+          customGroup: {
+            selector: 'selector1',
+          },
+          selectors: ['selector2'],
+          elementName: '',
+          modifiers: [],
+        }),
+      ).toBeFalsy()
+    })
+  })
+
+  describe('modifiers', () => {
+    it('returns true if custom group matches all modifiers', () => {
+      expect(
+        doesCustomGroupMatch({
+          customGroup: {
+            modifiers: ['modifier1', 'modifier2'],
+          },
+          modifiers: ['modifier2', 'modifier1'],
+          elementName: '',
+          selectors: [],
+        }),
+      ).toBeTruthy()
+    })
+
+    it('returns false if custom group does not match all modifiers', () => {
+      expect(
+        doesCustomGroupMatch({
+          customGroup: {
+            modifiers: ['modifier1', 'modifier2'],
+          },
+          modifiers: ['modifier1'],
+          elementName: '',
+          selectors: [],
+        }),
+      ).toBeFalsy()
+    })
+  })
+
+  it.each([
+    'hello',
+    ['noMatch', 'hello'],
+    { pattern: 'HELLO', flags: 'i' },
+    ['noMatch', { pattern: 'HELLO', flags: 'i' }],
+  ])(
+    'returns true if custom group matches elementNamePattern',
+    elementNamePattern => {
+      expect(
+        doesCustomGroupMatch({
+          customGroup: {
+            elementNamePattern,
+          },
+          elementName: 'hello',
+          modifiers: [],
+          selectors: [],
+        }),
+      ).toBeTruthy()
+    },
+  )
+
+  it.each([
+    'hello',
+    ['noMatch', 'hello'],
+    { pattern: 'HELLO', flags: 'i' },
+    ['noMatch', { pattern: 'HELLO', flags: 'i' }],
+  ])(
+    'returns true if custom group matches elementValuePattern',
+    elementValuePattern => {
+      expect(
+        doesCustomGroupMatch({
+          customGroup: {
+            elementValuePattern,
+          },
+          elementValue: 'hello',
+          elementName: '',
+          modifiers: [],
+          selectors: [],
+        }),
+      ).toBeTruthy()
+    },
+  )
+
+  it.each([
+    'hello',
+    ['noMatch', 'hello'],
+    { pattern: 'HELLO', flags: 'i' },
+    ['noMatch', { pattern: 'HELLO', flags: 'i' }],
+  ])(
+    'returns true if custom group matches decoratorNamePattern',
+    decoratorNamePattern => {
+      expect(
+        doesCustomGroupMatch({
+          customGroup: {
+            decoratorNamePattern,
+          },
+          decorators: ['foo', 'hello'],
+          elementName: '',
+          modifiers: [],
+          selectors: [],
+        }),
+      ).toBeTruthy()
+    },
+  )
+})