diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index b71a5be..096d1a1 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -27,6 +27,7 @@ jobs: with: additional-verbs: 'chrusimusi, unit-test' path-to-additional-verbs: src/additional-verbs.txt + max-body-line-length: 'Infinity' # test-pr-commits: # runs-on: ubuntu-latest @@ -44,3 +45,4 @@ jobs: - uses: ./ with: allow-one-liners: 'true' + max-body-line-length: 'Infinity' diff --git a/README.md b/README.md index cb12ad9..f6480f2 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,8 @@ You can change the imposed maximum subject length by setting the flag `max-subje max-subject-line-length: '100' ``` +If you want to disable the limit, set it to `Infinity`. + ## Custom line length on the body Similar to the subject line, for terminals and monospaced GUIs it is a good practice to limit the line length of the body to 72 characters. @@ -190,6 +192,8 @@ You can change the imposed maximum line length by setting the flag `max-body-lin max-body-line-length: '100' ``` +If you want to disable the limit, set it to `Infinity`. + ## Skip Body Check For some repositories only the subject matters while the body is allowed to be free-form. diff --git a/dist/index.js b/dist/index.js index 09c506c..b308232 100644 --- a/dist/index.js +++ b/dist/index.js @@ -29058,6 +29058,25 @@ class MaybeInputs { } } exports.MaybeInputs = MaybeInputs; +const infLiteralSet = new Set([ + 'inf', + 'infty', + 'infinity', + '-inf', + '-infty', + '-infinity', +]); +/** + * Parse the `text` as either an integer or `Infinity`. + * + * If the `text` could not be parsed, return a `NaN`. + */ +function parseIntOrInfinity(text) { + if (infLiteralSet.has(text.toLowerCase())) { + return Infinity; + } + return parseInt(text, 10); +} function parseInputs(rawInputs) { const { additionalVerbsInput = '', pathToAdditionalVerbsInput = '', allowOneLinersInput = '', maxSubjectLengthInput = '', maxBodyLineLengthInput = '', enforceSignOffInput = '', validatePullRequestCommitsInput = '', skipBodyCheckInput = '', ignoreMergeCommitsInput = '', ignorePatternsInput = '', } = rawInputs; const additionalVerbs = new Set(); @@ -29086,14 +29105,14 @@ function parseInputs(rawInputs) { } const maxSubjectLength = !maxSubjectLengthInput ? 50 - : parseInt(maxSubjectLengthInput, 10); + : parseIntOrInfinity(maxSubjectLengthInput); if (Number.isNaN(maxSubjectLength)) { return new MaybeInputs(null, 'Unexpected value for max-subject-line-length. ' + `Expected a number or nothing, got ${maxSubjectLengthInput}`); } const maxBodyLineLength = !maxBodyLineLengthInput ? 72 - : parseInt(maxBodyLineLengthInput, 10); + : parseIntOrInfinity(maxBodyLineLengthInput); if (Number.isNaN(maxBodyLineLength)) { return new MaybeInputs(null, 'Unexpected value for max-body-line-length. ' + `Expected a number or nothing, got ${maxBodyLineLengthInput}`); diff --git a/package-lock.json b/package-lock.json index 54743bb..8092f49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@mristin/opinionated-commit-message", - "version": "3.0.1", + "version": "3.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@mristin/opinionated-commit-message", - "version": "3.0.1", + "version": "3.1.1", "license": "MIT", "dependencies": { "@actions/core": "^1.10.1", diff --git a/src/__tests__/input.test.ts b/src/__tests__/input.test.ts index ca78464..6c05e21 100644 --- a/src/__tests__/input.test.ts +++ b/src/__tests__/input.test.ts @@ -43,7 +43,7 @@ it('parses the inputs.', () => { ignorePatternsInput: ` ^Some pattern$ Another pattern - ` + `, }); expect(maybeInputs.error).toBeNull(); @@ -52,7 +52,7 @@ it('parses the inputs.', () => { expect(inputs.hasAdditionalVerbsInput).toBeTruthy(); expect(inputs.pathToAdditionalVerbs).toEqual(pathToVerbs); expect(inputs.additionalVerbs).toEqual( - new Set(['rewrap', 'table', 'integrate', 'analyze']) + new Set(['rewrap', 'table', 'integrate', 'analyze']), ); expect(inputs.allowOneLiners).toBeTruthy(); expect(inputs.maxSubjectLength).toEqual(90); @@ -61,8 +61,18 @@ it('parses the inputs.', () => { expect(inputs.validatePullRequestCommits).toBeTruthy(); expect(inputs.skipBodyCheck).toBeTruthy(); expect(inputs.ignoreMergeCommits).toBeFalsy(); - expect(inputs.ignorePatterns).toEqual([ - /^Some pattern$/, - /Another pattern/ - ]); + expect(inputs.ignorePatterns).toEqual([/^Some pattern$/, /Another pattern/]); +}); + +it('parses the Infinity limits.', () => { + const maybeInputs = input.parseInputs({ + maxSubjectLengthInput: 'Infinity', + maxBodyLineLengthInput: 'Infinity', + }); + + expect(maybeInputs.error).toBeNull(); + + const inputs = maybeInputs.mustInputs(); + expect(inputs.maxSubjectLength).toEqual(Infinity); + expect(inputs.maxBodyLineLength).toEqual(Infinity); }); diff --git a/src/__tests__/inspection.test.ts b/src/__tests__/inspection.test.ts index f9059d8..acffcd7 100644 --- a/src/__tests__/inspection.test.ts +++ b/src/__tests__/inspection.test.ts @@ -2,9 +2,7 @@ import {Inputs} from '../input'; import * as input from '../input'; import * as inspection from '../inspection'; -const defaultInputs: input.Inputs = input - .parseInputs({}) - .mustInputs(); +const defaultInputs: input.Inputs = input.parseInputs({}).mustInputs(); it('reports no errors on correct multi-line message.', () => { const message = @@ -29,9 +27,7 @@ it('reports no errors on OK multi-line message with allowed one-liners.', () => }); it('reports no errors on OK single-line message with allowed one-liners.', () => { - const inputs = input - .parseInputs({allowOneLinersInput: 'true'}) - .mustInputs(); + const inputs = input.parseInputs({allowOneLinersInput: 'true'}).mustInputs(); const message = 'Change SomeClass to OtherClass'; @@ -54,7 +50,7 @@ it('reports too few lines with disallowed one-liners.', () => { const message = 'Change SomeClass to OtherClass'; const errors = inspection.check(message, defaultInputs); expect(errors).toEqual([ - 'Expected at least three lines (subject, empty, body), but got: 1' + 'Expected at least three lines (subject, empty, body), but got: 1', ]); }); @@ -86,15 +82,13 @@ it('reports missing body with disallowed one-liners.', () => { }); it('reports missing body with allowed one-liners.', () => { - const inputs = input - .parseInputs({allowOneLinersInput: 'true'}) - .mustInputs(); + const inputs = input.parseInputs({allowOneLinersInput: 'true'}).mustInputs(); const message = 'Change SomeClass to OtherClass\n'; const errors = inspection.check(message, inputs); expect(errors).toEqual([ 'Expected at least three lines (subject, empty, body) ' + - 'in a multi-line message, but got: 2' + 'in a multi-line message, but got: 2', ]); }); @@ -108,7 +102,7 @@ it('reports on missing empty line between subject and body.', () => { const errors = inspection.check(message, defaultInputs); expect(errors).toEqual([ 'Expected an empty line between the subject and the body, ' + - 'but got a second line of length: 3' + 'but got a second line of length: 3', ]); }); @@ -121,7 +115,7 @@ it('reports the subject starting with a non-word.', () => { 'consisting of letters and possibly dashes in-between, ' + 'but the subject was: "ABC12". ' + 'Please re-write the subject so that it starts ' + - 'with a verb in imperative mood.' + 'with a verb in imperative mood.', ]); }); @@ -136,7 +130,7 @@ it('reports the subject starting with a non-capitalized word.', () => { expect(errors).toEqual([ 'The subject must start with a capitalized word, ' + 'but the current first word is: "change". ' + - 'Please capitalize to: "Change".' + 'Please capitalize to: "Change".', ]); }); @@ -165,9 +159,9 @@ it( 'in a file given as "path-to-additional-verbs" input to ' + 'your GitHub action (currently no whitelist file was specified). ' + 'Please check the whitelist and either change the first word ' + - 'of the subject or whitelist the verb.' + 'of the subject or whitelist the verb.', ]); - } + }, ); it( @@ -177,7 +171,7 @@ it( const inputs = input .parseInputs({ additionalVerbsInput: 'table', - allowOneLinersInput: 'false' + allowOneLinersInput: 'false', }) .mustInputs(); @@ -203,9 +197,9 @@ it( 'as "path-to-additional-verbs" input to your GitHub action ' + '(currently no whitelist file was specified). Please check the ' + 'whitelist and either change the first word of the subject or ' + - 'whitelist the verb.' + 'whitelist the verb.', ]); - } + }, ); it( @@ -248,9 +242,9 @@ it( 'as "path-to-additional-verbs" input to your GitHub action ' + '(currently the file is: /some/path). Please check the ' + 'whitelist and either change the first word of the subject or ' + - 'whitelist the verb.' + 'whitelist the verb.', ]); - } + }, ); it('accepts the subject starting with an additional verb.', () => { @@ -276,21 +270,19 @@ it('reports the subject ending in a dot.', () => { const errors = inspection.check(message, defaultInputs); expect(errors).toEqual([ "The subject must not end with a dot ('.'). " + - 'Please remove the trailing dot(s).' + 'Please remove the trailing dot(s).', ]); }); it('reports an incorrect one-line message with allowed one-liners.', () => { - const inputs = input - .parseInputs({allowOneLinersInput: 'true'}) - .mustInputs(); + const inputs = input.parseInputs({allowOneLinersInput: 'true'}).mustInputs(); const message = 'Change SomeClass to OtherClass.'; const errors = inspection.check(message, inputs); expect(errors).toEqual([ "The subject must not end with a dot ('.'). " + - 'Please remove the trailing dot(s).' + 'Please remove the trailing dot(s).', ]); }); @@ -305,7 +297,7 @@ it('reports too long a subject line.', () => { expect(errors).toEqual([ `The subject exceeds the limit of 50 characters ` + `(got: 70, JSON: "Change SomeClass to OtherClass in order to handle upstream deprecation").` + - 'Please shorten the subject to make it more succinct.' + 'Please shorten the subject to make it more succinct.', ]); }); @@ -316,15 +308,13 @@ it('reports too long a subject line with custom max length.', () => { 'This replaces the SomeClass with OtherClass in all of the module\n' + 'since Some class was deprecated.'; - const inputs = input - .parseInputs({maxSubjectLengthInput: '60'}) - .mustInputs(); + const inputs = input.parseInputs({maxSubjectLengthInput: '60'}).mustInputs(); const errors = inspection.check(message, inputs); expect(errors).toEqual([ `The subject exceeds the limit of 60 characters ` + `(got: 70, JSON: "Change SomeClass to OtherClass in order to handle upstream deprecation").` + - 'Please shorten the subject to make it more succinct.' + 'Please shorten the subject to make it more succinct.', ]); }); @@ -341,7 +331,7 @@ it('reports too long a body line.', () => { '72 characters. The line contains 97 characters: ' + '"This replaces the SomeClass with OtherClass in all of the module since ' + 'Some class was deprecated.". ' + - 'Please reformat the body so that all the lines fit 72 characters.' + 'Please reformat the body so that all the lines fit 72 characters.', ]); }); @@ -352,9 +342,7 @@ it('reports too long a body line with custom max length.', () => { 'This replaces the SomeClass with OtherClass in all of the module ' + 'since Some class was deprecated.'; - const inputs = input - .parseInputs({maxBodyLineLengthInput: '90'}) - .mustInputs(); + const inputs = input.parseInputs({maxBodyLineLengthInput: '90'}).mustInputs(); const errors = inspection.check(message, inputs); expect(errors).toEqual([ @@ -362,7 +350,7 @@ it('reports too long a body line with custom max length.', () => { '90 characters. The line contains 97 characters: ' + '"This replaces the SomeClass with OtherClass in all of the module since ' + 'Some class was deprecated.". ' + - 'Please reformat the body so that all the lines fit 90 characters.' + 'Please reformat the body so that all the lines fit 90 characters.', ]); }); @@ -426,7 +414,7 @@ it('reports duplicate starting word in subject and body.', () => { 'Please make the body more informative by adding more information ' + 'instead of repeating the subject. For example, start by explaining ' + 'the problem that this change is intended to solve or what was ' + - 'previously missing (e.g., "Previously, ....").' + 'previously missing (e.g., "Previously, ....").', ]); }); @@ -438,14 +426,14 @@ it.each([ // Local merge from remote "Merge remote-tracking branch 'origin/remote-branch' into local-branch", // Web UI merge pull request - "Merge pull request #11 from acme-corp/the-project" -])('ignores merge messages.', (message) => { + 'Merge pull request #11 from acme-corp/the-project', +])('ignores merge messages.', message => { const inputs = new Inputs({ ...defaultInputs, ignoreMergeCommits: true, // Ensure all messages would fail if not for ignoring merge commits. maxSubjectLength: 1, - }) + }); const errors = inspection.check(message, inputs); expect(errors).toEqual([]); @@ -457,12 +445,13 @@ it('ignores messages with given pattern.', () => { ignorePatterns: [/\[ALWAYS VALID]/], // Ensure all messages would fail if not for the ignore pattern. maxSubjectLength: 1, - }) + }); -const message = 'Change SomeClass to OtherClass\n' - + '\n' - + 'This replaces the SomeClass with OtherClass in all of the module.\n' - + '[ALWAYS VALID] ' + const message = + 'Change SomeClass to OtherClass\n' + + '\n' + + 'This replaces the SomeClass with OtherClass in all of the module.\n' + + '[ALWAYS VALID] '; const errors = inspection.check(message, inputs); expect(errors).toEqual([]); @@ -499,7 +488,7 @@ ${url}`; 'The line 3 of the message (line 1 of the body) exceeds ' + 'the limit of 72 characters. The line contains 92 characters: ' + `"This ${long} patch does something with the URL.". ` + - 'Please reformat the body so that all the lines fit 72 characters.' + 'Please reformat the body so that all the lines fit 72 characters.', ]); }); @@ -539,14 +528,12 @@ The ${long} line is too long.`; 'The line 7 of the message (line 5 of the body) exceeds ' + 'the limit of 72 characters. The line contains 74 characters: ' + `"The ${long} line is too long.". ` + - 'Please reformat the body so that all the lines fit 72 characters.' + 'Please reformat the body so that all the lines fit 72 characters.', ]); }); it('accepts the valid body when enforcing the sign-off.', () => { - const inputs = input - .parseInputs({enforceSignOffInput: 'true'}) - .mustInputs(); + const inputs = input.parseInputs({enforceSignOffInput: 'true'}).mustInputs(); const message = `Do something @@ -565,9 +552,7 @@ Signed-off-by: Somebody Else }); it('rejects invalid sign-offs.', () => { - const inputs = input - .parseInputs({enforceSignOffInput: 'true'}) - .mustInputs(); + const inputs = input.parseInputs({enforceSignOffInput: 'true'}).mustInputs(); const message = `Do something @@ -586,6 +571,6 @@ Signed-off-by: Random Developer const errors = inspection.check(message, inputs); expect(errors).toEqual([ - "The body does not contain any 'Signed-off-by: ' line. Did you sign off the commit with `git commit --signoff`?" + "The body does not contain any 'Signed-off-by: ' line. Did you sign off the commit with `git commit --signoff`?", ]); }); diff --git a/src/__tests__/main.test.ts b/src/__tests__/main.test.ts index 641c6ef..4aa57a9 100644 --- a/src/__tests__/main.test.ts +++ b/src/__tests__/main.test.ts @@ -36,7 +36,7 @@ it('considers additional verbs.', async () => { jest .spyOn(core, 'getInput') .mockImplementation(name => - name === 'additional-verbs' ? 'rewrap,table' : '' + name === 'additional-verbs' ? 'rewrap,table' : '', ); await mainImpl.run(); @@ -56,7 +56,7 @@ it('considers additional verbs from path.', () => { jest .spyOn(core, 'getInput') .mockImplementation(name => - name === 'path-to-additional-verbs' ? pathToVerbs : '' + name === 'path-to-additional-verbs' ? pathToVerbs : '', ); jest.spyOn(fs, 'existsSync').mockImplementation(path => path === pathToVerbs); @@ -73,9 +73,7 @@ it('considers additional verbs from path.', () => { }); it('considers allow-one-liners.', async () => { - jest - .spyOn(commitMessages, 'retrieve') - .mockResolvedValue(['Do something']); + jest.spyOn(commitMessages, 'retrieve').mockResolvedValue(['Do something']); jest.spyOn(core, 'setFailed'); @@ -96,7 +94,7 @@ it('considers skip-body-check.', async () => { '\n' + 'Change SomeClass to OtherClass.' + 'This replaces the SomeClass with OtherClass in all of the module ' + - 'since Some class was deprecated.' + 'since Some class was deprecated.', ]); jest.spyOn(core, 'setFailed'); @@ -117,7 +115,7 @@ it('formats properly no error message.', async () => { 'Change SomeClass to OtherClass\n' + '\n' + 'This replaces the SomeClass with OtherClass in all of the module \n' + - 'since Some class was deprecated.' + 'since Some class was deprecated.', ]); jest.spyOn(core, 'setFailed'); @@ -131,7 +129,7 @@ it('formats properly errors on a single message.', async () => { jest .spyOn(commitMessages, 'retrieve') .mockResolvedValue([ - 'change SomeClass to OtherClass\n\nSomeClass with OtherClass' + 'change SomeClass to OtherClass\n\nSomeClass with OtherClass', ]); jest.spyOn(core, 'setFailed'); @@ -148,7 +146,7 @@ it('formats properly errors on a single message.', async () => { 'The original message was:\n' + 'change SomeClass to OtherClass\n' + '\n' + - 'SomeClass with OtherClass\n' + 'SomeClass with OtherClass\n', ); }); @@ -157,7 +155,7 @@ it('formats properly errors on two messages.', async () => { .spyOn(commitMessages, 'retrieve') .mockResolvedValue([ `change SomeClass to OtherClass\n\nDo something`, - 'Change other subject\n\nChange body' + 'Change other subject\n\nChange body', ]); jest.spyOn(core, 'setFailed'); @@ -180,6 +178,6 @@ it('formats properly errors on two messages.', async () => { 'is intended to solve or what was previously missing ' + '(e.g., "Previously, ....").\n' + 'The original message was:\n' + - 'Change other subject\n\nChange body\n' + 'Change other subject\n\nChange body\n', ); }); diff --git a/src/input.ts b/src/input.ts index 572ffb3..58732bb 100644 --- a/src/input.ts +++ b/src/input.ts @@ -90,6 +90,28 @@ interface RawInputs { ignorePatternsInput?: string; } +const infLiteralSet = new Set([ + 'inf', + 'infty', + 'infinity', + '-inf', + '-infty', + '-infinity', +]); + +/** + * Parse the `text` as either an integer or `Infinity`. + * + * If the `text` could not be parsed, return a `NaN`. + */ +function parseIntOrInfinity(text: string): number { + if (infLiteralSet.has(text.toLowerCase())) { + return Infinity; + } + + return parseInt(text, 10); +} + export function parseInputs(rawInputs: RawInputs): MaybeInputs { const { additionalVerbsInput = '', @@ -144,7 +166,7 @@ export function parseInputs(rawInputs: RawInputs): MaybeInputs { const maxSubjectLength: number = !maxSubjectLengthInput ? 50 - : parseInt(maxSubjectLengthInput, 10); + : parseIntOrInfinity(maxSubjectLengthInput); if (Number.isNaN(maxSubjectLength)) { return new MaybeInputs( @@ -156,7 +178,7 @@ export function parseInputs(rawInputs: RawInputs): MaybeInputs { const maxBodyLineLength: number = !maxBodyLineLengthInput ? 72 - : parseInt(maxBodyLineLengthInput, 10); + : parseIntOrInfinity(maxBodyLineLengthInput); if (Number.isNaN(maxBodyLineLength)) { return new MaybeInputs(