From 331579a8796af901b5e5103c44fedf1cb3a2f661 Mon Sep 17 00:00:00 2001 From: Marco Pasqualetti <24919330+marcalexiei@users.noreply.github.com> Date: Thu, 25 Jan 2024 11:44:45 +0100 Subject: [PATCH] feat(rules): add header-trim rule (#3199) (#3871) --- @commitlint/config-conventional/index.js | 1 + @commitlint/rules/src/header-trim.test.ts | 77 +++++++++++++++++++++++ @commitlint/rules/src/header-trim.ts | 26 ++++++++ @commitlint/rules/src/index.ts | 2 + @commitlint/types/src/rules.ts | 1 + docs/reference-rules.md | 5 ++ 6 files changed, 112 insertions(+) create mode 100644 @commitlint/rules/src/header-trim.test.ts create mode 100644 @commitlint/rules/src/header-trim.ts diff --git a/@commitlint/config-conventional/index.js b/@commitlint/config-conventional/index.js index a2e917d344..9a0a1d2507 100644 --- a/@commitlint/config-conventional/index.js +++ b/@commitlint/config-conventional/index.js @@ -6,6 +6,7 @@ module.exports = { 'footer-leading-blank': [1, 'always'], 'footer-max-line-length': [2, 'always', 100], 'header-max-length': [2, 'always', 100], + 'header-trim': [2, 'always'], 'subject-case': [ 2, 'never', diff --git a/@commitlint/rules/src/header-trim.test.ts b/@commitlint/rules/src/header-trim.test.ts new file mode 100644 index 0000000000..6a54c97813 --- /dev/null +++ b/@commitlint/rules/src/header-trim.test.ts @@ -0,0 +1,77 @@ +import parse from '@commitlint/parse'; +import {Commit} from '@commitlint/types'; +import {headerTrim} from './header-trim'; + +const messages = { + correct: 'test: subject', + + whitespaceStart: ' test: subject', + whitespaceEnd: 'test: subject ', + whitespaceSurround: ' test: subject ', + + tabStart: '\t\ttest: subject', + tabEnd: 'test: subject\t\t', + tabSurround: '\t\ttest: subject\t', + + mixStart: '\t\ttest: subject', + mixEnd: 'test: subject\t\t', + mixSurround: '\t \ttest: subject \t \t', +}; + +const parsed = Object.entries(messages).reduce((_parsed, [key, message]) => { + _parsed[key] = parse(message); + return _parsed; +}, {}) as Record>; + +test('should succeed when header is not surrounded by whitespace', async () => { + const result = headerTrim(await parsed.correct); + expect(result).toEqual(expect.arrayContaining([true])); +}); + +( + [ + ['mixed whitespace', parsed.mixStart], + ['whitespace', parsed.whitespaceStart], + ['tab', parsed.tabStart], + ] as const +).forEach(([desc, commit]) => { + test(`should fail with ${desc}`, async () => { + const result = headerTrim(await commit); + expect(result).toEqual( + expect.arrayContaining([false, 'header must not start with whitespace']) + ); + }); +}); + +( + [ + ['mixed whitespace', parsed.mixEnd], + ['whitespace', parsed.whitespaceEnd], + ['tab', parsed.tabEnd], + ] as const +).forEach(([desc, commit]) => { + test(`should fail when ends with ${desc}`, async () => { + const result = headerTrim(await commit); + expect(result).toEqual( + expect.arrayContaining([false, 'header must not end with whitespace']) + ); + }); +}); + +( + [ + ['mixed whitespace', parsed.mixSurround], + ['whitespace', parsed.whitespaceSurround], + ['tab', parsed.tabSurround], + ] as const +).forEach(([desc, commit]) => { + test(`should fail when surrounded by ${desc}`, async () => { + const result = headerTrim(await commit); + expect(result).toEqual( + expect.arrayContaining([ + false, + 'header must not be surrounded by whitespace', + ]) + ); + }); +}); diff --git a/@commitlint/rules/src/header-trim.ts b/@commitlint/rules/src/header-trim.ts new file mode 100644 index 0000000000..def49b3b6c --- /dev/null +++ b/@commitlint/rules/src/header-trim.ts @@ -0,0 +1,26 @@ +import message from '@commitlint/message'; +import {SyncRule} from '@commitlint/types'; + +export const headerTrim: SyncRule = (parsed) => { + const {header} = parsed; + + const startsWithWhiteSpace = header !== header.trimStart(); + const endsWithWhiteSpace = header !== header.trimEnd(); + + switch (true) { + case startsWithWhiteSpace && endsWithWhiteSpace: + return [ + false, + message(['header', 'must not be surrounded by whitespace']), + ]; + + case startsWithWhiteSpace: + return [false, message(['header', 'must not start with whitespace'])]; + + case endsWithWhiteSpace: + return [false, message(['header', 'must not end with whitespace'])]; + + default: + return [true]; + } +}; diff --git a/@commitlint/rules/src/index.ts b/@commitlint/rules/src/index.ts index af8ea6a544..211fcb7b49 100644 --- a/@commitlint/rules/src/index.ts +++ b/@commitlint/rules/src/index.ts @@ -12,6 +12,7 @@ import {footerMaxLineLength} from './footer-max-line-length'; import {footerMinLength} from './footer-min-length'; import {headerCase} from './header-case'; import {headerFullStop} from './header-full-stop'; +import {headerTrim} from './header-trim'; import {headerMaxLength} from './header-max-length'; import {headerMinLength} from './header-min-length'; import {referencesEmpty} from './references-empty'; @@ -51,6 +52,7 @@ export default { 'header-full-stop': headerFullStop, 'header-max-length': headerMaxLength, 'header-min-length': headerMinLength, + 'header-trim': headerTrim, 'references-empty': referencesEmpty, 'scope-case': scopeCase, 'scope-empty': scopeEmpty, diff --git a/@commitlint/types/src/rules.ts b/@commitlint/types/src/rules.ts index d0df4d0402..efca30e23c 100644 --- a/@commitlint/types/src/rules.ts +++ b/@commitlint/types/src/rules.ts @@ -105,6 +105,7 @@ export type RulesConfig = { 'header-full-stop': RuleConfig; 'header-max-length': LengthRuleConfig; 'header-min-length': LengthRuleConfig; + 'header-trim': RuleConfig; 'references-empty': RuleConfig; 'scope-case': CaseRuleConfig; 'scope-empty': RuleConfig; diff --git a/docs/reference-rules.md b/docs/reference-rules.md index 8622084dee..ac0b824d43 100644 --- a/docs/reference-rules.md +++ b/docs/reference-rules.md @@ -212,6 +212,11 @@ Infinity 0 ``` +#### header-trim + +- **condition**: `header` must not have initial and / or trailing whitespaces +- **rule**: `always` + #### references-empty - **condition**: `references` has at least one entry