Skip to content

Commit 39095fb

Browse files
committed
feat: allow scope-enum to also config delimiter
fix conventional-changelog#2108
1 parent baf1082 commit 39095fb

File tree

8 files changed

+102
-30
lines changed

8 files changed

+102
-30
lines changed

@commitlint/prompt/src/library/get-prompt.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
getHasName,
1515
getMaxLength,
1616
} from './utils';
17+
import {EnumRuleOptions} from '@commitlint/types';
1718

1819
/**
1920
* Get a cli prompt based on rule configuration
@@ -68,7 +69,9 @@ export default function getPrompt(
6869
throw new TypeError('getPrompt: prompt.show is not a function');
6970
}
7071

71-
const enumRule = rules.filter(getHasName('enum')).find(enumRuleIsActive);
72+
const enumRule = (rules as Array<RuleEntry<EnumRuleOptions>>)
73+
.filter(getHasName('enum'))
74+
.find(enumRuleIsActive);
7275

7376
const emptyRule = rules.find(getHasName('empty'));
7477

@@ -118,7 +121,7 @@ export default function getPrompt(
118121
if (enumRule) {
119122
const [, [, , enums]] = enumRule;
120123

121-
enums.forEach((enumerable) => {
124+
(enums as string[]).forEach((enumerable) => {
122125
const enumSettings = (settings.enumerables || {})[enumerable] || {};
123126
prompt
124127
.command(enumerable)

@commitlint/prompt/src/library/types.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import {RuleConfigCondition, RuleConfigSeverity} from '@commitlint/types';
22

3-
export type RuleEntry =
3+
export type RuleEntry<C = unknown> =
44
| [string, Readonly<[RuleConfigSeverity.Disabled]>]
55
| [string, Readonly<[RuleConfigSeverity, RuleConfigCondition]>]
6-
| [string, Readonly<[RuleConfigSeverity, RuleConfigCondition, unknown]>];
6+
| [string, Readonly<[RuleConfigSeverity, RuleConfigCondition, C]>];
77

88
export type InputSetting = {
99
description?: string;

@commitlint/prompt/src/library/utils.test.ts

+18-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import {RuleConfigSeverity} from '@commitlint/types';
1+
import {EnumRuleOptions, RuleConfigSeverity} from '@commitlint/types';
2+
import {RuleEntry} from './types';
23

34
import {
45
enumRuleIsActive,
@@ -85,37 +86,47 @@ test('getMaxLength', () => {
8586
});
8687

8788
test('check enum rule filters', () => {
88-
const rules: any = {
89+
const rules: Record<string, RuleEntry<EnumRuleOptions>[1]> = {
8990
'enum-string': [RuleConfigSeverity.Warning, 'always', ['1', '2', '3']],
9091
'type-enum': [RuleConfigSeverity.Error, 'always', ['build', 'chore', 'ci']],
9192
'scope-enum': [RuleConfigSeverity.Error, 'never', ['cli', 'core', 'lint']],
9293
'bar-enum': [RuleConfigSeverity.Disabled, 'always', ['foo', 'bar', 'baz']],
94+
'extendable-scope-enum': [
95+
RuleConfigSeverity.Disabled,
96+
'always',
97+
{values: ['foo', 'bar', 'baz']},
98+
],
9399
};
94100

95-
let enumRule = getRules('type', rules)
101+
let enumRule = getRules<EnumRuleOptions>('type', rules)
96102
.filter(getHasName('enum'))
97103
.find(enumRuleIsActive);
98104
expect(enumRule).toEqual([
99105
'type-enum',
100106
[2, 'always', ['build', 'chore', 'ci']],
101107
]);
102108

103-
enumRule = getRules('string', rules)
109+
enumRule = getRules<EnumRuleOptions>('string', rules)
104110
.filter(getHasName('enum'))
105111
.find(enumRuleIsActive);
106112
expect(enumRule).toEqual(undefined);
107113

108-
enumRule = getRules('enum', rules)
114+
enumRule = getRules<EnumRuleOptions>('enum', rules)
109115
.filter(getHasName('string'))
110116
.find(enumRuleIsActive);
111117
expect(enumRule).toEqual(['enum-string', [1, 'always', ['1', '2', '3']]]);
112118

113-
enumRule = getRules('bar', rules)
119+
enumRule = getRules<EnumRuleOptions>('bar', rules)
120+
.filter(getHasName('enum'))
121+
.find(enumRuleIsActive);
122+
expect(enumRule).toEqual(undefined);
123+
124+
enumRule = getRules<EnumRuleOptions>('scope', rules)
114125
.filter(getHasName('enum'))
115126
.find(enumRuleIsActive);
116127
expect(enumRule).toEqual(undefined);
117128

118-
enumRule = getRules('scope', rules)
129+
enumRule = getRules<EnumRuleOptions>('extendable-scope', rules)
119130
.filter(getHasName('enum'))
120131
.find(enumRuleIsActive);
121132
expect(enumRule).toEqual(undefined);

@commitlint/prompt/src/library/utils.ts

+36-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import {QualifiedRules, RuleConfigSeverity} from '@commitlint/types';
1+
import {
2+
QualifiedRules,
3+
RuleConfigSeverity,
4+
EnumRuleOptions,
5+
EnumRuleExtendableOptions,
6+
} from '@commitlint/types';
27
import {RuleEntry} from './types';
38

49
/**
@@ -25,7 +30,7 @@ export function getRulePrefix(id: string): string | null {
2530
* Get a predicate matching rule definitions with a given name
2631
*/
2732
export function getHasName(name: string) {
28-
return <T extends RuleEntry>(
33+
return <C = unknown, T extends RuleEntry<C> = RuleEntry<C>>(
2934
rule: RuleEntry
3035
): rule is Exclude<T, [string, undefined]> => getRuleName(rule[0]) === name;
3136
}
@@ -35,7 +40,10 @@ export function getHasName(name: string) {
3540
* @param rule to check
3641
* @return if the rule definition is active
3742
*/
38-
export function ruleIsActive<T extends RuleEntry>(
43+
export function ruleIsActive<
44+
C = unknown,
45+
T extends RuleEntry<C> = RuleEntry<C>
46+
>(
3947
rule: T
4048
): rule is Exclude<T, [string, Readonly<[RuleConfigSeverity.Disabled]>]> {
4149
const [, value] = rule;
@@ -50,11 +58,11 @@ export function ruleIsActive<T extends RuleEntry>(
5058
* @param rule to check
5159
* @return if the rule definition is applicable
5260
*/
53-
export function ruleIsApplicable(
54-
rule: RuleEntry
61+
export function ruleIsApplicable<C>(
62+
rule: RuleEntry<C>
5563
): rule is
5664
| [string, Readonly<[RuleConfigSeverity, 'always']>]
57-
| [string, Readonly<[RuleConfigSeverity, 'always', unknown]>] {
65+
| [string, Readonly<[RuleConfigSeverity, 'always', C]>] {
5866
const [, value] = rule;
5967
if (value && Array.isArray(value)) {
6068
return value[1] === 'always';
@@ -79,19 +87,32 @@ export function ruleIsNotApplicable(
7987
return false;
8088
}
8189

90+
function enumConfigIsExtendable(
91+
config?: EnumRuleOptions
92+
): config is EnumRuleExtendableOptions {
93+
return !Array.isArray(config);
94+
}
95+
8296
export function enumRuleIsActive(
83-
rule: RuleEntry
97+
rule: RuleEntry<EnumRuleOptions>
8498
): rule is [
8599
string,
86100
Readonly<
87-
[RuleConfigSeverity.Warning | RuleConfigSeverity.Error, 'always', string[]]
101+
[
102+
RuleConfigSeverity.Warning | RuleConfigSeverity.Error,
103+
'always',
104+
EnumRuleOptions
105+
]
88106
>
89107
] {
108+
let config: EnumRuleOptions | undefined;
90109
return (
91110
ruleIsActive(rule) &&
92111
ruleIsApplicable(rule) &&
93-
Array.isArray(rule[1][2]) &&
94-
rule[1][2].length > 0
112+
!!(config = rule[1][2]) &&
113+
(!enumConfigIsExtendable(config)
114+
? config.length > 0
115+
: Array.isArray(config.values) && config.values.length > 0)
95116
);
96117
}
97118

@@ -101,9 +122,12 @@ export function enumRuleIsActive(
101122
* @param rules rules to search in
102123
* @return rules matching the prefix search
103124
*/
104-
export function getRules(prefix: string, rules: QualifiedRules): RuleEntry[] {
125+
export function getRules<C = unknown>(
126+
prefix: string,
127+
rules: QualifiedRules
128+
): RuleEntry<C>[] {
105129
return Object.entries(rules).filter(
106-
(rule): rule is RuleEntry => getRulePrefix(rule[0]) === prefix
130+
(rule): rule is RuleEntry<C> => getRulePrefix(rule[0]) === prefix
107131
);
108132
}
109133

@commitlint/rules/src/scope-enum.test.ts

+11
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ const messages = {
66
superfluous: 'foo(): baz',
77
empty: 'foo: baz',
88
multiple: 'foo(bar,baz): qux',
9+
scoped: 'foo(@a/b): test',
910
};
1011

1112
const parsed = {
1213
plain: parse(messages.plain),
1314
superfluous: parse(messages.superfluous),
1415
empty: parse(messages.empty),
1516
multiple: parse(messages.multiple),
17+
scoped: parse(messages.scoped),
1618
};
1719

1820
test('scope-enum with plain message and always should succeed empty enum', async () => {
@@ -21,6 +23,15 @@ test('scope-enum with plain message and always should succeed empty enum', async
2123
expect(actual).toEqual(expected);
2224
});
2325

26+
test('scope-enum allows custom delimiters', async () => {
27+
const [actual] = scopeEnum(await parsed.scoped, 'always', {
28+
values: ['@a/b'],
29+
delimiter: /,/g,
30+
});
31+
const expected = true;
32+
expect(actual).toEqual(expected);
33+
});
34+
2435
test('scope-enum with plain message and never should error empty enum', async () => {
2536
const [actual] = scopeEnum(await parsed.plain, 'never', []);
2637
const expected = false;

@commitlint/rules/src/scope-enum.ts

+15-6
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,27 @@ import * as ensure from '@commitlint/ensure';
22
import message from '@commitlint/message';
33
import {SyncRule} from '@commitlint/types';
44

5-
export const scopeEnum: SyncRule<string[]> = (
6-
parsed,
7-
when = 'always',
8-
value = []
9-
) => {
5+
export const scopeEnum: SyncRule<
6+
string[] | {delimiter?: RegExp; values?: string[]}
7+
> = (parsed, when = 'always', config = []) => {
108
if (!parsed.scope) {
119
return [true, ''];
1210
}
1311

12+
let delimiters = /\/|\\|,/g;
13+
let value: string[] = [];
14+
if (!Array.isArray(config)) {
15+
if (config.delimiter) {
16+
delimiters = config.delimiter;
17+
}
18+
value = config.values || [];
19+
} else {
20+
value = config;
21+
}
22+
1423
// Scopes may contain slash or comma delimiters to separate them and mark them as individual segments.
1524
// This means that each of these segments should be tested separately with `ensure`.
16-
const delimiters = /\/|\\|,/g;
25+
1726
const scopeSegments = parsed.scope.split(delimiters);
1827

1928
const negated = when === 'never';

@commitlint/types/src/rules.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,16 @@ export type LengthRuleConfig<V = RuleConfigQuality.User> = RuleConfig<
8383
V,
8484
number
8585
>;
86+
export interface EnumRuleExtendableOptions {
87+
values: string[];
88+
[k: string]: any;
89+
}
90+
91+
export type EnumRuleOptions = EnumRuleExtendableOptions | string[];
92+
8693
export type EnumRuleConfig<V = RuleConfigQuality.User> = RuleConfig<
8794
V,
88-
string[]
95+
EnumRuleOptions
8996
>;
9097

9198
export type RulesConfig<V = RuleConfigQuality.User> = {

docs/reference-rules.md

+7
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,13 @@ Infinity
190190
```
191191
[]
192192
```
193+
or if you want to customize delimiter regex for [multiple scopes](https://commitlint.js.org/#/concepts-commit-conventions?id=multiple-scopes)
194+
```
195+
{
196+
values: [],
197+
delimiter: /,/g
198+
}
199+
```
193200

194201
#### scope-case
195202

0 commit comments

Comments
 (0)