Skip to content

Commit edf7187

Browse files
committed
style: validate rule configuration
BREAKING CHANGE: Due to additional validation while reading commitlint config, previously ignored rule settings are now considered critical errors when starting the CLI. The new behaviour is designed to help developers find issues with their configuration quicker.
1 parent b60adbc commit edf7187

File tree

2 files changed

+134
-2
lines changed

2 files changed

+134
-2
lines changed

@commitlint/core/src/lint.js

+61
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import util from 'util';
12
import isIgnored from '@commitlint/is-ignored';
23
import parse from '@commitlint/parse';
34
import implementations from '@commitlint/rules';
@@ -30,6 +31,66 @@ export default async (message, rules = {}, opts = {}) => {
3031
);
3132
}
3233

34+
const invalid = entries(rules)
35+
.map(([name, config]) => {
36+
if (!Array.isArray(config)) {
37+
return new Error(
38+
`config for rule ${name} must be array, received ${util.inspect(
39+
config
40+
)} of type ${typeof config}`
41+
);
42+
}
43+
44+
if (config.length !== 2 && config.length !== 3) {
45+
return new Error(
46+
`config for rule ${name} must be 2 or 3 items long, received ${util.inspect(
47+
config
48+
)} of length ${config.length}`
49+
);
50+
}
51+
52+
const [level, when] = config;
53+
54+
if (typeof level !== 'number' || isNaN(level)) {
55+
return new Error(
56+
`level for rule ${name} must be number, received ${util.inspect(
57+
level
58+
)} of type ${typeof level}`
59+
);
60+
}
61+
62+
if (level < 0 || level > 2) {
63+
return new RangeError(
64+
`level for rule ${name} must be between 0 and 2, received ${util.inspect(
65+
level
66+
)}`
67+
);
68+
}
69+
70+
if (typeof when !== 'string') {
71+
return new Error(
72+
`condition for rule ${name} must be string, received ${util.inspect(
73+
when
74+
)} of type ${typeof when}`
75+
);
76+
}
77+
78+
if (when !== 'never' && when !== 'always') {
79+
return new Error(
80+
`condition for rule ${name} must be "always" or "never", received ${util.inspect(
81+
when
82+
)}`
83+
);
84+
}
85+
86+
return null;
87+
})
88+
.filter(item => item instanceof Error);
89+
90+
if (invalid.length > 0) {
91+
throw new Error(invalid.map(i => i.message).join('\n'));
92+
}
93+
3394
// Validate against all rules
3495
const results = entries(rules)
3596
.filter(entry => {

@commitlint/core/src/lint.test.js

+73-2
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,81 @@ test('positive on stub message and opts', async t => {
5353
t.true(actual.valid);
5454
});
5555

56-
test('should throw for invalid rule names', async t => {
56+
test('throws for invalid rule names', async t => {
5757
const error = await t.throws(
5858
lint('foo', {foo: [2, 'always'], bar: [1, 'never']})
5959
);
6060

61-
t.is(error.message.indexOf('Found invalid rule names: foo, bar'), 0);
61+
t.is(error.message.indexOf('Found missing rule names: foo, bar'), 0);
62+
});
63+
64+
test('throws for invalid rule config', async t => {
65+
const error = await t.throws(
66+
lint('type(scope): foo', {
67+
'type-enum': 1,
68+
'scope-enum': {0: 2, 1: 'never', 2: ['foo'], length: 3}
69+
})
70+
);
71+
72+
t.true(error.message.indexOf('type-enum must be array') > -1);
73+
t.true(error.message.indexOf('scope-enum must be array') > -1);
74+
});
75+
76+
test('throws for rule with invalid length', async t => {
77+
const error = await t.throws(
78+
lint('type(scope): foo', {'type-enum': [], 'scope-enum': [1, 2, 3, 4]})
79+
);
80+
81+
t.true(error.message.indexOf('type-enum must be 2 or 3 items long') > -1);
82+
t.true(error.message.indexOf('scope-enum must be 2 or 3 items long') > -1);
83+
});
84+
85+
test('throws for rule with invalid level', async t => {
86+
const error = await t.throws(
87+
lint('type(scope): foo', {
88+
'type-enum': ['2', 'always'],
89+
'header-max-length': [{}, 'always']
90+
})
91+
);
92+
93+
t.true(error.message.indexOf('rule type-enum must be number') > -1);
94+
t.true(error.message.indexOf('rule type-enum must be number') > -1);
95+
});
96+
97+
test('throws for rule with out of range level', async t => {
98+
const error = await t.throws(
99+
lint('type(scope): foo', {
100+
'type-enum': [-1, 'always'],
101+
'header-max-length': [3, 'always']
102+
})
103+
);
104+
105+
t.true(error.message.indexOf('rule type-enum must be between 0 and 2') > -1);
106+
t.true(error.message.indexOf('rule type-enum must be between 0 and 2') > -1);
107+
});
108+
109+
test('throws for rule with invalid condition', async t => {
110+
const error = await t.throws(
111+
lint('type(scope): foo', {
112+
'type-enum': [1, 2],
113+
'header-max-length': [1, {}]
114+
})
115+
);
116+
117+
t.true(error.message.indexOf('type-enum must be string') > -1);
118+
t.true(error.message.indexOf('header-max-length must be string') > -1);
119+
});
120+
121+
test('throws for rule with out of range condition', async t => {
122+
const error = await t.throws(
123+
lint('type(scope): foo', {
124+
'type-enum': [1, 'foo'],
125+
'header-max-length': [1, 'bar']
126+
})
127+
);
128+
129+
t.true(error.message.indexOf('type-enum must be "always" or "never"') > -1);
130+
t.true(
131+
error.message.indexOf('header-max-length must be "always" or "never"') > -1
132+
);
62133
});

0 commit comments

Comments
 (0)