diff --git a/action.yml b/action.yml index b0e5da8..90ff6ec 100644 --- a/action.yml +++ b/action.yml @@ -24,6 +24,10 @@ inputs: description: 'Maximum length of the commit subject line' required: false default: '' + min-body-length: + description: 'Minimum length of the body of the commit message' + required: false + default: '' max-body-line-length: description: 'Maximum length of a line in the body of the commit message' required: false diff --git a/dist/index.js b/dist/index.js index b308232..d70352b 100644 --- a/dist/index.js +++ b/dist/index.js @@ -29029,6 +29029,7 @@ class Inputs { this.allowOneLiners = values.allowOneLiners; this.additionalVerbs = values.additionalVerbs; this.maxSubjectLength = values.maxSubjectLength; + this.minBodyLength = values.minBodyLength; this.maxBodyLineLength = values.maxBodyLineLength; this.enforceSignOff = values.enforceSignOff; this.validatePullRequestCommits = values.validatePullRequestCommits; @@ -29078,7 +29079,7 @@ function parseIntOrInfinity(text) { return parseInt(text, 10); } function parseInputs(rawInputs) { - const { additionalVerbsInput = '', pathToAdditionalVerbsInput = '', allowOneLinersInput = '', maxSubjectLengthInput = '', maxBodyLineLengthInput = '', enforceSignOffInput = '', validatePullRequestCommitsInput = '', skipBodyCheckInput = '', ignoreMergeCommitsInput = '', ignorePatternsInput = '', } = rawInputs; + const { additionalVerbsInput = '', pathToAdditionalVerbsInput = '', allowOneLinersInput = '', maxSubjectLengthInput = '', minBodyLengthInput = '', maxBodyLineLengthInput = '', enforceSignOffInput = '', validatePullRequestCommitsInput = '', skipBodyCheckInput = '', ignoreMergeCommitsInput = '', ignorePatternsInput = '', } = rawInputs; const additionalVerbs = new Set(); const hasAdditionalVerbsInput = additionalVerbsInput.length > 0; if (additionalVerbsInput) { @@ -29110,6 +29111,13 @@ function parseInputs(rawInputs) { return new MaybeInputs(null, 'Unexpected value for max-subject-line-length. ' + `Expected a number or nothing, got ${maxSubjectLengthInput}`); } + const minBodyLength = !minBodyLengthInput + ? 0 + : parseInt(minBodyLengthInput, 10); + if (Number.isNaN(minBodyLength)) { + return new MaybeInputs(null, 'Unexpected value for min-body-length. ' + + `Expected a number or nothing, got ${minBodyLengthInput}`); + } const maxBodyLineLength = !maxBodyLineLengthInput ? 72 : parseIntOrInfinity(maxBodyLineLengthInput); @@ -29158,6 +29166,7 @@ function parseInputs(rawInputs) { allowOneLiners, additionalVerbs, maxSubjectLength, + minBodyLength, maxBodyLineLength, enforceSignOff, validatePullRequestCommits, @@ -29380,6 +29389,15 @@ function checkBody(subject, bodyLines, inputs) { errors.push('Unexpected empty body'); return errors; } + // Minimum character body length + if (inputs.minBodyLength) { + const bodyLength = bodyLines.join(' ').length; + if (bodyLength < inputs.minBodyLength) { + errors.push(`The body must contain at least ${inputs.minBodyLength} characters. ` + + `The body contains ${bodyLength} characters.`); + return errors; + } + } for (const [i, line] of bodyLines.entries()) { if (urlLineRe.test(line) || linkDefinitionRe.test(line)) { continue; @@ -29574,6 +29592,9 @@ async function runWithExceptions() { const maxSubjectLengthInput = core.getInput('max-subject-line-length', { required: false, }); + const minBodyLengthInput = core.getInput('min-body-length', { + required: false, + }); const maxBodyLineLengthInput = core.getInput('max-body-line-length', { required: false, }); @@ -29597,6 +29618,7 @@ async function runWithExceptions() { pathToAdditionalVerbsInput, allowOneLinersInput, maxSubjectLengthInput, + minBodyLengthInput, maxBodyLineLengthInput, enforceSignOffInput, validatePullRequestCommitsInput, diff --git a/src/__tests__/input.test.ts b/src/__tests__/input.test.ts index 6c05e21..e9dc715 100644 --- a/src/__tests__/input.test.ts +++ b/src/__tests__/input.test.ts @@ -35,6 +35,7 @@ it('parses the inputs.', () => { pathToAdditionalVerbsInput: pathToVerbs, allowOneLinersInput: 'true', maxSubjectLengthInput: '90', + minBodyLengthInput: '120', maxBodyLineLengthInput: '100', enforceSignOffInput: 'true', validatePullRequestCommitsInput: 'true', @@ -56,6 +57,7 @@ it('parses the inputs.', () => { ); expect(inputs.allowOneLiners).toBeTruthy(); expect(inputs.maxSubjectLength).toEqual(90); + expect(inputs.minBodyLength).toEqual(120); expect(inputs.maxBodyLineLength).toEqual(100); expect(inputs.enforceSignOff).toBeTruthy(); expect(inputs.validatePullRequestCommits).toBeTruthy(); diff --git a/src/__tests__/inspection.test.ts b/src/__tests__/inspection.test.ts index acffcd7..330c294 100644 --- a/src/__tests__/inspection.test.ts +++ b/src/__tests__/inspection.test.ts @@ -212,6 +212,7 @@ it( allowOneLiners: false, additionalVerbs: new Set('table'), maxSubjectLength: 50, + minBodyLength: 0, maxBodyLineLength: 72, enforceSignOff: false, validatePullRequestCommits: false, @@ -318,6 +319,47 @@ it('reports too long a subject line with custom max length.', () => { ]); }); +it('reports too short a body length.', () => { + const message = + 'Change SomeClass to OtherClass\n' + + '\n' + + 'This replaces the SomeClass with OtherClass in all of the module\n' + + 'since Some class was deprecated.'; + + const inputs = input.parseInputs({minBodyLengthInput: '100'}).mustInputs(); + + const errors = inspection.check(message, inputs); + expect(errors).toEqual([ + `The body must contain at least 100 characters. ` + + `The body contains 97 characters.` + ]); +}); + +it('accepts a body length.', () => { + const message = + 'Change SomeClass to OtherClass\n' + + '\n' + + 'This replaces the SomeClass with OtherClass in all of the module\n' + + 'since Some class was deprecated.'; + + const inputs = input.parseInputs({minBodyLengthInput: '97'}).mustInputs(); + + const errors = inspection.check(message, inputs); + expect(errors).toEqual([]); +}); + +it('accepts a no minimum body length.', () => { + const message = + 'Change SomeClass to OtherClass\n' + + '\n' + + 'This changes SomeClass to OtherClass'; + + const inputs = input.parseInputs({}).mustInputs(); + + const errors = inspection.check(message, inputs); + expect(errors).toEqual([]); +}); + it('reports too long a body line.', () => { const message = 'Change SomeClass to OtherClass\n' + diff --git a/src/input.ts b/src/input.ts index 58732bb..c9036a7 100644 --- a/src/input.ts +++ b/src/input.ts @@ -6,6 +6,7 @@ interface InputValues { allowOneLiners: boolean; additionalVerbs: Set; maxSubjectLength: number; + minBodyLength: number; maxBodyLineLength: number; enforceSignOff: boolean; validatePullRequestCommits: boolean; @@ -19,6 +20,7 @@ export class Inputs implements InputValues { public pathToAdditionalVerbs: string; public allowOneLiners: boolean; public maxSubjectLength: number; + public minBodyLength: number; public maxBodyLineLength: number; public skipBodyCheck: boolean; public validatePullRequestCommits: boolean; @@ -38,6 +40,7 @@ export class Inputs implements InputValues { this.allowOneLiners = values.allowOneLiners; this.additionalVerbs = values.additionalVerbs; this.maxSubjectLength = values.maxSubjectLength; + this.minBodyLength = values.minBodyLength; this.maxBodyLineLength = values.maxBodyLineLength; this.enforceSignOff = values.enforceSignOff; this.validatePullRequestCommits = values.validatePullRequestCommits; @@ -82,6 +85,7 @@ interface RawInputs { pathToAdditionalVerbsInput?: string; allowOneLinersInput?: string; maxSubjectLengthInput?: string; + minBodyLengthInput?: string; maxBodyLineLengthInput?: string; enforceSignOffInput?: string; validatePullRequestCommitsInput?: string; @@ -118,6 +122,7 @@ export function parseInputs(rawInputs: RawInputs): MaybeInputs { pathToAdditionalVerbsInput = '', allowOneLinersInput = '', maxSubjectLengthInput = '', + minBodyLengthInput = '', maxBodyLineLengthInput = '', enforceSignOffInput = '', validatePullRequestCommitsInput = '', @@ -176,6 +181,18 @@ export function parseInputs(rawInputs: RawInputs): MaybeInputs { ); } + const minBodyLength: number = !minBodyLengthInput + ? 0 + : parseInt(minBodyLengthInput, 10); + + if (Number.isNaN(minBodyLength)) { + return new MaybeInputs( + null, + 'Unexpected value for min-body-length. ' + + `Expected a number or nothing, got ${minBodyLengthInput}`, + ); + } + const maxBodyLineLength: number = !maxBodyLineLengthInput ? 72 : parseIntOrInfinity(maxBodyLineLengthInput); @@ -253,6 +270,7 @@ export function parseInputs(rawInputs: RawInputs): MaybeInputs { allowOneLiners, additionalVerbs, maxSubjectLength, + minBodyLength, maxBodyLineLength, enforceSignOff, validatePullRequestCommits, diff --git a/src/inspection.ts b/src/inspection.ts index 06bc63c..fdfc4ed 100644 --- a/src/inspection.ts +++ b/src/inspection.ts @@ -228,6 +228,19 @@ function checkBody( return errors; } + // Minimum character body length + if (inputs.minBodyLength) { + const bodyLength = bodyLines.join(' ').length; + + if (bodyLength < inputs.minBodyLength) { + errors.push( + `The body must contain at least ${inputs.minBodyLength} characters. ` + + `The body contains ${bodyLength} characters.`, + ); + return errors; + } + } + for (const [i, line] of bodyLines.entries()) { if (urlLineRe.test(line) || linkDefinitionRe.test(line)) { continue; diff --git a/src/mainImpl.ts b/src/mainImpl.ts index 2c3cc01..9e29f46 100644 --- a/src/mainImpl.ts +++ b/src/mainImpl.ts @@ -25,6 +25,10 @@ async function runWithExceptions(): Promise { required: false, }); + const minBodyLengthInput = core.getInput('min-body-length', { + required: false, + }); + const maxBodyLineLengthInput = core.getInput('max-body-line-length', { required: false, }); @@ -57,6 +61,7 @@ async function runWithExceptions(): Promise { pathToAdditionalVerbsInput, allowOneLinersInput, maxSubjectLengthInput, + minBodyLengthInput, maxBodyLineLengthInput, enforceSignOffInput, validatePullRequestCommitsInput,