Skip to content

Commit 9f5f7bc

Browse files
authored
fix(rules): report the case that matched in case rule failure messages (#4962)
* test: cover matched-case reporting in case rule failure messages * fix(rules): report the case that matched in case rule failure messages --------- Co-authored-by: Dylan Pulver <dylanpulver@users.noreply.github.com>
1 parent 77df555 commit 9f5f7bc

11 files changed

Lines changed: 239 additions & 15 deletions

File tree

‎@commitlint/config-conventional/src/index.test.ts‎

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,12 @@ const errors = {
7070
name: "type-empty",
7171
valid: false,
7272
},
73-
subjectCase: {
73+
subjectCase: (matched: string) => ({
7474
level: 2,
75-
message: "subject must not be sentence-case, start-case, pascal-case, upper-case",
75+
message: `subject must not be ${matched}`,
7676
name: "subject-case",
7777
valid: false,
78-
},
78+
}),
7979
subjectEmpty: {
8080
level: 2,
8181
message: "subject may not be empty",
@@ -145,13 +145,22 @@ test("type-empty", async () => {
145145
});
146146

147147
test("subject-case", async () => {
148+
// One entry per `messages.invalidSubjectCases` input: the configured cases
149+
// that the subject matches, which is what the failure message reports.
150+
const matched = [
151+
"sentence-case",
152+
"sentence-case, start-case",
153+
"sentence-case, pascal-case",
154+
"sentence-case, start-case, upper-case",
155+
];
156+
148157
const invalidInputs = await Promise.all(
149158
messages.invalidSubjectCases.map((invalidInput) => commitLint(invalidInput)),
150159
);
151160

152-
invalidInputs.forEach((result) => {
161+
invalidInputs.forEach((result, index) => {
153162
expect(result.valid).toBe(false);
154-
expect(result.errors).toEqual([errors.subjectCase]);
163+
expect(result.errors).toEqual([errors.subjectCase(matched[index])]);
155164
});
156165
});
157166

‎@commitlint/rules/src/body-case.test.ts‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,40 @@ test('with lowercase body should succeed for "always uppercase"', async () => {
8787
const expected = true;
8888
expect(actual).toEqual(expected);
8989
});
90+
91+
test('with "never" should report only the case that matched', async () => {
92+
const [actual, message] = bodyCase(await parsed.uppercase, "never", [
93+
"sentence-case",
94+
"pascal-case",
95+
]);
96+
expect(actual).toEqual(false);
97+
expect(message).toEqual("body must not be sentence-case");
98+
});
99+
100+
test('with "never" should report every case that matched', async () => {
101+
const [actual, message] = bodyCase(await parsed.uppercase, "never", [
102+
"sentence-case",
103+
"start-case",
104+
"pascal-case",
105+
]);
106+
expect(actual).toEqual(false);
107+
expect(message).toEqual("body must not be sentence-case, start-case");
108+
});
109+
110+
test('with "always" should report every configured case when none matched', async () => {
111+
const [actual, message] = bodyCase(await parsed.uppercase, "always", [
112+
"pascal-case",
113+
"kebab-case",
114+
]);
115+
expect(actual).toEqual(false);
116+
expect(message).toEqual("body must be pascal-case, kebab-case");
117+
});
118+
119+
test('with "never" should report every configured case when none matched', async () => {
120+
const [actual, message] = bodyCase(await parsed.uppercase, "never", [
121+
"pascal-case",
122+
"kebab-case",
123+
]);
124+
expect(actual).toEqual(true);
125+
expect(message).toEqual("body must not be pascal-case, kebab-case");
126+
});

‎@commitlint/rules/src/body-case.ts‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,18 @@ export const bodyCase: SyncRule<TargetCaseType | TargetCaseType[]> = (
2525
return check;
2626
});
2727

28-
const result = checks.some((check) => {
28+
const matches = checks.filter((check) => {
2929
const r = ensureCase(body, check.case);
3030
return negated(check.when) ? !r : r;
3131
});
3232

33-
const list = checks.map((c) => c.case).join(", ");
33+
const result = matches.length > 0;
34+
35+
// A `never` rule fails because a case matched, so report the case(s) that
36+
// did. An `always` rule fails because none matched, so it keeps reporting
37+
// every configured case.
38+
const reported = negated(when) && result ? matches : checks;
39+
const list = reported.map((c) => c.case).join(", ");
3440

3541
return [
3642
negated(when) ? !result : result,

‎@commitlint/rules/src/header-case.test.ts‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,3 +297,40 @@ test('with numeric header should succeed for "always uppercase"', async () => {
297297
const expected = true;
298298
expect(actual).toEqual(expected);
299299
});
300+
301+
test('with "never" should report only the case that matched', async () => {
302+
const [actual, message] = headerCase(await parsed.pascalcase, "never", [
303+
"sentence-case",
304+
"start-case",
305+
]);
306+
expect(actual).toEqual(false);
307+
expect(message).toEqual("header must not be sentence-case");
308+
});
309+
310+
test('with "never" should report every case that matched', async () => {
311+
const [actual, message] = headerCase(await parsed.pascalcase, "never", [
312+
"sentence-case",
313+
"pascal-case",
314+
"start-case",
315+
]);
316+
expect(actual).toEqual(false);
317+
expect(message).toEqual("header must not be sentence-case, pascal-case");
318+
});
319+
320+
test('with "always" should report every configured case when none matched', async () => {
321+
const [actual, message] = headerCase(await parsed.pascalcase, "always", [
322+
"upper-case",
323+
"kebab-case",
324+
]);
325+
expect(actual).toEqual(false);
326+
expect(message).toEqual("header must be upper-case, kebab-case");
327+
});
328+
329+
test('with "never" should report every configured case when none matched', async () => {
330+
const [actual, message] = headerCase(await parsed.pascalcase, "never", [
331+
"upper-case",
332+
"kebab-case",
333+
]);
334+
expect(actual).toEqual(true);
335+
expect(message).toEqual("header must not be upper-case, kebab-case");
336+
});

‎@commitlint/rules/src/header-case.ts‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,18 @@ export const headerCase: SyncRule<TargetCaseType | TargetCaseType[]> = (
2525
return check;
2626
});
2727

28-
const result = checks.some((check) => {
28+
const matches = checks.filter((check) => {
2929
const r = ensureCase(header, check.case);
3030
return negated(check.when) ? !r : r;
3131
});
3232

33-
const list = checks.map((c) => c.case).join(", ");
33+
const result = matches.length > 0;
34+
35+
// A `never` rule fails because a case matched, so report the case(s) that
36+
// did. An `always` rule fails because none matched, so it keeps reporting
37+
// every configured case.
38+
const reported = negated(when) && result ? matches : checks;
39+
const list = reported.map((c) => c.case).join(", ");
3440

3541
return [
3642
negated(when) ? !result : result,

‎@commitlint/rules/src/scope-case.test.ts‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,3 +366,40 @@ test('with object-based configuration should respect "never" when custom delimit
366366
const expected = false;
367367
expect(actual).toEqual(expected);
368368
});
369+
370+
test('with "never" should report only the case that matched', async () => {
371+
const [actual, message] = scopeCase(await parsed.pascalcase, "never", [
372+
"sentence-case",
373+
"start-case",
374+
]);
375+
expect(actual).toEqual(false);
376+
expect(message).toEqual("scope must not be sentence-case");
377+
});
378+
379+
test('with "never" should report every case that matched', async () => {
380+
const [actual, message] = scopeCase(await parsed.pascalcase, "never", [
381+
"sentence-case",
382+
"pascal-case",
383+
"start-case",
384+
]);
385+
expect(actual).toEqual(false);
386+
expect(message).toEqual("scope must not be sentence-case, pascal-case");
387+
});
388+
389+
test('with "always" should report every configured case when none matched', async () => {
390+
const [actual, message] = scopeCase(await parsed.pascalcase, "always", [
391+
"upper-case",
392+
"kebab-case",
393+
]);
394+
expect(actual).toEqual(false);
395+
expect(message).toEqual("scope must be upper-case, kebab-case");
396+
});
397+
398+
test('with "never" should report every configured case when none matched', async () => {
399+
const [actual, message] = scopeCase(await parsed.pascalcase, "never", [
400+
"upper-case",
401+
"kebab-case",
402+
]);
403+
expect(actual).toEqual(true);
404+
expect(message).toEqual("scope must not be upper-case, kebab-case");
405+
});

‎@commitlint/rules/src/scope-case.ts‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,21 @@ export const scopeCase: SyncRule<
3939
const delimiterRegex = new RegExp(delimiterPatterns.join("|"));
4040
const scopeSegments = scope.split(delimiterRegex);
4141

42-
const result = checks.some((check) => {
42+
const matches = checks.filter((check) => {
4343
const r = scopeSegments.every(
4444
(segment) => delimiterRegex.test(segment) || ensureCase(segment, check.case),
4545
);
4646

4747
return negated(check.when) ? !r : r;
4848
});
4949

50-
const list = checks.map((c) => c.case).join(", ");
50+
const result = matches.length > 0;
51+
52+
// A `never` rule fails because a case matched, so report the case(s) that
53+
// did. An `always` rule fails because none matched, so it keeps reporting
54+
// every configured case.
55+
const reported = negated(when) && result ? matches : checks;
56+
const list = reported.map((c) => c.case).join(", ");
5157

5258
return [
5359
negated(when) ? !result : result,

‎@commitlint/rules/src/subject-case.test.ts‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,3 +511,40 @@ test("accepts mixed Latin and Cyrillic lowercase subjects", async () => {
511511

512512
expect(actual).toBe(true);
513513
});
514+
515+
test('with "never" should report only the case that matched', async () => {
516+
const [actual, message] = subjectCase(await parsed.pascalcase, "never", [
517+
"sentence-case",
518+
"start-case",
519+
]);
520+
expect(actual).toEqual(false);
521+
expect(message).toEqual("subject must not be sentence-case");
522+
});
523+
524+
test('with "never" should report every case that matched', async () => {
525+
const [actual, message] = subjectCase(await parsed.pascalcase, "never", [
526+
"sentence-case",
527+
"pascal-case",
528+
"start-case",
529+
]);
530+
expect(actual).toEqual(false);
531+
expect(message).toEqual("subject must not be sentence-case, pascal-case");
532+
});
533+
534+
test('with "always" should report every configured case when none matched', async () => {
535+
const [actual, message] = subjectCase(await parsed.pascalcase, "always", [
536+
"upper-case",
537+
"kebab-case",
538+
]);
539+
expect(actual).toEqual(false);
540+
expect(message).toEqual("subject must be upper-case, kebab-case");
541+
});
542+
543+
test('with "never" should report every configured case when none matched', async () => {
544+
const [actual, message] = subjectCase(await parsed.pascalcase, "never", [
545+
"upper-case",
546+
"kebab-case",
547+
]);
548+
expect(actual).toEqual(true);
549+
expect(message).toEqual("subject must not be upper-case, kebab-case");
550+
});

‎@commitlint/rules/src/subject-case.ts‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,18 @@ export const subjectCase: SyncRule<TargetCaseType | TargetCaseType[]> = (
4242
return check;
4343
});
4444

45-
const result = checks.some((check) => {
45+
const matches = checks.filter((check) => {
4646
const r = ensureCase(subject, check.case);
4747
return negated(check.when) ? !r : r;
4848
});
4949

50-
const list = checks.map((c) => c.case).join(", ");
50+
const result = matches.length > 0;
51+
52+
// A `never` rule fails because a case matched, so report the case(s) that
53+
// did. An `always` rule fails because none matched, so it keeps reporting
54+
// every configured case.
55+
const reported = negated(when) && result ? matches : checks;
56+
const list = reported.map((c) => c.case).join(", ");
5157

5258
return [
5359
negated(when) ? !result : result,

‎@commitlint/rules/src/type-case.test.ts‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,3 +308,40 @@ test('with uppercase scope should fail for "never [uppercase, lowercase]"', asyn
308308
const expected = false;
309309
expect(actual).toEqual(expected);
310310
});
311+
312+
test('with "never" should report only the case that matched', async () => {
313+
const [actual, message] = typeCase(await parsed.pascalcase, "never", [
314+
"sentence-case",
315+
"start-case",
316+
]);
317+
expect(actual).toEqual(false);
318+
expect(message).toEqual("type must not be sentence-case");
319+
});
320+
321+
test('with "never" should report every case that matched', async () => {
322+
const [actual, message] = typeCase(await parsed.pascalcase, "never", [
323+
"sentence-case",
324+
"pascal-case",
325+
"start-case",
326+
]);
327+
expect(actual).toEqual(false);
328+
expect(message).toEqual("type must not be sentence-case, pascal-case");
329+
});
330+
331+
test('with "always" should report every configured case when none matched', async () => {
332+
const [actual, message] = typeCase(await parsed.pascalcase, "always", [
333+
"upper-case",
334+
"kebab-case",
335+
]);
336+
expect(actual).toEqual(false);
337+
expect(message).toEqual("type must be upper-case, kebab-case");
338+
});
339+
340+
test('with "never" should report every configured case when none matched', async () => {
341+
const [actual, message] = typeCase(await parsed.pascalcase, "never", [
342+
"upper-case",
343+
"kebab-case",
344+
]);
345+
expect(actual).toEqual(true);
346+
expect(message).toEqual("type must not be upper-case, kebab-case");
347+
});

0 commit comments

Comments
 (0)