Skip to content

Commit c821d32

Browse files
committed
fix: prevent false positives for footer-leading-blank (#33)
* test: add test cases for footer-leading-blank * fix: succeed footer-leading-blank if no footer is found * fix: sanitize check for leading blank line before footer
1 parent fa0e681 commit c821d32

File tree

6 files changed

+135
-14
lines changed

6 files changed

+135
-14
lines changed

packages/conventional-changelog-lint/source/index.js

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import {sync as parse} from 'conventional-commits-parser';
21
import {merge} from 'lodash';
32

43
import ruleFunctions from './rules';
54
import format from './library/format';
65
import getConfiguration from './library/get-configuration';
76
import getMessages from './library/get-messages';
87
import getPreset from './library/get-preset';
8+
import parse from './library/parse';
99

1010
export {format, getConfiguration, getMessages, getPreset};
1111

@@ -21,10 +21,7 @@ export default async (message, options = {}) => {
2121
} = options;
2222

2323
// parse the commit message
24-
const parsed = merge(
25-
{raw: message},
26-
parse(message, parserOptions)
27-
);
24+
const parsed = parse(message);
2825

2926
// wildcard matches skip the linting
3027
const bails = Object.entries(wildcards)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import {sync} from 'conventional-commits-parser';
2+
3+
export default parse;
4+
5+
function parse(message, options) {
6+
const parsed = sync(message, options);
7+
parsed.raw = message;
8+
return parsed;
9+
}

packages/conventional-changelog-lint/source/rules/footer-leading-blank.js

+13-7
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
export default (parsed, when) => {
2+
// flunk if no footer is found
3+
if (!parsed.footer) {
4+
return [true];
5+
}
6+
27
const negated = when === 'never';
8+
39
// get complete body split into lines
4-
const lines = (parsed.raw || '').split('\n').slice(2);
5-
// check if the first line of body (if any) is empty
6-
const leadingBlank =
7-
lines.length > 0 ?
8-
lines[0].length === 0 :
9-
true;
10+
const lines = (parsed.raw || '').split(/\r|\n/).slice(2);
11+
const [leading] = lines;
12+
13+
// check if the first line of body is empty
14+
const succeeds = leading === '';
15+
1016
return [
11-
negated ? !leadingBlank : leadingBlank,
17+
negated ? !succeeds : succeeds,
1218
[
1319
'footer',
1420
negated ? 'may not' : 'must',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import test from 'ava';
2+
import parse from '../../source/library/parse';
3+
import footerLeadingBlank from '../../source/rules/footer-leading-blank';
4+
5+
const messages = {
6+
simple: 'chore: subject',
7+
body: 'chore: subject\nbody',
8+
trailing: 'chore: subject\nbody\n\n',
9+
without: 'chore: subject\nbody\nBREAKING CHANGE: something important',
10+
with: 'chore: subject\nbody\n\nBREAKING CHANGE: something important'
11+
};
12+
13+
const parsed = {
14+
simple: parse(messages.simple),
15+
body: parse(messages.body),
16+
trailing: parse(messages.trailing),
17+
without: parse(messages.without),
18+
with: parse(messages.with)
19+
};
20+
21+
test('footer-leading-blank with simple message should succeed for empty keyword', t => {
22+
const [actual] = footerLeadingBlank(parsed.simple);
23+
const expected = true;
24+
t.is(actual, expected);
25+
});
26+
27+
test('footer-leading-blank with simple message should succeed for "never"', t => {
28+
const [actual] = footerLeadingBlank(parsed.simple, 'never');
29+
const expected = true;
30+
t.is(actual, expected);
31+
});
32+
33+
test('footer-leading-blank with simple message should succeed for "always"', t => {
34+
const [actual] = footerLeadingBlank(parsed.simple, 'always');
35+
const expected = true;
36+
t.is(actual, expected);
37+
});
38+
39+
test('footer-leading-blank with body message should succeed for empty keyword', t => {
40+
const [actual] = footerLeadingBlank(parsed.body);
41+
const expected = true;
42+
t.is(actual, expected);
43+
});
44+
45+
test('footer-leading-blank with body message should succeed for "never"', t => {
46+
const [actual] = footerLeadingBlank(parsed.body, 'never');
47+
const expected = true;
48+
t.is(actual, expected);
49+
});
50+
51+
test('footer-leading-blank with body message should succeed for "always"', t => {
52+
const [actual] = footerLeadingBlank(parsed.body, 'always');
53+
const expected = true;
54+
t.is(actual, expected);
55+
});
56+
57+
test('footer-leading-blank with trailing message should succeed for empty keyword', t => {
58+
const [actual] = footerLeadingBlank(parsed.trailing);
59+
const expected = true;
60+
t.is(actual, expected);
61+
});
62+
63+
test('footer-leading-blank with trailing message should succeed for "never"', t => {
64+
const [actual] = footerLeadingBlank(parsed.trailing, 'never');
65+
const expected = true;
66+
t.is(actual, expected);
67+
});
68+
69+
test('footer-leading-blank with trailing message should succeed for "always"', t => {
70+
const [actual] = footerLeadingBlank(parsed.trailing, 'always');
71+
const expected = true;
72+
t.is(actual, expected);
73+
});
74+
75+
test('footer-leading-blank without blank line before footer should fail for empty keyword', t => {
76+
const [actual] = footerLeadingBlank(parsed.without);
77+
const expected = false;
78+
t.is(actual, expected);
79+
});
80+
81+
test('footer-leading-blank without blank line before footer should succeed for "never"', t => {
82+
const [actual] = footerLeadingBlank(parsed.without, 'never');
83+
const expected = true;
84+
t.is(actual, expected);
85+
});
86+
87+
test('footer-leading-blank without blank line before footer should fail for "always"', t => {
88+
const [actual] = footerLeadingBlank(parsed.without, 'always');
89+
const expected = false;
90+
t.is(actual, expected);
91+
});
92+
93+
test('footer-leading-blank with blank line before footer should succeed for empty keyword', t => {
94+
const [actual] = footerLeadingBlank(parsed.with);
95+
const expected = true;
96+
t.is(actual, expected);
97+
});
98+
99+
test('footer-leading-blank with blank line before footer should fail for "never"', t => {
100+
const [actual] = footerLeadingBlank(parsed.with, 'never');
101+
const expected = false;
102+
t.is(actual, expected);
103+
});
104+
105+
test('footer-leading-blank with blank line before footer should succeed for "always"', t => {
106+
const [actual] = footerLeadingBlank(parsed.with, 'always');
107+
const expected = true;
108+
t.is(actual, expected);
109+
});

packages/conventional-changelog-lint/test/rules/scope-empty.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import test from 'ava';
2-
import {sync as parse} from 'conventional-commits-parser';
2+
import parse from '../../source/library/parse';
33
import scopeEmpty from '../../source/rules/scope-empty';
44

55
const messages = {

packages/conventional-changelog-lint/test/rules/scope-enum.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import test from 'ava';
2-
import {sync as parse} from 'conventional-commits-parser';
2+
import parse from '../../source/library/parse';
33
import scopeEnum from '../../source/rules/scope-enum';
44

55
const messages = {

0 commit comments

Comments
 (0)