Skip to content

Commit 4c36cbd

Browse files
authored
feat(rules): create header-case and header-full-stop rules (#547)
* feat(rules): create header-full-stop rule * feat(rules): create header-case rule These are simplified versions of the subject-* rules. Headers are always present, so we don’t need to check for empty-ness. If headers are not defined, the parser will throw with an error “expecting raw commit”.
1 parent fa6168a commit 4c36cbd

File tree

6 files changed

+431
-0
lines changed

6 files changed

+431
-0
lines changed

@commitlint/rules/src/header-case.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import * as ensure from '@commitlint/ensure';
2+
import message from '@commitlint/message';
3+
4+
const negated = when => when === 'never';
5+
6+
export default (parsed, when, value) => {
7+
const {header} = parsed;
8+
9+
if (typeof header !== 'string' || !header.match(/^[a-z]/i)) {
10+
return [true];
11+
}
12+
13+
const checks = (Array.isArray(value) ? value : [value]).map(check => {
14+
if (typeof check === 'string') {
15+
return {
16+
when: 'always',
17+
case: check
18+
};
19+
}
20+
return check;
21+
});
22+
23+
const result = checks.some(check => {
24+
const r = ensure.case(header, check.case);
25+
return negated(check.when) ? !r : r;
26+
});
27+
28+
const list = checks.map(c => c.case).join(', ');
29+
30+
return [
31+
negated(when) ? !result : result,
32+
message([`header must`, negated(when) ? `not` : null, `be ${list}`])
33+
];
34+
};
+318
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
import test from 'ava';
2+
import parse from '@commitlint/parse';
3+
import headerCase from './header-case';
4+
5+
const messages = {
6+
numeric: '1.0.0\n',
7+
lowercase: 'header\n',
8+
mixedcase: 'hEaDeR\n',
9+
uppercase: 'HEADER\n',
10+
camelcase: 'heaDer\n',
11+
kebabcase: 'hea-der\n',
12+
pascalcase: 'HeaDer\n',
13+
snakecase: 'hea_der\n',
14+
startcase: 'Hea Der\n'
15+
};
16+
17+
const parsed = {
18+
numeric: parse(messages.numeric),
19+
lowercase: parse(messages.lowercase),
20+
mixedcase: parse(messages.mixedcase),
21+
uppercase: parse(messages.uppercase),
22+
camelcase: parse(messages.camelcase),
23+
kebabcase: parse(messages.kebabcase),
24+
pascalcase: parse(messages.pascalcase),
25+
snakecase: parse(messages.snakecase),
26+
startcase: parse(messages.startcase)
27+
};
28+
29+
test('with lowercase header should fail for "never lowercase"', async t => {
30+
const [actual] = headerCase(await parsed.lowercase, 'never', 'lowercase');
31+
const expected = false;
32+
t.is(actual, expected);
33+
});
34+
35+
test('with lowercase header should succeed for "always lowercase"', async t => {
36+
const [actual] = headerCase(await parsed.lowercase, 'always', 'lowercase');
37+
const expected = true;
38+
t.is(actual, expected);
39+
});
40+
41+
test('with mixedcase header should succeed for "never lowercase"', async t => {
42+
const [actual] = headerCase(await parsed.mixedcase, 'never', 'lowercase');
43+
const expected = true;
44+
t.is(actual, expected);
45+
});
46+
47+
test('with mixedcase header should fail for "always lowercase"', async t => {
48+
const [actual] = headerCase(await parsed.mixedcase, 'always', 'lowercase');
49+
const expected = false;
50+
t.is(actual, expected);
51+
});
52+
53+
test('with mixedcase header should succeed for "never uppercase"', async t => {
54+
const [actual] = headerCase(await parsed.mixedcase, 'never', 'uppercase');
55+
const expected = true;
56+
t.is(actual, expected);
57+
});
58+
59+
test('with mixedcase header should fail for "always uppercase"', async t => {
60+
const [actual] = headerCase(await parsed.mixedcase, 'always', 'uppercase');
61+
const expected = false;
62+
t.is(actual, expected);
63+
});
64+
65+
test('with uppercase header should fail for "never uppercase"', async t => {
66+
const [actual] = headerCase(await parsed.uppercase, 'never', 'uppercase');
67+
const expected = false;
68+
t.is(actual, expected);
69+
});
70+
71+
test('with lowercase header should succeed for "always uppercase"', async t => {
72+
const [actual] = headerCase(await parsed.uppercase, 'always', 'uppercase');
73+
const expected = true;
74+
t.is(actual, expected);
75+
});
76+
77+
test('with camelcase header should fail for "always uppercase"', async t => {
78+
const [actual] = headerCase(await parsed.camelcase, 'always', 'uppercase');
79+
const expected = false;
80+
t.is(actual, expected);
81+
});
82+
83+
test('with camelcase header should succeed for "never uppercase"', async t => {
84+
const [actual] = headerCase(await parsed.camelcase, 'never', 'uppercase');
85+
const expected = true;
86+
t.is(actual, expected);
87+
});
88+
89+
test('with camelcase header should fail for "always pascalcase"', async t => {
90+
const [actual] = headerCase(await parsed.camelcase, 'always', 'pascal-case');
91+
const expected = false;
92+
t.is(actual, expected);
93+
});
94+
95+
test('with camelcase header should fail for "always kebabcase"', async t => {
96+
const [actual] = headerCase(await parsed.camelcase, 'always', 'kebab-case');
97+
const expected = false;
98+
t.is(actual, expected);
99+
});
100+
101+
test('with camelcase header should fail for "always snakecase"', async t => {
102+
const [actual] = headerCase(await parsed.camelcase, 'always', 'snake-case');
103+
const expected = false;
104+
t.is(actual, expected);
105+
});
106+
107+
test('with camelcase header should succeed for "always camelcase"', async t => {
108+
const [actual] = headerCase(await parsed.camelcase, 'always', 'camel-case');
109+
const expected = true;
110+
t.is(actual, expected);
111+
});
112+
113+
test('with pascalcase header should fail for "always uppercase"', async t => {
114+
const [actual] = headerCase(await parsed.pascalcase, 'always', 'uppercase');
115+
const expected = false;
116+
t.is(actual, expected);
117+
});
118+
119+
test('with pascalcase header should succeed for "never uppercase"', async t => {
120+
const [actual] = headerCase(await parsed.pascalcase, 'never', 'uppercase');
121+
const expected = true;
122+
t.is(actual, expected);
123+
});
124+
125+
test('with pascalcase header should succeed for "always pascalcase"', async t => {
126+
const [actual] = headerCase(await parsed.pascalcase, 'always', 'pascal-case');
127+
const expected = true;
128+
t.is(actual, expected);
129+
});
130+
131+
test('with pascalcase header should fail for "always kebabcase"', async t => {
132+
const [actual] = headerCase(await parsed.pascalcase, 'always', 'kebab-case');
133+
const expected = false;
134+
t.is(actual, expected);
135+
});
136+
137+
test('with pascalcase header should fail for "always snakecase"', async t => {
138+
const [actual] = headerCase(await parsed.pascalcase, 'always', 'snake-case');
139+
const expected = false;
140+
t.is(actual, expected);
141+
});
142+
143+
test('with pascalcase header should fail for "always camelcase"', async t => {
144+
const [actual] = headerCase(await parsed.pascalcase, 'always', 'camel-case');
145+
const expected = false;
146+
t.is(actual, expected);
147+
});
148+
149+
test('with snakecase header should fail for "always uppercase"', async t => {
150+
const [actual] = headerCase(await parsed.snakecase, 'always', 'uppercase');
151+
const expected = false;
152+
t.is(actual, expected);
153+
});
154+
155+
test('with snakecase header should succeed for "never uppercase"', async t => {
156+
const [actual] = headerCase(await parsed.snakecase, 'never', 'uppercase');
157+
const expected = true;
158+
t.is(actual, expected);
159+
});
160+
161+
test('with snakecase header should fail for "always pascalcase"', async t => {
162+
const [actual] = headerCase(await parsed.snakecase, 'always', 'pascal-case');
163+
const expected = false;
164+
t.is(actual, expected);
165+
});
166+
167+
test('with snakecase header should fail for "always kebabcase"', async t => {
168+
const [actual] = headerCase(await parsed.snakecase, 'always', 'kebab-case');
169+
const expected = false;
170+
t.is(actual, expected);
171+
});
172+
173+
test('with snakecase header should succeed for "always snakecase"', async t => {
174+
const [actual] = headerCase(await parsed.snakecase, 'always', 'snake-case');
175+
const expected = true;
176+
t.is(actual, expected);
177+
});
178+
179+
test('with snakecase header should fail for "always camelcase"', async t => {
180+
const [actual] = headerCase(await parsed.snakecase, 'always', 'camel-case');
181+
const expected = false;
182+
t.is(actual, expected);
183+
});
184+
185+
test('with startcase header should fail for "always uppercase"', async t => {
186+
const [actual] = headerCase(await parsed.startcase, 'always', 'uppercase');
187+
const expected = false;
188+
t.is(actual, expected);
189+
});
190+
191+
test('with startcase header should succeed for "never uppercase"', async t => {
192+
const [actual] = headerCase(await parsed.startcase, 'never', 'uppercase');
193+
const expected = true;
194+
t.is(actual, expected);
195+
});
196+
197+
test('with startcase header should fail for "always pascalcase"', async t => {
198+
const [actual] = headerCase(await parsed.startcase, 'always', 'pascal-case');
199+
const expected = false;
200+
t.is(actual, expected);
201+
});
202+
203+
test('with startcase header should fail for "always kebabcase"', async t => {
204+
const [actual] = headerCase(await parsed.startcase, 'always', 'kebab-case');
205+
const expected = false;
206+
t.is(actual, expected);
207+
});
208+
209+
test('with startcase header should fail for "always snakecase"', async t => {
210+
const [actual] = headerCase(await parsed.startcase, 'always', 'snake-case');
211+
const expected = false;
212+
t.is(actual, expected);
213+
});
214+
215+
test('with startcase header should fail for "always camelcase"', async t => {
216+
const [actual] = headerCase(await parsed.startcase, 'always', 'camel-case');
217+
const expected = false;
218+
t.is(actual, expected);
219+
});
220+
221+
test('with startcase header should succeed for "always startcase"', async t => {
222+
const [actual] = headerCase(await parsed.startcase, 'always', 'start-case');
223+
const expected = true;
224+
t.is(actual, expected);
225+
});
226+
227+
test('should use expected message with "always"', async t => {
228+
const [, message] = headerCase(
229+
await parsed.uppercase,
230+
'always',
231+
'lower-case'
232+
);
233+
t.true(message.indexOf('must be lower-case') > -1);
234+
});
235+
236+
test('should use expected message with "never"', async t => {
237+
const [, message] = headerCase(await parsed.uppercase, 'never', 'upper-case');
238+
t.true(message.indexOf('must not be upper-case') > -1);
239+
});
240+
241+
test('with uppercase scope should succeed for "always [uppercase, lowercase]"', async t => {
242+
const [actual] = headerCase(await parsed.uppercase, 'always', [
243+
'uppercase',
244+
'lowercase'
245+
]);
246+
const expected = true;
247+
t.is(actual, expected);
248+
});
249+
250+
test('with lowercase header should succeed for "always [uppercase, lowercase]"', async t => {
251+
const [actual] = headerCase(await parsed.lowercase, 'always', [
252+
'uppercase',
253+
'lowercase'
254+
]);
255+
const expected = true;
256+
t.is(actual, expected);
257+
});
258+
259+
test('with mixedcase header should fail for "always [uppercase, lowercase]"', async t => {
260+
const [actual] = headerCase(await parsed.mixedcase, 'always', [
261+
'uppercase',
262+
'lowercase'
263+
]);
264+
const expected = false;
265+
t.is(actual, expected);
266+
});
267+
268+
test('with mixedcase header should pass for "always [uppercase, lowercase, camel-case]"', async t => {
269+
const [actual] = headerCase(await parsed.mixedcase, 'always', [
270+
'uppercase',
271+
'lowercase',
272+
'camel-case'
273+
]);
274+
const expected = true;
275+
t.is(actual, expected);
276+
});
277+
278+
test('with mixedcase scope should pass for "never [uppercase, lowercase]"', async t => {
279+
const [actual] = headerCase(await parsed.mixedcase, 'never', [
280+
'uppercase',
281+
'lowercase'
282+
]);
283+
const expected = true;
284+
t.is(actual, expected);
285+
});
286+
287+
test('with uppercase scope should fail for "never [uppercase, lowercase]"', async t => {
288+
const [actual] = headerCase(await parsed.uppercase, 'never', [
289+
'uppercase',
290+
'lowercase'
291+
]);
292+
const expected = false;
293+
t.is(actual, expected);
294+
});
295+
296+
test('with numeric header should succeed for "never lowercase"', async t => {
297+
const [actual] = headerCase(await parsed.numeric, 'never', 'lowercase');
298+
const expected = true;
299+
t.is(actual, expected);
300+
});
301+
302+
test('with numeric header should succeed for "always lowercase"', async t => {
303+
const [actual] = headerCase(await parsed.numeric, 'always', 'lowercase');
304+
const expected = true;
305+
t.is(actual, expected);
306+
});
307+
308+
test('with numeric header should succeed for "never uppercase"', async t => {
309+
const [actual] = headerCase(await parsed.numeric, 'never', 'uppercase');
310+
const expected = true;
311+
t.is(actual, expected);
312+
});
313+
314+
test('with numeric header should succeed for "always uppercase"', async t => {
315+
const [actual] = headerCase(await parsed.numeric, 'always', 'uppercase');
316+
const expected = true;
317+
t.is(actual, expected);
318+
});
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import message from '@commitlint/message';
2+
3+
export default (parsed, when, value) => {
4+
const {header} = parsed;
5+
const negated = when === 'never';
6+
const hasStop = header[header.length - 1] === value;
7+
8+
return [
9+
negated ? !hasStop : hasStop,
10+
message(['header', negated ? 'may not' : 'must', 'end with full stop'])
11+
];
12+
};

0 commit comments

Comments
 (0)