Skip to content

Conversation

@what1s1ove
Copy link

@what1s1ove what1s1ove commented Dec 9, 2025

Description

This PR introduces a new rule, scope-delimiter-style, which validates that commit scopes use only the allowed delimiters. This ensures consistent behavior when custom delimiters are configured.

It also adds support for passing custom delimiters to the scope-enum and scope-case rules, allowing users to redefine how multi-segment scopes are split and validated.

The default delimiters (/, \, ,) remain unchanged unless explicitly overridden.

Documentation has been updated to reflect:

  • support for object-based configuration in scope-enum and scope-case
  • ability to customize delimiters
  • introduction of the new scope-delimiter-style rule
  • updated Multiple Scopes section describing configurable delimiters

This brings delimiter handling across all scope-related rules to a unified and configurable model.

Motivation and Context

Resolves #701

Current scope-related rules (scope-enum, scope-case) use hardcoded delimiters to split multi-segment scopes. This makes it impossible for users to adapt commitlint to projects that use different delimiter conventions.

This PR introduces configurable delimiters and a dedicated scope-delimiter-style rule, enabling consistent validation across all scope rules. It provides a unified and flexible way to define how scopes should be parsed and validated, solving the long-standing limitation described in the linked issue.

Usage examples

// commitlint.config.js

const delimiters = ["/"];

export default {
  rules: {
    "scope-delimiter-style": [2, "always", delimiters],
    "scope-enum": [2, "always", { scopeEnums: ["api", "core"], delimiters }],
    "scope-case": [2, "always", { cases: "kebab-case", delimiters }],
  };
};
echo "feat(api,cor): add new version" | commitlint 

# ⧗   input: feat(api,cor): add new version
# ✖   scope must be one of [api, core] [scope-enum]
# ✖   scope delimiters must be one of [/] [scope-delimiter-style]
# ✖   found 2 problems, 0 warnings
echo "feat(api/core): add new version" | commitlint

# success

How Has This Been Tested?

All changes are covered by an extended test suite:

  • Updated existing tests for scope-enum and scope-case to ensure backward compatibility with default delimiters.

  • Added new tests for object-based configuration, including custom delimiter lists and empty delimiter arrays.

  • Added a full test suite for the new scope-delimiter-style rule, covering:

    • default behavior
    • custom delimiter configurations
    • always and never conditions
    • fallback to default delimiters
    • handling of special characters
    • non-delimiter characters such as - and _

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@codesandbox-ci
Copy link

codesandbox-ci bot commented Dec 9, 2025

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

This comment was marked as resolved.

@escapedcat
Copy link
Member

Pressed the copilot review. Have a look if any of that is valid or useful, thanks!

@what1s1ove
Copy link
Author

what1s1ove commented Dec 11, 2025

Hey @escapedcat! Thanks a lot, some of Copilot's suggestions were pretty helpful. I've pushed the changes. Whenever you have a moment, could you take a look?

@escapedcat escapedcat requested a review from JounQin December 12, 2025 13:21
@escapedcat
Copy link
Member

lgtm, @JounQin would you mind having a look?

@what1s1ove
Copy link
Author

Hey @escapedcat ! While we're waiting for additional review, I took another look at the code (with a fresh mind) and made a few small changes related to naming, to make it more consistent and unified (without affecting code quality). Hope that's okay 🙏


const checks = (Array.isArray(value) ? value : [value]).map((check) => {
const checks = (
isObjectBasedConfiguration
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just check 'cases' in value?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to do it like that initially, but it's not enough for TypeScript.

Type 'TargetCaseType | TargetCaseType[] | { cases: TargetCaseType[]; delimiters?: string[] }' is not assignable to type 'object'. Type 'string' is not assignable to type 'object'.

We could also do:

	const checks = (
		typeof value === "string"
			? [value]
			: Array.isArray(value)
				? value
				: value.cases

But to me, it's more verbose, because later, when we need to read delimiters, we'd need a similar check again. That's why I decided to introduce an extra variable.

const delimiters = /\/|\\|, ?/g;
const scopeSegments = scope.split(delimiters);
const delimiters =
isObjectBasedConfiguration && value.delimiters?.length
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just check 'delimiters' in value instead?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check the comments above please, same here

@what1s1ove what1s1ove requested a review from JounQin December 16, 2025 09:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: Configurable delimiter for scope

3 participants